[
  {
    "path": ".codespellrc",
    "content": "[codespell]\ncheck-filenames =\nbuiltin = clear,rare,usage,informal\nskip = */.git,*/cmake-build-*,*/.idea,*/completions,*/presets,*/screenshots,*/tests,*/3rdparty,*/logo/ascii,./src/detection/gpu/asahi_drm.h\nignore-words-list = iterm,compiletime,unknwn,pengwin,siduction,master,slave,sur,doas,conexant,ags,bu\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nend_of_line = lf\nindent_style = space\ntab_width = 4\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\ntrim_trailing_whitespace = false\n\n[*.txt]\ninsert_final_newline = false\n\n[*.yml]\ntab_width = 2\n"
  },
  {
    "path": ".gitattributes",
    "content": "*.h linguist-language=c\n*.c linguist-language=c\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: LinusDierheimer\nko_fi: carterli\ncustom: https://paypal.me/zhangsongcui\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report_crash.yml",
    "content": "name: Crash Bug Report\ndescription: If fastfetch crashes or freezes\ntitle: \"[BUG] \"\nlabels: [\"bug\", \"crash\", \"triage\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to fill out this bug report! We will try hard to solve the issue.\n        However since platforms and hardwares vary greatly, it can be hard to find the root cause of an issue.\n        Providing the following information may help us greatly. Thanks in advance!\n  - type: checkboxes\n    attributes:\n      label: Read the FAQ first\n      description: Please check if the issue is already covered in the FAQ.\n      options:\n        - label: I have checked the FAQ but the issue is not covered\n          required: true\n  - type: checkboxes\n    attributes:\n      label: Not a known issue\n      description: Please check if the issue is already known and being worked on.\n      options:\n        - label: I have checked the existing issues but the issue is not covered\n          required: true\n        - label: My issue is not about crashing on Fedora and KDE 6.6\n          required: true\n  - type: markdown\n    attributes:\n      value: \"### General description of the bug\"\n  - type: textarea\n    attributes:\n      label: Description\n      description: A clear and concise description of what the bug is.\n      placeholder: I was trying to [...] but [...]\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Version used\n      description: Fastfetch version used. Please use the latest version (found in the [releases](https://github.com/fastfetch-cli/fastfetch/releases)) if possible.\n      placeholder: Result of `fastfetch --version`\n    validations:\n      required: true\n  - type: dropdown\n    attributes:\n      label: Bug prevalence\n      description: How often does the bug occur?\n      options:\n        -\n        - Always\n        - Sometimes\n        - Rarely\n        - Once\n        - Other\n    validations:\n      required: true\n  - type: dropdown\n    attributes:\n      label: Regression\n      description: Did it work in an older version?\n      options:\n        -\n        - Not sure\n        - 'Yes'\n        - 'No'\n    validations:\n      required: true\n  - type: dropdown\n    attributes:\n      label: Installation\n      description: Where did you install fastfetch from?\n      options:\n        -\n        - GitHub Releases\n        - GitHub Actions (nightly)\n        - Built from source\n        - Package manager\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Package manager\n      description: Which package manager did you use if applicable?\n      placeholder: e.g. `apt`, `pacman`, `brew`, `scoop`\n  - type: markdown\n    attributes:\n      value: '### Often helpful information'\n  - type: textarea\n    attributes:\n      label: Screenshots\n      description: If applicable, add screenshots to help explain your problem.\n  - type: textarea\n    attributes:\n      label: Configuration\n      description: If applicable, paste your configuration file here.\n      placeholder: cat ~/.config/fastfetch/config.jsonc\n      render: jsonc\n  - type: markdown\n    attributes:\n      value: |\n        Paste the stacktrace here. You may get it with:\n\n        ```shell\n        # You may need Ctrl+C to stop the process if it freezes\n        gdb -q -ex 'set confirm off' -ex run -ex 'bt full' -ex quit --args /path/to/fastfetch\n        ```\n\n        If you are able to identify which module crashed, the strace can be helpful too\n\n        ```shell\n        strace /path/to/fastfetch --multithreading false -s {MODULE} --pipe\n        ```\n\n        If you cannot do the instructions above, please upload the core dump file if available.\n  - type: textarea\n    attributes:\n      label: Stacktrace\n      description: Paste the stacktrace or core dump file here.\n      render: text\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report_general.yml",
    "content": "name: General Bug Report\ndescription: If something is not working as expected (wrong output, missing info, etc)\ntitle: \"[BUG] \"\nlabels: [\"bug\", \"triage\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to fill out this bug report! We will try hard to solve the issue.\n        However since platforms and hardwares vary greatly, it can be hard to find the root cause of an issue.\n        Providing the following information may help us greatly. Thanks in advance!\n  - type: checkboxes\n    attributes:\n      label: Read the FAQ first\n      description: Please check if the issue is already covered in the FAQ.\n      options:\n        - label: I have checked the FAQ but the issue is not covered\n          required: true\n  - type: markdown\n    attributes:\n      value: \"### General description of the bug\"\n  - type: textarea\n    attributes:\n      label: Description\n      description: A clear and concise description of what the bug is.\n      placeholder: I was trying to [...] but [...]\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Version used\n      description: Fastfetch version used. Please use the latest version (found in the [releases](https://github.com/fastfetch-cli/fastfetch/releases)) if possible.\n      placeholder: Result of `fastfetch --version`\n    validations:\n      required: true\n  - type: dropdown\n    attributes:\n      label: Bug prevalence\n      description: How often does the bug occur?\n      options:\n        -\n        - Always\n        - Sometimes\n        - Rarely\n        - Once\n        - Other\n    validations:\n      required: true\n  - type: dropdown\n    attributes:\n      label: Regression\n      description: Did it work in an older version?\n      options:\n        -\n        - Not sure\n        - 'Yes'\n        - 'No'\n    validations:\n      required: true\n  - type: dropdown\n    attributes:\n      label: Installation\n      description: Where did you install fastfetch from?\n      options:\n        -\n        - GitHub Releases\n        - GitHub Actions (nightly)\n        - Built from source\n        - Package manager\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Package manager\n      description: Which package manager did you use if applicable?\n      placeholder: e.g. `apt`, `pacman`, `brew`, `scoop`\n  - type: markdown\n    attributes:\n      value: '### Often helpful information'\n  - type: textarea\n    attributes:\n      label: Screenshots\n      description: If applicable, add screenshots to help explain your problem.\n  - type: textarea\n    attributes:\n      label: Configuration\n      description: If applicable, paste your configuration file here.\n      placeholder: cat ~/.config/fastfetch/config.jsonc\n      render: jsonc\n  - type: textarea\n    attributes:\n      label: System information\n      description: Output of `fastfetch -c all.jsonc --stat --format json`\n      placeholder: |\n        Note that this output will contain you public IP.\n        If it is not relevant for the issue, feel free to remove it before uploading.\n      render: jsonc\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Features built-in\n      description: Output of `fastfetch --list-features`\n      render: text\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report_logo.yml",
    "content": "name: Logo Bug Report\ndescription: If my image logo is not displayed correctly\ntitle: \"[BUG] \"\nlabels: [\"bug\", \"logo\", \"triage\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to fill out this bug report! We will try hard to solve the issue.\n        However since platforms and hardwares vary greatly, it can be hard to find the root cause of an issue.\n        Providing the following information may help us greatly. Thanks in advance!\n  - type: checkboxes\n    attributes:\n      label: Read the FAQ first\n      description: Please check if the issue is already covered in the FAQ.\n      options:\n        - label: I have checked the FAQ but the issue is not covered\n          required: true\n  - type: markdown\n    attributes:\n      value: \"### General description of the bug\"\n  - type: textarea\n    attributes:\n      label: Description\n      description: A clear and concise description of what the bug is.\n      placeholder: I was trying to [...] but [...]\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Version used\n      description: Fastfetch version used. Please use the latest version (found in the [releases](https://github.com/fastfetch-cli/fastfetch/releases)) if possible.\n      placeholder: Result of `fastfetch --version`\n    validations:\n      required: true\n  - type: dropdown\n    attributes:\n      label: Bug prevalence\n      description: How often does the bug occur?\n      options:\n        -\n        - Always\n        - Sometimes\n        - Rarely\n        - Once\n        - Other\n    validations:\n      required: true\n  - type: dropdown\n    attributes:\n      label: Regression\n      description: Did it work in an older version?\n      options:\n        -\n        - Not sure\n        - 'Yes'\n        - 'No'\n    validations:\n      required: true\n  - type: dropdown\n    attributes:\n      label: Installation\n      description: Where did you install fastfetch from?\n      options:\n        -\n        - GitHub Releases\n        - GitHub Actions (nightly)\n        - Built from source\n        - Package manager\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Package manager\n      description: Which package manager did you use if applicable?\n      placeholder: e.g. `apt`, `pacman`, `brew`, `scoop`\n  - type: markdown\n    attributes:\n      value: '### Often helpful information'\n  - type: textarea\n    attributes:\n      label: Screenshots\n      description: If applicable, add screenshots to help explain your problem.\n  - type: textarea\n    attributes:\n      label: Configuration\n      description: If applicable, paste your configuration file here.\n      placeholder: cat ~/.config/fastfetch/config.jsonc\n      render: jsonc\n  - type: markdown\n    attributes:\n      value: |\n        #### If an image or logo didn't show\n\n        Please make sure your terminal supports the image protocol you used.\n        Note that GNOME Terminal doesn't support any image protocols as of now\n\n        Some tips:\n        1. Try `fastfetch --show-errors` to see if there are any errors.\n        2. Try `fastfetch --logo-width {WIDTH} --logo-height {HEIGHT}`. Some protocols may require a image size being set.\n  - type: input\n    attributes:\n      label: Image protocol\n      description: The image protocol you used\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Terminal\n      description: The terminal you used\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Image tried\n      description: Upload the image file, or paste the image URL here\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Error message\n      description: Error message printed by `fastfetch -s none --show-errors`, if any\n  - type: textarea\n    attributes:\n      label: Features built-in\n      description: Output of `fastfetch --list-features`\n      render: text\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: Feature Request\ndescription: Suggest an idea for this project\ntitle: \"[FEAT] \"\nlabels: [\"enhancement\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Fastfetch is a system information tool. We only accept hardware or system level software feature requests.\n        For most personal uses, I recommend using `Command` module to detect it yourself, which can be used to grab output from a custom shell script:\n\n        ```jsonc\n        // This module shows the git version\n        {\n          \"modules\": [\n            {\n              \"type\": \"command\",\n              \"key\": \"Git\",\n              \"text\": \"git version\",\n              \"format\": \"{~12}\" // cut the first 12 characters\n            }\n          ]\n        }\n        ```\n  - type: textarea\n    attributes:\n      label: Description\n      description: A clear and concise description of what the feature is.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Motivation\n      description: Why do you want this feature? Why doesn't `Command` module suffice for you?\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Additional context\n      description: Add any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/logo_request.yml",
    "content": "name: Logo Request\ndescription: Request a new ASCII logo for your favorite distro\ntitle: \"[LOGO] \"\nlabels: [\"logo request\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Tip: You can display a logo in fastfetch without adding it to fastfetch's official repo.\n        For highly customized, personal logos, we recommend keeping them locally.\n        Please refer to https://github.com/fastfetch-cli/fastfetch/wiki/Migrate-Neofetch-Logo-To-Fastfetch\n  - type: textarea\n    attributes:\n      label: OS\n      description: Paste the contents of `/etc/os-release` and `/etc/lsb-release` here. If neither file exists, describe how to identify the distro.\n      placeholder: cat /etc/os-release\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Distro Website\n      description: To help prevent spam and verify the request, a distro website is required, and a downloadable ISO must be available on that site.\n      placeholder: https://example.com\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: ASCII Art\n      description: The ASCII logo should not take up too much space (smaller than 50x20 characters, W x H). Please also include the color codes if they are not available in `os-release`.\n      placeholder: Paste ASCII art here\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Original Image URL\n      description: If the ASCII art is based on an image, please provide a link to the original image file.\n      placeholder: Image URL from the distro website mentioned above\n  - type: checkboxes\n    attributes:\n      label: Checklist\n      options:\n        - label: The ASCII art is smaller than 50x20 characters (W x H).\n          required: true\n        - label: The ASCII art includes color codes, or the color codes are available in `os-release`.\n          required: true\n        - label: The ASCII art has no internal padding (spaces at the start and/or end of lines).\n          required: true\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "## Summary\n\n<!-- Briefly describe what this PR does. -->\n\n## Related issue (required for new logos for new distros)\n\n<!--\nIf this PR adds a new logo, it MUST be linked to an existing \"Logo Request\" issue and has the requests fulfilled.\n\nUse one of the following formats so GitHub links it properly:\n- Closes #1234\n- Fixes #1234\n- Resolves #1234\n\nPRs that add logos without an associated Logo Request issue will not be accepted.\n-->\n\nCloses #\n\n## Changes\n\n- \n\n## Checklist\n\n- [ ] I have tested my changes locally.\n"
  },
  {
    "path": ".github/stale.yml",
    "content": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 60\n\n# Number of days of inactivity before a stale issue is closed\ndaysUntilClose: 7\n\n# Issues with these labels will never be considered stale\nexemptLabels:\n  - keepalive\n\n# Label to use when marking an issue as stale\nstaleLabel: stale\n\n# Comment to post when marking an issue as stale. Set to `false` to disable\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed if no further activity occurs. Thank you\n  for your contributions.\n\n# Comment to post when closing a stale issue. Set to `false` to disable\ncloseComment: false\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  - push\n  - pull_request\n\nenv:\n  CMAKE_BUILD_TYPE: ${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }}\n\njobs:\n  spellcheck:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: checkout repository\n        uses: actions/checkout@v6\n\n      - name: Install codespell\n        shell: bash\n        run: |\n          sudo apt-get update || true\n          sudo apt-get install -y codespell\n\n      - name: Run Spellchecker\n        run: codespell\n\n  no-features-test:\n    name: No-features-test\n    runs-on: ubuntu-latest\n    permissions:\n      security-events: write\n      contents: read\n    steps:\n      - name: checkout repository\n        uses: actions/checkout@v6\n\n      - name: uname -a\n        run: uname -a\n\n      - name: configure project\n        run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr . -DENABLE_VULKAN=OFF -DENABLE_WAYLAND=OFF -DENABLE_XCB_RANDR=OFF -DENABLE_XCB=OFF -DENABLE_XRANDR=OFF -DENABLE_X11=OFF -DENABLE_DRM=OFF -DENABLE_DRM_AMDGPU=OFF -DENABLE_GIO=OFF -DENABLE_DCONF=OFF -DENABLE_DBUS=OFF -DENABLE_SQLITE3=OFF -DENABLE_RPM=OFF -DENABLE_IMAGEMAGICK7=OFF -DENABLE_IMAGEMAGICK6=OFF -DENABLE_CHAFA=OFF -DENABLE_ZLIB=OFF -DENABLE_EGL=OFF -DENABLE_GLX=OFF -DENABLE_OPENCL=OFF -DENABLE_FREETYPE=OFF -DENABLE_PULSE=OFF -DENABLE_DDCUTIL=OFF -DENABLE_ELF=OFF -DENABLE_DIRECTX_HEADERS=OFF -DENABLE_THREADS=OFF\n\n      - name: build project\n        run: cmake --build . --target package --verbose -j4\n\n      - name: list features\n        run: ./fastfetch --list-features\n\n      - name: run fastfetch\n        run: time ./fastfetch -c presets/ci.jsonc --stat false\n\n      - name: run fastfetch --format json\n        run: time ./fastfetch -c presets/ci.jsonc --format json\n\n      - name: run flashfetch\n        run: time ./flashfetch\n\n      - name: print dependencies\n        run: ldd fastfetch\n\n      - name: run tests\n        run: ctest --output-on-failure\n\n  linux-hosts:\n    name: Linux-${{ matrix.arch }}\n    runs-on: ${{ matrix.runs-on }}\n    permissions:\n      security-events: write\n      contents: read\n    strategy:\n      matrix:\n        include:\n          - arch: amd64\n            runs-on: ubuntu-22.04\n          - arch: aarch64\n            runs-on: ubuntu-22.04-arm\n    outputs:\n      ffversion: ${{ steps.ffversion.outputs.ffversion }}\n    steps:\n      - name: checkout repository\n        uses: actions/checkout@v6\n\n      - name: uname -a\n        run: uname -a\n\n      - name: cat /etc/os-release\n        run: cat /etc/os-release\n\n      - name: cat /proc/cpuinfo\n        run: cat /proc/cpuinfo\n\n      - name: add gcc-13 repo\n        run: sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test\n\n      - name: install required packages\n        run: sudo apt-get update && sudo apt-get install -y gcc-13 g++-13 libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev libddcutil-dev directx-headers-dev rpm ninja-build\n\n      - name: install linuxbrew packages\n        run: |\n          /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"\n          /home/linuxbrew/.linuxbrew/bin/brew install imagemagick chafa --ignore-dependencies\n\n      - name: Initialize CodeQL\n        if: matrix.arch == 'amd64'\n        uses: github/codeql-action/init@v4\n        with:\n          languages: c\n\n      - name: configure project\n        run: CC=gcc-13 CXX=g++-13 PKG_CONFIG_PATH=/home/linuxbrew/.linuxbrew/lib/pkgconfig:$PKG_CONFIG_PATH cmake -GNinja -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_INSTALL_PREFIX=/usr .\n\n      - name: build project\n        run: cmake --build . --target package --verbose -j4\n\n      - name: perform CodeQL analysis\n        if: matrix.arch == 'amd64'\n        uses: github/codeql-action/analyze@v4\n\n      - name: list features\n        run: ./fastfetch --list-features\n\n      - name: run fastfetch\n        run: time ./fastfetch -c presets/ci.jsonc --stat false\n\n      - name: run fastfetch --format json\n        run: time ./fastfetch -c presets/ci.jsonc --format json\n\n      - name: run flashfetch\n        run: time ./flashfetch\n\n      - name: print dependencies\n        run: ldd fastfetch\n\n      - name: run tests\n        run: ctest --output-on-failure\n\n      - name: get fastfetch version\n        id: ffversion\n        run: echo \"ffversion=$(./fastfetch --version-raw)\" >> $GITHUB_OUTPUT\n\n      - name: polyfill glibc\n        run: |\n          wget https://github.com/CarterLi/polyfill-glibc/releases/download/v0.0.1/polyfill-glibc-${{ matrix.arch }} -O polyfill-glibc\n          chmod +x polyfill-glibc\n          strip fastfetch && ./polyfill-glibc fastfetch --target-glibc=2.17\n          strip flashfetch && ./polyfill-glibc flashfetch --target-glibc=2.17\n          echo 'set(CPACK_PACKAGE_FILE_NAME \"${CPACK_PACKAGE_FILE_NAME}-polyfilled\")' >> CPackConfig.cmake\n          echo 'set(CPACK_PACKAGE_RELOCATABLE OFF)' >> CPackConfig.cmake\n          echo 'set(CPACK_DEBIAN_PACKAGE_DEPENDS \"libc6 (>= 2.17)\")' >> CPackConfig.cmake\n          echo 'set(CPACK_RPM_SPEC_MORE_DEFINE \"%global __os_install_post %{nil}\")' >> CPackConfig.cmake\n          cpack -V\n\n      - name: upload artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: fastfetch-linux-${{ matrix.arch }}\n          path: ./fastfetch-*.*\n\n  linux-i686:\n    name: Linux-i686\n    runs-on: ubuntu-22.04\n    permissions:\n      security-events: write\n      contents: read\n    steps:\n      - name: checkout repository\n        uses: actions/checkout@v6\n\n      - name: uname -a\n        run: uname -a\n\n      - name: cat /etc/os-release\n        run: cat /etc/os-release\n\n      - name: cat /proc/cpuinfo\n        run: cat /proc/cpuinfo\n\n      - name: add gcc-13 repo\n        run: sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test\n\n      - name: install required packages\n        run: sudo apt-get update && sudo apt-get install -y gcc-13 gcc-13-multilib libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev libddcutil-dev rpm ninja-build\n\n      - name: install linuxbrew packages\n        run: |\n          /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"\n          /home/linuxbrew/.linuxbrew/bin/brew install imagemagick chafa --ignore-dependencies\n\n      - name: cmake version\n        run: cmake --version\n\n      - name: configure project\n        run: CC=gcc-13 PKG_CONFIG_PATH=/home/linuxbrew/.linuxbrew/lib/pkgconfig:$PKG_CONFIG_PATH cmake -DCMAKE_C_FLAGS=\"-m32 -march=i686 -mtune=i686\" -DCMAKE_SYSTEM_PROCESSOR_OVERRIDE=i686 -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=i386 -GNinja -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_INSTALL_PREFIX=/usr -DENABLE_DIRECTX_HEADERS=Off .\n\n      - name: build project\n        run: cmake --build . --target package --verbose -j4\n\n      - name: check deb package\n        run: dpkg -I fastfetch-*.deb\n\n      - name: list features\n        run: ./fastfetch --list-features\n\n      - name: run fastfetch\n        run: time ./fastfetch -c presets/ci.jsonc --stat false\n\n      - name: run fastfetch --format json\n        run: time ./fastfetch -c presets/ci.jsonc --format json\n\n      - name: run flashfetch\n        run: time ./flashfetch\n\n      - name: print dependencies\n        run: ldd fastfetch\n\n      - name: run tests\n        run: ctest --output-on-failure\n\n      - name: upload artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: fastfetch-linux-i686\n          path: ./fastfetch-*.*\n\n  linux-armv7l:\n    name: Linux-armv7l\n    runs-on: ubuntu-24.04\n    permissions:\n      security-events: write\n      contents: read\n    steps:\n      - name: checkout repository\n        uses: actions/checkout@v6\n\n      - name: run VM\n        uses: uraimo/run-on-arch-action@v3\n        id: runcmd\n        with:\n          arch: armv7\n          distro: ubuntu22.04\n          githubToken: ${{ github.token }}\n          run: |\n            uname -a\n            apt-get update && apt-get install -y software-properties-common ca-certificates gpg curl\n            add-apt-repository -y ppa:ubuntu-toolchain-r/test\n            curl -L https://apt.kitware.com/keys/kitware-archive-latest.asc | gpg --dearmor - | tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null\n            echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ jammy main' | tee /etc/apt/sources.list.d/kitware.list >/dev/null\n            echo -e 'Acquire::https::Verify-Peer \"false\";\\nAcquire::https::Verify-Host \"false\";' >> /etc/apt/apt.conf.d/99ignore-certificates\n            apt-get update && apt-get install -y cmake make gcc-13 libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev rpm\n            CC=gcc-13 cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_DIRECTX_HEADERS=Off -DCMAKE_INSTALL_PREFIX=/usr .\n            cmake --build . --target package --verbose -j4\n            ./fastfetch --list-features\n            time ./fastfetch -c presets/ci.jsonc --stat false\n            time ./fastfetch -c presets/ci.jsonc --format json\n            time ./flashfetch\n            ldd fastfetch\n            ctest --output-on-failure\n\n      - name: upload artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: fastfetch-linux-armv7l\n          path: ./fastfetch-*.*\n\n  linux-armv6l:\n    name: Linux-armv6l\n    runs-on: ubuntu-24.04\n    permissions:\n      security-events: write\n      contents: read\n    steps:\n      - name: checkout repository\n        uses: actions/checkout@v6\n\n      - name: run VM\n        uses: uraimo/run-on-arch-action@v3\n        id: runcmd\n        with:\n          arch: armv6\n          distro: bookworm\n          githubToken: ${{ github.token }}\n          run: |\n            uname -a\n            apt-get update && apt-get install -y wget\n            apt-get install -y cmake make gcc libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev rpm\n            cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_DIRECTX_HEADERS=Off -DCMAKE_INSTALL_PREFIX=/usr .\n            cmake --build . --target package --verbose -j4\n            ./fastfetch --list-features\n            time ./fastfetch -c presets/ci.jsonc --stat false\n            time ./fastfetch -c presets/ci.jsonc --format json\n            time ./flashfetch\n            ldd fastfetch\n            ctest --output-on-failure\n\n      - name: upload artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: fastfetch-linux-armv6l\n          path: ./fastfetch-*.*\n\n  linux-vms:\n    name: Linux-${{ matrix.arch }}\n    runs-on: ubuntu-latest\n    permissions:\n      security-events: write\n      contents: read\n    strategy:\n      matrix:\n        include:\n          - arch: riscv64\n          - arch: ppc64le\n          - arch: s390x\n    steps:\n      - name: checkout repository\n        uses: actions/checkout@v6\n\n      - name: run VM\n        uses: uraimo/run-on-arch-action@v3\n        id: runcmd\n        with:\n          arch: ${{ matrix.arch }}\n          distro: ubuntu22.04\n          githubToken: ${{ github.token }}\n          run: |\n            uname -a\n            apt-get update && apt-get install -y software-properties-common\n            add-apt-repository -y ppa:ubuntu-toolchain-r/test\n            apt-get update && apt-get install -y cmake make gcc-13 libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libchafa-dev libelf-dev rpm\n            CC=gcc-13 cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_DIRECTX_HEADERS=Off -DCMAKE_INSTALL_PREFIX=/usr .\n            cmake --build . --target package --verbose -j4\n            ./fastfetch --list-features\n            time ./fastfetch -c presets/ci.jsonc --stat false\n            time ./fastfetch -c presets/ci.jsonc --format json\n            time ./flashfetch\n            ldd fastfetch\n            ctest --output-on-failure\n\n      - name: upload artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: fastfetch-linux-${{ matrix.arch }}\n          path: ./fastfetch-*.*\n\n  musl-amd64:\n    name: Musl-amd64\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: setup alpine linux\n        uses: jirutka/setup-alpine@master\n\n      - name: install dependencies\n        run: |\n          cat /etc/alpine-release\n          uname -a\n          apk add cmake samurai vulkan-loader-dev libxcb-dev libxrandr-dev rpm-dev wayland-dev libdrm-dev dconf-dev imagemagick-dev chafa-dev zlib-dev dbus-dev mesa-dev opencl-dev sqlite-dev networkmanager-dev pulseaudio-dev ddcutil-dev elfutils-dev gcc g++\n        shell: alpine.sh --root {0}\n\n      - name: build\n        run: |\n            cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr -DIS_MUSL=ON -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -GNinja .\n            cmake --build . --target package --verbose -j4\n        shell: alpine.sh {0}\n\n      - name: run\n        run: |\n            ./fastfetch --list-features\n            time ./fastfetch -c presets/ci.jsonc --stat false\n            time ./fastfetch -c presets/ci.jsonc --format json\n            time ./flashfetch\n            ldd fastfetch\n            ctest --output-on-failure\n        shell: alpine.sh {0}\n\n      - name: upload artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: fastfetch-musl-amd64\n          path: ./fastfetch-*.*\n\n  macos-hosts:\n    name: macOS-${{ matrix.arch }}\n    runs-on: ${{ matrix.runs-on }}\n    permissions:\n      security-events: write\n      contents: read\n    strategy:\n      matrix:\n        include:\n          - arch: amd64\n            runs-on: macos-15-intel\n          - arch: aarch64\n            runs-on: macos-latest\n    steps:\n      - name: checkout repository\n        uses: actions/checkout@v6\n\n      - name: uname -a\n        run: uname -a\n\n      - name: install required packages\n        run: |\n          HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install --overwrite vulkan-loader vulkan-headers molten-vk imagemagick chafa\n\n      - name: configure project\n        run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On .\n\n      - name: build project\n        run: cmake --build . --target package --verbose -j4\n\n      - name: list features\n        run: ./fastfetch --list-features\n\n      - name: run fastfetch\n        run: time ./fastfetch -c presets/ci.jsonc --stat false\n\n      - name: run fastfetch --format json\n        run: time ./fastfetch -c presets/ci.jsonc --format json\n\n      - name: run flashfetch\n        run: time ./flashfetch\n\n      - name: print dependencies\n        run: otool -L fastfetch\n\n      - name: run tests\n        run: ctest --output-on-failure\n\n      - name: upload artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: fastfetch-macos-${{ matrix.arch }}\n          path: ./fastfetch-*.*\n\n  omnios-amd64:\n    runs-on: ubuntu-latest\n    name: OmniOS-amd64\n    steps:\n      - name: checkout repository\n        uses: actions/checkout@v6\n\n      - name: run VM\n        uses: vmactions/omnios-vm@v1\n        with:\n          usesh: true\n          envs: 'CMAKE_BUILD_TYPE'\n          prepare: |\n            uname -a\n            pkg update --accept\n            pkg install gcc14 cmake git pkg-config glib2 dbus sqlite-3 imagemagick ninja\n\n          run: |\n            cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -GNinja .\n            cmake --build . --verbose -j4\n            ./fastfetch --list-features\n            time ./fastfetch -c presets/ci.jsonc --stat false\n            time ./fastfetch -c presets/ci.jsonc --format json\n            time ./flashfetch\n            ldd fastfetch\n            ctest --output-on-failure\n            echo 'set(CPACK_PACKAGE_FILE_NAME \"fastfetch-omnios-amd64\")' >> CPackConfig.cmake\n            cpack -V\n\n      - name: upload artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: fastfetch-omnios-amd64\n          path: ./fastfetch-*.*\n\n  solaris-amd64:\n    runs-on: ubuntu-latest\n    name: Solaris-amd64\n    steps:\n      - name: checkout repository\n        uses: actions/checkout@v6\n\n      - name: run VM\n        uses: vmactions/solaris-vm@v1\n        with:\n          usesh: true\n          envs: 'CMAKE_BUILD_TYPE'\n          release: \"11.4-gcc-14\"\n          prepare: |\n            uname -a\n            pkg install cmake git pkg-config glib2 dbus sqlite-3 imagemagick ninja dconf mesa\n\n          run: |\n            export PKG_CONFIG_PATH=/usr/lib/64/pkgconfig\n            cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -GNinja .\n            cmake --build . --verbose -j4\n            ./fastfetch --list-features\n            time ./fastfetch -c presets/ci.jsonc --stat false\n            time ./fastfetch -c presets/ci.jsonc --format json\n            time ./flashfetch\n            ldd fastfetch\n            ctest --output-on-failure\n            echo 'set(CPACK_PACKAGE_FILE_NAME \"fastfetch-solaris-amd64\")' >> CPackConfig.cmake\n            cpack -V\n\n      - name: upload artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: fastfetch-solaris-amd64\n          path: ./fastfetch-*.*\n\n  freebsd-amd64:\n    name: FreeBSD-amd64\n    runs-on: ubuntu-latest\n    permissions:\n      security-events: write\n      contents: read\n    steps:\n      - name: checkout repository\n        uses: actions/checkout@v6\n\n      - name: run VM\n        uses: cross-platform-actions/action@master\n        with:\n          operating_system: freebsd\n          architecture: x86-64\n          cpu_count: 4\n          shell: bash\n          version: '15.0'\n          environment_variables: 'CMAKE_BUILD_TYPE'\n          run: |\n            uname -a\n            sudo pkg update\n            sudo pkg install -y cmake git pkgconf binutils wayland vulkan-headers vulkan-loader libxcb libXrandr libX11 libdrm glib dconf dbus sqlite3-tcl egl opencl ocl-icd v4l_compat chafa\n            cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On .\n            cmake --build . --target package --verbose -j4\n            ./fastfetch --list-features\n            time ./fastfetch -c presets/ci.jsonc --stat false\n            time ./fastfetch -c presets/ci.jsonc --format json\n            time ./flashfetch\n            ldd fastfetch\n            ctest --output-on-failure\n\n      - name: upload artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: fastfetch-freebsd-amd64\n          path: ./fastfetch-*.*\n\n  openbsd-amd64:\n    name: OpenBSD-amd64\n    runs-on: ubuntu-latest\n    permissions:\n      security-events: write\n      contents: read\n    steps:\n      - name: checkout repository\n        uses: actions/checkout@v6\n\n      - name: run VM\n        uses: cross-platform-actions/action@master\n        with:\n          operating_system: openbsd\n          architecture: x86-64\n          cpu_count: 4\n          shell: bash\n          version: '7.8'\n          environment_variables: 'CMAKE_BUILD_TYPE'\n          run: |\n            uname -a\n            sudo pkg_add -u\n            sudo pkg_add -r llvm-21.1.2p0 cmake git pkgconf wayland vulkan-headers vulkan-loader glib2 dconf dbus sqlite3 imagemagick chafa\n            CC=clang-21 cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On .\n            cmake --build . --target package --verbose -j4\n            ./fastfetch --list-features\n            time ./fastfetch -c presets/ci.jsonc --stat false\n            time ./fastfetch -c presets/ci.jsonc --format json\n            time ./flashfetch\n            ldd fastfetch\n            ctest --output-on-failure\n\n      - name: upload artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: fastfetch-openbsd-amd64\n          path: ./fastfetch-*.*\n\n  netbsd-amd64:\n    name: NetBSD-amd64\n    runs-on: ubuntu-latest\n    permissions:\n      security-events: write\n      contents: read\n    steps:\n      - name: checkout repository\n        uses: actions/checkout@v6\n\n      - name: run VM\n        uses: cross-platform-actions/action@master\n        with:\n          operating_system: netbsd\n          architecture: x86-64\n          cpu_count: 4\n          shell: bash\n          version: '10.1'\n          environment_variables: 'CMAKE_BUILD_TYPE'\n          run: |\n            uname -a\n            sudo pkgin -y install clang cmake git pkgconf wayland vulkan-headers dconf dbus sqlite3 ImageMagick\n            CC=clang cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On .\n            cmake --build . --target package --verbose -j4\n            ./fastfetch --list-features\n            time ./fastfetch -c presets/ci.jsonc --stat false\n            time ./fastfetch -c presets/ci.jsonc --format json\n            time ./flashfetch\n            ldd fastfetch\n            ctest --output-on-failure\n\n      - name: upload artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: fastfetch-netbsd-amd64\n          path: ./fastfetch-*.*\n\n  dragonfly-amd64:\n    name: DragonFly-amd64\n    runs-on: ubuntu-latest\n    permissions:\n      security-events: write\n      contents: read\n    steps:\n      - name: checkout repository\n        uses: actions/checkout@v6\n\n      - name: run VM\n        uses: vmactions/dragonflybsd-vm@v1\n        with:\n          usesh: yes\n          envs: 'CMAKE_BUILD_TYPE'\n          prepare: |\n            uname -a\n            pkg update\n            pkg install -y llvm cmake git pkgconf binutils wayland vulkan-headers vulkan-loader libxcb libXrandr libX11 libdrm glib dconf dbus sqlite3-tcl egl opencl ocl-icd v4l_compat chafa libelf\n\n          run: |\n            env CC=clang cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On .\n            cmake --build . --target package --verbose -j4\n            ./fastfetch --list-features\n            time ./fastfetch -c presets/ci.jsonc --stat false\n            time ./fastfetch -c presets/ci.jsonc --format json\n            time ./flashfetch\n            ldd fastfetch\n            ctest --output-on-failure\n\n      - name: upload artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: fastfetch-dragonfly-amd64\n          path: ./fastfetch-*.*\n\n  haiku-amd64:\n    name: Haiku-amd64\n    runs-on: ubuntu-latest\n    permissions:\n      security-events: write\n      contents: read\n    steps:\n      - name: checkout repository\n        uses: actions/checkout@v6\n\n      - name: run VM\n        uses: cross-platform-actions/action@master\n        with:\n          operating_system: haiku\n          version: 'r1beta5'\n          architecture: x86-64\n          cpu_count: 4\n          shell: bash\n          environment_variables: 'CMAKE_BUILD_TYPE'\n          run: |\n            uname -a\n            pkgman install -y git dbus_devel mesa_devel libelf_devel imagemagick_devel opencl_headers ocl_icd_devel vulkan_devel zlib_devel chafa_devel cmake gcc make pkgconfig python3.10 || pkgman install -y git dbus_devel mesa_devel libelf_devel imagemagick_devel opencl_headers ocl_icd_devel vulkan_devel zlib_devel chafa_devel cmake gcc make pkgconfig python3.10\n            cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On .\n            cmake --build . --target package --verbose -j4\n            ./fastfetch --list-features\n            time ./fastfetch -c presets/ci.jsonc --stat false\n            time ./fastfetch -c presets/ci.jsonc --format json\n            time ./flashfetch\n            ctest --output-on-failure\n\n      - name: upload artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: fastfetch-haiku-amd64\n          path: ./fastfetch-*.*\n\n  windows-hosts:\n    name: Windows-${{ matrix.arch }}\n    runs-on: ${{ matrix.runs-on }}\n    permissions:\n      security-events: write\n      contents: read\n    strategy:\n      matrix:\n        include:\n          - arch: amd64\n            runs-on: windows-latest\n            msystem: CLANG64\n            msystem-lower: clang64\n            msys-arch: x86_64\n          - arch: aarch64\n            runs-on: windows-11-arm\n            msystem: CLANGARM64\n            msystem-lower: clangarm64\n            msys-arch: aarch64\n    defaults:\n      run:\n        shell: msys2 {0}\n    steps:\n      - name: checkout repository\n        uses: actions/checkout@v6\n\n      - name: setup-msys2\n        uses: msys2/setup-msys2@v2\n        with:\n          msystem: ${{ matrix.msystem }}\n          update: true\n          install: git mingw-w64-clang-${{ matrix.msys-arch }}-7zip mingw-w64-clang-${{ matrix.msys-arch }}-cmake mingw-w64-clang-${{ matrix.msys-arch }}-clang mingw-w64-clang-${{ matrix.msys-arch }}-vulkan-loader mingw-w64-clang-${{ matrix.msys-arch }}-vulkan-headers mingw-w64-clang-${{ matrix.msys-arch }}-opencl-icd mingw-w64-clang-${{ matrix.msys-arch }}-opencl-headers mingw-w64-clang-${{ matrix.msys-arch }}-cppwinrt mingw-w64-clang-${{ matrix.msys-arch }}-imagemagick mingw-w64-clang-${{ matrix.msys-arch }}-chafa\n\n      - name: print msys version\n        run: uname -a\n\n      - name: configure project\n        run: env PKG_CONFIG_PATH=/${{ matrix.msystem-lower }}/lib/pkgconfig/:$PKG_CONFIG_PATH cmake -DSET_TWEAK=Off -DBUILD_TESTS=On .\n\n      - name: build project\n        run: cmake --build . --verbose -j4\n\n      - name: copy necessary dlls\n        run: cp /${{ matrix.msystem-lower }}/bin/{OpenCL,vulkan-1}.dll .\n\n      - name: list features\n        run: ./fastfetch --list-features\n\n      - name: run fastfetch\n        run: time ./fastfetch -c presets/ci.jsonc --stat false\n\n      - name: run fastfetch --format json\n        run: time ./fastfetch -c presets/ci.jsonc --format json\n\n      - name: run flashfetch\n        run: time ./flashfetch\n\n      - name: print dependencies\n        run: ldd fastfetch\n\n      - name: run tests\n        run: ctest --output-on-failure\n\n      - if: github.event_name == 'push' && github.repository == 'fastfetch-cli/fastfetch'\n        id: upload-unsigned-artifact\n        name: upload artifacts for signing\n        uses: actions/upload-artifact@v7\n        with:\n          name: fastfetch-windows-${{ matrix.arch }}\n          path: |\n            *.dll\n            fastfetch.exe\n            flashfetch.exe\n\n      - if: github.event_name == 'push' && github.repository == 'fastfetch-cli/fastfetch'\n        name: submit signing request\n        uses: signpath/github-action-submit-signing-request@v1\n        with:\n          api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'\n          organization-id: '${{ vars.SIGNPATH_ORG_ID }}'\n          project-slug: 'fastfetch'\n          signing-policy-slug: ${{ github.ref == 'refs/heads/master' && 'release-signing' || 'test-signing' }}\n          github-artifact-id: '${{ steps.upload-unsigned-artifact.outputs.artifact-id }}'\n          wait-for-completion: true\n          output-artifact-directory: '.'\n\n      - name: create zip archive\n        run: 7z a -tzip -mx9 -bd -y fastfetch-windows-${{ matrix.arch }}.zip LICENSE *.dll fastfetch.exe flashfetch.exe presets\n\n      - name: create 7z archive\n        run: 7z a -t7z -mx9 -bd -y fastfetch-windows-${{ matrix.arch }}.7z LICENSE *.dll fastfetch.exe flashfetch.exe presets\n\n      - name: upload true artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: fastfetch-windows-${{ matrix.arch }}\n          path: ./fastfetch-windows-${{ matrix.arch }}.*\n          overwrite: true\n\n  release:\n    if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository == 'fastfetch-cli/fastfetch'\n    name: Release\n    runs-on: ubuntu-latest\n    needs:\n      - linux-hosts\n      - linux-i686\n      - linux-armv7l\n      - linux-armv6l\n      - linux-vms\n      - musl-amd64\n      - macos-hosts\n      - freebsd-amd64\n      - openbsd-amd64\n      - netbsd-amd64\n      - dragonfly-amd64\n      - solaris-amd64\n      - omnios-amd64\n      - haiku-amd64\n      - windows-hosts\n    permissions:\n      contents: write\n    steps:\n      - name: get latest release version\n        id: get_version_release\n        uses: pozetroninc/github-action-get-latest-release@master\n        with:\n          repository: ${{ github.repository }}\n\n      - name: download artifacts\n        if: needs.linux-hosts.outputs.ffversion != steps.get_version_release.outputs.release\n        uses: actions/download-artifact@v8\n\n      - name: create release\n        if: needs.linux-hosts.outputs.ffversion != steps.get_version_release.outputs.release\n        uses: ncipollo/release-action@v1\n        with:\n          tag: ${{ needs.linux-hosts.outputs.ffversion }}\n          commit: ${{ github.sha }}\n          artifactErrorsFailBuild: true\n          artifacts: fastfetch-*/fastfetch-*\n          body: \"Please refer to [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/${{ needs.linux-hosts.outputs.ffversion }}/CHANGELOG.md) for details.\"\n\n      - name: download source tarballs\n        if: needs.linux-hosts.outputs.ffversion != steps.get_version_release.outputs.release\n        run: |\n          for i in 1 2 3 4 5; do curl -L --remote-name-all --output-dir fastfetch-source --create-dirs https://github.com/${{ github.repository }}/archive/refs/tags/${{ needs.linux-hosts.outputs.ffversion }}.{tar.gz,zip} && break || sleep 5; done\n          ls fastfetch-*/*\n\n      - name: generate release notes\n        if: needs.linux-hosts.outputs.ffversion != steps.get_version_release.outputs.release\n        run: |\n          echo \"Please refer to [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/${{ needs.linux-hosts.outputs.ffversion }}/CHANGELOG.md) for details.\" > fastfetch-release-notes.md\n          echo -e \"\\n---\\n\\n<details><summary>SHA256SUMs</summary><br>\\n\\n\\`\\`\\`\" >> fastfetch-release-notes.md\n          sha256sum fastfetch-*/* >> fastfetch-release-notes.md\n          echo -e \"\\`\\`\\`\\n</details>\" >> fastfetch-release-notes.md\n          echo -e \"\\n<details><summary>SHA512SUMs</summary><br>\\n\\n\\`\\`\\`\" >> fastfetch-release-notes.md\n          sha512sum fastfetch-*/* >> fastfetch-release-notes.md\n          echo -e \"\\`\\`\\`\\n</details>\" >> fastfetch-release-notes.md\n\n      - name: update release body\n        if: needs.linux-hosts.outputs.ffversion != steps.get_version_release.outputs.release\n        uses: ncipollo/release-action@v1\n        with:\n          tag: ${{ needs.linux-hosts.outputs.ffversion }}\n          commit: ${{ github.sha }}\n          bodyFile: fastfetch-release-notes.md\n          allowUpdates: true\n"
  },
  {
    "path": ".gitignore",
    "content": "build/\n.vs/\n.vscode/\n.cache/\n.kdev4/\n.DS_Store\ncscope.*\ntags\nfastfetch.kdev4\n*.user\n*.user.*\n*.swp\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# 2.60.0\n\nChanges:\n* The CMake option `ENABLE_WIN7_COMPAT:BOOLEAN` now defaults to `OFF` and will be removed in v2.61.0, effectively dropping support for Windows 7 in the next release.\n    * This follows the Windows 7 deprecation notice introduced in v2.57.0.\n* `wm.detectPlugin` now defaults to `true` (WM)\n\nFeatures:\n* Adds `{cwd}` for custom title formatting, which displays the current working directory (Title)\n* Adds support for detecting the Zed version (#2200, Editor)\n* Adds support for detecting `moss` packages (Packages, Linux)\n* Adds support for detecting komorebi, FancyWM, and GlazeWM (WM, Windows)\n* Adds support for WM plugin version detection on macOS (WM, macOS)\n* Adds support for retrieving the executable path on OpenBSD (#2195, OpenBSD)\n\nBugfixes:\n* Fixes a potential segmentation fault caused by dereferencing a negative index (#2198)\n* Fixes `tempSensor` parsing so that it accepts only string values (#2202, CPU)\n* Fixes an issue that unexpectedly caused fewer devices to be reported (Keyboard, Linux)\n* Improves WM detection on LXQt by querying WM settings only when no WM has already been detected (#2199, WM, Linux)\n* Fixes memory leaks in DBus connection handling and in the OpenGL EGL context lifecycle\n* Fixes niri version detection on Fedora (WM, Linux)\n* Includes various internal cleanups and optimizations\n\nLogos:\n* Adds `RengeOS` (#2170)\n* Adds `Emmabuntüs` (#2207)\n* Updates Artix Linux (#2157)\n* Updates Linux Mint (#2186)\n* Renames `Refracted Devuan` to `Refracta`\n* Renames `ExodiaPredator` to `ExodiaOS`\n\n# 2.59.0\n\nChanges:\n* Fastfetch no longer relies on the unreliable environment variables `$USER` or `%USERPROFILE%` to determine the current username (Title)\n    * People who set `$USER` to customize the Fastfetch title should use `{ \"type\": \"title\", \"format\": \"your-custom-user-name\" }` to achieve the same result.\n* Fastfetch no longer tries to probe inaccessible remote disk drives on Windows (Disk, Windows)\n    * People who have remote drives may use `{ \"type\": \"disk\", \"hideFolders\": \"X:\\\\\" }` to ignore problematic ones.\n    * This change removes some ugly hacks from the codebase and matches the behavior on `*nix`.\n\nFeatures:\n* Adds Oracle Solaris support (#2176, SunOS)\n* Adds UID / SID detection (Title)\n    * In custom format: `{user-id}`\n* Switches to native GPU detection on GNU/Hurd and removes the `libpciaccess` dependency (GPU, Hurd)\n* Improves memory size detection on macOS (Memory, macOS)\n    * Avoids relying on `hw.memsize_usable` by default, which may not be available on older macOS versions\n* Improves Windows disk detection accuracy and performance (Disk, Windows)\n* Adds more ARM CPU parts and removes duplicated cases (CPU, ARM)\n\nLogos:\n* Adds 6-color support to the NixOS logo (including the small variant) (#2180)\n\n# 2.58.0\n\nAn early release to fix compatibility issues with KDE Plasma 6.6.\n\nBreaking changes:\n* The `de.slowVersionDetection` option has been removed. Slow version detection is now always enabled, as required on non-FHS-compliant distros (e.g., NixOS). (#2149, DE, Linux)\n\nFeatures:\n* Adds the `--structure-disabled <modules...>` command-line flag to temporarily disable module structure printing.\n    * For example: `fastfetch --structure-disabled colors` removes the color blocks from the default output.\n* Supports chassis type detection on Linux ARM devices when reported via the device tree (Chassis, Linux)\n* Supports Bedrock Linux version detection (#2155, OS, Linux)\n* Honors the `DBPath` and `RootDir` settings in `pacman.conf` when detecting Pacman packages (#2154, Packages, Linux)\n\nBugfixes:\n* Fixes a crash issue on KDE Plasma 6.6 (Display, Linux)\n* Fixes the Command module not working with `--dynamic-interval` (#2152, Command)\n* Fixes Quartz Compositor version detection. It now correctly reports the version of `WindowServer` (`SkyLight`) instead of `WindowManager`. (WM, macOS)\n\nLogos:\n* Adds Kiss2\n\n# 2.57.1\n\nFeatures:\n* Tiny performance improvements (Windows)\n* Improves the reliability of hostname retrieval (Title, Windows)\n\nBugfixes:\n* Fixes potential compilation issues on Linux (#2142, Linux)\n* Fixes compilation errors on macOS when building with older SDKs (#2140, macOS)\n* Fixes compilation issues when building with `-DENABLE_SYSTEM_YYJSON=ON` (#2143)\n\nLogos:\n* Updates PrismLinux and adds a small variant\n\n# 2.57.0\n\nDeprecation notice:\n* Support for Windows 7 (and 8.x) is deprecated and will be removed in a future release. Extended support for Windows 7 (and 8.1) ended on January 10, 2023. These versions do not officially support ANSI escape codes (running fastfetch on them requires a third-party terminal such as ConEmu). In addition, Windows 7 lacks some APIs used by fastfetch. Fastfetch currently loads these APIs dynamically at runtime to maintain compatibility, but this adds complexity to the codebase and increases the maintenance burden.\n    * A CMake flag `ENABLE_WIN7_COMPAT:BOOLEAN` has been introduced (defaults to `ON` for now). If set to `OFF`, Windows 7 compatibility code is excluded, and the resulting binaries will support only Windows 10 (version 1607 and later) and Windows 11.\n    * The main prebuilt Windows binaries on the Release page (`fastfetch-windows-amd64.*`) are built with `ENABLE_WIN7_COMPAT=OFF`. These are the binaries used by `scoop` and `winget`. Users who need Windows 7 (or 8.x) support can download the `-win7` variant instead.\n    * ~~The `ENABLE_WIN7_COMPAT` CMake option and the `-win7` variant binaries are planned to be removed in 2.60.0~~.\n\nFeatures:\n* Supports COSMIC DE version detection (DE, Linux)\n* Supports niri version detection (#2121, WM, Linux)\n* Supports cosmic-term version and terminal font detection (Terminal / TerminalFont, Linux)\n* Supports urxvt font detection (TerminalFont, Linux) (#2105)\n* Improves xterm font detection by checking `xterm.vt100.faceName` (TerminalFont, Linux)\n* Supports Secure Boot detection (Bootmgr, macOS)\n* Supports DPI scale factor detection on Windows 7 (Display, Windows)\n* Supports xterm 256-color codes in color configuration\n    * In `display.color`: \"`@<color-index>`\" (e.g., \"`@34`\" for color index `34`)\n    * In `*.format` strings: \"`#@<color-index>`\" (e.g., \"`#@34`\" for color index `34`)\n* Improves uptime accuracy on Windows 10+ (Uptime, Windows)\n* Adds a new module `Logo` to query built-in logo raw data in JSON output (Logo)\n    * Usage: `fastfetch -s logo -l <logo-name> -j # Supported in JSON format only`\n* Supports shell version detection even if the binary has been deleted (#2136, Shell, Linux)\n* Overall code refinements and optimizations\n\nBugfixes:\n* Skips local / loopback routes when detecting network interfaces (LocalIP, Linux) (#2127)\n* Fixes CPU speed detection on s390x (CPU, Linux) (#2129)\n* Fixes GPU detection error handling and supports case-insensitive PCI ID parsing (GPU, Windows)\n* Fixes some networking issues and memory leaks (Networking)\n* Fixes `exePath` reporting relative paths on macOS (Shell, macOS)\n\nLogos:\n* Adds openSUSE Tumbleweed braille logo\n* Adds Xinux\n* Renames HydraPWK to NetHydra\n* Fixes colors of deepin and UOS\n* Fixes colors of macOS and variants\n\n# 2.56.1\n\nFeatures:\n* Improves compatibility with KDE Plasma 6.5 (#2093, Display)\n* Adds a `tempSensor` option to specify the sensor name used for CPU temperature detection (CPU)\n    * Example: `{ \"type\": \"cpu\", \"tempSensor\": \"hwmon0\" /* Use /sys/class/hwmon/hwmon0 for temperature detection */ }`\n* Refines Memory usage detection on macOS to match Activity Monitor more closely (Memory, macOS)\n* Minor optimizations\n\nBugfixes:\n* Fixes cache line size detection (CPU, macOS)\n\nLogos:\n* Removes Opak\n* Updates GXDE\n\n# 2.56.0\n\nFeatures:\n* Enhances config file loading. `--config` and `-c` with relative path now also searches paths defined in `fastfetch --list-config-paths` (typically `~/.config/fastfetch/`)\n    * This allows users to use `fastfetch -c my-config` without needing to specify the full path.\n* Adds NUMA node count detection (CPU)\n    * Exposed via `{numa-nodes}` in custom format\n    * Supported on Linux, FreeBSD and Windows\n* Supports the newest Alacritty config format (#2070, TerminalFont)\n* Detects driver specific info for Zhaoxin GPUs (GPU, Linux)\n* Detects Android OEM UI for certain OSes (DE, Android)\n* Improves users detection on Linux (#2064, Users, Linux)\n    * Adds systemd fallback when utmp is unavailable\n    * Fixes resource leaks\n    * Always reports the newest session info\n* Adds kiss package manager support (#2072, Packages, Linux)\n* Reports `sshd` if `$SSH_TTY` is not available (Terminal)\n* Zpool module rewrite (#2051, Zpool)\n    * Adds new Zpool properties: allocated, guid, readOnly\n    * Zpool module now uses runtime lookup for properties to ensure portability\n    * Adds NetBSD (requires `sudo`) and macOS support\n* Adds `splitLines` option for Command module, which splits the output into sub modules, each containing one line of the output (Command)\n```\n* Command output:\nLine 1\nLine 2\nLine 3\n\n* Old behavior:\nCommand: Line 1\nLine 2\nLine 3\n\n* With `\"splitLines\": true`:\nCommand 1: Line 1\nCommand 2: Line 2\nCommand 3: Line 3\n```\n\nBugfixes:\n* Fixes {m,o}ksh version detection on Linux (Shell)\n* Fixes Alacritty config parsing for TOML format (#2070, TerminalFont)\n* Improves builtin logo printing for piping and buffering (#2065, Logo)\n* Uses absolute path when detecting shell and terminal version if available (#2067, TerminalShell)\n\nLogos:\n* Updates Codex Linux logo (#2071)\n* Adds OS/2 Warp logo (#2062)\n* Adds Amiga logo (#2061)\n\n# 2.55.1\n\nBugfixes:\n* Fix parallel command execution breaks randomly (#2056 / #2058, Command)\n    * Regression from v2.55.0\n* Fix `dylib` searching path on macOS (macOS)\n    * Regression from v2.55.0\n* Fix an uninitialized field (#2057, Display)\n\n# 2.55.0\n\nChanges:\n* Commands are now executed in parallel by default to improve performance (#2045, Command)\n    * This behavior can be disabled in the config file with `\"parallel\": false` if it causes problems with certain scripts\n* Folder/filesystem hiding is moved to the detection stage; hidden entries are no longer probed, improving performance (#2043, Disk)\n\nFeatures:\n* Adds `command.parallel` and `command.useStdErr` config options (Command)\n    * `parallel`: set to `false` to disable parallel execution (see Changes above)\n    * `useStdErr`: set to `true` to use stderr output instead of stdout\n* Adds the command-line flag `--dynamic-interval <interval-in-ms>` to enable dynamic output auto-refresh (#2041)\n    * Due to internal limitations, some modules do not support dynamic updates (notably Display and Media)\n* Adds support for using the current playing media's cover art as a logo source (Media / Logo)\n    * Usage: `\"logo\": { \"type\": \"<image-protocol>\", \"source\": \"media-cover\" }` in JSON config; or `--<image-protocol> media-cover` in command line\n    * Supports local sources only\n* Adds native GPU detection support on OpenBSD and NetBSD (instead of depending on `libpciaccess`) (GPU)\n    * No functional changes\n    * Root privileges are required to access PCI config space on OpenBSD (as always)\n* Adds GPU detection support on GNU/Hurd (GPU)\n    * Requires building with `libpciaccess`\n* Shows Debian point release on Raspberry Pi OS (#2032, OS, Linux)\n* Adds `Brush` shell version detection (Shell)\n* Improves Mac family detection via prefix matching (Host)\n\nBugfixes:\n* Ignores `run-parts` during terminal/shell detection (#2048, Terminal / Shell, Linux)\n* Fixes fish version detection when `LC_ALL` is set (#2014, Shell, Linux)\n* Hides the module when no desktop icons are found (#2023, Icons, Windows)\n* Skips auxiliary display controllers to prevent the module from reporting duplicate entries (#2034, GPU, Linux)\n* Refines Apple rpath handling; fixes building for the Homebrew version on macOS (#1998, CMake)\n\nLogos:\n* Adds Vincent OS and MacaroniOS\n\n# 2.54.0\n\nWindows binaries in Release page are now signed by SignPath.\n\nChanges:\n* Moves macOS and Windows design language detection from the DE module to the Theme module\n\nFeatures:\n* Adds `--json` and `-j` command line flags as a shortcut for `--format json`\n* Various improvements to the OS module (OS)\n    * Displays point releases for Debian\n    * Displays code names for Ubuntu\n    * Displays build ID for macOS\n    * Displays code names for Windows (previously shown in the Kernel module)\n* Adds basic support for Wine (Windows)\n* Adds basic support for hppa and sh architectures (CPU, Linux)\n* Improves T-Head SoC name detection from the device tree (#1997, CPU, Linux)\n* Supports glob patterns in `Disk.hideFolders` (Disk)\n    * For example, `/boot/*` will match both `/boot/efi` and `/boot/firmware`\n* Adds brightness-level detection for external monitor support on Intel macOS (Brightness, macOS)\n* Adds configurable spacing between icon and text in keys\n    * `display.key.type: \"both-N\"` where N is `0-4`\n    * Useful for non-monospaced Nerd Fonts\n* Adds detection support for modern Samsung Exynos SoCs (CPU, Android)\n* Adds a new CMake option `-DENABLE_WORDEXP=<ON|OFF>` to enable or disable using `wordexp(3)` for acquiring logo file paths (`logo.source`)\n    * Enabled by default for compatibility\n    * Disabling this option reverts to using `glob(3)`, which is less functional but more secure\n\nBugfixes:\n* Avoids integer overflow when calculating swap size (#1988, Swap, Windows)\n* Trims whitespace from full user name (Title, macOS)\n* Fixes default font size for Ghostty (#1986, TerminalFont, Linux)\n* Works around an issue that could report impossibly high memory usage in rare cases (#1988, Memory, Linux)\n* Fixes incorrect glibc dependency in polyfilled DEB packages (#1983, Linux)\n* Fixes corrupted binaries in polyfilled RPM packages (#1990, Linux)\n* Fixes crashes on ancient Android kernels (#1993, Disk, Android)\n* Fixes incorrect usage of `glob(3)` (OpenBSD)\n* Prefers resolutions reported by RandR mode info, fixing incorrect resolutions on XFCE when DPI scaling is enabled (Display, Linux)\n* Various code cleanups and minor fixes\n\nLogos:\n* Adds secureblue, PrismLinux, EmperorOS and Zraxyl\n* Updates T2\n\n# 2.53.0\n\nChanges:\n* JSON property `length` in `Separator` module has been renamed to `times` for clarity (Separator)\n\nFeatures:\n* Adds IPv6 type selection (#1459, LocalIP)\n    * For example: `{ \"type\": \"localip\", \"showIpv6\": \"ula\" /* Show ULA only */ }`\n* Adds more ARM CPU part IDs (CPU, Linux)\n* Improves Ghostty font config parsing with fallback font detection (#1967, TerminalFont)\n* Replaces statx(2) call with syscall(2) for better compatibility (Disk, Linux)\n* Allows array input for disk folder and filesystem options (Disk)\n    * For example: `{ \"type\": \"disk\", \"folders\": [\"/\", \"/home\"] }`\n* Adds support for ignoring input devices by name prefix (#1950, Keyboard / Mouse / Gamepad)\n    * For example: `{ \"type\": \"keyboard\", \"ignores\": [\"Apple \", \"Corsair \"] }`\n* Adds support for (B)SSID detection on macOS Tahoe (Wifi, macOS)\n    * Please don't expect it to work on later macOS versions\n* Improves Ubuntu flavor detection (#1975, OS, Linux)\n* Refines ARMv8.4-A detection to require LSE2 (CPU, Windows)\n* Detects the latest Dimensity & Snapdragon SoC names (CPU, Android)\n\nBugfixes:\n* Handles zero temperature data (#1960, CPU, Windows)\n* Fixes `dlopen libzfs.so failed` error on Proxmox 9 (#1973, Zpool, Linux)\n\nLogos:\n* Removes Starry Linux\n* Adds TempleOS\n* Updates ObsidianOS\n\n# 2.52.0\n\nChanges:\n* New optional build dependencies on Android\n    * main: chafa dbus glib imagemagick libelf libxcb libxrandr pulseaudio zlib\n    * x11: dconf (Optional)\n* Dependency on `libxfconf` is removed. XFCE related detection now uses `libdbus` instead (Linux)\n* The default format of `Display` module is updated to `{width}x{height} @ {scale-factor}x in {inch}\", {refresh-rate} Hz`\n    * Replaced scaled resolution with scale factor for shorter texts and avoiding potential confusion.\n\nBugfixes:\n* Fixes linking on 32-bit Android (#1939)\n* Skips network interfaces without IPs unless MAC address is requested (#1949, LocalIP)\n* Fixes unexpected padding when setting `logo.width` with chafa logos (#1947, Logo)\n    * Regression from v2.51.0\n* Improves Wallpaper detection on XFCE4 (Wallpaper, Linux)\n* Ignores process `Relay(xxx)` when detecting terminal on WSL2 (Terminal, Linux)\n\nFeatures:\n* Enables X11-related info (i.e., WM/DE) detection on Android (Global, Android)\n    * This requires many dependencies. See above.\n* Adds scale factors detection for X11 (Display, Linux)\n    * X11 doesn't natively report scale factor as Wayland does. Instead, Fastfetch tries to detect `Xft.dpi` (DPI used by X FreeType for scaling fonts), which is usually set by the WM when DPI scaling is enabled.\n    * It's not always accurate. For example, XFCE4 has a separate config for text scaling, which is unaffected by the global DPI scaling setting.\n* Adds `display.fraction.trailingZeros: [always|never]` option for fraction formatting\n    * The default value of `display.fraction.ndigits` is changed from `-1` (unlimited) to `2` for usability.\n    * Used for displaying scale factor in Display module mentioned above, alongside other places for printing raw fraction numbers.\n* Informs users that module-specific CLI options are no longer supported and provide guidance for transitioning to JSON config\n* Adds CPU name detection support for IA64 (CPU, Linux)\n* Support Btrfs allocation profile detection (#1941, Btrfs, Linux)\n\n# 2.51.1\n\nBugfixes:\n* Fix building on macOS 14 or older; no functional changes (CPU, macOS)\n\n# 2.51.0\n\nChanges:\n* Fastfetch now requires [yyjson 0.12](https://github.com/ibireme/yyjson/releases/tag/0.12.0) to build when using `-DENABLE_SYSTEM_YYJSON=ON`.\n* The Disk module no longer shows hyperlink mountpoints by default, which cause issues on some real consoles (Disk)\n    * Instead, the custom key for the Disk module now supports `{mountpoint-link}` and `{name-link}` to show hyperlinks for mountpoints and names. For example, `{ \"type\": \"disk\", \"key\": \"Disk ({mountpoint-link})\" }` can be used to restore the old behavior.\n\nFeatures:\n* Adds `succeeded` module condition to JSONC config. When set to `false`, the module will only run if the last module failed (#1908)\n    * Useful for displaying fallback placeholders when a module fails. For example:\n```jsonc\n{\n    \"host\",\n    // If fastfetch fails to detect host info, display \"DIY PC\" instead\n    {\n        \"type\": \"custom\",\n        \"condition\": {\n            \"succeeded\": false\n        },\n        \"key\": \"Host\",\n        \"format\": \"DIY PC\"\n    }\n}\n```\n* By upgrading to yyjson 0.12, fastfetch now adds [JSON5](https://json5.org/) format support for configuration files (#1907)\n    * [JSON5](https://json5.org/) is a superset of JSONC that allows unquoted keys, single quotes, multi-line strings, etc., and is fully compatible with JSONC and strict JSON.\n    * To use JSON5, simply name your config file with a `.json5` extension. The `.jsonc` extension is still supported and used as the default extension for better IDE syntax highlighting support.\n* Fastfetch has been ported to [`GNU/Hurd`](https://www.gnu.org/software/hurd/) (#1895)\n    * Thanks to the efforts of @yelninei!\n* Built-in logos now honor `logo.width` (#1905)\n    * When its value is larger than the actual logo width, the logo will be padded with spaces to the right\n* Adds Trinity DE version detection (#1917, DE, Linux)\n* Adds formatted free and available disk size fields (#1929, Disk)\n    * `{size-free}`: free size of the disk\n    * `{size-available}`: available size of the disk\n    * See [askubuntu.com](https://askubuntu.com/questions/249387/df-h-used-space-avail-free-space-is-less-than-the-total-size-of-home) for the difference between free and available size\n* Adds [x86_64 micro-architecture level](https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels) detection (#1928, CPU)\n    * Useful when installing software that requires or is optimized for specific CPU features. E.g., [CachyOS](https://wiki.cachyos.org/features/optimized_repos/)\n    * Exposed via `{march}` in custom format\n* Adds [Aarch64 micro-architecture level](https://en.wikipedia.org/wiki/AArch64#Profiles) detection (CPU)\n    * Supported on Linux (including Android), macOS and Windows\n    * This is not fully accurate because there are many optional features across different levels, and not all levels are detectable.\n    * Exposed via `{march}` in custom format.\n* Adds shepherd detection support (InitSystem, Linux)\n\nBugfixes:\n* Refines GPU detection logic to correctly handle virtual devices (#1920, GPU, Windows)\n* Fixes possible default route detection failure when the route table is very large (#1919, LocalIP, Linux)\n* Fastfetch now correctly parses `hwdata/pci.ids` files alongside `pciids/pci.ids` on FreeBSD when detecting GPU names (#1924, GPU, FreeBSD)\n* Fixes twin WM detection (#1917, WM, Linux)\n* Various fixes for Android support\n    * Corrects WM name for Android (WM, Android)\n    * Fixes battery temperature detection when running in ADB (Battery, Android)\n    * Adds CPU and GPU temperature detection support (CPU, Android)\n\nLogos:\n* Adds AerynOS\n\n# 2.50.2\n\nBugfixes:\n* Fixes linglong package detection V2 (#1903, Packages, Linux)\n* Fixes building with `-DENABLE_SYSTEM_YYJSON=ON` (#1904)\n* Fixes `showMac` does not honor `defaultRouteOnly` (#1902, LocalIP, Linux)\n* Fixes failing to acquire default route on Linux in certain cases (#1902, LocalIP, Linux)\n\n# 2.50.1\n\nBugfixes:\n* Fixes percentage bar not displaying correctly in certain cases\n* Fixes linglong package detection on Debian 13 (#1899, Packages, Linux)\n\n# 2.50.0\n\nChanges:\n* Keys in JSON configuration files are now case-sensitive, as stated in v2.49.0.\n    * This is a breaking change, but it should not affect most users as long as your config file passes JSON schema validation.\n* All module config flags have been removed, as stated in v2.49.0.\n    * To configure modules via the command line, use: `echo '{\"modules\": [{\"type\":\"custom\",\"format\":\"Hello Fastfetch!\"}]}' | fastfetch -c -`.\n* The percent bar config `display.bar.*` options have been replaced with a more organized, nested object structure.\n    * `display.bar.charElapsed` has been renamed to `display.bar.char.elapsed`.\n    * `display.bar.charTotal` has been renamed to `display.bar.char.total`.\n    * `display.bar.borderLeft` has been renamed to `display.bar.border.left`.\n    * `display.bar.borderRight` has been renamed to `display.bar.border.right`.\n* The undocumented flag `--load-config` has been removed.\n    * Use `--config` or `-c` instead.\n* Flashfetch, a simplified fastfetch variant that used a hardcoded module list with direct function calls to reduce startup overhead, has been changed to a version that aims to match neofetch's behavior as closely as possible, for demonstration purposes.\n    * Flashfetch is intended to be built from source (like [st](https://st.suckless.org/)). We do not provide prebuilt binaries in distributions.\n\nFeatures:\n* Added support for reading JSON config from stdin using `--config -` or `-c -`.\n* Added `display.bar.border.{leftElapsed,rightElapsed}` for using the border as part of the bar content. (#1875)\n    * `display.bar.border: null` has been added as a shorthand to disable bar borders.\n* Added `display.bar.color.{elapsed,total,border}` to customize the color of the elapsed, total, and border sections of the percent bar.\n    * `display.bar.color: null` has been added as a shorthand to disable bar colors.\n* Improved Bedrock Linux detection (#1881, OS / Disk, Linux)\n* Added the command flag `--gen-config-full`, which generates a JSON config file containing all optional module options.\n* Improved the default IP address display when `localip.showAllIPs` is not set (LocalIP)\n    * For IPv4, the preferred source address (if detected) is shown.\n    * For IPv6, the first GUA or ULA that is not deprecated or temporary is shown.\n* Added support for interface speed detection on SunOS (LocalIP, SunOS)\n* Added detection support for Xlibre (#1888, WM, Linux)\n* Improved the accuracy of color detection (Cursor, macOS)\n* Improved the proformance of `Nix` package manager detection on macOS by porting optimizations form Linux port (#1893, Packages, macOS)\n\nBugfixes:\n* Fixed custom object inheriting a key from the previous custom object if the key is blank (#1477)\n* Fixed a possible segfault when parsing color strings in the JSON config (#1878)\n* Fixed GPU driver detection when DRM is used (GPU, FreeBSD)\n* Fixed default route detection on DragonFly BSD (LocalIP, DFBSD)\n* Fixed lliurex detection (#1882, OS, Linux)\n* Fixed compatibility with `-ffast-math` (#1894)\n* Fixed physical GPU sometimes being ignored (#1896, GPU, Windows)\n\nLogos:\n* Added ObsidianOS (#1890)\n\n# 2.49.0\n\nDeprecation Notice:\n* In fastfetch v2, the JSONC configuration format has been introduced, while command line configuration flags are kept for compatibility. Although they have the same effects, they use two different code paths, and as the number of flags grows, the codebase is becoming increasingly difficult to maintain.\n    * Removal of module config flags is planned for **v2.50.0**, which will also fix a long-standing issue #1477.\n    * Removal of most other config flags is also planned for later versions.\n* Keys of JSON configuration files will be all case-sensitive. Currently they are inconsistent. Planned for **v2.50.0**.\n\nChanges:\n* Due to more restrictive permissions in macOS Tahoe, SSID detection on macOS 26+ requires root privileges. `<redacted>` will be displayed otherwise.\n\nFeatures:\n* Improve `nouveau` driver support for `--gpu-driver-specific` (GPU, Linux)\n    * VRAM size detection\n    * GPU temperature detection\n    * Core count detection (when available)\n* Improve Scoop package manager detection (Packages, Windows)\n    * Support [`scoop-global`](https://github.com/ScoopInstaller/Install?tab=readme-ov-file#advanced-installation)\n    * Read Scoop's config file to find the installation path of Scoop\n* Improve ARM SoC detection (CPU, Android)\n    * Make SoC detection more lenient. Higher chance to match at the cost of accuracy.\n    * Add more Snapdragon SoC names\n* Support labwc WM version detection, used for XFCE4 on Wayland (WM, Linux)\n* Improve accuracy of GPU temperature detection for Intel dedicated GPUs on Windows (GPU, Windows)\n* Parse unicode escaped strings generated by qt5ct (#1864, Font, Linux)\n* Add `--{duration,percent,size,freq,temp}-space-before-unit [always|never]` options to add a space before the unit when printing duration, percent, size, frequency and temperature values\n* Add `--duration-abbreviation` to abbreviate duration values in custom format\n    * For example: `1 day, 2 hours, 3 mins` will be displayed as `1d 2h 3m`\n* Add `--percent-width` to pad the percent value with spaces to a fixed width\n    * For example: `--percent-width 3` will display ` 50%` instead of `50%`; useful for aligning percent values in custom format\n\nBugfixes:\n* Improve accuracy of Flatpak count detection (#1856, Packages, Linux)\n* Remove qi package manager support (#1858, Packages, Linux)\n* Fix LocalIP module on Windows (LocalIP, Windows)\n    * Fix default route detection when multiple network interfaces are connected\n    * Fix link speed calculation\n* Fix interface status when the interface is up but not connected (Wifi, Linux)\n* Fix variable names in custom format (#1861)\n    * `full-path` to `path` (Editor)\n    * `session` to `session-name` (Users)\n    * `name` to `project-name` (Version)\n* Fix wrong /s assignment in custom format (#1871, DiskIO)\n\nLogos:\n* Add `Aeon`\n* Remove `Evolinx`\n\n# 2.48.1\n\nFeatures:\n* Add support for detecting Openbox WM version (WM, Linux)\n* Improve reliability of child process spawning on Windows (Windows)\n* Add a new option `--packages-combined`, which combines related package managers into single counts (#1851, Packages)\n    * For example: if you have both `flatpak-system` and `flatpak-user` packages installed, they will be combined into a single `flatpak` count with `--packages-combined` enabled.\n* Add `modules[n].condition` to conditionally enable modules on different platforms\n    * Useful when sharing configuration files across platforms\n    * For example:\n```jsonc\n{\n    \"type\": \"custom\",\n    \"format\": \"This string will be printed on Intel macOS only\",\n    \"condition\": {\n        \"system\": \"macOS\", // Can be an array, optional\n        \"arch\": \"x86_64\"  // Can be an array, optional too\n    }\n}\n```\n\nBugfixes:\n* Revert the change of `posix_spawn` in v2.48.0 for Android and OpenBSD (Android / OpenBSD)\n    * Fix completion for Android 7 (Required by Termux)\n\n# 2.48.0\n\nFeatures:\n* Add support for detecting Fedora variants (#1830, OS, Linux)\n    * Currently supported variants: CoreOS, Kinoite, Sericea, Silverblue\n* Optimize GPU detection on Windows when `--gpu-driver-specific` is not used (GPU, Windows)\n    * Improve accuracy of GPU type detection. Previously it was guessed based on the dedicated vmem size, which causes issues on newer AMD integrated GPUs such as 9000 HX and AI 9 HX series. Supported on Windows 8.1 or later.\n    * Add support for generic frequency detection of GPU 3D engine on Windows 11 22H2 or later.\n    * Improve performance. GPU temperature detection is significantly improved when `--gpu-driver-specific` is not used.\n* Improve performance and security when spawning child processes by replacing `fork-exec` with `posix_spawn` (*nix)\n* Improve accuracy of sound device detection on macOS (Sound, macOS)\n* Trim leading and trailing whitespaces in disk serial numbers (PhysicalDisk)\n* Add `/etc/profiles/per-user` detection for Nix user packages (#1782, Packages, Linux)\n* Introduce `years` (whole years as integer), `days-of-year` (days since the last anniversary) and `years-fraction` (years as fraction, e.g. 1.5 means 1 year and 6 months) formatting placeholder to `Disk` (since disk creation), `Users` (since user login) and `Uptime` (since system boot) modules\n    * For example: `fastfetch -s disk --disk-key 'OS Installation' --disk-format '{years} years {days-of-year} days'`\n* Add `--fraction-ndigits` option to specify the number of digits after the decimal point when displaying ordinary fractional values\n    * Typically used with `{years-fraction}` above\n    * This option does not affect percentage values, sizes, etc, which are controlled by individual options.\n\nBugfixes:\n* Fix compilation issues when not using `-DBINARY_LINK_TYPE=dlopen`\n    * Regression from v2.47.0\n    * Note: this option was added for debugging purposes only and is not recommended for production use\n* Replace `MTLDevice::hasUnifiedMemory` with `MTLDevice::location` for GPU type Detection (GPU, macOS)\n    * This should resolve the issue where discrete GPUs were detected as integrated GPUs on Intel MacBooks with multi-GPU configurations.\n* Prevent text files from being loaded as image files (#1843, Logo)\n\nLogos:\n* Add Minimal System\n* Add AxOS\n* Rename Ada to Xray OS\n\n# 2.47.0\n\nFeatures:\n* Various improvements for Solaris / OpenIndiana support\n    * Support BIOS (UEFI or legacy) type detection (BIOS)\n    * Support physical disk detection (PhysicalDisk)\n    * Remove leading `-` from login shells (Shell)\n    * Improve GPU detection performance (GPU)\n        * Drop `libpciaccess` dependency\n    * Use native API to detect sound devices (Sound)\n        * Drop `PulseAudio` dependency\n* Improve DietPi OS and Raspberry Pi OS detection (#1816, OS, Linux)\n* Force reporting version 26 on macOS Tahoe (OS, macOS)\n* Append version string to Ubuntu variants (OS, Linux)\n* Improve performance of media detection for macOS 15.4+ (Media, macOS)\n* Increase `PROC_FILE_BUFFSIZ` to avoid possible short reads (Linux)\n    * Fix potential bugs in `DiskIO`, `NetIO` and `CPUUsage` modules\n* Improve accuracy of CPU usage calculations by including interrupt and softirq times (CPUUsage, Linux / *BSD)\n* Ignore `init` and `systemd` processes when detecting terminals (Terminal, Linux)\n* Improve accuracy of CPU usage detection on Windows 10+ with perflib, which matches values reported by Task Manager (CPUUsage, Windows)\n\nBugfixes:\n* Fix `pci.ids` file location (#1819, GPU, OpenBSD)\n* Fix compiling on FreeBSD when `libdrm` support is disabled (#1820, GPU, FreeBSD)\n\nLogos:\n* Improve visibility on white-background terminals for some logos by replacing white with the default foreground color\n    * According to Wikipedia, the default foreground color is implementation-defined. It's usually black for white themes and white for dark themes. However, some terminals, notably Konsole with the default theme, use a different color, which may cause issues with some logos.\n* Add Xubuntu\n\n# 2.46.0\n\nFeatures:\n* Support Rio terminal font detection (#1789, TerminalFont, Linux)\n* Support GPU detection by DRM on FreeBSD (GPU, FreeBSD)\n    * Enable by `--gpu-detection-method auto`\n    * Require proper DRM drivers installed and loaded\n* Support PowerPC CPU detection on NetBSD (#1802, CPU, NetBSD)\n* Support Aerospace WM detection (#1796, WM, macOS)\n* Improve Raspberry Pi OS for RPI5 detection (#1773, OS, Linux)\n* Support Linux Binary Compatibility detection on FreeBSD (#1786, Host, Linux)\n* Use `board-id` as board name if available (Board, macOS)\n    * Intel only\n* Support shared VRAM usage detection for AMD GPUs (GPU, Linux)\n* Use `perflib.h` instead of `pdh.h` for CPU temperature querying to get rid of `pdh.dll` dependency (#1787, CPU, Windows)\n* Support GPU info detection for old ATI radeon driver (#1810, GPU, Linux)\n* Add macOS 26 Tahoe support (macOS)\n    * Report macOS 26 code name (OS)\n    * Report Liquid Glass DE on macOS 26+ (DE)\n    * Detect Metal 4 support (GPU)\n\nBugfixes:\n* Fix packages counting by ignoring hidden folders (Packages, OpenBSD)\n* Fix Hyprland version detection (WM, FreeBSD)\n* Don't show `Please insert a disk into drive D:` error dialogs (#1805, Disk, Windows)\n* Hide `/boot/firmware` by default (Disk, Linux)\n\nLogos:\n* Rename Hydra Framework to HydraPwk (#1812)\n* Add AnushOS (#1806)\n* Add HarmonyOS (#1804)\n* Add GhostFreak (#1801)\n* Add TrueNAS Scale (#1795)\n* Add Fedora2_small (#1785)\n* Add xenia_old; update colors of xenia (#1797)\n* Improve colors of bedrock_small (#1790)\n* Add Kalpa Desktop (#1807)\n\n# 2.45.0\n\nFeatures:\n* Support OnePlus marketing name detection (#1768, Host, Android)\n* Recognize additional GPU vendors (GPU, Linux)\n* Support CTWM, FVWM and I3 window manager version detection (WM)\n* Support KDE version detection on *BSD (DE, FreeBSD)\n* Support `\"logo\": { \"type\": \"command-raw\" }` to run a command and display its output as logo (#1780, Logo)\n    * Useful for displaying custom logos generated by other programs such as `pokeget`: `{ \"type\": \"command-raw\", \"source\": \"pokeget random --hide-name\" }`\n    * Supported in JSONC config file only. `pokeget random --hide-name | fastfetch --file-raw -` should be used in shell.\n* Acquire SMBIOS information on DragonFly BSD from `/dev/mem`; legacy BIOS only (PhysicalMemory, DragonFly)\n* Support swap usage detection on DragonFly BSD (Swap, DragonFly)\n* Support `--swap-separate` to display detailed swap devices on separate lines (Swap)\n\nBugfixes\n* Fix MacBook Air model name (#1779, Host, macOS)\n* Don't ignore sshfs mountpoints (#1776, Disk, Linux / FreeBSD)\n* Fix dnf package count detection (#1777, Packages, Linux)\n\nLogos:\n* Add Starry Linux (#1771)\n* Add rhel_small (#1774)\n* Update color palette of voidlinux (#1775)\n* Add void2\n* Update Xenia Linux (#1783)\n\n# 2.44.0\n\nFeatures:\n* Add option `--disk-hide-folders` and `--disk-hide-fs` to hide specific mountpoints and filesystems in Disk module (Disk)\n    * `--disk-hide-folders` defaults to `/efi:/boot:/boot/efi` on Linux and *BSD. Previously these EFI-related folders were hardcoded to be hidden on Linux.\n\nBugfixes:\n* Fix Apple Terminal compatibility with `--stat` (macOS, #1755)\n* Ignore `/usr/bin/script` when detecting shell and terminal (Terminal / Shell, #1761)\n* Fix compatibility with KDE Plasma 6.4 which is in beta currently (Display, Linux, #1765)\n\nLogos:\n* Add Kylin (#1760)\n* Add UBLinux (#1764)\n\n# 2.43.0\n\nFeatures:\n* Support physical core count and package count detection on Solaris (CPU, SunOS)\n* Improve physical core count detection on FreeBSD (CPU, FreeBSD)\n* Add option to hide unknown GPUs (GPU)\n* Detect VRAM type of AMD GPUs on Linux (GPU, Linux)\n* Support playing media detection on macOS 15.4 (#1737, Media, macOS)\n    * Whether it works on newer versions is unknown\n* Detect player name for Windows UMP apps (Media, Windows)\n\nBugfixes:\n* Fix disk usage detection on 32-bit Linux (#1734, Disk, Linux)\n* Fix compiling on Asahi Linux (GPU, Linux)\n* Fix duplicated playback status (Media, Linux)\n* Don't show 255 in custom format when muted on macOS (#1750, Sound, macOS)\n* Remove shared memory detection for AMD GPUs, which doesn't work as expected (GPU, Windows)\n\nLogos:\n* new AthenaOS\n* add Hydra Framework\n\n# 2.42.0\n\nChanges:\n* Normalize the module name `Bios` to `BIOS` (#1721)\n    * No configuration file changes are required because fastfetch parses module names case-insensitively.\n\nBugfixes:\n* Disable disk type detection for virtual disks (PhysicalDisk, Linux, #1669)\n* Fix incorrect CPU temperature reporting (CPU, OpenBSD)\n* Fix setting `logo.chafa.symbols` in JSON configuration (Logo, #1709)\n* Fix non-normalized time display (Uptime, #1720)\n* Miscellaneous minor fixes\n\nFeatures:\n* Add CPU temperature detection support (CPU, SunOS)\n* Improve CPU frequency detection (CPU, NetBSD)\n* Add Wi-Fi detection support (Wifi, NetBSD)\n* Add Webcam detection support (Camera, OpenBSD)\n    * Requires root privileges\n\nLogos:\n* Remove GoralixOS logo (#1699)\n* Add Aurora logo (#1700)\n* Add Codex Linux logo (#1701)\n\n# 2.41.0\n\nChanges:\n* Due to [the deprecation](https://github.com/actions/runner-images/issues/11101), Linux x86_64 binaries are now built with Ubuntu 22.04 (Glibc 2.35, Debian 12)\n    * You can always build fastfetch yourself on your own. Please don't report bugs related to this change.\n\nFeatures:\n* Support physical core count detection on non-x86 platforms (CPU, Linux / FreeBSD)\n* Support CPU frequency detection on PPC64 (CPU, FreeBSD)\n* Support soar packages count detection (Packages, Linux)\n* Support `~` path expanding on Windows (Logo, Windows)\n* Support retrieving full user name (Title)\n    * Exposed with `--title-format '{full-user-name}'`\n* Improve CPU (thermal zone) temperature detection on Windows (CPU, Windows)\n    * Administrator privileges are no longer needed\n* Support base Wifi info detection on OpenBSD (Wifi, OpenBSD) \n    * To be tested\n* Support GPU temperature detection for Intel dGPU on Linux (GPU, Linux)\n    * To be tested\n* Add new ARM CPU part numbers (CPU, Linux)\n* Add base implementation of Bluetooth device detection (Bluetooth, NetBSD, #1690)\n* Some small improvements\n\nLogo:\n* Add anduinos\n* Add 2 more Alpine logos\n\n# 2.40.4\n\nBugfixes:\n* Fix loading presets config on Windows (Windows, #1682)\n    * Regression of v2.40.0\n* Remove the prefix `v` of Hyprland version on Arch Linux (WM, Linux)\n\n# 2.40.3\n\nBugfixes:\n* Fix loading example configs from presets directory (#1672)\n    * Regression of v2.40.2\n* Mark kitty image protocol support for warp terminal on macOS too (Logo)\n\n# 2.40.2\n\nChanges:\n* Since v2.40.0, we've been loading config files from the directory where the fastfetch binary is located. However, this approach may lead to loading unexpected files. For example, `fastfetch -c groups` would attempt to load `/usr/bin/groups`. Therefore, we now enforce the `.jsonc` extension when loading config files. Examples:\n    1. `-c filename`: loads `filename.jsonc`\n    2. `-c filename.jsonc`: loads `filename.jsonc`\n    3. `-c filename.json`: loads `filename.json` and enforces strict JSON syntax (no comments or trailing commas)\n    4. `-c filename.ext`: loads `filename.ext.jsonc` (`.jsonc` extension is enforced)\n\nFeatures:\n* Mark kitty image protocol support for warp terminal (Logo)\n* Documentation improvements\n\n# 2.40.1\n\nBugfixes:\n* Fix compiling error on old intel platform (TPM, macOS)\n* Fix `--file-raw -` no longer working (Logo, #1659)\n    * Regression of v2.40.0\n\n# 2.40.0\n\nChanges:\n* In `key-format` of `LocalIP` module, `{name}` has been renamed to `{ifname}` for consistency (LocalIP, #1639)\n\nFeatures:\n* Support Warp Terminal font detection (TerminalFont, Windows)\n* Support more AMD GPU information using ADL SDK, including memory type detection (GPU, Windows)\n* Support Intel dGPU memory type detection (GPU, Windows)\n* Support Nvidia VMEM type detection via NVAPI (GPU, Windows, #993)\n* Support Boot manager detection for OpenBSD and NetBSD (Bootmgr, OpenBSD / NetBSD)\n* Use `SystemConfiguration` for DNS entries detection (DNS, macOS)\n* Add `systemd-resolved` support for DNS module (DNS, Linux, #1646)\n* Improve performance and accuracy of Wifi detection on FreeBSD using ioctl (Wifi, FreeBSD)\n* Support remaining time reporting for batteries on NetBSD (Battery, NetBSD)\n* Add new Mac models support (Host, macOS)\n* Load config from fastfetch binary path with `--config` option (#1649)\n* Support TPM detection on macOS (TPM, macOS)\n* Support IPv6 client address report (Users, Linux / Windows)\n* Support default route detection for IPv6 (LocalIP, Linux)\n* Round seconds to the nearest minute to match the behavior of `uptime` command (Uptime)\n\nBugfixes:\n* Fix `outputColor` not working when `length` is set in Separator module (#1644)\n* Fix CPU detection on PowerPC platforms (#1640, CPU, Linux)\n* Fix battery manufacture date detection (Battery, macOS)\n* Fix battery critical state detection (Battery, Linux)\n* Fix Warp Terminal PID detection (Terminal, macOS)\n* Remove disk creation time detection support on SunOS as ctim is file status change timestamp, not creation time (Disk, SunOS)\n* Fall back to KDGKBINFO if `usbhid` fails (Keyboard, FreeBSD)\n* Fix multiple paging file support (Swap, Windows)\n* Fix memleaks, code smells in multiple modules\n* Fix boot time calculation on NetBSD (Uptime, NetBSD)\n* Temporarily fix Hyprland version detection (WM, Linux, #1657)\n\nLogo:\n* Fix opensuse-tumbleweed_small (#1636)\n* Change WiiLinuxNgx to more generic name with aliases Wii-Linux and WiiLinux (#1633)\n* Change name of Xray-OS to Ada (#1651)\n* Change Nexa Linux logo (#1653)\n\n# 2.39.1\n\nBugfixes:\n* Fix a regression that PublicIP detection fails randomly (PublicIP, #1629)\n\n# 2.39.0\n\nChanges:\n* OSMesa backend for OpenGL detection is removed (#1618)\n* Fastfetch no longer tries to use the private framework `Apple80211` to acquire SSID for Wifi module, which is only useful for macOS Sonoma (Wifi, macOS)\n\nFeatures:\n* Improve accuracy of HDR support on Windows 11 24H2 (Display, Windows)\n* Improve performance of SSID detection on macOS Sequoia (Wifi, macOS, #1597)\n* Support warp terminal version detection on Windows (Terminal, Windows)\n* Support default route detection on OpenBSD & DragonFly BSD (LocalIP, OpenBSD / DragonFly)\n* Improve bash completion script\n* Improve performance of networking (PublicIP / Weather)\n* Support pkgsrc package manager detection on Linux (Packages, Linux)\n\nLogo:\n* Add Common Torizon OS\n* Change FoxOS to WolfOS\n* Add Bredos\n* Add NetBSD2\n\n# 2.38.0\n\nBugfixes:\n* Fix empty battery slots handling (Battery, Haiku, #1575)\n* Fix `{day-pretty}` output in custom format (DateTime, Windows)\n* Fix VanillaOS detection (OS, Linux)\n* Fix secure boot testing (Bootmgr, Linux, #1584)\n* Fix the SI unit \"kB\" in help message (#1589)\n* Fix segfault on macOS 10.15 when using the binary downloaded from Github Releases (Camera, macOS, #1594)\n\nFeatures:\n* Support Chassis module in macOS (Chassis, macOS)\n* Allow customize key format with kernel name and distro name (OS)\n* Add missing `{icon}` in custom key format (Battery)\n* Add missing `{mountpoint}` and `{mount-from}` in custom output format (Disk, #1577)\n* Support percentage num & bar in custom format (GPU, #1583)\n* Support `pisi` package manager detection (Packages, Linux)\n* Support termite terminal font detection (TerminalFont, Linux)\n* Report monitor type in Brightness module (Brightness)\n\nLogo:\n* Add `opensuse-tumbleweed_small`\n* Add `Bedrock_small`\n* Add `fastfetch`\n* Remove some unnecessary distro names\n\n# 2.37.0\n\nChanges:\n* Option `--escape-bedrock` is removed. The function is always enabled now.\n\nFeatures:\n* Support for Haiku is greatly improved (Haiku)\n    * CPU, GPU, Disk, Sound, Terminal, Terminal Font, Init System, Battery, Mouse, Keyboard, NetIO, CPU Usage, Physical Disk and OpenGL should work on Haiku now\n    * SMBIOS related modules (Host, Bios, Board, Chassis, Physical Memory) should work in platforms with legacy BIOS system.\n    * Support for Gamepad and Bluetooth are WIP.\n    * Some bugs are found and fixed.\n* Remove `python-requests` dependency in `scripts/gen-*.py`.\n* Add cmake option `-DENABLE_EMBEDDED_AMDGPUIDS=BOOL` (disabled by default)\n    * If enabled, fastfetch will embed the newest [`amdgpu.ids`](https://gitlab.freedesktop.org/mesa/drm/-/blob/main/data/amdgpu.ids?ref_type=heads) file into fastfetch binary.\n* Weather module now honors `display.temp.unit` option (#1560, Weather)\n* Support Physical Memory module in NetBSD (PhysicalMemory, NetBSD)\n    * Requires root permission\n* Improve non-intel CPU detection in NetBSD (#1573, CPU, NetBSD)\n\nBugfixes:\n* Fix building in macOS 10.13 (GPU, macOS)\n* Properly round percent values when detecting volume (#1558, Sound)\n* Fix Physical Memory module doesn't work in `--format json` mode\n* Add some missing variable inits (GPU, Linux)\n* Fix `--localip-default-route-only false` not working with `--gen-config` (#1570, LocalIP)\n\nLogo:\n* Update Rosa linux\n* Add Haiku2\n\n# 2.36.1\n\nChanges:\n* To use [the native arm64 runner of Github Action](https://github.blog/changelog/2025-01-16-linux-arm64-hosted-runners-now-available-for-free-in-public-repositories-public-preview/), Linux aarch64 binary is built with Ubuntu 22.04 (Glibc 2.35, Debian 12).\n\nBugfixes:\n* Chimera Linux logo is now displayed correctly (#1554, Logo)\n    * Regression of 2.36.0\n* Fix building on Haiku\n\nLogo:\n* Fix ALT Linux\n\n# 2.36.0\n\nBugfixes:\n* Trim leading slash for login shells (Shell, OpenBSD)\n* Prefer SOC name if available over CPU name (CPU, Linux)\n\nFeatures:\n* Use kernel API to detect sound devices (Sound, NetBSD)\n* Use sndio for sound server detection on OpenBSD (Sound, OpenBSD)\n* Add minimal implementation for Haiku (#1538, Haiku)\n* Support CPU & GPU temperature detection for M4x (CPU / GPU, macOS)\n* Support VMEM size detection for old Nvidia cards (GPU, Linux)\n* Use [recommendedMaxWorkingSetSize](https://developer.apple.com/documentation/metal/mtldevice/recommendedmaxworkingsetsize) as total GPU mem size (GPU, macOS)\n* Support Physical core count and CPU package count detection for loongarch (CPU, Linux)\n* Split ID_LIKE when used for distro matching (#1540, Logo)\n* Capitalize `{type}`'s first letter in custom format (#1543, Display)\n* Support model name detection for s390x (CPU, Linux)\n* Support more Armbian variants detection (#1547, OS, Linux)\n* Support the syntax of `{$ENV_VAR}` in custom format, which will be replaced by the value of the environment variable `ENV_VAR` (#1541)\n    * This is another way to pass 3rd-party data to fastfetch besides `Custom` module.\n* Improve performance of Tilix version detection (Terminal, Linux)\n\nLogo:\n* Update arch_old\n* Add Nexa Linux\n* Add filotimo\n* Update some distro names\n\n# 2.35.0\n\nBugfixes:\n* Suppress output of EGL again (#1513, GPU, Linux)\n    * Regression of 2.34.0\n\nFeatures:\n* Show SOC name reported in `cpuinfo` if available (#1510, CPU, Linux)\n* Change package manager name of NetBSD from `pkg` to `pkgsrc` (#1515, Packages, NetBSD)\n* Detect SOC name on RISCV (#1519, CPU, Linux)\n* Report marketing name of new QS8Es (CPU, Android)\n* Acquire acquire more os info from lsb-release if missing from os-release (#1521)\n* CMake: add option `-DCUSTOM_LSB_RELEASE_PATH` to specify the path of `lsb-release` file\n    * `-DCUSTOM_OS_RELEASE_PATH` has been supported since `v2.11.4`\n* Report more SOC names on Android (CPU, Android)\n* Support duration printing in custom format (Disk / Users)\n    * For example:  \n```jsonc\n{\n    \"modules\": [\n        {\n            \"key\": \"OS Installation Date\", // No longer need to write bash scripts\n            \"type\": \"disk\",\n            \"folders\": \"/\", // Different OSes may need to specify different folders\n            \"format\": \"{create-time:10} [{days} days]\" // Reports the creation date of the root folder\n        }\n    ]\n}\n```\n\nLogo:\n* Add Arch_old\n* Update key color of NetBSD_small\n* Fix OpenBSD and many other ascii logos (#1522)\n\n# 2.34.1\n\nAn early release to fix KDE Plasma 6.3 compatibility. Hopefully it can be accepted by package managers before KDE 6.3 is officially released.\n\nTo package managers: if you find fastfetch bugs, it's highly appreciated if you can report them to the upstream, so that all users can benefit from the fix, instead of maintaining out-of-tree patches. Thanks!\n\nFeatures:\n* Report vendor name when detecting GPUs by OpenGL\n    * Note: the vendor name is actually the creator of the OpenGL driver (such as `Mesa`) and may not be the same as the GPU vendor.\n\nBugfixes:\n* Fix Ghostty termfont detection (#1495, TerminalFont, macOS)\n* Fix compatibility with KDE Plasma 6.3 (#1504, Display, Linux)\n* Make memory usage detection logic consistent with other systems (Memory, OpenBSD / NetBSD)\n* Report media file name if media title is not available (Media)\n* Fix max frequency detection for CPUs with both performance and efficiency cores (CPU, FreeBSD)\n\nLogo:\n* Add HeliumOS\n* Add Oreon\n* Update SnigdhaOS\n\n# 2.34.0\n\nChanges:\n* We now print distro pretty name if available (OS)\n    * This is a long requested feature. However, it may break some distros. File a bug with the content of `/etc/os-release` if it breaks your distro.\n\nBugfixes:\n* Fix thunderbolt version of new MBPs (#1465, Host, macOS)\n* Fix backlight name detection on FreeBSD (Brightness, FreeBSD)\n* Fix Terminal detection when running fastfetch in `pk-command-not-found` (#1467, Terminal, Linux)\n* Relax detection of terminals in NixOS (#1479, Terminal, Linux)\n    * Should fix konsole, ghostty and maybe others\n* Fix core count output in multi-package platforms (CPU)\n* Don't suppress the output of `preRun` (#1489)\n* Fix battery percentage detection (Battery, NetBSD)\n\nFeatures:\n* Support ghostty terminal font detection (TerminalFont, Linux / macOS)\n* Support `kitty-icat` image protocol, which uses `kitten icat` to generate image data\n    * Pros: support tmux; support gif animations; good performance\n    * Cons: due to the limitation of `kitten icat`, we need to clear the screen before displaying the image logo\n* Support WM version detection (WM)\n    * In Linux, Hyprland & sway are supported currently\n* Improve performance when stdout is redirected (TerminalSize)\n* Report thermal zone temp if CPU temp is not available (CPU, Linux)\n* Report sound server (Pipewire or PulseAudio) if available (#1454, Sound, Linux)\n* Enable OpenGL & OpenCL detection on Android (OpenGL / OpenCL, Android)\n* Detect & report MediaTek Dimensity 9000+ SOC name (CPU, Android)\n* Support appman (am-user) package manager detection (Packages, Linux)\n\nLogo:\n* Add Lubuntu\n* Update Xray_os\n* Add SnigdhaOS\n* Add Rhino Linux\n\n# 2.33.0\n\nChanges:\n* Introduce a new CMake flag `-DBUILD_FLASHFETCH=OFF` to disable building flashfetch binaries\n    * Package managers are encouraged to enable it. See <https://github.com/fastfetch-cli/fastfetch/discussions/627> for detail\n\nBugfixes:\n* Fix interconnect type detection (#1453, PhysicalDisk, Linux)\n    * Regression of v2.28\n* Don't report `proot` as terminal (Terminal, Android)\n* Remove a debug output (DiskIO, OpenBSD)\n* Fix media detection for some players (#1461, Media, Linux)\n    * Regression of v2.32\n\nFeatures:\n* Use `$POWERSHELL_VERSION` as PowerShell version if available (Shell, Windows)\n    * Fetching Windows PowerShell version can be very slow. Add `$env:POWERSHELL_VERSION = $PSVersionTable.PSVersion.ToString()` in `$PROFILE` before running `fastfetch` to improve the performance of `Shell` module\n* Add support for ubuntu-based armbian detection (#1447, OS, Linux)\n* Improve performance of Bluetooth detection (Bluetooth)\n    * We no longer report disconnected bluetooth devices in `--format json` when `--bluetooth-show-disconnected` isn't specified\n* Support brightness level detection for builtin displays (Brightness, OpenBSD / NetBSD)\n    * Requires root permission on OpenBSD\n* Support battery level detection (Battery, OpenBSD / NetBSD)\n* Support CPU temperature detection in NetBSD (CPU, NetBSD)\n* Hard code path of `libvulkan.so` for Android\n    * So that users don't need to install the vulkan-loader wrapper of termux\n\nLogo:\n* Add NurOS\n* Add GoralixOS\n\n# 2.32.1\n\nA hotfix for OpenBSD. No changes to other platforms.\n\nBugfixes:\n* Fix package count detection on OpenBSD (Packages, OpenBSD)\n\n# 2.32.0\n\nBugfixes:\n* Fix `pci.ids` file location on OpenBSD (GPU, OpenBSD)\n    * It's normally unused because enumerating PCI devices on OpenBSD requires root privileges\n* Fix bssid formatting (Wifi, Linux)\n* Fix Linux Lite distro detection (#1434, OS, Linux)\n* Suppress XE driver warnings from Mesa (#1435, OpenGL, Linux)\n* Fix format parameter name (#1443, Version)\n* Don't report useless information when Wifi is disabled (Wifi, FreeBSD)\n    * Currently there are issues when the SSID contains whitespaces. More fixes are expected in the future.\n* Always use physical size reported by X11 server to avoid inconsistent results (#1444, Display, Linux)\n\nFeatures:\n* Randomly select one if the logo source expands to multiple files (#1426, Logo)\n* Report mac product name when running Linux in MacBook (Host, Linux / FreeBSD)\n* Use screen size reported in DTD if make sense (Display)\n* Detect Virtualized Apple Silicon CPUs (CPU, Linux)\n* Add detection support for fvwm and ctwm (WM, OpenBSD / NetBSD)\n* Add Armbian-unofficial detection (OS, Linux)\n* Prefer surfaceless display when connect EGL (OpenGL)\n* Improve accuracy of WM detection on FreeBSD (WM, FreeBSD)\n* Add ratpoison window manager (WM, Linux)\n\nLogo:\n* Update Linux Lite\n* Add Serpent OS\n* Add Ultramarine Small\n* Update Debian\n\n# 2.31.0\n\nBugfixes:\n* Improve performance of media detection; fix musikcube detection (Media, Linux)\n    * After the change, `general.processingTimeout` will also control the timeout of dbus remote calls\n* Fix invalid variable names (#1408, Users)\n* Change physical size detection to use basic display parameters (#1406)\n* Fix possible sigfaults when detecting displays (#1393)\n* Fix Nvidia card type detection\n* Fix wl-restart parsing (#1422, WM, Linux)\n* Fix syntax error in completion file (#1421)\n* Fix hunging when using `ssh-agent` as command text (#1418, Command, macOS)\n\nFeatures:\n* Remove support of xcb & xlib and xrandr extension is always required (Display)\n* Support preferred resolution & refresh rate detection\n    * On macOS there is no preferred resolution reported and maximum available resolution is reported instead.\n    * `--display-format {preferred-width}x{preferred-height}@{preferred-refresh-rate}`\n* Report scale factor in custom format (Display)\n    * `--display-format {scale-factor}`\n* Detect current Wi-Fi channel and maximum frequency (Wifi)\n* Report processor package count (#1413, CPU)\n* Remove duplicate whitespaces in CPU name\n* Support sakura terminal version & font detection (Terminal / TerminalFont, Linux)\n\nLogo:\n* Fix LMDE\n* Update MidOS\n* Add Windows Server 2025\n\n# 2.30.1\n\nBugfixes:\n* Fix the destination where `fastfetch.1` is generated (#1403)\n\n# 2.30.0\n\nChanges:\n* Percent: bar type must be enabled in `percent.type` before using percent bar in custom format\n\nFeatures:\n* Port to MidnightBSD; add mport package manager support\n* Support bluetooth battery detection for macOS and Windows (Bluetooth, macOS / Windows)\n* Support M4 model detection (Host, macOS)\n* Support CPU temperature detection on OpenBSD (CPU, OpenBSD)\n* Display Android icon in Android devices (OS, Android)\n* Support qi package manager detection (Packages, Linux)\n* Detect WM / DE by enumerating running processes (WM / DE, NetBSD)\n* Generate manual pages from `help.json` (Doc)\n* Detect marketing name of vivo smartphone (Host, Android)\n* Add txDrops detection if supported (NetIO, *BSD)\n* Support tilix version detection (Terminal, Linux)\n* Support percent type config in module level. Example: \n\n```jsonc\n{\n    \"type\": \"memory\",\n    \"percent\": {\n        \"green\": 20, // [0%, 20%) will be displayed in green\n        \"yellow\": 40, // [20, 40) will be displayed in yellow and [40, 100] will be displayed in red\n        \"type\": [ // Display percent value in monochrome bar, same as 10\n            \"bar\",\n            \"bar-monochrome\"\n        ]\n    }\n}\n```\n\nBugfixes:\n* Don't display `()` in key if display name is not available (Display)\n* Fix & normalize bluetooth mac address detection (Bluetooth, macOS / Windows)\n* Don't print index in multi-battery devices (Battery)\n* Fix segfault in macOS (#1388, macOS)\n* Fix `CFStringGetCString() failed` errors (#1394, Media, macOS)\n* Fix CPU frequency detection on Apple M4 (#1394, CPU, macOS)\n* Fix exe path detection on macOS (Shell / Terminal, macOS)\n* Fix logo fails to load from symlinked files on macOS (#1395, Logo, macOS)\n* Fix 32-bit truncation (NetIO, macOS)\n\nLogos:\n* Fix Lilidog\n* Add MidnightBSD\n* Add Unifi\n* Add Cosmic DE\n* Update openSUSE Tumbleweed\n\n# 2.29.0\n\nChanges:\n* Due to [the upstream removal of MSYS2 CLANG32 environment](https://www.msys2.org/news/#2024-09-23-starting-to-drop-the-clang32-environment), we dropped fastfetch-windows-i686 support. v2.27.1 was the last version supporting it.\n    * Note: fastfetch built with MSVCRT has known bug that DateTime module doesn't work because of its bad support of [strftime](https://en.cppreference.com/w/c/chrono/strftime). Don't use it.\n\nFeatures:\n* Port to NetBSD and DragonFly BSD\n    * Fastfetch now supports all major BSD variants\n* Support DiskIO, NetIO, GPU and Users module on OpenBSD\n* Report SD8E SOC name (CPU, Android)\n* On Windows, try loading dlls from current exe path (Windows)\n    * Fix Media module when installed with winget\n\nBugfixes:\n* Fix the VIM version detection on Ubuntu (Editor, Linux)\n* Improve performance of OS version detection on Proxmox (#1370, OS, Linux)\n\nLogo:\n* Update OpenSuse Tumbleweed\n* Add XCP-ng\n* Add SummitOS\n* Add Lilidog\n* Update PikaOS\n* Update OpenSUSE Leap\n* Update aperture\n\n# 2.28.0\n\nFeatures:\n* Add new module `Mouse` and `Keyboard` which display connected mice and keyboards\n* Support remaining time detection (Battery)\n* Report if AC is connected (Battery, Linux)\n* Report platform API used for display detection for debugging (Display)\n* Report Wine version when running in Wine (Kernel, Windows)\n* Add option `waitTime` in modules `CPUUsage`, `DiskIO` and `NetIO`\n\nBugfixes:\n* Fix used memory size detection (Memory, OpenBSD)\n* Don't report invalid fragmentation percentage when fails to detect it (Zpool)\n* Fix unexpected errors when running fastfetch in parallel (#1346, Windows)\n* Don't report obviously invalid temperature values (PhysicalDisk, Linux)\n\nLogos:\n* Add eweOS\n* Add MidOS\n* Update XeroArch\n\n# 2.27.1\n\nBugfixes:\n* Fix invalid display name detection on GNOME, wayland (Display, Linux)\n\n# 2.27.0\n\nChanges:\n* We now print `\"` instead of `″` when displaying diagonal length in inches, so that the character can be correctly displayed in Linux console (Display)\n* All detection code of `monitor` module is merged into `display` module. Now `monitor` just prints the same information as `display` with different format. Notably:\n    * The resolution reported by `monitor` module is now current resolution instead of native / maximum resolution. PPI is calculated based on current resolution too.\n    * The refresh rate reported by `monitor` module is the current refresh rate.\n\nFeatures:\n* Add basic, highly experimental support of OpenBSD (OpenBSD)\n* Improve support for Raspberry pi (CPU / GPU, Linux)\n* Detect SOC name, instead of displaying components used in the SOC, if available (CPU, Linux)\n* Add option `--brightness-compact` to display multiple brightness values in one line (Brightness)\n* Add `day-pretty` (#1305, DateTime)\n* Support network interface adapter flag detection (#1315, LocalIP)\n    * Enable it with `--localip-show-flags`\n\nBugfixes:\n* Remove trailing newline in GPU name for Raspberry pi (#1303, GPU, Linux)\n* Fix a possible buffer overflow (GPU, Linux)\n* Fix CPU temp incorrectly reported as 0 celsius (#1308, CPU, Linux)\n* Correctly report `TPM device is not found` error (#1314, TPM, Windows)\n* Fix errors when triggering shell completion with python3 uninstalled (#1310)\n    * To package managers: as shell completion scripts of fastfetch use python3, it should be added as an optional dependency of fastfetch\n* Fix possible crashes when detecting term font of kitty (#1321, TerminalFont, Linux)\n\nLogos:\n* Add XeroArch\n* Add ValhallaOS\n\n# 2.26.1\n\nFeatures:\n* Allow to disable pacstall packager detection in CMake\n\nBugfixes:\n* Fix uninitialized variables (GPU, Windows)\n\n# 2.26.0\n\nChanges:\n* To be consistent to other platforms, CPU frequency detection on Linux no longer checks `bios_limit`\n\nFeatures:\n* Detect GPU index (#1267, GPU)\n* Count Flatpak runtime packages (#1085, Packages, Linux)\n* Support pacstall package manager (Packages, Linux)\n* Support CU core count, max frequency, VMEM usage detection for AMD cards on Linux (GPU, Linux)\n    * Requires `--gpu-driver-specific`\n* Support EU core count, VMEM size detection Intel cards on Linux (GPU, Linux)\n    * Requires `--gpu-driver-specific`. VMEM usage detection requires root permissions.\n* Add new module `TPM` to print TPM (Trusted Platform Module) version if available (TPM)\n* Support GPU driver version detection (GPU, macOS)\n* Add new CMake option `-DENABLE_EMBEDDED_PCIIDS=ON`.\n    * If enabled, fastfetch will download the newest [`pci.ids`](https://pci-ids.ucw.cz/) file, [transform it into C code](https://github.com/fastfetch-cli/fastfetch/blob/dev/scripts/gen-pciids.py) and compile it into fastfetch binaries.\n\nBugfixes:\n* Fix font size detecton of foot terminal (#1276, TerminalFont, Linux)\n* Ignore `su` and `sudo` when detecting terminal (#1283, Terminal, Linux)\n* Always print inches in integer (Display)\n* Fix Wifi connection protocol detection on macOS Sequoia (Wifi, macOS)\n* Fix hanging when font name is long when detecting kitty term font (#1289, TerminalFont)\n* Detect all enabled or connected connectors (#1301, Display, Linux)\n\nLogos:\n* Add FoxOS\n* Add GXDE OS\n\n# 2.25.0\n\nFeatures:\n* Moore Threads GPU add support to query number of cores (#1259, GPU)\n* Cache detection result based on last modification time (Packages)\n* Add cmake options to disable certain package managers at compile time\n    * Package managers are encouraged to disable some package managers by passing `-DPACKAGES_DISABLE_` when running `cmake`. For example, when building for Arch Linux, `-DPACKAGES_DISABLE_APK=ON -DPACKAGES_DISABLE_DPKG=ON -DPACKAGES_DISABLE_RPM=ON ...` should be specified.\n    * See all available options by [running `cmake -L | grep PACKAGES_DISABLE_`](https://github.com/fastfetch-cli/fastfetch/blob/dev/CMakeLists.txt#L91)\n    * This option does NOT remove the detection code. It just disables the detection at runtime. One can still use `--packages-disabled \"\"` to enable all package managers.\n* Add new option `--show-localip-{speed,mtu}` (LocalIP)\n* Add new module `Btrfs`, which prints all mounted Btrfs volumes, like `Zpool` module (#1262, Linux)\n* Improve Wifi module support for macOS Sequoia (Wifi, macOS)\n    * Currently it uses `system_profiler` which costs about 2 seconds on my MBP. I suggest disabling it for now until a better solution is found.\n\nBugfixes:\n* Fix invalid CPU temperature detection on FreeBSD (#1260, CPU, FreeBSD)\n* Remove `showPeCoreCount` support on FreeBSD (#1260, CPU, FreeBSD)\n* Don't use Wifi speed as Ethernet speed (LocalIP, FreeBSD)\n* Fix compiling with old linux headers (Camera, Linux)\n* Fix detecting public ipv6 address (PublicIP, Windows)\n\nLogo:\n* Fix parrot logo detection\n* Rename TorizonCore to Torizon OS\n\n# 2.24.0\n\nChanges:\n* Support of `--lib-XXX` is removed\n    * If fastfetch fails to load some `.so` `.dylib` libraries, `LD_LIBRARY_PATH` should be used.\n\nFeatures:\n* Support sixel image protocol on Windows (Logo, Windows)\n    * Requires imagemagick7 to be installed. MSYS2 is recommended.\n* Improve terminal query on Windows (Windows)\n    * TerminalSize, TerminalTheme\n* Detect more ARM microarchitectures and SOC names (CPU, Linux)\n* Detect the number of online cores (CPU, FreeBSD)\n* Support board name detection for Asahi Linux (Board, Linux)\n* Add new option `--command-param` to customize the parameters when running shell\n* Support syntax of sub string in `--<module>-format`: `{variable~startIndex,endIndex}`\n    * See `fastfetch -h format` for detail\n\nBugfixes:\n* Fix tests building when system yyjson is used (#1244)\n* Fix dinit detection; support dinit version detection (#1245, InitSystem, Linux)\n* Fix signal quality, refresh rate and maybe others in custom format (#1241)\n* Fix boot time calculation (#1249, Uptime, Linux)\n* Fix custom format for boolean values\n    * `{?false-value}This should not print{?}{?true-value}This should print{?}` will print `This should print`\n* Fix possible hanging when running fastfetch in screen 5.0 (TerminalTheme, macOS)\n\nLogos:\n* Add Lliurex\n\n# 2.23.0\n\nFeatures:\n* Support unity version detection (DE, Linux)\n* Print model name in Battery keys if available (Battery)\n* Add module `Zpool`\n* Improve performance (Shell / Terminal, Linux)\n* Support syntax of padded strings in `--<module>-format`. `{variable<padlength}` and `{variable>padlength}` are supported.\n    * If pad length is greater than the length of the variable, the variable will be padded with spaces.\n        * `fastfetch -l none -s command --command-text 'echo 12345' --command-format 'output({1<20})'` prints `Command: output(12345               )`\n        * `fastfetch -l none -s command --command-text 'echo 12345' --command-format 'output({1>20})'` prints `Command: output(               12345)`\n    * If pad length is less than the length of the variable, the variable will be truncated.\n\nBugfixes:\n* Fix broken `--list-presets`\n* Update zsh completion script\n* Don't print `*` if `defaultRouteOnly` is set (NetIO)\n* Fix Camera module incorrectly disabled on FreeBSD (Camera, FreeBSD)\n* Fix hanging on screen 5.0 (Terminal)\n* Improve image logo support on Windows (Logo, Windows)\n\nLogos:\n* Update AmogOS\n* Add Magix\n* Make ubuntu logo colorable\n* Add Steam Deck Logo\n* add Huawei Cloud EulerOS\n\n# 2.22.0\n\nFeatures:\n* Small performance improvements (Terminal, Editor)\n* Improve arm32 and loongarch support (CPU, Linux)\n* Ignore the parent process if env `$FFTS_IGNORE_PARENT` is set to `1` (Shell)\n* Add code name of Apple M4 (CPU, Linux)\n* Add ethernet speed rate detection support (LocalIP)\n* Add zsh completion script\n* Add Linglong package manager detection support (Packages, Linux)\n\nBugfixes:\n* Fix building on macOS 10.14\n* Fix tmux in linux TTY (Colors)\n* Fix hang in WSL when custom format is used (Disk, Linux)\n* Fix `/proc/loadavg` parsing (Loadavg, Linux)\n* Disable use of `LC_NUMERIC` locale settings to fix parsing of decimal numbers\n* Fix possible segfault (DiskIO, Linux)\n* Honor `preciseRefreshRate` in custom format (Display)\n\nLogos:\n* Add Lingmo OS\n* Add Sleeper OS\n\n# 2.21.3\n\nBugfixes:\n* Fix bad Intel Arc GPU name detection, which was supposed to be fixed in the last version but the change was reverted accidentally (#1177, GPU, Linux)\n* Fix arm32 CPU name detection no longer work. Regression of 2.21.2 (CPU, Linux)\n\n# 2.21.2\n\nFeatures:\n* Support `--stat <num_in_ms>` to display long running modules in yellow or red\n\nBugfixes:\n* Fix bad Intel Arc GPU name and type detection (GPU, Linux)\n* Fix uninited struct fields (GPU, Linux)\n* Skip cpu model smbios detection on ARM platforms (CPU, Linux)\n* Always use `CurrentControlSet` instead of `ControlSet001` when querying registry (Windows)\n* Fix NVIDIA GPUs are missing in GPU detection sometimes (GPU, Windows)\n* Fixing detection of `pthread_timedjoin_np` (Linux)\n\nLogos:\n* Add HyprOS\n* Add GoldenDog Linux\n\n# 2.21.1\n\nHotfix for a regression that breaks WM detection when running `startx` from TTY (Regression from 2.21.0, #1172 / #1162)\n\nChanges:\n* On Linux, FreeBSD and SunOS, a new recommended dependency `libelf` is introduced to extract strings in ELF binary, used for\n    * st term font detection when the term font is compiled directly into the binary\n    * fast path of systemd version detection\n\nFeatures:\n* Improve performance of\n    * kitty version detection (Terminal, Linux)\n    * st term font detection (TerminalFont, Linux)\n    * systemd version detection (InitSystem, Linux)\n\nBugfixes:\n* Fix building error without `linux/wireless.h` (Wifi, Linux)\n* Fix wrong GPU max frequency on Asahi Linux (GPU, Linux)\n* Don't rely `$XDG_SESSION_TYPE` for detecting wm protocol (#1172 / #1162, WM, Linux)\n* Fix light color doesn't work on Linux console (Colors, Linux)\n* `LC_ALL`, if set, overrides every other locale-related environment variable (Locale)\n* Increase timeout of DBus calls (Linux)\n\nLogos:\n* Add vanilla_small and vanilla2\n* Add LFS (Linux From Scratch)\n\n# 2.21.0\n\nChanges:\n* We no longer use `libnm` for Wifi detection on Linux. Instead, we use `libdbus` to communicate with NetworkManager directly\n    * To package managers: libnm dependency should be removed\n\nFeatures:\n* Add module `BluetoothRadio` that prints bluetooth radios installed on the system\n    * Don't confuse with module `Bluetooth` which lists connected bluetooth devices\n* Detect more information when `--gpu-driver-specific` is used (GPU)\n* Detect which type of nvidia driver (open source or proprietary) is used (GPU, Linux)\n* `--gpu-driver-specific` adds supports for Moore Threads GPU (#1142, GPU, Linux / Windows)\n* Use SetupAPI for detecting GPUs to support GPU detection when running fastfetch as a Windows Service (GPU, Windows)\n    * See https://github.com/gpustack/gpustack/pull/97#issuecomment-2264699787 for detail\n* Detect playback status (Media, Linux)\n\nBugfixes:\n* Don't try to connect display server in tty mode (Linux, #1110)\n* Improve ssh detection\n* Fix max frequency printing in custom format (CPU)\n* Fix displaying random characters when detecting kitty term font (#1136 / #1145, TerminalFont, Linux)\n* Make sure to detect all physical memory devices (#1137)\n* Don't detect `wl-restart` as WM (#1135, WM, Linux)\n* Use PCI bus ID to match Nvidia cards; fix multi-GPU detection (GPU)\n* Ignore invalid GPU (#1066, GPU, macOS)\n* Print error when invalid color code is found (#1138)\n* Fix invalid refresh rate detection on old macOS versions (Display, macOS)\n* Fix disk size detection on 32-bit systems (Disk, BSD)\n* Don't ignore disabled GPUs (#1140, GPU, Linux)\n* Fix GPU type detection on FreeBSD (GPU, FreeBSD)\n* Remove shell version detection for unknown shells (#1144, Shell)\n* Don't detect hyfetch as shell on NixOS (Shell, NixOS)\n\nLogos:\n* Update EndeavourOS_small\n* Add QTS\n\n# 2.20.0\n\nThis release fixes regression of `2.19.0` on M1 MacBook Air. It also introduces a new option `--key-type icon` to display predefined icons in keys (requires newest nerd font). See `fastfetch -h key-type` for detail.\n\nChanges:\n* JSON option `display.keyWidth` has been renamed to `display.key.width`\n    * Previously: `{ \"display\": { \"keyWidth\": 3 } }`\n    * Now: `{ \"display\": { \"key\": { \"width\": 3 } } }`\n* Windows Terminal font detection **in WSL** has been removed due to [issue #1113](https://github.com/fastfetch-cli/fastfetch/issues/1113)\n\nFeatures:\n* Add option `display.key.type: <enum>` to print icons in keys\n    * Supported value `string`, `icon` and `both`. Default to `string` (don't display icons)\n    * Example: `{ \"display\": { \"key\": { \"type\": \"icon\" } } }`\n* Add option `display.key.paddingLeft: <num>` to print left padding (whitespaces) in keys\n    * Example: `{ \"display\": { \"key\": { \"paddingLeft\": 2 } } }`\n* Add option `modules.keyIcon` to set icon for specified module\n    * Example: `{ \"modules\": { \"type\": \"command\", \"keyIcon\": \"🔑\" } }`\n* Report system mono font name for Terminator if used (TerminalFont, Linux)\n* Don't require logo height to be set when using `--logo-position right`\n* Report Snapdragon SOC marketing name for newer Android phones (CPU, Android)\n* Detect MTK SOC part name (CPU, Android)\n\nBugfixes:\n* Don't wake up suspended GPUs when using `--ds-force-drm` (Display, Linux)\n* Fix printing editor type in JSON result (Editor)\n* Fix `--logo-padding-*` not working correctly (#1121, Logo)\n* Fix possible segfault when detecting GPU frequency (#1121, macOS, GPU)\n\n# 2.19.1\n\nBugfixes\n* Fix frequency value printing when using custom format (#1111, CPU / GPU)\n* Fix display detection for XiaoMi Android phone (Display, Android)\n\nFeatures:\n* Display if HDR mode is enabled for screens (Display)\n    * Supported in Windows and Linux (KDE) correctly\n\n# 2.19.0\n\nChanges:\n* JSON option `modules.cpu.freqNdigits` has been renamed and moved to `display.freq.ndigits`\n    * Previously: `{ \"modules\": { \"type\": \"cpu\", \"freqNdigits\": 2 } }`\n    * Now: `{ \"display\": { \"freq\": { \"ndigits\": 2 } } }`\n    * This option now affects GPU frequency too\n    * By default, frequencies are displayed in *GHz*. Set `display.freq.ndigits` to `-1` to display them in *MHz*\n* JSON option `display.binaryPrefix` has been moved to `display.size.binaryPrefix`\n    * Previously: `{ \"display\": { \"binaryPrefix\": \"IEC\" } }`\n    * Now: `{ \"display\": { \"size\": { \"binaryPrefix\": \"IEC\" } } }`\n\nFeatures:\n* Print physical diagonal length if supported (Display)\n* Detect display type in X11 mode (Display)\n* Assume displays connected via DisplayPort are external monitors (Display, Linux)\n* Support GPU frequency detection for Intel XE driver (GPU, Linux)\n* Detect init system on Android (InitSystem, Android)\n* Use background to display color blocks (Colors)\n    * To fix weird vertical black lines in some terminals and match the behavior of neofetch (#1094)\n    * Can be reverted to old behavior with `--colors-symbol block`\n* Support Zed terminal version detection (Terminal)\n* Improve wezterm font detection (TerminalFont)\n* Add option `--separator-length`\n* Support GPU frequency detection for Apple Silicon (GPU, macOS)\n* Detect maximum refresh rate (#1101, Monitor)\n* Detect if HDR mode is supported and enabled (Windows, Display / Monitor)\n* Support physical monitor info detection for FreeBSD and SunOS (Monitor)\n* Support defining constant strings in JSON config file, which can be used to dedupe formattion strings\n```jsonc\n{\n    \"display\": {\n        \"constants\": [\n            \"Hello\", // {$1}\n            \"world\"  // {$2}\n        ]\n    },\n    \"modules\": [\n        {\n            \"type\": \"custom\",\n            \"format\": \"{$1} {$2}!\" // print \"Hello world!\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{$2} {$1}\" // print \"world Hello\"\n        }\n    ]\n}\n```\n\nBugfixes:\n* Fix some presets\n* Better detection for XTerm terminal fonts (#1095, TerminalFont, Linux)\n* Remove debug output (#1097, Windows)\n* Fix flag `--gpu-hide-type` doesn't work (#1098, GPU)\n* Fix wrong date on Raspbian 10 (#1108, DateTime, Linux)\n* Use `brightness` instead of `actuall_brightness` when detecting current brightness level (Brightness, Linux)\n    * Ref: https://bugzilla.kernel.org/show_bug.cgi?id=203905\n* Fix buffer overflow with long font family names when detecting kitty term font (TerminalFont)\n* Fix some typos\n\nLogos:\n* Update void_small\n* Add ALT Linux\n\n# 2.18.1\n\nFix a regression introduced in v2.18.0\n\nChanges:\n* `--ts-version` has been renamed to `--detect-version`\n    * `general.detectVersion` in JSON config file\n\nBugfixes:\n* Fix and improve GPU driver detection (#1084, GPU, Linux)\n\n# 2.18.0\n\nChanges:\n* `yyjson 0.10.0` is required\n* Fastfetch no longer prints `*` (which means it's the default route) if `defaultRouteOnly` is set (LocalIP)\n\nBugfixes:\n* Fix some memory leaks\n* Fix compatibility with old Python versions\n* Don't detect frequency for AMD cards (GPU, Linux)\n    * Fix possible hang with discrete AMD cards (#1077)\n* Don't print colors in `--pipe` mode (Separator)\n* Don't print `(null)` in property `locator` (PhysicalMemory)\n* Ignore disabled PCI devices (GPU)\n* Fix flag `--opengl-library` doesn't work (OpenGL)\n\nFeatures:\n* Detect revision of USB drives (#1048, Disk)\n* Support fractional scale factor detection (Display, Linux)\n* Support primary display detection for KDE and GNOME (Display, Linux)\n* Support percent bar in custom formatting\n* Print signal quality by default (Wifi)\n* Detect used OpenGL library version (OpenGL)\n* Support detecting OpenGL version by `EGL` (ANGLE) on Windows (OpenGL)\n\nLogos:\n* Add Arkane Linux\n* Add Opak\n\n# 2.17.2\n\nChanges:\n* Flatpak package count no longer takes runtime packages into account (Packages, Linux)\n\nBugfixes:\n* Fix formattion with multiple batteries (Battery)\n* Fix incorrect size value for large memory sticks (PhysicalMemory)\n* Fix spelling of `Qt` and `LXQt`\n* Fix building on SunOS if imagemagick support is enabled (Logo, SunOS)\n* Fix typos\n\nFeatures:\n* Support Ptyxis terminal version and font detection (Terminal / TerminalFont, Linux)\n* Improve Cinnamon version detection (DE)\n* Support `cinnamon-wayland` (WMTheme)\n* `--ts-version false` will disable editor version detection (Editor)\n\n# 2.17.1\n\nHotfix for a regression that breaks Qt font detection\n\nBugfixes:\n* Don't generate and install `libffwinrt.dll.a` on MinGW (Windows)\n* Fix building on Windows when imagemagick support is enabled (Logo, Windows)\n* Don't print GPU frequency with `--gpu-temp` for Nvidia cards (#1052, GPU)\n    * `--gpu-driver-specific` needs to be specified\n* Print formatted size when `--gpu-format` is used (#1052, GPU)\n* Ignore QVariant format; fix unreadable Qt font (#1053, Theme, Linux)\n* Fix segfaults with `--show-errors` and an invalid module (#1055)\n\n# 2.17.0\n\nChanges:\n* CMake option `ENABLE_PROPRIETARY_GPU_DRIVER_API` is removed. The GPU driver APIs are now enabled by default.\n    * The option was introduced to reduce the license concerns. Since all non MIT proprietary code has been rewritten manually from scratch, it is no longer necessary.\n    * See <https://github.com/fastfetch-cli/fastfetch/issues/533#issuecomment-2122830958> for detail\n* Option `--logo-separate true` is changed to `--logo-position top` for better readability\n    * Builtin ascii logos can be positioned on the right side now with`--logo-position right`\n\nFeatures:\n* Add support for `--gpu-detection-method opencl` which uses OpenCL to detect GPUs.\n* Support detecting CPU cache size by using SMBIOS as fallback (CPUCache)\n* Support GPU detection (SunOS)\n* Support GPU type detection with AMD GPU driver (GPU, Windows)\n* Add fast path of version and font detection for kitty (Terminal / TerminalFont)\n* Make sure `stdin` and `stdout` are TTYs when querying terminal\n    * So modules like `TerminalSize` should work when `stdin` or `stdout` is redirected\n* Support argument truncation in `--<module>-format` (#1043)\n    * See `fastfetch --help format` for detail\n* Improve Qt theme detection (#1047, Theme, Linux)\n* Add new JSON config option `general.preRun`, which is executed before fastfetch prints output.\n    * It can be used to generate a temp logo file. For example  \n```jsonc\n{\n    \"general\": {\n        \"preRun\": \"kitten icat --align=left /path/to/image > /tmp/logo.kitty\"\n    },\n    \"logo\": {\n        \"source\": \"/tmp/logo.kitty\",\n        \"type\": \"raw\"\n    }\n}\n```\n\nBugfixes:\n* Fix invalid path (#1031, LM, Linux)\n* Fix VMEM detection for Nvidia GPU (requires `--gpu-driver-specific`) (GPU)\n* Fix AMD `--gpu-driver-specific` for AMD cards (#1032, GPU, Windows)\n* Use Coordinated Universal Time rather than timezone-varying local date (#1046)\n\nLogo:\n* Fix colors of Asahi Linux\n\n# 2.16.0\n\nThis release added basic support for SunOS (Solaris, illumos). The binaries provided in the release lack a few useful features (such as Display detection). People who use SunOS should consider building fastfetch themselves.\n\nChanges:\n* Fastfetch now prefers `/etc/os-release` over `/etc/lsb-release` when detecting distro info. \n    * This may break some distros (notably some debian based distros). File a bug with the content of `os-release` and `lsb-release` if it breaks your distro.\n\nFeatures:\n* Support Media detection in Windows (Media / Player, Windows)\n    * Requires Windows 10 and later\n* Add new option `--users-myself-only` to display current login user only (Users)\n* Add code name of macOS Sequoia (OS, macOS)\n* Add new module `DNS` to show active DNS servers (DNS)\n* Add new option `--loadavg-compact`. Defaults to true (Loadavg)\n    * Use `--loadavg-compact false` to display load averages in different lines\n* Detect MTU size (LocalIP)\n* Support version detection of pluma, which is the default editor of OpenIndiana (Editor)\n* Print used OGL library, eg EGL, GLX or OSMesa (OpenGL)\n\nBugfixes:\n* Report error if cache size is unavailable (CPUCache, Android)\n* Trim white spaces in device name (Sound, Linux, #1005)\n* Fix `display.bar.border{Left,Right}` doesn't work in JSON config files (Config)\n* Fix invalid call to `realpath(3)` (Platform, Linux)\n* Fix result calculation (CPUUsage, FreeBSD)\n\nLogos:\n* Add Mauna\n* Add Tuxdeo\n* Add Manjaro ARM\n* Add RedOS\n* Add Arch3\n\n# 2.15.0\n\nChanges:\n* `--bar-border <?bool>` has been changed to `--bar-border-left <string>` and `--bar-border-right <string>`, which are used for customizing the style of bar border.\n    * `--bar-border-left '' --bar-border-right ''` can be used to disable the border\n\nFeatures:\n* Add ability to skip installing license with INSTALL_LICENSE option (CMake)\n* Make it possible to shorten the theme and icons output (Theme / Icons)\n* Support `-l '?'` to show a question mark\n* Add new module `CPUCache` to display CPU cache sizes (CPUCache)\n* In `--<module>-format`, `{#keys}` and `{#title}` can be used to reference the color of keys and title\n* Improve speed of Guix package detection (Packages, Linux)\n* Assume wm plugins are daemon processes to improve performance (WM, macOS)\n\nBugfixes:\n* Remove shebangs from completions (#980)\n* Fix while chars not visible in terminal of light theme (Logo)\n* Normalize bright colors to fix color display in Apple Terminal (#991, Colors)\n* Correctly capitalize GNOME (#997, DE, Linux)\n* Fix segfault on system using turkish language (#995, InitSystem, Linux)\n* Fix kubuntu detection (#1000, OS, Linux)\n* Don't display duplicate entries (OS, Linux)\n\n# 2.14.0\n\nFeatures:\n* Support monochrome bar type (#960)\n* Support editor version detection on Windows (Editor, Windows)\n* Apply default color palettes in `--file` and `--data` (Logo)\n* Print all presets in `--list-presets` for better Windows support (Windows)\n* Support for guix package manager detection (Packages, Linux)\n* Support named variable placeholders in custom module formattion string (#796)\n    * `--title-format '{user-name-colored}{at-symbol-colored}{host-name-colored}'` is now equivalent to `--title-format '{6}{7}{8}'`\n* Support named color placeholders in custom module formattion string\n    * `--<module>-format '{#red}'` is now equivalent to `--<module>-format '{#31}'`\n    * `'{#red}'` or `'{#31}'` is preferred over `\\u001b[31m` because is more readable and `--pipe` aware (will be ignored in pipe mode)\n    * Supported in `Custom` module too\n    * See `fastfetch -h format` for detail\n* Add new module `InitSystem`, which detects the name of init system\n    * i.e. process name of pid1. `init`, `systemd`, etc\n* Add option `--color-separator` to set the color of key-value separators\n* Support Guix package manager count (#792, Packages, Linux)\n* Improve python based shell detection (#977, Shell, macOS)\n* Print error reason when vulkan init fails (Vulkan)\n\nBugfixes:\n* Don't detect `.conf` files in `--list-config-paths`\n* Don't try to detect terminals in MSYS shell with process backtracing (Windows)\n* Fix `outputColor` doesn't work if module keys are disabled\n\nLogos:\n* Add Cereus Linux\n* Re-add special handling of Loc-OS\n\n# 2.13.2\n\nAnother hotfix release :(\n\nBugfixes:\n* Remove DRM driver version detection feature, which caused a performance regression for nouveau drivers (#956, Display, Linux)\n* Fix compatibility for old python versions. Regression of `2.13.0`\n* Don't use `*-unknown` as display name for Wayland protocol (Display, Linux)\n\nFeatures:\n* Add new module `Editor` which prints information of the default editor, i.e. $VISUAL or $EDITOR (#430, Editor)\n\nLogos:\n* Added CuerdOS\n* Remove special handling of Loc-OS\n\n# 2.13.1\n\nFix a regression introduced in v2.13.0\n\nBugfixes:\n* Fix CPU frequency not displayed if `bios_limit` is not available (CPU, Linux)\n\nFeatures:\n* Add `--cpu-show-pe-core-count` to detect and display core count for performance / efficiency cores (CPU, FreeBSD)\n\n# 2.13.0\n\nChanges:\n* Option `--gpu-force-vulkan <?bool>` has been changed to `--gpu-detection-method <enum>`\n    * Use `--gpu-detection-method vulkan` to get the old behavior\n    * See `fastfetch -h gpu-detection-method` for detail\n* In Linux, BIOS limited CPU frequency is printed by default to match the behavior of neofetch (CPU, Linux, #947)\n\nFeatures:\n* Add new module `Bootmgr` which prints information of stage 2 bootloader (grub, system-boot, etc)\n    * Requires root permission to work on Windows and FreeBSD\n    * Requires booting in UEFI mode\n* Add package manager lpkg and lpkg-build support (Packages, Linux)\n* Improve macOS 10.13 compatibility (macOS)\n* Detect core count for performance / efficiency cores (CPU)\n    * Test it with `fastfetch -s cpu --cpu-format '{9}'`\n* Support min / max frequency and physical core count detection in FreeBSD, if kernel supports it (CPU, FreeBSD)\n* Detect DRM driver version if DRM detection method is used (GPU, Linux)\n\nBugfixes:\n* Don't detect `clifm` and `valgrind` as a terminal (Terminal, Linux)\n* Improve stability (PhysicalMemory, FreeBSD)\n* Fix bssid & status detection (Wifi, FreeBSD)\n* Ensure createTime is correctly initialized (Disk, FreeBSD / macOS)\n* Fix `--cpu-freq-ndigits` not working if `--cpu-format` is used\n* Fix `nix-user` package count detection (Packages, Linux)\n* Fix some memory leaks\n\nLogos:\n* Fix Manjaro logo not displayed\n* Add SpoinkOS\n* Add Loc-OS\n* Add Furreto Linux\n* Fix TorizonCore logo colors\n* Fix KDE neon logo not displayed\n\n# 2.12.0\n\nChanges:\n* The long deprecated flag based config files are removed.\n    * They can still be used with `xargs fastfetch < /path/to/config.conf`\n    * `--gen-config` can be used to migrate them to json based config files\n* The long deprecated options `--set` and `--set-keyless` are removed.\n* `Kernel` module now prints kernel name by default\n\nFeatures:\n* Support `st` terminal font detection for font configuration compiled in `st` binary (TerminalFont, Linux)\n* Add option `--color-output` to change output color of all modules except `Title`, `Separator`\n    * `display.color.output` in JSONC config file\n* Add option `--<module>-output-color` to change output color of one specified module, which overrides the global option `--color-output`\n* Add option `--publicip-ipv6` to print IPv6 address (PublicIP)\n* Add new module `Loadavg` to print load averages (Loadavg)\n* Add new module `PhysicalMemory` to print information of physical memory devices (PhysicalMemory)\n    * Requires root permission to work on Linux and FreeBSD\n* Support specifying `--logo-width` only for `--kitty-direct` and `--iterm` (Logo)\n* Add option `--localip-show-all-ips` to show all IPs assigned to the same interface (LocalIP)\n    * Default to `false`\n* Improve compatibility with `(*term)` (#909, Terminal, macOS)\n* Support GPU core count and frequency detection for Asahi Linux (GPU, Linux)\n\nBugfixes:\n* Rename option `--temperature-unit` to `--temp-unit` as documented in help messages\n* Fix alternate logo doesn't work with `{ \"type\": \"builtin\" }` (#914, Logo)\n\nLogos:\n* Fix DahliaOS detection\n* Add openSUSE Slowroll\n* Add macOS3\n* Add Quirinux\n\n# 2.11.5\n\nBugfix:\n* Fix logo printing for OpenMandriva (#896)\n* Remove `--os-file` in help messages\n\n# 2.11.4\n\nChanges:\n* Fastfetch will print a colorless ascii logo in `--pipe` mode for better `lolcat` compatibility. `fastfetch | lolcat` should work and no `--pipe false` needed.\n    * Previously the logo would be disabled in `--pipe` mode.\n    * Use `--pipe -l none` to get the old beheavior\n* `--os-file` was removed and CMake option `-DCUSTOM_OS_RELEASE_PATH=/path/to/os-release` was introduced for configuring at compile time by package managers if needed. This option should not used in most cases.\n\nBugfixes:\n* Fix possible out-of-bound memory access (#868)\n* Fix Apple Terminal detection (#878, macOS, Terminal)\n* Fix deprecation warning for macOS 14.0 hopefully (#860, macOS, Camera)\n* Fix memory leaks when passing informative options (#888)\n* Fix JSON config `size.ndigits` doesn't work \n\nFeatures:\n* Enable `--pipe` mode if environment variable `$NO_COLOR` is set\n* Support Armbian and Proxmox distro detection (OS, Linux)\n\nLogo:\n* Add Armbian\n\n# 2.11.3\n\nHotfix for nix (https://github.com/NixOS/nixpkgs/issues/308849#issuecomment-2093962376)\n\nFeatures:\n* Add cmake option `CUSTOM_AMDGPU_IDS_PATH` for specifying custom path of `amdgpu.ids`\n\nBugfixes:\n* Fix hanging when detecting disconnected network drive (Disk, Windows)\n* Ensure line ending is printed when printing image logo errors (Logo)\n* Revert image logo limitation change in 2.11.2; allow image logo in SSH session and tmux again (#861, Logo)\n* Fix doubled output in custom formation (PhysicalDisk, Windows)\n\n# 2.11.2\n\nHotfix for Debian 11\n\nChanges:\n* Error messages when trying to print image logo will only be printed with `--show-errors`\n* When generating JSON output, fastfetch will generate an empty array when no result is detected, instead of an error.\n\nBugfixes:\n* Fix segfault in Debian 11 and some old kernels. Regression introduced in 2.11.0 (#845, GPU, Linux)\n* Don't try detecting version of raw `sh` shell (#849, Shell, Linux)\n* Trim `\\r` on Windows\n\nFeatures:\n* Check xdg state home for nix user packages (#837, Packages, Linux)\n* Disable image logos in ssh and tmux sessions (#839)\n* Support MX Linux distro detection (#847, OS, Linux)\n\nLogo:\n* Add KernelOS\n* Fix name of DraugerOS\n* Add missing `FF_LOGO_LINE_TYPE_SMALL_BIT` flags\n* Add MX2\n\n# 2.11.1\n\nHotfix for Android\n\nBugfixes:\n* Fix uninitialized variables which can cause crashes (#760 #838, Battery, Android)\n* Don't detect hyfetch as shell when used as backend of [hyfetch](https://github.com/hykilpikonna/hyfetch)\n* Fix incorrect information in man page (#828)\n\nFeatures:\n* Support sorcery package manager detection (Packages, Linux)\n* Make `--custom-format` optional (Custom)\n* Make `/` an alias of `C:\\` for `--disk-folders` (Disk, Windows)\n* Build for Linux armv7\n\nLogo:\n* Fix colors of Source Mage logo\n\n# 2.11.0\n\nChanges:\n* Default `hideCursor` to false. It doesn't make much difference but makes user's terminal unusable if fastfetch is not exited correctly.\n* Linux amd64 binaries are built with Ubuntu 20.04 again (#808)\n\nBugfixes:\n* Fix swap usage detection in x86-32 build (Windows, Swap)\n* Fix minimum cmake version support (#810)\n* Fix wifi detection on platforms that don't use NetworkManager (#811, Wifi, Linux)\n* Fix NixOS wrapped process name (#814, Terminal, Linux)\n* Fix GPU type detection for AMD cards (#816, GPU, Linux)\n* Silence system deprecation warnings (#822, Camera, macOS)\n\nFeatures:\n* Add basic support DE detection support for UKUI (DE, Linux)\n* Support printing total number of nix / flatpak / brew packages (Packages)\n    * See `fastfetch -h packages-format` for detail\n* Better max CPU frequency detection support with `CPUID / 16H` instruction (CPU, Windows)\n    * This requires Intel Core I Gen 6 or newer, and with `Virtual Machine Platform` Windows feature disabled. X86 only.\n* Improve performance of nix packages detection (Packages, Linux)\n* Make config specified in JSONC overridable by command line flags\n    * Note this change only make global config overridable; module configs are still not\n* Suggest increasing `--processing-timeout` when child process timeouts\n* Only detect folders that specified by `--disk-folders`\n    * Previously `--disk-folders` only omits unmatched disks from output\n    * This option can be used to improve detection performance by ignoring slow network drives\n\n# 2.10.2\n\nBugfixes:\n* Fix a regression that detect x11 as wayland (#805, WM, Linux)\n\n# 2.10.1\n\nBugfixes:\n* Fix build with `-DENABLE_DBUS=OFF` (Linux)\n\n# 2.10.0\n\nChanges:\n* We now always detect max frequency of GPUs for consistent, instead of current frequency\n\nFeatures:\n* Improve display detection for wlroots based WMs. Fastfetch now correctly reports fractional scale factors in hyprland (Display, Linux)\n* Improve GPU detection on Linux (GPU, Linux)\n    * Support GPU memory usage detection for AMD GPUs\n    * Support GPU frequency detection for Intel GPUs\n* Improve performance of GNOME version detection (DE, Linux)\n* Improve performance of kitty version detection (Terminal, Linux)\n* Detect refresh rate when using `--ds-force-drm sysfs-only` (Display, Linux)\n* Add option `--ts-version` to disable terminal and shell version detection. Mainly for benchmarking purposes\n* Improve performance of detecting WSL version (Host, Linux)\n\nBugfixes:\n* Correctly detect `/bin/sh` as current shell if it's used as default shell (#798, Shell, Linux)\n* Work around an issue which CPU module reports incorrect CPU frequency that is too high (#800, CPU, Linux)\n* Don't print ANSI escape codes in `--pipe` mode\n\n# 2.9.2\n\nChanges:\n* To make use of the newly introduced `yyjson` flag `YYJSON_WRITE_NEWLINE_AT_END`, fastfetch now requires `yyjson` 0.9.0 or later\n\nFeatures:\n* Always add a final new-line when generating JSON output\n* Detect partition create time, which can be used as OS installation time (Disk)\n* Print time string when generating JSON result instead of UNIX epoch time number, which is more human-readable\n\nBugfixes:\n* Fix a memory leak\n* Better portable mode detection of Windows Terminal (TerminalFont, Windows)\n* Fix parsing of option `--packages-disabled` (Packages)\n* Don't use command `time` as a shell (Shell)\n\nLogos:\n* Add openSUSE MicroOS\n* Fix color of AOSC OS\n\n# 2.9.1\n\nFeatures:\n* Support weston-terminal (missed commit in v2.9.0) (TerminalFont, Linux)\n* Support hyprcursor detection (#776, Cursor, Linux)\n\nBugfixes:\n* Fix `fastfetch --gen-config` raises SIGSEGV when `~/.config/fastfetch` doesn't exist. Regression of `2.9.0` (#778)\n\n# 2.9.0\n\nFeatures:\n* Support Lxterminal version detection (Terminal, Linux)\n* Support weston-terminal version detection (Terminal, Linux)\n* Support `am` package manager detection (#771, Packages, Linux)\n* Support network prefix length detection for IPv6 (LocalIP)\n* Display all IPs when multiple IPs are assigned to the same interface (LocalIP)\n* Add option `--localip-show-prefix-len` to show network prefix length for both IPv4 and IPv6. Defaults to `true` (LocalIP)\n\nBugfixes:\n* Fix network prefix length detection when the value is greater than 24 (#773, LocalIP, Linux)\n* For xfce4-terminal, use system mono font if no config file is found (TerminalFont, Linux)\n\n# 2.8.10\n\nBugfixes:\n* Don't display 0.00 GHz (CPU, FreeBSD)\n* Don't detect manufactor of Qualcomm as ARM (CPU, Android)\n* Ignore `chezmoi` (Terminal, Linux)\n* Trim trailing possible whitespaces (PublicIP)\n* Fix detection compatibility for KDE 6 (Font, Linux)\n* Always use Metal API to detect vmem size (GPU, macOS)\n\nFeatures:\n* Improve stability; print more useful error message; avoid misuse (PublicIP / Weather)\n* Use MS-DOS device name as mountFrom result, instead of useless GUID volume name (Windows, Disk)\n* Some adjustments to Terminal detection (Terminal, Windows)\n    * Don't pretty print CMD\n    * Print conhost as Windows Console\n    * Don't detect `wininit` as Terminal\n\nLogo:\n* Fix color of Arco Linux\n\n# 2.8.9\n\nBugfixes:\n* Don't detect `SessionLeader` as terminal, actually (Terminal, Linux)\n* Fix blurry chafa result when specifying both width and height (#757, Logo)\n\nFeatures:\n* Support new MacBook Air (Host, macOS)\n* Distinguish min frequency and base frequency (CPU)\n\nLogo:\n* Fix proxmox\n\n# 2.8.8\n\nBugfixes:\n* Fix old fish version compatibility (#744)\n* Fix truncated texts in `--help format` (#745)\n* Fix old vulkan-header and libdrm library compatibility (#748, Linux)\n* Fix possible segfaults in `--help *-format` (#749)\n* Fix invalid resolution detection when using libdrm (Linux, Display)\n* Fix segfault when `/sys/devices/system/cpu/cpufreq/` doesn't exist (#750, CPU, Linux)\n* Don't detect `SessionLeader` as terminal (Terminal, Linux)\n* Fix detection of client IP (Users, Linux)\n\n# 2.8.7\n\nBugfixes:\n* Fix max CPU frequency detection for some cases (CPU, Linux)\n* Fix some memory leaks\n* Fix ddcutil 2.1 compatibility (Brightness, Linux)\n* Workaround `permission denied` error when reading `/proc/uptime` (Uptime, Android)\n\nFeatures:\n* Support zellij version detection (Linux, Terminal)\n\nLogo:\n* Fix PostMarketOS\n\n# 2.8.6\n\nChanges:\n* Due to newly introduced configs, JSONC option `{ \"temperatureUnit\": \"C\" }` has been changed to `{ \"temp\": { \"unit\": \"C\" } }`\n\nBugfixes:\n* Fix incorrect GPU name detection for Intel iGPU on Linux (#736, GPU, Linux)\n\nFeatures:\n* Support additional temperature formatting options (#737)\n    * `{ \"temp\": { \"ndigits\": 1 } }`\n    * `{ \"temp\": { \"color\": { \"green\": \"green\", \"yellow\": \"yellow\", \"red\": \"red\" } } }`\n* Support specifying custom `pci.ids` path for Linux (GPU, Linux)\n* Support warp-linux terminal version & terminal font detection (Terminal, Linux)\n\n# 2.8.5\n\nBugfixes:\n* Fix uninitialized variables\n\n# 2.8.4\n\nBugfixes:\n* Fix segfault if we fail to find `Vendor ID` in `lscpu` (#718, CPU, Linux)\n* Fix multi-device bcachefs filesystem compatibility (#731, Disk, Linux)\n\nFeatures:\n* Support portable Windows Terminal settings (#720, Terminal, Windows)\n* Support `--color-block-width` and `--color-block-range` (#721, Colors)\n* Support `--diskio-detect-total` to show total bytes read/written (DiskIO)\n* Support `--netio-detect-total` to show total bytes received/sent (NetIO)\n* Support `--packages-disabled` to disable specified package manager (#729, Packages)\n* Support `--display-order` to sort multiple displays in a specific order (Display)\n* Support `--display-compact-type original-with-refresh-rate` to show refresh rates in compact (oneline) mode (Display)\n\n# 2.8.3\n\nBugfixes:\n* Fix GPU name detection for AMD graphic cards (GPU, Linux / FreeBSD)\n\n# 2.8.2\n\nChanges:\n* The linux binaries are now built with glibc 2.35, which means they no longer support Debian 11 and Ubuntu 20.04. Users using these distros may download the artifacts `fastfetch-linux-old` from GitHub Actions.\n\nFeatures:\n* Rewrite GPU module, drop libpci dependency (GPU, Linux)\n* Detect marketing name of Apple Silicon CPUs for asahi linux (CPU, Linux)\n* Add new module `Camera`, which prints the name and resolution of connected cameras\n\nBugfixes:\n* Fix compatibility with packages installed by flatpak (Terminal, Linux)\n* Don't show an empty battery if no battery is detected (macOS, Battery)\n* Don't show `not connected` if no power adapter is found (macOS / Linux, PowerAdapter)\n* Make format of battery status be consistent with other platforms (Linux, Battery)\n\nLogo:\n* Print Asahi logo in asahi linux (Logo, Linux)\n* Add Asahi2, z/OS, Tatra, PikaOS\n\n# 2.7.1\n\nFeatures:\n* Config presets in app folder now work with symlinks\n\nBugfixes:\n* Fix a possible segfault when detecting terminal (Terminal, Linux)\n\n# 2.7.0\n\nFeatures:\n* Add new module `TerminalTheme`, which prints the foreground and background color of the current terminal window. Currently doesn't work on Windows.\n* Allow command substitution when expanding paths. For example, now it's possible to use `\"source\": \"$(ls ~/path/to/images/*.png | shuf -n 1)\"` in JSONC config file to randomly choose an image to display. (#698)\n* Use native methods instead of pciutils to detect GPUs in FreeBSD. (GPU, FreeBSD)\n\nBugfixes:\n* Fix text formatting (Wifi, Linux)\n* Fix terminal detection in some cases (Terminal)\n* Remove trailing `\\0` in JSON results (FreeBSD)\n* Fix uninitialized variables (GPU, Linux)\n* Fix a possible segfault (OpenCL)\n\nLogo:\n* Add ASCII logos for fedora immutable variants (#700)\n\n# 2.6.3\n\nBugfixes:\n* Fix module not working (Bluetooth)\n\n# 2.6.2\n\nBugfixes:\n* Fix building for GCC in Windows (Windows)\n\n# 2.6.1\n\nFeatures:\n* Improve xonsh shell detection (Shell)\n* Support colored percentage values (Bluetooth / Gamepad / Sound)\n* Add `--<module>-percent-[green|yellow]` options to specify threshold of percentage colors\n    * eg. `--disk-percent-green 20 --disk-percent-yellow 50` will show green if disk usage is less than 20%, yellow if disk usage is less then 50%, and red otherwise.\n* Add `--percent-color-[green|yellow|red]` options to specify color of different percent value states.\n    * eg. `--percent-color-green blue` will show blue color if percent value falls in green state.\n* Improve Intel macbook support (macOS)\n\nBugfixes:\n* Fix segfault in CPU module when running in aarch64 machine without `lscpu` installed (CPU, Linux)\n* Don't use `login` as terminal process (Terminal, Linux)\n* Silence warnings when building in 32bit machines.\n* Create sub folders when writing config file (#690)\n* Improve user specific locale detection; fix locale detection in Windows 7 (Locale)\n* Fix GPU type detection (GPU, macOS)\n\n# 2.6.0\n\nChanges:\n* Remove support of option `--battery-dir`. We detect a lot of things in `/sys/class/*` and only module `Battery` supports specifying a custom directory for some reason, which is weird.\n* Remove `--chassis-use-wmi` which is no longer used.\n\nFeatures:\n* Add `ENABLE_PROPRIETARY_GPU_DRIVER_API` cmake option to disable using of proprietary GPU driver APIs (GPU)\n* Support wallpaper detection for macOS Sonoma (Wallpaper, macOS)\n* Support power adapter detection for Asahi Linux (PowerAdapter, Linux)\n* Support battery serial number and manufacturer date detection (Battery)\n* Support host serial number and UUID detection (Host)\n* Support battery level detection for gamepads where possible (Gamepad)\n* Support maximum CPU clock detection. Previously base clock was printed (CPU, Windows)\n* Support manufacture date and serial number detection for physical monitors (Monitor)\n* Support ash (default shell of BusyBox) version detection (Shell, Linux)\n* Sound module in FreeBSD now uses native `ioctl`s. Pulseaudio dependency is no longer used.\n* Locale module in Windows now prints the same format as in Linux and other posix systems.\n\nBugfixes:\n* Fix overall memory leaks (macOS)\n* Remove trailing `\\0` in JSON results (FreeBSD)\n* Fix physical monitor detection with Nvidia drivers (Monitor, Linux)\n* Don't print llvmpipe in vulkan module (Vulkan)\n* Fix system yyjson usage in `fastfetch.c`. Previously embedded `3rdparty/yyjson/yyjson.h` was used in `fastfetch.c` even if `ENABLE_SYSTEM_YYJSON` was set (CMake)\n* Fix locale module printing unexpected results in specific environments (Locale)\n* Fix battery temperature detection in Windows. Note only smart batteries report temperatures but few laptops uses smart battery (Battery, Windows)\n* Print device name if no backlight name is available, so we don't print empty parentheses (Brightness, FreeBSD)\n\n# 2.5.0\n\nChanges:\n* `--gpu-use-nvml` has been renamed to `--gpu-driver-specific` due to using of `IGCL` and `AGS`\n* We now detect external partitions more conservatively in Linux. USB partitions will not be detected as external always ( eg. The Linux kernel itself is installed in a USB drive )\n\nFeatures:\n* Support more authentication type detection for macOS Sonoma (Wifi, macOS)\n* Default preset names to `.jsonc`. For example, `fastfetch -c all` will load `presets/all.jsonc` (#666)\n* Use Intel Graphics Control Library (IGCL) to detect more GPU information. Windows only (GPU, Windows)\n* Improve support of Asahi Linux (Brightness / CPU / GPU / Disk, Linux)\n* Support more properties of physical disks (PhysicalDisk)\n* Support SSD temperature detection with `--physicaldisk-temp` (PhysicalDisk)\n* Support partition label detection (Disk, FreeBSD)\n* Support platform specific graphic API version detection (GPU, macOS / Windows)\n\nBugfixes:\n* Fix Windows partition detection for WSL2 (Linux, Disk)\n* Fix Btrfs subvolumes being detected as external partitions some times (Linux, Disk)\n* Fix battery cycle counts in some places (Battery)\n* Fix CodeWhisperer compatibility (#676, Terminal, macOS)\n\n# 2.4.0\n\n**We are deprecating flags based config files (will be removed in v3.0.0). We suggest you migrate to json based config files.** One may use `-c /path/to/config.conf --gen-config` to migrate existing flag based config files.\n\nChanges:\n* All flag based presets are removed\n\nFeatures:\n* Improve performance of detecting rpm and pkg package count (Packages, Linux / FreeBSD)\n* Support Apple M3X temperature detection (CPU / GPU, macOS)\n* `--ds-force-drm` support a new option `sysfs-only`\n* Improve xfce4 version detection\n* Detect WM and DE by enumerating running processes (WM / DE, FreeBSD)\n* Add a new module `Physical Disk`, which detects product name, full size, serial number and so on.\n\nBugfixes:\n* Fix crashes sometimes when `--logo-padding-top` is not set (Logo)\n* Fix memory usage counting algorithm (Memory, macOS)\n* Fix the behavior of `--no-buffer` in Windows\n* Fix possible segfault in some devices (Display, Linux)\n* Fix segfaults on first use of new images with Sixel flag (Image) \n\nLogo:\n* Remove unnecessary escaping for Adelie logo\n* Add EshanizedOS\n\n# 2.3.4\n\nBugfixes:\n* Fix `--help` doesn't work when built without python\n\nFeatures:\n* Use `MemAvailable` if available (Memory, Linux)\n* Improve performance of detecting dpkg package count (Packages, Linux)\n\n# 2.3.3\n\nBugfixes:\n* Fix `--help` doesn't work in Windows and some other platforms\n\n# 2.3.2\n\nBugfixes:\n* Fix fish completion script, and install the script correctly\n\nLogo:\n* Fix Xray-OS logo name\n\n# 2.3.1\n\nBugfixes:\n* Fix man page install location\n\n# 2.3.0\n\n**We are deprecating flags based config files (will be removed in v3.0.0). We suggest you migrate to json based config files.**\n\nConfig related changes:\n* The deprecated flag `--gen-config conf` is removed\n* Flag `--gen-config` now does the same thing as `--migrate-config`, which can be used as config migration and default config file generation. Flag `--migrate-config` is removed\n* Fastfetch now searches for config files in the order of `fastfetch --list-config-paths`, and won't load other config if one is found.\n* The undocumented flag `--load-user-config` is removed. As an alternative, `--config none` can be used to disable loading config files.\n* `--config` (previously named `--load-config`) is now supported for command line arguments only. If specified, other config files won't be loaded, which works like other programs.\n* Config files will always be loaded before other command line flags being parsed. That is to say, command line flags will always override options defined in config files.\n* the value of GPUType `integrated` contained a typo and was fixed. Existing config files may need to be updated.\n\nFeatures:\n* Support Oils and elvish shell version detection (Shell)\n* Support Windows Server Core (Windows)\n* Better ddcutil 2.x compatibility (Brightness, Linux)\n* Add completion support for fish (natively) and nushell (via [carapace-bin](https://github.com/rsteube/carapace-bin))\n* Support nix in macOS (Packages, macOS)\n* Print module description for `--list-modules`\n* Support `alacritty.toml` (TerminalFont)\n* Support board detection on macOS. It simplily prints machine model identifier as for now (Board, macOS)\n* Add general method to query product name (Host, macOS)\n* Use `libdrm` as a better fall back for detecting displays, which correctly detects current mode; supports refresh rate detection and maybe also faster than using `/sys/class/drm` (Display, Linux)\n* Support physical disk size detection (DiskIO)\n* Support physical disk name and type detection (DiskIO, FreeBSD)\n\nBugfixes:\n* End `va_list` before returning (@VoltrexKeyva)\n* Don't use background color when printing blocks (Color)\n* Fix lots of typos\n* Fix compatibility with Linux containers (Linux)\n* Don't report disabled monitors when using DRM (Linux)\n* Fix bad performance in some cases when using X11 (Display, Linux)\n* Fix some memory leaks\n* Fix used swap space detection (Swap, FreeBSD)\n* Don't leak fds to child processes (Linux)\n* Fix possible issues when reading procfs (Linux, @apocelipes)\n\nLogos:\n* Add Adelie, Ironclad\n* Update parch\n\n# 2.2.3\n\nFeatures:\n* Update the latest mac models (Host, macOS)\n\nBugfixes:\n* Fix local ips detection on Android. Regression from `2.0.0` (LocalIP, Android)\n* Fix terminal detection on NixOS (Terminal)\n\n# 2.2.2\n\nChanges:\n* `--percent-type` now defaults to 9 (colored percentage numbers)\n* `fastfetch` now prints LocalIp module by default\n\nFeatures:\n* LocalIP module now prints netmask in CIDR format for IPv4 (LocalIP)\n* Bios module now detects system firmware type (Bios)\n* Improve detection of `Battery`\n    * Detect cycle count on supported platforms\n    * Detect temperature on Linux when supported\n    * Status detection on macOS has been adjusted to be consistent with other platforms\n* Linux binaries are built with imagemagick7 support\n\nBugfixes:\n* Fix uninitialized variables (#609)\n* Fix spelling of `--preserve-aspect-ratio` (#614)\n\nLogos:\n\n* Update NixOS_small\n\n# 2.2.1\n\nHotfix release for #606\n\nBugfixes:\n* Fix broken presets due to the breaking changes introduced in 2.2.0\n\nFeatures:\n* Pretty print `fastfetch --help`\n\n# 2.2.0\n\nThis release introduces a new option `--migrate-config`, which migrates old flag based config file to new JSONC format\n\nChanges:\n* `--pipe` and `--stat` are moved from `general` options to `display` options. This affects cjson configuration.\n* Display keys `percent*` and `size*` in JSON config are restructured. e.g. `{ \"sizeNdigits\": 1 }` is now `{ \"size\": { \"ndigits\": 1 } }`\n* With the introduction of `--migrate-config`, the old flag based config file is deprecated, and will be removed in 3.0.0 (next major version)\n* Support of `--gen-config conf` is deprecated accordingly, and will be removed in 2.3.0 (next minor version)\n* The global flag `--allow-slow-operations` is split into some explicit flags in different modules\n    * `--packages-winget`: control whether `winget` packages count should be detected. Note it's a very slow operation, please enable it with caution.\n    * `--chassis-use-wmi`: control whether `WMI` query should be used to detect chassis type, which detects more information, but slower. This flag only affects `--chassis-format` and `--format json`.\n    * `--battery-use-setup-api`: control whether `SetupAPI` should be used on Windows to detect battery info, which supports multi batteries, but slower.\n    * `--wm-detect-plugin`: control whether WM plugins should be detected. Note it's implemented with global processes enumeration and can report false results.\n    * `--de-slow-version-detection`: control DE version should be detected with slow operations. It's usually not necessary and only provided as a backup.\n* `--localip-default-route-only` and `--netio-default-route-only` defaults to true to avoid large number of results\n\nFeatures:\n* Quirks for MIPS platforms (CPU, Linux)\n* Use devicetree path for OBP hosts (Host, Linux)\n* Detect `tmux: server` as tmux (Terminal, Linux)\n* Support urxvt version detection (Terminal, Linux)\n* Support st version detection (Terminal, Linux)\n* Support st terminal font detection (TerminalFont, Linux)\n* Support xfce4-terminal 1.1.0+ terminal font detection (TerminalFont, Linux)\n* Add option `--migrate-config <?target-file-path>`\n* Support Nvidia GPU temp and cuda core count detection via nvml. Use `--gpu-use-nvml` to enable it (GPU)\n* Try supporting Wifi authentication type detection in macOS Sonoma. Please file a feature request if you get `to be supported (num)` with result of `/System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport -I | grep auth` (Wifi, macOS)\n\nBugfixes:\n* Better GPU memory and type detection (GPU, Windows)\n* Don't print display type twice (Display)\n* Detect BSSID instead of Wifi MAC address to align with other platforms (Wifi, macOS)\n* Remove support of used GPU memory detection, which is not reliable and only supported with `--gpu-force-vulkan`. (GPU)\n* Fix flag `--brightness-ddcci-sleep` (Brightness, Linux)\n* Fix hanging if a child process prints to both stdout and stderr (Linux)\n\nLogos:\n* Add Black Mesa\n* Add cycledream\n* Add Evolinx\n* Add azos\n* Add Interix\n\n# 2.1.2\n\nBugfixes:\n* Fix icon detection on Windows. It shows enabled system icons in desktop (`This PC`, `Recycle Bin`, etc) (Icon, Windows)\n* Fix compatibility with ddcutil 2.0 (Brightness, Linux)\n* Fix a compile warning (CPUUsage, FreeBSD)\n\n# 2.1.1\n\nFeatures:\n* Support opkg (Packages, Linux)\n* Support GNOME Console terminal version and font detection (Terminal, Linux)\n* Add `--cpu-freq-ndigits` to set number of digits for CPU frequency (CPU)\n* New module to detect physical disk I/O usage (DiskIO)\n* Add `--cpuusage-separate` to display CPU usage per CPU logical core\n* Add `--brightness-ddcci-sleep` to set the sleep times (in ms) when sending DDC/CI requests (Brightness, #580)\n\nBugfixes:\n* Fix possible crashes on Windows 7 (Disk, Windows)\n* Fix possible crashes caused by uninitialized strings (Users, Windows)\n* Improve support of `--help *-format` and several bugs are found and fixed\n* Don't incorrectly print `No active sound devices found` when using a non-controllable sound device (Sound, macOS)\n* Fix implementation processes counting (Processes, Linux)\n* Work around a issue that SSID cannot be detected on macOS Sonoma (Wifi, macOS)\n\nLogo:\n* Add Chimera Linux\n* Add EndeavourSmall\n* Add Xenia\n* Add MainsailOS\n* Fix phyOS\n\n# 2.1.0\n\nThis release introduces a new output format: JSON result\n\nChanges:\n* Users module detects and prints user login time by default. Specifying `--users-compact` to disable it\n* Fastfetch now requires yyjson 0.8.0 or later, which is embedded in fastfetch source tree. If you build fastfetch with `-DENABLE_SYSTEM_YYJSON` cmake option, you must upgrade your yyjson package\n* Reduced information supported by `--terminal-format`, `--shell-format`\n* Some config presets (`devinfo` and `verbose`) are obsolete and removed. They are barely maintained and can be replaced with `--format json` now.\n* Custom strings in `--module-key` and `--module-format` are no longer trimmed.\n* `/boot` is hidden by default (FreeBSD, Disk)\n\nFeatures:\n* Add `--format json`, which prints system information as JSON format\n* Add fast path for xfce4 version detection (DE, FreeBSD)\n* Support contour terminal version and font detection (Terminal / TerminalFont)\n* Support `kitty-direct` / `iterm` without specifying logo width / height. Note: in this case, the entre screen will be cleared.\n* Support new flag `--logo-separate`. If true, print modules at bottom of the logo\n* Support Apple Silicon CPU frequency detection (CPU, macOS)\n* Support user login time detection (Users)\n* Support winget package manager detection, guarded behind `--allow-slow-operations` (Packages, Windows)\n* Print monitor type (built-in or external) (Display)\n* Support full GPU detection in WSL (Linux, GPU)\n* Add `--module-key \" \"` as a special case for hiding keys\n* Support `--title-format`. See `fastfetch --help title-format` for detail\n* Support `--colors-key` (Colors)\n* Add `-c` as a shortcut of `--load-config`. Note it was used as the shortcut of `--color` before 2.0.5\n* Support Windows Service Pack version detection (Kernel, Windows)\n* Support Debian point releases detection (OS, Linux)\n* Add new module `NetIO` which prints network throughput (usage) of specified interface. Note this module costs about 1 second to finish.\n* Use `lscpu` to detect CPU name for ARM CPUs (CPU, Linux)\n\nBugfixes:\n* Fix fastfetch hanging in specific environment (#561)\n* Fix short read when reading from stdin (Logo)\n* Fix `poll() timeout or failed` error when image is very large (Logo)\n* Fix Termux Monet terminal version detection (Terminal)\n* Fix zpool volumes detection (Disk, Linux)\n* Fix external volumes detection (Disk, Linux)\n* Fix snap package number detection on systems other than Ubuntu (Packages, Linux)\n* Fix dpkg / apt package number detection (Packages, Linux)\n* Fix bluetooth mac address detection (Bluetooth, Windows)\n\nLogo:\n* Add Afterglow\n* Add Elbrus\n* Update EvolutionOS\n* Update AOSC OS\n* Update Ubuntu_old\n* Update Windows 11_small\n* Add Amazon Linux\n* Add LainOS\n* Fix colors of Slackware\n\n# 2.0.5\n\nBugfixes:\n* Fix segfault when using libxrandr (#544, Display, Linux)\n* Don't print 0px (#544, Cursor)\n\nFeatures:\n* Add option `--disk-use-available` (#543)\n* Add option `--disk-show-readonly`\n\n# 2.0.4\n\nBugfixes:\n* Fix building on 32-bit FreeBSD (Memory, FreeBSD)\n* Fix `--file-raw` doesn't work (Logo)\n\nFeatures:\n* Trait `-` as an alias for `/dev/stdin`. Available for `--file`, `--file-raw` and `--raw` (Logo)\n\n# 2.0.3\n\nBugfixes:\n* Fix typo in config parsing for --color-title (#534)\n* Fix percent formatting for `--*-format` (#535)\n* Fix loading presets for homebrew (macOS)\n\nFeatures:\n* Add option `--percent-ndigits`\n* Add command flag `--config` as an alias of `--load-config`\n* Windows packages now include presets (Windows)\n\n# 2.0.2\n\nBugfixes:\n* Workaround [a compiler bug of GCC](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85282) (Windows)\n* Fix presets not detected by file name (#529)\n\nLogo:\n* Add Tuxedo OS\n\n# 2.0.1\n\nFirst stable release of Fastfetch V2\n\nChanges:\n* Unescape strings only when parsing `.conf` files\n    * Previously: `$ NO_CONFIG=1 fastfetch --os-key \\\\\\\\ -s os -l none` prints `\\: *`. Note the backslashes are unescaped twice (once by shell and once by fastfetch).\n    * Now: `$ NO_CONFIG=1 fastfetch --os-key \\\\\\\\ -s os -l none` prints `\\\\: *`\n* Remove option shortcut `-c` (alias of `--color`), which is more commonly used as alias of `--config`\n* Rename `--recache` to `--logo-recache` (which is used for regenerate image logo cache). Remove option shortcut `-r` (alias of `--recache`).\n* Detecting brightness of external displays with DDC/CI is no longer guarded behind `--allow-slow-operations` (Brightness)\n\nFeatures:\n* Add `--key-width` for aligning the left edge of values, supported both for global `--key-width` and specific module `--module-key-width`\n* Add `--bar-char-elapsed`, `--bar-char-total`, `--bar-width` and `--bar-border` options\n* Add CMake option `ENABLE_SYSTEM_YYJSON`, which allow building fastfetch with system-provided yyjson (for package managers)\n* Add new module `Version`, which prints fastfetch version (like `fastfetch --version`)\n\nBugfixes:\n* Fix label detection. Use `--disk-key 'Disk ({2})'` to display it (Disk, Linux)\n* Fix some module options were not inited\n* Fix terminal version and font detection on NixOS (Terminal, Linux)\n\n# 2.0.0-beta\n\nFastfetch v2 introduces a new configuration file format: JSON config. Please refer to <https://github.com/fastfetch-cli/fastfetch/wiki/Configuration> for details.\n\nChanges:\n* Drop the dependency of cJSON. We now use [yyjson](https://ibireme.github.io/yyjson/doc/doxygen/html/index.html) to parse JSON documents.\n* Remove `--shell-version` and `--terminal-version`. They are always enabled (Terminal / Shell)\n* Remove `--*-error-format`, which seems to be useless\n* Remove `--display-detect-name`. Display name is always detected, and will be printed if multiple displays are detected\n* Deprecate `--set` and `--set-keyless`; they may be removed in future releases. Use JSON config with Custom module instead\n* Remove the special handling of Command module (it can be set once in the triditional `config.conf`). Use JSON config with Command module instead\n* Change `--wm-theme-*` to `--wmtheme-*`. Affect `key` and `format` (WMTheme)\n* Change `--terminal-font-*` to `--terminalfont-*`. Affect `key` and `format` (TerminalFont)\n* Module `Command` uses `/bin/sh` as the default shell on systems other than Windows (Command)\n* Fix M2 CPU temperature detection (CPU, macOS)\n* Detect monitor name when available, instead of using DRM connector name (Display / Brightness, Linux)\n\nFeatures:\n* FreeBSD support is improved greatly, and actually tested in a physical machine\n* Add `--no-buffer` option for easier debugging. CMake option `ENABLE_BUFFER` is removed and always enabled.\n* Support `--*-key-color` option to change the key color of specified module\n* Support `--colors-symbol` and `--colors-padding-left` (Colors)\n* Add LM (Login Manager) module. Currently requires systemd installed (thus Linux only)\n* Add `--wmi-timeout` option (Windows)\n* Add `--logo-type small` to search for small logos\n* Support detecting brightness of external displays with DDC/CI (guard behind `--allow-slow-operations`) (Brightness)\n* Add option `--size-ndigits` and `--size-max-prefix` (#494)\n* Add option `--processing-timeout` to the timeout when waiting for child processes.\n* Public IP module prints the IP location if `--publicip-url` is not set (PublicIP)\n* Add option `--localip-default-route-only` (LocalIP)\n* Add option `--weather-location` (Weather)\n* Support iTerm non-ascii font detection (Terminal, macOS)\n* Add option `--title-color-user`, `--title-color-at` and `--title-color-host` (Title)\n* Add Exherbo logo and package manager count (Packages, Linux, #503)\n* Add module `Terminal Size` which prints the number of terminal width and height in characters and pixels\n* Add new option `--temperature-unit`\n* Better CPU and Host detection for Android (Android)\n* Support yakuake terminal version & font detection (Terminal, Linux)\n* Add new option `--bright-color` which can be used to disable the default bright color of keys, title and ASCII logo.\n* Add module `Monitor` which prints physical parameters (native resolutions and dimensions) of connected monitors\n* Support path with environment variables for `--logo-source` and `--load-config`.\n\nBugfixes:\n* Fix possible hanging (TerminalFont, #493)\n* Fix heap-buffer-overflow reading (DisplayServer, Linux)\n* Fix false errors when built without libnm support (Wifi, Linux)\n* Properly detect CPU on POWER (CPU, Linux)\n* Fix compatibility with Fig (Terminal, macOS)\n* Fix option `--title-fqdn` doesn't work (Title)\n* Fix used spaces calculation (Disk, Linux / BSD / macOS, #508)\n* Fix `--brightness-format` (Brightness)\n* Fix specifying `--set-keyless` with the same key second time won't override the value set before (#517)\n* Fix specifying `--color` second time won't clear the value set before (#517)\n\nLogo:\n* Change the special handling of `kitty` protocol with `.png` image file to a new image protocol `kitty-direct`. This is the fastest image protocol because fastfetch doesn't need to pre-encode the image to base64 or something and the image content doesn't need to be transmitted via tty. Note:\n    1. Although konsole was said to support `kitty` image protocol, it doesn't support `kitty-direct`\n    2. wezterm support more image formats other than `.png` (tested with `.jpg` and `.webp`)\n* Port all logos supported by neo(wo)fetch. Fastfetch now has 350 builtin logos in total.\n\n# 1.12.2\n\nFeatures:\n* Support terminator terminal version detection (Linux, Terminal)\n* Support `pkgtool` package manager detection (Linux, Packages)\n* Support `Far` shell version detection (Windows, Shell)\n\nBugfixes:\n* Fix ConEmu terminal detection in some special cases (Windows, Terminal, #488)\n* Fix incorrect Host on M2 Mac Studio with M2 Max CPU (macOS, Host, #490)\n\n# 1.12.1\n\nBugfixes:\n* Fix compiling error on Apple Slicon (Bios, macOS)\n\n# 1.12.0\n\nThis release backports some changes from dev branch, and fixes 2 crashing issues\n\nFeatures:\n* Support KDE / LXQt / MATE / Cinnamon wallpaper detection (Wallpaper, Linux)\n* Support QTerminal version & terminal font detection\n* Support MATE Terminal version & terminal font detection\n* Set `--pipe true` automatically if stdout is not a tty\n* Detect new macs released on WWDC 2023 (macOS, Host)\n* Count cached memory as free memory (FreeBSD, Memory)\n* Support sound detection (FreeBSD, Sound)\n\nBugfixes:\n* Fix DE detection on Windows 8.1 (Windows, DE)\n* Fix `--logo-padding-left` doesn't work when `--logo-padding-top` is set (Logo)\n* Fix KDE version detection on Fedora (DE)\n* Include limits.h when needed (Linux, #472)\n* Fix Windows drives detection in WSL (Linux, Disk)\n* Fix CPU temp detection (FreeBSD, CPU)\n* Fix disk detection (Android, Disk)\n* Fix GNOME Terminal version and font detection (FreeBSD, TerminalFont)\n* Fix crash on newer wayland desktops (Linux, Display, #477)\n* Fix vendor detection for Intel GPU (macOS, GPU)\n* Fix possible crashes on Windows Server (Windows, GPU, #484)\n\nLogo:\n* Add bsd, freebsd_small, ghostbsd\n* Make Windows 11 logo colorable\n\n# 1.11.3\n\nBugfixes:\n* Fix a segfault bug, regression of `1.11.1` (Linux, wmtheme, #467)\n\n# 1.11.2\n\nThis release should be the last version of fastfetch 1.x (if no serious bugs found, hopefully)\n\nFeatures:\n* Support display name, type, rotation detection on Wayland (Linux, Display)\n* Print more useful display name instead of intel_backlight (Linux, Brightness)\n* Icons module supports Windows (Windows, Icons)\n* Add Wallpaper module which shows the current wallpaper image path\n* Add mac address detection `--localip-show-mac` (LocalIP, #451)\n\nBugfixes:\n* Fix GNOME version detection on Fedora (DE)\n* Fix Windows drives detection in WSL (Disk)\n\nChanges:\n* In order to make Icons module consistent between different platforms, `--icons-format` no longer supports individual GTK / Qt icon params.\n* `--theme-format` no longer supports individual GTK / plasma theme params.\n* `--local-ip-*` and `--public-ip-*` have been changed to `--localip-*` and `--publicip-*`\n* `--localip-compact-type` is no longer supported. Fastfetch now display IPs as `--localip-compat-type multiline` by default, with `--local-compact true` can be set as an alias of `--localip-compact-type oneline`\n* `--localip-v6first` is no longer supported.\n\n# 1.11.1\n\nFeatures:\n* Support xonsh detection (TerminalShell)\n* Support Tabby version / terminal font detection (TerminalFont)\n\nBugfixes:\n* Fix name of Pro Controller (Gamepad, Windows)\n* Fix compile error with imagemagick enabled (Windows)\n* Fix copy-and-paste errors (Gamepad)\n* Flatpak: Fix user package count\n* Flatpak: Count runtime packages too (#441)\n* Fix flatpak package count (#441)\n* Don't print white color blocks with `--pipe` (#450)\n* Fix iTerm being detected as iTermServer-* sometimes\n* Fix sound device volume being incorrectly detected as muted sometimes (Sound)\n* Fix memleaks reported by LeakSanitizer (Linux)\n* Fix potential memory corruption bug in unicode.c (Windows)\n\nLogo:\n* Update Windows 11 ASCII logo to look more visually consistent (#445)\n* Add another font color index to arch icon (#446)\n* Add SteamOS\n* Add macOS small / small2\n\n# 1.11.0\n\nFeatures:\n* Support linuxbrew (Packages, Linux)\n* Support foot terminal (#431, Linux)\n* Support cursor size detection on Windows (Cursor, Windows)\n* Support cursor detection on macOS (Cursor, macOS)\n* Support display name, display type and decimal refresh rate detection (Display, macOS / Windows)\n* Support `--display-compact-type` to display multiple resolutions in one line (Display)\n* Support flatpak-user (Packages, Linux, #436)\n* Support `--gpu-force-vulkan` to force using vulkan to detect GPUs, which support video memory usage detection with `--allow-slow-operations` (GPU)\n\nBugfixes:\n* Fix date time format\n* Fix compiling with musl (Wifi, Linux, #429)\n* Don't exit if libpci is failed to init (GPU, Linux, #433)\n* Names of most well-known gamepads are correctly printed instead of `Wireless Controller` on Windows\n\nLogo:\n* Small update for nobara logo (#435, @regulargvy13)\n\n# 1.10.3\n\nBugfixes:\n* Fix uninitialized variables (GPU, Windows)\n* Fix compiling errors (Windows)\n\nImprovements:\n* Improve performance (WmTheme amd Font, Windows and macOS)\n* Enable nonblocking public-ip / weather detection (Android)\n\n# 1.10.2\n\nBugfixes:\n* Handle `kAudioObjectPropertyElementMain` for macOS **SDK** < 12 (#425, @nandahkrishna)\n* Add missing `NULL` for `ffProcessAppendStdOut` (#421)\n\n# 1.10.1\n\nNew release for debugging #421\n\n# 1.10.0\n\nNotable Changes:\n\n* With the support of Win32 platform, original Windows 64bit artifact file is renamed to Win64 to avoid possible confusion\n\nFeatures:\n* Bluetooth module\n* Sound module\n* Gamepad module\n* Support colored percentage numbers output (#409)\n* Support `--localip-compact-type` option (#408)\n* Terminator terminal font detection (@Zerogiven, #415)\n* Windows 32bit compatibility\n* Support global configuration in MSYS2 environment (Windows)\n* Support GPU driver version detection on Windows 11\n* Support scaled resolution detection for wayland (Linux)\n\nBugfixes:\n\n* Fix build with older libnm versions\n* Fix a rare case that fails to detect terminal\n* Fix Muffin detection (@Zerogiven, #411)\n* Fix IPv6 detection (Windows)\n* Fix scoop package count detection when scoop is installed in non-default path (Windows, #417)\n* Fix UB reported by clang\n* Honor $SCOOP when detecting scoop packages (#417)\n\nOther:\n\n* Simplified wmtheme output format (Windows)\n* Improved GPU detection performance on Windows 11\n* Latest Mac model identifier support (macOS)\n\n# 1.9.1\n\nBugfixes:\n\n* Fix builds on s390x (@jonathanspw, #402)\n* Fix zero refresh rate on some monitors (macOS)\n* Fix default formatting of Wifi module\n\n# 1.9.0\n\nNotable Changes:\n* fastfetch no longer creates a sample config file silently. Use `--gen-config` to generate one.\n* fastfetch now search for user config file in the order of `fastfetch --list-config-paths`\n* Unknown disks are hidden by default.\n* `Resolution` module is renamed to `Display`. (#393)\n\nFeatures:\n* `--logo-padding-top` option (@CarterLi, #372)\n* Raw image file as logo support (@CarterLi)\n* Look for config files in `$APPDATA` ([RoamingAppData](https://superuser.com/questions/21458/why-are-there-directories-called-local-locallow-and-roaming-under-users-user#answer-21462)) (Windows)\n* Look for config files in `~/Library/Preferences` (macOS)\n* Add `--list-config-paths` option which list search paths of config files\n* Add `--list-data-paths` option which list search paths for presets and logos\n* Add `Brightness` module support\n* Add `Battery` module support for FreeBSD\n* Add `--disk-show-unknown` option for Disk module\n* Add `--disk-show-subvolumes` option for Disk module\n* Add `--gpu-hide-integrated` option (#379)\n* Add `--gpu-hide-discrete` option (#379)\n* Detect terminal version when available\n* Support `WezTerm` terminal font detection (requires [`wezterm` executable](https://wezfurlong.org/wezterm/cli/general.html) being available)\n* Add `--shell-version` and `--terminal-version` options to disable shell / terminal version detection\n* Enhance `--percent-type` to allow hiding other texts (#387)\n* Add Wifi module support for Linux\n* Detect scaled resolutions (Windows, macOS)\n* Optimise font module printing (Windows)\n* Detect pacman package count inside MSYS2 environment (Windows)\n* Add Wifi / Battery module support for Android\n* Disk name support for Linux\n\nLogos:\n* Raspbian (@IamNoRobot, #373)\n\nBugfixes:\n* `--logo-type` now does accept `iterm` too (@CarterLi, #374)\n* Fix mintty terminal font detection (Windows)\n* Fix bug that line buffering doesn't work properly (Windows)\n* Fix rpm package count detection (Linux)\n* Fix cpu temp detection (Linux)\n\nOther:\n* Fixed a Typo in iterm error message (@jessebot, #376)\n* Don't try to load config file in `/etc` (Windows)\n\n# 1.8.2\n\nBugfixes:\n\n* Fix memleaks Users module (Windows)\n* Fix shell detection when installed with scoop (Windows)\n* Don't use libcJSON as wlanapi's dll name (Windows)\n* Align artifact names to other platforms (Windows)\n\n# 1.8.1\n\nNotable Changes:\n\n* `Song` was used as an alias to `Media` module. It's removed to avoid confusion. All song related flags (`--song-key`, etc) should change to media (`--media-key`, etc). (@CarterLi)\n\nBugfixes:\n\n* Mountpoint paths on linux get decoded correctly (#364)\n* Color parsing once again works (@IanManske, #365)\n* Using a custom key with a placeholder for the local ip module now does work correctly if multiple interfaces are present (#368)\n\n# 1.8.0\n\nThis release introduces Windows support! Fastfetch now fully support all major desktop OSes (Linux, macOS, Windows and FreeBSD)\n\nNotable Changes:\n* Bios / Board / Chassis modules are split against Host module for performance reasons\n* Caching is removed. Option `--nocache` is removed accordingly\n\nFeatures:\n* Windows (7 and newer) is officially and fully supported\n* FreeBSD support is improved greatly (Bios, Cpu Temp, Cpu Usage, Disk, Host, Processes, Swap, Terminal / Shell, Uptime)\n* Adds a new flag `--stat`, which prints time usage for individual modules\n* Adds Wifi module which supports Windows and macOS\n* Adds data source option for logo printing\n* Detects Homebrew Cellar and Cask separately\n* Detects WSL version\n* Detects disk based on mount point\n* Exposes more chafa configs\n* Improves performance for Cpu Usage, Public IP, Weather modules\n* Improves performance for Kitty image protocol when both image width / height specified\n* Improves performance for large file loading\n* Improves performance for macOS WM and Host detection\n* Improves shell and terminal detection on macOS\n* Supports Deepin Terminal terminal font\n* Supports GPU detection on Android\n* Supports Kitty Terminal terminal font\n* Supports bar output for percentage values\n* Supports Bios module on macOS\n* Supports eopkg package manager detection\n* Supports iTerm image logo protocol\n* Supports image logo printing on macOS\n* Supports tcsh version detection\n* Vulkan module on macOS no longer requires vulkan-loader to work\n\nLogos:\n* Alpine\n* CRUX\n* EndeavourOS\n* Enso\n* Garuda small\n* Nobara\n* OpenMandriva\n* Parabola GNU/Linux-libre\n* Rocky\n* Rosa\n* Solus\n* Univalent\n* Vanilla OS\n\nBugfixes:\n* Fixes disk size detection on 32bit Linux (#337)\n* Fixes cpu freq detection in WSL\n* Fixes internal bug of FFstrbuf\n* Fixes some memory leaks\n* Fixes segfault if 0 is given as argument index\n* Lots of code refactors\n\n# 1.7.5\n\nFixes a crash on linux that could happen when getting zsh version (#285)\n\n# 1.7.4\n\nThe last element in the default structure (currently the color blocks) is now printed again (#283)\n\n# 1.7.3\n\nA lot of small improvements for MacOS & BSD platforms.\n\nFeatures:\n* BSD is now officially supported (#228)\n* MacPorts package manager support (@SladeGetz, #234)\n* Battery support for MacOS (@CarterLi, #235)\n* Processes, swap & terminal font support for MacOS(@CarterLi, #237)\n* Media support for MacOS (@CarterLi, #242)\n* Player support for MacOS (@CarterLi, #245)\n* WM theme support for MacOS (@CarterLi, #246)\n* CPU usage support for MacOS (@CarterLi, #247)\n* Power Adapter module (@CarterLi, #249)\n* Windows terminal font for WSL (@CarterLi, #254)\n* Temps & Font support for MacOS (@CarterLi, #258)\n* Terminal font support for Termux (@CarterLi, #263)\n* Weather module (@CarterLi, #266)\n\nLogos\n* Crystal linux (@AloneER0, #239)\n* FreeBSD (@draumaz, #244)\n* New Ubuntu (@AloneER0, #259)\n\nBugfixes:\n* Don't segfault in GPU code on Intel Macs (@CarterLi, #236)\n* Don't use hardcoded size units in presets (@dr460nf1r3, #255)\n* Don't crash with some format strings (#252)\n* --logo none keeps key color now (#264)\n\n# 1.7.2\n\nFixes the bash completions\n\n# 1.7.1\n\nThis release brings a lot of bug fixes and improvements for MacOS. Big thanks to @CarterLi for the help on this!\n\nFeatures:\n* The color of the title and the keys can now be configured individually, using `--color-keys` and `--color-title` respectively. Some distros have different defaults now, similar to neofetch\n* Swap module, similar to the Memory module, but for swap. Add `Swap` to your structure to enable it (#225)\n\nLogos:\n* Slackware (#227)\n\nBugfixes:\n* Used disk space is now calculated much more accurately\n* On Linux, GPU names are no longer truncated, if they are longer than 32 characters (#224)\n* On Linux, NVIDIA GPUs once again have a proper name\n\n* On M1 platforms, showing the GPU name no longer crashes the program (#222)\n* Brew package count does now work on M1 platforms too\n* The Vulkan module now does work on MacOS too\n* The OpenGL and OpenCL modules now work on MacOS too (@CarterLi, #226)\n* The LocalIp module now works on MacOS too (@CarterLi, #232)\n* Detecting custom WMs on MacOS does now work\n\nOther:\n* GitHub actions now builds a dmg file for MacOS, as you can see in the release page\n\n# 1.7.0\n\nThis release brings support for MacOS!\nThe basics things are working, but it is far from feature parity with Linux.\nI developed this in a VM, so bugs on real hardware are likely.\nIf you have a Mac and no idea what to do with your free time, i am very happy to accept pull requests / work on issues.\n\nA lot of things were changed under the hood to make this possible, which should bring better performance and stability on all platforms.\n\nBesides that, the following things have changed:\n\nFeatures:\n* The binary prefix used can now be configured, and is used consistently across all modules. Set `--binary-prefix` to `iec` (default), `si` or `jedec`.\n* AMD GPUs now have a much better name, if the file `/usr/share/libdrm/amdgpu.ids` exists. For example my dedicated GPU, which was displayed as `AMD/ATI Radeon RX 5600 OEM/5600 XT / 5700/5700 XT`, is now `AMD Radeon RX 5600M`.\n\nLogos:\n* MacOS\n* CachyOS small (@sirlucjan, #220)\n* MSYS2 (#219)\n\nBugfixes:\n* the `--file` option, which can be used to display the contents of a file as the logo, is now working again.\n\n# 1.6.5\n\nFixes parsing quoted values in config files\n\n# 1.6.4\n\nReleasing this, so fedora can package fastfetch. Thanks to @jonathanspw for doing that!\n\nFeatures:\n* --set-keyless option (#215)\n* Replace `\\n`, `\\t`, `\\e` and `\\\\` in user provided strings, just like c would do it (#215)\n* APK (Alpine Package Keeper) support (@mxkrsv, #216)\n\nLogos:\n* Alma Linux (@jonathanspw, #214)\n\nBugfixes:\n* replace deprecated gethostbyname call with getaddrinfo (#217)\n\n# 1.6.3\n\nFixes installing presets in their own directory (@ceamac, #212)\n\n# 1.6.2\n\nReleasing this, so void linux can package fastfetch.\n\nLogos:\n* Rosa linux (#206)\n* KISS linux (@draumaz, #207)\n* LangitKetujuh (@hervyqa, #208)\n\nBugfixes:\n* Using musl as libc does work now (#210)\n* XBPS packages are once again printed (#209)\n* configured target dirs are applied to install directories too\n* empty XDG_* env vars don't cause a crash anymore\n\n# 1.6.1\n\nFixes build on android (#205)\n\n# 1.6.0\n\nFeatures:\n* Detect Qt on more DEs than just KDE Plasma. The [Plasma] category was therefore renamed to [Qt]\n* Alacritty font detection\n* Load `/etc/fastfetch/config.conf` before user config\n* Disk: print one decimal point if size < 100GB\n* `--title-fqdn` option, to print fully qualified domain name instead of host name in title\n\nLogos:\n* updated old NixOS logo\n\nBugfixes:\n* Correctly detect GTK on DEs that store their settings in dconf\n* Correctly detect NixOS packages\n* Mutter WM detected once again\n* Show full NixOS version in OS output\n* Don't segfault if an invalid structure is given\n* WSL doesn't output GPU anymore, as the name is always meaningless\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.12.0) # target_link_libraries with OBJECT libs & project homepage url\n\nproject(fastfetch\n    VERSION 2.60.0\n    LANGUAGES C\n    DESCRIPTION \"Fast neofetch-like system information tool\"\n    HOMEPAGE_URL \"https://github.com/fastfetch-cli/fastfetch\"\n)\n\nset(PROJECT_LICENSE \"MIT license\")\n\nif(DEFINED CMAKE_SYSTEM_PROCESSOR_OVERRIDE) # Used by github actions for i686 build\n    set(CMAKE_SYSTEM_PROCESSOR ${CMAKE_SYSTEM_PROCESSOR_OVERRIDE} CACHE INTERNAL \"\")\nendif()\nstring(TOLOWER \"${CMAKE_SYSTEM_PROCESSOR}\" CMAKE_SYSTEM_PROCESSOR)\nif(CMAKE_SYSTEM_PROCESSOR STREQUAL \"x86_64\")\n    set(CMAKE_SYSTEM_PROCESSOR \"amd64\")\nelseif(CMAKE_SYSTEM_PROCESSOR STREQUAL \"arm64\")\n    set(CMAKE_SYSTEM_PROCESSOR \"aarch64\")\nendif()\nmessage(STATUS \"Build for system processor: ${CMAKE_SYSTEM_PROCESSOR}\")\n\n###################\n# Target Platform #\n###################\nif(ANDROID)\n    set(LINUX FALSE)\nelseif(\"${CMAKE_SYSTEM_NAME}\" STREQUAL \"Linux\")\n    set(LINUX TRUE CACHE BOOL \"...\" FORCE) # LINUX means GNU/Linux, not just the kernel\nelseif(\"${CMAKE_SYSTEM_NAME}\" STREQUAL \"FreeBSD\")\n    set(FreeBSD TRUE CACHE BOOL \"...\" FORCE)\nelseif(\"${CMAKE_SYSTEM_NAME}\" STREQUAL \"OpenBSD\")\n    set(OpenBSD TRUE CACHE BOOL \"...\" FORCE)\nelseif(\"${CMAKE_SYSTEM_NAME}\" STREQUAL \"MidnightBSD\")\n    set(FreeBSD TRUE CACHE BOOL \"...\" FORCE)\n    set(MidnightBSD TRUE CACHE BOOL \"...\" FORCE)\nelseif(\"${CMAKE_SYSTEM_NAME}\" STREQUAL \"NetBSD\")\n    set(NetBSD TRUE CACHE BOOL \"...\" FORCE)\nelseif(\"${CMAKE_SYSTEM_NAME}\" STREQUAL \"DragonFly\")\n    set(FreeBSD TRUE CACHE BOOL \"...\" FORCE)\n    set(DragonFly TRUE CACHE BOOL \"...\" FORCE)\nelseif(\"${CMAKE_SYSTEM_NAME}\" STREQUAL \"SunOS\")\n    set(SunOS TRUE CACHE BOOL \"...\" FORCE)\nelseif(\"${CMAKE_SYSTEM_NAME}\" STREQUAL \"Haiku\")\n    set(Haiku TRUE CACHE BOOL \"...\" FORCE)\nelseif(\"${CMAKE_SYSTEM_NAME}\" STREQUAL \"GNU\")\n    set(GNU TRUE CACHE BOOL \"...\" FORCE)\nelseif(NOT APPLE AND NOT WIN32)\n    message(FATAL_ERROR \"Unsupported platform: ${CMAKE_SYSTEM_NAME}\")\nendif()\n\n#############################\n# Compile time dependencies #\n#############################\n\nset(THREADS_PREFER_PTHREAD_FLAG NOT WIN32)\nfind_package(Threads)\n\nfind_package(PkgConfig)\nif(NOT PKG_CONFIG_FOUND)\n    message(WARNING \"pkg-config not found, library detection might be limited\")\nendif()\n\ninclude(CheckIncludeFile)\n\n#####################\n# Configure options #\n#####################\n\ninclude(CMakeDependentOption)\n\ncmake_dependent_option(ENABLE_VULKAN \"Enable vulkan\" ON \"LINUX OR APPLE OR FreeBSD OR OpenBSD OR NetBSD OR WIN32 OR ANDROID OR SunOS OR Haiku OR GNU\" OFF)\ncmake_dependent_option(ENABLE_WAYLAND \"Enable wayland-client\" ON \"LINUX OR FreeBSD OR OpenBSD OR NetBSD OR GNU\" OFF)\ncmake_dependent_option(ENABLE_XCB_RANDR \"Enable xcb-randr\" ON \"LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR GNU\" OFF)\ncmake_dependent_option(ENABLE_XRANDR \"Enable xrandr\" ON \"LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR GNU\" OFF)\ncmake_dependent_option(ENABLE_DRM \"Enable libdrm\" ON \"LINUX OR FreeBSD OR OpenBSD OR NetBSD OR SunOS OR GNU\" OFF)\ncmake_dependent_option(ENABLE_DRM_AMDGPU \"Enable libdrm_amdgpu\" ON \"LINUX OR FreeBSD OR GNU\" OFF)\ncmake_dependent_option(ENABLE_GIO \"Enable gio-2.0\" ON \"LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR GNU\" OFF)\ncmake_dependent_option(ENABLE_DCONF \"Enable dconf\" ON \"LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR GNU\" OFF)\ncmake_dependent_option(ENABLE_DBUS \"Enable dbus-1\" ON \"LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR Haiku OR GNU\" OFF)\ncmake_dependent_option(ENABLE_SQLITE3 \"Enable sqlite3\" ON \"LINUX OR FreeBSD OR APPLE OR OpenBSD OR NetBSD OR SunOS OR GNU\" OFF)\ncmake_dependent_option(ENABLE_RPM \"Enable rpm\" ON \"LINUX OR GNU\" OFF)\ncmake_dependent_option(ENABLE_IMAGEMAGICK7 \"Enable imagemagick 7\" ON \"LINUX OR FreeBSD OR OpenBSD OR NetBSD OR APPLE OR ANDROID OR WIN32 OR SunOS OR Haiku OR GNU\" OFF)\ncmake_dependent_option(ENABLE_IMAGEMAGICK6 \"Enable imagemagick 6\" ON \"LINUX OR FreeBSD OR OpenBSD OR NetBSD OR APPLE OR SunOS OR GNU\" OFF)\ncmake_dependent_option(ENABLE_CHAFA \"Enable chafa\" ON \"ENABLE_IMAGEMAGICK6 OR ENABLE_IMAGEMAGICK7\" OFF)\ncmake_dependent_option(ENABLE_EGL \"Enable egl\" ON \"LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR WIN32 OR SunOS OR Haiku OR GNU\" OFF)\ncmake_dependent_option(ENABLE_GLX \"Enable glx\" ON \"LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR GNU\" OFF)\ncmake_dependent_option(ENABLE_OPENCL \"Enable opencl\" ON \"LINUX OR FreeBSD OR OpenBSD OR NetBSD OR WIN32 OR ANDROID OR SunOS OR Haiku OR GNU\" OFF)\ncmake_dependent_option(ENABLE_FREETYPE \"Enable freetype\" ON \"ANDROID\" OFF)\ncmake_dependent_option(ENABLE_PULSE \"Enable pulse\" ON \"LINUX OR ANDROID OR GNU\" OFF)\ncmake_dependent_option(ENABLE_DDCUTIL \"Enable ddcutil\" ON \"LINUX\" OFF)\ncmake_dependent_option(ENABLE_DIRECTX_HEADERS \"Enable DirectX headers for WSL\" ON \"LINUX\" OFF)\ncmake_dependent_option(ENABLE_ELF \"Enable libelf\" ON \"LINUX OR ANDROID OR DragonFly OR Haiku OR GNU\" OFF)\ncmake_dependent_option(ENABLE_THREADS \"Enable multithreading\" ON \"Threads_FOUND\" OFF)\n\noption(ENABLE_ZLIB \"Enable zlib\" ON)\noption(ENABLE_SYSTEM_YYJSON \"Use system provided (instead of fastfetch embedded) yyjson library\" OFF)\noption(ENABLE_ASAN \"Build fastfetch with ASAN (address sanitizer)\" OFF)\noption(ENABLE_LTO \"Enable link-time optimization in release mode if supported\" ON)\noption(BUILD_FLASHFETCH \"Build flashfetch\" ON) # Also build the flashfetch binary\noption(BUILD_TESTS \"Build tests\" OFF) # Also create test executables\noption(SET_TWEAK \"Add tweak to project version\" ON) # This is set to off by github actions for release builds\noption(IS_MUSL \"Build with musl libc\" OFF) # Used by Github Actions\noption(INSTALL_LICENSE \"Install license into /usr/share/licenses\" ON)\noption(ENABLE_EMBEDDED_PCIIDS \"Embed pci.ids into fastfetch, requires `python`\" OFF)\noption(ENABLE_EMBEDDED_AMDGPUIDS \"Embed amdgpu.ids into fastfetch, requires `python`\" OFF)\noption(ENABLE_WORDEXP \"Enable using of wordexp(3) if available, instead of glob(3)\" ON)\noption(ENABLE_LIBZFS \"Enable libzfs\" ON)\nif(WIN32 AND NOT CMAKE_SYSTEM_PROCESSOR STREQUAL \"aarch64\")\n    option(ENABLE_WIN81_COMPAT \"Enable legacy Windows compatibility (Windows 8.1 and later; Windows 7 unsupported)\" ON)\nendif()\nif(APPLE)\n    option(ENABLE_APPLE_MEMSIZE_USABLE \"Use usable memory size as total memory size in Memory module, to match other systems\" OFF)\nendif()\n\nset(BINARY_LINK_TYPE_OPTIONS dlopen dynamic static)\nset(BINARY_LINK_TYPE dlopen CACHE STRING \"How to link fastfetch\")\nset_property(CACHE BINARY_LINK_TYPE PROPERTY STRINGS ${BINARY_LINK_TYPE_OPTIONS})\nif(NOT BINARY_LINK_TYPE IN_LIST BINARY_LINK_TYPE_OPTIONS)\n    message(FATAL_ERROR \"BINARY_LINK_TYPE must be one of ${BINARY_LINK_TYPE_OPTIONS}\")\nendif()\n\nset(PACKAGE_MANAGERS AM APK BREW CHOCO DPKG EMERGE EOPKG FLATPAK GUIX LINGLONG LPKG LPKGBUILD MACPORTS NIX OPKG PACMAN PACSTALL PALUDIS PISI PKG PKGTOOL RPM SCOOP SNAP SOAR SORCERY WINGET XBPS)\nforeach(package_manager ${PACKAGE_MANAGERS})\n    if(package_manager STREQUAL \"WINGET\")\n        option(PACKAGES_DISABLE_${package_manager} \"Disable ${package_manager} package manager detection by default\" ON)\n    else()\n        option(PACKAGES_DISABLE_${package_manager} \"Disable ${package_manager} package manager detection by default\" OFF)\n    endif()\nendforeach()\n\nif (LINUX)\n    set(CUSTOM_PCI_IDS_PATH \"\" CACHE STRING \"Custom path to file pci.ids, defaults to `/usr/share/hwdata/pci.ids`\")\n    set(CUSTOM_AMDGPU_IDS_PATH \"\" CACHE STRING \"Custom path to file amdgpu.ids, defaults to `/usr/share/libdrm/amdgpu.ids`\")\n    set(CUSTOM_OS_RELEASE_PATH \"\" CACHE STRING \"Custom path to file os-release, defaults to `/etc/os-release`\")\nendif()\n\n####################\n# Compiler options #\n####################\n\nif(NOT CMAKE_BUILD_TYPE)\n    set(CMAKE_BUILD_TYPE RelWithDebInfo)\nendif()\n\nmessage(STATUS \"Build type: ${CMAKE_BUILD_TYPE}\")\nif(ENABLE_THREADS)\n    if(CMAKE_USE_WIN32_THREADS_INIT)\n        message(STATUS \"Threads type: Win32 thread\")\n    elseif(CMAKE_USE_PTHREADS_INIT)\n        message(STATUS \"Threads type: pthread\")\n    endif()\nelse()\n    message(STATUS \"Threads type: disabled\")\nendif()\n\nset(WARNING_FLAGS \"-Wall -Wextra -Wconversion -Werror=uninitialized -Werror=return-type -Werror=vla\")\n\nset(CMAKE_C_STANDARD 11)\nset(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=incompatible-pointer-types -Werror=implicit-function-declaration -Werror=int-conversion\")\n\nif(WIN32 OR Haiku OR ENABLE_DIRECTX_HEADERS)\n    enable_language(CXX)\n    set(CMAKE_CXX_STANDARD 17)\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}\")\nendif()\n\nif(WIN32)\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -Wl,--tsaware -Wl,--build-id\")\n\n    if(ENABLE_WIN81_COMPAT)\n        set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -Wl,--subsystem,console,--major-os-version,6,--minor-os-version,3\")\n    else()\n        set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -Wl,--subsystem,console,--major-os-version,10,--minor-os-version,0\")\n    endif()\nelseif(APPLE AND CMAKE_C_COMPILER_ID MATCHES \"Clang\")\n    set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -fobjc-arc\")\nendif()\n\nset(FASTFETCH_FLAGS_DEBUG \"-fno-omit-frame-pointer\")\nif(ENABLE_ASAN)\n    message(STATUS \"Address sanitizer enabled (DEBUG only)\")\n    set(FASTFETCH_FLAGS_DEBUG \"${FASTFETCH_FLAGS_DEBUG} -fsanitize=address\")\nendif()\nset(CMAKE_C_FLAGS_DEBUG \"${CMAKE_C_FLAGS_DEBUG} ${FASTFETCH_FLAGS_DEBUG}\")\nset(CMAKE_EXE_LINKER_FLAGS_DEBUG \"${CMAKE_EXE_LINKER_FLAGS_DEBUG} ${FASTFETCH_FLAGS_DEBUG} -fstack-protector-all -fno-delete-null-pointer-checks\")\nif(NOT WIN32)\n    set(CMAKE_EXE_LINKER_FLAGS_DEBUG \"${CMAKE_EXE_LINKER_FLAGS_DEBUG} -rdynamic\")\nendif()\n\nif(ENABLE_LTO AND NOT CMAKE_BUILD_TYPE STREQUAL \"Debug\")\n    message(STATUS \"Enabling LTO\")\n    include(CheckIPOSupported)\n    check_ipo_supported(RESULT IPO_SUPPORTED)\n    if(IPO_SUPPORTED)\n        set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)\n    endif()\nendif()\n\n#######################\n# Target FS structure #\n#######################\n\nif(NOT TARGET_DIR_ROOT)\n    if(NOT ANDROID)\n        set(TARGET_DIR_ROOT \"\")\n    else()\n        set(TARGET_DIR_ROOT \"/data/data/com.termux/files/usr\")\n    endif()\nendif()\n\nif(NOT TARGET_DIR_USR)\n    if(NOT ANDROID)\n        set(TARGET_DIR_USR \"${TARGET_DIR_ROOT}/usr\")\n    else()\n        set(TARGET_DIR_USR \"${TARGET_DIR_ROOT}\")\n    endif()\nendif()\n\nif(NOT TARGET_DIR_HOME)\n    if(APPLE)\n        set(TARGET_DIR_HOME \"${TARGET_DIR_ROOT}/Users\")\n    elseif(ANDROID)\n        set(TARGET_DIR_HOME \"/data/data/com.termux/files/home\")\n    else()\n        set(TARGET_DIR_HOME \"${TARGET_DIR_ROOT}/home\")\n    endif()\nendif()\n\nif(NOT TARGET_DIR_ETC)\n    set(TARGET_DIR_ETC \"${TARGET_DIR_ROOT}/etc\")\nendif()\n\nmessage(STATUS \"Target dirs: ROOT=\\\"${TARGET_DIR_ROOT}\\\" USR=\\\"${TARGET_DIR_USR}\\\" HOME=\\\"${TARGET_DIR_HOME}\\\" ETC=\\\"${TARGET_DIR_ETC}\\\"\")\n\n#https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT.html\nif(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)\n    set(CMAKE_INSTALL_PREFIX \"${TARGET_DIR_USR}\" CACHE PATH \"...\" FORCE)\nendif()\n\nif(NOT CMAKE_INSTALL_SYSCONFDIR)\n    set(CMAKE_INSTALL_SYSCONFDIR \"${TARGET_DIR_ETC}\" CACHE PATH \"...\" FORCE)\nendif()\n\n#################\n# Tweak version #\n#################\n\nif (SET_TWEAK AND EXISTS \"${CMAKE_SOURCE_DIR}/.git\")\n    execute_process(\n        COMMAND git describe --tags\n        WORKING_DIRECTORY \"${CMAKE_SOURCE_DIR}\"\n        OUTPUT_VARIABLE PROJECT_VERSION_GIT\n        OUTPUT_STRIP_TRAILING_WHITESPACE\n    )\n    string(REGEX MATCH \"-[0-9]+\" PROJECT_VERSION_TWEAK \"${PROJECT_VERSION_GIT}\")\nendif()\nif(PROJECT_VERSION_TWEAK)\n    string(REGEX MATCH \"[0-9]+\" PROJECT_VERSION_TWEAK_NUM \"${PROJECT_VERSION_TWEAK}\")\nelse()\n    set(PROJECT_VERSION_TWEAK_NUM 0)\nendif()\n\n#############\n# Text data #\n#############\n\nfunction(fastfetch_encode_c_string STR OUTVAR)\n    string(REGEX REPLACE \"\\n$\" \"\" TEMP \"${STR}\")  # Remove trailing newline\n    string(REPLACE \"\\\\\" \"\\\\\\\\\" TEMP \"${TEMP}\")    # Escape backslashes\n    string(REPLACE \"\\n\" \"\\\\n\" TEMP \"${TEMP}\")     # Replace newlines with \\n\n    string(REPLACE \"\\\"\" \"\\\\\\\"\" TEMP \"${TEMP}\")    # Replace quotes with \\\"\n    set(${OUTVAR} \"\\\"${TEMP}\\\"\" PARENT_SCOPE)\nendfunction(fastfetch_encode_c_string)\n\nfunction(fastfetch_load_text FILENAME OUTVAR)\n    file(READ \"${FILENAME}\" TEMP)\n    fastfetch_encode_c_string(\"${TEMP}\" TEMP)\n    set(${OUTVAR} \"${TEMP}\" PARENT_SCOPE)\nendfunction(fastfetch_load_text)\n\nfind_package(Python)\nif(Python_FOUND)\n    message(STATUS \"Minifying 'help.json'\")\n    execute_process(COMMAND ${Python_EXECUTABLE} -c \"import json,sys;json.dump(json.load(sys.stdin),sys.stdout,separators=(',',':'))\"\n                    INPUT_FILE \"${CMAKE_CURRENT_SOURCE_DIR}/src/data/help.json\"\n                    OUTPUT_VARIABLE DATATEXT_JSON_HELP)\n    if(DATATEXT_JSON_HELP STREQUAL \"\")\n        message(ERROR \"DATATEXT_JSON_HELP is empty, which should not happen!\")\n    endif()\nelse()\n    message(WARNING \"Python3 is not found, 'help.json' will not be minified\")\n    file(READ \"src/data/help.json\" DATATEXT_JSON_HELP)\nendif()\n\nif(ENABLE_EMBEDDED_PCIIDS AND NOT EXISTS \"${PROJECT_BINARY_DIR}/fastfetch_pciids.c.inc\")\n    if(Python_FOUND)\n        if(NOT EXISTS \"${PROJECT_BINARY_DIR}/pci.ids\")\n            message(STATUS \"'${PROJECT_BINARY_DIR}/pci.ids' is missing, downloading...\")\n            file(DOWNLOAD \"https://pci-ids.ucw.cz/v2.2/pci.ids\" \"${PROJECT_BINARY_DIR}/pci.ids\")\n        endif()\n        message(STATUS \"Generating 'fastfetch_pciids.c.inc'\")\n        execute_process(COMMAND ${Python_EXECUTABLE} \"${CMAKE_CURRENT_SOURCE_DIR}/scripts/gen-pciids.py\" \"${PROJECT_BINARY_DIR}/pci.ids\"\n                        OUTPUT_FILE \"${PROJECT_BINARY_DIR}/fastfetch_pciids.c.inc\"\n                        RESULT_VARIABLE PYTHON_PCIIDS_RETCODE)\n        if(NOT PYTHON_PCIIDS_RETCODE EQUAL 0)\n            file(REMOVE \"${PROJECT_BINARY_DIR}/fastfetch_pciids.c.inc\")\n            message(WARNING \"Failed to generate 'fastfetch_pciids.c.inc'\")\n            set(ENABLE_EMBEDDED_PCIIDS OFF)\n        endif()\n    else()\n        message(WARNING \"Python3 is not found, 'fastfetch_pciids.c.inc' will not be generated\")\n        set(ENABLE_EMBEDDED_PCIIDS OFF)\n    endif()\nendif()\n\nif(ENABLE_EMBEDDED_AMDGPUIDS AND NOT EXISTS \"${PROJECT_BINARY_DIR}/fastfetch_amdgpuids.c.inc\")\n    if(Python_FOUND)\n        if(NOT EXISTS \"${PROJECT_BINARY_DIR}/amdgpu.ids\")\n            message(STATUS \"'${PROJECT_BINARY_DIR}/amdgpu.ids' is missing, downloading...\")\n            file(DOWNLOAD \"https://gitlab.freedesktop.org/mesa/drm/-/raw/main/data/amdgpu.ids\" \"${PROJECT_BINARY_DIR}/amdgpu.ids\")\n        endif()\n        message(STATUS \"Generating 'fastfetch_amdgpuids.c.inc'\")\n        execute_process(COMMAND ${Python_EXECUTABLE} \"${CMAKE_CURRENT_SOURCE_DIR}/scripts/gen-amdgpuids.py\" \"${PROJECT_BINARY_DIR}/amdgpu.ids\"\n                        OUTPUT_FILE \"${PROJECT_BINARY_DIR}/fastfetch_amdgpuids.c.inc\"\n                        RESULT_VARIABLE PYTHON_AMDGPUIDS_RETCODE)\n        if(NOT PYTHON_AMDGPUIDS_RETCODE EQUAL 0)\n            file(REMOVE \"${PROJECT_BINARY_DIR}/fastfetch_amdgpuids.c.inc\")\n            message(WARNING \"Failed to generate 'fastfetch_amdgpuids.c.inc'\")\n            set(ENABLE_EMBEDDED_AMDGPUIDS OFF)\n        endif()\n    else()\n        message(WARNING \"Python3 is not found, 'fastfetch_amdgpuids.c.inc' will not be generated\")\n        set(ENABLE_EMBEDDED_AMDGPUIDS OFF)\n    endif()\nendif()\n\nif(Python_FOUND)\n    message(STATUS \"Generating 'fastfetch.1'\")\n    execute_process(COMMAND ${Python_EXECUTABLE} \"${CMAKE_CURRENT_SOURCE_DIR}/scripts/gen-man.py\"\n                    OUTPUT_FILE \"${PROJECT_BINARY_DIR}/fastfetch.1\"\n                    RESULT_VARIABLE PYTHON_MANPAGE_RETCODE)\n        if(NOT PYTHON_MANPAGE_RETCODE EQUAL 0)\n            message(FATAL_ERROR \"Failed to generate 'fastfetch.1'\")\n        endif()\nelse()\n    message(WARNING \"Python3 is not found, use basic 'fastfetch.1.in' instead\")\n    string(TIMESTAMP FASTFETCH_BUILD_DATE \"%d %B %Y\" UTC)\n    configure_file(doc/fastfetch.1.in fastfetch.1 @ONLY)\nendif()\n\nfastfetch_encode_c_string(\"${DATATEXT_JSON_HELP}\" DATATEXT_JSON_HELP)\nfastfetch_load_text(src/data/structure.txt DATATEXT_STRUCTURE)\n\nconfigure_file(src/fastfetch_config.h.in fastfetch_config.h @ONLY)\nconfigure_file(src/fastfetch_datatext.h.in fastfetch_datatext.h @ONLY)\nif(APPLE)\n    configure_file(src/common/apple/Info.plist.in Info.plist @ONLY)\nendif()\n\n####################\n# Ascii image data #\n####################\n\nfile(GLOB LOGO_FILES CONFIGURE_DEPENDS \"src/logo/ascii/*.txt\")\nset(LOGO_BUILTIN_H \"#pragma once\\n#pragma GCC diagnostic ignored \\\"-Wtrigraphs\\\"\\n\\n\")\nforeach(file ${LOGO_FILES})\n    fastfetch_load_text(\"${file}\" content)\n    get_filename_component(file \"${file}\" NAME_WE)\n    string(TOUPPER \"${file}\" file)\n    set(LOGO_BUILTIN_H \"${LOGO_BUILTIN_H}#define FASTFETCH_DATATEXT_LOGO_${file} ${content}\\n\")\nendforeach()\nfile(GENERATE OUTPUT logo_builtin.h CONTENT \"${LOGO_BUILTIN_H}\")\n\n#######################\n# libfastfetch target #\n#######################\n\nset(LIBFASTFETCH_SRC\n    src/common/impl/commandoption.c\n    src/common/impl/duration.c\n    src/common/impl/font.c\n    src/common/impl/format.c\n    src/common/impl/frequency.c\n    src/common/impl/init.c\n    src/common/impl/jsonconfig.c\n    src/common/impl/library.c\n    src/common/impl/netif.c\n    src/common/impl/networking_common.c\n    src/common/impl/option.c\n    src/common/impl/parsing.c\n    src/common/impl/percent.c\n    src/common/impl/printing.c\n    src/common/impl/properties.c\n    src/common/impl/settings.c\n    src/common/impl/size.c\n    src/common/impl/temps.c\n    src/common/impl/time.c\n    src/common/impl/edidHelper.c\n    src/common/impl/base64.c\n    src/common/impl/FFlist.c\n    src/common/impl/FFstrbuf.c\n    src/common/impl/path.c\n    src/common/impl/FFPlatform.c\n    src/common/impl/smbiosHelper.c\n    src/detection/bluetoothradio/bluetoothradio.c\n    src/detection/bootmgr/bootmgr.c\n    src/detection/chassis/chassis.c\n    src/detection/cpu/cpu.c\n    src/detection/cpuusage/cpuusage.c\n    src/detection/command/command.c\n    src/detection/disk/disk.c\n    src/detection/diskio/diskio.c\n    src/detection/displayserver/displayserver.c\n    src/detection/editor/editor.c\n    src/detection/font/font.c\n    src/detection/gpu/gpu.c\n    src/detection/media/media.c\n    src/detection/netio/netio.c\n    src/detection/opencl/opencl.c\n    src/detection/opengl/opengl_shared.c\n    src/detection/os/os.c\n    src/detection/packages/packages.c\n    src/detection/physicalmemory/physicalmemory.c\n    src/detection/publicip/publicip.c\n    src/detection/terminaltheme/terminaltheme.c\n    src/detection/terminalfont/terminalfont.c\n    src/detection/terminalshell/terminalshell.c\n    src/detection/version/version.c\n    src/detection/vulkan/vulkan.c\n    src/detection/weather/weather.c\n    src/detection/zpool/zpool.c\n    src/logo/builtin.c\n    src/logo/image/im6.c\n    src/logo/image/im7.c\n    src/logo/image/image.c\n    src/logo/logo.c\n    src/modules/battery/battery.c\n    src/modules/bios/bios.c\n    src/modules/bluetooth/bluetooth.c\n    src/modules/bluetoothradio/bluetoothradio.c\n    src/modules/board/board.c\n    src/modules/bootmgr/bootmgr.c\n    src/modules/brightness/brightness.c\n    src/modules/break/break.c\n    src/modules/btrfs/btrfs.c\n    src/modules/camera/camera.c\n    src/modules/chassis/chassis.c\n    src/modules/colors/colors.c\n    src/modules/cpu/cpu.c\n    src/modules/cpucache/cpucache.c\n    src/modules/cpuusage/cpuusage.c\n    src/modules/cursor/cursor.c\n    src/modules/custom/custom.c\n    src/modules/command/command.c\n    src/modules/datetime/datetime.c\n    src/modules/de/de.c\n    src/modules/disk/disk.c\n    src/modules/diskio/diskio.c\n    src/modules/dns/dns.c\n    src/modules/editor/editor.c\n    src/modules/font/font.c\n    src/modules/gpu/gpu.c\n    src/modules/host/host.c\n    src/modules/icons/icons.c\n    src/modules/initsystem/initsystem.c\n    src/modules/gamepad/gamepad.c\n    src/modules/kernel/kernel.c\n    src/modules/keyboard/keyboard.c\n    src/modules/lm/lm.c\n    src/modules/loadavg/loadavg.c\n    src/modules/locale/locale.c\n    src/modules/localip/localip.c\n    src/modules/logo/logo.c\n    src/modules/memory/memory.c\n    src/modules/monitor/monitor.c\n    src/modules/netio/netio.c\n    src/modules/opencl/opencl.c\n    src/modules/opengl/opengl.c\n    src/modules/os/os.c\n    src/modules/packages/packages.c\n    src/modules/physicaldisk/physicaldisk.c\n    src/modules/physicalmemory/physicalmemory.c\n    src/modules/processes/processes.c\n    src/modules/player/player.c\n    src/modules/poweradapter/poweradapter.c\n    src/modules/publicip/publicip.c\n    src/modules/display/display.c\n    src/modules/separator/separator.c\n    src/modules/shell/shell.c\n    src/modules/sound/sound.c\n    src/modules/swap/swap.c\n    src/modules/media/media.c\n    src/modules/mouse/mouse.c\n    src/modules/terminal/terminal.c\n    src/modules/terminaltheme/terminaltheme.c\n    src/modules/terminalfont/terminalfont.c\n    src/modules/terminalsize/terminalsize.c\n    src/modules/theme/theme.c\n    src/modules/title/title.c\n    src/modules/tpm/tpm.c\n    src/modules/uptime/uptime.c\n    src/modules/users/users.c\n    src/modules/version/version.c\n    src/modules/vulkan/vulkan.c\n    src/modules/wallpaper/wallpaper.c\n    src/modules/weather/weather.c\n    src/modules/wifi/wifi.c\n    src/modules/wm/wm.c\n    src/modules/wmtheme/wmtheme.c\n    src/modules/zpool/zpool.c\n    src/modules/modules.c\n    src/options/display.c\n    src/options/logo.c\n    src/options/general.c\n)\n\nif(LINUX)\n    list(APPEND LIBFASTFETCH_SRC\n        src/common/impl/dbus.c\n        src/common/impl/io_unix.c\n        src/common/impl/netif_linux.c\n        src/common/impl/networking_linux.c\n        src/common/impl/processing_linux.c\n        src/common/impl/FFPlatform_unix.c\n        src/common/impl/binary_linux.c\n        src/common/impl/kmod_linux.c\n        src/detection/battery/battery_linux.c\n        src/detection/bios/bios_linux.c\n        src/detection/board/board_linux.c\n        src/detection/bootmgr/bootmgr_linux.c\n        src/detection/brightness/brightness_linux.c\n        src/detection/btrfs/btrfs_linux.c\n        src/detection/chassis/chassis_linux.c\n        src/detection/cpu/cpu_linux.c\n        src/detection/cpucache/cpucache_linux.c\n        src/detection/cpuusage/cpuusage_linux.c\n        src/detection/cursor/cursor_linux.c\n        src/detection/bluetooth/bluetooth_linux.c\n        src/detection/bluetoothradio/bluetoothradio_linux.c\n        src/detection/disk/disk_linux.c\n        src/detection/dns/dns_linux.c\n        src/detection/physicaldisk/physicaldisk_linux.c\n        src/detection/physicalmemory/physicalmemory_linux.c\n        src/detection/diskio/diskio_linux.c\n        src/detection/displayserver/linux/displayserver_linux.c\n        src/detection/displayserver/linux/common.c\n        src/detection/displayserver/linux/drm.c\n        src/detection/displayserver/linux/wayland/wayland.c\n        src/detection/displayserver/linux/wayland/global-output.c\n        src/detection/displayserver/linux/wayland/zwlr-output.c\n        src/detection/displayserver/linux/wayland/kde-output.c\n        src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c\n        src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c\n        src/detection/displayserver/linux/wayland/kde-output-order-v1-protocol.c\n        src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c\n        src/detection/displayserver/linux/wmde.c\n        src/detection/displayserver/linux/xcb.c\n        src/detection/displayserver/linux/xlib.c\n        src/detection/font/font_linux.c\n        src/detection/gpu/gpu_linux.c\n        src/detection/gpu/gpu_drm.c\n        src/detection/gpu/gpu_pci.c\n        src/detection/gtk_qt/gtk.c\n        src/detection/host/host_linux.c\n        src/detection/host/host_mac.c\n        src/detection/icons/icons_linux.c\n        src/detection/initsystem/initsystem_linux.c\n        src/detection/keyboard/keyboard_linux.c\n        src/detection/libc/libc_linux.c\n        src/detection/lm/lm_linux.c\n        src/detection/loadavg/loadavg_linux.c\n        src/detection/locale/locale_linux.c\n        src/detection/localip/localip_linux.c\n        src/detection/gamepad/gamepad_linux.c\n        src/detection/media/media_linux.c\n        src/detection/memory/memory_linux.c\n        src/detection/mouse/mouse_linux.c\n        src/detection/netio/netio_linux.c\n        src/detection/opengl/opengl_linux.c\n        src/detection/os/os_linux.c\n        src/detection/packages/packages_linux.c\n        src/detection/packages/packages_nix.c\n        src/detection/poweradapter/poweradapter_linux.c\n        src/detection/processes/processes_linux.c\n        src/detection/gtk_qt/qt.c\n        src/detection/sound/sound_linux.c\n        src/detection/swap/swap_linux.c\n        src/detection/terminalfont/terminalfont_linux.c\n        src/detection/terminalshell/terminalshell_linux.c\n        src/detection/terminalsize/terminalsize_linux.c\n        src/detection/theme/theme_linux.c\n        src/detection/tpm/tpm_linux.c\n        src/detection/uptime/uptime_linux.c\n        src/detection/users/users_linux.c\n        src/detection/wallpaper/wallpaper_linux.c\n        src/detection/wifi/wifi_linux.c\n        src/detection/wm/wm_linux.c\n        src/detection/de/de_linux.c\n        src/detection/wmtheme/wmtheme_linux.c\n        src/detection/camera/camera_linux.c\n    )\nelseif(ANDROID)\n    list(APPEND LIBFASTFETCH_SRC\n        src/common/impl/dbus.c\n        src/common/impl/io_unix.c\n        src/common/impl/netif_linux.c\n        src/common/impl/networking_linux.c\n        src/common/impl/processing_linux.c\n        src/common/impl/FFPlatform_unix.c\n        src/common/impl/binary_linux.c\n        src/common/impl/kmod_linux.c\n        src/detection/battery/battery_android.c\n        src/detection/bios/bios_android.c\n        src/detection/bluetooth/bluetooth_nosupport.c\n        src/detection/bluetoothradio/bluetoothradio_nosupport.c\n        src/detection/board/board_android.c\n        src/detection/bootmgr/bootmgr_nosupport.c\n        src/detection/brightness/brightness_nosupport.c\n        src/detection/btrfs/btrfs_nosupport.c\n        src/detection/chassis/chassis_nosupport.c\n        src/detection/cpu/cpu_linux.c\n        src/detection/cpucache/cpucache_linux.c\n        src/detection/cursor/cursor_linux.c\n        src/detection/cpuusage/cpuusage_linux.c\n        src/detection/disk/disk_linux.c\n        src/detection/dns/dns_linux.c\n        src/detection/physicaldisk/physicaldisk_linux.c\n        src/detection/physicalmemory/physicalmemory_nosupport.c\n        src/detection/diskio/diskio_linux.c\n        src/detection/displayserver/displayserver_android.c\n        src/detection/displayserver/linux/common.c\n        src/detection/displayserver/linux/wmde.c\n        src/detection/displayserver/linux/xcb.c\n        src/detection/displayserver/linux/xlib.c\n        src/detection/gtk_qt/gtk.c\n        src/detection/gtk_qt/qt.c\n        src/detection/font/font_linux.c\n        src/detection/gpu/gpu_android.c\n        src/detection/host/host_android.c\n        src/detection/icons/icons_linux.c\n        src/detection/initsystem/initsystem_linux.c\n        src/detection/keyboard/keyboard_nosupport.c\n        src/detection/libc/libc_android.c\n        src/detection/lm/lm_nosupport.c\n        src/detection/loadavg/loadavg_linux.c\n        src/detection/locale/locale_linux.c\n        src/detection/localip/localip_linux.c\n        src/detection/gamepad/gamepad_nosupport.c\n        src/detection/media/media_linux.c\n        src/detection/memory/memory_linux.c\n        src/detection/mouse/mouse_nosupport.c\n        src/detection/netio/netio_linux.c\n        src/detection/opengl/opengl_linux.c\n        src/detection/os/os_android.c\n        src/detection/packages/packages_linux.c\n        src/detection/packages/packages_nix.c\n        src/detection/poweradapter/poweradapter_nosupport.c\n        src/detection/processes/processes_linux.c\n        src/detection/sound/sound_linux.c\n        src/detection/swap/swap_linux.c\n        src/detection/terminalfont/terminalfont_android.c\n        src/detection/terminalfont/terminalfont_linux.c\n        src/detection/terminalshell/terminalshell_linux.c\n        src/detection/terminalsize/terminalsize_linux.c\n        src/detection/theme/theme_linux.c\n        src/detection/tpm/tpm_nosupport.c\n        src/detection/uptime/uptime_linux.c\n        src/detection/users/users_linux.c\n        src/detection/wallpaper/wallpaper_linux.c\n        src/detection/wifi/wifi_android.c\n        src/detection/wm/wm_linux.c\n        src/detection/de/de_linux.c\n        src/detection/wmtheme/wmtheme_linux.c\n        src/detection/camera/camera_android.c\n    )\nelseif(FreeBSD)\n    list(APPEND LIBFASTFETCH_SRC\n        src/common/impl/dbus.c\n        src/common/impl/io_unix.c\n        src/common/impl/netif_bsd.c\n        src/common/impl/networking_linux.c\n        src/common/impl/processing_linux.c\n        src/common/impl/sysctl.c\n        src/common/impl/FFPlatform_unix.c\n        src/common/impl/binary_linux.c\n        src/common/impl/kmod_bsd.c\n        src/detection/battery/battery_bsd.c\n        src/detection/bios/bios_bsd.c\n        src/detection/bluetoothradio/bluetoothradio_nosupport.c\n        src/detection/board/board_bsd.c\n        src/detection/bootmgr/bootmgr_bsd.c\n        src/detection/brightness/brightness_bsd.c\n        src/detection/btrfs/btrfs_nosupport.c\n        src/detection/chassis/chassis_bsd.c\n        src/detection/cpu/cpu_bsd.c\n        src/detection/cpucache/cpucache_shared.c\n        src/detection/cpuusage/cpuusage_bsd.c\n        src/detection/cursor/cursor_linux.c\n        src/detection/disk/disk_bsd.c\n        src/detection/dns/dns_linux.c\n        src/detection/physicaldisk/physicaldisk_bsd.c\n        src/detection/physicalmemory/physicalmemory_linux.c\n        src/detection/diskio/diskio_bsd.c\n        src/detection/displayserver/linux/displayserver_linux.c\n        src/detection/displayserver/linux/common.c\n        src/detection/displayserver/linux/drm.c\n        src/detection/displayserver/linux/wayland/wayland.c\n        src/detection/displayserver/linux/wayland/global-output.c\n        src/detection/displayserver/linux/wayland/zwlr-output.c\n        src/detection/displayserver/linux/wayland/kde-output.c\n        src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c\n        src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c\n        src/detection/displayserver/linux/wayland/kde-output-order-v1-protocol.c\n        src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c\n        src/detection/displayserver/linux/wmde.c\n        src/detection/displayserver/linux/xcb.c\n        src/detection/displayserver/linux/xlib.c\n        src/detection/font/font_linux.c\n        src/detection/gpu/gpu_bsd.c\n        src/detection/gpu/gpu_drm.c\n        src/detection/gpu/gpu_pci.c\n        src/detection/gtk_qt/gtk.c\n        src/detection/host/host_bsd.c\n        src/detection/host/host_mac.c\n        src/detection/lm/lm_linux.c\n        src/detection/icons/icons_linux.c\n        src/detection/initsystem/initsystem_linux.c\n        src/detection/keyboard/keyboard_bsd.c\n        src/detection/libc/libc_bsd.c\n        src/detection/loadavg/loadavg_bsd.c\n        src/detection/locale/locale_linux.c\n        src/detection/localip/localip_linux.c\n        src/detection/gamepad/gamepad_bsd.c\n        src/detection/media/media_linux.c\n        src/detection/memory/memory_bsd.c\n        src/detection/mouse/mouse_bsd.c\n        src/detection/netio/netio_bsd.c\n        src/detection/opengl/opengl_linux.c\n        src/detection/os/os_linux.c\n        src/detection/packages/packages_bsd.c\n        src/detection/poweradapter/poweradapter_nosupport.c\n        src/detection/processes/processes_bsd.c\n        src/detection/gtk_qt/qt.c\n        src/detection/sound/sound_bsd.c\n        src/detection/swap/swap_bsd.c\n        src/detection/terminalfont/terminalfont_linux.c\n        src/detection/terminalshell/terminalshell_linux.c\n        src/detection/terminalsize/terminalsize_linux.c\n        src/detection/theme/theme_linux.c\n        src/detection/tpm/tpm_bsd.c\n        src/detection/uptime/uptime_bsd.c\n        src/detection/users/users_linux.c\n        src/detection/wallpaper/wallpaper_linux.c\n        src/detection/wm/wm_linux.c\n        src/detection/de/de_linux.c\n        src/detection/wmtheme/wmtheme_linux.c\n        src/detection/camera/camera_linux.c\n    )\n    if(DragonFly)\n        list(APPEND LIBFASTFETCH_SRC\n            src/detection/bluetooth/bluetooth_nosupport.c\n            src/detection/wifi/wifi_nosupport.c\n        )\n    else()\n        list(APPEND LIBFASTFETCH_SRC\n            src/detection/bluetooth/bluetooth_bsd.c\n            src/detection/wifi/wifi_bsd.c\n        )\n    endif()\nelseif(NetBSD)\n    list(APPEND LIBFASTFETCH_SRC\n        src/common/impl/dbus.c\n        src/common/impl/io_unix.c\n        src/common/impl/netif_bsd.c\n        src/common/impl/networking_linux.c\n        src/common/impl/processing_linux.c\n        src/common/impl/sysctl.c\n        src/common/impl/FFPlatform_unix.c\n        src/common/impl/binary_linux.c\n        src/common/impl/kmod_nbsd.c\n        src/detection/battery/battery_nbsd.c\n        src/detection/bios/bios_nbsd.c\n        src/detection/bluetooth/bluetooth_bsd.c\n        src/detection/bluetoothradio/bluetoothradio_nosupport.c\n        src/detection/board/board_nbsd.c\n        src/detection/bootmgr/bootmgr_bsd.c\n        src/detection/brightness/brightness_nbsd.c\n        src/detection/btrfs/btrfs_nosupport.c\n        src/detection/chassis/chassis_nbsd.c\n        src/detection/cpu/cpu_nbsd.c\n        src/detection/cpucache/cpucache_shared.c\n        src/detection/cpuusage/cpuusage_bsd.c\n        src/detection/cursor/cursor_linux.c\n        src/detection/disk/disk_bsd.c\n        src/detection/dns/dns_linux.c\n        src/detection/physicaldisk/physicaldisk_nosupport.c\n        src/detection/physicalmemory/physicalmemory_linux.c\n        src/detection/diskio/diskio_nbsd.c\n        src/detection/displayserver/linux/displayserver_linux.c\n        src/detection/displayserver/linux/common.c\n        src/detection/displayserver/linux/drm.c\n        src/detection/displayserver/linux/wayland/wayland.c\n        src/detection/displayserver/linux/wayland/global-output.c\n        src/detection/displayserver/linux/wayland/zwlr-output.c\n        src/detection/displayserver/linux/wayland/kde-output.c\n        src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c\n        src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c\n        src/detection/displayserver/linux/wayland/kde-output-order-v1-protocol.c\n        src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c\n        src/detection/displayserver/linux/wmde.c\n        src/detection/displayserver/linux/xcb.c\n        src/detection/displayserver/linux/xlib.c\n        src/detection/font/font_linux.c\n        src/detection/gpu/gpu_nbsd.c\n        src/detection/gpu/gpu_pci.c\n        src/detection/gtk_qt/gtk.c\n        src/detection/host/host_nbsd.c\n        src/detection/lm/lm_linux.c\n        src/detection/icons/icons_linux.c\n        src/detection/initsystem/initsystem_linux.c\n        src/detection/keyboard/keyboard_nosupport.c\n        src/detection/libc/libc_nosupport.c\n        src/detection/loadavg/loadavg_bsd.c\n        src/detection/locale/locale_linux.c\n        src/detection/localip/localip_linux.c\n        src/detection/gamepad/gamepad_nosupport.c\n        src/detection/media/media_linux.c\n        src/detection/memory/memory_nbsd.c\n        src/detection/mouse/mouse_nosupport.c\n        src/detection/netio/netio_bsd.c\n        src/detection/opengl/opengl_linux.c\n        src/detection/os/os_nbsd.c\n        src/detection/packages/packages_nbsd.c\n        src/detection/poweradapter/poweradapter_nosupport.c\n        src/detection/processes/processes_nbsd.c\n        src/detection/gtk_qt/qt.c\n        src/detection/sound/sound_nbsd.c\n        src/detection/swap/swap_obsd.c\n        src/detection/terminalfont/terminalfont_linux.c\n        src/detection/terminalshell/terminalshell_linux.c\n        src/detection/terminalsize/terminalsize_linux.c\n        src/detection/theme/theme_linux.c\n        src/detection/tpm/tpm_nosupport.c\n        src/detection/uptime/uptime_bsd.c\n        src/detection/users/users_linux.c\n        src/detection/wallpaper/wallpaper_linux.c\n        src/detection/wifi/wifi_nbsd.c\n        src/detection/wm/wm_linux.c\n        src/detection/de/de_linux.c\n        src/detection/wmtheme/wmtheme_linux.c\n        src/detection/camera/camera_linux.c\n    )\nelseif(OpenBSD)\n    list(APPEND LIBFASTFETCH_SRC\n        src/common/impl/dbus.c\n        src/common/impl/io_unix.c\n        src/common/impl/netif_bsd.c\n        src/common/impl/networking_linux.c\n        src/common/impl/processing_linux.c\n        src/common/impl/sysctl.c\n        src/common/impl/FFPlatform_unix.c\n        src/common/impl/binary_linux.c\n        src/common/impl/smbiosHelper.c\n        src/common/impl/kmod_nosupport.c\n        src/detection/battery/battery_obsd.c\n        src/detection/bios/bios_windows.c\n        src/detection/bluetooth/bluetooth_nosupport.c\n        src/detection/bluetoothradio/bluetoothradio_nosupport.c\n        src/detection/board/board_windows.c\n        src/detection/bootmgr/bootmgr_bsd.c\n        src/detection/brightness/brightness_obsd.c\n        src/detection/btrfs/btrfs_nosupport.c\n        src/detection/chassis/chassis_windows.c\n        src/detection/cpu/cpu_obsd.c\n        src/detection/cpucache/cpucache_shared.c\n        src/detection/cpuusage/cpuusage_bsd.c\n        src/detection/cursor/cursor_linux.c\n        src/detection/disk/disk_bsd.c\n        src/detection/dns/dns_linux.c\n        src/detection/physicaldisk/physicaldisk_nosupport.c\n        src/detection/physicalmemory/physicalmemory_linux.c\n        src/detection/diskio/diskio_obsd.c\n        src/detection/displayserver/linux/displayserver_linux.c\n        src/detection/displayserver/linux/common.c\n        src/detection/displayserver/linux/drm.c\n        src/detection/displayserver/linux/wayland/wayland.c\n        src/detection/displayserver/linux/wayland/global-output.c\n        src/detection/displayserver/linux/wayland/zwlr-output.c\n        src/detection/displayserver/linux/wayland/kde-output.c\n        src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c\n        src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c\n        src/detection/displayserver/linux/wayland/kde-output-order-v1-protocol.c\n        src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c\n        src/detection/displayserver/linux/wmde.c\n        src/detection/displayserver/linux/xcb.c\n        src/detection/displayserver/linux/xlib.c\n        src/detection/font/font_linux.c\n        src/detection/gpu/gpu_pci.c\n        src/detection/gpu/gpu_obsd.c\n        src/detection/gtk_qt/gtk.c\n        src/detection/host/host_obsd.c\n        src/detection/lm/lm_nosupport.c\n        src/detection/icons/icons_linux.c\n        src/detection/initsystem/initsystem_linux.c\n        src/detection/keyboard/keyboard_nosupport.c\n        src/detection/libc/libc_nosupport.c\n        src/detection/loadavg/loadavg_bsd.c\n        src/detection/locale/locale_linux.c\n        src/detection/localip/localip_linux.c\n        src/detection/gamepad/gamepad_nosupport.c\n        src/detection/media/media_linux.c\n        src/detection/memory/memory_obsd.c\n        src/detection/mouse/mouse_nosupport.c\n        src/detection/netio/netio_bsd.c\n        src/detection/opengl/opengl_linux.c\n        src/detection/os/os_obsd.c\n        src/detection/packages/packages_obsd.c\n        src/detection/poweradapter/poweradapter_nosupport.c\n        src/detection/processes/processes_obsd.c\n        src/detection/gtk_qt/qt.c\n        src/detection/sound/sound_obsd.c\n        src/detection/swap/swap_obsd.c\n        src/detection/terminalfont/terminalfont_linux.c\n        src/detection/terminalshell/terminalshell_linux.c\n        src/detection/terminalsize/terminalsize_linux.c\n        src/detection/theme/theme_linux.c\n        src/detection/tpm/tpm_nosupport.c\n        src/detection/uptime/uptime_bsd.c\n        src/detection/users/users_obsd.c\n        src/detection/wallpaper/wallpaper_linux.c\n        src/detection/wifi/wifi_obsd.c\n        src/detection/wm/wm_linux.c\n        src/detection/de/de_linux.c\n        src/detection/wmtheme/wmtheme_linux.c\n        src/detection/camera/camera_linux.c\n    )\nelseif(APPLE)\n    list(APPEND LIBFASTFETCH_SRC\n        src/common/impl/io_unix.c\n        src/common/impl/netif_apple.c\n        src/common/impl/networking_linux.c\n        src/common/impl/processing_linux.c\n        src/common/impl/sysctl.c\n        src/common/impl/FFPlatform_unix.c\n        src/common/impl/binary_apple.c\n        src/common/impl/kmod_apple.c\n        src/common/apple/cf_helpers.c\n        src/common/apple/osascript.m\n        src/common/apple/smc_temps.c\n        src/detection/battery/battery_apple.c\n        src/detection/bios/bios_apple.c\n        src/detection/bluetooth/bluetooth_apple.m\n        src/detection/bluetoothradio/bluetoothradio_apple.m\n        src/detection/board/board_apple.c\n        src/detection/bootmgr/bootmgr_apple.c\n        src/detection/brightness/brightness_apple.c\n        src/detection/btrfs/btrfs_nosupport.c\n        src/detection/chassis/chassis_apple.c\n        src/detection/cpu/cpu_apple.c\n        src/detection/cpucache/cpucache_apple.c\n        src/detection/cpuusage/cpuusage_apple.c\n        src/detection/cursor/cursor_apple.m\n        src/detection/disk/disk_bsd.c\n        src/detection/dns/dns_apple.c\n        src/detection/physicaldisk/physicaldisk_apple.c\n        src/detection/physicalmemory/physicalmemory_apple.m\n        src/detection/diskio/diskio_apple.c\n        src/detection/displayserver/displayserver_apple.c\n        src/detection/font/font_apple.m\n        src/detection/gpu/gpu_apple.c\n        src/detection/gpu/gpu_apple.m\n        src/detection/host/host_apple.c\n        src/detection/host/host_mac.c\n        src/detection/icons/icons_nosupport.c\n        src/detection/initsystem/initsystem_linux.c\n        src/detection/keyboard/keyboard_apple.c\n        src/detection/lm/lm_nosupport.c\n        src/detection/loadavg/loadavg_bsd.c\n        src/detection/libc/libc_apple.c\n        src/detection/locale/locale_linux.c\n        src/detection/localip/localip_linux.c\n        src/detection/gamepad/gamepad_apple.c\n        src/detection/media/media_apple.m\n        src/detection/memory/memory_apple.c\n        src/detection/mouse/mouse_apple.c\n        src/detection/netio/netio_apple.c\n        src/detection/opengl/opengl_apple.c\n        src/detection/os/os_apple.m\n        src/detection/packages/packages_apple.c\n        src/detection/packages/packages_nix.c\n        src/detection/poweradapter/poweradapter_apple.c\n        src/detection/processes/processes_bsd.c\n        src/detection/sound/sound_apple.c\n        src/detection/swap/swap_apple.c\n        src/detection/terminalfont/terminalfont_apple.m\n        src/detection/terminalshell/terminalshell_linux.c\n        src/detection/terminalsize/terminalsize_linux.c\n        src/detection/theme/theme_apple.c\n        src/detection/tpm/tpm_apple.c\n        src/detection/uptime/uptime_bsd.c\n        src/detection/users/users_linux.c\n        src/detection/wallpaper/wallpaper_apple.m\n        src/detection/wifi/wifi_apple.m\n        src/detection/wm/wm_apple.m\n        src/detection/de/de_nosupport.c\n        src/detection/wmtheme/wmtheme_apple.m\n        src/detection/camera/camera_apple.m\n    )\nelseif(WIN32)\n    list(APPEND LIBFASTFETCH_SRC\n        src/common/impl/io_windows.c\n        src/common/impl/netif_windows.c\n        src/common/impl/networking_windows.c\n        src/common/impl/processing_windows.c\n        src/common/impl/FFPlatform_windows.c\n        src/common/impl/binary_windows.c\n        src/common/impl/debug_windows.c\n        src/common/impl/kmod_windows.c\n        src/common/windows/getline.c\n        src/common/windows/com.cpp\n        src/common/windows/registry.c\n        src/common/windows/unicode.c\n        src/common/windows/wmi.cpp\n        src/common/windows/variant.cpp\n        src/common/windows/version.c\n        src/detection/battery/battery_windows.c\n        src/detection/bios/bios_windows.c\n        src/detection/bluetooth/bluetooth_windows.c\n        src/detection/bluetooth/bluetooth_windows.cpp\n        src/detection/bluetoothradio/bluetoothradio_windows.c\n        src/detection/board/board_windows.c\n        src/detection/bootmgr/bootmgr_windows.c\n        src/detection/brightness/brightness_windows.cpp\n        src/detection/btrfs/btrfs_nosupport.c\n        src/detection/chassis/chassis_windows.c\n        src/detection/cpu/cpu_windows.c\n        src/detection/cpucache/cpucache_windows.c\n        src/detection/cpuusage/cpuusage_windows.c\n        src/detection/cursor/cursor_windows.c\n        src/detection/disk/disk_windows.c\n        src/detection/physicaldisk/physicaldisk_windows.c\n        src/detection/diskio/diskio_windows.c\n        src/detection/displayserver/displayserver_windows.c\n        src/detection/dns/dns_windows.c\n        src/detection/font/font_windows.c\n        src/detection/gpu/gpu_windows.c\n        src/detection/host/host_mac.c\n        src/detection/host/host_windows.c\n        src/detection/icons/icons_windows.c\n        src/detection/initsystem/initsystem_nosupport.c\n        src/detection/keyboard/keyboard_windows.c\n        src/detection/libc/libc_windows.cpp\n        src/detection/lm/lm_nosupport.c\n        src/detection/loadavg/loadavg_nosupport.c\n        src/detection/locale/locale_windows.c\n        src/detection/localip/localip_windows.c\n        src/detection/gamepad/gamepad_windows.c\n        src/detection/media/media_windows.c\n        src/detection/memory/memory_windows.c\n        src/detection/mouse/mouse_windows.c\n        src/detection/physicalmemory/physicalmemory_linux.c\n        src/detection/netio/netio_windows.c\n        src/detection/opengl/opengl_windows.c\n        src/detection/os/os_windows.c\n        src/detection/packages/packages_windows.c\n        src/detection/poweradapter/poweradapter_nosupport.c\n        src/detection/processes/processes_windows.c\n        src/detection/sound/sound_windows.cpp\n        src/detection/swap/swap_windows.c\n        src/detection/terminalfont/terminalfont_windows.c\n        src/detection/terminalshell/terminalshell_windows.c\n        src/detection/terminalsize/terminalsize_windows.c\n        src/detection/theme/theme_windows.c\n        src/detection/tpm/tpm_windows.c\n        src/detection/uptime/uptime_windows.c\n        src/detection/users/users_windows.c\n        src/detection/wallpaper/wallpaper_windows.c\n        src/detection/wifi/wifi_windows.c\n        src/detection/wm/wm_windows.c\n        src/detection/de/de_nosupport.c\n        src/detection/wmtheme/wmtheme_windows.c\n        src/detection/camera/camera_windows.cpp\n    )\nelseif(SunOS)\n    list(APPEND LIBFASTFETCH_SRC\n        src/common/impl/dbus.c\n        src/common/impl/io_unix.c\n        src/common/impl/netif_apple.c\n        src/common/impl/networking_linux.c\n        src/common/impl/processing_linux.c\n        src/common/impl/FFPlatform_unix.c\n        src/common/impl/binary_linux.c\n        src/common/impl/kmod_sunos.c\n        src/detection/battery/battery_nosupport.c\n        src/detection/bios/bios_windows.c\n        src/detection/board/board_windows.c\n        src/detection/bootmgr/bootmgr_nosupport.c\n        src/detection/brightness/brightness_nosupport.c\n        src/detection/btrfs/btrfs_nosupport.c\n        src/detection/chassis/chassis_windows.c\n        src/detection/cpu/cpu_sunos.c\n        src/detection/cpucache/cpucache_shared.c\n        src/detection/cpuusage/cpuusage_sunos.c\n        src/detection/cursor/cursor_linux.c\n        src/detection/bluetooth/bluetooth_nosupport.c\n        src/detection/bluetoothradio/bluetoothradio_nosupport.c\n        src/detection/disk/disk_sunos.c\n        src/detection/dns/dns_linux.c\n        src/detection/physicaldisk/physicaldisk_sunos.c\n        src/detection/physicalmemory/physicalmemory_linux.c\n        src/detection/diskio/diskio_sunos.c\n        src/detection/displayserver/linux/displayserver_linux.c\n        src/detection/displayserver/linux/common.c\n        src/detection/displayserver/linux/drm.c\n        src/detection/displayserver/linux/wayland/wayland.c\n        src/detection/displayserver/linux/wayland/global-output.c\n        src/detection/displayserver/linux/wayland/zwlr-output.c\n        src/detection/displayserver/linux/wayland/kde-output.c\n        src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c\n        src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c\n        src/detection/displayserver/linux/wayland/kde-output-order-v1-protocol.c\n        src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c\n        src/detection/displayserver/linux/wmde.c\n        src/detection/displayserver/linux/xcb.c\n        src/detection/displayserver/linux/xlib.c\n        src/detection/font/font_linux.c\n        src/detection/gpu/gpu_sunos.c\n        src/detection/gpu/gpu_pci.c\n        src/detection/gtk_qt/gtk.c\n        src/detection/host/host_windows.c\n        src/detection/icons/icons_linux.c\n        src/detection/initsystem/initsystem_linux.c\n        src/detection/keyboard/keyboard_nosupport.c\n        src/detection/libc/libc_nosupport.c\n        src/detection/lm/lm_nosupport.c\n        src/detection/loadavg/loadavg_sunos.c\n        src/detection/locale/locale_linux.c\n        src/detection/localip/localip_linux.c\n        src/detection/gamepad/gamepad_nosupport.c\n        src/detection/media/media_linux.c\n        src/detection/memory/memory_sunos.c\n        src/detection/mouse/mouse_nosupport.c\n        src/detection/netio/netio_sunos.c\n        src/detection/opengl/opengl_linux.c\n        src/detection/os/os_sunos.c\n        src/detection/packages/packages_sunos.c\n        src/detection/poweradapter/poweradapter_nosupport.c\n        src/detection/processes/processes_linux.c\n        src/detection/gtk_qt/qt.c\n        src/detection/sound/sound_sunos.c\n        src/detection/swap/swap_sunos.c\n        src/detection/terminalfont/terminalfont_linux.c\n        src/detection/terminalshell/terminalshell_linux.c\n        src/detection/terminalsize/terminalsize_linux.c\n        src/detection/theme/theme_linux.c\n        src/detection/tpm/tpm_nosupport.c\n        src/detection/uptime/uptime_sunos.c\n        src/detection/users/users_linux.c\n        src/detection/wallpaper/wallpaper_linux.c\n        src/detection/wifi/wifi_nosupport.c\n        src/detection/wm/wm_nosupport.c\n        src/detection/de/de_linux.c\n        src/detection/wmtheme/wmtheme_linux.c\n        src/detection/camera/camera_nosupport.c\n    )\nelseif(Haiku)\n    list(APPEND LIBFASTFETCH_SRC\n        src/common/impl/dbus.c\n        src/common/impl/io_unix.c\n        src/common/impl/netif_haiku.c\n        src/common/impl/networking_linux.c\n        src/common/impl/processing_linux.c\n        src/common/impl/FFPlatform_unix.c\n        src/common/impl/binary_linux.c\n        src/common/impl/kmod_nosupport.c\n        src/common/haiku/version.cpp\n        src/detection/battery/battery_haiku.c\n        src/detection/bios/bios_windows.c\n        src/detection/board/board_windows.c\n        src/detection/bootmgr/bootmgr_nosupport.c\n        src/detection/brightness/brightness_nosupport.c\n        src/detection/btrfs/btrfs_nosupport.c\n        src/detection/chassis/chassis_windows.c\n        src/detection/cpu/cpu_haiku.c\n        src/detection/cpucache/cpucache_shared.c\n        src/detection/cpuusage/cpuusage_haiku.c\n        src/detection/cursor/cursor_nosupport.c\n        src/detection/bluetooth/bluetooth_haiku.cpp\n        src/detection/bluetoothradio/bluetoothradio_nosupport.c\n        src/detection/disk/disk_haiku.cpp\n        src/detection/dns/dns_linux.c\n        src/detection/physicaldisk/physicaldisk_haiku.c\n        src/detection/physicalmemory/physicalmemory_linux.c\n        src/detection/diskio/diskio_nosupport.c\n        src/detection/displayserver/displayserver_haiku.cpp\n        src/detection/font/font_haiku.cpp\n        src/detection/gpu/gpu_haiku.c\n        src/detection/gpu/gpu_pci.c\n        src/detection/gtk_qt/gtk.c\n        src/detection/host/host_windows.c\n        src/detection/icons/icons_nosupport.c\n        src/detection/initsystem/initsystem_haiku.c\n        src/detection/keyboard/keyboard_haiku.cpp\n        src/detection/libc/libc_nosupport.c\n        src/detection/lm/lm_nosupport.c\n        src/detection/loadavg/loadavg_nosupport.c\n        src/detection/locale/locale_linux.c\n        src/detection/localip/localip_linux.c\n        src/detection/gamepad/gamepad_haiku.cpp\n        src/detection/media/media_linux.c\n        src/detection/memory/memory_haiku.c\n        src/detection/mouse/mouse_haiku.cpp\n        src/detection/netio/netio_haiku.cpp\n        src/detection/opengl/opengl_haiku.cpp\n        src/detection/os/os_haiku.c\n        src/detection/packages/packages_haiku.c\n        src/detection/poweradapter/poweradapter_nosupport.c\n        src/detection/processes/processes_haiku.c\n        src/detection/gtk_qt/qt.c\n        src/detection/sound/sound_haiku.cpp\n        src/detection/swap/swap_haiku.c\n        src/detection/terminalfont/terminalfont_linux.c\n        src/detection/terminalshell/terminalshell_linux.c\n        src/detection/terminalsize/terminalsize_linux.c\n        src/detection/theme/theme_nosupport.c\n        src/detection/tpm/tpm_nosupport.c\n        src/detection/uptime/uptime_haiku.c\n        src/detection/users/users_linux.c\n        src/detection/wallpaper/wallpaper_nosupport.c\n        src/detection/wifi/wifi_nosupport.c\n        src/detection/wm/wm_nosupport.c\n        src/detection/de/de_nosupport.c\n        src/detection/wmtheme/wmtheme_nosupport.c\n        src/detection/camera/camera_nosupport.c\n    )\nelseif(GNU)\n    list(APPEND LIBFASTFETCH_SRC\n        src/common/impl/dbus.c\n        src/common/impl/io_unix.c\n        src/common/impl/netif_gnu.c\n        src/common/impl/networking_linux.c\n        src/common/impl/processing_linux.c\n        src/common/impl/FFPlatform_unix.c\n        src/common/impl/binary_linux.c\n        src/common/impl/kmod_nosupport.c\n        src/detection/battery/battery_nosupport.c\n        src/detection/bios/bios_nosupport.c\n        src/detection/board/board_nosupport.c\n        src/detection/bootmgr/bootmgr_nosupport.c\n        src/detection/brightness/brightness_nosupport.c\n        src/detection/btrfs/btrfs_nosupport.c\n        src/detection/chassis/chassis_nosupport.c\n        src/detection/cpu/cpu_linux.c\n        src/detection/cpucache/cpucache_nosupport.c\n        src/detection/cpuusage/cpuusage_linux.c\n        src/detection/cursor/cursor_linux.c\n        src/detection/bluetooth/bluetooth_linux.c\n        src/detection/bluetoothradio/bluetoothradio_linux.c\n        src/detection/disk/disk_linux.c\n        src/detection/dns/dns_linux.c\n        src/detection/physicaldisk/physicaldisk_nosupport.c\n        src/detection/physicalmemory/physicalmemory_nosupport.c\n        src/detection/diskio/diskio_nosupport.c\n        src/detection/displayserver/linux/displayserver_linux.c\n        src/detection/displayserver/linux/common.c\n        src/detection/displayserver/linux/drm.c\n        src/detection/displayserver/linux/wayland/wayland.c\n        src/detection/displayserver/linux/wayland/global-output.c\n        src/detection/displayserver/linux/wayland/zwlr-output.c\n        src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c\n        src/detection/displayserver/linux/wmde.c\n        src/detection/displayserver/linux/xcb.c\n        src/detection/displayserver/linux/xlib.c\n        src/detection/font/font_linux.c\n        src/detection/gpu/gpu_gnu.c\n        src/detection/gpu/gpu_pci.c\n        src/detection/gtk_qt/gtk.c\n        src/detection/host/host_nosupport.c\n        src/detection/icons/icons_linux.c\n        src/detection/initsystem/initsystem_linux.c\n        src/detection/keyboard/keyboard_nosupport.c\n        src/detection/libc/libc_linux.c\n        src/detection/lm/lm_linux.c\n        src/detection/loadavg/loadavg_linux.c\n        src/detection/locale/locale_linux.c\n        src/detection/localip/localip_linux.c\n        src/detection/gamepad/gamepad_nosupport.c\n        src/detection/media/media_linux.c\n        src/detection/memory/memory_linux.c\n        src/detection/mouse/mouse_nosupport.c\n        src/detection/netio/netio_nosupport.c\n        src/detection/opengl/opengl_linux.c\n        src/detection/os/os_linux.c\n        src/detection/packages/packages_linux.c\n        src/detection/packages/packages_nix.c\n        src/detection/poweradapter/poweradapter_nosupport.c\n        src/detection/processes/processes_linux.c\n        src/detection/gtk_qt/qt.c\n        src/detection/sound/sound_linux.c\n        src/detection/swap/swap_linux.c\n        src/detection/terminalfont/terminalfont_linux.c\n        src/detection/terminalshell/terminalshell_linux.c\n        src/detection/terminalsize/terminalsize_linux.c\n        src/detection/theme/theme_linux.c\n        src/detection/tpm/tpm_nosupport.c\n        src/detection/uptime/uptime_linux.c\n        src/detection/users/users_linux.c\n        src/detection/wallpaper/wallpaper_linux.c\n        src/detection/wifi/wifi_nosupport.c\n        src/detection/wm/wm_linux.c\n        src/detection/de/de_linux.c\n        src/detection/wmtheme/wmtheme_linux.c\n        src/detection/camera/camera_nosupport.c\n   )\nendif()\n\nif(ENABLE_DIRECTX_HEADERS)\n    message(STATUS \"Enabling DirectX headers for WSL\")\n    list(APPEND LIBFASTFETCH_SRC src/detection/gpu/gpu_wsl.cpp)\nendif()\n\n# Proprietary GPU driver APIs\nif(LINUX OR FreeBSD OR WIN32)\n    list(APPEND LIBFASTFETCH_SRC src/detection/gpu/gpu_nvidia.c)\n    list(APPEND LIBFASTFETCH_SRC src/detection/gpu/gpu_mthreads.c)\nendif()\nif(WIN32)\n    list(APPEND LIBFASTFETCH_SRC src/detection/gpu/gpu_intel.c)\n    list(APPEND LIBFASTFETCH_SRC src/detection/gpu/gpu_amd.c)\nendif()\ninclude(CheckFunctionExists)\ncheck_function_exists(wcwidth HAVE_WCWIDTH)\nif(NOT HAVE_WCWIDTH)\n    list(APPEND LIBFASTFETCH_SRC src/common/impl/wcwidth.c)\nendif()\nif(NOT WIN32)\n    check_function_exists(pipe2 HAVE_PIPE2)\nendif()\ncheck_function_exists(memrchr HAVE_MEMRCHR)\nif(NOT HAVE_MEMRCHR)\n    list(APPEND LIBFASTFETCH_SRC src/common/impl/memrchr.c)\nendif()\n\nif(ENABLE_SYSTEM_YYJSON)\n    find_package(yyjson)\n    if(yyjson_FOUND)\n        message(STATUS \"System provided yyjson is used\")\n    else()\n        message(FATAL_ERROR \"ENABLE_SYSTEM_YYJSON is set but system provided yyjson is not found\")\n    endif()\nelse()\n    list(APPEND LIBFASTFETCH_SRC\n        src/3rdparty/yyjson/yyjson.c\n    )\nendif()\n\nadd_library(libfastfetch OBJECT\n    ${LIBFASTFETCH_SRC}\n)\n\ninclude(CheckCSourceCompiles)\ncheck_c_source_compiles(\"int main(void){int arr[1];return _Countof(arr);}\" COMPILER_SUPPORTS_COUNT_OF)\nif(COMPILER_SUPPORTS_COUNT_OF)\n    message(STATUS \"_Countof is supported by the compiler\")\n    target_compile_definitions(libfastfetch PUBLIC FF_SUPPORTS_COUNT_OF=1)\nelse()\n    message(STATUS \"_Countof is NOT supported by the compiler\")\nendif()\n\nif(yyjson_FOUND)\n    target_compile_definitions(libfastfetch PUBLIC FF_USE_SYSTEM_YYJSON=1)\n    target_link_libraries(libfastfetch PUBLIC yyjson::yyjson)\n    # `target_link_libraries(yyjson::yyjson)` sets rpath implicitly\nendif()\n\n# Used for dlopen finding dylibs installed by homebrew\n# `/opt/homebrew/lib` is not on in dlopen search path by default\nif(APPLE AND BINARY_LINK_TYPE STREQUAL \"dlopen\")\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,/opt/homebrew/lib -Wl,-rpath,/usr/local/lib\")\nendif()\n\nif(ANDROID)\n    if(CMAKE_SIZEOF_VOID_P EQUAL 8)\n        set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,/vendor/lib64 -Wl,-rpath,/system/lib64\")\n    else()\n        set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,/vendor/lib -Wl,-rpath,/system/lib\")\n    endif()\nendif()\n\nif(LINUX AND EXISTS \"/lib/ld-musl-${CMAKE_HOST_SYSTEM_PROCESSOR}.so.1\")\n    execute_process(COMMAND \"/lib/ld-musl-${CMAKE_HOST_SYSTEM_PROCESSOR}.so.1\"\n                    ERROR_VARIABLE LD_MUSL_RESULT)\n    if(\"${LD_MUSL_RESULT}\" MATCHES \"Version ([0-9]+\\\\.[0-9]+\\\\.[0-9]+)\")\n        target_compile_definitions(libfastfetch PUBLIC FF_MUSL_VERSION=\"${CMAKE_MATCH_1}\")\n    endif()\nendif()\nif(APPLE AND EXISTS \"/usr/bin/otool\")\n    execute_process(COMMAND /usr/bin/otool -L /usr/bin/otool\n                    OUTPUT_VARIABLE OTOOL_OTOOL_RESULT)\n    if(\"${OTOOL_OTOOL_RESULT}\" MATCHES \"libSystem\\\\.B\\\\.dylib \\\\(.*current version ([0-9]+\\\\.[0-9]+\\\\.[0-9]+)\\\\)\")\n        target_compile_definitions(libfastfetch PUBLIC FF_LIBSYSTEM_VERSION=\"${CMAKE_MATCH_1}\")\n    endif()\nendif()\nif(MidnightBSD AND EXISTS \"/usr/bin/objdump\")\n    execute_process(COMMAND /bin/sh -c \"/usr/bin/objdump -T /lib/libc.so.* | grep 'FBSD_[0-9][0-9]*\\\\.[0-9][0-9]*' -o | sort -Vru | head -1\"\n                    OUTPUT_VARIABLE OBJDUMP_T_RESULT)\n    if(\"${OBJDUMP_T_RESULT}\" MATCHES \"FBSD_([0-9]+\\\\.[0-9]+)\")\n        message(STATUS \"Found FBSD ${CMAKE_MATCH_1}\")\n        target_compile_definitions(libfastfetch PUBLIC FF_FBSD_VERSION=\"${CMAKE_MATCH_1}\")\n    endif()\nelseif(FreeBSD AND EXISTS \"/usr/local/bin/objdump\")\n    execute_process(COMMAND /bin/sh -c \"/usr/local/bin/objdump -T /lib/libc.so.* | grep 'FBSD_[0-9][0-9]*\\\\.[0-9][0-9]*' -o | sort -Vru | head -1\"\n                    OUTPUT_VARIABLE OBJDUMP_T_RESULT)\n    if(\"${OBJDUMP_T_RESULT}\" MATCHES \"FBSD_([0-9]+\\\\.[0-9]+)\")\n        message(STATUS \"Found FBSD ${CMAKE_MATCH_1}\")\n        target_compile_definitions(libfastfetch PUBLIC FF_FBSD_VERSION=\"${CMAKE_MATCH_1}\")\n    endif()\nelseif(DragonFly AND EXISTS \"/usr/local/bin/objdump\")\n    execute_process(COMMAND /bin/sh -c \"/usr/local/bin/objdump -T /lib/libc.so.* | grep 'DF[0-9][0-9]*\\\\.[0-9][0-9]*' -o | sort -Vru | head -1\"\n                    OUTPUT_VARIABLE OBJDUMP_T_RESULT)\n    if(\"${OBJDUMP_T_RESULT}\" MATCHES \"DF([0-9]+\\\\.[0-9]+)\")\n        message(STATUS \"Found DF ${CMAKE_MATCH_1}\")\n        target_compile_definitions(libfastfetch PUBLIC FF_DF_VERSION=\"${CMAKE_MATCH_1}\")\n    endif()\nendif()\n\nif(LINUX)\n    target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE _XOPEN_SOURCE _ATFILE_SOURCE __STDC_WANT_LIB_EXT1__ _FILE_OFFSET_BITS=64 _TIME_BITS=64) # \"$<$<CONFIG:Release>:_FORTIFY_SOURCE=3>\"\nelseif(ANDROID)\n    target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE _XOPEN_SOURCE _FILE_OFFSET_BITS=64 \"$<$<CONFIG:DEBUG>:__BIONIC_FORTIFY>\" \"$<$<CONFIG:DEBUG>:__BIONIC_FORTIFY_RUNTIME_CHECKS_ENABLED>\")\nelseif(WIN32)\n    target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x0A00 NOMINMAX UNICODE) # \"$<$<CONFIG:Release>:_FORTIFY_SOURCE=3>\"\nelseif(APPLE)\n    target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE _XOPEN_SOURCE __STDC_WANT_LIB_EXT1__ _FILE_OFFSET_BITS=64 _DARWIN_C_SOURCE)\nelseif(OpenBSD)\n    target_compile_definitions(libfastfetch PUBLIC _XOPEN_SOURCE=700 _FILE_OFFSET_BITS=64 _BSD_SOURCE)\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,/usr/X11R6/lib\") # detect x11 lib path automatically\nelseif(DragonFly)\n    target_compile_definitions(libfastfetch PUBLIC __FreeBSD__)\nelseif(SunOS)\n    target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE __STDC_WANT_LIB_EXT1__ _FILE_OFFSET_BITS=64 __EXTENSIONS__ _POSIX_C_SOURCE)\nelseif(NetBSD)\n    target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE)\n    set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -Wno-char-subscripts\")\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,/usr/X11R7/lib -Wl,-rpath,/usr/pkg/lib\") # ditto\nelseif(Haiku)\n    target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE)\nelseif(GNU)\n    # On Hurd PATH_MAX is not defined. Set an arbitrary limit as workaround.\n    target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE PATH_MAX=4096 O_PATH=0)\nendif()\n\nif(APPLE)\n    if(ENABLE_APPLE_MEMSIZE_USABLE)\n        target_compile_definitions(libfastfetch PUBLIC FF_APPLE_MEMSIZE_USABLE=1)\n    else()\n        target_compile_definitions(libfastfetch PUBLIC FF_APPLE_MEMSIZE_USABLE=0)\n    endif()\nendif()\n\nif(WIN32)\n    check_function_exists(_msize HAVE_MSVC_MSIZE)\n    if(HAVE_MSVC_MSIZE)\n        target_compile_definitions(libfastfetch PUBLIC FF_HAVE_MSVC_MSIZE=1)\n    endif()\n\n    if(ENABLE_WIN81_COMPAT)\n        target_compile_definitions(libfastfetch PUBLIC FF_WIN81_COMPAT=1)\n    else()\n        target_compile_definitions(libfastfetch PUBLIC FF_WIN81_COMPAT=0)\n    endif()\nelse()\n    check_function_exists(malloc_usable_size HAVE_MALLOC_USABLE_SIZE)\n    if(HAVE_MALLOC_USABLE_SIZE)\n        target_compile_definitions(libfastfetch PUBLIC FF_HAVE_MALLOC_USABLE_SIZE=1)\n    else()\n        check_function_exists(malloc_size HAVE_MALLOC_SIZE)\n        if(HAVE_MALLOC_SIZE)\n            target_compile_definitions(libfastfetch PUBLIC FF_HAVE_MALLOC_SIZE=1)\n        endif()\n    endif()\nendif()\n\nif(FreeBSD OR OpenBSD OR NetBSD)\n    include(CheckStructHasMember)\n    set(CMAKE_REQUIRED_DEFINITIONS \"-D_IFI_OQDROPS=1\")\n    CHECK_STRUCT_HAS_MEMBER(\"struct if_data\" ifi_oqdrops net/if.h HAVE_IFI_OQDROPS LANGUAGE C)\n    if(HAVE_IFI_OQDROPS)\n    target_compile_definitions(libfastfetch PUBLIC _IFI_OQDROPS FF_HAVE_IFI_OQDROPS)\n    endif()\n    unset(CMAKE_REQUIRED_DEFINITIONS)\nendif()\n\nif(HAVE_WCWIDTH)\n    target_compile_definitions(libfastfetch PUBLIC FF_HAVE_WCWIDTH)\nendif()\n\nif(HAVE_PIPE2)\n    target_compile_definitions(libfastfetch PUBLIC FF_HAVE_PIPE2)\nendif()\n\nif(NOT \"${CUSTOM_PCI_IDS_PATH}\" STREQUAL \"\")\n    message(STATUS \"Custom file path of pci.ids: ${CUSTOM_PCI_IDS_PATH}\")\n    target_compile_definitions(libfastfetch PRIVATE FF_CUSTOM_PCI_IDS_PATH=${CUSTOM_PCI_IDS_PATH})\nendif()\nif(NOT \"${CUSTOM_AMDGPU_IDS_PATH}\" STREQUAL \"\")\n    message(STATUS \"Custom file path of amdgpu.ids: ${CUSTOM_AMDGPU_IDS_PATH}\")\n    target_compile_definitions(libfastfetch PRIVATE FF_CUSTOM_AMDGPU_IDS_PATH=${CUSTOM_AMDGPU_IDS_PATH})\nendif()\nif(NOT \"${CUSTOM_OS_RELEASE_PATH}\" STREQUAL \"\")\n    message(STATUS \"Custom file path of os_release: ${CUSTOM_OS_RELEASE_PATH}\")\n    target_compile_definitions(libfastfetch PRIVATE FF_CUSTOM_OS_RELEASE_PATH=${CUSTOM_OS_RELEASE_PATH})\nendif()\nif(NOT \"${CUSTOM_LSB_RELEASE_PATH}\" STREQUAL \"\")\n    message(STATUS \"Custom file path of lsb_release: ${CUSTOM_LSB_RELEASE_PATH}\")\n    target_compile_definitions(libfastfetch PRIVATE FF_CUSTOM_LSB_RELEASE_PATH=${CUSTOM_LSB_RELEASE_PATH})\nendif()\n\nif(NOT BINARY_LINK_TYPE STREQUAL \"dlopen\")\n    message(STATUS \"Enabling custom link type: ${BINARY_LINK_TYPE}\")\n    target_compile_definitions(libfastfetch PRIVATE FF_DISABLE_DLOPEN=1)\n    if(NOT WIN32)\n        set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -Wl,--copy-dt-needed-entries\")\n    endif()\nendif()\n\nfunction(ff_lib_enable VARNAME PKGCONFIG_NAMES CMAKE_NAME)\n    if(NOT ENABLE_${VARNAME})\n        return()\n    endif()\n\n    if(PKG_CONFIG_FOUND)\n        pkg_search_module(${VARNAME} QUIET ${PKGCONFIG_NAMES})\n    endif()\n\n    if(NOT ${VARNAME}_FOUND)\n        find_package(${CMAKE_NAME} QUIET)\n\n        set(${VARNAME}_FOUND ${${CMAKE_NAME}_FOUND})\n        set(${VARNAME}_INCLUDE_DIRS ${${CMAKE_NAME}_INCLUDE_DIRS})\n        set(${VARNAME}_LIBRARIES ${${CMAKE_NAME}_LIBRARIES})\n        set(${VARNAME}_CFLAGS_OTHER ${${CMAKE_NAME}_CFLAGS_OTHER})\n    endif()\n\n    if(NOT ${VARNAME}_FOUND)\n        message(STATUS \"Library: missing: ${VARNAME}\")\n        return()\n    endif()\n\n    message(STATUS \"Library: found ${VARNAME}\")\n\n    target_compile_definitions(libfastfetch PRIVATE FF_HAVE_${VARNAME}=1)\n    target_include_directories(libfastfetch PRIVATE ${${VARNAME}_INCLUDE_DIRS})\n\n    if(NOT BINARY_LINK_TYPE STREQUAL \"dlopen\")\n        target_link_directories(libfastfetch PUBLIC ${${VARNAME}_LIBRARY_DIRS})\n        target_link_libraries(libfastfetch PRIVATE ${${VARNAME}_LIBRARIES})\n    endif()\n\n    foreach(FLAG ${${VARNAME}_CFLAGS_OTHER})\n        if(FLAG MATCHES \"-D.*\")\n            string(SUBSTRING ${FLAG} 2 -1 FLAG)\n            target_compile_definitions(libfastfetch PRIVATE ${FLAG})\n        endif()\n    endforeach()\nendfunction()\n\nff_lib_enable(VULKAN\n    \"vulkan\"\n    \"Vulkan\"\n)\nff_lib_enable(WAYLAND\n    \"wayland-client\"\n    \"WaylandClient\"\n)\nff_lib_enable(XCB_RANDR\n    \"xcb-randr\"\n    \"XcbRandr\"\n)\nff_lib_enable(XRANDR\n    \"xrandr\"\n    \"XRandr\"\n)\nff_lib_enable(DRM\n    \"libdrm\"\n    \"Libdrm\"\n)\nff_lib_enable(DRM_AMDGPU\n    \"libdrm_amdgpu\"\n    \"Libdrm_Amdgpu\"\n)\nff_lib_enable(GIO\n    \"gio-2.0\"\n    \"GIO\"\n)\nff_lib_enable(DCONF\n    \"dconf\"\n    \"DConf\"\n)\nff_lib_enable(DBUS\n    \"dbus-1\"\n    \"DBus\"\n)\nff_lib_enable(SQLITE3\n    \"sqlite3\"\n    \"SQLite3\"\n)\nff_lib_enable(RPM\n    \"rpm\"\n    \"RPM\"\n)\nff_lib_enable(IMAGEMAGICK7\n    \"MagickCore-7.Q16HDRI;MagickCore-7.Q16;MagickCore-7;/usr/lib/imagemagick7/pkgconfig/MagickCore-7.Q16HDRI.pc;/usr/lib/imagemagick7/pkgconfig/MagickCore-7.Q16.pc;/usr/lib/imagemagick7/pkgconfig/MagickCore-7.pc\"\n    \"ImageMagick7\"\n)\nff_lib_enable(IMAGEMAGICK6\n    \"MagickCore-6.Q16HDRI;MagickCore-6.Q16;MagickCore-6;/usr/lib/imagemagick6/pkgconfig/MagickCore-6.Q16HDRI.pc;/usr/lib/imagemagick6/pkgconfig/MagickCore-6.Q16.pc;/usr/lib/imagemagick6/pkgconfig/MagickCore-6.pc\"\n    \"ImageMagick6\"\n)\nff_lib_enable(ZLIB\n    \"zlib\"\n    \"ZLIB\"\n)\nff_lib_enable(CHAFA\n    \"chafa>=1.10\"\n    \"Chafa\"\n)\nff_lib_enable(EGL\n    \"egl\"\n    \"EGL\"\n)\nif(NOT OpenBSD AND NOT NetBSD)\n    ff_lib_enable(GLX\n        \"glx\"\n        \"GLX\"\n    )\nelse()\n    ff_lib_enable(GLX\n        \"gl\"\n        \"GL\"\n    )\nendif()\nff_lib_enable(OPENCL\n    \"OpenCL\"\n    \"OpenCL\"\n)\nff_lib_enable(FREETYPE\n    \"freetype2\"\n    \"FreeType2\"\n)\nff_lib_enable(PULSE\n    \"libpulse\"\n    \"Pulse\"\n)\nff_lib_enable(DDCUTIL\n    \"ddcutil\"\n    \"Ddcutil\"\n)\nff_lib_enable(ELF\n    \"libelf\"\n    \"libelf\"\n)\nff_lib_enable(DIRECTX_HEADERS\n    \"DirectX-Headers\"\n    \"DirectX-Headers\"\n)\n\nif(ENABLE_THREADS)\n    target_compile_definitions(libfastfetch PRIVATE FF_HAVE_THREADS=1)\n    if(CMAKE_USE_PTHREADS_INIT) #Threads::Threads is not set for WIN32\n        target_link_libraries(libfastfetch PRIVATE Threads::Threads)\n    endif()\nendif()\n\nif(ENABLE_EMBEDDED_PCIIDS)\n    target_compile_definitions(libfastfetch PRIVATE FF_HAVE_EMBEDDED_PCIIDS=1)\nendif()\nif(ENABLE_EMBEDDED_AMDGPUIDS)\n    target_compile_definitions(libfastfetch PRIVATE FF_HAVE_EMBEDDED_AMDGPUIDS=1)\nendif()\nif(ENABLE_LIBZFS)\n    target_compile_definitions(libfastfetch PRIVATE FF_HAVE_LIBZFS=1)\n\n    if(NOT BINARY_LINK_TYPE STREQUAL \"dlopen\")\n        target_link_libraries(libfastfetch\n            PRIVATE \"zfs\"\n        )\n    endif()\nendif()\nif(NOT HAVE_MEMRCHR)\n    target_compile_definitions(libfastfetch PRIVATE FF_HAVE_CUSTOM_MEMRCHR=1)\nendif()\n\nif(LINUX)\n    target_link_libraries(libfastfetch\n        PRIVATE \"m\"\n    )\n\n    if(ENABLE_DIRECTX_HEADERS)\n        if(NOT BINARY_LINK_TYPE STREQUAL \"dlopen\")\n            target_link_libraries(libfastfetch\n                PRIVATE \"/usr/lib/wsl/lib/libdxcore.so\"\n            )\n        endif()\n    endif()\nelseif(APPLE)\n    target_link_libraries(libfastfetch\n        PRIVATE \"-framework AVFoundation\"\n        PRIVATE \"-framework Cocoa\"\n        PRIVATE \"-framework CoreFoundation\"\n        PRIVATE \"-framework CoreAudio\"\n        PRIVATE \"-framework CoreMedia\"\n        PRIVATE \"-framework CoreVideo\"\n        PRIVATE \"-framework CoreWLAN\"\n        PRIVATE \"-framework IOBluetooth\"\n        PRIVATE \"-framework IOKit\"\n        PRIVATE \"-framework Metal\"\n        PRIVATE \"-framework OpenGL\"\n        PRIVATE \"-framework OpenCL\"\n        PRIVATE \"-framework SystemConfiguration\"\n        PRIVATE \"-weak_framework CoreDisplay\"\n\n        PRIVATE \"-F /System/Library/PrivateFrameworks\"\n        PRIVATE \"-weak_framework DisplayServices\"\n        PRIVATE \"-weak_framework MediaRemote\"\n    )\nelseif(WIN32)\n    target_link_libraries(libfastfetch\n        PRIVATE \"gdi32\"\n        PRIVATE \"iphlpapi\"\n        PRIVATE \"ole32\"\n        PRIVATE \"oleaut32\"\n        PRIVATE \"ws2_32\"\n        PRIVATE \"ntdll\"\n        PRIVATE \"version\"\n        PRIVATE \"hid\"\n        PRIVATE \"wtsapi32\"\n        PRIVATE \"cfgmgr32\"\n        PRIVATE \"winbrand\"\n        PRIVATE \"secur32\"\n    )\n    if(NOT ENABLE_WIN81_COMPAT)\n        target_link_libraries(libfastfetch\n            PRIVATE \"mincore\"\n        )\n    endif()\nelseif(FreeBSD)\n    target_link_libraries(libfastfetch\n        PRIVATE \"m\"\n        PRIVATE \"usbhid\"\n        PRIVATE \"bluetooth\"\n    )\n    if(NOT DragonFly)\n        target_link_libraries(libfastfetch\n            PRIVATE \"geom\"\n        )\n    else()\n        target_link_libraries(libfastfetch\n            PRIVATE \"devstat\"\n        )\n    endif()\nelseif(OpenBSD)\n    target_link_libraries(libfastfetch\n        PRIVATE \"m\"\n        PRIVATE \"kvm\"\n        PRIVATE \"sndio\"\n    )\nelseif(NetBSD)\n    target_link_libraries(libfastfetch\n        PRIVATE \"bluetooth\"\n        PRIVATE \"m\"\n        PRIVATE \"prop\"\n    )\nelseif(SunOS)\n    target_link_libraries(libfastfetch\n        PRIVATE \"m\"\n        PRIVATE \"socket\"\n        PRIVATE \"kstat\"\n        PRIVATE \"proc\"\n        PRIVATE \"devinfo\"\n    )\nelseif(GNU)\n    target_link_libraries(libfastfetch\n        PRIVATE \"m\"\n    )\nelseif(ANDROID)\n    target_link_libraries(libfastfetch\n        PRIVATE \"m\"\n    )\n    if(ENABLE_WORDEXP)\n        # https://github.com/termux/termux-packages/pull/7056\n        CHECK_LIBRARY_EXISTS(-l:libandroid-wordexp.a wordexp \"\" HAVE_LIBANDROID_WORDEXP_STATIC)\n        if(HAVE_LIBANDROID_WORDEXP_STATIC)\n            target_link_libraries(libfastfetch\n                PRIVATE -l:libandroid-wordexp.a\n            )\n        else()\n            CHECK_LIBRARY_EXISTS(android-wordexp wordexp \"\" HAVE_LIBANDROID_WORDEXP)\n            if(HAVE_LIBANDROID_WORDEXP)\n                target_link_libraries(libfastfetch\n                    PRIVATE android-wordexp\n                )\n            endif()\n        endif()\n    endif()\nelseif(Haiku)\n    target_link_libraries(libfastfetch\n        PRIVATE \"network\"\n        PRIVATE \"bnetapi\"\n        PRIVATE \"media\"\n        PRIVATE \"device\"\n        PRIVATE \"bluetooth\"\n        PRIVATE \"GL\"\n        PRIVATE \"be\"\n        PRIVATE \"gnu\"\n    )\nendif()\n\ntarget_include_directories(libfastfetch\n    PUBLIC ${PROJECT_BINARY_DIR}\n    PUBLIC ${PROJECT_SOURCE_DIR}/src\n)\n\ntarget_link_libraries(libfastfetch\n    PRIVATE ${CMAKE_DL_LIBS}\n)\n\ntarget_compile_options(libfastfetch PRIVATE\n    $<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions -fno-rtti>)\n\nif(WIN32)\n    set(CMAKE_CXX_STANDARD 20)\n    include(CheckIncludeFileCXX)\n    CHECK_INCLUDE_FILE_CXX(\"winrt/Windows.Foundation.h\" HAVE_WINRT)\n    if(HAVE_WINRT)\n        add_library(ffwinrt MODULE src/detection/media/media_windows.dll.cpp)\n        target_link_libraries(ffwinrt PRIVATE \"RuntimeObject\")\n        target_compile_definitions(ffwinrt PRIVATE WIN32_LEAN_AND_MEAN=1 WINRT_LEAN_AND_MEAN=1)\n        target_link_options(ffwinrt\n            PRIVATE \"-static\" # stdc++, winpthread, gcc_s, etc.\n        )\n    endif()\n    set(CMAKE_CXX_STANDARD 17)\nendif()\nif(FreeBSD)\n    set(CMAKE_REQUIRED_INCLUDES \"/usr/local/include\" \"/usr/include\")\nendif()\nif(LINUX OR FreeBSD OR OpenBSD OR NetBSD)\n    CHECK_INCLUDE_FILE(\"linux/videodev2.h\" HAVE_LINUX_VIDEODEV2)\n    if(HAVE_LINUX_VIDEODEV2)\n        target_compile_definitions(libfastfetch PRIVATE FF_HAVE_LINUX_VIDEODEV2=1)\n    endif()\nendif()\nif(LINUX)\n    CHECK_INCLUDE_FILE(\"linux/wireless.h\" HAVE_LINUX_WIRELESS)\n    if(HAVE_LINUX_WIRELESS)\n        target_compile_definitions(libfastfetch PRIVATE FF_HAVE_LINUX_WIRELESS=1)\n    endif()\nendif()\nif(NOT WIN32)\n    CHECK_INCLUDE_FILE(\"utmpx.h\" HAVE_UTMPX)\n    if(HAVE_UTMPX)\n        target_compile_definitions(libfastfetch PRIVATE FF_HAVE_UTMPX=1)\n    endif()\n    if(ENABLE_WORDEXP)\n        CHECK_INCLUDE_FILE(\"wordexp.h\" HAVE_WORDEXP)\n        if(HAVE_WORDEXP)\n            target_compile_definitions(libfastfetch PRIVATE FF_HAVE_WORDEXP=1)\n            message(STATUS \"wordexp.h found, wordexp support enabled\")\n        else()\n            set(ENABLE_WORDEXP OFF)\n        endif()\n    endif()\n    if(NOT ENABLE_WORDEXP)\n        message(STATUS \"wordexp.h not found or disabled, glob used instead\")\n    endif()\n    if(ENABLE_THREADS AND CMAKE_USE_PTHREADS_INIT)\n        CHECK_INCLUDE_FILE(\"pthread_np.h\" HAVE_PTHREAD_NP)\n        if(HAVE_PTHREAD_NP)\n            target_compile_definitions(libfastfetch PRIVATE FF_HAVE_PTHREAD_NP=1)\n            set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES} pthread_np.h)\n        endif()\n        set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} Threads::Threads)\n        check_function_exists(\"pthread_timedjoin_np\" HAVE_TIMEDJOIN_NP)\n        if(HAVE_TIMEDJOIN_NP)\n            target_compile_definitions(libfastfetch PRIVATE FF_HAVE_TIMEDJOIN_NP=1)\n        else()\n            message(WARNING \"pthread_timedjoin_np was not found; networking timeout will not work\")\n        endif()\n    endif()\nendif()\n\nset(PACKAGES_DISABLE_LIST \"\")\nforeach(package_manager ${PACKAGE_MANAGERS})\n    if(PACKAGES_DISABLE_${package_manager})\n        list(APPEND PACKAGES_DISABLE_LIST \"${package_manager}\")\n    endif()\nendforeach()\nif(\"${PACKAGES_DISABLE_LIST}\" STREQUAL \"\")\n    set(PACKAGES_DISABLE_LIST \"FF_PACKAGES_FLAG_NONE\")\nelse()\n    message(STATUS \"Disabled package managers: ${PACKAGES_DISABLE_LIST}\")\n    list(TRANSFORM PACKAGES_DISABLE_LIST PREPEND \"FF_PACKAGES_FLAG_\")\n    list(TRANSFORM PACKAGES_DISABLE_LIST APPEND \"_BIT\")\n    list(JOIN PACKAGES_DISABLE_LIST \" | \" PACKAGES_DISABLE_LIST)\nendif()\ntarget_compile_definitions(libfastfetch PRIVATE FF_PACKAGES_DISABLE_LIST=${PACKAGES_DISABLE_LIST})\n\n######################\n# Executable targets #\n######################\n\nadd_executable(fastfetch\n    src/fastfetch.c\n)\ntarget_compile_definitions(fastfetch\n    PRIVATE FASTFETCH_TARGET_BINARY_NAME=fastfetch\n)\ntarget_link_libraries(fastfetch\n    PRIVATE libfastfetch\n)\n\n# Prevent fastfetch from linking to libstdc++\nset(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES \"\")\nset(CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES \"\")\nset_target_properties(fastfetch PROPERTIES LINKER_LANGUAGE C)\n\nif(WIN32)\n    target_sources(fastfetch\n        PRIVATE src/common/windows/version.rc\n    )\nelseif(APPLE)\n    target_link_options(fastfetch\n        PRIVATE LINKER:-sectcreate,__TEXT,__info_plist,Info.plist\n    )\nendif()\n\nif(BINARY_LINK_TYPE STREQUAL \"static\")\n    target_link_options(fastfetch PRIVATE \"-static\")\nendif()\n\n# Apply all above parameters to flashfetch if it is built\nif (BUILD_FLASHFETCH)\n    add_executable(flashfetch\n        src/flashfetch.c\n    )\n    target_compile_definitions(flashfetch\n        PRIVATE FASTFETCH_TARGET_BINARY_NAME=flashfetch\n    )\n    target_link_libraries(flashfetch\n        PRIVATE libfastfetch\n    )\n\n    set_target_properties(flashfetch PROPERTIES LINKER_LANGUAGE C)\n\n    if(WIN32)\n        target_sources(flashfetch\n            PRIVATE src/common/windows/version.rc\n        )\n    elseif(APPLE)\n        target_link_options(flashfetch\n            PRIVATE LINKER:-sectcreate,__TEXT,__info_plist,Info.plist\n        )\n    endif()\n\n    if(BINARY_LINK_TYPE STREQUAL \"static\")\n        target_link_options(flashfetch PRIVATE \"-static\")\n    endif()\nendif()\n\n###################\n# Testing targets #\n###################\n\nif (BUILD_TESTS)\n    add_executable(fastfetch-test-strbuf\n        tests/strbuf.c\n    )\n    target_link_libraries(fastfetch-test-strbuf\n        PRIVATE libfastfetch\n    )\n\n    add_executable(fastfetch-test-list\n        tests/list.c\n    )\n    target_link_libraries(fastfetch-test-list\n        PRIVATE libfastfetch\n    )\n\n    add_executable(fastfetch-test-format\n        tests/format.c\n    )\n    target_link_libraries(fastfetch-test-format\n        PRIVATE libfastfetch\n    )\n\n    add_executable(fastfetch-test-color\n        tests/color.c\n    )\n    target_link_libraries(fastfetch-test-color\n        PRIVATE libfastfetch\n    )\n\n    add_executable(fastfetch-test-duration\n        tests/duration.c\n    )\n    target_link_libraries(fastfetch-test-duration\n        PRIVATE libfastfetch\n    )\n\n    enable_testing()\n    add_test(NAME test-strbuf COMMAND fastfetch-test-strbuf)\n    add_test(NAME test-list COMMAND fastfetch-test-list)\n    add_test(NAME test-format COMMAND fastfetch-test-format)\n    add_test(NAME test-color COMMAND fastfetch-test-color)\n    add_test(NAME test-duration COMMAND fastfetch-test-duration)\nendif()\n\n##################\n# install target #\n##################\n\ninclude(GNUInstallDirs)\n\ninstall(\n    TARGETS fastfetch\n    DESTINATION \"${CMAKE_INSTALL_BINDIR}\"\n)\n\nif (TARGET flashfetch)\n    install(\n        TARGETS flashfetch\n        DESTINATION \"${CMAKE_INSTALL_BINDIR}\"\n    )\nendif()\n\nif (TARGET ffwinrt)\n    install(\n        TARGETS ffwinrt\n        DESTINATION \"${CMAKE_INSTALL_BINDIR}\"\n    )\nendif()\n\ninstall(\n    FILES \"${CMAKE_SOURCE_DIR}/completions/${CMAKE_PROJECT_NAME}.bash\"\n    DESTINATION \"${CMAKE_INSTALL_DATAROOTDIR}/bash-completion/completions\"\n    RENAME \"${CMAKE_PROJECT_NAME}\"\n)\n\ninstall(\n    FILES \"${CMAKE_SOURCE_DIR}/completions/${CMAKE_PROJECT_NAME}.zsh\"\n    DESTINATION \"${CMAKE_INSTALL_DATAROOTDIR}/zsh/site-functions\"\n    RENAME \"_${CMAKE_PROJECT_NAME}\"\n)\n\ninstall(\n    FILES \"${CMAKE_SOURCE_DIR}/completions/${CMAKE_PROJECT_NAME}.fish\"\n    DESTINATION \"${CMAKE_INSTALL_DATAROOTDIR}/fish/vendor_completions.d\"\n    RENAME \"${CMAKE_PROJECT_NAME}.fish\"\n)\n\ninstall(\n    DIRECTORY \"${CMAKE_SOURCE_DIR}/presets\"\n    DESTINATION \"${CMAKE_INSTALL_DATAROOTDIR}/${CMAKE_PROJECT_NAME}\"\n)\n\nif(INSTALL_LICENSE)\n    install(\n        FILES \"${CMAKE_SOURCE_DIR}/LICENSE\"\n        DESTINATION \"${CMAKE_INSTALL_DATAROOTDIR}/licenses/${CMAKE_PROJECT_NAME}\"\n    )\nendif()\n\ninstall(\n    FILES \"${PROJECT_BINARY_DIR}/fastfetch.1\"\n    DESTINATION \"${CMAKE_INSTALL_MANDIR}/man1\"\n)\n\n##################\n# package target #\n##################\n\nset(CPACK_GENERATOR \"TGZ;ZIP\")\nif(APPLE)\n    string(TOLOWER \"${CMAKE_PROJECT_NAME}-macos-${CMAKE_SYSTEM_PROCESSOR}\" CPACK_PACKAGE_FILE_NAME) # use macos instead of darwin\nelseif(IS_MUSL)\n    string(TOLOWER \"${CMAKE_PROJECT_NAME}-musl-${CMAKE_SYSTEM_PROCESSOR}\" CPACK_PACKAGE_FILE_NAME)\nelse()\n    string(TOLOWER \"${CMAKE_PROJECT_NAME}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}\" CPACK_PACKAGE_FILE_NAME)\nendif()\n\nif(LINUX)\n    find_program(HAVE_DPKG \"dpkg\")\n    if(HAVE_DPKG)\n        set(CPACK_GENERATOR \"${CPACK_GENERATOR};DEB\")\n\n        set(CPACK_DEBIAN_PACKAGE_SECTION, \"utils\")\n        set(CPACK_DEBIAN_PACKAGE_PRIORITY \"optional\")\n\n        if(NOT IS_MUSL)\n            EXECUTE_PROCESS(\n                COMMAND getconf GNU_LIBC_VERSION\n                OUTPUT_VARIABLE GLIBC_VERSION\n                OUTPUT_STRIP_TRAILING_WHITESPACE)\n            if(GLIBC_VERSION)\n                STRING(REPLACE \"glibc \" \"\" GLIBC_VERSION ${GLIBC_VERSION})\n                set(CPACK_DEBIAN_PACKAGE_DEPENDS \"libc6 (>= ${GLIBC_VERSION})\")\n                message(STATUS \"found glibc ${GLIBC_VERSION}\")\n            else()\n                message(WARNING \"Could not determine glibc version. If `musl` is used, `-DIS_MUSL=ON` should be set\")\n            endif()\n        endif()\n    endif()\n\n    if(NOT IS_MUSL)\n        find_program(HAVE_RPMBUILD \"rpmbuild\")\n        if(HAVE_RPMBUILD)\n            set(CPACK_GENERATOR \"${CPACK_GENERATOR};RPM\")\n\n            set(CPACK_RPM_PACKAGE_LICENSE \"MIT\")\n        endif()\n    endif()\nelseif(FreeBSD AND NOT DragonFly)\n    set(CPACK_FREEBSD_PACKAGE_LICENSE \"MIT\")\n    set(CPACK_GENERATOR \"${CPACK_GENERATOR};FREEBSD\")\nendif()\n\nset(CPACK_SET_DESTDIR ON)\n\nset(CPACK_PACKAGE_CONTACT \"Carter Li <zhangsongcui@live.cn>\")\nset(CPACK_PACKAGE_DESCRIPTION \"\\\nfastfetch is a neofetch-like tool for fetching system information and displaying them in a pretty way. \\\nIt is written mostly in C to achieve much better performance.\\\n\")\n\ninclude(CPack)\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "\n# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, caste, color, religion, or sexual\nidentity and orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the overall\n  community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or advances of\n  any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email address,\n  without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official email address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\n[INSERT CONTACT METHOD].\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series of\nactions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or permanent\nban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior, harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within the\ncommunity.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.1, available at\n[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].\n\nCommunity Impact Guidelines were inspired by\n[Mozilla's code of conduct enforcement ladder][Mozilla CoC].\n\nFor answers to common questions about this code of conduct, see the FAQ at\n[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at\n[https://www.contributor-covenant.org/translations][translations].\n\n[homepage]: https://www.contributor-covenant.org\n[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html\n[Mozilla CoC]: https://github.com/mozilla/diversity\n[FAQ]: https://www.contributor-covenant.org/faq\n[translations]: https://www.contributor-covenant.org/translations\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021-2023 Linus Dierheimer\nCopyright (c) 2022-2026 Carter Li\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README-cn.md",
    "content": "# Fastfetch\n\nFastfetch 是一款类似 neofetch 的系统信息展示工具，主要用 C 编写，强调性能和可定制性。支持 Linux、macOS、Windows 7+、Android、FreeBSD、OpenBSD、NetBSD、DragonFly、Haiku、SunOS。\n\n示例配置见 presets/examples，更多截图与平台说明见 Wiki。\n\n## 安装\n\nLinux（部分）：\n- Debian 13+ / Ubuntu: apt install fastfetch\n- Arch: pacman -S fastfetch\n- Fedora: dnf install fastfetch\n- openSUSE: zypper install fastfetch\n- Linuxbrew：brew install fastfetch\n- 各发行版打包状态：https://repology.org/project/fastfetch/versions\n\nmacOS：\n- Homebrew：brew install fastfetch\n- MacPorts：sudo port install fastfetch\n\nWindows：\n- scoop install fastfetch\n- choco install fastfetch\n- winget install fastfetch\n- MSYS2：pacman -S mingw-w64-<subsystem>-<arch>-fastfetch\n\nBSD：\n- FreeBSD：pkg install fastfetch\n- NetBSD：pkgin in fastfetch\n- OpenBSD：pkg_add fastfetch\n\nAndroid（Termux）：\n- pkg install fastfetch\n\nNightly 构建：\n- https://nightly.link/fastfetch-cli/fastfetch/workflows/ci/dev?preview\n\n## 源码构建\n\n基本上是 `cmake . && make`。详见 Wiki：https://github.com/fastfetch-cli/fastfetch/wiki/Building\n\n## 使用\n\n- 默认运行：`fastfetch`\n- 查看所有可用模块示例：`fastfetch -c all.jsonc`\n- 以 JSON 输出指定模块：`fastfetch -s <module1>[:<module2>] --format json`\n- 完整命令行帮助：`fastfetch --help`\n- 生成最小配置：`fastfetch --gen-config [</path/to/config.jsonc>]`\n  - 生成完整配置：`fastfetch --gen-config-full`\n  - 请使用支持 JSON schema 的编辑器（如 VSCode）编辑配置文件！\n  - 如果你连接 Github 有网络困难（智能提示不生效），可将配置文件中的 `$schema` 的值替换为 `https://gitee.com/carterl/fastfetch/raw/dev/doc/json_schema.json`\n\n## 定制\n\n- 配置使用 JSONC，语法与选项见 Wiki：https://github.com/fastfetch-cli/fastfetch/wiki/Configuration\n- 预设示例位于 presets，可用 `-c <filename>` 加载\n- Logo 选项与图像显示见文档：https://github.com/fastfetch-cli/fastfetch/wiki/Logo-options\n- 模块格式化（示例，仅显示 GPU 名称）：\n```jsonc\n{\n  \"modules\": [\n    { \"type\": \"gpu\", \"format\": \"{name}\" }\n  ]\n}\n```\n详见：https://github.com/fastfetch-cli/fastfetch/wiki/Format-String-Guide\n\n## 反馈与支持\n\n- 使用问题：Discussions https://github.com/fastfetch-cli/fastfetch/discussions\n- 疑似缺陷：Issues https://github.com/fastfetch-cli/fastfetch/issues（请填写模版）\n\n## 赞助\n\n<img src=\"https://github.com/user-attachments/assets/a36a6501-e8b0-4a10-9061-b9206d12ffba\" width=\"220\">\n"
  },
  {
    "path": "README.md",
    "content": "# Fastfetch\n\n[![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/fastfetch-cli/fastfetch/ci.yml)](https://github.com/fastfetch-cli/fastfetch/actions)\n[![GitHub license](https://img.shields.io/github/license/fastfetch-cli/fastfetch)](https://github.com/fastfetch-cli/fastfetch/blob/dev/LICENSE)\n[![GitHub contributors](https://img.shields.io/github/contributors/fastfetch-cli/fastfetch)](https://github.com/fastfetch-cli/fastfetch/graphs/contributors)\n[![GitHub top language](https://img.shields.io/github/languages/top/fastfetch-cli/fastfetch?logo=c&label=)](https://github.com/fastfetch-cli/fastfetch/blob/dev/CMakeLists.txt#L5)\n[![GitHub commit activity (branch)](https://img.shields.io/github/commit-activity/m/fastfetch-cli/fastfetch)](https://github.com/fastfetch-cli/fastfetch/commits)  \n[![homebrew downloads](https://img.shields.io/homebrew/installs/dm/fastfetch?logo=homebrew)](https://formulae.brew.sh/formula/fastfetch#default)\n[![GitHub all releases](https://img.shields.io/github/downloads/fastfetch-cli/fastfetch/total?logo=github)](https://github.com/fastfetch-cli/fastfetch/releases)  \n[![GitHub release (with filter)](https://img.shields.io/github/v/release/fastfetch-cli/fastfetch?logo=github)](https://github.com/fastfetch-cli/fastfetch/releases)\n[![latest packaged version(s)](https://repology.org/badge/latest-versions/fastfetch.svg)](https://repology.org/project/fastfetch/versions)\n[![Packaging status](https://repology.org/badge/tiny-repos/fastfetch.svg)](https://repology.org/project/fastfetch/versions)\n[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/fastfetch-cli/fastfetch)\n[![中文README](https://img.shields.io/badge/%E4%B8%AD%E6%96%87-README-red)](README-cn.md)\n\nFastfetch is a [neofetch](https://github.com/dylanaraps/neofetch)-like tool for fetching system information and displaying it in a visually appealing way. It is written mainly in C, with a focus on performance and customizability. Currently, it supports Linux, macOS, Windows 8.1+, Android, FreeBSD, OpenBSD, NetBSD, DragonFly, Haiku and SunOS (illumos, Solaris).\n\n<img src=\"screenshots/example1.png\" width=\"49%\" align=\"left\" />\n<img src=\"https://upload.wikimedia.org/wikipedia/commons/2/24/Transparent_Square_Tiles_Texture.png\" width=\"49%\" height=\"16px\" align=\"left\" />\n<img src=\"screenshots/example4.png\" width=\"49%\" align=\"left\" />\n<img src=\"https://upload.wikimedia.org/wikipedia/commons/2/24/Transparent_Square_Tiles_Texture.png\" width=\"49%\" height=\"16px\" align=\"left\" />\n<img src=\"screenshots/example2.png\" width=\"48%\" align=\"top\" />\n<img src=\"screenshots/example3.png\" width=\"48%\" align=\"top\" />\n<img src=\"screenshots/example5.png\" height=\"15%\" align=\"top\" />\n\nAccording configuration files for examples are located [here](https://github.com/fastfetch-cli/fastfetch/tree/dev/presets/examples).\n\nThere are [screenshots on different platforms](https://github.com/fastfetch-cli/fastfetch/wiki).\n\n## Installation\n\n### Linux\n\nSome distributions package outdated versions of fastfetch. Older versions receive no support, so please always try to use the latest version.\n\n<a href=\"https://repology.org/project/fastfetch/versions\">\n    <img src=\"https://repology.org/badge/vertical-allrepos/fastfetch.svg?columns=2\" alt=\"Packaging status\" align=\"right\">\n</a>\n\n* Ubuntu: [`ppa:zhangsongcui3371/fastfetch`](https://launchpad.net/~zhangsongcui3371/+archive/ubuntu/fastfetch) (Ubuntu 22.04 or newer; latest version)\n* Debian / Ubuntu: `apt install fastfetch` (Debian 13 or newer; Ubuntu 25.04 or newer)\n* Debian / Ubuntu: Download `fastfetch-linux-<proper architecture>.deb` from [Github release page](https://github.com/fastfetch-cli/fastfetch/releases/latest) and double-click it (for Ubuntu 20.04 or newer and Debian 11 or newer).\n* Arch Linux: `pacman -S fastfetch`\n* Fedora: `dnf install fastfetch`\n* Gentoo: `emerge --ask app-misc/fastfetch`\n* Alpine: `apk add --upgrade fastfetch`\n* NixOS: `nix-shell -p fastfetch`\n* openSUSE: `zypper install fastfetch`\n* ALT Linux: `apt-get install fastfetch`\n* Exherbo: `cave resolve --execute app-misc/fastfetch`\n* Solus: `eopkg install fastfetch`\n* Slackware: `sbopkg -i fastfetch`\n* Void Linux: `xbps-install fastfetch`\n* Venom Linux: `scratch install fastfetch`\n\nYou may need `sudo`, `doas`, or `sup` to run these commands.\n\nIf fastfetch is not packaged for your distribution or an outdated version is packaged, [linuxbrew](https://brew.sh/) is a good alternative: `brew install fastfetch`\n\n### macOS\n\n* [Homebrew](https://formulae.brew.sh/formula/fastfetch#default): `brew install fastfetch`\n* [MacPorts](https://ports.macports.org/port/fastfetch/): `sudo port install fastfetch`\n\n### Windows\n\n* [scoop](https://scoop.sh/#/apps?q=fastfetch): `scoop install fastfetch`\n* [Chocolatey](https://community.chocolatey.org/packages/fastfetch): `choco install fastfetch`\n* [winget](https://github.com/microsoft/winget-pkgs/tree/master/manifests/f/Fastfetch-cli/Fastfetch): `winget install fastfetch`\n* [MSYS2 MinGW](https://packages.msys2.org/base/mingw-w64-fastfetch): `pacman -S mingw-w64-<subsystem>-<arch>-fastfetch`\n\nYou may also download the program directly from [the GitHub releases page](https://github.com/fastfetch-cli/fastfetch/releases/latest) in the form of an archive file.\n\n### BSD systems\n\n* FreeBSD: `pkg install fastfetch`\n* NetBSD: `pkgin in fastfetch`\n* OpenBSD: `pkg_add fastfetch` (Snapshots only)\n* DragonFly BSD: `pkg install fastfetch` (Snapshots only)\n\n### Android (Termux)\n\n* `pkg install fastfetch`\n\n### Nightly\n\n<https://nightly.link/fastfetch-cli/fastfetch/workflows/ci/dev?preview>\n\n## Build from source\n\nSee the Wiki: https://github.com/fastfetch-cli/fastfetch/wiki/Building\n\n## Usage\n\n* Run with default configuration: `fastfetch`\n* Run with [all supported modules](https://github.com/fastfetch-cli/fastfetch/wiki/Support+Status#available-modules) to find what interests you: `fastfetch -c all.jsonc`\n* View all data that fastfetch detects: `fastfetch -s <module1>[:<module2>][:<module3>] --format json`\n* Display help messages: `fastfetch --help`\n* Generate a minimal config file: `fastfetch [-s <module1>[:<module2>]] --gen-config [</path/to/config.jsonc>]`\n    * Use: `--gen-config-full` to generate a full config file with all optional options\n\n## Customization\n\nFastfetch uses JSONC (JSON with comments) for configuration. [See the Wiki for details](https://github.com/fastfetch-cli/fastfetch/wiki/Configuration). There are some premade config files in the [`presets`](presets) directory, including those used for the screenshots above. You can load them using `-c <filename>`. These files can serve as examples of the configuration syntax.\n\nLogos can also be heavily customized; see the [logo documentation](https://github.com/fastfetch-cli/fastfetch/wiki/Logo-options) for more information.\n\n### WARNING\n\nFastfetch supports a `Command` module that can run arbitrary shell commands. If you copy-paste a config file from an untrusted source, it may contain malicious commands that can harm your system or compromise your privacy. Please always review the config file before using it.\n\n## FAQ\n\n### Q: Neofetch is good enough. Why do I need fastfetch?\n\n1. Fastfetch is actively maintained.\n2. Fastfetch is faster, as the name suggests.\n3. Fastfetch has a greater number of features, though by default it only has a few modules enabled; use `fastfetch -c all` to discover what you want.\n4. Fastfetch is more configurable. You can find more information in the Wiki: <https://github.com/fastfetch-cli/fastfetch/wiki/Configuration>.\n5. Fastfetch is more polished. For example, neofetch prints `555 MiB` in the Memory module and `23 G` in the Disk module, whereas fastfetch prints `555.00 MiB` and `22.97 GiB` respectively.\n6. Fastfetch is more accurate. For example, [neofetch never actually supports the Wayland protocol](https://github.com/dylanaraps/neofetch/pull/2395).\n\n### Q: Fastfetch shows my local IP address. Does it leak my privacy?\n\nA local IP address (10.x.x.x, 172.x.x.x, 192.168.x.x) has nothing to do with privacy. It only has meaning if you are on the same network, for example, if you connect to the same Wi-Fi network.\n\nActually, the `Local IP` module is the most useful module for me personally. I (@CarterLi) have several VMs installed to test fastfetch and often need to SSH into them. With fastfetch running on shell startup, I never need to type `ip addr` manually.\n\nIf you really don't like it, you can disable the `Local IP` module in `config.jsonc`.\n\n### Q: Where is the config file? I can't find it.\n\nFastfetch does not generate a config file automatically. You can use `fastfetch --gen-config` to generate one. The config file will be saved in `~/.config/fastfetch/config.jsonc` by default. See the [Wiki for details](https://github.com/fastfetch-cli/fastfetch/wiki/Configuration).\n\n### Q: The configuration is so complex. Where is the documentation?\n\nFastfetch uses JSON (with comments) for configuration. I suggest using an IDE with JSON schema support (like VSCode) to edit it.\n\nAlternatively, you can refer to the presets in the [`presets` directory](https://github.com/fastfetch-cli/fastfetch/tree/dev/presets).\n\nThe **correct** way to edit the configuration:\n\nThis is an example that [changes size prefix from MiB / GiB to MB / GB](https://github.com/fastfetch-cli/fastfetch/discussions/1014). Editor used: [helix](https://github.com/helix-editor/helix)\n\n[![asciicast](https://asciinema.org/a/1uF6sTPGKrHKI1MVaFcikINSQ.svg)](https://asciinema.org/a/1uF6sTPGKrHKI1MVaFcikINSQ)\n\n### Q: I WANT THE DOCUMENTATION!\n\n[Here is the documentation](https://github.com/fastfetch-cli/fastfetch/wiki/Json-Schema). It is generated from the [JSON schema](https://github.com/fastfetch-cli/fastfetch/blob/dev/doc/json_schema.json), but you might not find it very user-friendly.\n\n### Q: How can I customize the module output?\n\nFastfetch uses `format` to generate output. For example, to make the `GPU` module show only the GPU name (leaving other information undisplayed), you can use:\n\n```jsonc\n{\n    \"modules\": [\n        {\n            \"type\": \"gpu\",\n            \"format\": \"{name}\" // See `fastfetch -h gpu-format` for details\n        }\n    ]\n}\n```\n\n...which is equivalent to `fastfetch -s gpu --gpu-format '{name}'`\n\nSee `fastfetch -h format` for information on basic usage. For module-specific formatting, see `fastfetch -h <module>-format`\n\n### Q: I have my own ASCII art / image file. How can I show it with fastfetch?\n\nTry `fastfetch -l /path/to/logo`. See the [logo documentation](https://github.com/fastfetch-cli/fastfetch/wiki/Logo-options) for details.\n\nIf you just want to display the distro name in [FIGlet text](https://github.com/pwaller/pyfiglet):\n\n```bash\n# install pyfiglet and jq first\npyfiglet -s -f small_slant $(fastfetch -s os --format json | jq -r '.[0].result.name') && fastfetch -l none\n```\n\n![image](https://github.com/fastfetch-cli/fastfetch/assets/6134068/6466524e-ab8c-484f-848d-eec7ddeb7df2)\n\n### Q: My image logo behaves strangely. How can I fix it?\n\nSee the troubleshooting section: <https://github.com/fastfetch-cli/fastfetch/wiki/Logo-options#troubleshooting>\n\n### Q: Fastfetch runs in black and white on shell startup. Why?\n\nThis issue usually occurs when using fastfetch with `p10k`. There are known incompatibilities between fastfetch and p10k instant prompt.\nThe p10k documentation clearly states that you should NOT print anything to stdout after `p10k-instant-prompt` is initialized. You should put `fastfetch` before the initialization of `p10k-instant-prompt` (recommended).\n\nYou can always use `fastfetch --pipe false` to force fastfetch to run in colorful mode.\n\n### Q: Why do fastfetch and neofetch show different memory usage results?\n\nSee [#1096](https://github.com/fastfetch-cli/fastfetch/issues/1096).\n\n### Q: Fastfetch shows fewer dpkg packages than neofetch. Is it a bug?\n\nNeofetch incorrectly counts `rc` packages (packages that have been removed but still have configuration files remaining). See bug: https://github.com/dylanaraps/neofetch/issues/2278\n\n### Q: I use Debian / Ubuntu / Debian-derived distro. My GPU is detected as `XXXX Device XXXX (VGA compatible)`. Is this a bug?\n\nTry upgrading `pci.ids`: Download <https://pci-ids.ucw.cz/v2.2/pci.ids> and overwrite the file `/usr/share/hwdata/pci.ids`. For AMD GPUs, you should also upgrade `amdgpu.ids`: Download <https://gitlab.freedesktop.org/mesa/drm/-/raw/main/data/amdgpu.ids> and overwrite the file `/usr/share/libdrm/amdgpu.ids`\n\nAlternatively, you may try using `fastfetch --gpu-driver-specific`, which will make fastfetch attempt to ask the driver for the GPU name if supported.\n\n### Q: I get the error `Authorization required, but no authorization protocol specified` when running fastfetch as root\n\nTry `export XAUTHORITY=$HOME/.Xauthority`\n\n### Q: Fastfetch cannot detect my awesome 3rd-party macOS window manager!\n\nTry `fastfetch --wm-detect-plugin`. See also [#984](https://github.com/fastfetch-cli/fastfetch/issues/984)\n\n### Q: How can I change the colors of my ASCII logo?\n\nTry `fastfetch --logo-color-[1-9] <color>`, where `[1-9]` is the index of color placeholders.\n\nFor example: `fastfetch --logo-color-1 red --logo-color-2 green`.\n\nIn JSONC, you can use:\n\n```jsonc\n{\n    \"logo\": {\n        \"color\": {\n            \"1\": \"red\",\n            \"2\": \"green\"\n        }\n    }\n}\n```\n\n### Q: How do I hide a key?\n\nSet the key to a white space.\n\n```jsonc\n{\n    \"key\": \" \"\n}\n```\n\n### Q: How can I display images on Windows?\n\nAs of April 2025:\n\n#### mintty and Wezterm\n\nmintty (used by Bash on Windows and MSYS2) and Wezterm (nightly build only) support the iTerm image protocol on Windows.\n\nIn `config.jsonc`:  \n```json\n{\n  \"logo\": {\n    \"type\": \"iterm\",\n    \"source\": \"C:/path/to/image.png\",\n    \"width\": <num-in-chars>\n  }\n}\n```\n\n#### Windows Terminal\n\nWindows Terminal supports the sixel image protocol only.\n\n* If you installed fastfetch through MSYS2:\n    1. Install imagemagick: `pacman -S mingw-w64-<subsystem>-x86_64-imagemagick`\n    2. In `config.jsonc`:  \n```jsonc\n{\n  \"logo\": {\n    \"type\": \"sixel\", // DO NOT USE \"auto\"\n    \"source\": \"C:/path/to/image.png\", // Do NOT use `~` as fastfetch is a native Windows program and doesn't apply cygwin path conversion\n    \"width\": <image-width-in-chars>, // Optional\n    \"height\": <image-height-in-chars> // Optional\n  }\n}\n```\n* If you installed fastfetch via scoop or downloaded the binary directly from the GitHub Releases page:\n    1. Convert your image manually to sixel format using [any online image conversion service](https://www.google.com/search?q=convert+image+to+sixel)\n    2. In `config.jsonc`:  \n```jsonc\n{\n  \"logo\": {\n    \"type\": \"raw\", // DO NOT USE \"auto\"\n    \"source\": \"C:/path/to/image.sixel\",\n    \"width\": <image-width-in-chars>, // Required\n    \"height\": <image-height-in-chars> // Required\n  }\n}\n```\n\n### Q: I want feature A / B / C. Will fastfetch support it?\n\nFastfetch is a system information tool. We only accept hardware or system-level software feature requests. For most personal uses, I recommend using the `Command` module to implement custom functionality, which can be used to grab output from a custom shell script:\n\n```jsonc\n// This module shows the default editor\n{\n    \"modules\": [\n        {\n            \"type\": \"command\",\n            \"text\": \"$EDITOR --version | head -1\",\n            \"key\": \"Editor\"\n        }\n    ]\n}\n```\n\nOtherwise, please open a feature request in [GitHub Issues](https://github.com/fastfetch-cli/fastfetch/issues).\n\n### Q: I have questions. Where can I get help?\n\n* For usage questions, please start a discussion in [GitHub Discussions](https://github.com/fastfetch-cli/fastfetch/discussions).\n* For possible bugs, please open an issue in [GitHub Issues](https://github.com/fastfetch-cli/fastfetch/issues). Be sure to fill out the bug report template carefully to help developers investigate.\n\n## Donate\n\nIf you find Fastfetch useful, please consider donating.\n\n* Current maintainer: [@CarterLi](https://paypal.me/zhangsongcui)\n* Original author: [@LinusDierheimer](https://github.com/sponsors/LinusDierheimer)\n\n## Code signing\n\n* Free code signing provided by [SignPath.io](https://about.signpath.io/), certificate by [SignPath Foundation](https://signpath.org/)\n* This program will not transfer any information to other networked systems unless specifically requested by the user or the person installing or operating it\n\n## Star History\n\nGive us a star to show your support!\n\n<a href=\"https://star-history.com/#fastfetch-cli/fastfetch&Date\">\n  <picture>\n    <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://api.star-history.com/svg?repos=fastfetch-cli/fastfetch&type=Date&theme=dark\" />\n    <source media=\"(prefers-color-scheme: light)\" srcset=\"https://api.star-history.com/svg?repos=fastfetch-cli/fastfetch&type=Date\" />\n    <img alt=\"Star History Chart\" src=\"https://api.star-history.com/svg?repos=fastfetch-cli/fastfetch&type=Date\" />\n  </picture>\n</a>\n"
  },
  {
    "path": "completions/fastfetch.bash",
    "content": "#!/usr/bin/env bash\n\n_fastfetch() {\n  # Use Bash built-in variables directly\n  local cur=\"${COMP_WORDS[COMP_CWORD]}\"\n  local prev=\"${COMP_WORDS[COMP_CWORD-1]}\"\n\n  # Check if Python is available\n  if ! command -v python3 &>/dev/null; then\n    return\n  fi\n\n  # Handle standard completion cases\n  case \"$prev\" in\n    --color|--color-keys|--color-title|--color-output|--color-separator|--*-color|--*-key-color|--*-output-color|--logo-color-[1-9]|--percent-color-*|--temp-color-*)\n      local -a colors=(\"black\" \"red\" \"green\" \"yellow\" \"blue\" \"magenta\" \"cyan\" \"white\" \"default\")\n      COMPREPLY=($(compgen -W \"${colors[*]}\" -- \"$cur\"))\n      return\n      ;;\n    --logo|-l)\n      local -a logos\n      readarray -t logos < <(fastfetch --list-logos autocompletion 2>/dev/null)\n      logos+=(\"none\" \"small\")\n      COMPREPLY=($(compgen -W \"${logos[*]}\" -- \"$cur\"))\n      return\n      ;;\n    --config|-c)\n      local -a presets\n      readarray -t presets < <(fastfetch --list-presets autocompletion 2>/dev/null)\n      presets+=(\"none\")\n      COMPREPLY=($(compgen -W \"${presets[*]}\" -- \"$cur\"))\n      # Also allow file path completion\n      if type _filedir &>/dev/null; then\n        _filedir\n      elif type compgen &>/dev/null; then\n        COMPREPLY+=($(compgen -f -- \"$cur\"))\n      fi\n      return\n      ;;\n    --structure|-s)\n      # Get all module names in lowercase only\n      local -a structures\n      readarray -t structures < <(fastfetch --list-modules autocompletion 2>/dev/null | cut -d':' -f1 | tr '[:upper:]' '[:lower:]')\n      COMPREPLY=($(compgen -W \"${structures[*]}\" -- \"$cur\"))\n      return\n      ;;\n    --help|-h)\n      local -a modules\n      readarray -t modules < <(fastfetch --list-modules autocompletion 2>/dev/null)\n      # Convert to lowercase and keep only module names\n      local -a module_names=()\n      for module in \"${modules[@]}\"; do\n        module_names+=($(echo \"$module\" | cut -d':' -f1 | tr '[:upper:]' '[:lower:]')-format)\n      done\n      module_names+=(\"format\" \"color\")\n      COMPREPLY=($(compgen -W \"${module_names[*]}\" -- \"$cur\"))\n      return\n      ;;\n    --format)\n      COMPREPLY=($(compgen -W \"json default\" -- \"$cur\"))\n      return\n      ;;\n    --*-format)\n      # Format string completion, handle spaces\n      return\n      ;;\n    --*path*|--*file*|--gen-config*|--*data*)\n      # File path completion\n      if type _filedir &>/dev/null; then\n        _filedir\n      elif type compgen &>/dev/null; then\n        COMPREPLY=($(compgen -f -- \"$cur\"))\n      fi\n      return\n      ;;\n  esac\n\n  # If not a special option, generate all possible options\n  if [[ \"$cur\" == -* ]]; then\n    local -a opts\n    readarray -t opts < <(python3 - \"$cur\" <<'EOF'\nimport json\nimport sys\nimport subprocess\n\ndef main(current):\n    try:\n        # Use fastfetch --help-raw to get option data\n        output = subprocess.check_output(['fastfetch', '--help-raw'], stderr=subprocess.DEVNULL)\n        data = json.loads(output)\n\n        for category in data.values():\n            for flag in category:\n                if flag.get(\"pseudo\", False):\n                    continue\n\n                if \"short\" in flag:\n                    print(f\"-{flag['short']}\")\n\n                if \"long\" in flag:\n                    if flag[\"long\"] == \"logo-color-[1-9]\":\n                        for i in range(1, 10):\n                            print(f\"--logo-color-{i}\")\n                    else:\n                        print(f\"--{flag['long']}\")\n    except Exception:\n        # If error occurs, return no options\n        pass\n\nif __name__ == \"__main__\":\n    main(sys.argv[1])\nEOF\n)\n    COMPREPLY=($(compgen -W \"${opts[*]}\" -- \"$cur\"))\n  fi\n\n  return 0\n}\n\n# Register completion\ncomplete -F _fastfetch fastfetch\n"
  },
  {
    "path": "completions/fastfetch.fish",
    "content": "if not type -q fastfetch\n    exit\nend\n\ncommand -q python3\nif test $status -ne 0\n    exit\nend\n\ncomplete -c fastfetch -f\n\nfunction __fastfetch_complete_bool\n    echo -e \"true\\tBool\"\n    echo -e \"false\\tBool\"\nend\n\nfunction __fastfetch_complete_color\n    echo -e \"black\\tColor\"\n    echo -e \"red\\tColor\"\n    echo -e \"green\\tColor\"\n    echo -e \"yellow\\tColor\"\n    echo -e \"blue\\tColor\"\n    echo -e \"magenta\\tColor\"\n    echo -e \"cyan\\tColor\"\n    echo -e \"white\\tColor\"\n    echo -e \"default\\tColor\"\nend\n\nfunction __fastfetch_complete_command\n    for line in (fastfetch --list-modules autocompletion)\n        set -l pair (string split -m 2 : $line)\n        set -l module (string lower $pair[1])\n\n        echo -e \"$module-format\\tModule format\"\n    end\n    echo -e \"format\\tCustom format\"\n    echo -e \"color\\tColor format\"\nend\n\nfunction __fastfetch_complete_config\n    for line in (fastfetch --list-presets autocompletion)\n        echo -e \"$line\\tPreset\"\n    end\n    echo -e \"none\\tDisable loading config file\"\nend\n\nfunction __fastfetch_complete_logo\n    for line in (fastfetch --list-logos autocompletion)\n        echo -e \"$line\\tBuiltin logo\"\n    end\n    echo -e \"none\\tDon't print logo\"\n    echo -e \"small\\tPrint small ascii logo if available\"\nend\n\nfunction __fastfetch_complete_structure\n    for line in (fastfetch --list-modules autocompletion)\n        set -l pair (string split -m 2 : $line)\n        echo -e \"$pair[1]\\t$pair[2]\"\n    end\nend\n\necho '\nimport json, subprocess, sys\n\n\ndef main():\n    data: dict[str, list[dict]] = json.loads(subprocess.check_output([\"fastfetch\", \"--help-raw\"]))\n\n    for key in data:\n        for flag in data[key]:\n            if flag[\"long\"] == \"logo-color-[1-9]\":\n                for i in range(1, 10):\n                    print(f\"\"\"complete -c fastfetch -d \"{flag[\"desc\"]}\" -l \"logo-color-{i}\" -x -a \"(__fastfetch_complete_color)\" \"\"\")\n                continue\n\n            if flag.get(\"pseudo\", False):\n                continue\n\n            command_prefix = f\"\"\"complete -c fastfetch -d \"{flag[\"desc\"]}\" -l \"{flag[\"long\"]}\\\"\"\"\"\n            if \"short\" in flag:\n                command_prefix += f\"\"\" -o {flag[\"short\"]}\"\"\"\n\n            if \"arg\" in flag:\n                type: str = flag[\"arg\"][\"type\"]\n                if type == \"bool\":\n                    print(f\"{command_prefix} -x -a \\\"(__fastfetch_complete_bool)\\\"\")\n                elif type == \"color\":\n                    print(f\"{command_prefix} -x -a \\\"(__fastfetch_complete_color)\\\"\")\n                elif type == \"command\":\n                    print(f\"{command_prefix} -x -a \\\"(__fastfetch_complete_command)\\\"\")\n                elif type == \"config\":\n                    print(f\"{command_prefix} -x -a \\\"(__fastfetch_complete_config)\\\"\")\n                elif type == \"enum\":\n                    temp: str = \" \".join(flag[\"arg\"][\"enum\"])\n                    print(f\"{command_prefix} -x -a \\\"{temp}\\\"\")\n                elif type == \"logo\":\n                    print(f\"{command_prefix} -x -a \\\"(__fastfetch_complete_logo)\\\"\")\n                elif type == \"structure\":\n                    print(f\"{command_prefix} -x -a \\\"(__fish_complete_list : __fastfetch_complete_structure)\\\"\")\n                elif type == \"path\":\n                    print(f\"{command_prefix} -r -F\")\n                else:\n                    print(f\"{command_prefix} -x\")\n            else:\n                print(f\"{command_prefix} -f\")\n\n\nif __name__ == \"__main__\":\n    try:\n        main()\n    except:\n        sys.exit(1)\n' | python3 | source\n"
  },
  {
    "path": "completions/fastfetch.zsh",
    "content": "#compdef fastfetch\n\nfunction _fastfetch() {\n\n  whence python3 &> /dev/null\n  if [ $? -ne 0 ]\n  then\n    return\n  fi\n\n  local state\n\n  local -a opts=(\"${(f)$(\n        python3 <<EOF\nimport json\nimport subprocess\nimport sys\n\n\ndef main():\n    data: dict[str, list[dict]] = json.loads(\n        subprocess.check_output([\"fastfetch\", \"--help-raw\"])\n    )\n\n    for key in data:\n        for flag in data[key]:\n            if flag[\"long\"] == \"logo-color-[1-9]\":\n                for i in range(1, 10):\n                    command_prefix = f\"--logo-color-{i}[{flag['desc']} ({i})]\"\n                    print_command(command_prefix, flag)\n                continue\n\n            if flag.get(\"pseudo\", False):\n                continue\n\n            if \"short\" in flag:\n                command_prefix = f\"-{flag['short']}[{flag['desc']}]\"\n                print_command(command_prefix, flag)\n\n            if \"long\" in flag:\n                command_prefix = f\"--{flag['long']}[{flag['desc']}]\"\n                print_command(command_prefix, flag)\n\n\ndef print_command(command_prefix: str, flag: dict):\n    if \"arg\" in flag:\n        type: str = flag[\"arg\"][\"type\"]\n        if type == \"bool\":\n            print(f\"{command_prefix}::bool:(true false)\")\n        elif type == \"color\":\n            print(f\"{command_prefix}:color:->colors\")\n        elif type == \"command\":\n            print(f\"{command_prefix}::module:->modules\")\n        elif type == \"config\":\n            print(f\"{command_prefix}:preset:->presets\")\n        elif type == \"enum\":\n            temp: str = \" \".join(flag[\"arg\"][\"enum\"])\n            print(f'{command_prefix}:type:({temp})')\n        elif type == \"logo\":\n            print(f\"{command_prefix}:logo:->logos\")\n        elif type == \"structure\":\n            print(f\"{command_prefix}:structure:->structures\")\n        elif type == \"path\":\n            print(f\"{command_prefix}::path:_files\")\n        else:\n            print(f\"{command_prefix}:\")\n    else:\n        print(f\"{command_prefix}\")\n\n\nif __name__ == \"__main__\":\n    try:\n        main()\n    except Exception:\n        sys.exit(1)\nEOF\n  )}\")\n\n  _arguments \"$opts[@]\"\n\n  case $state in\n    colors)\n      local -a colors=(black red green yellow blue magenta cyan white default)\n      _describe 'color' colors\n      ;;\n    modules)\n      local -a modules=(\"${(f)$(fastfetch --list-modules autocompletion)}\")\n      modules=(${(L)^modules[@]%%:*}-format format color)\n      _describe 'module' modules\n      ;;\n    presets)\n      local -a presets=(\n        \"${(f)$(fastfetch --list-presets autocompletion)}\"\n        \"none:Disable loading config file\"\n      )\n      _describe 'preset' presets || _files\n      ;;\n    structures)\n      local -a structures=(\"${(f)$(fastfetch --list-modules autocompletion)}\")\n      _describe 'structure' structures\n      ;;\n    logos)\n      local -a logos=(\n        \"${(f)$(fastfetch --list-logos autocompletion)}\"\n        \"none:Don't print logo\"\n        \"small:Print small ascii logo if available\"\n      )\n      _describe 'logo' logos\n      ;;\n  esac\n}\n\n_fastfetch \"$@\"\n"
  },
  {
    "path": "debian/changelog.tpl",
    "content": "fastfetch (2.58.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium\n\n  * Update to 2.58.0\n\n -- Carter Li <zhangsongcui@live.cn>  Thu, 22 Jan 2026 15:30:58 +0800\n\nfastfetch (2.57.1~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium\n\n  * Update to 2.57.1\n\n -- Carter Li <zhangsongcui@live.cn>  Wed, 14 Jan 2026 14:00:17 +0800\n\nfastfetch (2.57.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium\n\n  * Update to 2.57.0\n\n -- Carter Li <zhangsongcui@live.cn>  Mon, 12 Jan 2026 10:11:21 +0800\n\nfastfetch (2.56.1~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium\n\n  * Update to 2.56.1\n\n -- Carter Li <zhangsongcui@live.cn>  Thu, 18 Dec 2025 14:57:36 +0800\n\nfastfetch (2.56.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium\n\n  * Update to 2.56.0\n\n -- Carter Li <zhangsongcui@live.cn>  Mon, 08 Dec 2025 09:21:58 +0800\n\nfastfetch (2.55.1~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium\n\n  * Update to 2.55.1\n\n -- Carter Li <zhangsongcui@live.cn>  Mon, 17 Nov 2025 10:15:44 +0800\n\nfastfetch (2.55.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium\n\n  * Update to 2.55.0\n\n -- Carter Li <zhangsongcui@live.cn>  Wed, 12 Nov 2025 09:15:24 +0800\n\nfastfetch (2.54.1~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium\n\n  * Fix building on plucky\n\n -- Carter Li <zhangsongcui@live.cn>  Mon, 20 Oct 2025 14:27:02 +0800\n\nfastfetch (2.54.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium\n\n  * Update to 2.54.0\n  * Test independent changelog entries for different Ubuntu releases\n\n -- Carter Li <zhangsongcui@live.cn>  Mon, 20 Oct 2025 10:47:02 +0800\n\nfastfetch (2.53.0) jammy; urgency=medium\n\n  * Update to 2.53.0\n\n -- Carter Li <zhangsongcui@live.cn>  Tue, 23 Sep 2025 09:58:48 +0800\n\nfastfetch (2.52.0) jammy; urgency=medium\n\n  * Update to 2.52.0\n\n -- Carter Li <zhangsongcui@live.cn>  Fri, 05 Sep 2025 14:59:44 +0800\n\nfastfetch (2.51.0) jammy; urgency=medium\n\n  * Update to 2.51.0\n\n -- Carter Li <zhangsongcui@live.cn>  Fri, 29 Aug 2025 08:55:03 +0800\n\nfastfetch (2.50.2) jammy; urgency=medium\n\n  * Update to 2.50.2\n\n -- Carter Li <zhangsongcui@live.cn>  Thu, 21 Aug 2025 10:33:07 +0800\n\nfastfetch (2.49.0) jammy; urgency=medium\n\n  * Update to 2.49.0\n\n -- Carter Li <zhangsongcui@live.cn>  Thu, 31 Jul 2025 14:32:59 +0800\n\nfastfetch (2.48.0) jammy; urgency=medium\n\n  * Update to 2.48.0\n\n -- Carter Li <zhangsongcui@live.cn>  Thu, 17 Jul 2025 16:16:52 +0800\n\nfastfetch (2.47.0) jammy; urgency=medium\n\n  * Update to 2.47.0\n\n -- Carter Li <zhangsongcui@live.cn>  Thu, 03 Jul 2025 14:51:45 +0800\n\nfastfetch (2.46.0) jammy; urgency=medium\n\n  * Update to 2.46.0\n\n -- Carter Li <zhangsongcui@live.cn>  Fri, 20 Jun 2025 15:01:21 +0800\n\nfastfetch (2.45.0) jammy; urgency=medium\n\n  * Update to 2.45.0\n\n -- Carter Li <zhangsongcui@live.cn>  Thu, 05 Jun 2025 10:56:38 +0800\n\nfastfetch (2.44.0) jammy; urgency=medium\n\n  * Update to 2.44.0\n\n -- Carter Li <zhangsongcui@live.cn>  Mon, 26 May 2025 14:59:01 +0800\n\nfastfetch (2.43.0) jammy; urgency=medium\n\n  * Update to 2.43.0\n\n -- Carter Li <zhangsongcui@live.cn>  Wed, 14 May 2025 09:49:50 +0800\n\nfastfetch (2.42.0) jammy; urgency=medium\n\n  * Update to 2.42.0\n\n -- Carter Li <zhangsongcui@live.cn>  Wed, 30 Apr 2025 14:08:57 +0800\n\nfastfetch (2.41.0) jammy; urgency=medium\n\n  * Update to 2.41.0\n\n -- Carter Li <zhangsongcui@live.cn>  Wed, 16 Apr 2025 13:42:58 +0800\n\nfastfetch (2.40.4) jammy; urgency=medium\n\n  * Update to 2.40.4\n\n -- Carter Li <zhangsongcui@live.cn>  Thu, 10 Apr 2025 15:38:21 +0800\n\nfastfetch (2.40.3) jammy; urgency=medium\n\n  * Update to 2.40.3\n\n -- Carter Li <zhangsongcui@live.cn>  Mon, 07 Apr 2025 09:29:27 +0800\n\nfastfetch (2.40.0) jammy; urgency=medium\n\n  * Update to 2.40.0\n\n -- Carter Li <zhangsongcui@live.cn>  Thu, 03 Apr 2025 08:46:54 +0800\n\nfastfetch (2.39.1) jammy; urgency=medium\n\n  * Update to 2.39.1\n\n -- Carter Li <zhangsongcui@live.cn>  Fri, 21 Mar 2025 11:02:19 +0800\n\nfastfetch (2.39.0ubuntu1) jammy; urgency=medium\n\n  * Remove unwanted debugging code\n\n -- Carter Li <zhangsongcui@live.cn>  Thu, 20 Mar 2025 10:39:22 +0800\n\nfastfetch (2.39.0) jammy; urgency=medium\n\n  * Update to 2.39.0\n\n -- Carter Li <zhangsongcui@live.cn>  Thu, 20 Mar 2025 10:35:18 +0800\n\nfastfetch (2.38.0) jammy; urgency=medium\n\n  * Update to 2.38.0\n\n -- Carter Li <zhangsongcui@live.cn>  Wed, 05 Mar 2025 14:56:24 +0800\n\nfastfetch (2.37.0) jammy; urgency=medium\n\n  * Update to 2.37.0\n\n -- Carter Li <zhangsongcui@live.cn>  Wed, 19 Feb 2025 15:43:42 +0800\n\nfastfetch (2.36.1) jammy; urgency=medium\n\n  * Update to 2.36.1\n\n -- Carter Li <zhangsongcui@live.cn>  Tue, 11 Feb 2025 13:39:55 +0800\n\nfastfetch (2.36.0) jammy; urgency=medium\n\n  * Update to 2.36.0\n\n -- Carter Li <zhangsongcui@live.cn>  Mon, 10 Feb 2025 10:13:53 +0800\n\nfastfetch (2.35.0) jammy; urgency=medium\n\n  * Update to 2.35.0\n\n -- Carter Li <zhangsongcui@live.cn>  Sun, 26 Jan 2025 10:15:22 +0800\n\nfastfetch (2.34.1) jammy; urgency=medium\n\n  * Update to 2.34.1\n\n -- Carter Li <zhangsongcui@live.cn>  Mon, 13 Jan 2025 16:06:22 +0800\n\nfastfetch (2.34.0) jammy; urgency=medium\n\n  * Update to 2.34.0\n\n -- Carter Li <zhangsongcui@live.cn>  Thu, 09 Jan 2025 09:03:17 +0800\n\nfastfetch (2.33.0) jammy; urgency=medium\n\n  * Update to 2.33.0\n\n -- Carter Li <zhangsongcui@live.cn>  Thu, 26 Dec 2024 09:42:27 +0800\n\nfastfetch (2.32.0) jammy; urgency=medium\n\n  * Update to 2.32.0\n\n -- Carter Li <zhangsongcui@live.cn>  Wed, 18 Dec 2024 10:39:06 +0800\n\nfastfetch (2.31.0) jammy; urgency=medium\n\n  * Update to 2.31.0\n\n -- Carter Li <zhangsongcui@live.cn>  Wed, 04 Dec 2024 08:41:40 +0800\n\nfastfetch (2.30.1) jammy; urgency=medium\n\n  * Update to 2.30.1\n\n -- Carter Li <zhangsongcui@live.cn>  Mon, 18 Nov 2024 15:40:48 +0800\n\nfastfetch (2.30.0) jammy; urgency=medium\n\n  * Update to 2.30.0\n\n -- Carter Li <zhangsongcui@live.cn>  Mon, 18 Nov 2024 09:30:58 +0800\n\nfastfetch (2.29.0) jammy; urgency=medium\n\n  * Update to 2.29.0\n\n -- Carter Li <zhangsongcui@live.cn>  Mon, 04 Nov 2024 15:05:02 +0800\n\nfastfetch (2.28.0) jammy; urgency=medium\n\n  * Update to 2.28.0\n\n -- Carter Li <zhangsongcui@live.cn>  Wed, 23 Oct 2024 10:18:59 +0800\n\nfastfetch (2.27.1) jammy; urgency=medium\n\n  * Update to 2.27.1\n\n -- Carter Li <zhangsongcui@live.cn>  Sun, 06 Oct 2024 12:55:18 +0800\n\nfastfetch (2.26.1ubuntu1) jammy; urgency=medium\n\n  * Update correct code\n\n -- Carter Li <zhangsongcui@live.cn>  Mon, 30 Sep 2024 00:21:20 +0800\n\nfastfetch (2.26.1) jammy; urgency=medium\n\n  * Update to 2.26.1\n\n -- Carter Li <zhangsongcui@live.cn>  Sun, 29 Sep 2024 16:15:31 +0800\n\nfastfetch (2.26.0) jammy; urgency=medium\n\n  * Update to 2.26.0\n\n -- Carter Li <zhangsongcui@live.cn>  Sun, 29 Sep 2024 13:31:25 +0800\n\nfastfetch (2.25.0) jammy; urgency=medium\n\n  * Update to 2.25.0\n\n -- Carter Li <zhangsongcui@live.cn>  Thu, 19 Sep 2024 10:28:38 +0800\n\nfastfetch (2.24.0) jammy; urgency=medium\n\n  * Update to 2.24.0\n\n -- Carter Li <zhangsongcui@live.cn>  Wed, 11 Sep 2024 13:50:02 +0800\n\nfastfetch (2.23.0) jammy; urgency=medium\n\n  * Update to 2.23.0\n\n -- Carter Li <zhangsongcui@live.cn>  Tue, 03 Sep 2024 18:44:11 +0800\n\nfastfetch (2.22.0) jammy; urgency=medium\n\n  * Update to 2.22.0\n\n -- Carter Li <zhangsongcui@live.cn>  Mon, 26 Aug 2024 18:53:35 +0800\n\nfastfetch (2.21.3) jammy; urgency=medium\n\n  * Update to 2.21.3\n\n -- Carter Li <zhangsongcui@live.cn>  Thu, 15 Aug 2024 16:14:52 +0800\n\nfastfetch (2.21.2) jammy; urgency=medium\n\n  * Update to 2.21.2\n\n -- Carter Li <zhangsongcui@live.cn>  Wed, 14 Aug 2024 14:42:07 +0800\n\nfastfetch (2.21.1) jammy; urgency=medium\n\n  * Update to 2.21.1\n\n -- Carter Li <zhangsongcui@live.cn>  Fri, 09 Aug 2024 14:27:10 +0800\n\nfastfetch (2.21.0) jammy; urgency=medium\n\n  * Update to 2.21.0\n\n -- Carter Li <zhangsongcui@live.cn>  Mon, 05 Aug 2024 14:35:43 +0800\n\nfastfetch (2.20.0) jammy; urgency=medium\n\n  * Update to 2.20.0\n\n -- Carter Li <zhangsongcui@live.cn>  Fri, 26 Jul 2024 14:02:50 +0800\n\nfastfetch (2.19.1) jammy; urgency=medium\n\n  * Update to 2.19.1\n\n -- Carter Li <zhangsongcui@live.cn>  Tue, 23 Jul 2024 10:25:14 +0800\n\nfastfetch (2.19.0) jammy; urgency=medium\n\n  * Update to 2.19.0\n\n -- Carter Li <zhangsongcui@live.cn>  Mon, 22 Jul 2024 14:17:55 +0800\n\nfastfetch (2.18.1) jammy; urgency=medium\n\n  * Update to 2.18.1\n\n -- Carter Li <zhangsongcui@live.cn>  Thu, 11 Jul 2024 14:32:15 +0800\n\nfastfetch (2.18.0) jammy; urgency=medium\n\n  * Update to 2.18.0\n\n -- Carter Li <zhangsongcui@live.cn>  Wed, 10 Jul 2024 16:46:32 +0800\n\nfastfetch (2.17.2) jammy; urgency=medium\n\n  * Update to 2.17.2\n\n -- Carter Li <zhangsongcui@live.cn>  Thu, 04 Jul 2024 10:22:44 +0800\n\nfastfetch (2.17.1) jammy; urgency=medium\n\n  * Update to 2.17.1\n\n -- Carter Li <zhangsongcui@live.cn>  Mon, 01 Jul 2024 08:56:29 +0800\n\nfastfetch (2.17.0) jammy; urgency=medium\n\n  * Update to 2.17.0\n\n -- Carter Li <zhangsongcui@live.cn>  Fri, 28 Jun 2024 13:43:18 +0800\n\nfastfetch (2.16.0) jammy; urgency=medium\n\n  * Update to 2.16.0\n\n -- Carter Li <zhangsongcui@live.cn>  Wed, 19 Jun 2024 14:53:43 +0800\n\nfastfetch (2.15.0) jammy; urgency=medium\n\n  * Update to 2.15.0\n\n -- Carter Li <zhangsongcui@live.cn>  Fri, 07 Jun 2024 13:52:43 +0800\n\nfastfetch (2.14.0) jammy; urgency=medium\n\n  * Update to 2.14.0\n\n -- Carter Li <zhangsongcui@live.cn>  Thu, 30 May 2024 14:27:54 +0800\n\nfastfetch (2.13.2) jammy; urgency=medium\n\n  * Update to 2.13.2\n\n -- Carter Li <zhangsongcui@live.cn>  Fri, 24 May 2024 13:48:59 +0800\n\nfastfetch (2.13.1) jammy; urgency=medium\n\n  * Update to 2.13.1\n\n -- Carter Li <zhangsongcui@live.cn>  Tue, 21 May 2024 15:10:37 +0800\n\nfastfetch (2.12.0) jammy; urgency=medium\n\n  * Update to 2.12.0\n\n -- Carter Li <zhangsongcui@live.cn>  Tue, 14 May 2024 16:33:33 +0800\n\nfastfetch (2.11.5) jammy; urgency=medium\n\n  * Update to 2.11.5\n\n -- Carter Li <zhangsongcui@live.cn>  Tue, 07 May 2024 09:30:05 +0800\n\nfastfetch (2.11.3) jammy; urgency=medium\n\n  * Update to 2.11.3\n\n -- Carter Li <zhangsongcui@live.cn>  Mon, 06 May 2024 08:46:45 +0800\n\nfastfetch (2.10.2) jammy; urgency=medium\n\n  * Update to 2.10.2\n\n -- Carter Li <zhangsongcui@live.cn>  Tue, 23 Apr 2024 15:18:23 +0800\n\nfastfetch (2.10.1) jammy; urgency=medium\n\n  * Update to 2.10.1\n\n -- Carter Li <zhangsongcui@live.cn>  Tue, 23 Apr 2024 09:55:02 +0800\n\nfastfetch (2.9.2) jammy; urgency=medium\n\n  * Update to 2.9.2\n\n -- Carter Li <zhangsongcui@live.cn>  Tue, 16 Apr 2024 16:32:40 +0800\n\nfastfetch (2.9.1) jammy; urgency=medium\n\n  * Update to 2.9.1\n\n -- Carter Li <zhangsongcui@live.cn>  Mon, 08 Apr 2024 09:34:30 +0800\n\nfastfetch (2.8.10) jammy; urgency=medium\n\n  * Update to 2.8.10\n\n -- Carter Li <zhangsongcui@live.cn>  Mon, 25 Mar 2024 15:01:53 +0800\n\nfastfetch (2.8.9) jammy; urgency=medium\n\n  * Update to 2.8.9\n\n -- Carter Li <zhangsongcui@live.cn>  Fri, 15 Mar 2024 10:49:42 +0800\n\nfastfetch (2.8.8) jammy; urgency=medium\n\n  * Update to 2.8.8\n\n -- Carter Li <zhangsongcui@live.cn>  Fri, 08 Mar 2024 09:59:41 +0800\n\nfastfetch (2.8.6) jammy; urgency=medium\n\n  * Update to 2.8.6\n\n -- Carter Li <zhangsongcui@live.cn>  Wed, 28 Feb 2024 10:01:40 +0800\n\nfastfetch (2.8.4) jammy; urgency=medium\n\n  * Update to 2.8.4\n\n -- Carter Li <zhangsongcui@live.cn>  Fri, 23 Feb 2024 16:14:57 +0800\n\nfastfetch (2.7.1ubuntu2) jammy; urgency=medium\n\n  * Ignore .git\n\n -- Carter Li <zhangsongcui@live.cn>  Wed, 07 Feb 2024 14:23:23 +0800\n\nfastfetch (2.7.1ubuntu1) jammy; urgency=medium\n\n  * Update build scripts\n\n -- Carter Li <zhangsongcui@live.cn>  Wed, 07 Feb 2024 13:53:37 +0800\n\nfastfetch (2.7.1) jammy; urgency=medium\n\n  * Initial release.\n\n -- Carter Li <zhangsongcui@live.cn>  Tue, 06 Feb 2024 15:01:11 +0800\n"
  },
  {
    "path": "debian/compat",
    "content": "12\n"
  },
  {
    "path": "debian/control",
    "content": "Source: fastfetch\nSection: universe/utils\nPriority: optional\nMaintainer: Carter Li <zhangsongcui@live.cn>\nBuild-Depends: libelf-dev, libvulkan-dev, libwayland-dev, libxrandr-dev, libxcb-randr0-dev, libdconf-dev, libdbus-1-dev, libmagickcore-dev, libsqlite3-dev, librpm-dev, libegl-dev, libglx-dev, ocl-icd-opencl-dev, libpulse-dev, libdrm-dev, libddcutil-dev, libchafa-dev, directx-headers-dev, pkgconf, cmake (>= 3.12), debhelper (>= 11.2), dh-cmake, dh-cmake-compat (= 1), dh-sequence-cmake, dh-sequence-ctest, ninja-build, python3-setuptools\nStandards-Version: 4.0.0\nHomepage: https://github.com/fastfetch-cli/fastfetch\n\nPackage: fastfetch\nArchitecture: any\nDepends: ${shlibs:Depends}, ${misc:Depends}\nDescription: Fastfetch is a neofetch-like tool for fetching system information and displaying them in a pretty way.\n It is written mainly in C, with performance and customizability in mind.\n"
  },
  {
    "path": "debian/copyright",
    "content": "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\nUpstream-Name: fastfetch\nSource: https://github.com/fastfetch-cli/fastfetch\n\nFiles: *\nCopyright: 2024 fastfetch-cli\nLicense: Expat\n\nFiles: src/3rdparty/yyjson/*\nCopyright: 2020 YaoYuan\nLicense: Expat\n"
  },
  {
    "path": "debian/publish.sh",
    "content": "#!/usr/bin/env bash\n# WARNING: DO NOT RUN THIS SCRIPT UNLESS YOU KNOW WHAT YOU ARE DOING.\n\nset -euo pipefail\n\ncommand -v debuild >/dev/null 2>&1 || { echo \"debuild not found. Install devscripts.\" >&2; exit 1; }\ncommand -v dput >/dev/null 2>&1 || { echo \"dput not found.\" >&2; exit 1; }\n\nSCRIPT_DIR=\"$(cd -- \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\nROOT_DIR=\"$(cd -- \"$SCRIPT_DIR/..\" >/dev/null 2>&1 && pwd)\"\nOUT_DIR=\"$(dirname \"$ROOT_DIR\")\"\nPPA=\"ppa:zhangsongcui3371/fastfetch\"\nCODENAMES=( jammy noble plucky questing resolute )\nDRY_RUN=0\n\nif [[ -d \"$OUT_DIR/build\" ]]; then\n    echo \"Output directory exists: $OUT_DIR/build\" >&2\n    echo \"Please move or delete it before proceeding.\" >&2\n    exit 1\nfi\n\nTEMPLATE=\"$ROOT_DIR/debian/changelog.tpl\"\nCHANGELOG_DEBIAN=\"$ROOT_DIR/debian/changelog\"\n\nif [[ ! -f \"$TEMPLATE\" ]]; then\n  echo \"Template not found: $TEMPLATE\" >&2\n  exit 1\nfi\n\nif ! grep -q '#UBUNTU_CODENAME#' \"$TEMPLATE\"; then\n  echo \"Template missing placeholder: #UBUNTU_CODENAME#\" >&2\n  exit 1\nfi\n\necho \"IMPORTANT: Before proceeding, please ensure that 'debian/changelog.tpl' is up-to-date\"\necho \"   with the correct version number, release notes, and maintainer information.\"\necho\necho \"   The template must contain a placeholder like '#UBUNTU_CODENAME#' for the codename.\"\necho\nread -p \"Have you reviewed and updated 'debian/changelog.tpl'? (y/N): \" -r\necho\nif [[ ! $REPLY =~ ^[Yy]$ ]]; then\n    exit 1\nfi\n\ncleanup() {\n    rm -f \"$CHANGELOG_DEBIAN\"\n    rm -f \"$ROOT_DIR/debian/files\"\n}\ntrap cleanup EXIT\n\nshopt -s nullglob\n\nfor codename in \"${CODENAMES[@]}\"; do\n  echo \"==> Publishing distro: $codename\"\n  sed \"s/#UBUNTU_CODENAME#/${codename}/g\" \"$TEMPLATE\" > \"$CHANGELOG_DEBIAN\"\n\n  ( cd \"$ROOT_DIR\" && debuild -S -i -I )\n\n  changes=( \"$OUT_DIR\"/fastfetch_*~${codename}_source.changes )\n  if [[ ${#changes[@]} -ne 1 ]]; then\n    echo \"Unable to uniquely identify .changes for '$codename' in: $OUT_DIR\" >&2\n    printf 'Found:\\n'; printf '  %s\\n' \"${changes[@]}\" >&2 || true\n    exit 1\n  fi\n\n  if [[ $DRY_RUN -eq 1 ]]; then\n    echo \"[DRY-RUN] dput \\\"$PPA\\\" \\\"${changes[0]}\\\"\"\n  else\n    dput \"$PPA\" \"${changes[0]}\"\n  fi\n\n  rm -f \"$OUT_DIR\"/fastfetch_*~${codename}_source.{changes,dsc,tar.*}\n\n  echo \"<== Done: $codename\"\ndone\n"
  },
  {
    "path": "debian/rules",
    "content": "#!/usr/bin/make -f\n\n%:\n\tdh $@ --buildsystem=cmake+ninja\n\noverride_dh_ctest_configure:\n\tdh_ctest_configure -- -DSET_TWEAK=OFF -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo\n"
  },
  {
    "path": "debian/watch",
    "content": "version=4\nopts=\"filenamemangle=s%.*/@ANY_VERSION@%@PACKAGE@-$1.tar.gz%,searchmode=plain\" \\\nhttps://api.github.com/repos/fastfetch-cli/fastfetch/releases?per_page=100 \\\nhttps://api.github.com/repos/fastfetch-cli/fastfetch/tarball/@ANY_VERSION@\n"
  },
  {
    "path": "doc/fastfetch.1.in",
    "content": ".TH FASTFETCH 1 \"@FASTFETCH_BUILD_DATE@\" \"@CMAKE_PROJECT_NAME@ @CMAKE_PROJECT_VERSION@\"\n\n.SH NAME\nfastfetch \\- a fast and customizable system information tool similar to neofetch\n\n.SH SYNOPSIS\n\n.B fastfetch\n.I [options...]\n\n.SH DESCRIPTION\n\nFastfetch is a tool for displaying system information in a visually appealing way. Written primarily in C, it focuses on performance and customizability while providing functionality similar to neofetch.\nIt supports Linux, Android, *BSD, macOS, Haiku, and Windows 7 or newer.\n\n.SH \"EXIT STATUS\"\n\nFastfetch returns zero on successful execution. Any errors result in a non-zero exit code.\n\n.SH OPTIONS\n\n.SS \"Informative Options\"\n.TP\n\n.B \\-h, \\-\\-help \\fI[command]\nDisplay help information for all available options or for a specific command\n.TP\n\n.B \\-v, \\-\\-version\nDisplay the version of fastfetch\n.TP\n\n.B \\-\\-version\\-raw\nDisplay the raw version string (major.minor.patch)\n.TP\n\n.B \\-\\-list\\-config\\-paths\nList search paths for configuration files\n.TP\n\n.B \\-\\-list\\-data\\-paths\nList search paths for presets and logos\n.TP\n\n.B \\-\\-list\\-logos\nList available logos that can be loaded with \\fI\\-\\-logo\n.TP\n\n.B \\-\\-list\\-modules\nList all available modules\n.TP\n\n.B \\-\\-list\\-presets\nList available presets that can be loaded with \\fI\\-\\-config\n.TP\n\n.B \\-\\-list\\-features\nList the features that fastfetch was compiled with (mainly for development)\n.TP\n\n.B \\-\\-print\\-logos\nDisplay all available logos\n.TP\n\n.B \\-\\-print\\-structure\nDisplay the default structure\n.TP\n\n.B \\-\\-format \\fI<type>\nSet the output format. Available options are:\n.RS\n.IP \\(bu 2\n\\fIdefault\\fR: Default human-readable format\n.IP \\(bu 2\n\\fIjson\\fR: JSON format for machine processing\n.RE\n\n.SS \"Config Options\"\n.TP\n\n.B \\-c, \\-\\-config \\fI<config>\nUse the specified config file or preset. Specify \\fInone\\fR to disable further config loading. See the CONFIGURATION section for details on config files.\n.TP\n\n.B \\-\\-gen\\-config \\fI[path]\nGenerate a config file with options specified on the command line. If \\fIpath\\fR is not specified, it defaults to \\fB~/.config/fastfetch/config.jsonc\\fR. If \\fIpath\\fR is \"\\-\", the configuration will be written to stdout.\n.TP\n\n.B \\-\\-gen\\-config\\-force \\fI[path]\nSame as \\fB\\-\\-gen\\-config\\fR, but overwrites any existing file at the destination path.\n.TP\n\n.SS \"Logo Options\"\n.TP\n\n.B \\-l, \\-\\-logo \\fI<logo>\nSet the logo to display. Can be the name of a built-in logo or a path to an image file. Use \\fInone\\fR to disable the logo.\n.TP\n\n.B \\-\\-logo\\-type \\fI<type>\nSet the type of the logo specified with \\fI\\-\\-logo\\fR. Available types include \\fIauto\\fR, \\fIbuiltin\\fR, \\fIfile\\fR, \\fIsixel\\fR, \\fIkitty\\fR, and others. See \\fB\\-\\-help logo\\-type\\fR for details.\n.TP\n\n.B \\-\\-logo\\-width \\fI<width>\nSet the width of the logo in characters (for image logos)\n.TP\n\n.B \\-\\-logo\\-height \\fI<height>\nSet the height of the logo in characters (for image logos)\n.TP\n\n.B \\-\\-logo\\-color\\-[1\\-9] \\fI<color>\nOverride specific colors in the logo\n\n.SS \"Display Options\"\n.TP\n\n.B \\-s, \\-\\-structure \\fI<structure>\nSet the structure of the fetch (a colon-separated list of module names)\n.TP\n\n.B \\-\\-color \\fI<color>\nSet the color of keys and title. See \\fB\\-\\-help color\\fR for available colors.\n.TP\n\n.B \\-\\-color\\-keys \\fI<color>\nSet the color of keys only\n.TP\n\n.B \\-\\-color\\-title \\fI<color>\nSet the color of the title only\n.TP\n\n.B \\-\\-separator \\fI<string>\nSet the separator between key and value (default: \": \")\n.TP\n\n.B \\-\\-key\\-width \\fI<num>\nAlign the width of keys to \\fI<num>\\fR characters\n.TP\n\n.B \\-\\-show\\-errors\nDisplay errors when they occur (default: false)\n.TP\n\n.B \\-\\-pipe\nDisable colors (automatically detected based on whether stdout is a terminal)\n\nTo list all available options including module-specific options, use \\fB\\-\\-help\\fR.\n\n.SH CONFIGURATION\n.SS \"Fetch Structure\"\nThe structure defines which modules to display and in what order. It consists of module names separated by colons (:).\nFor example: \\fBtitle:separator:os:kernel:uptime\\fR\n\nTo list all available modules, use \\fB\\-\\-list\\-modules\\fR\n\n.SS \"Config Files\"\n\nFastfetch uses JSONC for configuration files. JSONC is JSON with support for comments (// and /* */). Configuration files must have the .jsonc extension.\n\nYou can generate a default config file using \\fB\\-\\-gen\\-config\\fR. By default, the config file is saved at \\fB~/.config/fastfetch/config.jsonc\\fR.\n\nThe configuration/preset files are searched in the following locations (in order):\n\n.RS\n.IP 1. 4\nRelative to the current working directory\n.IP 2. 4\nRelative to ~/.local/share/fastfetch/presets/\n.IP 3. 4\nRelative to /usr/share/fastfetch/presets/\n.RE\n\nFor detailed information on logo options, module configuration, and formatting, visit:\n.RS\n\\fIhttps://github.com/fastfetch-cli/fastfetch/wiki/Configuration\\fR\n.RE\n\nFastfetch provides several built-in presets. List them with \\fB\\-\\-list\\-presets\\fR.\n\n.SS \"JSON Schema\"\nA JSON schema is available for editor intelligence when editing the configuration file. Add the following line at the beginning of your config file:\n.PP\n\\fB\"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\"\\fR\n\n.SH EXAMPLES\n.TP\nBasic usage:\n.RS\n\\fBfastfetch\\fR\n.RE\n.TP\nUse a specific logo:\n.RS\n\\fBfastfetch \\-\\-logo arch\\fR\n.RE\n.TP\nCustom structure:\n.RS\n\\fBfastfetch \\-\\-structure title:os:kernel:uptime:memory\\fR\n.RE\n.TP\nGenerate a config file:\n.RS\n\\fBfastfetch \\-\\-gen\\-config\\fR\n.RE\n.TP\nUse a preset:\n.RS\n\\fBfastfetch \\-\\-config neofetch\\fR\n.RE\n\n.SH \"SEE ALSO\"\n.BR neofetch (1)\n\n.SH BUGS\nPlease report bugs to: \\fIhttps://github.com/fastfetch\\-cli/fastfetch/issues\\fR\n\n.SH AUTHORS\nFastfetch is developed by a team of contributors on GitHub.\nVisit \\fIhttps://github.com/fastfetch-cli/fastfetch\\fR for more information.\n"
  },
  {
    "path": "doc/json_schema.json",
    "content": "{\n    \"$schema\": \"https://json-schema.org/draft-07/schema\",\n    \"$defs\": {\n        \"colors\": {\n            \"oneOf\": [\n                {\n                    \"type\": \"string\",\n                    \"$comment\": \"https://github.com/fastfetch-cli/fastfetch/wiki/Color-Format-Specification\",\n                    \"examples\": [\n                        \"reset_\", \"bright_\", \"dim_\", \"italic_\", \"underline_\", \"blink_\", \"inverse_\", \"hidden_\", \"strike_\", \"light_\",\n                        \"black\", \"red\", \"green\", \"yellow\", \"blue\", \"magenta\", \"cyan\", \"white\", \"default\"\n                    ]\n                },\n                {\n                    \"type\": \"null\",\n                    \"$comment\": \"Disable default color\"\n                }\n            ]\n        },\n        \"key\": {\n            \"description\": \"Key of the module\\nOne whitespace character (` `) can be used to hide the key\",\n            \"type\": \"string\",\n            \"minLength\": 1\n        },\n        \"keyColor\": {\n            \"description\": \"Color of the module key to override the global setting `display.color.key`\",\n            \"$ref\": \"#/$defs/colors\"\n        },\n        \"keyWidth\": {\n            \"description\": \"Width of the module key to override the global setting `display.keyWidth`\",\n            \"type\": \"integer\",\n            \"minimum\": 1\n        },\n        \"keyIcon\": {\n            \"description\": \"Set the icon to be displayed by `display.keyType: \\\"icon\\\"`\",\n            \"type\": \"string\"\n        },\n        \"outputColor\": {\n            \"description\": \"Output color of the module to override the global setting `display.color.output`\",\n            \"$ref\": \"#/$defs/colors\"\n        },\n        \"percentType\": {\n            \"description\": \"Set the percentage output type\",\n            \"oneOf\": [\n                {\n                    \"type\": \"number\",\n                    \"description\": \"0 to use global setting, 1 for percentage number, 2 for multi-color bar, 3 for both, 6 for bar only, 9 for colored number, 10 for monochrome bar\",\n                    \"minimum\": 0,\n                    \"maximum\": 255,\n                    \"default\": 9\n                },\n                {\n                    \"type\": \"array\",\n                    \"description\": \"Array of string flags\",\n                    \"items\": {\n                        \"enum\": [\n                            \"num\",\n                            \"bar\",\n                            \"hide-others\",\n                            \"num-color\",\n                            \"bar-monochrome\"\n                        ]\n                    },\n                    \"uniqueItems\": true,\n                    \"default\": [\n                        \"num\",\n                        \"num-color\"\n                    ]\n                }\n            ]\n        },\n        \"percent\": {\n            \"description\": \"Thresholds for percentage colors\",\n            \"type\": \"object\",\n            \"additionalProperties\": false,\n            \"properties\": {\n                \"green\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 0,\n                    \"maximum\": 100,\n                    \"description\": \"Values less than green will be shown in green\"\n                },\n                \"yellow\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 0,\n                    \"maximum\": 100,\n                    \"description\": \"Values greater than green and less than yellow will be shown in yellow.\\nValues greater than yellow will be shown in red\"\n                },\n                \"type\": {\n                    \"$ref\": \"#/$defs/percentType\"\n                }\n            }\n        },\n        \"temperature\": {\n            \"description\": \"Detect and display temperature if supported\",\n            \"oneOf\": [\n                {\n                    \"type\": \"boolean\",\n                    \"default\": false\n                },\n                {\n                    \"type\": \"object\",\n                    \"additionalProperties\": false,\n                    \"properties\": {\n                        \"green\": {\n                            \"type\": \"integer\",\n                            \"minimum\": 0,\n                            \"maximum\": 100,\n                            \"description\": \"Values (in celsius) less than green will be shown in green\"\n                        },\n                        \"yellow\": {\n                            \"type\": \"integer\",\n                            \"minimum\": 0,\n                            \"maximum\": 100,\n                            \"description\": \"Values (in celsius) greater than green and less than yellow will be shown in yellow.\\nValues greater than yellow will be shown in red\"\n                        }\n                    }\n                }\n            ]\n        },\n        \"systems\": {\n            \"type\": \"string\",\n            \"enum\": [\n                \"Android\",\n                \"Linux\",\n                \"DragonFly\",\n                \"MidnightBSD\",\n                \"FreeBSD\",\n                \"macOS\",\n                \"Windows\",\n                \"SunOS\",\n                \"OpenBSD\",\n                \"NetBSD\",\n                \"Haiku\"\n            ]\n        },\n        \"architectures\": {\n            \"type\": \"string\",\n            \"enum\": [\n                \"x86_64\",\n                \"i386\",\n                \"ia64\",\n                \"aarch64\",\n                \"arm\",\n                \"mips\",\n                \"powerpc\",\n                \"riscv\",\n                \"s390x\",\n                \"loongarch\",\n                \"sparc\",\n                \"alpha\",\n                \"hppa\",\n                \"m68k\"\n            ]\n        },\n        \"conditions\": {\n            \"description\": \"Only show the module if conditions are met\",\n            \"type\": \"object\",\n            \"additionalProperties\": false,\n            \"properties\": {\n                \"system\": {\n                    \"description\": \"System name to match\",\n                    \"oneOf\": [\n                        {\n                            \"$ref\": \"#/$defs/systems\"\n                        },\n                        {\n                            \"type\": \"array\",\n                            \"uniqueItems\": true,\n                            \"items\": {\n                                \"$ref\": \"#/$defs/systems\"\n                            },\n                            \"description\": \"Array of system names to match\"\n                        },\n                        {\n                            \"type\": \"null\",\n                            \"description\": \"Null to disable this condition\"\n                        }\n                    ]\n                },\n                \"!system\": {\n                    \"description\": \"System name to not match\",\n                    \"oneOf\": [\n                        {\n                            \"$ref\": \"#/$defs/systems\"\n                        },\n                        {\n                            \"type\": \"array\",\n                            \"uniqueItems\": true,\n                            \"items\": {\n                                \"$ref\": \"#/$defs/systems\"\n                            },\n                            \"description\": \"Array of system names to not match\"\n                        },\n                        {\n                            \"type\": \"null\",\n                            \"description\": \"Null to disable this condition\"\n                        }\n                    ]\n                },\n                \"arch\": {\n                    \"description\": \"Architecture to match\",\n                    \"oneOf\": [\n                        {\n                            \"$ref\": \"#/$defs/architectures\"\n                        },\n                        {\n                            \"type\": \"array\",\n                            \"uniqueItems\": true,\n                            \"items\": {\n                                \"$ref\": \"#/$defs/architectures\"\n                            },\n                            \"description\": \"Array of architectures to match\"\n                        },\n                        {\n                            \"type\": \"null\",\n                            \"description\": \"Null to disable this condition\"\n                        }\n                    ]\n                },\n                \"!arch\": {\n                    \"description\": \"Architecture to not match\",\n                    \"oneOf\": [\n                        {\n                            \"$ref\": \"#/$defs/architectures\"\n                        },\n                        {\n                            \"type\": \"array\",\n                            \"uniqueItems\": true,\n                            \"items\": {\n                                \"$ref\": \"#/$defs/architectures\"\n                            },\n                            \"description\": \"Array of architectures to not match\"\n                        },\n                        {\n                            \"type\": \"null\",\n                            \"description\": \"Null to disable this condition\"\n                        }\n                    ]\n                },\n                \"succeeded\": {\n                    \"description\": \"Whether the module succeeded in the last run\",\n                    \"oneOf\": [\n                        {\n                            \"type\": \"boolean\",\n                            \"description\": \"True to only show the module if it succeeded, false to only show it if it failed\"\n                        },\n                        {\n                            \"type\": \"null\",\n                            \"description\": \"Null to disable this condition\"\n                        }\n                    ]\n                }\n            }\n        },\n        \"spaceBeforeUnit\": {\n            \"type\": \"string\",\n            \"description\": \"Whether to put a space before the unit\",\n            \"oneOf\": [\n                {\n                    \"const\": \"default\",\n                    \"description\": \"Use the default behavior of the module\"\n                },\n                {\n                    \"const\": \"always\",\n                    \"description\": \"Always add a space before the unit\"\n                },\n                {\n                    \"const\": \"never\",\n                    \"description\": \"Never add a space before the unit\"\n                }\n            ]\n        },\n\n\n        \"batteryFormat\": {\n            \"description\": \"Output format of the module `Battery`. See Wiki for formatting syntax\\n    1. {manufacturer}: Battery manufacturer\\n    2. {model-name}: Battery model name\\n    3. {technology}: Battery technology\\n    4. {capacity}: Battery capacity (percentage num)\\n    5. {status}: Battery status\\n    6. {temperature}: Battery temperature (formatted)\\n    7. {cycle-count}: Battery cycle count\\n    8. {serial}: Battery serial number\\n    9. {manufacture-date}: Battery manufactor date\\n    10. {capacity-bar}: Battery capacity (percentage bar)\\n    11. {time-days}: Battery time remaining days\\n    12. {time-hours}: Battery time remaining hours\\n    13. {time-minutes}: Battery time remaining minutes\\n    14. {time-seconds}: Battery time remaining seconds\\n    15. {time-formatted}: Battery time remaining (formatted)\",\n            \"type\": \"string\"\n        },\n        \"biosFormat\": {\n            \"description\": \"Output format of the module `BIOS`. See Wiki for formatting syntax\\n    1. {date}: Bios date\\n    2. {release}: Bios release\\n    3. {vendor}: Bios vendor\\n    4. {version}: Bios version\\n    5. {type}: Firmware type\",\n            \"type\": \"string\"\n        },\n        \"bluetoothFormat\": {\n            \"description\": \"Output format of the module `Bluetooth`. See Wiki for formatting syntax\\n    1. {name}: Name\\n    2. {address}: Address\\n    3. {type}: Type\\n    4. {battery-percentage}: Battery percentage number\\n    5. {connected}: Is connected\\n    6. {battery-percentage-bar}: Battery percentage bar\",\n            \"type\": \"string\"\n        },\n        \"bluetoothradioFormat\": {\n            \"description\": \"Output format of the module `BluetoothRadio`. See Wiki for formatting syntax\\n    1. {name}: Radio name for discovering\\n    2. {address}: Address\\n    3. {lmp-version}: LMP version\\n    4. {lmp-subversion}: LMP subversion\\n    5. {version}: Bluetooth version\\n    6. {vendor}: Vendor\\n    7. {discoverable}: Discoverable\\n    8. {connectable}: Connectable / Pairable\",\n            \"type\": \"string\"\n        },\n        \"boardFormat\": {\n            \"description\": \"Output format of the module `Board`. See Wiki for formatting syntax\\n    1. {name}: Board name\\n    2. {vendor}: Board vendor\\n    3. {version}: Board version\\n    4. {serial}: Board serial number\",\n            \"type\": \"string\"\n        },\n        \"bootmgrFormat\": {\n            \"description\": \"Output format of the module `Bootmgr`. See Wiki for formatting syntax\\n    1. {name}: Name / description\\n    2. {firmware-path}: Firmware file path\\n    3. {firmware-name}: Firmware file name\\n    4. {secure-boot}: Is secure boot enabled\\n    5. {order}: Boot order\",\n            \"type\": \"string\"\n        },\n        \"brightnessFormat\": {\n            \"description\": \"Output format of the module `Brightness`. See Wiki for formatting syntax\\n    1. {percentage}: Screen brightness (percentage num)\\n    2. {name}: Screen name\\n    3. {max}: Maximum brightness value\\n    4. {min}: Minimum brightness value\\n    5. {current}: Current brightness value\\n    6. {percentage-bar}: Screen brightness (percentage bar)\\n    7. {is-builtin}: Is built-in screen\",\n            \"type\": \"string\"\n        },\n        \"btrfsFormat\": {\n            \"description\": \"Output format of the module `Btrfs`. See Wiki for formatting syntax\\n    1. {name}: Name / Label\\n    2. {uuid}: UUID\\n    3. {devices}: Associated devices\\n    4. {features}: Enabled features\\n    5. {used}: Size used\\n    6. {allocated}: Size allocated\\n    7. {total}: Size total\\n    8. {used-percentage}: Used percentage num\\n    9. {allocated-percentage}: Allocated percentage num\\n    10. {used-percentage-bar}: Used percentage bar\\n    11. {allocated-percentage-bar}: Allocated percentage bar\\n    12. {node-size}: Node size\\n    13. {sector-size}: Sector size\",\n            \"type\": \"string\"\n        },\n        \"cameraFormat\": {\n            \"description\": \"Output format of the module `Camera`. See Wiki for formatting syntax\\n    1. {name}: Device name\\n    2. {vendor}: Vendor\\n    3. {colorspace}: Color space\\n    4. {id}: Identifier\\n    5. {width}: Width (in px)\\n    6. {height}: Height (in px)\",\n            \"type\": \"string\"\n        },\n        \"chassisFormat\": {\n            \"description\": \"Output format of the module `Chassis`. See Wiki for formatting syntax\\n    1. {type}: Chassis type\\n    2. {vendor}: Chassis vendor\\n    3. {version}: Chassis version\\n    4. {serial}: Chassis serial number\",\n            \"type\": \"string\"\n        },\n        \"commandFormat\": {\n            \"description\": \"Output format of the module `Command`. See Wiki for formatting syntax\\n    1. {result}: Command result\",\n            \"type\": \"string\"\n        },\n        \"cpuFormat\": {\n            \"description\": \"Output format of the module `CPU`. See Wiki for formatting syntax\\n    1. {name}: Name\\n    2. {vendor}: Vendor\\n    3. {cores-physical}: Physical core count\\n    4. {cores-logical}: Logical core count\\n    5. {cores-online}: Online core count\\n    6. {freq-base}: Base frequency (formatted)\\n    7. {freq-max}: Max frequency (formatted)\\n    8. {temperature}: Temperature (formatted)\\n    9. {core-types}: Logical core count grouped by frequency\\n    10. {packages}: Processor package count\\n    11. {march}: CPU microarchitecture\\n    12. {numa-nodes}: NUMA node count\",\n            \"type\": \"string\"\n        },\n        \"cpucacheFormat\": {\n            \"description\": \"Output format of the module `CPUCache`. See Wiki for formatting syntax\\n    1. {result}: Separate result\\n    2. {sum}: Sum result\",\n            \"type\": \"string\"\n        },\n        \"cpuusageFormat\": {\n            \"description\": \"Output format of the module `CPUUsage`. See Wiki for formatting syntax\\n    1. {avg}: CPU usage (percentage num, average)\\n    2. {max}: CPU usage (percentage num, maximum)\\n    3. {max-index}: CPU core index of maximum usage\\n    4. {min}: CPU usage (percentage num, minimum)\\n    5. {min-index}: CPU core index of minimum usage\\n    6. {avg-bar}: CPU usage (percentage bar, average)\\n    7. {max-bar}: CPU usage (percentage bar, maximum)\\n    8. {min-bar}: CPU usage (percentage bar, minimum)\",\n            \"type\": \"string\"\n        },\n        \"cursorFormat\": {\n            \"description\": \"Output format of the module `Cursor`. See Wiki for formatting syntax\\n    1. {theme}: Cursor theme\\n    2. {size}: Cursor size\",\n            \"type\": \"string\"\n        },\n        \"datetimeFormat\": {\n            \"description\": \"Output format of the module `DateTime`. See Wiki for formatting syntax\\n    1. {year}: Year\\n    2. {year-short}: Last two digits of year\\n    3. {month}: Month\\n    4. {month-pretty}: Month with leading zero\\n    5. {month-name}: Month name\\n    6. {month-name-short}: Month name short\\n    7. {week}: Week number on year\\n    8. {weekday}: Weekday\\n    9. {weekday-short}: Weekday short\\n    10. {day-in-year}: Day in year\\n    11. {day-in-month}: Day in month\\n    12. {day-in-week}: Day in week\\n    13. {hour}: Hour\\n    14. {hour-pretty}: Hour with leading zero\\n    15. {hour-12}: Hour 12h format\\n    16. {hour-12-pretty}: Hour 12h format with leading zero\\n    17. {minute}: Minute\\n    18. {minute-pretty}: Minute with leading zero\\n    19. {second}: Second\\n    20. {second-pretty}: Second with leading zero\\n    21. {offset-from-utc}: Offset from UTC in the ISO 8601 format\\n    22. {timezone-name}: Locale-dependent timezone name or abbreviation\\n    23. {day-pretty}: Day in month with leading zero\",\n            \"type\": \"string\"\n        },\n        \"deFormat\": {\n            \"description\": \"Output format of the module `DE`. See Wiki for formatting syntax\\n    1. {process-name}: DE process name\\n    2. {pretty-name}: DE pretty name\\n    3. {version}: DE version\",\n            \"type\": \"string\"\n        },\n        \"displayFormat\": {\n            \"description\": \"Output format of the module `Display`. See Wiki for formatting syntax\\n    1. {width}: Screen configured width (in pixels)\\n    2. {height}: Screen configured height (in pixels)\\n    3. {refresh-rate}: Screen configured refresh rate (in Hz)\\n    4. {scaled-width}: Screen scaled width (in pixels)\\n    5. {scaled-height}: Screen scaled height (in pixels)\\n    6. {name}: Screen name\\n    7. {type}: Screen type (Built-in or External)\\n    8. {rotation}: Screen rotation (in degrees)\\n    9. {is-primary}: True if being the primary screen\\n    10. {physical-width}: Screen physical width (in millimeters)\\n    11. {physical-height}: Screen physical height (in millimeters)\\n    12. {inch}: Physical diagonal length in inches\\n    13. {ppi}: Pixels per inch (PPI)\\n    14. {bit-depth}: Bits per color channel\\n    15. {hdr-enabled}: True if high dynamic range (HDR) mode is enabled\\n    16. {manufacture-year}: Year of manufacturing\\n    17. {manufacture-week}: Nth week of manufacturing in the year\\n    18. {serial}: Serial number\\n    19. {platform-api}: The platform API used when detecting the display\\n    20. {hdr-compatible}: True if the display is HDR compatible\\n    21. {scale-factor}: HiDPI scale factor\\n    22. {preferred-width}: Screen preferred width (in pixels)\\n    23. {preferred-height}: Screen preferred height (in pixels)\\n    24. {preferred-refresh-rate}: Screen preferred refresh rate (in Hz)\",\n            \"type\": \"string\"\n        },\n        \"diskFormat\": {\n            \"description\": \"Output format of the module `Disk`. See Wiki for formatting syntax\\n    1. {size-used}: Size used\\n    2. {size-total}: Size total\\n    3. {size-percentage}: Size percentage num\\n    4. {files-used}: Files used\\n    5. {files-total}: Files total\\n    6. {files-percentage}: Files percentage num\\n    7. {is-external}: True if external volume\\n    8. {is-hidden}: True if hidden volume\\n    9. {filesystem}: Filesystem\\n    10. {name}: Label / name\\n    11. {is-readonly}: True if read-only\\n    12. {create-time}: Create time in local timezone\\n    13. {size-percentage-bar}: Size percentage bar\\n    14. {files-percentage-bar}: Files percentage bar\\n    15. {days}: Days after creation\\n    16. {hours}: Hours after creation\\n    17. {minutes}: Minutes after creation\\n    18. {seconds}: Seconds after creation\\n    19. {milliseconds}: Milliseconds after creation\\n    20. {mountpoint}: Mount point / drive letter\\n    21. {mount-from}: Mount from (device path)\\n    22. {years}: Years integer after creation\\n    23. {days-of-year}: Days of year after creation\\n    24. {years-fraction}: Years fraction after creation\\n    25. {size-free}: Size free\\n    26. {size-available}: Size available\",\n            \"type\": \"string\"\n        },\n        \"diskioFormat\": {\n            \"description\": \"Output format of the module `DiskIO`. See Wiki for formatting syntax\\n    1. {size-read}: Size of data read [per second] (formatted)\\n    2. {size-written}: Size of data written [per second] (formatted)\\n    3. {name}: Device name\\n    4. {dev-path}: Device raw file path\\n    5. {bytes-read}: Size of data read [per second] (in bytes)\\n    6. {bytes-written}: Size of data written [per second] (in bytes)\\n    7. {read-count}: Number of reads\\n    8. {write-count}: Number of writes\",\n            \"type\": \"string\"\n        },\n        \"dnsFormat\": {\n            \"description\": \"Output format of the module `DNS`. See Wiki for formatting syntax\\n    1. {result}: DNS result\",\n            \"type\": \"string\"\n        },\n        \"editorFormat\": {\n            \"description\": \"Output format of the module `Editor`. See Wiki for formatting syntax\\n    1. {type}: Type (Visual / Editor)\\n    2. {name}: Name\\n    3. {exe-name}: Exe name of real path\\n    4. {path}: Full path of real path\\n    5. {version}: Version\",\n            \"type\": \"string\"\n        },\n        \"fontFormat\": {\n            \"description\": \"Output format of the module `Font`. See Wiki for formatting syntax\\n    1. {font1}: Font 1\\n    2. {font2}: Font 2\\n    3. {font3}: Font 3\\n    4. {font4}: Font 4\\n    5. {combined}: Combined fonts for display\",\n            \"type\": \"string\"\n        },\n        \"gamepadFormat\": {\n            \"description\": \"Output format of the module `Gamepad`. See Wiki for formatting syntax\\n    1. {name}: Name\\n    2. {serial}: Serial number\\n    3. {battery-percentage}: Battery percentage num\\n    4. {battery-percentage-bar}: Battery percentage bar\",\n            \"type\": \"string\"\n        },\n        \"gpuFormat\": {\n            \"description\": \"Output format of the module `GPU`. See Wiki for formatting syntax\\n    1. {vendor}: GPU vendor\\n    2. {name}: GPU name\\n    3. {driver}: GPU driver\\n    4. {temperature}: GPU temperature\\n    5. {core-count}: GPU core count\\n    6. {type}: GPU type\\n    7. {dedicated-total}: GPU total dedicated memory\\n    8. {dedicated-used}: GPU used dedicated memory\\n    9. {shared-total}: GPU total shared memory\\n    10. {shared-used}: GPU used shared memory\\n    11. {platform-api}: The platform API used when detecting the GPU\\n    12. {frequency}: Current frequency in GHz\\n    13. {index}: GPU vendor specific index\\n    14. {dedicated-percentage-num}: Dedicated memory usage percentage num\\n    15. {dedicated-percentage-bar}: Dedicated memory usage percentage bar\\n    16. {shared-percentage-num}: Shared memory usage percentage num\\n    17. {shared-percentage-bar}: Shared memory usage percentage bar\\n    18. {core-usage-num}: Core usage percentage num\\n    19. {core-usage-bar}: Core usage percentage bar\\n    20. {memory-type}: Memory type (Windows only)\",\n            \"type\": \"string\"\n        },\n        \"hostFormat\": {\n            \"description\": \"Output format of the module `Host`. See Wiki for formatting syntax\\n    1. {family}: Product family\\n    2. {name}: Product name\\n    3. {version}: Product version\\n    4. {sku}: Product sku\\n    5. {vendor}: Product vendor\\n    6. {serial}: Product serial number\\n    7. {uuid}: Product uuid\",\n            \"type\": \"string\"\n        },\n        \"iconsFormat\": {\n            \"description\": \"Output format of the module `Icons`. See Wiki for formatting syntax\\n    1. {icons1}: Icons part 1\\n    2. {icons2}: Icons part 2\",\n            \"type\": \"string\"\n        },\n        \"initsystemFormat\": {\n            \"description\": \"Output format of the module `InitSystem`. See Wiki for formatting syntax\\n    1. {name}: Init system name\\n    2. {exe}: Init system exe path\\n    3. {version}: Init system version path\\n    4. {pid}: Init system pid\",\n            \"type\": \"string\"\n        },\n        \"kernelFormat\": {\n            \"description\": \"Output format of the module `Kernel`. See Wiki for formatting syntax\\n    1. {sysname}: Sysname\\n    2. {release}: Release\\n    3. {version}: Version\\n    4. {arch}: Architecture\\n    5. {display-version}: Display version\\n    6. {page-size}: Page size\",\n            \"type\": \"string\"\n        },\n        \"keyboardFormat\": {\n            \"description\": \"Output format of the module `Keyboard`. See Wiki for formatting syntax\\n    1. {name}: Name\\n    2. {serial}: Serial number\",\n            \"type\": \"string\"\n        },\n        \"lmFormat\": {\n            \"description\": \"Output format of the module `LM`. See Wiki for formatting syntax\\n    1. {service}: LM service\\n    2. {type}: LM type\\n    3. {version}: LM version\",\n            \"type\": \"string\"\n        },\n        \"loadavgFormat\": {\n            \"description\": \"Output format of the module `Loadavg`. See Wiki for formatting syntax\\n    1. {loadavg1}: Load average over 1min\\n    2. {loadavg2}: Load average over 5min\\n    3. {loadavg3}: Load average over 15min\",\n            \"type\": \"string\"\n        },\n        \"localeFormat\": {\n            \"description\": \"Output format of the module `Locale`. See Wiki for formatting syntax\\n    1. {result}: Locale code\",\n            \"type\": \"string\"\n        },\n        \"localipFormat\": {\n            \"description\": \"Output format of the module `LocalIp`. See Wiki for formatting syntax\\n    1. {ipv4}: IPv4 address\\n    2. {ipv6}: IPv6 address\\n    3. {mac}: MAC address\\n    4. {ifname}: Interface name\\n    5. {is-default-route}: Is default route\\n    6. {mtu}: MTU size in bytes\\n    7. {speed}: Link speed (formatted)\\n    8. {flags}: Interface flags\",\n            \"type\": \"string\"\n        },\n        \"mediaFormat\": {\n            \"description\": \"Output format of the module `Media`. See Wiki for formatting syntax\\n    1. {combined}: Pretty media name\\n    2. {title}: Media name\\n    3. {artist}: Artist name\\n    4. {album}: Album name\\n    5. {status}: Status\",\n            \"type\": \"string\"\n        },\n        \"memoryFormat\": {\n            \"description\": \"Output format of the module `Memory`. See Wiki for formatting syntax\\n    1. {used}: Used size\\n    2. {total}: Total size\\n    3. {percentage}: Percentage used (num)\\n    4. {percentage-bar}: Percentage used (bar)\",\n            \"type\": \"string\"\n        },\n        \"monitorFormat\": {\n            \"description\": \"Output format of the module `Monitor`. See Wiki for formatting syntax\\n    1. {name}: Display name\\n    2. {width}: Native resolution width in pixels\\n    3. {height}: Native resolution height in pixels\\n    4. {physical-width}: Physical width in millimeters\\n    5. {physical-height}: Physical height in millimeters\\n    6. {inch}: Physical diagonal length in inches\\n    7. {ppi}: Pixels per inch (PPI)\\n    8. {manufacture-year}: Year of manufacturing\\n    9. {manufacture-week}: Nth week of manufacturing in the year\\n    10. {serial}: Serial number\\n    11. {refresh-rate}: Maximum refresh rate in Hz\\n    12. {hdr-compatible}: True if the display is HDR compatible\",\n            \"type\": \"string\"\n        },\n        \"mouseFormat\": {\n            \"description\": \"Output format of the module `Mouse`. See Wiki for formatting syntax\\n    1. {name}: Mouse name\\n    2. {serial}: Mouse serial number\",\n            \"type\": \"string\"\n        },\n        \"netioFormat\": {\n            \"description\": \"Output format of the module `NetIO`. See Wiki for formatting syntax\\n    1. {rx-size}: Size of data received [per second] (formatted)\\n    2. {tx-size}: Size of data sent [per second] (formatted)\\n    3. {ifname}: Interface name\\n    4. {is-default-route}: Is default route\\n    5. {rx-bytes}: Size of data received [per second] (in bytes)\\n    6. {tx-bytes}: Size of data sent [per second] (in bytes)\\n    7. {rx-packets}: Number of packets received [per second]\\n    8. {tx-packets}: Number of packets sent [per second]\\n    9. {rx-errors}: Number of errors received [per second]\\n    10. {tx-errors}: Number of errors sent [per second]\\n    11. {rx-drops}: Number of packets dropped when receiving [per second]\\n    12. {tx-drops}: Number of packets dropped when sending [per second]\",\n            \"type\": \"string\"\n        },\n        \"openclFormat\": {\n            \"description\": \"Output format of the module `OpenCL`. See Wiki for formatting syntax\\n    1. {version}: Platform version\\n    2. {name}: Platform name\\n    3. {vendor}: Platform vendor\",\n            \"type\": \"string\"\n        },\n        \"openglFormat\": {\n            \"description\": \"Output format of the module `OpenGL`. See Wiki for formatting syntax\\n    1. {version}: OpenGL version\\n    2. {renderer}: OpenGL renderer\\n    3. {vendor}: OpenGL vendor\\n    4. {slv}: OpenGL shading language version\\n    5. {library}: OpenGL library used\",\n            \"type\": \"string\"\n        },\n        \"osFormat\": {\n            \"description\": \"Output format of the module `OS`. See Wiki for formatting syntax\\n    1. {sysname}: Name of the kernel\\n    2. {name}: Name of the OS\\n    3. {pretty-name}: Pretty name of the OS, if available\\n    4. {id}: ID of the OS\\n    5. {id-like}: ID like of the OS\\n    6. {variant}: Variant of the OS\\n    7. {variant-id}: Variant ID of the OS\\n    8. {version}: Version of the OS\\n    9. {version-id}: Version ID of the OS\\n    10. {codename}: Version codename of the OS\\n    11. {build-id}: Build ID of the OS\\n    12. {arch}: Architecture of the OS\",\n            \"type\": \"string\"\n        },\n        \"packagesFormat\": {\n            \"description\": \"Output format of the module `Packages`. See Wiki for formatting syntax\\n    1. {all}: Number of all packages\\n    2. {pacman}: Number of pacman packages\\n    3. {pacman-branch}: Pacman branch on manjaro\\n    4. {dpkg}: Number of dpkg packages\\n    5. {rpm}: Number of rpm packages\\n    6. {emerge}: Number of emerge packages\\n    7. {eopkg}: Number of eopkg packages\\n    8. {xbps}: Number of xbps packages\\n    9. {nix-system}: Number of nix-system packages\\n    10. {nix-user}: Number of nix-user packages\\n    11. {nix-default}: Number of nix-default packages\\n    12. {apk}: Number of apk packages\\n    13. {pkg}: Number of pkg packages\\n    14. {flatpak-system}: Number of flatpak-system app packages\\n    15. {flatpak-user}: Number of flatpak-user app packages\\n    16. {snap}: Number of snap packages\\n    17. {brew}: Number of brew packages\\n    18. {brew-cask}: Number of brew-cask packages\\n    19. {macports}: Number of macports packages\\n    20. {scoop-user}: Number of scoop-user packages\\n    21. {scoop-global}: Number of scoop-global packages\\n    22. {choco}: Number of choco packages\\n    23. {pkgtool}: Number of pkgtool packages\\n    24. {paludis}: Number of paludis packages\\n    25. {winget}: Number of winget packages\\n    26. {opkg}: Number of opkg packages\\n    27. {am-system}: Number of am-system packages\\n    28. {sorcery}: Number of sorcery packages\\n    29. {lpkg}: Number of lpkg packages\\n    30. {lpkgbuild}: Number of lpkgbuild packages\\n    31. {guix-system}: Number of guix-system packages\\n    32. {guix-user}: Number of guix-user packages\\n    33. {guix-home}: Number of guix-home packages\\n    34. {linglong}: Number of linglong packages\\n    35. {pacstall}: Number of pacstall packages\\n    36. {mport}: Number of mport packages\\n    37. {am-user}: Number of am-user (aka appman) packages\\n    38. {pkgsrc}: Number of pkgsrc packages\\n    39. {hpkg-system}: Number of hpkg-system packages\\n    40. {hpkg-user}: Number of hpkg-user packages\\n    41. {pisi}: Number of pisi packages\\n    42. {soar}: Number of soar packages\\n    43. {kiss}: Number of kiss packages\\n    44. {moss}: Number of moss packages\\n    45. {nix-all}: Total number of all nix packages\\n    46. {flatpak-all}: Total number of all flatpak app packages\\n    47. {brew-all}: Total number of all brew packages\\n    48. {guix-all}: Total number of all guix packages\\n    49. {hpkg-all}: Total number of all hpkg packages\",\n            \"type\": \"string\"\n        },\n        \"physicaldiskFormat\": {\n            \"description\": \"Output format of the module `PhysicalDisk`. See Wiki for formatting syntax\\n    1. {size}: Device size (formatted)\\n    2. {name}: Device name\\n    3. {interconnect}: Device interconnect type\\n    4. {dev-path}: Device raw file path\\n    5. {serial}: Serial number\\n    6. {physical-type}: Device kind (SSD or HDD)\\n    7. {removable-type}: Device kind (Removable or Fixed)\\n    8. {readonly-type}: Device kind (Read-only or Read-write)\\n    9. {revision}: Product revision\\n    10. {temperature}: Device temperature (formatted)\",\n            \"type\": \"string\"\n        },\n        \"physicalmemoryFormat\": {\n            \"description\": \"Output format of the module `PhysicalMemory`. See Wiki for formatting syntax\\n    1. {bytes}: Size (in bytes)\\n    2. {size}: Size formatted\\n    3. {max-speed}: Max speed (in MT/s)\\n    4. {running-speed}: Running speed (in MT/s)\\n    5. {type}: Type (DDR4, DDR5, etc.)\\n    6. {form-factor}: Form factor (SODIMM, DIMM, etc.)\\n    7. {locator}: Bank/Device Locator (BANK0/SIMM0, BANK0/SIMM1, etc.)\\n    8. {vendor}: Vendor\\n    9. {serial}: Serial number\\n    10. {part-number}: Part number\\n    11. {is-ecc-enabled}: True if ECC enabled\\n    12. {is-installed}: True if a memory module is installed in the slot\",\n            \"type\": \"string\"\n        },\n        \"playerFormat\": {\n            \"description\": \"Output format of the module `Player`. See Wiki for formatting syntax\\n    1. {player}: Pretty player name\\n    2. {name}: Player name\\n    3. {id}: Player Identifier\\n    4. {url}: URL name\",\n            \"type\": \"string\"\n        },\n        \"poweradapterFormat\": {\n            \"description\": \"Output format of the module `PowerAdapter`. See Wiki for formatting syntax\\n    1. {watts}: Power adapter watts\\n    2. {name}: Power adapter name\\n    3. {manufacturer}: Power adapter manufacturer\\n    4. {model}: Power adapter model\\n    5. {description}: Power adapter description\\n    6. {serial}: Power adapter serial number\",\n            \"type\": \"string\"\n        },\n        \"processesFormat\": {\n            \"description\": \"Output format of the module `Processes`. See Wiki for formatting syntax\\n    1. {result}: Process count\",\n            \"type\": \"string\"\n        },\n        \"publicipFormat\": {\n            \"description\": \"Output format of the module `PublicIp`. See Wiki for formatting syntax\\n    1. {ip}: Public IP address\\n    2. {location}: Location\",\n            \"type\": \"string\"\n        },\n        \"shellFormat\": {\n            \"description\": \"Output format of the module `Shell`. See Wiki for formatting syntax\\n    1. {process-name}: Shell process name\\n    2. {exe}: The first argument of the command line when running the shell\\n    3. {exe-name}: Shell base name of arg0\\n    4. {version}: Shell version\\n    5. {pid}: Shell pid\\n    6. {pretty-name}: Shell pretty name\\n    7. {exe-path}: Shell full exe path\\n    8. {tty}: Shell tty used\",\n            \"type\": \"string\"\n        },\n        \"soundFormat\": {\n            \"description\": \"Output format of the module `Sound`. See Wiki for formatting syntax\\n    1. {is-main}: Is main sound device\\n    2. {name}: Device name\\n    3. {volume-percentage}: Volume (in percentage num)\\n    4. {identifier}: Identifier\\n    5. {volume-percentage-bar}: Volume (in percentage bar)\\n    6. {platform-api}: Platform API used\",\n            \"type\": \"string\"\n        },\n        \"swapFormat\": {\n            \"description\": \"Output format of the module `Swap`. See Wiki for formatting syntax\\n    1. {used}: Used size\\n    2. {total}: Total size\\n    3. {percentage}: Percentage used (num)\\n    4. {percentage-bar}: Percentage used (bar)\\n    5. {name}: Name\",\n            \"type\": \"string\"\n        },\n        \"terminalFormat\": {\n            \"description\": \"Output format of the module `Terminal`. See Wiki for formatting syntax\\n    1. {process-name}: Terminal process name\\n    2. {exe}: The first argument of the command line when running the terminal\\n    3. {exe-name}: Terminal base name of arg0\\n    4. {pid}: Terminal pid\\n    5. {pretty-name}: Terminal pretty name\\n    6. {version}: Terminal version\\n    7. {exe-path}: Terminal full exe path\\n    8. {tty}: Terminal tty / pts used\",\n            \"type\": \"string\"\n        },\n        \"terminalfontFormat\": {\n            \"description\": \"Output format of the module `TerminalFont`. See Wiki for formatting syntax\\n    1. {combined}: Terminal font combined\\n    2. {name}: Terminal font name\\n    3. {size}: Terminal font size\\n    4. {styles}: Terminal font styles\",\n            \"type\": \"string\"\n        },\n        \"terminalsizeFormat\": {\n            \"description\": \"Output format of the module `TerminalSize`. See Wiki for formatting syntax\\n    1. {rows}: Terminal rows\\n    2. {columns}: Terminal columns\\n    3. {width}: Terminal width (in pixels)\\n    4. {height}: Terminal height (in pixels)\",\n            \"type\": \"string\"\n        },\n        \"terminalthemeFormat\": {\n            \"description\": \"Output format of the module `TerminalTheme`. See Wiki for formatting syntax\\n    1. {fg-color}: Terminal foreground color\\n    2. {fg-type}: Terminal foreground type (Dark / Light)\\n    3. {bg-color}: Terminal background color\\n    4. {bg-type}: Terminal background type (Dark / Light)\",\n            \"type\": \"string\"\n        },\n        \"titleFormat\": {\n            \"description\": \"Output format of the module `Title`. See Wiki for formatting syntax\\n    1. {user-name}: User name\\n    2. {host-name}: Host name\\n    3. {home-dir}: Home directory\\n    4. {exe-path}: Executable path of current process\\n    5. {user-shell}: User's default shell\\n    6. {user-name-colored}: User name (colored)\\n    7. {at-symbol-colored}: @ symbol (colored)\\n    8. {host-name-colored}: Host name (colored)\\n    9. {full-user-name}: Full user name\\n    10. {user-id}: UID (*nix) / SID (Windows)\\n    11. {pid}: PID of current process\\n    12. {cwd}: CWD with home dir replaced by `~`\",\n            \"type\": \"string\"\n        },\n        \"themeFormat\": {\n            \"description\": \"Output format of the module `Theme`. See Wiki for formatting syntax\\n    1. {theme1}: Theme part 1\\n    2. {theme2}: Theme part 2\",\n            \"type\": \"string\"\n        },\n        \"tpmFormat\": {\n            \"description\": \"Output format of the module `TPM`. See Wiki for formatting syntax\\n    1. {version}: TPM device version\\n    2. {description}: TPM general description\",\n            \"type\": \"string\"\n        },\n        \"uptimeFormat\": {\n            \"description\": \"Output format of the module `Uptime`. See Wiki for formatting syntax\\n    1. {days}: Days after boot\\n    2. {hours}: Hours after boot\\n    3. {minutes}: Minutes after boot\\n    4. {seconds}: Seconds after boot\\n    5. {milliseconds}: Milliseconds after boot\\n    6. {boot-time}: Boot time in local timezone\\n    7. {years}: Years integer after boot\\n    8. {days-of-year}: Days of year after boot\\n    9. {years-fraction}: Years fraction after boot\\n    10. {formatted}: Formatted uptime\",\n            \"type\": \"string\"\n        },\n        \"usersFormat\": {\n            \"description\": \"Output format of the module `Users`. See Wiki for formatting syntax\\n    1. {name}: User name\\n    2. {host-name}: Host name\\n    3. {session-name}: Session name\\n    4. {client-ip}: Client IP\\n    5. {login-time}: Login Time in local timezone\\n    6. {days}: Days after login\\n    7. {hours}: Hours after login\\n    8. {minutes}: Minutes after login\\n    9. {seconds}: Seconds after login\\n    10. {milliseconds}: Milliseconds after login\\n    11. {years}: Years integer after login\\n    12. {days-of-year}: Days of year after login\\n    13. {years-fraction}: Years fraction after login\",\n            \"type\": \"string\"\n        },\n        \"versionFormat\": {\n            \"description\": \"Output format of the module `Version`. See Wiki for formatting syntax\\n    1. {project-name}: Project name\\n    2. {version}: Version\\n    3. {version-tweak}: Version tweak\\n    4. {build-type}: Build type (debug or release)\\n    5. {sysname}: System name\\n    6. {arch}: Architecture\\n    7. {cmake-built-type}: CMake build type when compiling (Debug, Release, RelWithDebInfo, MinSizeRel)\\n    8. {compile-time}: Date time when compiling\\n    9. {compiler}: Compiler used when compiling\\n    10. {libc}: Libc used when compiling\",\n            \"type\": \"string\"\n        },\n        \"vulkanFormat\": {\n            \"description\": \"Output format of the module `Vulkan`. See Wiki for formatting syntax\\n    1. {driver}: Driver name\\n    2. {api-version}: API version\\n    3. {conformance-version}: Conformance version\\n    4. {instance-version}: Instance version\",\n            \"type\": \"string\"\n        },\n        \"wallpaperFormat\": {\n            \"description\": \"Output format of the module `Wallpaper`. See Wiki for formatting syntax\\n    1. {file-name}: File name\\n    2. {full-path}: Full path\",\n            \"type\": \"string\"\n        },\n        \"weatherFormat\": {\n            \"description\": \"Output format of the module `Weather`. See Wiki for formatting syntax\\n    1. {result}: Weather result\",\n            \"type\": \"string\"\n        },\n        \"wmFormat\": {\n            \"description\": \"Output format of the module `WM`. See Wiki for formatting syntax\\n    1. {process-name}: WM process name\\n    2. {pretty-name}: WM pretty name\\n    3. {protocol-name}: WM protocol name\\n    4. {plugin-name}: WM plugin name\\n    5. {version}: WM version\",\n            \"type\": \"string\"\n        },\n        \"wifiFormat\": {\n            \"description\": \"Output format of the module `Wifi`. See Wiki for formatting syntax\\n    1. {inf-desc}: Interface description\\n    2. {inf-status}: Interface status\\n    3. {status}: Connection status\\n    4. {ssid}: Connection SSID\\n    5. {bssid}: Connection BSSID\\n    6. {protocol}: Connection protocol\\n    7. {signal-quality}: Connection signal quality (percentage num)\\n    8. {rx-rate}: Connection RX rate\\n    9. {tx-rate}: Connection TX rate\\n    10. {security}: Connection Security algorithm\\n    11. {signal-quality-bar}: Connection signal quality (percentage bar)\\n    12. {channel}: Connection channel number\\n    13. {band}: Connection channel band in GHz\",\n            \"type\": \"string\"\n        },\n        \"wmthemeFormat\": {\n            \"description\": \"Output format of the module `WMTheme`. See Wiki for formatting syntax\\n    1. {result}: WM theme\",\n            \"type\": \"string\"\n        },\n        \"zpoolFormat\": {\n            \"description\": \"Output format of the module `Zpool`. See Wiki for formatting syntax\\n    1. {name}: Zpool name\\n    2. {guid}: Zpool guid\\n    3. {state}: Zpool state\\n    4. {used}: Size used\\n    5. {allocated}: Size allocated\\n    6. {total}: Size total\\n    7. {used-percentage}: Size used percentage num\\n    8. {allocated-percentage}: Size allocated percentage num\\n    9. {fragmentation-percentage}: Fragmentation percentage num\\n    10. {used-percentage-bar}: Size used percentage bar\\n    11. {allocated-percentage-bar}: Size allocated percentage bar\\n    12. {fragmentation-percentage-bar}: Fragmentation percentage bar\\n    13. {is-readonly}: Is read-only\",\n            \"type\": \"string\"\n        }\n    },\n    \"type\": \"object\",\n    \"additionalProperties\": false,\n    \"title\": \"JSON config\",\n    \"description\": \"JSON config file for fastfetch. Usually located at `~/.config/fastfetch/config.jsonc`\",\n    \"properties\": {\n        \"$schema\": {\n            \"type\": \"string\",\n            \"description\": \"JSON schema URL, for JSON validation and IDE intelligence\",\n            \"format\": \"uri\",\n            \"default\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\"\n        },\n        \"logo\": {\n            \"description\": \"Fastfetch logo configurations\\nSee also https://github.com/fastfetch-cli/fastfetch/wiki/Logo-options\",\n            \"oneOf\": [\n                {\n                    \"description\": \"Disable logo\",\n                    \"type\": \"null\",\n                    \"const\": null\n                },\n                {\n                    \"description\": \"Set the source file of the logo or built-in ASCII art name\",\n                    \"type\": \"string\"\n                },\n                {\n                    \"description\": \"Fastfetch logo configurations\",\n                    \"type\": \"object\",\n                    \"additionalProperties\": false,\n                    \"properties\": {\n                        \"type\": {\n                            \"description\": \"Set the type of the logo\",\n                            \"oneOf\": [\n                                {\n                                    \"const\": \"auto\",\n                                    \"description\": \"If something is given, first try built-in, then file. Otherwise detect logo\"\n                                },\n                                {\n                                    \"const\": \"builtin\",\n                                    \"description\": \"Built-in ASCII art\"\n                                },\n                                {\n                                    \"const\": \"small\",\n                                    \"description\": \"Built-in ASCII art, small version (not all logos support this option)\"\n                                },\n                                {\n                                    \"const\": \"file\",\n                                    \"description\": \"Text file, printed with color code replacement\"\n                                },\n                                {\n                                    \"const\": \"file-raw\",\n                                    \"description\": \"Text file, printed as is\"\n                                },\n                                {\n                                    \"const\": \"data\",\n                                    \"description\": \"Text data, printed with color code replacement\"\n                                },\n                                {\n                                    \"const\": \"data-raw\",\n                                    \"description\": \"Text data, printed as is\"\n                                },\n                                {\n                                    \"const\": \"sixel\",\n                                    \"description\": \"Image file, printed as sixel codes\"\n                                },\n                                {\n                                    \"const\": \"kitty\",\n                                    \"description\": \"Image file, printed using kitty graphics protocol\"\n                                },\n                                {\n                                    \"const\": \"kitty-direct\",\n                                    \"description\": \"Image file, tells the terminal emulator to read image data from the specified file\"\n                                },\n                                {\n                                    \"const\": \"kitty-icat\",\n                                    \"description\": \"Image file, uses `kitten icat` to display the image. Requires binary `kitten` to be installed\"\n                                },\n                                {\n                                    \"const\": \"iterm\",\n                                    \"description\": \"Image file, uses `iTerm` image protocol\"\n                                },\n                                {\n                                    \"const\": \"chafa\",\n                                    \"description\": \"Image file, prints `ASCII` art image generated by `libchafa`\"\n                                },\n                                {\n                                    \"const\": \"raw\",\n                                    \"description\": \"Image file, printed as `raw` binary string image\"\n                                },\n                                {\n                                    \"const\": \"none\",\n                                    \"description\": \"Disable logo printing\"\n                                }\n                            ],\n                            \"default\": \"auto\"\n                        },\n                        \"source\": {\n                            \"type\": \"string\",\n                            \"description\": \"Set the source file of the logo\"\n                        },\n                        \"color\": {\n                            \"type\": \"object\",\n                            \"additionalProperties\": false,\n                            \"description\": \"Override colors in the logo\",\n                            \"properties\": {\n                                \"1\": {\n                                    \"description\": \"Color 1\",\n                                    \"$ref\": \"#/$defs/colors\"\n                                },\n                                \"2\": {\n                                    \"description\": \"Color 2\",\n                                    \"$ref\": \"#/$defs/colors\"\n                                },\n                                \"3\": {\n                                    \"description\": \"Color 3\",\n                                    \"$ref\": \"#/$defs/colors\"\n                                },\n                                \"4\": {\n                                    \"description\": \"Color 4\",\n                                    \"$ref\": \"#/$defs/colors\"\n                                },\n                                \"5\": {\n                                    \"description\": \"Color 5\",\n                                    \"$ref\": \"#/$defs/colors\"\n                                },\n                                \"6\": {\n                                    \"description\": \"Color 6\",\n                                    \"$ref\": \"#/$defs/colors\"\n                                },\n                                \"7\": {\n                                    \"description\": \"Color 7\",\n                                    \"$ref\": \"#/$defs/colors\"\n                                },\n                                \"8\": {\n                                    \"description\": \"Color 8\",\n                                    \"$ref\": \"#/$defs/colors\"\n                                },\n                                \"9\": {\n                                    \"description\": \"Color 9\",\n                                    \"$ref\": \"#/$defs/colors\"\n                                }\n                            }\n                        },\n                        \"width\": {\n                            \"oneOf\": [\n                                {\n                                    \"type\": \"null\",\n                                    \"description\": \"Auto detect width (default)\"\n                                },\n                                {\n                                    \"type\": \"integer\",\n                                    \"description\": \"Set the width of the logo (in characters). Required for some image protocols\",\n                                    \"minimum\": 1\n                                }\n                            ]\n                        },\n                        \"height\": {\n                            \"oneOf\": [\n                                {\n                                    \"type\": \"null\",\n                                    \"description\": \"Auto detect width (default)\"\n                                },\n                                {\n                                    \"type\": \"integer\",\n                                    \"description\": \"Set the height of the logo (in characters). Required for some image protocols\",\n                                    \"minimum\": 1\n                                }\n                            ]\n                        },\n                        \"padding\": {\n                            \"type\": \"object\",\n                            \"additionalProperties\": false,\n                            \"description\": \"Set the padding of the logo\",\n                            \"properties\": {\n                                \"top\": {\n                                    \"type\": \"integer\",\n                                    \"description\": \"Set the top padding of the logo\",\n                                    \"minimum\": 0\n                                },\n                                \"left\": {\n                                    \"type\": \"integer\",\n                                    \"description\": \"Set the left padding of the logo\",\n                                    \"minimum\": 0\n                                },\n                                \"right\": {\n                                    \"type\": \"integer\",\n                                    \"description\": \"Set the right padding of the logo\",\n                                    \"minimum\": 0\n                                }\n                            }\n                        },\n                        \"printRemaining\": {\n                            \"type\": \"boolean\",\n                            \"description\": \"Whether to print the remaining logo if it has more lines than modules to display\",\n                            \"default\": true\n                        },\n                        \"preserveAspectRatio\": {\n                            \"type\": \"boolean\",\n                            \"description\": \"Whether to preserve the aspect ratio of the logo. Supported by iTerm image protocol only\",\n                            \"default\": false\n                        },\n                        \"recache\": {\n                            \"type\": \"boolean\",\n                            \"description\": \"If true, regenerate image logo cache\",\n                            \"default\": false\n                        },\n                        \"position\": {\n                            \"type\": \"string\",\n                            \"description\": \"Set the position where the logo should be displayed\",\n                            \"enum\": [\n                                \"left\",\n                                \"top\",\n                                \"right\"\n                            ],\n                            \"default\": \"left\"\n                        },\n                        \"chafa\": {\n                            \"type\": \"object\",\n                            \"additionalProperties\": false,\n                            \"description\": \"Chafa configuration. See chafa documentation for details\",\n                            \"properties\": {\n                                \"fgOnly\": {\n                                    \"type\": \"boolean\",\n                                    \"description\": \"Produce character-cell output using foreground colors only\",\n                                    \"default\": false\n                                },\n                                \"symbols\": {\n                                    \"type\": \"string\",\n                                    \"description\": \"Specify character symbols to employ in final output\"\n                                },\n                                \"canvasMode\": {\n                                    \"type\": \"string\",\n                                    \"description\": \"Determine how colors are used in the output. This value maps to enum ChafaCanvasMode.\",\n                                    \"oneOf\": [\n                                        {\n                                            \"const\": \"TRUECOLOR\",\n                                            \"description\": \"Use 24-bit true colors\"\n                                        },\n                                        {\n                                            \"const\": \"INDEXED_256\",\n                                            \"description\": \"Use 256 colors\"\n                                        },\n                                        {\n                                            \"const\": \"INDEXED_240\",\n                                            \"description\": \"Use 240 colors, but avoid using the lower 16 whose values vary between terminal environments\"\n                                        },\n                                        {\n                                            \"const\": \"INDEXED_16\",\n                                            \"description\": \"Use 16 colors using the aixterm ANSI extension\"\n                                        },\n                                        {\n                                            \"const\": \"FGBG_BGFG\",\n                                            \"description\": \"Use default foreground and background colors, plus inversion\"\n                                        },\n                                        {\n                                            \"const\": \"FGBG\",\n                                            \"description\": \"Use default foreground and background colors. No ANSI codes will be used\"\n                                        },\n                                        {\n                                            \"const\": \"INDEXED_8\",\n                                            \"description\": \"Use 8 colors, compatible with original ANSI X3.64\"\n                                        },\n                                        {\n                                            \"const\": \"INDEXED_16_8\",\n                                            \"description\": \"Use 16 FG colors (8 of which enabled with bold/bright) and 8 BG colors\"\n                                        }\n                                    ]\n                                },\n                                \"colorSpace\": {\n                                    \"type\": \"string\",\n                                    \"description\": \"Set color space used for quantization. This value maps to enum ChafaColorSpace.\",\n                                    \"oneOf\": [\n                                        {\n                                            \"const\": \"RGB\",\n                                            \"description\": \"RGB color space. Fast but imprecise\"\n                                        },\n                                        {\n                                            \"const\": \"DIN99D\",\n                                            \"description\": \"DIN99d color space. Slower, but good perceptual color precision\"\n                                        }\n                                    ]\n                                },\n                                \"ditherMode\": {\n                                    \"type\": \"string\",\n                                    \"description\": \"Set output dither mode (No effect with 24-bit color). This value maps to enum ChafaDitherMode.\",\n                                    \"oneOf\": [\n                                        {\n                                            \"const\": \"NONE\",\n                                            \"description\": \"No dithering\"\n                                        },\n                                        {\n                                            \"const\": \"ORDERED\",\n                                            \"description\": \"Ordered dithering (Bayer or similar)\"\n                                        },\n                                        {\n                                            \"const\": \"DIFFUSION\",\n                                            \"description\": \"Error diffusion dithering (Floyd-Steinberg or similar)\"\n                                        }\n                                    ]\n                                }\n                            }\n                        }\n                    }\n                }\n            ]\n        },\n        \"general\": {\n            \"description\": \"Fastfetch general configurations\",\n            \"type\": \"object\",\n            \"additionalProperties\": false,\n            \"properties\": {\n                \"thread\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Use separate threads for HTTP requests\",\n                    \"default\": true\n                },\n                \"escapeBedrock\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"On Bedrock Linux, whether to escape the bedrock jail\",\n                    \"default\": true\n                },\n                \"playerName\": {\n                    \"type\": \"string\",\n                    \"description\": \"The name of the player to use for Media and Player modules. Linux only\"\n                },\n                \"dsForceDrm\": {\n                    \"description\": \"Force display detection to use DRM. Linux only\",\n                    \"oneOf\": [\n                        {\n                            \"type\": \"boolean\",\n                            \"const\": false,\n                            \"description\": \"Try `wayland`, then `x11`, then `drm`\"\n                        },\n                        {\n                            \"type\": \"string\",\n                            \"description\": \"Use `/sys/class/drm` only\",\n                            \"const\": \"sysfs-only\"\n                        },\n                        {\n                            \"type\": \"boolean\",\n                            \"const\": true,\n                            \"description\": \"Try `libdrm` first, then `sysfs` if libdrm fails\"\n                        }\n                    ],\n                    \"default\": false\n                },\n                \"wmiTimeout\": {\n                    \"type\": \"integer\",\n                    \"description\": \"Set the timeout (ms) for WMI queries, `-1` for no timeout. Windows only\",\n                    \"default\": 5000\n                },\n                \"processingTimeout\": {\n                    \"type\": \"integer\",\n                    \"description\": \"Set the timeout (ms) when waiting for child processes, `-1` for no timeout\",\n                    \"default\": 5000\n                },\n                \"preRun\": {\n                    \"type\": \"string\",\n                    \"description\": \"Set the command to be executed before printing logos\",\n                    \"default\": \"\"\n                },\n                \"detectVersion\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Whether to detect and display component versions. Mainly for benchmarking\",\n                    \"default\": true\n                }\n            }\n        },\n        \"display\": {\n            \"description\": \"Configure how things should be displayed\",\n            \"type\": \"object\",\n            \"additionalProperties\": false,\n            \"properties\": {\n                \"stat\": {\n                    \"description\": \"Show time usage (in ms) for individual modules with optional threshold\",\n                    \"oneOf\": [\n                        {\n                            \"type\": \"boolean\",\n                            \"default\": false\n                        },\n                        {\n                            \"type\": \"integer\",\n                            \"minimum\": 1\n                        }\n                    ]\n                },\n                \"pipe\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Whether to disable colors (auto-detected based on isatty(1) by default)\",\n                    \"default\": false\n                },\n                \"showErrors\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Print occurring errors to the console. False to ignore errored modules\",\n                    \"default\": false\n                },\n                \"disableLinewrap\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Whether to disable line wrap during execution\",\n                    \"default\": true\n                },\n                \"hideCursor\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Whether to hide the cursor during execution\",\n                    \"default\": true\n                },\n                \"separator\": {\n                    \"type\": \"string\",\n                    \"description\": \"Set the separator between key and value\",\n                    \"default\": \": \"\n                },\n                \"color\": {\n                    \"description\": \"Set the color of the keys and title\",\n                    \"oneOf\": [\n                        {\n                            \"description\": \"Set both the colors of keys and title\",\n                            \"$ref\": \"#/$defs/colors\"\n                        },\n                        {\n                            \"type\": \"object\",\n                            \"additionalProperties\": false,\n                            \"properties\": {\n                                \"keys\": {\n                                    \"description\": \"Set the color of the keys\",\n                                    \"$ref\": \"#/$defs/colors\"\n                                },\n                                \"title\": {\n                                    \"description\": \"Set the color of the title\",\n                                    \"$ref\": \"#/$defs/colors\"\n                                },\n                                \"output\": {\n                                    \"description\": \"Set the color of the module output\",\n                                    \"$ref\": \"#/$defs/colors\"\n                                },\n                                \"separator\": {\n                                    \"description\": \"Set the color of the key-value separator\",\n                                    \"$ref\": \"#/$defs/colors\"\n                                }\n                            }\n                        }\n                    ]\n                },\n                \"brightColor\": {\n                    \"description\": \"Set if the keys, title and ASCII logo should be printed in bright color\",\n                    \"type\": \"boolean\",\n                    \"default\": true\n                },\n                \"key\": {\n                    \"type\": \"object\",\n                    \"additionalProperties\": false,\n                    \"description\": \"Set how module keys should be displayed\",\n                    \"properties\": {\n                        \"width\": {\n                            \"description\": \"Align the width of keys to number of characters, 0 to disable\",\n                            \"type\": \"integer\",\n                            \"minimum\": 0,\n                            \"default\": 0\n                        },\n                        \"type\": {\n                            \"type\": \"string\",\n                            \"description\": \"Set whether to show builtin icon before string keys\",\n                            \"oneOf\": [\n                                {\n                                    \"const\": \"none\",\n                                    \"description\": \"Disable keys\"\n                                },\n                                {\n                                    \"const\": \"string\",\n                                    \"description\": \"Show string keys\"\n                                },\n                                {\n                                    \"const\": \"icon\",\n                                    \"description\": \"Show builtin icon (requires newest nerd font)\"\n                                },\n                                {\n                                    \"const\": \"both\",\n                                    \"description\": \"Show both icon and string keys (alias of `both-1`)\"\n                                },\n                                {\n                                    \"const\": \"both-0\",\n                                    \"description\": \"Show both icon and string with no spaces between them\"\n                                },\n                                {\n                                    \"const\": \"both-1\",\n                                    \"description\": \"Show both icon and string with a space between them\"\n                                },\n                                {\n                                    \"const\": \"both-2\",\n                                    \"description\": \"Show both icon and string with 2 spaces between them\"\n                                },\n                                {\n                                    \"const\": \"both-3\",\n                                    \"description\": \"Show both icon and string with 3 spaces between them\"\n                                },\n                                {\n                                    \"const\": \"both-4\",\n                                    \"description\": \"Show both icon and string with 4 spaces between them\"\n                                }\n                            ],\n                            \"default\": \"string\"\n                        },\n                        \"paddingLeft\": {\n                            \"type\": \"integer\",\n                            \"description\": \"Set the left padding of keys\",\n                            \"minimum\": 0,\n                            \"default\": 0\n                        }\n                    }\n                },\n                \"size\": {\n                    \"type\": \"object\",\n                    \"additionalProperties\": false,\n                    \"description\": \"Set how size values should be displayed\",\n                    \"properties\": {\n                        \"binaryPrefix\": {\n                            \"type\": \"string\",\n                            \"description\": \"Set the binary prefix to use when formatting sizes\",\n                            \"oneOf\": [\n                                {\n                                    \"const\": \"iec\",\n                                    \"description\": \"1024 Bytes = 1 KiB, 1024 KiB = 1 MiB, ... (standard)\"\n                                },\n                                {\n                                    \"const\": \"si\",\n                                    \"description\": \"1000 Bytes = 1 kB, 1000 kB = 1 MB, ...\"\n                                },\n                                {\n                                    \"const\": \"jedec\",\n                                    \"description\": \"1024 Bytes = 1 KB, 1024 KB = 1 MB, ...\"\n                                }\n                            ],\n                            \"default\": \"iec\"\n                        },\n                        \"maxPrefix\": {\n                            \"type\": \"string\",\n                            \"description\": \"Set the largest binary prefix to use when formatting sizes\",\n                            \"enum\": [\"B\", \"kB\", \"MB\", \"GB\", \"TB\", \"PB\", \"EB\", \"ZB\", \"YB\"],\n                            \"default\": \"YB\"\n                        },\n                        \"ndigits\": {\n                            \"type\": \"integer\",\n                            \"description\": \"Set the number of digits to keep after the decimal point when formatting sizes\",\n                            \"minimum\": 0,\n                            \"maximum\": 9,\n                            \"default\": 2\n                        },\n                        \"spaceBeforeUnit\": {\n                            \"$ref\": \"#/$defs/spaceBeforeUnit\"\n                        }\n                    }\n                },\n                \"temp\": {\n                    \"type\": \"object\",\n                    \"additionalProperties\": false,\n                    \"description\": \"Set how temperature values should be displayed\",\n                    \"properties\": {\n                        \"unit\": {\n                            \"type\": \"string\",\n                            \"description\": \"Set the unit of temperature\",\n                            \"oneOf\": [\n                                {\n                                    \"const\": \"C\",\n                                    \"description\": \"Celsius\"\n                                },\n                                {\n                                    \"const\": \"F\",\n                                    \"description\": \"Fahrenheit\"\n                                },\n                                {\n                                    \"const\": \"K\",\n                                    \"description\": \"Kelvin\"\n                                },\n                                {\n                                    \"const\": \"D\",\n                                    \"description\": \"Default (alias for Celsius)\"\n                                }\n                            ],\n                            \"default\": \"D\"\n                        },\n                        \"ndigits\": {\n                            \"type\": \"integer\",\n                            \"description\": \"Set the number of digits to keep after the decimal point when formatting temperature values\",\n                            \"minimum\": 0,\n                            \"maximum\": 9,\n                            \"default\": 1\n                        },\n                        \"color\": {\n                            \"type\": \"object\",\n                            \"additionalProperties\": false,\n                            \"description\": \"Set colors used in different states of temperature values\",\n                            \"properties\": {\n                                \"green\": {\n                                    \"description\": \"Color used in green state\",\n                                    \"$ref\": \"#/$defs/colors\",\n                                    \"default\": \"green\"\n                                },\n                                \"yellow\": {\n                                    \"description\": \"Color used in yellow state\",\n                                    \"$ref\": \"#/$defs/colors\",\n                                    \"default\": \"light_yellow\"\n                                },\n                                \"red\": {\n                                    \"description\": \"Color used in red state\",\n                                    \"$ref\": \"#/$defs/colors\",\n                                    \"default\": \"light_red\"\n                                }\n                            }\n                        },\n                        \"spaceBeforeUnit\": {\n                            \"$ref\": \"#/$defs/spaceBeforeUnit\"\n                        }\n                    }\n                },\n                \"bar\": {\n                    \"type\": \"object\",\n                    \"additionalProperties\": false,\n                    \"description\": \"Set the bar configuration\",\n                    \"properties\": {\n                        \"char\": {\n                            \"type\": \"object\",\n                            \"additionalProperties\": false,\n                            \"description\": \"Set the characters used in the bar\",\n                            \"properties\": {\n                                \"elapsed\": {\n                                    \"type\": \"string\",\n                                    \"description\": \"Set the character to use in elapsed part\",\n                                    \"default\": \"■\"\n                                },\n                                \"total\": {\n                                    \"type\": \"string\",\n                                    \"description\": \"Set the character to use in total part\",\n                                    \"default\": \"-\"\n                                }\n                            }\n                        },\n                        \"border\": {\n                            \"oneOf\": [\n                                {\n                                    \"type\": \"null\",\n                                    \"description\": \"Disable bar borders\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"additionalProperties\": false,\n                                    \"description\": \"Set the string to use of borders of percentage bars\",\n                                    \"properties\": {\n                                        \"left\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Set the string to use at left border\",\n                                            \"default\": \"[ \"\n                                        },\n                                        \"right\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Set the string to use at right border\",\n                                            \"default\": \" ]\"\n                                        },\n                                        \"leftElapsed\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"If both leftElapsed and rightElapsed are set, the border will be used as parts of bar content\",\n                                            \"default\": \"\"\n                                        },\n                                        \"rightElapsed\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"If both leftElapsed and rightElapsed are set, the border will be used as parts of bar content\",\n                                            \"default\": \"\"\n                                        }\n                                    }\n                                }\n                            ]\n                        },\n                        \"color\": {\n                            \"oneOf\": [\n                                {\n                                    \"type\": \"null\",\n                                    \"description\": \"Disable color in percentage bars\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"additionalProperties\": false,\n                                    \"description\": \"Set the color to use of percentage bars\",\n                                    \"properties\": {\n                                        \"elapsed\": {\n                                            \"description\": \"Color to use in the elapsed part of percentage bars\\nBy default, auto selected by percent.color.{green,yellow,red}\",\n                                            \"$ref\": \"#/$defs/colors\",\n                                            \"default\": \"auto\"\n                                        },\n                                        \"total\": {\n                                            \"description\": \"Color to use in the total part of percentage bars\",\n                                            \"$ref\": \"#/$defs/colors\",\n                                            \"default\": \"light_white\"\n                                        },\n                                        \"border\": {\n                                            \"description\": \"Color to use in the borders of percentage bars\",\n                                            \"$ref\": \"#/$defs/colors\",\n                                            \"default\": \"light_white\"\n                                        }\n                                    }\n                                }\n                            ]\n                        },\n                        \"width\": {\n                            \"type\": \"integer\",\n                            \"description\": \"Set the width of the bar, in number of characters\",\n                            \"minimum\": 1,\n                            \"default\": 10\n                        }\n                    }\n                },\n                \"percent\": {\n                    \"type\": \"object\",\n                    \"additionalProperties\": false,\n                    \"description\": \"Set how percentage values should be displayed\",\n                    \"properties\": {\n                        \"type\": {\n                            \"$ref\": \"#/$defs/percentType\"\n                        },\n                        \"ndigits\": {\n                            \"type\": \"number\",\n                            \"description\": \"Set the number of digits to keep after the decimal point when formatting percentage numbers\",\n                            \"minimum\": 0,\n                            \"maximum\": 9,\n                            \"default\": 0\n                        },\n                        \"color\": {\n                            \"type\": \"object\",\n                            \"additionalProperties\": false,\n                            \"description\": \"Set colors used in different states of percentage bars and numbers\",\n                            \"properties\": {\n                                \"green\": {\n                                    \"description\": \"Color used in green state\",\n                                    \"$ref\": \"#/$defs/colors\",\n                                    \"default\": \"green\"\n                                },\n                                \"yellow\": {\n                                    \"description\": \"Color used in yellow state\",\n                                    \"$ref\": \"#/$defs/colors\",\n                                    \"default\": \"light_yellow\"\n                                },\n                                \"red\": {\n                                    \"description\": \"Color used in red state\",\n                                    \"$ref\": \"#/$defs/colors\",\n                                    \"default\": \"light_red\"\n                                }\n                            }\n                        },\n                        \"spaceBeforeUnit\": {\n                            \"$ref\": \"#/$defs/spaceBeforeUnit\"\n                        },\n                        \"width\": {\n                            \"type\": \"integer\",\n                            \"description\": \"Set the width of the percentage number, in number of characters\",\n                            \"minimum\": 0,\n                            \"default\": 0\n                        }\n                    }\n                },\n                \"freq\": {\n                    \"type\": \"object\",\n                    \"additionalProperties\": false,\n                    \"description\": \"Set how frequency values should be displayed\",\n                    \"properties\": {\n                        \"ndigits\": {\n                            \"description\": \"Set the number of decimal places to display when formatting frequency values\",\n                            \"oneOf\": [\n                                {\n                                    \"type\": \"integer\",\n                                    \"minimum\": 0,\n                                    \"maximum\": 9,\n                                    \"description\": \"Integer value displays the frequency in GHz with specified decimal places\"\n                                },\n                                {\n                                    \"type\": \"null\",\n                                    \"description\": \"Null value display the frequency as integer MHz\"\n                                }\n                            ],\n                            \"default\": 2\n                        },\n                        \"spaceBeforeUnit\": {\n                            \"$ref\": \"#/$defs/spaceBeforeUnit\"\n                        }\n                    }\n                },\n                \"duration\": {\n                    \"type\": \"object\",\n                    \"description\": \"Set how duration values should be displayed\",\n                    \"properties\": {\n                        \"abbreviation\": {\n                            \"type\": \"boolean\",\n                            \"description\": \"Set whether to abbreviate duration values\\nIf true, the output will be in the form of \\\"1h 2m\\\" instead of \\\"1 hour, 2 mins\\\"\",\n                            \"default\": false\n                        },\n                        \"spaceBeforeUnit\": {\n                            \"$ref\": \"#/$defs/spaceBeforeUnit\"\n                        }\n                    }\n                },\n                \"fraction\": {\n                    \"type\": \"object\",\n                    \"additionalProperties\": false,\n                    \"description\": \"Set how ordinary fraction numbers should be displayed\",\n                    \"properties\": {\n                        \"ndigits\": {\n                            \"oneOf\": [\n                                {\n                                    \"type\": \"number\",\n                                    \"description\": \"Set the number of digits to keep after the decimal point when formatting ordinary fraction numbers\",\n                                    \"minimum\": 0,\n                                    \"maximum\": 9\n                                },\n                                {\n                                    \"type\": \"null\",\n                                    \"description\": \"The number of digits will be automatically determined based on the value\"\n                                }\n                            ],\n                            \"default\": 2\n                        },\n                        \"trailingZeros\": {\n                            \"description\": \"Set when to keep trailing zeros\",\n                            \"oneOf\": [\n                                {\n                                    \"type\": \"null\",\n                                    \"description\": \"Same as `default`\"\n                                },\n                                {\n                                    \"const\": \"default\",\n                                    \"description\": \"Use the behavior defined internally\"\n                                },\n                                {\n                                    \"const\": \"always\",\n                                    \"description\": \"Always keep trailing zeros\"\n                                },\n                                {\n                                    \"const\": \"never\",\n                                    \"description\": \"Never keep trailing zeros\"\n                                }\n                            ],\n                            \"default\": null\n                        }\n                    }\n                },\n                \"noBuffer\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Whether to disable the stdout application buffer\",\n                    \"default\": false\n                },\n                \"constants\": {\n                    \"type\": \"array\",\n                    \"description\": \"List of strings to be used in custom format of modules\",\n                    \"items\": {\n                        \"type\": \"string\"\n                    }\n                }\n            }\n        },\n        \"modules\": {\n            \"description\": \"Fastfetch modules to run\",\n            \"type\": \"array\",\n            \"items\": {\n                \"anyOf\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"Run module with default configurations\",\n                        \"enum\": [\n                            \"battery\",\n                            \"bios\",\n                            \"bluetooth\",\n                            \"bluetoothradio\",\n                            \"board\",\n                            \"bootmgr\",\n                            \"break\",\n                            \"brightness\",\n                            \"btrfs\",\n                            \"camera\",\n                            \"chassis\",\n                            \"cpu\",\n                            \"cpucache\",\n                            \"cpuusage\",\n                            \"command\",\n                            \"colors\",\n                            \"cursor\",\n                            \"datetime\",\n                            \"display\",\n                            \"disk\",\n                            \"diskio\",\n                            \"de\",\n                            \"dns\",\n                            \"editor\",\n                            \"font\",\n                            \"gamepad\",\n                            \"gpu\",\n                            \"host\",\n                            \"icons\",\n                            \"initsystem\",\n                            \"keyboard\",\n                            \"kernel\",\n                            \"lm\",\n                            \"loadavg\",\n                            \"locale\",\n                            \"localip\",\n                            \"media\",\n                            \"memory\",\n                            \"monitor\",\n                            \"mouse\",\n                            \"netio\",\n                            \"opencl\",\n                            \"opengl\",\n                            \"os\",\n                            \"packages\",\n                            \"physicaldisk\",\n                            \"physicalmemory\",\n                            \"player\",\n                            \"poweradapter\",\n                            \"processes\",\n                            \"publicip\",\n                            \"separator\",\n                            \"shell\",\n                            \"sound\",\n                            \"swap\",\n                            \"terminal\",\n                            \"terminalfont\",\n                            \"terminalsize\",\n                            \"terminaltheme\",\n                            \"title\",\n                            \"theme\",\n                            \"tpm\",\n                            \"uptime\",\n                            \"users\",\n                            \"version\",\n                            \"vulkan\",\n                            \"wallpaper\",\n                            \"weather\",\n                            \"wm\",\n                            \"wifi\",\n                            \"wmtheme\",\n                            \"zpool\"\n                        ]\n                    },\n                    {\n                        \"type\": \"object\",\n                        \"description\": \"Run module with custom configurations\",\n                        \"required\": [\n                            \"type\"\n                        ],\n                        \"properties\": {\n                            \"type\": {\n                                \"type\": \"string\"\n                            }\n                        },\n                        \"oneOf\": [\n                            {\n                                \"title\": \"Break\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"break\",\n                                        \"description\": \"Print a empty line\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Battery\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"battery\",\n                                        \"description\": \"Print battery capacity, status, etc\"\n                                    },\n                                    \"useSetupApi\": {\n                                        \"description\": \"Set if `CM API` should be used on Windows to detect battery info, which supports multi batteries, but slower. Windows only\",\n                                        \"type\": \"boolean\",\n                                        \"default\": false\n                                    },\n                                    \"temp\": {\n                                        \"$ref\": \"#/$defs/temperature\"\n                                    },\n                                    \"percent\": {\n                                        \"$ref\": \"#/$defs/percent\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/batteryFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"BIOS\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"Print information of 1st-stage bootloader (name, version, release date, etc)\",\n                                        \"const\": \"bios\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/biosFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Bluetooth\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"List (connected) bluetooth devices\",\n                                        \"const\": \"bluetooth\"\n                                    },\n                                    \"showDisconnected\": {\n                                        \"description\": \"Set if disconnected bluetooth devices should be printed\",\n                                        \"type\": \"boolean\",\n                                        \"default\": false\n                                    },\n                                    \"percent\": {\n                                        \"$ref\": \"#/$defs/percent\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/bluetoothFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Bluetooth Radio\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"List bluetooth radios width supported version and vendor\",\n                                        \"const\": \"bluetoothradio\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/bluetoothradioFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Board\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"Print motherboard name and other info\",\n                                        \"const\": \"board\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/boardFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Boot Manager\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"Print information of 2nd-stage bootloader (name, firmware, etc)\",\n                                        \"const\": \"bootmgr\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/bootmgrFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Brightness\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"brightness\",\n                                        \"description\": \"Print current brightness level of your monitors\"\n                                    },\n                                    \"percent\": {\n                                        \"$ref\": \"#/$defs/percent\"\n                                    },\n                                    \"ddcciSleep\": {\n                                        \"type\": \"integer\",\n                                        \"description\": \"Set the sleep times (in ms) when sending DDC/CI requests.\\nSee <https://www.ddcutil.com/performance_options/#option-sleep-multiplier> for detail\",\n                                        \"minimum\": 0,\n                                        \"maximum\": 400,\n                                        \"default\": 10\n                                    },\n                                    \"compact\": {\n                                        \"description\": \"Set if multiple results should be printed in one line\",\n                                        \"type\": \"boolean\",\n                                        \"default\": false\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/brightnessFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"BTRFS\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"btrfs\",\n                                        \"description\": \"Print Linux BTRFS volumes\"\n                                    },\n                                    \"percent\": {\n                                        \"$ref\": \"#/$defs/percent\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/btrfsFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Camera\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"Print available cameras\",\n                                        \"const\": \"camera\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/cameraFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Chassis\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"chassis\",\n                                        \"description\": \"Print chassis type (desktop, laptop, etc)\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/chassisFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"CPU\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"Print CPU name, frequency, etc\",\n                                        \"const\": \"cpu\"\n                                    },\n                                    \"temp\": {\n                                        \"$ref\": \"#/$defs/temperature\"\n                                    },\n                                    \"tempSensor\": {\n                                        \"description\": \"Set the temperature sensor to use for CPU temperature detection\\n* Linux: `hwmon` or `thermal` path name (eg. `hwmon0`, `thermal_zone0)`\\n* macOS: SMC sensor key (eg. `Tp01`)\\n* Windows: thermal zone key (eg. `\\\\_TZ.CPUZ`)\\n* FreeBSD: sysctl key (eg. `dev.cpu.0.temperature`)\\n* NetBSD: sysmon sensor key (eg. `coretemp0`)\",\n                                        \"type\": \"string\"\n                                    },\n                                    \"showPeCoreCount\": {\n                                        \"description\": \"Detect and display CPU frequency of different core types (eg. Pcore and Ecore) if supported\",\n                                        \"type\": \"boolean\",\n                                        \"default\": false\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/cpuFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"CPU Cache\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"cpucache\",\n                                        \"description\": \"Print CPU cache sizes\"\n                                    },\n                                    \"percent\": {\n                                        \"$ref\": \"#/$defs/percent\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/cpucacheFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"CPU Usage\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"cpuusage\",\n                                        \"description\": \"Print CPU usage. Costs some time to collect data\"\n                                    },\n                                    \"percent\": {\n                                        \"$ref\": \"#/$defs/percent\"\n                                    },\n                                    \"separate\": {\n                                        \"type\": \"boolean\",\n                                        \"description\": \"Display CPU usage per CPU logical core, instead of an average result\",\n                                        \"default\": false\n                                    },\n                                    \"waitTime\": {\n                                        \"type\": \"integer\",\n                                        \"description\": \"Wait time (in ms). CPU usage = (inUseEnd - inUseStart) / waitTime\",\n                                        \"default\": 200,\n                                        \"minimum\": 1\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/cpuusageFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Colors\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"Display the terminal's 16-color palette\",\n                                        \"const\": \"colors\"\n                                    },\n                                    \"symbol\": {\n                                        \"description\": \"Set the symbol to use\",\n                                        \"type\": \"string\",\n                                        \"oneOf\": [\n                                            {\n                                                \"const\": \"block\",\n                                                \"description\": \"\\u2588\\u2588\\u2588\"\n                                            },\n                                            {\n                                                \"const\": \"background\",\n                                                \"description\": \"(whitespaces with background)\"\n                                            },\n                                            {\n                                                \"const\": \"circle\",\n                                                \"description\": \"\\u25cf\"\n                                            },\n                                            {\n                                                \"const\": \"diamond\",\n                                                \"description\": \"\\u25c6\"\n                                            },\n                                            {\n                                                \"const\": \"triangle\",\n                                                \"description\": \"\\u25b2\"\n                                            },\n                                            {\n                                                \"const\": \"square\",\n                                                \"description\": \"\\u25a0\"\n                                            },\n                                            {\n                                                \"const\": \"star\",\n                                                \"description\": \"\\u2605\"\n                                            }\n                                        ],\n                                        \"default\": \"background\"\n                                    },\n                                    \"paddingLeft\": {\n                                        \"description\": \"Set the number of white spaces to print before the symbol\",\n                                        \"type\": \"integer\",\n                                        \"minimum\": 0,\n                                        \"default\": 0\n                                    },\n                                    \"block\": {\n                                        \"description\": \"Set behavior of block printing\",\n                                        \"type\": \"object\",\n                                        \"additionalProperties\": false,\n                                        \"properties\": {\n                                            \"width\": {\n                                                \"description\": \"Set the block width in spaces\",\n                                                \"type\": \"integer\",\n                                                \"minimum\": 1,\n                                                \"default\": 3\n                                            },\n                                            \"range\": {\n                                                \"description\": \"Set the range of colors in the blocks to print\",\n                                                \"type\": \"array\",\n                                                \"items\": {\n                                                    \"type\": \"integer\",\n                                                    \"minimum\": 0,\n                                                    \"maximum\": 15\n                                                },\n                                                \"minItems\": 2,\n                                                \"maxItems\": 2\n                                            }\n                                        }\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Command\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"Running custom shell scripts\",\n                                        \"const\": \"command\"\n                                    },\n                                    \"shell\": {\n                                        \"description\": \"Set the shell program to execute the command text\\nDefault: cmd for Windows, /bin/sh for *nix\",\n                                        \"type\": \"string\"\n                                    },\n                                    \"param\": {\n                                        \"description\": \"Set the parameter used when starting the shell\\nIf set to an empty string, it will be ignored\\nDefault: /c for Windows, -c for *nix\",\n                                        \"type\": \"string\"\n                                    },\n                                    \"text\": {\n                                        \"description\": \"Set the command text to be executed\",\n                                        \"type\": \"string\"\n                                    },\n                                    \"useStdErr\": {\n                                        \"description\": \"Set if stderr should be used instead of stdout for command output\",\n                                        \"type\": \"boolean\",\n                                        \"default\": false\n                                    },\n                                    \"parallel\": {\n                                        \"description\": \"Set if the command should be executed in parallel with other commands\\nImprove performance when using multiple commands, but may cause issues with some commands\",\n                                        \"type\": \"boolean\",\n                                        \"default\": true\n                                    },\n                                    \"splitLines\": {\n                                        \"description\": \"Set if the command output should be split into multiple lines\",\n                                        \"type\": \"boolean\",\n                                        \"default\": false\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/commandFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Cursor\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"cursor\",\n                                        \"description\": \"Print cursor style name\"\n                                    },\n                                    \"percent\": {\n                                        \"$ref\": \"#/$defs/percent\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/cursorFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Custom\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"Print a custom string, with or without key\",\n                                        \"const\": \"custom\"\n                                    },\n                                    \"key\": {\n                                        \"description\": \"Leave empty not to print the key\",\n                                        \"type\": \"string\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"description\": \"Text to print\",\n                                        \"type\": \"string\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Date Time\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"datetime\",\n                                        \"description\": \"Print current date and time\"\n                                    },\n                                    \"percent\": {\n                                        \"$ref\": \"#/$defs/percent\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/datetimeFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Display\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"Print resolutions, refresh rates, etc\",\n                                        \"const\": \"display\"\n                                    },\n                                    \"compactType\": {\n                                        \"description\": \"Set if all displays should be printed in one line\",\n                                        \"oneOf\": [\n                                            {\n                                                \"const\": null,\n                                                \"description\": \"Disable compact mode\"\n                                            },\n                                            {\n                                                \"const\": \"none\",\n                                                \"description\": \"Disable compact mode (kept for compatibility)\"\n                                            },\n                                            {\n                                                \"const\": \"original\",\n                                                \"description\": \"Print original resolutions\"\n                                            },\n                                            {\n                                                \"const\": \"scaled\",\n                                                \"description\": \"Print scaled resolutions\"\n                                            },\n                                            {\n                                                \"const\": \"original-with-refresh-rate\",\n                                                \"description\": \"Print original resolutions with refresh rates\"\n                                            },\n                                            {\n                                                \"const\": \"scaled-with-refresh-rate\",\n                                                \"description\": \"Print scaled resolutions with refresh rates\"\n                                            }\n                                        ],\n                                        \"default\": null\n                                    },\n                                    \"preciseRefreshRate\": {\n                                        \"description\": \"Set if decimal refresh rates should not be rounded into integers when printing\",\n                                        \"type\": \"boolean\",\n                                        \"default\": false\n                                    },\n                                    \"order\": {\n                                        \"description\": \"Set the order should be used when printing\",\n                                        \"oneOf\": [\n                                            {\n                                                \"const\": null,\n                                                \"description\": \"Use the default order\"\n                                            },\n                                            {\n                                                \"const\": \"none\",\n                                                \"description\": \"Use the detected order (kept for compatibility)\"\n                                            },\n                                            {\n                                                \"const\": \"asc\",\n                                                \"description\": \"Sort by display name in ascending order\"\n                                            },\n                                            {\n                                                \"const\": \"desc\",\n                                                \"description\": \"Sort by display name in descending order\"\n                                            }\n                                        ],\n                                        \"default\": null\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/displayFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Disk\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"Print partitions, space usage, disk type, etc\",\n                                        \"const\": \"disk\"\n                                    },\n                                    \"folders\": {\n                                        \"description\": \"A list of folder paths for the disk output\\nDefault: auto detection using mount-points\\nThis option overrides other `show*` options\",\n                                        \"oneOf\": [\n                                            {\n                                                \"type\": \"string\",\n                                                \"description\": \"A colon (semicolon on Windows) separated list of folder paths to get disk usage from\",\n                                                \"default\": \"/\"\n                                            },\n                                            {\n                                                \"type\": \"array\",\n                                                \"description\": \"An array of folder paths to get disk usage from\",\n                                                \"items\": {\n                                                    \"type\": \"string\"\n                                                },\n                                                \"minItems\": 1,\n                                                \"uniqueItems\": true\n                                            }\n                                        ]\n                                    },\n                                    \"hideFolders\": {\n                                        \"description\": \"A list of folder paths (or glob patterns) to hide from the disk output\",\n                                        \"oneOf\": [\n                                            {\n                                                \"type\": \"string\",\n                                                \"description\": \"A colon (semicolon on Windows) separated list of folder paths to hide from the disk output\"\n                                            },\n                                            {\n                                                \"type\": \"array\",\n                                                \"description\": \"An array of folder paths to hide from the disk output\",\n                                                \"items\": {\n                                                    \"type\": \"string\"\n                                                },\n                                                \"minItems\": 1,\n                                                \"uniqueItems\": true\n                                            }\n                                        ],\n                                        \"default\": \"/efi:/boot:/boot/*\"\n                                    },\n                                    \"hideFS\": {\n                                        \"description\": \"A list of file systems to hide from the disk output\",\n                                        \"oneOf\": [\n                                            {\n                                                \"type\": \"string\",\n                                                \"description\": \"A colon separated list of file systems to hide from the disk output\"\n                                            },\n                                            {\n                                                \"type\": \"array\",\n                                                \"description\": \"An array of file systems to hide from the disk output\",\n                                                \"items\": {\n                                                    \"type\": \"string\"\n                                                },\n                                                \"minItems\": 1,\n                                                \"uniqueItems\": true\n                                            }\n                                        ]\n                                    },\n                                    \"showRegular\": {\n                                        \"type\": \"boolean\",\n                                        \"description\": \"Set if regular volume should be printed\",\n                                        \"default\": true\n                                    },\n                                    \"showExternal\": {\n                                        \"type\": \"boolean\",\n                                        \"description\": \"Set if external volume should be printed\",\n                                        \"default\": true\n                                    },\n                                    \"showHidden\": {\n                                        \"type\": \"boolean\",\n                                        \"description\": \"Set if hidden volumes should be printed\",\n                                        \"default\": false\n                                    },\n                                    \"showSubvolumes\": {\n                                        \"type\": \"boolean\",\n                                        \"description\": \"Set if subvolumes should be printed\",\n                                        \"default\": false\n                                    },\n                                    \"showReadOnly\": {\n                                        \"type\": \"boolean\",\n                                        \"description\": \"Set if read only volumes should be printed\",\n                                        \"default\": false\n                                    },\n                                    \"showUnknown\": {\n                                        \"type\": \"boolean\",\n                                        \"description\": \"Set if unknown (unable to detect sizes) volumes should be printed\",\n                                        \"default\": false\n                                    },\n                                    \"useAvailable\": {\n                                        \"type\": \"boolean\",\n                                        \"description\": \"Use f_bavail (lpFreeBytesAvailableToCaller for Windows) instead of f_bfree to calculate used bytes\\nMay be required for macOS to display correct results\",\n                                        \"default\": false\n                                    },\n                                    \"percent\": {\n                                        \"$ref\": \"#/$defs/percent\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/diskFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"DiskIO\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"Print physical disk I/O throughput\",\n                                        \"const\": \"diskio\"\n                                    },\n                                    \"namePrefix\": {\n                                        \"description\": \"Show disks with given name prefix only\",\n                                        \"type\": \"string\"\n                                    },\n                                    \"detectTotal\": {\n                                        \"description\": \"Detect total bytes instead of current rate\",\n                                        \"type\": \"boolean\",\n                                        \"default\": false\n                                    },\n                                    \"waitTime\": {\n                                        \"type\": \"integer\",\n                                        \"description\": \"Wait time (in ms). Disk I/O = (totalBytesEnd - totalBytesStart) / waitTime\",\n                                        \"default\": 200,\n                                        \"minimum\": 1\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/diskioFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Desktop Environment\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"de\",\n                                        \"description\": \"Print desktop environment name\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/deFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"DNS\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"dns\",\n                                        \"description\": \"Print DNS servers\"\n                                    },\n                                    \"showType\": {\n                                        \"oneOf\": [\n                                            {\n                                                \"const\": \"ipv4\",\n                                                \"description\": \"Show IPv4 addresses only\"\n                                            },\n                                            {\n                                                \"const\": \"ipv6\",\n                                                \"description\": \"Show IPv6 addresses only\"\n                                            },\n                                            {\n                                                \"const\": \"both\",\n                                                \"description\": \"Show both IPv4 and IPv6 addresses\"\n                                            }\n                                        ],\n                                        \"default\": \"both\",\n                                        \"description\": \"Specify the type of DNS servers should be detected\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/dnsFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Editor\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"editor\",\n                                        \"description\": \"Print information of the default editor ($VISUAL or $EDITOR)\"\n                                    },\n                                    \"percent\": {\n                                        \"$ref\": \"#/$defs/percent\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/editorFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Font\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"font\",\n                                        \"description\": \"Print system font names\"\n                                    },\n                                    \"percent\": {\n                                        \"$ref\": \"#/$defs/percent\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/fontFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Gamepad\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"gamepad\",\n                                        \"description\": \"List connected gamepads\"\n                                    },\n                                    \"ignores\": {\n                                        \"type\": \"array\",\n                                        \"description\": \"An array of case-insensitive device name prefixes to ignore\",\n                                        \"items\": {\n                                            \"type\": \"string\"\n                                        },\n                                        \"minItems\": 1,\n                                        \"uniqueItems\": true\n                                    },\n                                    \"percent\": {\n                                        \"$ref\": \"#/$defs/percent\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/gamepadFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"GPU\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"Print GPU names, graphic memory size, type, etc\",\n                                        \"const\": \"gpu\"\n                                    },\n                                    \"temp\": {\n                                        \"$ref\": \"#/$defs/temperature\"\n                                    },\n                                    \"driverSpecific\": {\n                                        \"description\": \"Use driver specific method to detect more detailed GPU information (memory usage, core count, etc)\\nRequires the latest GPU drivers to be installed.\",\n                                        \"type\": \"boolean\",\n                                        \"default\": false\n                                    },\n                                    \"detectionMethod\": {\n                                        \"description\": \"Force using a specified method to detect GPUs\",\n                                        \"type\": \"string\",\n                                        \"oneOf\": [\n                                            {\n                                                \"const\": \"auto\",\n                                                \"description\": \"Query platform-specific graphics APIs.\\nRequires proper GPU drivers to be installed.\\nSupported on Linux, FreeBSD, Windows and macOS\"\n                                            },\n                                            {\n                                                \"const\": \"pci\",\n                                                \"description\": \"Search PCI devices, which does not require GPU drivers to be installed.\\nNot supported on Windows and macOS\"\n                                            },\n                                            {\n                                                \"const\": \"vulkan\",\n                                                \"description\": \"Use Vulkan API.\\nSlow and requires proper Vulkan drivers to be installed.\\nUsed for Android\"\n                                            },\n                                            {\n                                                \"const\": \"opencl\",\n                                                \"description\": \"Use OpenCL API.\\nSlow and requires proper OpenCL drivers to be installed\"\n                                            },\n                                            {\n                                                \"const\": \"opengl\",\n                                                \"description\": \"Use OpenGL API.\\nSlow and only detects one GPU.\\nUsed for OpenBSD\"\n                                            }\n                                        ],\n                                        \"default\": \"<varies-by-platform>\"\n                                    },\n                                    \"hideType\": {\n                                        \"description\": \"Specify the type of GPUs should not be printed\",\n                                        \"oneOf\": [\n                                            {\n                                                \"const\": null,\n                                                \"description\": \"Do not hide any GPUs\"\n                                            },\n                                            {\n                                                \"const\": \"none\",\n                                                \"description\": \"Do not hide any GPUs (kept for compatibility)\"\n                                            },\n                                            {\n                                                \"const\": \"integrated\",\n                                                \"description\": \"Hide integrated GPUs\"\n                                            },\n                                            {\n                                                \"const\": \"discrete\",\n                                                \"description\": \"Hide discrete GPUs\"\n                                            },\n                                            {\n                                                \"const\": \"unknown\",\n                                                \"description\": \"Hide unknown (unrecognized) GPUs\"\n                                            }\n                                        ],\n                                        \"default\": null\n                                    },\n                                    \"percent\": {\n                                        \"$ref\": \"#/$defs/percent\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/gpuFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Host\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"host\",\n                                        \"description\": \"Print product name of your computer\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/hostFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Icons\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"icons\",\n                                        \"description\": \"Print icon style name\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/iconsFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Init System\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"initsystem\",\n                                        \"description\": \"Print init system (pid 1) name and version\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/initsystemFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Kernel\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"kernel\",\n                                        \"description\": \"Print system kernel version\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/kernelFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Keyboard\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"keyboard\",\n                                        \"description\": \"List (connected) keyboards\"\n                                    },\n                                    \"ignores\": {\n                                        \"type\": \"array\",\n                                        \"description\": \"An array of case-insensitive device name prefixes to ignore\",\n                                        \"items\": {\n                                            \"type\": \"string\"\n                                        },\n                                        \"minItems\": 1,\n                                        \"uniqueItems\": true\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/keyboardFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Login Manager\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"lm\",\n                                        \"description\": \"Print login manager (desktop manager) name and version\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/lmFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Local IP\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"List local IP addresses (v4 or v6), MAC addresses, etc\",\n                                        \"const\": \"localip\"\n                                    },\n                                    \"showIpv4\": {\n                                        \"description\": \"Show IPv4 addresses\",\n                                        \"type\": \"boolean\",\n                                        \"default\": true\n                                    },\n                                    \"showIpv6\": {\n                                        \"description\": \"Show IPv6 addresses\",\n                                        \"oneOf\": [\n                                            {\n                                                \"const\": true,\n                                                \"description\": \"Show the most useful IPv6 addresses\"\n                                            },\n                                            {\n                                                \"const\": false,\n                                                \"description\": \"Do not show IPv6 addresses\"\n                                            },\n                                            {\n                                                \"const\": \"gua\",\n                                                \"description\": \"Show only global unicast IPv6 addresses (2000::/3)\"\n                                            },\n                                            {\n                                                \"const\": \"ula\",\n                                                \"description\": \"Show only unique local IPv6 addresses (fc00::/7)\"\n                                            },\n                                            {\n                                                \"const\": \"lla\",\n                                                \"description\": \"Show only link-local IPv6 addresses (fe80::/10)\"\n                                            },\n                                            {\n                                                \"const\": \"unknown\",\n                                                \"description\": \"Show only IPv6 addresses that are not gua, ula or lla\"\n                                            }\n                                        ],\n                                        \"default\": false\n                                    },\n                                    \"showSpeed\": {\n                                        \"description\": \"Show ethernet rx speed\",\n                                        \"type\": \"boolean\",\n                                        \"default\": false\n                                    },\n                                    \"showMtu\": {\n                                        \"description\": \"Show MTU\",\n                                        \"type\": \"boolean\",\n                                        \"default\": false\n                                    },\n                                    \"showMac\": {\n                                        \"description\": \"Show MAC addresses\",\n                                        \"type\": \"boolean\",\n                                        \"default\": false\n                                    },\n                                    \"showLoop\": {\n                                        \"description\": \"Show loop back addresses (127.0.0.1)\",\n                                        \"type\": \"boolean\",\n                                        \"default\": false\n                                    },\n                                    \"showPrefixLen\": {\n                                        \"description\": \"Show network prefix length (/N)\",\n                                        \"type\": \"boolean\",\n                                        \"default\": true\n                                    },\n                                    \"showAllIps\": {\n                                        \"description\": \"Show all IPs bound to the same interface.\\nBy default only the firstly detected IP is shown\",\n                                        \"type\": \"boolean\",\n                                        \"default\": false\n                                    },\n                                    \"showFlags\": {\n                                        \"description\": \"Show the interface's flags\",\n                                        \"type\": \"boolean\",\n                                        \"default\": false\n                                    },\n                                    \"compact\": {\n                                        \"description\": \"Show all IPs in one line\",\n                                        \"type\": \"boolean\",\n                                        \"default\": false\n                                    },\n                                    \"namePrefix\": {\n                                        \"description\": \"Show IPs with given name prefix only\",\n                                        \"type\": \"string\"\n                                    },\n                                    \"defaultRouteOnly\": {\n                                        \"description\": \"Show ips that are used for default routing only\\nDoesn't work on Android\",\n                                        \"type\": \"boolean\",\n                                        \"default\": true\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/localipFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Loadavg\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"loadavg\",\n                                        \"description\": \"Print system load averages\"\n                                    },\n                                    \"ndigits\": {\n                                        \"type\": \"integer\",\n                                        \"description\": \"Set the number of digits to keep after the decimal point\",\n                                        \"minimum\": 0,\n                                        \"maximum\": 9,\n                                        \"default\": 2\n                                    },\n                                    \"compact\": {\n                                        \"type\": \"boolean\",\n                                        \"description\": \"Show values in one line\",\n                                        \"default\": true\n                                    },\n                                    \"percent\": {\n                                        \"$ref\": \"#/$defs/percent\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/loadavgFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Locale\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"locale\",\n                                        \"description\": \"Print system locale name\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/localeFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Logo\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"logo\",\n                                        \"description\": \"Query built-in logo for JSON output\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Media\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"media\",\n                                        \"description\": \"Print song name of currently playing\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/mediaFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Memory\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"memory\",\n                                        \"description\": \"Print system memory usage info\"\n                                    },\n                                    \"percent\": {\n                                        \"$ref\": \"#/$defs/percent\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/memoryFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Mouse\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"mouse\",\n                                        \"description\": \"List connected mouses\"\n                                    },\n                                    \"ignores\": {\n                                        \"type\": \"array\",\n                                        \"description\": \"An array of case-insensitive device name prefixes to ignore\",\n                                        \"items\": {\n                                            \"type\": \"string\"\n                                        },\n                                        \"minItems\": 1,\n                                        \"uniqueItems\": true\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/mouseFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Monitor\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"monitor\",\n                                        \"description\": \"Alias of Display module (for backwards compatibility, deprecated)\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/monitorFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"NetIO\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"Print network I/O throughput\",\n                                        \"const\": \"netio\"\n                                    },\n                                    \"namePrefix\": {\n                                        \"description\": \"Show IPs with given name prefix only\",\n                                        \"type\": \"string\"\n                                    },\n                                    \"defaultRouteOnly\": {\n                                        \"description\": \"Show ips that are used for default routing only\\nDoesn't work on Android\",\n                                        \"type\": \"boolean\",\n                                        \"default\": true\n                                    },\n                                    \"detectTotal\": {\n                                        \"description\": \"Detect total bytes instead of current rate\",\n                                        \"type\": \"boolean\",\n                                        \"default\": false\n                                    },\n                                    \"waitTime\": {\n                                        \"type\": \"integer\",\n                                        \"description\": \"Wait time (in ms). Net I/O = (totalBytesEnd - totalBytesStart) / waitTime\",\n                                        \"default\": 200,\n                                        \"minimum\": 1\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/netioFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"OpenCL\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"opencl\",\n                                        \"description\": \"Print highest OpenCL version supported by the GPU\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/openclFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"OpenGL\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"Print highest OpenGL version supported by the GPU\",\n                                        \"const\": \"opengl\"\n                                    },\n                                    \"library\": {\n                                        \"description\": \"Set the OpenGL context creation library to use\",\n                                        \"oneOf\": [\n                                            {\n                                                \"const\": \"auto\",\n                                                \"description\": \"Prefer EGL on *nix; prefer platform-specific implementation on others\"\n                                            },\n                                            {\n                                                \"const\": \"egl\",\n                                                \"description\": \"Use EGL, which works on TTY\"\n                                            },\n                                            {\n                                                \"const\": \"glx\",\n                                                \"description\": \"Use GLX, requires X session (*nix only)\"\n                                            }\n                                        ],\n                                        \"default\": \"auto\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/openglFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Operating System\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"os\",\n                                        \"description\": \"Print OS / or Linux distro name and version\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/osFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Packages\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"packages\",\n                                        \"description\": \"List installed package managers and count of installed packages\"\n                                    },\n                                    \"disabled\": {\n                                        \"oneOf\": [\n                                            {\n                                                \"description\": \"List of package managers to be disabled when detecting\\nWarning: Some detection methods can be very slow.\",\n                                                \"type\": \"array\",\n                                                \"items\": {\n                                                    \"type\": \"string\",\n                                                    \"enum\": [\n                                                        \"am\",\n                                                        \"apk\",\n                                                        \"brew\",\n                                                        \"choco\",\n                                                        \"dpkg\",\n                                                        \"emerge\",\n                                                        \"eopkg\",\n                                                        \"flatpak\",\n                                                        \"guix\",\n                                                        \"hpkg\",\n                                                        \"linglong\",\n                                                        \"lpkg\",\n                                                        \"lpkgbuild\",\n                                                        \"macports\",\n                                                        \"mport\",\n                                                        \"nix\",\n                                                        \"opkg\",\n                                                        \"pacman\",\n                                                        \"pacstall\",\n                                                        \"paludis\",\n                                                        \"pisi\",\n                                                        \"pkg\",\n                                                        \"pkgtool\",\n                                                        \"rpm\",\n                                                        \"scoop\",\n                                                        \"snap\",\n                                                        \"sorcery\",\n                                                        \"winget\",\n                                                        \"xbps\"\n                                                    ],\n                                                    \"uniqueItems\": true\n                                                }\n                                            },\n                                            {\n                                                \"description\": \"Enable all package managers\",\n                                                \"type\": \"null\"\n                                            }\n                                        ],\n                                        \"default\": [\"winget\"]\n                                    },\n                                    \"combined\": {\n                                        \"description\": \"Whether to combine related package managers into single counts (e.g., nix-system + nix-user = nix)\",\n                                        \"type\": \"boolean\",\n                                        \"default\": false\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/packagesFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Physical Disk\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"Print physical disk information\",\n                                        \"const\": \"physicaldisk\"\n                                    },\n                                    \"namePrefix\": {\n                                        \"description\": \"Show disks with given name prefix only\",\n                                        \"type\": \"string\"\n                                    },\n                                    \"temp\": {\n                                        \"$ref\": \"#/$defs/temperature\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/physicaldiskFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Physical Memory\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"physicalmemory\",\n                                        \"description\": \"Print system physical memory devices\"\n                                    },\n                                    \"showEmptySlots\": {\n                                        \"description\": \"Set if uninstalled memory slots should be printed\",\n                                        \"type\": \"boolean\",\n                                        \"default\": false\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/physicalmemoryFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Player\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"player\",\n                                        \"description\": \"Print music player name\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/playerFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Power Adapter\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"poweradapter\",\n                                        \"description\": \"Print power adapter name and charging watts\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/poweradapterFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Processes\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"processes\",\n                                        \"description\": \"Count running processes\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/processesFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Public IP\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"Print your public IP address, etc\",\n                                        \"const\": \"publicip\"\n                                    },\n                                    \"url\": {\n                                        \"description\": \"The URL of public IP detection server to be used. Only HTTP protocol is supported\",\n                                        \"type\": \"string\",\n                                        \"format\": \"url\",\n                                        \"default\": \"http://ipinfo.io/ip\"\n                                    },\n                                    \"timeout\": {\n                                        \"description\": \"Time in milliseconds to wait for the public ip server to respond.\\n0 to disable timeout\",\n                                        \"type\": \"integer\",\n                                        \"minimum\": 0,\n                                        \"default\": 0\n                                    },\n                                    \"ipv6\": {\n                                        \"description\": \"Whether to use IPv6 for public IP detection server\",\n                                        \"type\": \"boolean\",\n                                        \"default\": false\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/publicipFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Separator\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"Print a separator line\",\n                                        \"const\": \"separator\"\n                                    },\n                                    \"string\": {\n                                        \"description\": \"Set the string to be printed by the separator line\",\n                                        \"type\": \"string\",\n                                        \"default\": \"-\"\n                                    },\n                                    \"outputColor\": {\n                                        \"description\": \"Set the color of the separator line\",\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"times\": {\n                                        \"description\": \"Set the times of separator string to repeat, or 0 to auto-detect\",\n                                        \"type\": \"integer\",\n                                        \"minimum\": 0,\n                                        \"default\": 0\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Shell\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"shell\",\n                                        \"description\": \"Print current shell name and version\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/shellFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Sound\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"Print sound devices, volume, etc\",\n                                        \"const\": \"sound\"\n                                    },\n                                    \"soundType\": {\n                                        \"description\": \"Set what type of sound devices should be printed\",\n                                        \"type\": \"string\",\n                                        \"oneOf\": [\n                                            {\n                                                \"const\": \"main\",\n                                                \"description\": \"Print only main sound devices\"\n                                            },\n                                            {\n                                                \"const\": \"active\",\n                                                \"description\": \"Print only active sound devices\"\n                                            },\n                                            {\n                                                \"const\": \"all\",\n                                                \"description\": \"Print all sound devices\"\n                                            }\n                                        ],\n                                        \"default\": \"main\"\n                                    },\n                                    \"percent\": {\n                                        \"$ref\": \"#/$defs/percent\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/soundFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Swap\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"swap\",\n                                        \"description\": \"Print swap (paging file) space usage\"\n                                    },\n                                    \"separate\": {\n                                        \"type\": \"boolean\",\n                                        \"description\": \"Set if detailed swap devices should be reported on separate lines instead of a summary\",\n                                        \"default\": false\n                                    },\n                                    \"percent\": {\n                                        \"$ref\": \"#/$defs/percent\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/swapFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Terminal\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"terminal\",\n                                        \"description\": \"Print current terminal name and version\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/terminalFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Terminal Font\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"terminalfont\",\n                                        \"description\": \"Print font name and size used by current terminal\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/terminalfontFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Terminal Size\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"terminalsize\",\n                                        \"description\": \"Print current terminal size\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/terminalsizeFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Terminal Theme\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"terminaltheme\",\n                                        \"description\": \"Print current terminal theme (foreground and background colors)\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/terminalthemeFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Theme\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"theme\",\n                                        \"description\": \"Print current theme of desktop environment\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/themeFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Title\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"Print title, which contains your user name, hostname\",\n                                        \"const\": \"title\"\n                                    },\n                                    \"fqdn\": {\n                                        \"type\": \"boolean\",\n                                        \"description\": \"Set if the title should use fully qualified domain name\",\n                                        \"default\": false\n                                    },\n                                    \"color\": {\n                                        \"description\": \"Set colors of the different part of title\",\n                                        \"type\": \"object\",\n                                        \"additionalProperties\": false,\n                                        \"properties\": {\n                                            \"user\": {\n                                                \"description\": \"Set color of the user name (left part)\",\n                                                \"$ref\": \"#/$defs/colors\"\n                                            },\n                                            \"at\": {\n                                                \"description\": \"Set color of the @ symbol (middle part)\",\n                                                \"$ref\": \"#/$defs/colors\"\n                                            },\n                                            \"host\": {\n                                                \"description\": \"Set color of the host name (right part)\",\n                                                \"$ref\": \"#/$defs/colors\"\n                                            }\n                                        }\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/titleFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"TPM\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"tpm\",\n                                        \"description\": \"Print info of Trusted Platform Module (TPM) Security Device\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/tpmFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Users\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"users\",\n                                        \"description\": \"Print users currently logged in\"\n                                    },\n                                    \"compact\": {\n                                        \"type\": \"boolean\",\n                                        \"description\": \"Show all active users in one line\",\n                                        \"default\": false\n                                    },\n                                    \"myselfOnly\": {\n                                        \"type\": \"boolean\",\n                                        \"description\": \"Show only the current user\",\n                                        \"default\": false\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/usersFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Uptime\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"uptime\",\n                                        \"description\": \"Print how long system has been running\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/uptimeFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Version\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"version\",\n                                        \"description\": \"Print Fastfetch version\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/versionFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Vulkan\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"vulkan\",\n                                        \"description\": \"Print highest Vulkan version supported by the GPU\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/vulkanFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Wallpaper\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"wallpaper\",\n                                        \"description\": \"Print image file path of current wallpaper\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/wallpaperFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Weather\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"description\": \"Print weather information\",\n                                        \"const\": \"weather\"\n                                    },\n                                    \"location\": {\n                                        \"description\": \"The location to display\\nMust be URI encoded (e.g., a whitespace must be encoded as \\\"+\\\")\",\n                                        \"type\": \"string\"\n                                    },\n                                    \"timeout\": {\n                                        \"description\": \"Time in milliseconds to wait for the weather server to respond.\\n0 to disable timeout\",\n                                        \"type\": \"integer\",\n                                        \"minimum\": 0,\n                                        \"default\": 0\n                                    },\n                                    \"outputFormat\": {\n                                        \"description\": \"The output weather format to be used (must be URI encoded)\",\n                                        \"type\": \"string\",\n                                        \"default\": \"%t+-+%C+(%l)\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/weatherFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Wi-Fi\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"wifi\",\n                                        \"description\": \"Print connected Wi-Fi info (SSID, connection and security protocol)\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/wifiFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Window Manager\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"wm\",\n                                        \"description\": \"Print window manager name and version\"\n                                    },\n                                    \"detectPlugin\": {\n                                        \"description\": \"Set if window manager plugin should be detected on supported platforms\",\n                                        \"type\": \"boolean\",\n                                        \"default\": true\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/wmFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"WM Theme\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"wmtheme\",\n                                        \"description\": \"Print current theme of window manager\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/wmthemeFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            },\n                            {\n                                \"title\": \"Zpool\",\n                                \"type\": \"object\",\n                                \"additionalProperties\": false,\n                                \"properties\": {\n                                    \"type\": {\n                                        \"const\": \"zpool\",\n                                        \"description\": \"Print ZFS storage pools\"\n                                    },\n                                    \"percent\": {\n                                        \"$ref\": \"#/$defs/percent\"\n                                    },\n                                    \"key\": {\n                                        \"$ref\": \"#/$defs/key\"\n                                    },\n                                    \"keyColor\": {\n                                        \"$ref\": \"#/$defs/keyColor\"\n                                    },\n                                    \"keyIcon\": {\n                                        \"$ref\": \"#/$defs/keyIcon\"\n                                    },\n                                    \"keyWidth\": {\n                                        \"$ref\": \"#/$defs/keyWidth\"\n                                    },\n                                    \"outputColor\": {\n                                        \"$ref\": \"#/$defs/outputColor\"\n                                    },\n                                    \"format\": {\n                                        \"$ref\": \"#/$defs/zpoolFormat\"\n                                    },\n                                    \"condition\": {\n                                        \"$ref\": \"#/$defs/conditions\"\n                                    }\n                                }\n                            }\n                        ]\n                    }\n                ]\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "presets/all.jsonc",
    "content": "{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"padding\": {\n            \"top\": 2\n        }\n    },\n    \"modules\": [\n        \"title\",\n        \"separator\",\n        \"os\",\n        \"host\",\n        \"bios\",\n        \"bootmgr\",\n        \"board\",\n        \"chassis\",\n        \"kernel\",\n        \"initsystem\",\n        \"uptime\",\n        \"loadavg\",\n        \"processes\",\n        \"packages\",\n        \"shell\",\n        \"editor\",\n        \"display\",\n        \"brightness\",\n        \"monitor\",\n        \"lm\",\n        \"de\",\n        \"wm\",\n        \"wmtheme\",\n        \"theme\",\n        \"icons\",\n        \"font\",\n        \"cursor\",\n        \"wallpaper\",\n        \"terminal\",\n        \"terminalfont\",\n        \"terminalsize\",\n        \"terminaltheme\",\n        {\n            \"type\": \"cpu\",\n            \"showPeCoreCount\": true,\n            \"temp\": true\n        },\n        \"cpucache\",\n        \"cpuusage\",\n        {\n            \"type\": \"gpu\",\n            \"driverSpecific\": true,\n            \"temp\": true\n        },\n        \"memory\",\n        \"physicalmemory\",\n        {\n            \"type\": \"swap\",\n            \"separate\": true\n        },\n        \"disk\",\n        \"btrfs\",\n        \"zpool\",\n        {\n            \"type\": \"battery\",\n            \"temp\": true\n        },\n        \"poweradapter\",\n        \"player\",\n        \"media\",\n        {\n            \"type\": \"publicip\",\n            \"timeout\": 1000\n        },\n        {\n            \"type\": \"localip\",\n            \"showIpv6\": true,\n            \"showMac\": true,\n            \"showSpeed\": true,\n            \"showMtu\": true,\n            \"showLoop\": true,\n            \"showFlags\": true,\n            \"showAllIps\": true\n        },\n        \"dns\",\n        \"wifi\",\n        \"datetime\",\n        \"locale\",\n        \"vulkan\",\n        \"opengl\",\n        \"opencl\",\n        \"users\",\n        \"bluetooth\",\n        \"bluetoothradio\",\n        \"sound\",\n        \"camera\",\n        \"gamepad\",\n        \"mouse\",\n        \"keyboard\",\n        {\n          \"type\": \"weather\",\n          \"timeout\": 1000\n        },\n        \"netio\",\n        \"diskio\",\n        {\n            \"type\": \"physicaldisk\",\n            \"temp\": true\n        },\n        \"tpm\",\n        \"version\",\n        \"break\",\n        \"colors\"\n    ]\n}\n"
  },
  {
    "path": "presets/archey.jsonc",
    "content": "{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"modules\": [\n        {\n            \"type\": \"title\",\n            \"key\": \"User\",\n            \"format\": \"{user-name}\"\n        },\n        {\n            \"type\": \"title\",\n            \"key\": \"Hostname\",\n            \"format\": \"{host-name}\"\n        },\n        {\n            \"type\": \"host\",\n            \"key\": \"Model\"\n        },\n        {\n            \"type\": \"os\",\n            \"format\": \"{pretty-name} {version-id} {arch}\"\n        },\n        \"kernel\",\n        \"uptime\",\n        {\n            \"type\": \"loadavg\",\n            \"key\": \"Load Average\"\n        },\n        \"processes\",\n        {\n            \"type\": \"wm\",\n            \"key\": \"Window Manager\"\n        },\n        {\n            \"type\": \"de\",\n            \"key\": \"Desktop Environment\"\n        },\n        \"shell\",\n        {\n            \"type\": \"terminal\",\n            \"format\": \"{pretty-name} {version} {#37}█{#97}█ {#36}█{#96}█ {#35}█{#95}█ {#34}█{#94}█ {#33}█{#93}█ {#32}█{#92}█ {#31}█{#91}█ {#30}█{#90}█\"\n        },\n        {\n            \"type\": \"packages\",\n            \"format\": \"{all}\"\n        },\n        {\n            \"type\": \"cpu\",\n            \"key\": \"Temperature\",\n            \"temp\": true,\n            \"format\": \"{temperature}\"\n        },\n        {\n            \"type\": \"cpu\",\n            \"key\": \"CPU\",\n            \"format\": \"{cores-logical} x {name}\"\n        },\n        {\n            \"type\": \"gpu\",\n            \"format\": \"{name}\"\n        },\n        {\n            \"type\": \"memory\",\n            \"key\": \"RAM\"\n        },\n        {\n            \"type\": \"disk\",\n            \"key\": \"Disk\",\n            \"folders\": \"/\"\n        },\n        {\n            \"type\": \"localip\",\n            \"key\": \"LAN IP\",\n            \"showIpv6\": true,\n            \"showPrefixLen\": false\n        },\n        {\n            \"type\": \"publicip\",\n            \"key\": \"WAN IP\",\n            \"timeout\": 1000\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/ci.jsonc",
    "content": "{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"display\": {\n        \"stat\": true,\n        \"pipe\": true,\n        \"showErrors\": true,\n        \"noBuffer\": true\n    },\n    \"logo\": null,\n    \"modules\": [\n        \"title\",\n        \"separator\",\n        \"os\",\n        \"host\",\n        \"bios\",\n        \"bootmgr\",\n        \"board\",\n        \"chassis\",\n        \"kernel\",\n        \"initsystem\",\n        \"uptime\",\n        \"loadavg\",\n        \"processes\",\n        \"packages\",\n        \"shell\",\n        \"editor\",\n        \"display\",\n        \"brightness\",\n        \"monitor\",\n        \"lm\",\n        \"de\",\n        \"wm\",\n        \"wmtheme\",\n        \"theme\",\n        \"icons\",\n        \"font\",\n        \"cursor\",\n        \"wallpaper\",\n        \"terminal\",\n        \"terminalfont\",\n        \"terminalsize\",\n        \"terminaltheme\",\n        {\n            \"type\": \"cpu\",\n            \"showPeCoreCount\": true,\n            \"temp\": true\n        },\n        \"cpucache\",\n        \"cpuusage\",\n        {\n            \"type\": \"gpu\",\n            \"driverSpecific\": true,\n            \"temp\": true\n        },\n        \"memory\",\n        \"physicalmemory\",\n        {\n            \"type\": \"swap\",\n            \"separate\": true\n        },\n        \"disk\",\n        \"btrfs\",\n        \"zpool\",\n        {\n            \"type\": \"battery\",\n            \"temp\": true\n        },\n        \"poweradapter\",\n        \"player\",\n        \"media\",\n        {\n            \"type\": \"publicip\",\n            \"timeout\": 1000\n        },\n        {\n            \"type\": \"localip\",\n            \"showIpv6\": true,\n            \"showMac\": true,\n            \"showSpeed\": true,\n            \"showMtu\": true,\n            \"showLoop\": true,\n            \"showFlags\": true,\n            \"showAllIps\": true\n        },\n        \"dns\",\n        \"wifi\",\n        \"datetime\",\n        \"locale\",\n        \"vulkan\",\n        \"opengl\",\n        \"opencl\",\n        \"users\",\n        // \"bluetooth\", // doesn't work on macOS because it requires bluetooth permissions\n        // \"bluetoothradio\",\n        \"sound\",\n        \"camera\",\n        \"gamepad\",\n        \"mouse\",\n        \"keyboard\",\n        {\n            \"type\": \"weather\",\n            \"timeout\": 1000\n        },\n        \"netio\",\n        \"diskio\",\n        {\n            \"type\": \"physicaldisk\",\n            \"temp\": true\n        },\n        \"tpm\",\n        \"version\",\n        \"logo\",\n        \"break\",\n        \"colors\"\n    ]\n}\n"
  },
  {
    "path": "presets/examples/10.jsonc",
    "content": "// Load with --config examples/2.jsonc\n// Note that you must replace the image path to an existing image to display it.\n\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"padding\": {\n            \"top\": 2\n        }\n    },\n    \"display\": {\n        \"separator\": \" -> \",\n        \"constants\": [\n            \"──────────────────────────────\"\n        ]\n    },\n    \"modules\": [\n        {\n            \"type\": \"custom\",\n            \"format\": \"┌{$1}{$1}┐\",\n            \"outputColor\": \"90\"\n        },\n        {\n            \"type\": \"title\",\n            \"keyWidth\": 10\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"└{$1}{$1}┘\",\n            \"outputColor\": \"90\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \" {#90}  {#31}  {#32}  {#33}  {#34}  {#35}  {#36}  {#37}  {#38}  {#39}       {#38}  {#37}  {#36}  {#35}  {#34}  {#33}  {#32}  {#31}  {#90}\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"┌{$1}{$1}┐\",\n            \"outputColor\": \"90\"\n        },\n        {\n            \"type\": \"os\",\n            \"key\": \"{icon} OS\",\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"kernel\",\n            \"key\": \"│ ├\",\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"packages\",\n            \"key\": \"│ ├󰏖\",\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"shell\",\n            \"key\": \"│ └\",\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"wm\",\n            \"key\": \" DE/WM\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"lm\",\n            \"key\": \"│ ├󰧨\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"wmtheme\",\n            \"key\": \"│ ├󰉼\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"icons\",\n            \"key\": \"│ ├󰀻\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"terminal\",\n            \"key\": \"│ ├\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"wallpaper\",\n            \"key\": \"│ └󰸉\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"host\",\n            \"key\": \"󰌢 PC\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"cpu\",\n            \"key\": \"│ ├󰻠\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"gpu\",\n            \"key\": \"│ ├󰍛\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"disk\",\n            \"key\": \"│ ├\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"memory\",\n            \"key\": \"│ ├󰑭\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"swap\",\n            \"key\": \"│ ├󰓡\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"uptime\",\n            \"key\": \"│ ├󰅐\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"display\",\n            \"key\": \"│ └󰍹\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"sound\",\n            \"key\": \" SND\",\n            \"keyColor\": \"cyan\"\n        },\n        {\n            \"type\": \"player\",\n            \"key\": \"│ ├󰥠\",\n            \"keyColor\": \"cyan\"\n        },\n        {\n            \"type\": \"media\",\n            \"key\": \"│ └󰝚\",\n            \"keyColor\": \"cyan\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"└{$1}{$1}┘\",\n            \"outputColor\": \"90\"\n        },\n        \"break\",\n        {\n            \"type\": \"custom\",\n            \"format\": \" {#90}  {#31}  {#32}  {#33}  {#34}  {#35}  {#36}  {#37}  {#38}  {#39}       {#38}  {#37}  {#36}  {#35}  {#34}  {#33}  {#32}  {#31}  {#90}\"\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/11.jsonc",
    "content": "{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"type\": \"small\"\n    },\n    \"display\": {\n        \"separator\": \"->   \",\n        \"color\": {\n            \"separator\": \"red\"\n        }\n    },\n    \"modules\": [\n        {\n            \"key\": \"Distro      \",\n            \"type\": \"os\"\n        },\n        {\n            \"key\": \"Shell       \",\n            \"type\": \"shell\"\n        },\n        {\n            \"key\": \"Terminal    \",\n            \"type\": \"terminal\"\n        },\n        {\n            \"key\": \"Display     \",\n            \"type\": \"display\"\n        },\n        {\n            \"key\": \"Backlight   \",\n            \"type\": \"brightness\"\n        },\n        \"break\",\n        {\n            \"type\": \"colors\",\n            \"paddingLeft\": 6,\n            \"symbol\": \"circle\"\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/12.jsonc",
    "content": "{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"type\": \"none\"\n    },\n    \"display\": {\n        \"separator\": \"->   \",\n        \"color\": {\n            \"separator\": \"1\" // Bold\n        },\n        \"constants\": [\n            \"───────────────────────────\"\n        ],\n        \"key\": {\n            \"type\": \"both\",\n            \"paddingLeft\": 4\n        }\n    },\n    \"modules\": [\n        {\n            \"type\": \"title\",\n            \"format\": \"                             {user-name-colored}{at-symbol-colored}{host-name-colored}\"\n        },\n        \"break\",\n        {\n            \"type\": \"custom\",\n            \"format\": \"┌{$1} {#1}System Information{#} {$1}┐\"\n        },\n        \"break\",\n        {\n            \"key\": \"OS           \",\n            \"keyColor\": \"red\",\n            \"type\": \"os\"\n        },\n        {\n            \"key\": \"Machine      \",\n            \"keyColor\": \"green\",\n            \"type\": \"host\"\n        },\n        {\n            \"key\": \"Kernel       \",\n            \"keyColor\": \"magenta\",\n            \"type\": \"kernel\"\n        },\n        {\n            \"key\": \"Uptime       \",\n            \"keyColor\": \"red\",\n            \"type\": \"uptime\"\n        },\n        {\n            \"key\": \"Resolution   \",\n            \"keyColor\": \"yellow\",\n            \"type\": \"display\",\n            \"compactType\": \"original-with-refresh-rate\"\n        },\n        {\n            \"key\": \"WM           \",\n            \"keyColor\": \"blue\",\n            \"type\": \"wm\"\n        },\n        {\n            \"key\": \"DE           \",\n            \"keyColor\": \"green\",\n            \"type\": \"de\"\n        },\n        {\n            \"key\": \"Shell        \",\n            \"keyColor\": \"cyan\",\n            \"type\": \"shell\"\n        },\n        {\n            \"key\": \"Terminal     \",\n            \"keyColor\": \"red\",\n            \"type\": \"terminal\"\n        },\n        {\n            \"key\": \"CPU          \",\n            \"keyColor\": \"yellow\",\n            \"type\": \"cpu\"\n        },\n        {\n            \"key\": \"GPU          \",\n            \"keyColor\": \"blue\",\n            \"type\": \"gpu\"\n        },\n        {\n            \"key\": \"Memory       \",\n            \"keyColor\": \"magenta\",\n            \"type\": \"memory\"\n        },\n        {\n            \"key\": \"Local IP     \",\n            \"keyColor\": \"red\",\n            \"type\": \"localip\",\n            \"compact\": true\n        },\n        {\n            \"key\": \"Public IP    \",\n            \"keyColor\": \"cyan\",\n            \"type\": \"publicip\",\n            \"timeout\": 1000\n        },\n        \"break\",\n        {\n            \"type\": \"custom\",\n            \"format\": \"└{$1}────────────────────{$1}┘\"\n        },\n        \"break\",\n        {\n            \"type\": \"colors\",\n            \"paddingLeft\": 34,\n            \"symbol\": \"circle\"\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/13.jsonc",
    "content": "// Inspired by Catnap\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"type\": \"small\",\n        \"padding\": {\n            \"top\": 1\n        }\n    },\n    \"display\": {\n        \"separator\": \" \"\n    },\n    \"modules\": [\n        {\n            \"key\": \"╭───────────╮\",\n            \"type\": \"custom\"\n        },\n        {\n            \"key\": \"│ {#31} user    {#keys}│\",\n            \"type\": \"title\",\n            \"format\": \"{user-name}\"\n        },\n        {\n            \"key\": \"│ {#32}󰇅 hname   {#keys}│\",\n            \"type\": \"title\",\n            \"format\": \"{host-name}\"\n        },\n        {\n            \"key\": \"│ {#33}󰅐 uptime  {#keys}│\",\n            \"type\": \"uptime\"\n        },\n        {\n            \"key\": \"│ {#34}{icon} distro  {#keys}│\",\n            \"type\": \"os\"\n        },\n        {\n            \"key\": \"│ {#35} kernel  {#keys}│\",\n            \"type\": \"kernel\"\n        },\n        {\n            \"key\": \"│ {#36}󰇄 desktop {#keys}│\",\n            \"type\": \"de\"\n        },\n        {\n            \"key\": \"│ {#31} term    {#keys}│\",\n            \"type\": \"terminal\"\n        },\n        {\n            \"key\": \"│ {#32} shell   {#keys}│\",\n            \"type\": \"shell\"\n        },\n        {\n            \"key\": \"│ {#33}󰍛 cpu     {#keys}│\",\n            \"type\": \"cpu\",\n            \"showPeCoreCount\": true\n        },\n        {\n            \"key\": \"│ {#34}󰉉 disk    {#keys}│\",\n            \"type\": \"disk\",\n            \"folders\": \"/\"\n        },\n        {\n            \"key\": \"│ {#35} memory  {#keys}│\",\n            \"type\": \"memory\"\n        },\n        {\n            \"key\": \"│ {#36}󰩟 network {#keys}│\",\n            \"type\": \"localip\",\n            \"format\": \"{ipv4} ({ifname})\"\n        },\n        {\n            \"key\": \"├───────────┤\",\n            \"type\": \"custom\"\n        },\n        {\n            \"key\": \"│ {#39} colors  {#keys}│\",\n            \"type\": \"colors\",\n            \"symbol\": \"circle\"\n        },\n        {\n            \"key\": \"╰───────────╯\",\n            \"type\": \"custom\"\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/14.jsonc",
    "content": "// Inspired by Catnap\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"type\": \"small\"\n    },\n    \"display\": {\n        \"separator\": \"\",\n        \"key\": {\n            \"width\": 15\n        }\n    },\n    \"modules\": [\n        {\n            // draw borders first to make colors of left and right border consistant\n            \"key\": \" user\",\n            \"type\": \"title\",\n            \"format\": \"{user-name}\",\n            \"keyColor\": \"31\"\n        },\n        {\n            \"key\": \"󰇅 hname\",\n            \"type\": \"title\",\n            \"format\": \"{host-name}\",\n            \"keyColor\": \"32\"\n\n        },\n        {\n            \"key\": \"󰅐 uptime\",\n            \"type\": \"uptime\",\n            \"keyColor\": \"33\"\n        },\n        {\n            \"key\": \"{icon} distro\",\n            \"type\": \"os\",\n            \"keyColor\": \"34\"\n        },\n        {\n            \"key\": \" kernel\",\n            \"type\": \"kernel\",\n            \"keyColor\": \"35\"\n        },\n        {\n            \"key\": \"󰇄 desktop\",\n            \"type\": \"de\",\n            \"keyColor\": \"36\"\n        },\n        {\n            \"key\": \" term\",\n            \"type\": \"terminal\",\n            \"keyColor\": \"31\"\n        },\n        {\n            \"key\": \" shell\",\n            \"type\": \"shell\",\n            \"keyColor\": \"32\"\n        },\n        {\n            \"key\": \"󰍛 cpu\",\n            \"type\": \"cpu\",\n            \"showPeCoreCount\": true,\n            \"keyColor\": \"33\"\n        },\n        {\n            \"key\": \"󰉉 disk\",\n            \"type\": \"disk\",\n            \"folders\": \"/\",\n            \"keyColor\": \"34\"\n        },\n        {\n            \"key\": \" memory\",\n            \"type\": \"memory\",\n            \"keyColor\": \"35\"\n        },\n        {\n            \"key\": \"󰩟 network\",\n            \"type\": \"localip\",\n            \"format\": \"{ipv4} ({ifname})\",\n            \"keyColor\": \"36\"\n        },\n        {\n            \"key\": \" colors\",\n            \"type\": \"colors\",\n            \"symbol\": \"circle\",\n            \"keyColor\": \"39\"\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/15.jsonc",
    "content": "// Inspired by Catnap\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"type\": \"small\",\n        \"padding\": {\n            \"top\": 1\n        }\n    },\n    \"display\": {\n        \"separator\": \" \"\n    },\n    \"modules\": [\n        {\n            \"key\": \"•••••••••••••\",\n            \"type\": \"custom\"\n        },\n        {\n            \"key\": \"• {#31} user    {#keys}•\",\n            \"type\": \"title\",\n            \"format\": \"{user-name}\"\n        },\n        {\n            \"key\": \"• {#32}󰇅 hname   {#keys}•\",\n            \"type\": \"title\",\n            \"format\": \"{host-name}\"\n        },\n        {\n            \"key\": \"• {#33}󰅐 uptime  {#keys}•\",\n            \"type\": \"uptime\"\n        },\n        {\n            \"key\": \"• {#34}{icon} distro  {#keys}•\",\n            \"type\": \"os\"\n        },\n        {\n            \"key\": \"• {#35} kernel  {#keys}•\",\n            \"type\": \"kernel\"\n        },\n        {\n            \"key\": \"• {#36}󰇄 desktop {#keys}•\",\n            \"type\": \"de\"\n        },\n        {\n            \"key\": \"• {#31} term    {#keys}•\",\n            \"type\": \"terminal\"\n        },\n        {\n            \"key\": \"• {#32} shell   {#keys}•\",\n            \"type\": \"shell\"\n        },\n        {\n            \"key\": \"• {#33}󰍛 cpu     {#keys}•\",\n            \"type\": \"cpu\",\n            \"showPeCoreCount\": true\n        },\n        {\n            \"key\": \"• {#34}󰉉 disk    {#keys}•\",\n            \"type\": \"disk\",\n            \"folders\": \"/\"\n        },\n        {\n            \"key\": \"• {#35} memory  {#keys}•\",\n            \"type\": \"memory\"\n        },\n        {\n            \"key\": \"• {#36}󰩟 network {#keys}•\",\n            \"type\": \"localip\",\n            \"format\": \"{ipv4} ({ifname})\"\n        },\n        {\n            \"key\": \"•••••••••••••\",\n            \"type\": \"custom\"\n        },\n        {\n            \"key\": \"• {#39} colors  {#keys}•\",\n            \"type\": \"colors\",\n            \"symbol\": \"circle\"\n        },\n        {\n            \"key\": \"•••••••••••••\",\n            \"type\": \"custom\"\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/16.jsonc",
    "content": "// Inspired by Catnap\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"type\": \"small\",\n        \"padding\": {\n            \"top\": 1\n        }\n    },\n    \"display\": {\n        \"separator\": \" \"\n    },\n    \"modules\": [\n        {\n            \"key\": \"╔═══════════╗\",\n            \"type\": \"custom\"\n        },\n        {\n            \"key\": \"║ {#31} user    {#keys}║\",\n            \"type\": \"title\",\n            \"format\": \"{user-name}\"\n        },\n        {\n            \"key\": \"║ {#32}󰇅 hname   {#keys}║\",\n            \"type\": \"title\",\n            \"format\": \"{host-name}\"\n        },\n        {\n            \"key\": \"║ {#33}󰅐 uptime  {#keys}║\",\n            \"type\": \"uptime\"\n        },\n        {\n            \"key\": \"║ {#34}{icon} distro  {#keys}║\",\n            \"type\": \"os\"\n        },\n        {\n            \"key\": \"║ {#35} kernel  {#keys}║\",\n            \"type\": \"kernel\"\n        },\n        {\n            \"key\": \"║ {#36}󰇄 desktop {#keys}║\",\n            \"type\": \"de\"\n        },\n        {\n            \"key\": \"║ {#31} term    {#keys}║\",\n            \"type\": \"terminal\"\n        },\n        {\n            \"key\": \"║ {#32} shell   {#keys}║\",\n            \"type\": \"shell\"\n        },\n        {\n            \"key\": \"║ {#33}󰍛 cpu     {#keys}║\",\n            \"type\": \"cpu\",\n            \"showPeCoreCount\": true\n        },\n        {\n            \"key\": \"║ {#34}󰉉 disk    {#keys}║\",\n            \"type\": \"disk\",\n            \"folders\": \"/\"\n        },\n        {\n            \"key\": \"║ {#35} memory  {#keys}║\",\n            \"type\": \"memory\"\n        },\n        {\n            \"key\": \"║ {#36}󰩟 network {#keys}║\",\n            \"type\": \"localip\",\n            \"format\": \"{ipv4} ({ifname})\"\n        },\n        {\n            \"key\": \"╠═══════════╣\",\n            \"type\": \"custom\"\n        },\n        {\n            \"key\": \"║ {#39} colors  {#keys}║\",\n            \"type\": \"colors\",\n            \"symbol\": \"circle\"\n        },\n        {\n            \"key\": \"╚═══════════╝\",\n            \"type\": \"custom\"\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/17.jsonc",
    "content": "{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"type\": \"small\",\n        \"padding\": {\n            \"top\": 1,\n            \"right\": 2\n        }\n    },\n    \"display\": {\n        \"separator\": \">  \",\n        \"color\": {\n            \"separator\": \"red\"\n        },\n        \"constants\": [\n            \"───────────────────────────────────────────────────────────────────────────\",\n            \"│\\u001b[75C│\\u001b[75D\"\n        ]\n    },\n    \"modules\": [\n        {\n            \"format\": \"{#1}{#keys}╭{$1}╮\\u001b[76D {user-name-colored}{at-symbol-colored}{host-name-colored} 🖥  \",\n            \"type\": \"title\"\n        },\n        {\n            \"key\": \"{$2}{#31} kernel   \",\n            \"type\": \"kernel\"\n        },\n        {\n            \"key\": \"{$2}{#32}󰅐 uptime   \",\n            \"type\": \"uptime\"\n        },\n        {\n            \"key\": \"{$2}{#33}{icon} distro   \",\n            \"type\": \"os\"\n        },\n        {\n            \"key\": \"{$2}{#34}󰇄 desktop  \",\n            \"type\": \"de\"\n        },\n        {\n            \"key\": \"{$2}{#35} term     \",\n            \"type\": \"terminal\"\n        },\n        {\n            \"key\": \"{$2}{#36} shell    \",\n            \"type\": \"shell\"\n        },\n        {\n            \"key\": \"{$2}{#35}󰍛 cpu      \",\n            \"type\": \"cpu\",\n            \"showPeCoreCount\": true,\n            \"temp\": true\n        },\n        {\n            \"key\": \"{$2}{#34}󰍛 gpu      \",\n            \"type\": \"gpu\"\n        },\n        {\n            \"key\": \"{$2}{#33}󰉉 disk     \",\n            \"type\": \"disk\",\n            \"folders\": \"/\"\n        },\n        {\n            \"key\": \"{$2}{#32} memory   \",\n            \"type\": \"memory\"\n        },\n        {\n            \"key\": \"{$2}{#31}󰩟 network  \",\n            \"type\": \"localip\",\n            \"format\": \"{ipv4} ({ifname})\"\n        },\n        {\n            \"format\": \"{#1}{#keys}├{$1}┤\",\n            \"type\": \"custom\"\n        },\n        {\n            \"key\": \"{$2}{#39} colors   \",\n            \"type\": \"colors\",\n            \"symbol\": \"circle\"\n        },\n        {\n            \"format\": \"{#1}{#keys}╰{$1}╯\",\n            \"type\": \"custom\"\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/18.jsonc",
    "content": "{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"type\": \"small\",\n        \"padding\": {\n            \"top\": 1,\n            \"right\": 2\n        }\n    },\n    \"display\": {\n        \"separator\": \">  \",\n        \"color\": {\n            \"separator\": \"red\"\n        },\n        \"constants\": [\n            \"═══════════════════════════════════════════════════════════════════════════\",\n            \"║\\u001b[75C║\\u001b[75D\"\n        ]\n    },\n    \"modules\": [\n        {\n            \"format\": \"{#1}{#keys}╔{$1}╗\\u001b[76D {user-name-colored}{at-symbol-colored}{host-name-colored} 💻 \",\n            \"type\": \"title\"\n        },\n        {\n            \"key\": \"{$2}{#31} kernel   \",\n            \"type\": \"kernel\"\n        },\n        {\n            \"key\": \"{$2}{#32}󰅐 uptime   \",\n            \"type\": \"uptime\"\n        },\n        {\n            \"key\": \"{$2}{#33}{icon} distro   \",\n            \"type\": \"os\"\n        },\n        {\n            \"key\": \"{$2}{#34}󰇄 desktop  \",\n            \"type\": \"de\"\n        },\n        {\n            \"key\": \"{$2}{#35} term     \",\n            \"type\": \"terminal\"\n        },\n        {\n            \"key\": \"{$2}{#36} shell    \",\n            \"type\": \"shell\"\n        },\n        {\n            \"key\": \"{$2}{#35}󰍛 cpu      \",\n            \"type\": \"cpu\",\n            \"showPeCoreCount\": true,\n            \"temp\": true\n        },\n        {\n            \"key\": \"{$2}{#34}󰍛 gpu      \",\n            \"type\": \"gpu\"\n        },\n        {\n            \"key\": \"{$2}{#33}󰉉 disk     \",\n            \"type\": \"disk\",\n            \"folders\": \"/\"\n        },\n        {\n            \"key\": \"{$2}{#32} memory   \",\n            \"type\": \"memory\"\n        },\n        {\n            \"key\": \"{$2}{#31}󰩟 network  \",\n            \"type\": \"localip\",\n            \"format\": \"{ipv4} ({ifname})\"\n        },\n        {\n            \"format\": \"{#1}{#keys}╠{$1}╣\",\n            \"type\": \"custom\"\n        },\n        {\n            \"key\": \"{$2}{#39} colors   \",\n            \"type\": \"colors\",\n            \"symbol\": \"circle\"\n        },\n        {\n            \"format\": \"{#1}{#keys}╚{$1}╝\",\n            \"type\": \"custom\"\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/19.jsonc",
    "content": "//   _____ _____ _____ _____ _____ _____ _____ _____ _____\n//  |   __|  _  |   __|_   _|   __|   __|_   _|     |  |  |\n//  |   __|     |__   | | | |   __|   __| | | |   --|     |\n//  |__|  |__|__|_____| |_| |__|  |_____| |_| |_____|__|__|\n//\n//  By CarterLi - https://github.com/CarterLi\n//  Homepage - https://github.com/fastfetch-cli/fastfetch\n//  config.jsonc - ニリ @niri-san\n//  pokemon-colorscripts - https://gitlab.com/phoneybadger/pokemon-colorscripts\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"source\": \" _____ _____ _____ _____ _____ _____ _____ _____ _____\\n|   __|  _  |   __|_   _|   __|   __|_   _|     |  |  |\\n|   __|     |__   | | | |   __|   __| | | |   --|     |\\n|__|  |__|__|_____| |_| |__|  |_____| |_| |_____|__|__|\",\n        \"type\": \"data\",\n        \"position\": \"top\",\n        \"padding\": {\n            \"right\": 2\n        }\n    },\n    \"display\": {\n        \"separator\": \" - \"\n    },\n    \"modules\": [\n        {\n            \"type\": \"custom\", // HardwareInfo\n            \"format\": \"• {#green}SYSTEM INFORMATION\"\n        },\n        {\n            \"type\": \"host\",\n            \"key\": \"HOST\",\n            \"format\": \"{name}{?vendor} ({vendor}){?}\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"chassis\",\n            \"key\": \"COMPUTER TYPE\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"cpu\",\n            \"key\": \"CPU\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"gpu\",\n            \"key\": \"GPU\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"memory\",\n            \"key\": \"MEMORY USED\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"swap\",\n            \"key\": \"SWAP USED\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"disk\",\n            \"key\": \"DISK\",\n            \"folders\": \"/\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"custom\", // SoftwareInfo\n            \"format\": \"• {#red}SOFTWARE INFORMATION\"\n        },\n        {\n            \"type\": \"os\",\n            \"key\": \"DISTRO\",\n            \"keyColor\": \"red\"\n        },\n        {\n            \"type\": \"disk\",\n            \"folders\": \"/\", // Use \"/System/Volumes/VM\" or something else on macOS\n            \"format\": \"{create-time}\",\n            \"key\": \"INSTALLED DATE\",\n            \"keyColor\": \"red\"\n        },\n        {\n            \"type\": \"kernel\",\n            \"key\": \"KERNEL\",\n            \"keyColor\": \"red\"\n        },\n        {\n            \"type\": \"packages\",\n            \"key\": \"PACKAGES\",\n            \"keyColor\": \"red\"\n        },\n        {\n            \"type\": \"uptime\",\n            \"key\": \"UPTIME\",\n            \"keyColor\": \"red\"\n        },\n        {\n            \"type\": \"custom\", // DisplayInfo\n            \"format\": \"• {#blue}DISPLAY INFORMATION\"\n        },\n        {\n            \"type\": \"de\",\n            \"key\": \"DESKTOP ENVIRONMENT\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"lm\",\n            \"key\": \"LOGIN MANAGER\",\n            \"format\": \"{type}\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"wm\",\n            \"key\": \"WM\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"wmtheme\",\n            \"key\": \"WM THEME\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"display\",\n            \"key\": \"MONITOR ({name})\",\n            \"keyColor\": \"blue\",\n            \"format\": \"{width}x{height} @ {refresh-rate} Hz - {physical-width}x{physical-height} mm ({inch} inches, {ppi} ppi)\"\n        },\n        {\n            \"type\": \"custom\", // DesignInfo\n            \"format\": \"• {#yellow}DESIGN INFORMATION\"\n        },\n        {\n            \"type\": \"wallpaper\",\n            \"key\": \"WALLPAPER\",\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"theme\",\n            \"key\": \"KDE THEME\",\n            \"format\": \"{1}\",\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"icons\",\n            \"key\": \"ICON THEME\",\n            \"format\": \"{1}\",\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"font\",\n            \"key\": \"FONT\",\n            \"format\": \"{?1}{1} [Qt]{?}{/1}Unknown\", // Remove \"[Qt]\" if not using Qt\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"terminalfont\",\n            \"key\": \"TERMINAL FONT\",\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"cursor\",\n            \"key\": \"CURSOR\",\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"custom\", // OtherInfo\n            \"format\": \"• {#cyan}VARIOUS INFORMATION\"\n        },\n        {\n            \"type\": \"media\",\n            \"key\": \"NOW PLAYING\",\n            \"format\": \"{?artist}{artist} - {?}{title}\",\n            \"keyColor\": \"cyan\"\n        },\n        {\n            \"type\": \"weather\",\n            \"key\": \"WEATHER\",\n            \"timeout\": 1000,\n            \"keyColor\": \"cyan\"\n        },\n        {\n            \"type\": \"version\",\n            \"key\": \"INFO\",\n            \"keyColor\": \"cyan\"\n        },\n        \"break\",\n        \"colors\",\n        \"break\"\n    ]\n}\n"
  },
  {
    "path": "presets/examples/2.jsonc",
    "content": "// Load with --config examples/2.jsonc\n// Note that you must replace the image path to an existing image to display it.\n\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    // \"logo\": {\n    //     \"type\": \"iterm\",\n    //     \"source\": \"~/Desktop/apple1.png\",\n    //     \"width\": 28,\n    //     \"height\": 12\n    // },\n    \"display\": {\n        \"separator\": \"  \",\n        \"constants\": [\n            \"─────────────────\" // {$1}, used in Custom module\n        ],\n        \"key\": {\n            \"type\": \"icon\",\n            \"paddingLeft\": 2\n        }\n    },\n    \"modules\": [\n        {\n            \"type\": \"custom\", // HardwareStart\n            // {#1} is equivalent to `\\u001b[1m`. {#} is equivalent to `\\u001b[m`\n            \"format\": \"┌{$1} {#1}Hardware Information{#} {$1}┐\"\n        },\n        \"host\",\n        \"cpu\",\n        \"gpu\",\n        \"disk\",\n        \"memory\",\n        \"swap\",\n        \"display\",\n        \"brightness\",\n        \"battery\",\n        \"poweradapter\",\n        \"bluetooth\",\n        \"sound\",\n        \"gamepad\",\n        {\n            \"type\": \"custom\", // SoftwareStart\n            \"format\": \"├{$1} {#1}Software Information{#} {$1}┤\"\n        },\n        {\n            \"type\": \"title\",\n            \"keyIcon\": \"\",\n            \"key\": \"Title\", // Title module has no key by default, so that icon is not displayed\n            \"format\": \"{user-name}@{host-name}\"\n        },\n        \"os\",\n        \"kernel\",\n        \"lm\",\n        \"de\",\n        \"wm\",\n        \"shell\",\n        \"terminal\",\n        \"terminalfont\",\n        \"theme\",\n        \"icons\",\n        \"wallpaper\",\n        \"packages\",\n        \"uptime\",\n        \"media\",\n        {\n            \"type\": \"localip\",\n            \"compact\": true\n        },\n        {\n            \"type\": \"publicip\",\n            \"timeout\": 1000\n        },\n        {\n            \"type\": \"wifi\",\n            \"format\": \"{ssid}\"\n        },\n        \"locale\",\n        {\n            \"type\": \"custom\", // InformationEnd\n            \"format\": \"└{$1}──────────────────────{$1}┘\"\n        },\n        {\n            \"type\": \"colors\",\n            \"paddingLeft\": 2,\n            \"symbol\": \"circle\"\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/20.jsonc",
    "content": "// Inspired by https://github.com/usgraphics/TR-100\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": null,\n    \"display\": {\n        \"pipe\": true,\n        \"key\": {\n            \"width\": 16\n        },\n        \"separator\": \"│ \",\n        \"percent\": {\n            \"type\": [\"bar\", \"hide-others\"]\n        },\n        \"bar\": {\n            \"border\": null,\n            \"char\": {\n                \"elapsed\": \"█\",\n                \"total\": \"░\"\n            },\n            \"width\": 40\n        },\n        \"constants\": [\n            \"\\u001b[42C\"\n        ]\n    },\n    \"modules\": [\n        {\n            \"type\": \"custom\",\n            \"format\": \"┌┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┐\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"├┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┤\"\n        },\n        {\n            \"type\": \"version\",\n            \"key\": \" \",\n            \"format\": \"│                   FASTFETCH v{version}                   │\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"│                 TR-100 MACHINE REPORT                 │\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"├────────────┬──────────────────────────────────────────┤\"\n        },\n        {\n            \"type\": \"os\",\n            \"key\": \"│ OS         │{$1}\"\n        },\n        {\n            \"type\": \"kernel\",\n            \"key\": \"│ KERNEL     │{$1}\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"├────────────┼──────────────────────────────────────────┤\"\n        },\n        {\n            \"type\": \"title\",\n            \"key\": \"│ HOSTNAME   │{$1}\",\n            \"format\": \"{host-name}\"\n        },\n        {\n            \"type\": \"localip\",\n            \"key\": \"│ CLIENT IP  │{$1}\",\n            \"format\": \"{ipv4}\"\n        },\n        {\n            \"type\": \"localip\",\n            \"key\": \"│ MAC ADDR   │{$1}\",\n            \"format\": \"{mac} ({ifname})\",\n            \"showIpv4\": false,\n            \"showMac\": true\n        },\n        {\n            \"type\": \"dns\",\n            \"key\": \"│ DNS        │{$1}\",\n            \"showType\": \"ipv4\"\n        },\n        {\n            \"type\": \"title\",\n            \"key\": \"│ USER       │{$1}\",\n            \"format\": \"{user-name}\"\n        },\n        {\n            \"type\": \"host\",\n            \"key\": \"│ MACHINE    │{$1}\",\n            \"format\": \"{name}\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"├────────────┼──────────────────────────────────────────┤\"\n        },\n        {\n            \"type\": \"cpu\",\n            \"key\": \"│ PROCESSOR  │{$1}\",\n            \"format\": \"{name}\"\n        },\n        {\n            \"type\": \"cpu\",\n            \"key\": \"│ CORES      │{$1}\",\n            \"format\": \"{cores-physical} PHYSICAL CORES / {cores-logical} THREADS\",\n            \"showPeCoreCount\": false\n        },\n        {\n            \"type\": \"cpu\",\n            \"key\": \"│ CPU FREQ   │{$1}\",\n            \"format\": \"{freq-max}{/freq-max}{freq-base}{/}\"\n        },\n        {\n            \"type\": \"loadavg\",\n            \"compact\": false,\n            \"key\": \"│ LOAD  {duration>2}m  │{$1}\" // pad duration to 2 chars\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"├────────────┼──────────────────────────────────────────┤\"\n        },\n        {\n            \"type\": \"memory\",\n            \"key\": \"│ MEMORY     │{$1}\",\n            \"format\": \"{used} / {total} [{percentage}]\",\n            \"percent\": {\n                \"type\": [\"num\"]\n            }\n        },\n        {\n            \"type\": \"memory\",\n            \"key\": \"│ USAGE      │{$1}\",\n            \"format\": \"\",\n            \"percent\": {\n                \"type\": [\"bar\", \"hide-others\"]\n            }\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"├────────────┼──────────────────────────────────────────┤\"\n        },\n        {\n            \"type\": \"disk\",\n            \"key\": \"│ VOLUME     │{$1}\",\n            \"format\": \"{size-used} / {size-total} [{size-percentage}]\",\n            \"folders\": \"/\",\n            \"percent\": {\n                \"type\": [\"num\"]\n            }\n        },\n        {\n            \"type\": \"disk\",\n            \"key\": \"│ DISK USAGE │{$1}\",\n            \"format\": \"\",\n            \"percent\": {\n                \"type\": [\"bar\", \"hide-others\"]\n            }\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"├────────────┼──────────────────────────────────────────┤\"\n        },\n        {\n            \"type\": \"users\",\n            \"key\": \"│ LAST LOGIN │{$1}\",\n            \"format\": \"{login-time}{?client-ip} ({client-ip})\",\n            \"myselfOnly\": true\n        },\n        {\n            \"type\": \"uptime\",\n            \"key\": \"│ UPTIME     │{$1}\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"└────────────┴──────────────────────────────────────────┘\"\n        }\n    ]\n  }\n"
  },
  {
    "path": "presets/examples/21.jsonc",
    "content": "{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"type\": \"small\"\n    },\n    \"display\": {\n        \"constants\": [\n            \"██ \"\n        ]\n    },\n    \"modules\": [\n        {\n            \"key\": \"{$1}Distro\",\n            \"keyColor\": \"38;5;210\",\n            \"type\": \"os\"\n        },\n        {\n            \"key\": \"{$1}Kernel\",\n            \"keyColor\": \"38;5;84\",\n            \"type\": \"kernel\"\n        },\n        {\n            \"key\": \"{$1}Shell\",\n            \"keyColor\": \"38;5;147\",\n            \"type\": \"shell\"\n        },\n        {\n            \"key\": \"{$1}Packages\",\n            \"keyColor\": \"38;5;200\",\n            \"type\": \"packages\"\n        },\n        {\n            \"key\": \"{$1}WM\",\n            \"keyColor\": \"38;5;44\",\n            \"type\": \"wm\"\n        },\n        {\n            \"key\": \"{$1}CPU\",\n            \"keyColor\": \"38;5;75\",\n            \"type\": \"cpu\"\n        },\n        {\n            \"key\": \"{$1}Memory\",\n            \"keyColor\": \"38;5;123\",\n            \"type\": \"memory\"\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/22.jsonc",
    "content": "// Designed for Arch Linux\n// Modified from: https://github.com/fastfetch-cli/fastfetch/pull/1025#issuecomment-2177566138\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"source\": \"arch3\",\n        \"color\": {\n            \"1\": \"red\",\n            \"2\": \"yellow\"\n        }\n    },\n    \"display\": {\n        \"color\": {\n            \"separator\": \"blue\"\n        },\n        \"separator\": \" | \",\n        \"constants\": [\n            \">-----------<+>---------------------------------------------<\"\n        ]\n    },\n    \"modules\": [\n        {\n            \"type\": \"kernel\",\n            \"key\": \" /\\\\rch Linux\",\n            \"keyColor\": \"magenta\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{$1}\",\n            \"outputColor\": \"separator\"\n        },\n        {\n            \"type\": \"uptime\",\n            \"key\": \"   Uptime   \",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"shell\",\n            \"key\": \"   Shell    \",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"terminal\",\n            \"key\": \"   Terminal \",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"terminalfont\",\n            \"key\": \"   Font     \",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"packages\",\n            \"key\": \"   Packages \",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"localip\",\n            \"key\": \"   Local IP \",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{$1}\",\n            \"outputColor\": \"separator\"\n        },\n        {\n            \"type\": \"display\",\n            \"key\": \"   Display  \",\n            \"keyColor\": \"cyan\"\n        },\n        {\n            \"type\": \"cpu\",\n            \"key\": \"   CPU      \",\n            \"keyColor\": \"cyan\"\n        },\n        {\n            \"type\": \"gpu\",\n            \"key\": \"   GPU      \",\n            \"keyColor\": \"cyan\"\n        },\n        {\n            \"type\": \"memory\",\n            \"key\": \"   RAM      \",\n            \"keyColor\": \"cyan\"\n        },\n        {\n            \"type\": \"swap\",\n            \"key\": \"   SWAP     \",\n            \"keyColor\": \"cyan\"\n        },\n        {\n            \"type\": \"disk\",\n            \"key\": \"   Disk     \",\n            \"keyColor\": \"cyan\"\n        },\n        {\n            \"type\": \"battery\",\n            \"key\": \"   Battery  \",\n            \"keyColor\": \"cyan\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{$1}\",\n            \"outputColor\": \"separator\"\n        },\n        \"break\",\n        {\n            \"type\": \"colors\",\n            \"paddingLeft\": 15\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/23.jsonc",
    "content": "// designed for presenting Vanilla Linux\n// inspired from imstilllearnin's Vanilla Logo Ultra\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"type\": \"small\"\n    },\n    \"display\": {\n        \"color\": {\n            \"output\": \"cyan\"\n        },\n        \"separator\": \"\"\n    },\n    \"modules\": [\n        {\n            \"type\": \"kernel\",\n            \"key\": \"[_Kernel___> \",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"packages\",\n            \"outputColor\": \"white\",\n            \"key\": \" [_Packages_> \",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"localip\",\n            \"outputColor\": \"white\",\n            \"key\": \" [_Local_IP_> \",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"memory\",\n            \"format\": \"[{3}] {1} / {2}\",\n            \"key\": \" [_RAM______> \",\n            \"keyColor\": \"magenta\"\n        },\n        {\n            \"type\": \"swap\",\n            \"format\": \"[{3}] {1} / {2}\",\n            \"key\": \" [_SWAP_____> \",\n            \"keyColor\": \"magenta\"\n        },\n        {\n            \"type\": \"disk\",\n            \"format\": \"[{3}] {1} / {2} {9}\",\n            \"key\": \" [_Disk_____> \",\n            \"keyColor\": \"magenta\"\n        },\n        {\n            \"type\": \"battery\",\n            \"format\": \"[{4}] {5}\",\n            \"key\": \" [_Battery__> \",\n            \"keyColor\": \"magenta\"\n        },\n        \"break\",\n        {\n            \"type\": \"colors\",\n            \"paddingLeft\": 9,\n            \"symbol\": \"circle\"\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/24.jsonc",
    "content": "// By jan-rex\n// Modified from: https://github.com/fastfetch-cli/fastfetch/discussions/1269\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n      \"padding\": {\n        \"top\": 2\n      }\n    },\n    \"display\": {\n      \"separator\": \"\",\n      \"constants\": [\n        // CONSTANT {$1} - COLOR BACKGROUND FOR KEY\n        \"\\u001b[48;2;43;43;69m\",\n        // CONSTANT {$2} - COLOR BACKGROUND FOR OUTPUT\n        \"\\u001b[48;2;56;59;78m\",\n        // CONSTANT {$3} - VERTICAL BARS AT START AND 75th CHARACTERS FORWARD AND BACKWARD\n        \"\\u001b[90m│                                                            │\\u001b[60D\\u001b[39m\",\n      ]\n    },\n    \"modules\": [\n      // CUSTOM - Top UI bar\n      {\n        \"type\": \"custom\",\n        \"key\": \"{#90}{$1}╭─────────────╮\",\n        \"format\": \"{#90}{$2}╭────────────────────────────────────────────────────────────╮\",\n      },\n      {\n        \"type\": \"title\",\n        \"key\": \"{#90}{$1}│ {#92}User        {#90}│\",\n        \"format\": \"{$2}{$3}{user-name}  {#2}[{home-dir}]\"\n      },\n      {\n        \"type\": \"users\",\n        \"key\": \"{#90}{$1}│ {#92}Users       {#90}│\",\n        \"myselfOnly\": false,\n        \"format\": \"{$2}{$3}{1}@{host-name}{/host-name}localhost{/}{?client-ip}  {#2}[IP:{client-ip}]{?}  [Login time: {login-time}]\"\n      },\n      {\n        \"type\": \"datetime\",\n        \"key\": \"{#90}{$1}│ {#92}Datetime    {#90}│\",\n        \"format\": \"{$2}{$3}{year}-{month-pretty}-{day-in-month} {hour-pretty}:{minute-pretty}:{second-pretty}  [{weekday}] [W{week}] [UTC{offset-from-utc}]\"\n      },\n      {\n        \"type\": \"title\",\n        \"key\": \"{#90}{$1}│ {#93}Host        {#90}│\",\n        \"format\": \"{$2}{$3}{host-name}\"\n      },\n      {\n        \"type\": \"host\",\n        \"key\": \"{#90}{$1}│ {#93}Machine     {#90}│\",\n        \"format\": \"{$2}{$3}{name}  {#2}{version}\"\n      },\n      {\n        \"type\": \"os\",\n        \"key\": \"{#90}{$1}│ {#93}OS          {#90}│\",\n        \"format\": \"{$2}{$3}{?pretty-name}{pretty-name}{?}{/pretty-name}{name}{/} {codename}  {#2}[v{version}] [{arch}]\"\n      },\n      {\n        \"type\": \"kernel\",\n        \"key\": \"{#90}{$1}│ {#93}Kernel      {#90}│\",\n        \"format\": \"{$2}{$3}{sysname}  {#2}[v{release}]\"\n      },\n      {\n        \"type\": \"uptime\",\n        \"key\": \"{#90}{$1}│ {#93}Uptime      {#90}│\",\n        \"format\": \"{$2}{$3}{?days}{days} Days + {?}{hours}:{minutes}:{seconds}\"\n      },\n      {\n        \"type\": \"cpu\",\n        \"key\": \"{#90}{$1}│ {#91}CPU         {#90}│\",\n        \"showPeCoreCount\": true,\n        \"temp\": true,\n        \"format\": \"{$2}{$3}{name}  {#2}[C:{core-types}] [{freq-max}]\"\n      },\n      {\n        \"type\": \"gpu\",\n        \"key\": \"{#90}{$1}│ {#91}GPU         {#90}│\",\n        \"detectionMethod\": \"auto\",\n        \"driverSpecific\": true,\n        \"format\": \"{$2}{$3}{name}  {#2}[C:{core-count}]{?frequency} [{frequency}]{?} [{type}]\"\n      },\n      {\n        \"type\": \"memory\",\n        \"key\": \"{#90}{$1}│ {#91}Memory      {#90}│\",\n        \"format\": \"{$2}{$3}{used} / {total} ({percentage}{$2})\"\n      },\n      {\n        \"type\": \"disk\",\n        \"key\": \"{#90}{$1}│ {#91}Disk        {#90}│\",\n        \"format\": \"{$2}{$3}{size-used} / {size-total} ({size-percentage}{$2})\"\n      },\n      {\n        \"type\": \"poweradapter\",\n        \"key\": \"{#90}{$1}│ {#91}Power       {#90}│\",\n        \"format\": \"{$2}{$3}{name}\"\n      },\n      {\n        \"type\": \"terminal\",\n        \"key\": \"{#90}{$1}│ {#95}Terminal    {#90}│\",\n        \"format\": \"{$2}{$3}{pretty-name}  {#2}[{version}] [PID:{pid}]\"\n      },\n      {\n        \"type\": \"terminalfont\",\n        \"key\": \"{#90}{$1}│ {#95}Font        {#90}│\",\n        \"format\": \"{$2}{$3}{name}  {#2}[{size}]\"\n      },\n      {\n        \"type\": \"shell\",\n        \"key\": \"{#90}{$1}│ {#95}Shell       {#90}│\",\n        \"format\": \"{$2}{$3}{pretty-name}  {#2}[v{version}] [PID:{pid}]\"\n      },\n      {\n        // localip IPv4\n        \"type\": \"localip\",\n        \"key\": \"{#90}{$1}│ {#94}Local IPv4  {#90}│\",\n        \"showPrefixLen\": true,\n        \"showIpv4\": true,\n        \"showIpv6\": false,\n        \"showMtu\": true,\n        \"format\": \"{$2}{$3}{ifname}: {ipv4}  {#2}[MTU:{mtu}]\"\n      },\n      {\n        // localip IPv6\n        \"type\": \"localip\",\n        \"key\": \"{#90}{$1}│ {#94}Local IPv6  {#90}│\",\n        \"showPrefixLen\": true,\n        \"showIpv4\": false,\n        \"showIpv6\": true,\n        \"showMtu\": true,\n        \"format\": \"{$2}{$3}{ifname}: {ipv6}  {#2}[MTU:{mtu}]\"\n      },\n      {\n        \"type\": \"publicip\",\n        \"key\": \"{#90}{$1}│ {#94}Public IPv4 {#90}│\",\n        \"ipv6\": false,\n        \"format\": \"{$2}{$3}{ip}  {#2}[{location}]\"\n      },\n      {\n        \"type\": \"publicip\",\n        \"key\": \"{#90}{$1}│ {#94}Public IPv6 {#90}│\",\n        \"ipv6\": true,\n        \"format\": \"{$2}{$3}{ip}  {#2}[{location}]\"\n      },\n      // CUSTOM - Button UI bar\n      {\n        \"type\": \"custom\",\n        \"key\": \"{#90}{$1}╰─────────────╯\",\n        \"format\": \"{#90}{$2}╰────────────────────────────────────────────────────────────╯\",\n      }\n    ]\n  }\n"
  },
  {
    "path": "presets/examples/25.jsonc",
    "content": "// Based on #1576\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"display\": {\n        \"color\": {\n            \"keys\": \"blue\"\n        },\n        \"separator\": \"\",\n        // Constants are reusable strings referenced by {$1}, {$2}, etc.\n        // These contain ANSI escape codes for cursor positioning\n        \"constants\": [\n            \"──────────────────────────────────────────────\", // {$1} - horizontal line for inner borders\n            \"\\u001b[47D\",                                     // {$2} - move cursor left 47 columns\n            \"\\u001b[47C\",                                     // {$3} - move cursor right 47 columns\n            \"\\u001b[46C\",                                     // {$4} - move cursor right 46 columns\n            \"══════════════════════════════════════════════\"  // {$5} - horizontal line for outer borders\n        ],\n        \"brightColor\": false\n    },\n    \"modules\": [\n        {\n            \"type\": \"version\",\n            \"key\": \"╔═══════════════╦═{$5}╗\\u001b[41D\",\n            \"format\": \"\\u001b[1m{#keys} {1} - {2} \"\n        },\n        {\n            \"type\": \"os\",\n            // Key format breakdown for OS module:\n            // \"║  {icon}  \\u001b[s{sysname}\\u001b[u\\u001b[10C│{$3}║{$2}\"\n            //\n            // ║                    - Left border of key block\n            //   {icon}             - OS icon (defined internally by fastfetch)\n            //   \\u001b[s           - ANSI escape: save cursor position (ESC[s)\n            // {sysname}            - Format variable: system name (e.g., \"Linux\", \"Darwin\")\n            // \\u001b[u             - ANSI escape: restore cursor to saved position (ESC[u)\n            //                        Necessary because the length of `{sysname}` differs between different platforms\n            // \\u001b[10C           - ANSI escape: move cursor right 10 columns (ESC[10C)\n            // │                    - Right border of key block (always 10 columns from left border)\n            // {$3}                 - Reference to constants[2]: move cursor right 47 columns\n            // ║                    - Right border of value block\n            // {$2}                 - Reference to constants[1]: move cursor left 47 columns\n            //\n            // This creates a fixed-width layout where the key block is exactly 10 columns wide,\n            // regardless of the actual content length. The cursor manipulation ensures proper\n            // alignment for the table-like structure.\n            \"key\": \"║  {icon}  \\u001b[s{sysname}\\u001b[u\\u001b[10C║{$3}║{$2}\"\n        },\n        {\n            \"type\": \"datetime\",\n            \"key\": \"║  {icon}  Fetched   ║{$3}║{$2}\",\n            \"format\": \"{year}-{month-pretty}-{day-pretty} {hour-pretty}:{minute-pretty}:{second-pretty} {timezone-name}\"\n        },\n        {\n            \"type\": \"locale\",\n            \"key\": \"║  {icon}  Locale    ║{$3}║{$2}\"\n        },\n\n        // Hardware section with cyan color theme\n        {\n            \"type\": \"custom\",\n            \"key\": \"║{#cyan}┌──────────────┬{$1}┐{#keys}║\\u001b[37D\",\n            \"format\": \"{#bright_cyan} Hardware \"\n        },\n        {\n            \"type\": \"chassis\",\n            // Similar structure but with cyan color formatting:\n            // │{#cyan}│             - Left border with cyan color\n            //  {icon}               - Chassis icon\n            //  Chassis             - Fixed label text\n            // │{$4}│{#keys}║{$2}    - Positioning and borders for value area\n            \"key\": \"║{#cyan}│ {icon}  Chassis   │{$4}│{#keys}║{$2}\"\n        },\n        {\n            \"type\": \"memory\",\n            \"key\": \"║{#cyan}│ {icon}  RAM       │{$4}│{#keys}║{$2}\"\n        },\n        {\n            \"type\": \"swap\",\n            \"key\": \"║{#cyan}│ {icon}  SWAP      │{$4}│{#keys}║{$2}\"\n        },\n        {\n            \"type\": \"cpu\",\n            \"key\": \"║{#cyan}│ {icon}  CPU       │{$4}│{#keys}║{$2}\",\n            \"showPeCoreCount\": true\n        },\n        {\n            \"type\": \"gpu\",\n            \"key\": \"║{#cyan}│ {icon}  GPU       │{$4}│{#keys}║{$2}\"\n        },\n        {\n            \"type\": \"disk\",\n            \"key\": \"║{#cyan}│ {icon}  Disk      │{$4}│{#keys}║{$2}\",\n            \"format\": \"{size-used} \\/ {size-total} ({size-percentage}) - {filesystem}\",\n        },\n        {\n            \"type\": \"battery\",\n            \"key\": \"║{#cyan}│ {icon}  Battery   │{$4}│{#keys}║{$2}\"\n        },\n        {\n            \"type\": \"custom\",\n            \"key\": \"║{#cyan}└──────────────┴{$1}┘{#keys}║\",\n            \"format\": \"\"\n        },\n\n        // Desktop section with green color theme\n        {\n            \"type\": \"custom\",\n            \"key\": \"║{#green}┌──────────────┬{$1}┐{#keys}║\\u001b[37D\",\n            \"format\": \"{#bright_green} Desktop \"\n        },\n        {\n            \"type\": \"de\",\n            \"key\": \"║{#green}│ {icon}  Desktop   │{$4}│{#keys}║{$2}\"\n        },\n        {\n            \"type\": \"wm\",\n            \"key\": \"║{#green}│ {icon}  Session   │{$4}│{#keys}║{$2}\"\n        },\n        {\n            \"type\": \"display\",\n            \"key\": \"║{#green}│ {icon}  Display   │{$4}│{#keys}║{$2}\",\n            \"compactType\": \"original-with-refresh-rate\"\n        },\n        {\n            \"type\": \"gpu\",\n            \"key\": \"║{#green}│ {icon}  G-Driver  │{$4}│{#keys}║{$2}\",\n            \"format\": \"{driver}\"\n        },\n        {\n            \"type\": \"custom\",\n            \"key\": \"║{#green}└──────────────┴{$1}┘{#keys}║\",\n            \"format\": \"\"\n        },\n\n        // Terminal section with yellow color theme\n        {\n            \"type\": \"custom\",\n            \"key\": \"║{#yellow}┌──────────────┬{$1}┐{#keys}║\\u001b[37D\",\n            \"format\": \"{#bright_yellow} Terminal \"\n        },\n        {\n            \"type\": \"shell\",\n            \"key\": \"║{#yellow}│ {icon}  Shell     │{$4}│{#keys}║{$2}\"\n        },\n        {\n            \"type\": \"terminal\",\n            \"key\": \"║{#yellow}│ {icon}  Terminal  │{$4}│{#keys}║{$2}\"\n        },\n        {\n            \"type\": \"terminalfont\",\n            \"key\": \"║{#yellow}│ {icon}  Term Font │{$4}│{#keys}║{$2}\"\n        },\n        {\n            \"type\": \"terminaltheme\",\n            \"key\": \"║{#yellow}│ {icon}  Colors    │{$4}│{#keys}║{$2}\"\n        },\n        {\n            \"type\": \"packages\",\n            \"key\": \"║{#yellow}│ {icon}  Packages  │{$4}│{#keys}║{$2}\"\n        },\n        {\n            \"type\": \"custom\",\n            \"key\": \"║{#yellow}└──────────────┴{$1}┘{#keys}║\",\n            \"format\": \"\"\n        },\n\n        // Development section with red color theme\n        {\n            \"type\": \"custom\",\n            \"key\": \"║{#red}┌──────────────┬{$1}┐{#keys}║\\u001b[39D\",\n            \"format\": \"{#bright_red} Development \"\n        },\n        {\n            \"type\": \"command\",\n            \"keyIcon\": \"\",                                   // Custom icon override\n            \"key\": \"║{#red}│ {icon}  Rust      │{$4}│{#keys}║{$2}\",\n            \"text\": \"rustc --version\",\n            \"format\": \"rustc {~6,13}\"                         // Print 6th to 13th characters (version number)\n        },\n        {\n            \"type\": \"command\",\n            \"condition\": {\n                \"!system\": \"Windows\"                          // Posix version\n            },\n            \"keyIcon\": \"\",\n            \"key\": \"║{#red}│ {icon}  Clang     │{$4}│{#keys}║{$2}\",\n            \"text\": \"clang --version | sed -n 's/.*version \\\\([0-9][0-9.]*\\\\).*/\\\\1/p'\",\n            \"format\": \"clang {}\"\n        },\n        {\n            \"type\": \"command\",\n            \"condition\": {\n                \"system\": \"Windows\"                           // Windows version\n            },\n            \"keyIcon\": \"\",\n            \"key\": \"║{#red}│ {icon}  Clang     │{$4}│{#keys}║{$2}\",\n            \"text\": \"clang --version | findstr version\",      // Finds the line with \"version\"\n            \"format\": \"clang {~-6}\"                           // Prints the last 6 characters (version number)\n        },\n        {\n            \"type\": \"command\",\n            \"keyIcon\": \"\",\n            \"key\": \"║{#red}│ {icon}  NodeJS    │{$4}│{#keys}║{$2}\",\n            \"text\": \"node --version\",\n            \"format\": \"node {~1}\"                             // {~1} removes first character (v)\n        },\n        {\n            \"type\": \"command\",\n            \"keyIcon\": \"\",\n            \"key\": \"║{#red}│ {icon}  Go        │{$4}│{#keys}║{$2}\",\n            \"text\": \"go version | cut -d' ' -f3\",\n            \"format\": \"go {~2}\"                               // {~2} removes first 2 characters (go)\n        },\n        {\n            \"type\": \"command\",\n            \"keyIcon\": \"\",\n            \"key\": \"║{#red}│ {icon}  Zig       │{$4}│{#keys}║{$2}\",\n            \"text\": \"zig version\",\n            \"format\": \"zig {}\"\n        },\n        {\n            \"type\": \"editor\",\n            \"key\": \"║{#red}│ {icon}  Editor    │{$4}│{#keys}║{$2}\"\n        },\n        {\n            \"type\": \"command\",\n            \"keyIcon\": \"󰊢\",\n            \"key\": \"║{#red}│ {icon}  Git       │{$4}│{#keys}║{$2}\",\n            \"text\": \"git version\",\n            \"format\": \"git {~12}\"\n        },\n        {\n            \"type\": \"font\",\n            \"key\": \"║{#red}│ {icon}  Interface │{$4}│{#keys}║{$2}\"\n        },\n        {\n            \"type\": \"custom\",\n            \"key\": \"║{#red}└──────────────┴{$1}┘{#keys}║\",\n            \"format\": \"\"\n        },\n\n        // Uptime section with magenta color theme\n        {\n            \"type\": \"custom\",\n            \"key\": \"║{#magenta}┌──────────────┬{$1}┐{#keys}║\\u001b[36D\",\n            \"format\": \"{#bright_magenta} Uptime \"\n        },\n        {\n            \"type\": \"uptime\",\n            \"key\": \"║{#magenta}│ {icon}  Uptime    │{$4}│{#keys}║{$2}\"\n        },\n        {\n            \"type\": \"users\",\n            \"myselfOnly\": true,                               // Only show current user\n            \"keyIcon\": \"\",\n            \"key\": \"║{#magenta}│ {icon}  Login     │{$4}│{#keys}║{$2}\"\n        },\n        {\n            \"condition\": {                                    // Conditional module: only show on non-macOS\n                \"!system\": \"macOS\"\n            },\n            \"type\": \"disk\",\n            \"keyIcon\": \"\",\n            \"key\": \"║{#magenta}│ {icon}  OS Age    │{$4}│{#keys}║{$2}\",\n            \"folders\": \"/\",                                   // Check root filesystem\n            \"format\": \"{create-time:10} [{days} days]\"        // Show creation time and age in days\n        },\n        {\n            \"condition\": {                                    // Conditional module: only show on macOS\n                \"system\": \"macOS\"\n            },\n            \"type\": \"disk\",\n            \"keyIcon\": \"\",\n            \"key\": \"║{#magenta}│ {icon}  OS Age    │{$4}│{#keys}║{$2}\",\n            \"folders\": \"/System/Volumes/VM\",                 // Work around for APFS on macOS\n            \"format\": \"{create-time:10} [{days} days]\"\n        },\n        {\n            \"type\": \"custom\",\n            \"key\": \"║{#magenta}└──────────────┴{$1}┘{#keys}║\",\n            \"format\": \"\"\n        },\n        {\n            \"type\": \"custom\",\n            \"key\": \"╚═════════════════{$5}╝\",                // Bottom border of the entire layout\n            \"format\": \"\"\n        },\n\n        // End with color palette and line break\n        \"break\",                                             // Add a blank line\n        \"colors\"                                             // Display color palette\n    ]\n}\n\n/*\nKey Format Structure Explanation:\n\nThe key format uses a combination of:\n1. Unicode box drawing characters (│ ┌ ┐ └ ┘ ┬ ┴) for borders\n2. ANSI escape codes for cursor positioning (\\u001b[...)\n3. Format variables ({icon}, {sysname}, etc.)\n4. Constant references ({$1}, {$2}, etc.)\n5. Color formatting ({#color})\n\nANSI Escape Codes Used:\n- \\u001b[s      - Save cursor position (ESC[s)\n- \\u001b[u      - Restore cursor position (ESC[u)\n- \\u001b[nC     - Move cursor right n columns (ESC[nC)\n- \\u001b[nD     - Move cursor left n columns (ESC[nD)\n\nThis creates a table-like layout with fixed column widths and proper alignment,\nregardless of the actual content length in each field.\n\nFor more ANSI escape code reference, see:\nhttps://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#cursor-controls\n*/\n"
  },
  {
    "path": "presets/examples/26.jsonc",
    "content": "// Modified from: 24.jsonc\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n      \"padding\": {\n        \"top\": 2\n      }\n    },\n    \"display\": {\n      \"separator\": \"\",\n      \"constants\": [\n        // CONSTANT {$1} - VERTICAL BARS AT START AND 75th CHARACTERS FORWARD AND BACKWARD\n        \"\\u001b[90m│                                                            │\\u001b[60D\\u001b[39m\"\n      ]\n    },\n    \"modules\": [\n      // CUSTOM - Top UI bar\n      {\n        \"type\": \"custom\",\n        \"key\": \"{#90}╭ Keys ───────╮\",\n        \"format\": \"{#90}╭ Values ────────────────────────────────────────────────────╮\",\n      },\n      {\n        \"type\": \"title\",\n        \"key\": \"{#90}│ {#92}User        {#90}│\",\n        \"format\": \"{$1}{user-name}  {#2}[{home-dir}]\"\n      },\n      {\n        \"type\": \"users\",\n        \"key\": \"{#90}│ {#92}Users       {#90}│\",\n        \"myselfOnly\": false,\n        \"format\": \"{$1}{1}@{host-name}{/host-name}localhost{/}{?client-ip}  {#2}[IP:{client-ip}]{?}  [Login time: {login-time}]\"\n      },\n      {\n        \"type\": \"datetime\",\n        \"key\": \"{#90}│ {#92}Datetime    {#90}│\",\n        \"format\": \"{$1}{year}-{month-pretty}-{day-in-month} {hour-pretty}:{minute-pretty}:{second-pretty}  {#2}[{weekday}] [W{week}] [UTC{offset-from-utc}]\"\n      },\n      {\n        \"type\": \"title\",\n        \"key\": \"{#90}│ {#93}Host        {#90}│\",\n        \"format\": \"{$1}{host-name}\"\n      },\n      {\n        \"type\": \"host\",\n        \"key\": \"{#90}│ {#93}Machine     {#90}│\",\n        \"format\": \"{$1}{name}  {#2}{version}\"\n      },\n      {\n        \"type\": \"os\",\n        \"key\": \"{#90}│ {#93}OS          {#90}│\",\n        \"format\": \"{$1}{?pretty-name}{pretty-name}{?}{/pretty-name}{name}{/} {codename}  {#2}[v{version}] [{arch}]\"\n      },\n      {\n        \"type\": \"kernel\",\n        \"key\": \"{#90}│ {#93}Kernel      {#90}│\",\n        \"format\": \"{$1}{sysname}  {#2}[v{release}]\"\n      },\n      {\n        \"type\": \"uptime\",\n        \"key\": \"{#90}│ {#93}Uptime      {#90}│\",\n        \"format\": \"{$1}{?days}{days} Days + {?}{hours}:{minutes}:{seconds}\"\n      },\n      {\n        \"type\": \"cpu\",\n        \"key\": \"{#90}│ {#91}CPU         {#90}│\",\n        \"showPeCoreCount\": true,\n        \"temp\": true,\n        \"format\": \"{$1}{name}  {#2}[C:{core-types}] [{freq-max}]\"\n      },\n      {\n        \"type\": \"gpu\",\n        \"key\": \"{#90}│ {#91}GPU         {#90}│\",\n        \"detectionMethod\": \"auto\",\n        \"driverSpecific\": true,\n        \"format\": \"{$1}{name}  {#2}[C:{core-count}]{?frequency} [{frequency}]{?} {#2}[{type}]\"\n      },\n      {\n        \"type\": \"memory\",\n        \"key\": \"{#90}│ {#91}Memory      {#90}│\",\n        \"format\": \"{$1}{used} / {total} ({percentage})\"\n      },\n      {\n        \"type\": \"disk\",\n        \"key\": \"{#90}│ {#91}Disk        {#90}│\",\n        \"format\": \"{$1}{size-used} / {size-total} ({size-percentage})\"\n      },\n      {\n        \"type\": \"poweradapter\",\n        \"key\": \"{#90}│ {#91}Power       {#90}│\",\n        \"format\": \"{$1}{name}\"\n      },\n      {\n        \"type\": \"terminal\",\n        \"key\": \"{#90}│ {#95}Terminal    {#90}│\",\n        \"format\": \"{$1}{pretty-name}  {#2}[{version}] [PID:{pid}]\"\n      },\n      {\n        \"type\": \"terminalfont\",\n        \"key\": \"{#90}│ {#95}Font        {#90}│\",\n        \"format\": \"{$1}{name}  {#2}[{size}]\"\n      },\n      {\n        \"type\": \"shell\",\n        \"key\": \"{#90}│ {#95}Shell       {#90}│\",\n        \"format\": \"{$1}{pretty-name}  {#2}[v{version}] [PID:{pid}]\"\n      },\n      {\n        // localip IPv4\n        \"type\": \"localip\",\n        \"key\": \"{#90}│ {#94}Local IPv4  {#90}│\",\n        \"showPrefixLen\": true,\n        \"showIpv4\": true,\n        \"showIpv6\": false,\n        \"showMtu\": true,\n        \"format\": \"{$1}{ifname}: {ipv4}  {#2}[MTU:{mtu}]\"\n      },\n      {\n        // localip IPv6\n        \"type\": \"localip\",\n        \"key\": \"{#90}│ {#94}Local IPv6  {#90}│\",\n        \"showPrefixLen\": true,\n        \"showIpv4\": false,\n        \"showIpv6\": true,\n        \"showMtu\": true,\n        \"format\": \"{$1}{ifname}: {ipv6}  {#2}[MTU:{mtu}]\"\n      },\n      {\n        \"type\": \"publicip\",\n        \"key\": \"{#90}│ {#94}Public IPv4 {#90}│\",\n        \"ipv6\": false,\n        \"format\": \"{$1}{ip}  {#2}[{location}]\"\n      },\n      {\n        \"type\": \"publicip\",\n        \"key\": \"{#90}│ {#94}Public IPv6 {#90}│\",\n        \"ipv6\": true,\n        \"format\": \"{$1}{ip}  {#2}[{location}]\"\n      },\n      // CUSTOM - Button UI bar\n      {\n        \"type\": \"custom\",\n        \"key\": \"{#90}╰─────────────╯\",\n        \"format\": \"{#90}╰────────────────────────────────────────────────────────────╯\",\n      },\n      \"break\",\n      {\n        \"type\": \"custom\",\n        \"key\": \" \",\n        \"format\": \"{#90}╭ Colors ───────────────────────────────────────────────────────────────────╮\",\n      },\n      {\n        \"type\": \"custom\",\n        \"format\": \"{#90}│ {#40}    {#41}    {#42}    {#43}    {#44}    {#45}    {#46}    {#47}    {#}                                          {#90}│\",\n      },\n      {\n        \"type\": \"custom\",\n        \"format\": \"{#90}│ {#100}    {#101}    {#102}    {#103}    {#104}    {#105}    {#106}    {#107}    {#}                                          {#90}│\",\n      },\n      {\n        \"type\": \"custom\",\n        \"format\": \"{#90}╰───────────────────────────────────────────────────────────────────────────╯\",\n      },\n    ]\n  }\n"
  },
  {
    "path": "presets/examples/27.jsonc",
    "content": "{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"type\": \"small\",\n        \"padding\": {\n            \"top\": 1\n        }\n    },\n    \"display\": {\n        \"separator\": \"  \"\n    },\n    \"modules\": [\n        \"break\",\n        \"title\",\n        {\n            \"type\": \"os\",\n            \"key\": \"os    \",\n            \"keyColor\": \"red\"\n        },\n        {\n            \"type\": \"kernel\",\n            \"key\": \"kernel\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"host\",\n            \"format\": \"{vendor} {family}\",\n            \"key\": \"host  \",\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"packages\",\n            \"key\": \"pkgs  \",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"uptime\",\n            \"format\": \"{?days}{days}d {?}{hours}h {minutes}m\",\n            \"key\": \"uptime\",\n            \"keyColor\": \"magenta\"\n        },\n        {\n            \"type\": \"memory\",\n            \"key\": \"memory\",\n            \"keyColor\": \"cyan\"\n        },\n        \"break\"\n    ]\n}\n"
  },
  {
    "path": "presets/examples/28.jsonc",
    "content": "{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"type\": \"small\"\n    },\n    \"display\": {\n        \"separator\": \" \",\n        \"key\": {\n            \"type\": \"both\"\n        },\n        \"bar\": {\n            \"border\": {\n                \"left\": \"\\uee00\",\n                \"leftElapsed\": \"\\uee03\",\n                \"right\": \"\\uee02\",\n                \"rightElapsed\": \"\\uee05\"\n            },\n            \"char\": {\n                \"total\": \"\\uee01\",\n                \"elapsed\": \"\\uee04\"\n            },\n            \"color\": {\n                \"total\": null\n            }\n        },\n        \"percent\": {\n            \"type\": [\n                \"bar\",\n                \"bar-monochrome\"\n            ]\n        }\n    },\n    \"modules\": [\n        \"title\",\n        \"separator\",\n        {\n            \"type\": \"memory\",\n            \"key\": \"MEM\"\n        },\n        {\n            \"type\": \"swap\",\n            \"key\": \"SWP\"\n        },\n        {\n            \"type\": \"disk\",\n            \"folders\": \"/\",\n            \"key\": \"DSK\"\n        },\n        {\n            \"type\": \"battery\",\n            \"key\": \"BAT\"\n        },\n        {\n            \"type\": \"brightness\",\n            \"key\": \"BGT\"\n        },\n        {\n            \"type\": \"colors\",\n            \"paddingLeft\": 6,\n            \"symbol\": \"circle\"\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/29.jsonc",
    "content": "// #1887\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": null,\n    \"display\": {\n        \"constants\": [\n            \"\\u001b[s\\u001b[33C│\\u001b[u\",\n            \"  »  \"\n        ],\n        \"separator\": \"\",\n        \"percent\": {\n            \"type\": [\"num\", \"bar\"]\n        },\n        \"brightColor\": false,\n        \"bar\": {\n            \"border\": {\n                \"left\": \"[\",\n                \"leftElapsed\": \"[\",\n                \"right\": \"]\",\n                \"rightElapsed\": \"]\"\n            },\n            \"char\": {\n                \"elapsed\": \"─\",\n                \"total\": \"─\"\n            },\n            \"color\": {\n                \"elapsed\": \"default\",\n                \"total\": \"light_black\"\n            },\n            \"width\": 16\n        },\n        \"color\": {\n            \"separator\": \"default\",\n            \"keys\": \"default\",\n            \"output\": \"default\"\n        }\n    },\n    \"modules\": [\n        \"title\",\n        {\n            \"type\": \"custom\",\n            \"format\": \"┌────「 {#1}OS{#} 」────────────────────────────┐\"\n        },\n        {\n            \"type\": \"os\",\n            \"key\": \"│ {icon}{$2}{$1}\"\n        },\n        {\n            \"type\": \"disk\",\n            \"folders\": \"/\",\n            \"key\": \"│       {$1}\",\n            \"format\": \"{size-percentage-bar} {size-percentage}\"\n        },\n        {\n            \"type\": \"disk\",\n            \"folders\": \"/\",\n            \"key\": \"│       {$1}\",\n            \"format\": \"{size-used} / {size-total}\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"└────────────────────────────────────────┘\"\n        },\n        \"break\",\n        {\n            \"type\": \"custom\",\n            \"format\": \"┌────「 {#1}UI{#} 」────────────────────────────┐\"\n        },\n        {\n            \"type\": \"wm\",\n            \"key\": \"│ {icon}{$2}{$1}\"\n        },\n        {\n            \"type\": \"wmtheme\",\n            \"key\": \"│ {icon}{$2}{$1}\"\n        },\n        {\n            \"type\": \"custom\",\n            \"key\": \"│       {$1}\"\n        },\n        {\n            \"type\": \"display\",\n            \"key\": \"│ {icon}{$2}{$1}\",\n            \"format\": \"{width}x{height} @ {refresh-rate} Hz\"\n        },\n        {\n            \"type\": \"custom\",\n            \"key\": \"│       {$1}\"\n        },\n        {\n            \"type\": \"terminal\",\n            \"key\": \"│ {icon}{$2}{$1}\"\n        },\n        {\n            \"type\": \"terminalfont\",\n            \"key\": \"│ {icon}{$2}{$1}\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"└────────────────────────────────────────┘\"\n        },\n        \"break\",\n        {\n            \"type\": \"custom\",\n            \"format\": \"┌────「 {#1}HW{#} 」────────────────────────────┐\"\n        },\n        {\n            \"type\": \"cpu\",\n            \"key\": \"│ {icon}{$2}{$1}\",\n            \"format\": \"{name}\"\n        },\n        {\n            \"type\": \"gpu\",\n            \"key\": \"│ {icon}{$2}{$1}\",\n            \"format\": \"{name}\"\n        },\n        {\n            \"type\": \"custom\",\n            \"key\": \"│       {$1}\"\n        },\n        {\n            \"type\": \"memory\",\n            \"key\": \"│ {icon}{$2}{$1}\",\n            \"format\": \"{percentage-bar} {percentage}\"\n        },\n        {\n            \"type\": \"memory\",\n            \"key\": \"│       {$1}\",\n            \"format\": \"{used} / {total}\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"└────────────────────────────────────────┘\"\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/3.jsonc",
    "content": "// Load with --config examples/3.jsonc\n\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"type\": \"small\"\n    },\n    \"display\": {\n        \"size\": {\n            \"binaryPrefix\": \"si\"\n        }\n    },\n    \"modules\": [\n        \"vulkan\",\n        \"opengl\",\n        \"opencl\",\n        \"memory\",\n        {\n            \"type\": \"disk\",\n            \"folders\": \"/:/home:/boot:/efi\"\n        },\n        \"localip\"\n    ]\n}\n"
  },
  {
    "path": "presets/examples/30.jsonc",
    "content": "{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": null,\n    \"display\": {\n        \"key\": {\n            \"type\": \"both\",\n            \"paddingLeft\": 6,\n            \"width\": 17\n        }\n    },\n    \"modules\": [\n        {\n            \"type\": \"custom\",\n            \"format\": \"|---------------------: {#1}Hardware{#} : ---------------------|\"\n        },\n        \"break\",\n        {\n            \"keyColor\": \"green\",\n            \"type\": \"host\"\n        },\n        {\n            \"keyColor\": \"green\",\n            \"type\": \"cpu\"\n        },\n        {\n            \"keyColor\": \"yellow\",\n            \"type\": \"memory\"\n        },\n        {\n            \"keyColor\": \"yellow\",\n            \"type\": \"swap\"\n        },\n        {\n            \"type\": \"custom\",\n            \"keyIcon\": \"\",\n            \"key\": \"Disks\"\n        },\n        {\n            \"type\": \"disk\",\n            \"key\": \" \",\n            \"format\": \"        [{mountpoint}] - {size-used} / {size-total} ({size-percentage})\"\n        },\n        \"break\",\n        {\n            \"type\": \"title\",\n            \"format\": \"|-------------------------------------------------------|\\u001b[40D: {#1}{user-name} @ {host-name}{#} :\"\n        },\n        \"break\",\n        {\n            \"type\": \"os\",\n            \"keyColor\": \"cyan\"\n        },\n        {\n            \"type\": \"kernel\",\n            \"keyColor\": \"cyan\"\n        },\n        {\n            \"type\": \"packages\",\n            \"keyColor\": \"red\",\n            \"key\": \"Pkgs\"\n        },\n        {\n            \"type\": \"shell\",\n            \"keyColor\": \"red\"\n        },\n        {\n            \"type\": \"terminal\",\n            \"key\": \"Term\",\n            \"keyColor\": \"red\"\n        },\n        {\n            \"type\": \"locale\",\n            \"keyColor\": \"magenta\"\n        },\n        \"break\",\n        {\n            \"type\": \"custom\",\n            \"format\": \"|---------------------: {#1}Software{#} : ---------------------|\"\n        },\n        \"break\",\n        {\n            \"type\": \"colors\",\n            \"symbol\": \"circle\",\n            \"paddingLeft\": 8\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/31.jsonc",
    "content": "// Modified from https://github.com/fastfetch-cli/fastfetch/discussions/2133\n\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"source\": \"\\u001b[?25l\\u001b[0m\\u001b[38;2;0;0;0;48;2;23;23;23m▇\\u001b[48;2;24;24;24m▇▇▇▇▇\\u001b[38;2;6;2;4;48;2;39;20;29m▅\\u001b[38;2;45;17;30;48;2;5;4;4m▁\\u001b[38;2;57;23;43;48;2;5;3;4m▁\\u001b[38;2;46;17;34;48;2;4;3;4m▁\\u001b[38;2;49;19;37;48;2;7;5;6m▁\\u001b[38;2;52;20;41;48;2;13;8;10m▄\\u001b[38;2;96;79;131;48;2;52;23;42m▗\\u001b[38;2;153;160;221;48;2;83;63;97m▅\\u001b[38;2;162;178;235;48;2;95;103;133m▇\\u001b[38;2;158;173;227;48;2;93;103;135m▇\\u001b[38;2;165;183;244;48;2;94;104;138m▇\\u001b[38;2;166;184;248;48;2;96;103;138m▇\\u001b[38;2;164;180;243;48;2;94;102;137m▇\\u001b[38;2;162;178;240m▇\\u001b[38;2;164;180;247;48;2;93;101;137m▇\\u001b[38;2;163;181;247;48;2;94;102;138m▇\\u001b[38;2;163;180;245;48;2;94;101;139m▇\\u001b[38;2;164;180;246;48;2;95;102;139m▇\\u001b[38;2;165;179;248;48;2;94;102;139m▇\\u001b[38;2;164;179;248;48;2;95;101;139m▇\\u001b[38;2;163;179;247;48;2;94;101;139m▇\\u001b[38;2;163;178;247;48;2;93;100;138m▇\\u001b[38;2;163;179;246;48;2;95;100;136m▇\\u001b[38;2;27;17;22;48;2;144;151;202m▝\\u001b[38;2;94;74;108;48;2;50;21;40m▁\\u001b[38;2;55;17;43;48;2;19;9;14m▄\\u001b[38;2;51;16;41;48;2;9;5;7m▂\\u001b[38;2;4;1;3;48;2;23;23;24m▇\\u001b[38;2;4;1;2;48;2;24;24;24m▇\\u001b[38;2;45;15;33;48;2;4;3;3m▁\\u001b[38;2;48;17;36;48;2;10;5;6m▁\\u001b[38;2;0;0;0;48;2;24;24;24m▇\\u001b[48;2;23;23;23m▇\\u001b[48;2;24;24;24m▇▇▇\\u001b[0m\\n\\u001b[7m\\u001b[38;2;0;0;0m \\u001b[0m\\u001b[38;2;0;0;0;48;2;0;0;0m   \\u001b[38;2;24;11;18;48;2;2;1;2m▁\\u001b[38;2;44;18;32;48;2;6;3;4m▅\\u001b[38;2;42;13;28;48;2;50;21;38m╴\\u001b[38;2;55;25;45;48;2;53;23;42m╴\\u001b[38;2;54;24;44;48;2;56;22;45m▄\\u001b[38;2;56;23;45;48;2;55;24;48m▘\\u001b[38;2;54;25;48;48;2;55;23;46m╴\\u001b[38;2;90;82;131;48;2;66;41;72m▗\\u001b[38;2;116;115;169;48;2;151;155;218m▚\\u001b[38;2;107;106;151;48;2;150;158;205m╼\\u001b[38;2;137;142;194;48;2;164;166;200m╹\\u001b[38;2;128;121;155;48;2;186;187;218m▏\\u001b[38;2;170;166;177;48;2;186;189;219m▁\\u001b[38;2;158;158;196;48;2;162;177;234m╴\\u001b[38;2;130;129;169;48;2;156;168;222m╱\\u001b[38;2;163;182;247;48;2;163;180;244m┊\\u001b[38;2;164;183;248;48;2;162;181;246m▆\\u001b[38;2;164;183;249;48;2;162;181;247m▄\\u001b[38;2;163;181;248;48;2;163;181;245m╴\\u001b[38;2;148;152;200;48;2;156;168;225m╴\\u001b[38;2;162;181;245;48;2;163;181;247m╼\\u001b[38;2;155;156;191;48;2;162;179;242m╺\\u001b[38;2;172;176;214;48;2;194;194;226m▏\\u001b[38;2;145;143;163;48;2;187;190;225m╶\\u001b[38;2;116;116;151;48;2;169;174;217m▏\\u001b[38;2;171;172;203;48;2;161;176;238m▁\\u001b[38;2;152;162;215;48;2;114;104;145m▇\\u001b[38;2;133;126;167;48;2;52;18;45m▖\\u001b[38;2;56;20;48;48;2;55;19;46m╴\\u001b[38;2;55;20;46;48;2;56;20;46m╸\\u001b[38;2;55;18;47;48;2;56;20;47m▝\\u001b[38;2;55;18;44;48;2;55;19;45m╹\\u001b[38;2;44;14;35;48;2;50;18;42m╵\\u001b[38;2;12;4;9;48;2;46;16;36m▝\\u001b[38;2;34;12;25;48;2;2;0;1m▖\\u001b[48;2;0;0;0m   \\u001b[0m\\n\\u001b[7m\\u001b[38;2;0;0;0m \\u001b[0m\\u001b[38;2;0;0;0;48;2;0;0;0m  \\u001b[38;2;109;75;91;48;2;15;6;11m▗\\u001b[38;2;154;127;138;48;2;64;36;49m▖\\u001b[38;2;57;26;45;48;2;42;17;32m▇\\u001b[38;2;43;18;30;48;2;53;24;40m╶\\u001b[38;2;57;27;44;48;2;55;25;42m╴\\u001b[38;2;56;26;48;48;2;56;26;46m━\\u001b[38;2;39;17;29;48;2;54;24;43m▁\\u001b[38;2;51;24;43;48;2;89;75;111m▋\\u001b[38;2;107;100;153;48;2;90;82;126m▝\\u001b[38;2;234;232;241;48;2;140;125;150m▗\\u001b[38;2;125;97;98;48;2;224;218;222m▗\\u001b[38;2;219;190;204;48;2;190;175;186m╴\\u001b[38;2;149;148;174;48;2;188;193;225m╴\\u001b[38;2;118;118;152;48;2;162;170;215m╻\\u001b[38;2;117;116;154;48;2;160;171;224m╻\\u001b[38;2;119;119;160;48;2;163;179;238m▗\\u001b[38;2;138;144;205;48;2;165;181;245m▏\\u001b[38;2;165;184;246;48;2;165;183;249m▂\\u001b[38;2;165;185;246m▂\\u001b[38;2;166;185;244;48;2;165;184;248m▃\\u001b[38;2;136;139;184;48;2;163;177;231m┃\\u001b[38;2;147;157;202;48;2;161;176;233m╴\\u001b[38;2;167;182;240;48;2;165;183;247m▁\\u001b[38;2;164;180;239;48;2;169;176;210m▇\\u001b[38;2;153;155;189;48;2;192;197;228m╾\\u001b[38;2;148;123;123;48;2;194;182;195m┕\\u001b[38;2;155;137;136;48;2;217;208;219m▏\\u001b[38;2;207;204;230;48;2;156;156;192m▊\\u001b[38;2;160;169;221;48;2;124;118;156m▇\\u001b[38;2;112;96;128;48;2;61;30;56m▏\\u001b[38;2;48;14;35;48;2;51;19;42m╴\\u001b[38;2;57;22;48;48;2;57;22;47m╴\\u001b[38;2;58;23;46m▁\\u001b[38;2;45;16;36;48;2;54;21;43m┎\\u001b[38;2;43;17;33;48;2;53;21;43m╵\\u001b[38;2;151;120;136;48;2;69;37;56m▗\\u001b[38;2;108;77;93;48;2;17;7;12m▖\\u001b[48;2;0;0;0m \\u001b[38;2;0;0;1m▗\\u001b[0m\\n\\u001b[7m\\u001b[38;2;0;0;0m \\u001b[0m\\u001b[38;2;0;0;0;48;2;0;0;0m \\u001b[38;2;27;17;26m▝\\u001b[38;2;49;25;40;48;2;150;122;134m▍\\u001b[38;2;118;92;100;48;2;63;34;49m▘\\u001b[38;2;59;31;47;48;2;57;29;46m▖\\u001b[38;2;59;28;46;48;2;58;28;46m╵\\u001b[38;2;37;15;26;48;2;55;25;42m╹\\u001b[38;2;58;27;45;48;2;44;16;31m▉\\u001b[38;2;39;16;30;48;2;49;23;39m┆\\u001b[38;2;57;30;50;48;2;91;78;114m▋\\u001b[38;2;86;74;115;48;2;95;86;131m┊\\u001b[38;2;102;95;139;48;2;185;174;200m▂\\u001b[38;2;170;163;199;48;2;101;84;108m▚\\u001b[38;2;171;187;245;48;2;166;178;229m╴\\u001b[38;2;147;158;201;48;2;167;183;238m╴\\u001b[38;2;136;137;173;48;2;165;179;229m┃\\u001b[38;2;105;97;144;48;2;155;166;216m┃\\u001b[38;2;168;182;234;48;2;91;81;122m▋\\u001b[38;2;114;112;170;48;2;162;175;235m▖\\u001b[38;2;162;175;236;48;2;165;182;242m╻\\u001b[38;2;160;176;234;48;2;166;184;243m╷\\u001b[38;2;166;185;245m▉\\u001b[38;2;95;91;129;48;2;161;170;224m┃\\u001b[38;2;125;127;165;48;2;162;172;224m│\\u001b[38;2;117;120;156;48;2;163;175;223m│\\u001b[38;2;102;100;149;48;2;164;178;235m╷\\u001b[38;2;167;185;244;48;2;166;184;245m╴\\u001b[38;2;162;175;230;48;2;157;157;195m▇\\u001b[38;2;127;110;128;48;2;173;175;215m▘\\u001b[38;2;190;187;226;48;2;152;154;194m▊\\u001b[38;2;174;186;239;48;2;167;187;246m▏\\u001b[38;2;165;178;231;48;2;102;88;115m▊\\u001b[38;2;41;14;33;48;2;49;20;40m╷\\u001b[38;2;44;16;35;48;2;58;24;49m▎\\u001b[38;2;58;23;48;48;2;37;10;24m▊\\u001b[38;2;50;21;37;48;2;59;24;48m▏\\u001b[38;2;59;25;48;48;2;58;24;50m▅\\u001b[38;2;65;32;53;48;2;127;97;109m▊\\u001b[38;2;149;121;137;48;2;84;57;70m▉\\u001b[38;2;39;15;27;48;2;4;1;2m▏\\u001b[48;2;0;0;0m \\u001b[0m\\n\\u001b[7m\\u001b[38;2;0;0;0m \\u001b[0m\\u001b[38;2;0;0;0;48;2;0;0;0m \\u001b[38;2;0;0;1m╵\\u001b[38;2;109;85;92;48;2;36;21;29m▝\\u001b[38;2;58;33;47;48;2;61;30;47m▇\\u001b[38;2;59;32;50;48;2;59;32;47m┒\\u001b[38;2;47;21;35;48;2;55;28;43m╶\\u001b[38;2;54;25;41;48;2;58;30;46m▏\\u001b[38;2;57;28;46;48;2;71;46;67m▋\\u001b[38;2;94;75;106;48;2;59;32;54m▆\\u001b[38;2;51;31;53;48;2;97;88;130m▘\\u001b[38;2;87;78;121;48;2;102;95;149m▏\\u001b[38;2;103;96;151;48;2;90;81;126m▉\\u001b[38;2;97;88;135;48;2;150;158;209m▍\\u001b[38;2;124;124;160;48;2;156;167;213m╴\\u001b[38;2;127;120;140;48;2;166;177;220m╻\\u001b[38;2;121;102;114;48;2;169;156;177m╹\\u001b[38;2;181;188;221;48;2;109;96;131m▎\\u001b[38;2;161;172;220;48;2;107;89;112m▊\\u001b[38;2;143;149;208;48;2;103;92;145m▝\\u001b[38;2;104;103;158;48;2;155;166;224m╻\\u001b[38;2;166;181;233;48;2;112;110;152m▋\\u001b[38;2;140;146;202;48;2;166;182;239m▏\\u001b[38;2;166;177;222;48;2;107;100;150m▎\\u001b[38;2;165;170;211;48;2;111;102;142m▌\\u001b[38;2;146;111;114;48;2;168;164;197m┍\\u001b[38;2;164;161;194;48;2;107;101;141m▊\\u001b[38;2;108;99;144;48;2;160;175;231m▖\\u001b[38;2;117;119;161;48;2;159;172;223m┕\\u001b[38;2;129;131;184;48;2;167;183;236m▖\\u001b[38;2;146;152;193;48;2;162;176;225m╸\\u001b[38;2;170;190;244;48;2;168;188;244m▂\\u001b[38;2;158;167;207;48;2;76;62;82m▉\\u001b[38;2;99;78;114;48;2;64;34;60m▅\\u001b[38;2;80;59;88;48;2;59;28;52m▖\\u001b[38;2;60;27;49;48;2;59;25;48m╴\\u001b[38;2;52;19;38;48;2;55;22;44m╴\\u001b[38;2;60;26;49;48;2;59;26;48m▃\\u001b[38;2;59;26;48;48;2;60;26;49m╵\\u001b[38;2;134;107;115;48;2;57;37;45m▘\\u001b[38;2;0;0;1;48;2;0;0;0m╵ \\u001b[0m\\n\\u001b[7m\\u001b[38;2;0;0;0m \\u001b[0m\\u001b[38;2;0;0;1;48;2;0;0;0m╸ \\u001b[38;2;48;30;39;48;2;5;3;4m▝\\u001b[38;2;59;34;48;48;2;59;35;47m╴\\u001b[38;2;59;36;48;48;2;59;34;48m╷\\u001b[38;2;42;16;29;48;2;54;30;43m╹\\u001b[38;2;60;33;49;48;2;60;32;49m▃\\u001b[38;2;57;31;51;48;2;99;78;108m▍\\u001b[38;2;102;81;116;48;2;101;81;114m╵\\u001b[38;2;69;52;73;48;2;88;76;108m╻\\u001b[38;2;105;100;152;48;2;83;75;115m▋\\u001b[38;2;104;98;151;48;2;86;77;114m▋\\u001b[38;2;102;94;135;48;2;75;65;95m▚\\u001b[38;2;117;91;94;48;2;166;158;184m╻\\u001b[38;2;174;150;157;48;2;157;125;125m┣\\u001b[38;2;164;121;108;48;2;202;155;147m▃\\u001b[38;2;92;62;55;48;2;157;122;119m▂\\u001b[38;2;155;158;202;48;2;125;100;115m╹\\u001b[38;2;187;145;138;48;2;121;98;127m▖\\u001b[38;2;101;91;147;48;2;103;95;147m╵\\u001b[38;2;152;159;207;48;2;125;99;109m▊\\u001b[38;2;109;90;125;48;2;154;154;196m▍\\u001b[38;2;169;143;149;48;2;110;90;120m▏\\u001b[38;2;123;88;86;48;2;151;126;144m▂\\u001b[38;2;101;74;62;48;2;206;176;176m▂\\u001b[38;2;98;73;59;48;2;199;168;163m▁\\u001b[38;2;141;96;81;48;2;147;114;119m╻\\u001b[38;2;169;183;235;48;2;131;116;151m▝\\u001b[38;2;112;110;152;48;2;142;149;197m▚\\u001b[38;2;174;192;240;48;2;163;180;230m╵\\u001b[38;2;171;190;240;48;2;171;190;243m╲\\u001b[38;2;173;184;226;48;2;100;85;108m▎\\u001b[38;2;100;80;111;48;2;99;80;115m▏\\u001b[38;2;98;77;111;48;2;58;28;54m▊\\u001b[38;2;60;28;51;48;2;60;27;50m▅\\u001b[38;2;41;12;32;48;2;57;25;47m╹\\u001b[38;2;60;27;50;48;2;61;28;48m▘\\u001b[38;2;60;26;51;48;2;58;26;47m╵\\u001b[38;2;33;16;26;48;2;2;0;1m▘\\u001b[48;2;0;0;0m  \\u001b[0m\\n\\u001b[38;2;0;0;0;48;2;0;0;0m   \\u001b[48;2;0;0;1m▉\\u001b[38;2;2;1;1;48;2;43;28;36m▖\\u001b[38;2;49;26;37;48;2;55;32;45m┌\\u001b[38;2;47;24;36;48;2;58;34;48m▏\\u001b[38;2;59;35;49;48;2;55;32;46m▉\\u001b[38;2;56;31;50;48;2;102;85;113m▎\\u001b[38;2;99;82;112;48;2;99;81;111m▎\\u001b[38;2;103;83;110;48;2;89;71;98m┊\\u001b[38;2;89;75;108;48;2;94;88;128m▃\\u001b[38;2;99;95;141;48;2;90;83;124m▇\\u001b[38;2;85;73;107;48;2;94;85;125m┃\\u001b[38;2;82;56;72;48;2;121;102;130m╹\\u001b[38;2;170;129;124;48;2;194;148;138m▏\\u001b[38;2;125;91;82;48;2;220;193;186m▘\\u001b[38;2;198;176;170;48;2;145;83;80m▋\\u001b[38;2;232;207;202;48;2;104;52;53m▗\\u001b[38;2;234;208;202;48;2;148;102;99m▆\\u001b[38;2;230;199;192;48;2;132;101;113m▄\\u001b[38;2;132;111;138;48;2;220;176;169m▉\\u001b[38;2;214;177;169;48;2;147;111;118m▅\\u001b[38;2;235;205;199;48;2;143;100;98m▅\\u001b[38;2;229;208;204;48;2;134;87;81m▇\\u001b[38;2;215;180;178;48;2;112;34;37m▂\\u001b[38;2;106;67;64;48;2;223;202;196m▘\\u001b[38;2;237;209;204;48;2;139;95;88m▆\\u001b[38;2;122;99;120;48;2;187;161;170m▝\\u001b[38;2;142;146;195;48;2;98;89;129m▖\\u001b[38;2;110;105;156;48;2;168;183;235m▎\\u001b[38;2;134;130;157;48;2;161;173;211m▁\\u001b[38;2;116;108;135;48;2;93;74;102m▏\\u001b[38;2;100;81;114;48;2;100;80;113m╸\\u001b[38;2;98;79;112;48;2;57;37;57m▉\\u001b[38;2;49;16;39;48;2;62;28;51m▏\\u001b[38;2;61;27;50;48;2;52;22;38m▉\\u001b[38;2;48;20;33;48;2;56;27;44m╴\\u001b[38;2;61;31;47;48;2;14;7;11m▘\\u001b[48;2;0;0;0m   \\u001b[0m\\n\\u001b[38;2;0;0;0;48;2;0;0;0m     \\u001b[38;2;43;28;35;48;2;5;3;3m▝\\u001b[38;2;57;39;48;48;2;54;34;45m╴\\u001b[38;2;59;35;48;48;2;51;27;41m▉\\u001b[38;2;35;10;26;48;2;100;83;110m▏\\u001b[38;2;99;85;113;48;2;97;82;111m▍\\u001b[38;2;85;68;93;48;2;93;76;104m╻\\u001b[38;2;99;81;112;48;2;96;79;108m╵\\u001b[38;2;83;66;91;48;2;96;86;123m╸\\u001b[38;2;75;65;93;48;2;93;89;130m▖\\u001b[38;2;101;100;149;48;2;100;98;147m┊\\u001b[38;2;90;75;95;48;2;108;96;127m┕\\u001b[38;2;101;77;88;48;2;213;184;173m▖\\u001b[38;2;245;224;216;48;2;239;217;207m╵\\u001b[38;2;245;223;214;48;2;243;222;212m▌\\u001b[38;2;243;221;211;48;2;239;219;210m▃\\u001b[38;2;243;220;211;48;2;241;219;210m╴\\u001b[38;2;191;154;140;48;2;235;206;196m╻\\u001b[38;2;243;219;210;48;2;243;220;212m▘\\u001b[38;2;243;220;212;48;2;245;219;215m▇\\u001b[38;2;243;219;211;48;2;245;219;213m▃\\u001b[38;2;241;218;209;48;2;245;221;216m▁\\u001b[38;2;166;140;133;48;2;241;218;212m▂\\u001b[38;2;144;145;172;48;2;187;177;187m╶\\u001b[38;2;144;154;201;48;2;171;186;231m▁\\u001b[38;2;142;149;193;48;2;161;173;223m▁\\u001b[38;2;87;73;103;48;2;157;159;182m▌\\u001b[38;2;126;121;137;48;2;97;77;106m▘\\u001b[38;2;100;80;112;48;2;88;66;94m▋\\u001b[38;2;99;80;115;48;2;99;80;114m╹\\u001b[38;2;97;77;109;48;2;50;22;46m▉\\u001b[38;2;57;24;45;48;2;62;30;51m▏\\u001b[38;2;14;6;9;48;2;56;29;45m▗\\u001b[38;2;1;0;1;48;2;33;18;26m▇\\u001b[48;2;0;0;0m    \\u001b[0m\\n\\u001b[7m\\u001b[38;2;0;0;0m \\u001b[0m\\u001b[38;2;0;0;0;48;2;0;0;0m \\u001b[48;2;2;2;2m╴\\u001b[38;2;166;152;177;48;2;32;25;30m▗\\u001b[38;2;160;149;172;48;2;10;5;6m▅\\u001b[38;2;66;58;65;48;2;9;7;8m▏\\u001b[38;2;37;27;32;48;2;1;0;0m▝\\u001b[38;2;22;14;17;48;2;55;35;46m▂\\u001b[38;2;98;83;109;48;2;56;36;51m▝\\u001b[38;2;51;34;47;48;2;95;82;108m▁\\u001b[38;2;67;55;70;48;2;91;76;102m▁\\u001b[38;2;101;87;117;48;2;98;84;110m╴\\u001b[38;2;100;85;114;48;2;97;82;109m╴\\u001b[38;2;64;51;70;48;2;92;77;103m┖\\u001b[38;2;94;81;111;48;2;74;65;89m▉\\u001b[38;2;68;58;68;48;2;96;92;132m▂\\u001b[38;2;116;100;123;48;2;110;81;85m▆\\u001b[38;2;130;66;58;48;2;202;169;153m▂\\u001b[38;2;219;188;169;48;2;240;218;205m▁\\u001b[38;2;239;221;206;48;2;243;221;210m▂\\u001b[38;2;244;222;212;48;2;240;218;207m╵\\u001b[38;2;154;108;101;48;2;225;198;186m╹\\u001b[38;2;227;203;193;48;2;238;215;205m╻\\u001b[38;2;242;219;209;48;2;242;220;210m▄\\u001b[38;2;241;218;207;48;2;242;219;210m▁\\u001b[38;2;181;129;118;48;2;222;194;184m▁\\u001b[38;2;145;74;66;48;2;166;132;133m▖\\u001b[38;2;92;76;95;48;2;149;149;180m┎\\u001b[38;2;61;53;72;48;2;123;122;169m▄\\u001b[38;2;130;134;174;48;2;91;74;101m▘\\u001b[38;2;87;69;85;48;2;98;79;106m╹\\u001b[38;2;100;81;113;48;2;98;79;109m╵\\u001b[38;2;69;49;65;48;2;95;75;103m▁\\u001b[38;2;53;27;45;48;2;97;77;107m▁\\u001b[38;2;99;78;109;48;2;59;32;51m▘\\u001b[38;2;5;2;3;48;2;51;26;40m▗\\u001b[38;2;0;0;0;48;2;3;1;2m╵\\u001b[48;2;1;0;0m┊\\u001b[38;2;128;108;125;48;2;2;0;1m▄\\u001b[38;2;140;121;146;48;2;2;1;2m▃\\u001b[38;2;0;0;0;48;2;4;3;4m╴\\u001b[48;2;0;0;0m \\u001b[0m\\n\\u001b[7m\\u001b[38;2;0;0;0m \\u001b[0m\\u001b[38;2;0;0;0;48;2;2;1;2m╴\\u001b[38;2;7;5;6;48;2;89;75;96m▘\\u001b[38;2;100;88;115;48;2;115;104;128m▇\\u001b[38;2;100;89;114;48;2;156;149;176m▅\\u001b[38;2;141;128;144;48;2;25;17;20m▋\\u001b[38;2;54;38;48;48;2;0;0;0m▂\\u001b[38;2;56;39;51;48;2;1;0;0m▂\\u001b[38;2;57;40;49;48;2;12;8;10m▂\\u001b[38;2;57;37;50;48;2;18;12;15m▂\\u001b[38;2;0;0;0;48;2;36;22;28m━\\u001b[38;2;105;75;79;48;2;18;10;13m▁\\u001b[38;2;192;174;183;48;2;27;17;21m▂\\u001b[38;2;213;205;220;48;2;72;47;56m▃\\u001b[38;2;188;176;181;48;2;64;19;20m▖\\u001b[38;2;134;23;22;48;2;65;19;19m▆\\u001b[38;2;92;57;57;48;2;177;164;175m▌\\u001b[38;2;120;30;32;48;2;187;172;180m▝\\u001b[38;2;193;163;176;48;2;132;53;49m▂\\u001b[38;2;118;26;22;48;2;220;194;178m▄\\u001b[38;2;166;93;81;48;2;235;215;200m▂\\u001b[38;2;237;210;190;48;2;239;219;207m▁\\u001b[38;2;239;220;211;48;2;236;216;203m╵\\u001b[38;2;149;78;71;48;2;238;214;203m▃\\u001b[38;2;113;12;12;48;2;201;156;154m╼\\u001b[38;2;209;194;197;48;2;152;68;69m▄\\u001b[38;2;224;216;213;48;2;184;144;144m▇\\u001b[38;2;164;57;18;48;2;174;155;153m▆\\u001b[38;2;234;70;2;48;2;142;55;30m╴\\u001b[38;2;238;227;219;48;2;85;44;48m▂\\u001b[38;2;240;230;232;48;2;44;26;29m▁\\u001b[38;2;108;80;80;48;2;15;7;11m▁\\u001b[38;2;10;4;6;48;2;42;22;32m▆\\u001b[38;2;62;35;49;48;2;12;6;9m▁\\u001b[38;2;64;37;51;48;2;7;3;4m▁\\u001b[38;2;56;28;45;48;2;0;0;0m▁\\u001b[38;2;29;10;20m▁\\u001b[38;2;24;16;17;48;2;138;119;136m▍\\u001b[38;2;89;70;94;48;2;158;142;171m▗\\u001b[38;2;100;82;110;48;2;135;120;145m▆\\u001b[38;2;12;7;11;48;2;94;75;101m▝\\u001b[38;2;0;0;0;48;2;3;1;2m┊\\u001b[0m\\n\\u001b[38;2;0;0;0;48;2;1;1;1m╴\\u001b[38;2;6;5;6;48;2;87;76;97m▘\\u001b[38;2;99;90;117;48;2;101;90;117m▇\\u001b[38;2;99;89;117;48;2;99;89;115m▎\\u001b[38;2;59;45;58;48;2;97;84;109m▗\\u001b[38;2;52;37;47;48;2;56;39;50m▏\\u001b[38;2;47;30;40;48;2;57;39;48m╴\\u001b[38;2;211;205;201;48;2;62;42;52m▂\\u001b[38;2;222;224;219;48;2;58;38;47m▄\\u001b[38;2;235;241;236;48;2;77;52;60m▅\\u001b[38;2;238;246;239;48;2;122;97;100m▆\\u001b[38;2;239;244;236;48;2;212;195;194m▇\\u001b[38;2;141;98;78;48;2;229;233;227m▃\\u001b[38;2;181;62;13;48;2;170;162;160m▅\\u001b[38;2;186;57;10;48;2;87;23;20m▆\\u001b[38;2;200;183;178;48;2;122;34;30m▃\\u001b[38;2;176;167;175;48;2;204;198;210m╶\\u001b[38;2;109;95;97;48;2;197;193;205m▝\\u001b[38;2;123;113;115;48;2;200;194;208m▘\\u001b[38;2;213;209;228;48;2;163;101;111m▇\\u001b[38;2;207;196;214;48;2;119;16;19m▆\\u001b[38;2;120;32;34;48;2;195;151;128m▇\\u001b[38;2;186;157;169;48;2;140;58;58m▅\\u001b[38;2;220;215;227;48;2;172;102;110m▇\\u001b[38;2;84;65;58;48;2;217;211;214m╺\\u001b[38;2;119;97;91;48;2;204;198;190m┛\\u001b[38;2;206;199;191;48;2;231;229;223m╸\\u001b[38;2;114;45;24;48;2;216;212;208m▝\\u001b[38;2;144;116;109;48;2;177;57;11m▖\\u001b[38;2;197;72;22;48;2;208;194;182m▆\\u001b[38;2;160;70;38;48;2;230;227;217m▃\\u001b[38;2;183;168;151;48;2;239;236;229m▁\\u001b[38;2;241;241;237;48;2;126;101;108m▆\\u001b[38;2;236;235;231;48;2;79;50;61m▅\\u001b[38;2;239;239;236;48;2;77;50;62m▃\\u001b[38;2;212;202;203;48;2;64;37;50m▂\\u001b[38;2;49;24;35;48;2;66;39;53m╴\\u001b[38;2;62;36;51;48;2;93;74;86m▇\\u001b[38;2;69;45;61;48;2;111;92;113m▅\\u001b[38;2;100;81;112;48;2;100;82;111m▋\\u001b[38;2;101;83;115m▝\\u001b[38;2;6;4;6;48;2;84;68;89m▝\\u001b[0m\\n\\u001b[38;2;5;3;5;48;2;82;74;92m▘\\u001b[38;2;100;91;116;48;2;99;91;116m╴\\u001b[38;2;99;90;117;48;2;97;88;112m╵\\u001b[38;2;59;47;57;48;2;94;85;107m▃\\u001b[38;2;56;39;49;48;2;65;48;57m╵\\u001b[38;2;220;221;210;48;2;87;65;72m▄\\u001b[38;2;236;244;235;48;2;159;142;141m▇\\u001b[38;2;238;248;243;48;2;237;248;240m╍\\u001b[38;2;238;248;242m╸\\u001b[38;2;235;248;239;48;2;235;246;236m╵\\u001b[38;2;122;70;48;48;2;221;228;215m▃\\u001b[38;2;186;63;14;48;2;170;159;145m▆\\u001b[38;2;125;39;11;48;2;218;69;4m▗\\u001b[38;2;227;222;210;48;2;184;78;35m▃\\u001b[38;2;236;241;232;48;2;161;118;106m▆\\u001b[38;2;238;248;240;48;2;237;247;238m┊\\u001b[38;2;225;229;233;48;2;236;245;238m▝\\u001b[38;2;224;228;233;48;2;193;190;204m▇\\u001b[38;2;184;182;192;48;2;210;210;222m╺\\u001b[38;2;185;185;194;48;2;205;206;221m┐\\u001b[38;2;209;211;231;48;2;212;214;228m╴\\u001b[38;2;236;241;238;48;2;217;215;230m▃\\u001b[38;2;236;241;236;48;2;222;221;230m▆\\u001b[38;2;221;221;211;48;2;230;233;225m╷\\u001b[38;2;196;193;184;48;2;231;234;228m╴\\u001b[38;2;233;238;233;48;2;217;219;211m▇\\u001b[38;2;236;242;235;48;2;237;243;237m╲\\u001b[38;2;236;244;237;48;2;238;243;239m▃\\u001b[38;2;239;244;240;48;2;233;236;231m╴\\u001b[38;2;226;226;220;48;2;137;73;53m▄\\u001b[38;2;96;25;7;48;2;193;69;20m╴\\u001b[38;2;212;64;3;48;2;132;86;72m▇\\u001b[38;2;181;73;30;48;2;234;229;216m▆\\u001b[38;2;116;71;57;48;2;240;239;228m▖\\u001b[38;2;239;241;237;48;2;241;245;240m▃\\u001b[38;2;242;245;241;48;2;241;244;239m╺\\u001b[38;2;241;243;239;48;2;196;182;182m▇\\u001b[38;2;232;231;228;48;2;108;83;92m▅\\u001b[38;2;164;147;148;48;2;65;39;51m▖\\u001b[38;2;67;45;59;48;2;92;72;95m▃\\u001b[38;2;101;80;110;48;2;98;79;106m╴\\u001b[38;2;101;82;112;48;2;104;85;113m▉\\u001b[0m\\n\\u001b[38;2;65;63;77;48;2;99;92;116m▏\\u001b[38;2;55;42;50;48;2;90;82;101m▗\\u001b[38;2;51;37;46;48;2;69;59;72m▇\\u001b[38;2;53;36;45;48;2;61;45;52m┊\\u001b[38;2;110;95;90;48;2;213;206;194m▚\\u001b[38;2;187;185;173;48;2;236;244;232m▏\\u001b[38;2;237;249;240;48;2;236;249;237m▝\\u001b[38;2;236;249;238;48;2;237;248;241m▃\\u001b[38;2;236;247;235;48;2;235;247;236m┊\\u001b[38;2;199;200;187;48;2;115;70;51m▌\\u001b[38;2;157;47;7;48;2;224;70;3m▏\\u001b[38;2;233;73;0;48;2;230;73;2m╵\\u001b[38;2;117;45;25;48;2;204;192;180m▋\\u001b[48;2;236;246;236m \\u001b[38;2;238;246;239;48;2;236;247;239m▝\\u001b[38;2;233;247;235;48;2;236;247;238m▂\\u001b[38;2;235;246;236;48;2;236;246;238m▅\\u001b[38;2;234;245;234;48;2;235;246;237m╍\\u001b[38;2;234;246;234;48;2;235;245;237m▅\\u001b[38;2;234;244;235;48;2;229;233;232m▇\\u001b[38;2;209;208;194;48;2;226;230;219m╵\\u001b[38;2;195;194;179;48;2;233;239;228m─\\u001b[38;2;204;202;187;48;2;230;234;223m╸\\u001b[38;2;236;244;239;48;2;235;241;233m╴\\u001b[38;2;235;243;234;48;2;235;243;236m▍\\u001b[38;2;235;244;232m▅\\u001b[38;2;235;243;233;48;2;237;243;236m▅\\u001b[38;2;235;242;236m▅\\u001b[38;2;236;242;234;48;2;238;243;237m▃\\u001b[38;2;237;242;232;48;2;238;243;236m▅\\u001b[38;2;229;224;215;48;2;129;45;20m▍\\u001b[38;2;235;72;1;48;2;234;72;0m╴\\u001b[38;2;236;71;0m╵\\u001b[38;2;152;47;13;48;2;172;151;134m▊\\u001b[38;2;237;240;235;48;2;237;236;228m╵\\u001b[38;2;234;237;231;48;2;237;240;235m▃\\u001b[38;2;232;235;231;48;2;236;239;235m▂\\u001b[38;2;230;233;228;48;2;236;238;232m▃\\u001b[38;2;122;101;93;48;2;210;204;196m▄\\u001b[38;2;208;198;185;48;2;76;50;59m▖\\u001b[38;2;61;36;50;48;2;75;54;71m▇\\u001b[38;2;70;49;66;48;2;101;82;111m▖\\u001b[0m\\n\\u001b[38;2;58;46;54;48;2;90;84;100m▗\\u001b[38;2;41;27;33;48;2;53;40;46m┎\\u001b[38;2;58;44;51;48;2;60;45;52m╴\\u001b[38;2;236;223;202;48;2;104;87;79m╱\\u001b[38;2;202;195;183;48;2;97;84;76m▗\\u001b[38;2;234;246;233;48;2;232;241;228m╵\\u001b[38;2;233;244;231;48;2;234;247;236m▄\\u001b[38;2;132;139;141;48;2;224;236;224m▁\\u001b[38;2;77;78;109;48;2;217;225;213m▅\\u001b[38;2;52;53;100;48;2;95;43;29m▆\\u001b[38;2;57;50;94;48;2;209;72;6m▆\\u001b[38;2;55;52;98;48;2;168;55;9m▅\\u001b[38;2;229;237;223;48;2;83;72;91m▝\\u001b[38;2;242;243;231;48;2;234;246;234m▏\\u001b[38;2;236;247;236;48;2;234;248;235m▘\\u001b[38;2;234;248;234;48;2;234;247;234m╍\\u001b[38;2;234;247;234;48;2;234;245;235m▄\\u001b[38;2;234;244;234;48;2;234;246;235m┷\\u001b[38;2;232;243;233;48;2;234;245;234m┬\\u001b[38;2;233;243;233;48;2;241;248;236m▉\\u001b[38;2;216;213;200;48;2;150;130;113m▏\\u001b[38;2;219;207;188;48;2;143;120;104m┠\\u001b[38;2;148;124;108;48;2;191;187;165m▇\\u001b[38;2;229;217;200;48;2;235;242;231m▏\\u001b[38;2;233;243;233;48;2;234;242;235m▌\\u001b[38;2;233;241;232;48;2;234;242;233m┊\\u001b[38;2;234;243;232m┳\\u001b[38;2;233;241;233;48;2;234;241;235m▆\\u001b[38;2;236;241;233;48;2;234;242;233m╴\\u001b[38;2;234;241;234;48;2;236;241;232m▂\\u001b[38;2;226;225;216;48;2;77;44;62m▌\\u001b[38;2;71;48;87;48;2;224;75;7m▅\\u001b[38;2;61;49;95;48;2;206;67;6m▅\\u001b[38;2;62;49;97;48;2;154;50;15m▆\\u001b[38;2;70;62;103;48;2;199;197;189m▆\\u001b[38;2;79;72;103;48;2;216;218;210m▃\\u001b[38;2;156;154;153;48;2;224;227;219m▁\\u001b[38;2;219;223;213;48;2;225;229;221m▅\\u001b[38;2;196;193;187;48;2;124;108;107m▌\\u001b[38;2;174;156;142;48;2;87;67;63m▝\\u001b[38;2;160;143;127;48;2;67;42;51m▖\\u001b[38;2;79;58;76;48;2;56;33;46m▝\\u001b[0m\\n\\u001b[38;2;75;67;76;48;2;47;38;41m▘\\u001b[38;2;35;34;34;48;2;58;43;48m▂\\u001b[38;2;80;66;68;48;2;175;168;153m▊\\u001b[38;2;193;182;167;48;2;111;103;90m╶\\u001b[38;2;60;62;59;48;2;223;230;212m▂\\u001b[38;2;57;59;55;48;2;231;244;226m▂\\u001b[38;2;52;51;65;48;2;190;196;189m▄\\u001b[38;2;56;54;100;48;2;54;55;84m╴\\u001b[38;2;34;34;38;48;2;51;55;101m▂\\u001b[38;2;33;33;36;48;2;47;50;93m▂\\u001b[38;2;49;52;94;48;2;20;21;36m▝\\u001b[38;2;34;34;37;48;2;53;55;99m▂\\u001b[38;2;56;50;76;48;2;170;159;151m▊\\u001b[38;2;60;62;60;48;2;236;248;234m▂\\u001b[38;2;60;63;60;48;2;236;249;236m▂\\u001b[48;2;236;249;235m▂\\u001b[48;2;235;248;235m▂\\u001b[38;2;60;63;61;48;2;235;248;236m▂\\u001b[38;2;60;62;60;48;2;235;248;235m▂\\u001b[48;2;235;247;235m▂\\u001b[38;2;60;62;61;48;2;236;247;236m▂\\u001b[48;2;236;247;235m▂\\u001b[38;2;60;62;60;48;2;237;244;235m▂\\u001b[38;2;61;62;61;48;2;236;245;236m▂\\u001b[38;2;60;62;60;48;2;236;244;237m▂\\u001b[48;2;236;244;234m▂▂\\u001b[48;2;235;244;236m▂\\u001b[48;2;235;244;234m▂\\u001b[38;2;60;61;60;48;2;235;243;235m▂\\u001b[38;2;188;188;183;48;2;62;49;78m▌\\u001b[38;2;35;34;39;48;2;59;51;104m▂\\u001b[38;2;18;17;26;48;2;44;41;80m▗\\u001b[38;2;21;19;31;48;2;48;45;84m▖\\u001b[38;2;34;34;39;48;2;57;50;101m▂\\u001b[38;2;34;34;38;48;2;56;48;95m▂\\u001b[38;2;52;47;92;48;2;58;52;79m╴\\u001b[38;2;45;44;44;48;2;175;172;168m▂\\u001b[38;2;56;56;55;48;2;215;217;205m▂\\u001b[38;2;206;203;196;48;2;120;113;108m╾\\u001b[38;2;95;76;69;48;2;158;147;133m▋\\u001b[38;2;145;123;118;48;2;68;50;55m╴\\u001b[0m\",\n        \"type\": \"data-raw\"\n    },\n    \"display\": {\n        \"separator\": \"▌\",\n        \"key\": {\n            \"width\": 18\n        },\n        \"bar\": {\n            \"border\": null,\n            \"char\": {\n                \"elapsed\": \"█\",\n                \"total\": \"░\"\n            },\n            \"width\": 35\n        }\n    },\n    \"modules\": [\n        {\n            \"type\": \"custom\",\n            \"format\": \"{#@196}╔════════════════════════════════════════════════════════╗\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{#@196}║ {#@46}███╗   ██╗███████╗██████╗ ██╗   ██╗  {#@220}EVA-00 SYSTEM     {#@196}║\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{#@196}║ {#@46}████╗  ██║██╔════╝██╔══██╗██║   ██║  {#@220}MAGI INTERFACE   {#@196} ║\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{#@196}║ {#@46}██╔██╗ ██║█████╗  ██████╔╝██║   ██║                    {#@196}║\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{#@196}║ {#@46}██║╚██╗██║██╔══╝  ██╔══██╗╚██╗ ██╔╝  {#@208}CLASSIFIED       {#@196} ║\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{#@196}║ {#@46}██║ ╚████║███████╗██║  ██║ ╚████╔╝   {#@208}ACCESS ONLY      {#@196} ║\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{#@196}║ {#@46}╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝  ╚═══╝                     {#@196} ║\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{#@196}╚════════════════════════════════════════════════════════╝\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{#@208}╔══●{#@166}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━●══╗\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{#@208}║ {#@46}00101{#@208} ●{#@166}────{#@208}● NERVE CONNECTION INTERFACE ●{#@166}────{#@208}● {#@46}00102{#@196}   ║\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{#@208}╚══●{#@166}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━●══╝\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{#@208}▰{#@166}▰{#@208}▰{#@166}▰{#@208} BORDER LINE {#@166}▰{#@208}▰{#@166}▰{#@208}▰ {#@46}[{#@220}SYSTEM CONFIG{#@46}]\"\n        },\n        {\n            \"type\": \"os\",\n            \"key\": \"{#@46}│ OS          \"\n        },\n        {\n            \"type\": \"kernel\",\n            \"key\": \"{#@46}│ KERNEL      \"\n        },\n        {\n            \"type\": \"uptime\",\n            \"key\": \"{#@46}│ UPTIME      \"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{#@208}▰{#@166}▰{#@208}▰{#@166}▰{#@208}▰ ABSOLUTE {#@166}▰{#@208}▰{#@166}▰{#@208}▰{#@166}▰ {#@196}[{#@220}FIRST CHILD{#@196}] {#@51}REI AYANAMI\"\n        },\n        {\n            \"type\": \"users\",\n            \"key\": \"{#@46}│ DESIGNATION \",\n            \"format\": \"{name}, since {login-time}\",\n            \"myselfOnly\": true\n        },\n        {\n            \"type\": \"localip\",\n            \"key\": \"{#@46}│ NEURAL-LINK \",\n            \"format\": \"{ipv4}\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{#@196}▰{#@208}▰{#@196}▰{#@208}▰{#@196}▰ DANGER {#@208}▰{#@196}▰{#@208}▰{#@196}▰{#@208}▰ {#@220}[{#@196}UNIT-00{#@220}] {#@51}CORE STATUS\"\n        },\n        {\n            \"type\": \"cpu\",\n            \"key\": \"{#@220}│ CPU-CORE    \",\n            \"format\": \"{name}\"\n        },\n        {\n            \"type\": \"cpu\",\n            \"key\": \"{#@220}│ SYNC-RATE   \",\n            \"format\": \"{cores-physical}C / {cores-logical}T\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{#@196}▰{#@208}▰{#@196}▰{#@208}▰{#@196} WARNING {#@208}▰{#@196}▰{#@208}▰{#@196}▰ {#@220}[{#@51}LCL{#@220}] {#@208}[{#@51}A.T. FIELD{#@208}]\"\n        },\n        {\n            \"type\": \"memory\",\n            \"key\": \"{#@208}{#@166}{#@208}{#@220}│ LCL-LEVEL   \",\n            \"format\": \"{used} / {total} (RAM)\"\n        },\n        {\n            \"type\": \"memory\",\n            \"key\": \"{#@208}{#@166}{#@208}{#@220}│ A.T.FIELD   \",\n            \"percent\": {\n                \"type\": [\n                    \"bar\",\n                    \"hide-others\"\n                ]\n            }\n        },\n        {\n            \"type\": \"disk\",\n            \"folders\": \"/\",\n            \"key\": \"{#@208}{#@166}{#@208}{#@220}│ ENTRY-PLUG  \",\n            \"format\": \"{size-used} / {size-total} (DISK)\"\n        },\n        {\n            \"type\": \"disk\",\n            \"folders\": \"/\",\n            \"key\": \"{#@208}{#@166}{#@208}{#@220}│ PLUG-DEPTH  \",\n            \"percent\": {\n                \"type\": [\n                    \"bar\",\n                    \"hide-others\"\n                ]\n            }\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{#@196}╔═════════════════════════════════════════════════════════╗\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{#@196}║ {#@46}[SYSTEMS NOMINAL] {#@208}MAGI: {#@46} ONLINE {#@208} STATUS: {#@46} OK {#@196}           ║\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{#@196}╚═════════════════════════════════════════════════════════╝\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{#@46}          PILOT REI AYANAMI - READY FOR DEPLOYMENT\"\n        },\n        {\n            \"type\": \"custom\",\n            \"format\": \"{#@208}            綾波レイ  (I am not a doll. I am me.)\"\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/32.jsonc",
    "content": "// Inspired by microfetch\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"type\": \"small\"\n    },\n    \"general\": {\n        \"detectVersion\": false\n    },\n    \"display\": {\n        \"separator\": \"  \",\n        \"brightColor\": false,\n        \"key\": {\n            \"type\": \"both-2\"\n        }\n    },\n    \"modules\": [\n        {\n            \"type\": \"title\",\n            \"format\": \"{user-name-colored}{#light_red}@{host-name-colored} {#}{cwd}\"\n        },\n        {\n            \"type\": \"os\",\n            \"key\": \"System       \"\n        },\n        {\n            \"type\": \"kernel\",\n            \"key\": \"Kernel       \"\n        },\n        {\n            \"type\": \"shell\",\n            \"key\": \"Shell        \"\n        },\n        {\n            \"type\": \"uptime\",\n            \"key\": \"Uptime       \"\n        },\n        {\n            \"type\": \"wm\",\n            \"key\": \"Desktop      \"\n        },\n        {\n            \"type\": \"memory\",\n            \"key\": \"Memory       \"\n        },\n        {\n            \"type\": \"disk\",\n            \"key\": \"Storage (/)  \",\n            \"folders\": \"/\"\n        },\n        {\n            \"type\": \"colors\",\n            \"key\": \"Colors       \",\n            \"symbol\": \"circle\"\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/4.jsonc",
    "content": "// Load with --config examples/4.jsonc\n\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"type\": \"small\",\n        \"padding\": {\n            \"right\": 1\n        }\n    },\n    \"display\": {\n        \"size\": {\n            \"binaryPrefix\": \"si\"\n        },\n        \"color\": \"blue\",\n        \"separator\": \"  \"\n    },\n    \"modules\": [\n        {\n            \"type\": \"datetime\",\n            \"key\": \"Date\",\n            \"format\": \"{1}-{3}-{11}\"\n        },\n        {\n            \"type\": \"datetime\",\n            \"key\": \"Time\",\n            \"format\": \"{14}:{17}:{20}\"\n        },\n        \"break\",\n        \"player\",\n        \"media\"\n    ]\n}\n"
  },
  {
    "path": "presets/examples/5.jsonc",
    "content": "// Load with --config examples/5.jsonc\n\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": null,\n    \"display\": {\n        \"color\": \"magenta\"\n    },\n    \"modules\": [\n        {\n            \"type\": \"theme\",\n            \"key\": \"T\"\n        },\n        {\n            \"type\": \"icons\",\n            \"key\": \"I\"\n        },\n        {\n            \"type\": \"font\",\n            \"key\": \"F\"\n        },\n        {\n            \"type\": \"cursor\",\n            \"key\": \"C\"\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/6.jsonc",
    "content": "// Load with --config examples/2.jsonc\n// Note that you must replace the image path to an existing image to display it.\n\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"display\": {\n        \"separator\": \" \"\n    },\n    \"modules\": [\n        {\n            \"type\": \"host\",\n            \"key\": \"╭─󰌢\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"cpu\",\n            \"key\": \"├─󰻠\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"gpu\",\n            \"key\": \"├─󰍛\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"disk\",\n            \"key\": \"├─\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"memory\",\n            \"key\": \"├─󰑭\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"swap\",\n            \"key\": \"├─󰓡\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"display\",\n            \"key\": \"├─󰍹\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"brightness\",\n            \"key\": \"├─󰃞\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"battery\",\n            \"key\": \"├─\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"poweradapter\",\n            \"key\": \"├─\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"gamepad\",\n            \"key\": \"├─\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"bluetooth\",\n            \"key\": \"├─\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"sound\",\n            \"key\": \"╰─\",\n            \"keyColor\": \"green\"\n        },\n        \"break\",\n\n        {\n            \"type\": \"shell\",\n            \"key\": \"╭─\",\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"terminal\",\n            \"key\": \"├─\",\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"terminalfont\",\n            \"key\": \"├─\",\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"lm\",\n            \"key\": \"├─󰧨\",\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"de\",\n            \"key\": \"├─\",\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"wm\",\n            \"key\": \"├─\",\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"theme\",\n            \"key\": \"├─󰉼\",\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"icons\",\n            \"key\": \"├─󰀻\",\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"wallpaper\",\n            \"key\": \"╰─󰸉\",\n            \"keyColor\": \"yellow\"\n        },\n        \"break\",\n\n        {\n            \"type\": \"title\",\n            \"key\": \"╭─\",\n            \"format\": \"{user-name}@{host-name}\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"os\",\n            \"key\": \"├─{icon}\", // Just get your distro's logo off nerdfonts.com\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"kernel\",\n            \"key\": \"├─\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"packages\",\n            \"key\": \"├─󰏖\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"uptime\",\n            \"key\": \"├─󰅐\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"media\",\n            \"key\": \"├─󰝚\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"localip\",\n            \"key\": \"├─󰩟\",\n            \"compact\": true,\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"publicip\",\n            \"key\": \"├─󰩠\",\n            \"keyColor\": \"blue\",\n            \"timeout\": 1000\n        },\n        {\n            \"type\": \"wifi\",\n            \"key\": \"├─\",\n            \"format\": \"{ssid}\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"locale\",\n            \"key\": \"╰─\",\n            \"keyColor\": \"blue\"\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/7.jsonc",
    "content": "// Load with --config examples/2.jsonc\n// Note that you must replace the image path to an existing image to display it.\n\n{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"padding\": {\n            \"top\": 2\n        }\n    },\n    \"display\": {\n        \"separator\": \" -> \"\n    },\n    \"modules\": [\n        \"title\",\n        \"separator\",\n        {\n            \"type\": \"os\",\n            \"key\": \" OS\",\n            \"keyColor\": \"yellow\",\n            \"format\": \"{2}\"\n        },\n        {\n            \"type\": \"os\",\n            \"key\": \"├{icon}\", // Just get your distro's logo off nerdfonts.com\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"kernel\",\n            \"key\": \"├\",\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"packages\",\n            \"key\": \"├󰏖\",\n            \"keyColor\": \"yellow\"\n        },\n        {\n            \"type\": \"shell\",\n            \"key\": \"└\",\n            \"keyColor\": \"yellow\"\n        },\n        \"break\",\n\n        {\n            \"type\": \"wm\",\n            \"key\": \" DE/WM\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"lm\",\n            \"key\": \"├󰧨\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"wmtheme\",\n            \"key\": \"├󰉼\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"icons\",\n            \"key\": \"├󰀻\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"terminal\",\n            \"key\": \"├\",\n            \"keyColor\": \"blue\"\n        },\n        {\n            \"type\": \"wallpaper\",\n            \"key\": \"└󰸉\",\n            \"keyColor\": \"blue\"\n        },\n\n        \"break\",\n        {\n            \"type\": \"host\",\n            \"key\": \"󰌢 PC\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"cpu\",\n            \"key\": \"├󰻠\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"gpu\",\n            \"key\": \"├󰍛\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"disk\",\n            \"key\": \"├\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"memory\",\n            \"key\": \"├󰑭\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"swap\",\n            \"key\": \"├󰓡\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"display\",\n            \"key\": \"├󰍹\",\n            \"keyColor\": \"green\"\n        },\n        {\n            \"type\": \"uptime\",\n            \"key\": \"└󰅐\",\n            \"keyColor\": \"green\"\n        },\n\n        \"break\",\n        {\n            \"type\": \"sound\",\n            \"key\": \" SOUND\",\n            \"keyColor\": \"cyan\"\n        },\n        {\n            \"type\": \"player\",\n            \"key\": \"├󰥠\",\n            \"keyColor\": \"cyan\"\n        },\n        {\n            \"type\": \"media\",\n            \"key\": \"└󰝚\",\n            \"keyColor\": \"cyan\"\n        },\n\n        \"break\",\n        \"colors\"\n    ]\n}\n"
  },
  {
    "path": "presets/examples/8.jsonc",
    "content": "{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"type\": \"small\"\n    },\n    \"display\": {\n        \"separator\": \"  \",\n        \"color\": {\n            \"keys\": \"magenta\"\n        },\n        \"size\": {\n            \"ndigits\": 0,\n            \"maxPrefix\": \"MB\"\n        },\n        \"key\": {\n            \"type\": \"icon\"\n        }\n    },\n    \"modules\": [\n        {\n            \"type\": \"title\",\n            \"color\": {\n                \"user\": \"green\",\n                \"at\": \"red\",\n                \"host\": \"blue\"\n            }\n        },\n        \"os\",\n        \"kernel\",\n        \"memory\",\n        \"packages\",\n        \"uptime\",\n        {\n            \"type\": \"colors\",\n            \"key\": \"Colors\", // For printing icon\n            \"block\": {\n                \"range\": [1, 6]\n            }\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/examples/9.jsonc",
    "content": "{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {\n        \"type\": \"small\"\n    },\n    \"display\": {\n        \"key\": {\n            \"width\": 11\n        },\n        \"bar\": {\n            \"char\": {\n                \"elapsed\": \"=\",\n                \"total\": \"-\"\n            },\n            \"width\": 13\n        },\n        \"percent\": {\n            \"type\": 2\n        }\n    },\n    \"modules\": [\n        \"title\",\n        \"separator\",\n        \"memory\",\n        \"swap\",\n        {\n            \"type\": \"disk\",\n            \"folders\": \"/\"\n        },\n        {\n            \"type\": \"battery\",\n            \"key\": \"Battery\"\n        },\n        {\n            \"type\": \"colors\",\n            \"paddingLeft\": 10,\n            \"symbol\": \"circle\"\n        }\n    ]\n}\n"
  },
  {
    "path": "presets/neofetch.jsonc",
    "content": "{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"display\": {\n        \"size\": {\n            \"maxPrefix\": \"MB\",\n            \"ndigits\": 0,\n            \"spaceBeforeUnit\": \"never\"\n        },\n        \"freq\": {\n            \"ndigits\": 3,\n            \"spaceBeforeUnit\": \"never\"\n        }\n    },\n    \"modules\": [\n        \"title\",\n        \"separator\",\n        \"os\",\n        \"host\",\n        {\n            \"type\": \"kernel\",\n            \"format\": \"{release}\"\n        },\n        \"uptime\",\n        {\n            \"type\": \"packages\",\n            \"combined\": true\n        },\n        \"shell\",\n        {\n            \"type\": \"display\",\n            \"compactType\": \"original\",\n            \"key\": \"Resolution\"\n        },\n        \"de\",\n        \"wm\",\n        \"wmtheme\",\n        \"theme\",\n        \"icons\",\n        \"terminal\",\n        {\n            \"type\": \"terminalfont\",\n            \"format\": \"{/name}{-}{/}{name}{?size} {size}{?}\"\n        },\n        \"cpu\",\n        {\n            \"type\": \"gpu\",\n            \"key\": \"GPU\",\n            \"format\": \"{name}\"\n        },\n        {\n            \"type\": \"memory\",\n            \"format\": \"{used} / {total}\"\n        },\n        \"break\",\n        \"colors\"\n    ]\n}\n"
  },
  {
    "path": "presets/paleofetch.jsonc",
    "content": "{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"modules\": [\n        \"title\",\n        \"separator\",\n        \"os\",\n        {\n            \"type\": \"host\",\n            \"format\": \"{/2}{-}{/}{2}{?3} {3}{?}\"\n        },\n        \"kernel\",\n        \"uptime\",\n        {\n            \"type\": \"battery\",\n            \"format\": \"{/4}{-}{/}{4}{?5} [{5}]{?}\"\n        },\n        \"break\",\n        \"packages\",\n        \"shell\",\n        \"display\",\n        \"terminal\",\n        \"break\",\n        \"cpu\",\n        {\n            \"type\": \"gpu\",\n            \"key\": \"GPU\"\n        },\n        \"memory\",\n        \"break\",\n        \"colors\"\n    ]\n}\n"
  },
  {
    "path": "presets/screenfetch.jsonc",
    "content": "{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"modules\": [\n        \"title\",\n        \"os\",\n        \"kernel\",\n        \"uptime\",\n        {\n            \"type\": \"packages\",\n            \"format\": \"{all}\"\n        },\n        \"shell\",\n        {\n            \"type\": \"display\",\n            \"key\": \"Resolution\",\n            \"compactType\": \"original\"\n        },\n        \"de\",\n        \"wm\",\n        \"wmtheme\",\n        {\n            \"type\": \"terminalfont\",\n            \"key\": \"font\"\n        },\n        {\n            \"type\": \"disk\",\n            \"folders\": \"/\",\n            \"key\": \"Disk\"\n        },\n        \"cpu\",\n        \"gpu\",\n        {\n            \"type\": \"memory\",\n            \"key\": \"RAM\"\n        }\n    ]\n}\n"
  },
  {
    "path": "run.sh",
    "content": "#!/usr/bin/env sh\n\nset -e\n\nmkdir -p build/\ncd build/\n\ncmake ..\n\nkernel_name=\"$(uname -s)\"\n\ncase \"${kernel_name}\" in\n    \"Linux\" | \"MINGW\"*)\n        cmake_build_args=\"-j$(nproc)\"\n        ;;\n    \"Darwin\" | *\"BSD\" | \"DragonFly\")\n        cmake_build_args=\"-j$(sysctl -n hw.ncpu)\"\n        ;;\n    *)\n        cmake_build_args=\"\"\n        ;;\nesac\n\ncmake --build . --target fastfetch \"${cmake_build_args}\"\n\n./fastfetch \"$@\"\n"
  },
  {
    "path": "scripts/gen-amdgpuids.py",
    "content": "#!/usr/bin/env python3\n\nimport sys\n\ndef main(amdgpu_ids_path: str):\n    with open(amdgpu_ids_path, 'r') as f:\n        full_text = f.read()\n\n    if full_text == '':\n        sys.exit('Error: pci.ids file is empty')\n\n    products = []\n    for line in full_text.split('\\n'):\n        if not line or line[0] == '#' or not ',\\t' in line:\n            continue\n        device, revision, name = line.split(',\\t', maxsplit=2)\n        products.append((device, revision, name))\n\n    code = \"\"\"\\\n// SPDX-License-Identifier: MIT\n// https://opensource.org/license/mit\n// Generated from https://gitlab.freedesktop.org/mesa/drm/-/raw/main/data/amdgpu.ids\n\n#include <stdint.h>\n#include <stddef.h>\n\ntypedef struct FFArmGpuProduct\n{\n    const uint32_t id; // device << 8 | revision\n    const char* name;\n} FFArmGpuProduct;\n\nconst FFArmGpuProduct ffAmdGpuProducts[] = {\n\"\"\"\n\n    for device, revision, name in products:\n        code += f\"    {{ 0x{device} << 8 | 0x{revision}, \\\"{name}\\\" }},\\n\"\n\n    code += \"};\\n\"\n\n    print(code)\n\nif __name__ == '__main__':\n    len(sys.argv) == 2 or sys.exit('Usage: gen-amdgpuids.py </path/to/amdgpu.ids>')\n\n    main(sys.argv[1])\n"
  },
  {
    "path": "scripts/gen-man.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"\nPython script to generate a comprehensive man page for the command `fastfetch`.\n\nThe generated man page content will be printed to stdout,\nso you will need to pipe it to a file if you want to save it.\nExample: python3 gen-man.py > fastfetch.1\n\nThe command options are generated using a JSON file.\nFor the JSON file format, see:\nhttps://github.com/fastfetch-cli/fastfetch/blob/dev/src/data/help.json\n\"\"\"\n\nfrom json import load\nfrom datetime import datetime, timezone\nfrom time import time\nfrom re import search\nfrom os import environ, path\n\n\n###### Text Decorations Tags ######\n\nstartUnderline = r\"\\fI\" # start underline text tag\nendUnderline = r\"\\fR\" # end underline text tag\n\nstartBold = r\"\\fB\" # start bold text tag\nendBold = r\"\\fR\" # end bold text tag\n\n\n###### Parameters ######\n\n# path to the current directory\npathToCurrentDir = path.dirname(__file__)\n# path to the JSON option file\npathToHelpFile = path.join(pathToCurrentDir, \"../src/data/help.json\")\n# man page section\nmanSection = 1\n# title (center header)\ntitlePage = \"FASTFETCH\"\n# date (center footer)\n# format : \"Month (abbreviation) Day Year\"\ntodayDate = datetime.fromtimestamp(\n    int(environ.get(\"SOURCE_DATE_EPOCH\", time())),\n    tz=timezone.utc,\n).strftime(\"%b %d %Y\")\n# file to fastfetch version (left footer)\npathToVersionFile = path.join(pathToCurrentDir, \"../CMakeLists.txt\")\n\n\n###### Sections Text ######\n\n# text displayed in the \"NAME\" section\nnameSection = r\"fastfetch \\- A fast and feature-rich system information tool similar to neofetch\"\n\n# text displayed in the \"DESCRIPTION\" section\ndescriptionSection = r\"\"\"\nFastfetch is a tool for displaying system information in a visually appealing way. Written primarily in C, it focuses on performance and customizability while providing functionality similar to neofetch.\nIt supports Linux, Android, FreeBSD, macOS, and Windows 7 or newer.\n\"\"\"\n\n# text displayed at the beginning of the \"OPTIONS\" section\noptionSection = r\"\"\"\nOptions are parsed in a case-insensitive manner. For example, \\fB--logo-type\\fR and \\fB--LOGO-TYPE\\fR are treated identically.\n\nArguments in square brackets are optional. Optional boolean arguments default to 'true' when specified without a value.\n\nFor more detailed information about a specific option, use:\n\\fBfastfetch -h <option_name_without_dashes>\\fR\n\nAny combination of options can be made permanent by generating a configuration file:\n\\fBfastfetch <options> --gen-config\\fR\n\"\"\"\n\n# text displayed in the \"CONFIGURATION\"\nconfigurationSection = f\"\"\"\n.SS Fetch Structure\n\nThe structure defines which modules to display and in what order. It consists of module names separated by colons (:).\nFor example: {startBold}title:separator:os:kernel:uptime{endBold}\n\nTo list all available modules, use {startBold}--list-modules{endBold}\n\n\n.SS Config Files\n\nFastfetch uses JSONC (JSON with Comments) for configuration files. These files must have the .jsonc extension.\n\nYou can generate a default config file using {startBold}--gen-config{endBold}. By default, the config file is saved at {startBold}~/.config/fastfetch/config.jsonc{endBold}.\n\nThe configuration/preset files are searched in the following locations (in order):\n\n{startBold}1.{endBold} Relative to the current working directory\n\n{startBold}2.{endBold} Relative to ~/.local/share/fastfetch/presets/\n\n{startBold}3.{endBold} Relative to /usr/share/fastfetch/presets/\n\nFor detailed information on logo options, module configuration, and formatting, visit:\n{startBold}https://github.com/fastfetch-cli/fastfetch/wiki/Configuration{endBold}\n\nFastfetch provides several built-in presets. List them with {startBold}--list-presets{endBold}.\n\n.SS JSON Schema\nA JSON schema is available for editor intelligence when editing the configuration file. Add the following line at the beginning of your config file:\n\n{startBold}\"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\"{endBold}\n\"\"\"\n\n# text displayed in the \"EXAMPLE\" section\nexampleSection = f\"\"\"\n.SS Basic Usage\n{startBold}fastfetch{endBold}\n\n.SS Use a specific logo\n{startBold}fastfetch --logo arch{endBold}\n\n.SS Custom structure\n{startBold}fastfetch --structure title:os:kernel:uptime:memory{endBold}\n\n.SS Generate a config file\n{startBold}fastfetch --gen-config{endBold}\n\n.SS Use a preset\n{startBold}fastfetch --config neofetch{endBold}\n\n.SS Config File Example\n.nf\n// ~/.config/fastfetch/config.jsonc\n{{\n    \"$schema\": \"https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json\",\n    \"logo\": {{\n        \"type\": \"auto\",\n        \"source\": \"arch\"\n    }},\n    \"display\": {{\n        \"separator\": \": \",\n        \"color\": {{\n            \"keys\": \"blue\",\n            \"title\": \"red\"\n        }},\n        \"key\": {{\n            \"width\": 12\n        }}\n    }},\n    \"modules\": [\n        \"title\",\n        \"separator\",\n        \"os\",\n        \"kernel\",\n        \"uptime\",\n        {{\n            \"type\": \"memory\",\n            \"format\": \"{{used}}/{{total}} ({{used_percent}}%)\"\n        }}\n    ]\n}}\n.fi\n\"\"\"\n\n# text displayed in the \"BUGS\" section\nbugSection = \"Please report bugs to: https://github.com/fastfetch-cli/fastfetch/issues\"\n\n# text displayed in the \"AUTHORS\" section\nauthorsSection = \"Fastfetch is developed by a team of contributors on GitHub.\\nVisit https://github.com/fastfetch-cli/fastfetch for more information.\"\n\n\n###### Argument decoration ######\n\n### optional arguments tags ###\n\n# if an optional argument is displayed as [?optArg] (with \"optArg\" underlined)\n# this value should be f\"[?{startUnderline}\"\nstartOptionalArgument = f\"[{startUnderline}\"\n# if an optional argument is displayed as [?optArg] (with \"optArg underlined\")\n# this value should be f\"{endUnderline}]\"\nendOptionalArgument = f\"{endUnderline}]\"\n\n### mandatory arguments tags ###\nstartMandatoryArgument = f\"{startUnderline}\"\nendMandatoryArgument = f\"{endUnderline}\"\n\ndef main():\n\n    # importing the JSON file\n    with open(pathToHelpFile, 'r') as jsonFile:\n        helpFileData = load(jsonFile) # json.load\n\n\n    ######## Start printing the generated .1 file ########\n\n\n    ###### header, footer & config #####\n\n    print(f\".TH FASTFETCH {manSection} \", end=\" \")\n    print(f\"\\\"{todayDate}\\\"\", end=\" \")\n\n    # version number\n    with open(pathToVersionFile, 'r') as versionFile:\n\n        # research version number in file with regex\n        for line in versionFile:\n            researchVersion = search(r\"^\\s*VERSION (\\d+\\.\\d+\\.\\d+)$\", line)\n            if (researchVersion):\n                print(f\"\\\"Fastfetch {researchVersion.group(1)}\\\"\", end=\" \")\n                break\n\n    print(f\"\\\"{titlePage}\\\"\")\n\n\n    ###### Name ######\n\n    print(\".SH NAME\")\n    print(nameSection)\n\n\n    ##### Synopsis ######\n\n    print(\".SH SYNOPSIS\")\n    print(\".B fastfetch\")\n    print(f\"[{startUnderline}OPTIONS{endUnderline}...]\")\n\n\n    ###### Description ######\n\n    print(\".SH DESCRIPTION\")\n    print(descriptionSection)\n\n\n    ###### Configuration ######\n\n    print(\".SH CONFIGURATION\")\n    print(configurationSection)\n\n\n    ###### Options ######\n\n    print(\".SH OPTIONS\")\n    print(optionSection)\n    print()\n\n    # loop through every options sections\n    for key, value in helpFileData.items():\n\n        # print new subsection\n        print(f\".SS {key}\")\n\n        # loop through every option in a section\n        for option in value:\n            # list of existing keys for this option\n            keyList = option.keys()\n\n            # start a new \"option\" entry\n            print(\".TP\")\n            print(startBold, end=\"\")\n\n            # short option (-opt)\n            if \"short\" in keyList:\n                print(fr\"\\-{ option['short'] }\", end=\"\")\n                # if also have a long option, print a comma\n                if \"long\" in keyList:\n                    print(\", \", end=\"\")\n\n            # long option (--option)\n            if \"long\" in keyList:\n                print(fr\"\\-\\-{ option['long'] }\", end=\"\")\n\n            print(endBold, end=\" \")\n\n            # arguments\n            if \"arg\" in keyList:\n                # if argument is optional, print \"[arg]\"\n                if \"optional\" in option[\"arg\"].keys() and option[\"arg\"][\"optional\"]:\n                    print(startOptionalArgument + option['arg']['type'] + endOptionalArgument, end=\"\")\n\n                # if argument is mandatory, print \"arg\"\n                else:\n                    print(startMandatoryArgument + option['arg']['type'] + endMandatoryArgument, end=\"\")\n\n            # description\n            print()\n\n            # If desc is a list, join with newlines and proper spacing\n            if isinstance(option['desc'], list):\n                desc_text = \"\\n \".join(option['desc'])\n                print(f\" {desc_text}\")\n            else:\n                print(f\" {option['desc']}\")\n\n            # Add remarks if available\n            if \"remark\" in keyList:\n                print()\n                if isinstance(option['remark'], list):\n                    for remark in option['remark']:\n                        print(f\" {remark}\")\n                else:\n                    print(f\" {option['remark']}\")\n\n            print()\n\n\n    ###### Examples ######\n\n    print(\".SH EXAMPLES\")\n    print(exampleSection)\n\n\n    ###### See Also ######\n\n    print(\".SH \\\"SEE ALSO\\\"\")\n    print(\".BR neofetch (1)\")\n\n\n    ###### Bugs ######\n\n    print(\".SH BUGS\")\n    print(bugSection)\n\n\n    ###### Authors ######\n\n    print(\".SH AUTHORS\")\n    print(authorsSection)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "scripts/gen-pciids.py",
    "content": "#!/usr/bin/env python3\n\nimport sys\n\nclass PciDeviceModel:\n    def __init__(self, id: int, name: str):\n        self.id = id\n        self.name = name\n\nclass PciVendorModel:\n    def __init__(self, id: int, name: str):\n        self.id = id\n        self.name = name\n        self.devices = []\n\ndef main(keep_vendor_list: set, pci_ids_path: str):\n    vendors = []\n    with open(pci_ids_path, 'r') as f:\n        full_text = f.read()\n\n    if full_text == '':\n        sys.exit('Error: pci.ids file is empty')\n\n    dev_list_text = full_text[:full_text.rfind('\\n\\n\\n')]  # remove known classes\n    for line in dev_list_text.split('\\n'):\n        if not line or line[0] == '#':\n            continue\n        if line[0] != '\\t':\n            id, name = line.split('  ', maxsplit=1)\n            vendors.append(PciVendorModel(int(id, 16), name))\n        elif line[1] != '\\t':\n            id, name = line[1:].split('  ', maxsplit=1)\n            vendors[-1].devices.append(PciDeviceModel(int(id, 16), name))\n\n    code = \"\"\"\\\n// SPDX-License-Identifier: BSD-3-Clause\n// https://opensource.org/license/BSD-3-Clause\n// Generated from https://pci-ids.ucw.cz/v2.2/pci.ids\n\n#include <stdint.h>\n#include <stddef.h>\n\ntypedef struct FFPciDevice\n{\n    const uint32_t id;\n    const char* name;\n} FFPciDevice;\n\ntypedef struct FFPciVendor\n{\n    const uint32_t id;\n    const char* name;\n    const FFPciDevice* devices;\n    const uint32_t nDevices;\n} FFPciVendor;\n\"\"\"\n\n    if keep_vendor_list:\n        vendors = [vendor for vendor in vendors if vendor.id in keep_vendor_list]\n\n    for vendor in vendors:\n        if vendor.devices:\n            piece = ',\\n    '.join('{{ 0x{:04X}, \"{}\" }}'.format(device.id, device.name.replace('\"', '\\\\\"')) for device in vendor.devices)\n            code += f\"\"\"\n// {vendor.name}\nstatic const FFPciDevice pciDevices_{vendor.id:04X}[] = {{\n    {piece},\n    {{}},\n}};\n\"\"\"\n\n    piece = ',\\n    '.join('{{ 0x{:04X}, \"{}\", {}, {} }}'.format(vendor.id, vendor.name.replace('\"', '\\\\\"'), vendor.devices and f\"pciDevices_{vendor.id:04X}\" or \"NULL\", len(vendor.devices)) for vendor in vendors)\n    code += f\"\"\"\nconst FFPciVendor ffPciVendors[] = {{\n    {piece},\n    {{}},\n}};\"\"\"\n\n    print(code)\n\nif __name__ == '__main__':\n    len(sys.argv) == 2 or sys.exit('Usage: gen-pciids.py </path/to/pci.ids>')\n\n    # From <src/detection/gpu/gpu.c>\n    main({\n        0x106b, # Apple\n        0x1002, 0x1022, # AMD\n        0x8086, 0x8087, 0x03e7, # Intel\n        0x0955, 0x10de, 0x12d2, # Nvidia\n        0x1ed5, # MThreads\n        0x5143, # Qualcomm\n        0x14c3, # MTK\n        0x15ad, # VMware\n        0x1af4, # RedHat\n        0x1ab8, # Parallel\n        0x1414, # Microsoft\n        0x108e, # Oracle\n    }, sys.argv[1])\n"
  },
  {
    "path": "src/3rdparty/display-library/adl_defines.h",
    "content": "//\r\n// Copyright (c) 2016 - 2022 Advanced Micro Devices, Inc. All rights reserved.\r\n//\r\n// MIT LICENSE:\r\n// Permission is hereby granted, free of charge, to any person obtaining a copy\r\n// of this software and associated documentation files (the \"Software\"), to deal\r\n// in the Software without restriction, including without limitation the rights\r\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n// copies of the Software, and to permit persons to whom the Software is\r\n// furnished to do so, subject to the following conditions:\r\n//\r\n// The above copyright notice and this permission notice shall be included in\r\n// all copies or substantial portions of the Software.\r\n//\r\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n// SOFTWARE.\r\n\r\n/// \\file adl_defines.h\r\n/// \\brief Contains all definitions exposed by ADL for \\ALL platforms.\\n <b>Included in ADL SDK</b>\r\n///\r\n/// This file contains all definitions used by ADL.\r\n/// The ADL definitions include the following:\r\n/// \\li ADL error codes\r\n/// \\li Enumerations for the ADLDisplayInfo structure\r\n/// \\li Maximum limits\r\n///\r\n\r\n#ifndef ADL_DEFINES_H_\r\n#define ADL_DEFINES_H_\r\n\r\n/// \\defgroup DEFINES Constants and Definitions\r\n/// @{\r\n\r\n/// \\defgroup define_misc Miscellaneous Constant Definitions\r\n/// @{\r\n\r\n/// \\name General Definitions\r\n/// @{\r\n\r\n/// Defines ADL_TRUE\r\n#define ADL_TRUE    1\r\n/// Defines ADL_FALSE\r\n#define ADL_FALSE        0\r\n\r\n/// Defines the maximum string length\r\n#define ADL_MAX_CHAR                                    4096\r\n/// Defines the maximum string length\r\n#define ADL_MAX_PATH                                    256\r\n/// Defines the maximum number of supported adapters\r\n#define ADL_MAX_ADAPTERS                               250\r\n/// Defines the maxumum number of supported displays\r\n#define ADL_MAX_DISPLAYS                                150\r\n/// Defines the maxumum string length for device name\r\n#define ADL_MAX_DEVICENAME                                32\r\n/// Defines for all adapters\r\n#define ADL_ADAPTER_INDEX_ALL                            -1\r\n///    Defines APIs with iOption none\r\n#define ADL_MAIN_API_OPTION_NONE                        0\r\n/// @}\r\n\r\n/// \\name Definitions for iOption parameter used by\r\n/// ADL_Display_DDCBlockAccess_Get()\r\n/// @{\r\n\r\n/// Switch to DDC line 2 before sending the command to the display.\r\n#define ADL_DDC_OPTION_SWITCHDDC2              0x00000001\r\n/// Save command in the registry under a unique key, corresponding to parameter \\b iCommandIndex\r\n#define ADL_DDC_OPTION_RESTORECOMMAND 0x00000002\r\n/// Combine write-read DDC block access command.\r\n#define ADL_DDC_OPTION_COMBOWRITEREAD 0x00000010\r\n/// Direct DDC access to the immediate device connected to graphics card.\r\n/// MST with this option set: DDC command is sent to first branch.\r\n/// MST with this option not set: DDC command is sent to the end node sink device.\r\n#define ADL_DDC_OPTION_SENDTOIMMEDIATEDEVICE 0x00000020\r\n/// @}\r\n\r\n/// \\name Values for\r\n/// ADLI2C.iAction used with ADL_Display_WriteAndReadI2C()\r\n/// @{\r\n\r\n#define ADL_DL_I2C_ACTIONREAD\t\t\t\t\t\t\t\t0x00000001\r\n#define ADL_DL_I2C_ACTIONWRITE\t\t\t\t\t\t\t\t0x00000002\r\n#define ADL_DL_I2C_ACTIONREAD_REPEATEDSTART    \t\t\t\t0x00000003\r\n#define ADL_DL_I2C_ACTIONIS_PRESENT\t\t\t\t\t\t\t0x00000004\r\n/// @}\r\n\r\n\r\n/// @}        //Misc\r\n\r\n/// \\defgroup define_adl_results Result Codes\r\n/// This group of definitions are the various results returned by all ADL functions \\n\r\n/// @{\r\n/// All OK, but need to wait\r\n#define ADL_OK_WAIT                4\r\n/// All OK, but need restart\r\n#define ADL_OK_RESTART                3\r\n/// All OK but need mode change\r\n#define ADL_OK_MODE_CHANGE            2\r\n/// All OK, but with warning\r\n#define ADL_OK_WARNING                1\r\n/// ADL function completed successfully\r\n#define ADL_OK                    0\r\n/// Generic Error. Most likely one or more of the Escape calls to the driver failed!\r\n#define ADL_ERR                    -1\r\n/// ADL not initialized\r\n#define ADL_ERR_NOT_INIT            -2\r\n/// One of the parameter passed is invalid\r\n#define ADL_ERR_INVALID_PARAM            -3\r\n/// One of the parameter size is invalid\r\n#define ADL_ERR_INVALID_PARAM_SIZE        -4\r\n/// Invalid ADL index passed\r\n#define ADL_ERR_INVALID_ADL_IDX            -5\r\n/// Invalid controller index passed\r\n#define ADL_ERR_INVALID_CONTROLLER_IDX        -6\r\n/// Invalid display index passed\r\n#define ADL_ERR_INVALID_DIPLAY_IDX        -7\r\n/// Function  not supported by the driver\r\n#define ADL_ERR_NOT_SUPPORTED            -8\r\n/// Null Pointer error\r\n#define ADL_ERR_NULL_POINTER            -9\r\n/// Call can't be made due to disabled adapter\r\n#define ADL_ERR_DISABLED_ADAPTER        -10\r\n/// Invalid Callback\r\n#define ADL_ERR_INVALID_CALLBACK            -11\r\n/// Display Resource conflict\r\n#define ADL_ERR_RESOURCE_CONFLICT                -12\r\n//Failed to update some of the values. Can be returned by set request that include multiple values if not all values were successfully committed.\r\n#define ADL_ERR_SET_INCOMPLETE                 -20\r\n/// There's no Linux XDisplay in Linux Console environment\r\n#define ADL_ERR_NO_XDISPLAY                    -21\r\n/// escape call failed becuse of incompatiable driver found in driver store\r\n#define ADL_ERR_CALL_TO_INCOMPATIABLE_DRIVER            -22\r\n/// not running as administrator\r\n#define ADL_ERR_NO_ADMINISTRATOR_PRIVILEGES            -23\r\n/// Feature Sync Start api is not called yet\r\n#define ADL_ERR_FEATURESYNC_NOT_STARTED            -24\r\n/// Adapter is in an invalid power state\r\n#define ADL_ERR_INVALID_POWER_STATE             -25\r\n\r\n/// @}\r\n/// </A>\r\n\r\n/// \\defgroup define_display_type Display Type\r\n/// Define Monitor/CRT display type\r\n/// @{\r\n/// Define Monitor display type\r\n#define ADL_DT_MONITOR                  0\r\n/// Define TV display type\r\n#define ADL_DT_TELEVISION                    1\r\n/// Define LCD display type\r\n#define ADL_DT_LCD_PANEL                       2\r\n/// Define DFP display type\r\n#define ADL_DT_DIGITAL_FLAT_PANEL        3\r\n/// Define Componment Video display type\r\n#define ADL_DT_COMPONENT_VIDEO               4\r\n/// Define Projector display type\r\n#define ADL_DT_PROJECTOR                       5\r\n/// @}\r\n\r\n/// \\defgroup define_display_connection_type Display Connection Type\r\n/// @{\r\n/// Define unknown display output type\r\n#define ADL_DOT_UNKNOWN                0\r\n/// Define composite display output type\r\n#define ADL_DOT_COMPOSITE            1\r\n/// Define SVideo display output type\r\n#define ADL_DOT_SVIDEO                2\r\n/// Define analog display output type\r\n#define ADL_DOT_ANALOG                3\r\n/// Define digital display output type\r\n#define ADL_DOT_DIGITAL                4\r\n/// @}\r\n\r\n/// \\defgroup define_color_type Display Color Type and Source\r\n/// Define  Display Color Type and Source\r\n/// @{\r\n#define ADL_DISPLAY_COLOR_BRIGHTNESS    (1 << 0)\r\n#define ADL_DISPLAY_COLOR_CONTRAST    (1 << 1)\r\n#define ADL_DISPLAY_COLOR_SATURATION    (1 << 2)\r\n#define ADL_DISPLAY_COLOR_HUE        (1 << 3)\r\n#define ADL_DISPLAY_COLOR_TEMPERATURE    (1 << 4)\r\n\r\n/// Color Temperature Source is EDID\r\n#define ADL_DISPLAY_COLOR_TEMPERATURE_SOURCE_EDID    (1 << 5)\r\n/// Color Temperature Source is User\r\n#define ADL_DISPLAY_COLOR_TEMPERATURE_SOURCE_USER    (1 << 6)\r\n/// @}\r\n\r\n/// \\defgroup define_adjustment_capabilities Display Adjustment Capabilities\r\n/// Display adjustment capabilities values.  Returned by ADL_Display_AdjustCaps_Get\r\n/// @{\r\n#define ADL_DISPLAY_ADJUST_OVERSCAN        (1 << 0)\r\n#define ADL_DISPLAY_ADJUST_VERT_POS        (1 << 1)\r\n#define ADL_DISPLAY_ADJUST_HOR_POS        (1 << 2)\r\n#define ADL_DISPLAY_ADJUST_VERT_SIZE        (1 << 3)\r\n#define ADL_DISPLAY_ADJUST_HOR_SIZE        (1 << 4)\r\n#define ADL_DISPLAY_ADJUST_SIZEPOS        (ADL_DISPLAY_ADJUST_VERT_POS | ADL_DISPLAY_ADJUST_HOR_POS | ADL_DISPLAY_ADJUST_VERT_SIZE | ADL_DISPLAY_ADJUST_HOR_SIZE)\r\n#define ADL_DISPLAY_CUSTOMMODES            (1<<5)\r\n#define ADL_DISPLAY_ADJUST_UNDERSCAN        (1<<6)\r\n/// @}\r\n\r\n///Down-scale support\r\n#define ADL_DISPLAY_CAPS_DOWNSCALE        (1 << 0)\r\n\r\n/// Sharpness support\r\n#define ADL_DISPLAY_CAPS_SHARPNESS      (1 << 0)\r\n\r\n/// \\defgroup define_desktop_config Desktop Configuration Flags\r\n/// These flags are used by ADL_DesktopConfig_xxx\r\n/// \\deprecated This API has been deprecated because it was only used for RandR 1.1 (Red Hat 5.x) distributions which is now not supported.\r\n/// @{\r\n#define ADL_DESKTOPCONFIG_UNKNOWN    0          /* UNKNOWN desktop config   */\r\n#define ADL_DESKTOPCONFIG_SINGLE     (1 <<  0)    /* Single                   */\r\n#define ADL_DESKTOPCONFIG_CLONE      (1 <<  2)    /* Clone                    */\r\n#define ADL_DESKTOPCONFIG_BIGDESK_H  (1 <<  4)    /* Big Desktop Horizontal   */\r\n#define ADL_DESKTOPCONFIG_BIGDESK_V  (1 <<  5)    /* Big Desktop Vertical     */\r\n#define ADL_DESKTOPCONFIG_BIGDESK_HR (1 <<  6)    /* Big Desktop Reverse Horz */\r\n#define ADL_DESKTOPCONFIG_BIGDESK_VR (1 <<  7)    /* Big Desktop Reverse Vert */\r\n#define ADL_DESKTOPCONFIG_RANDR12    (1 <<  8)    /* RandR 1.2 Multi-display */\r\n/// @}\r\n\r\n/// needed for ADLDDCInfo structure\r\n#define ADL_MAX_DISPLAY_NAME                                256\r\n\r\n/// \\defgroup define_edid_flags Values for ulDDCInfoFlag\r\n/// defines for ulDDCInfoFlag EDID flag\r\n/// @{\r\n#define ADL_DISPLAYDDCINFOEX_FLAG_PROJECTORDEVICE       (1 << 0)\r\n#define ADL_DISPLAYDDCINFOEX_FLAG_EDIDEXTENSION         (1 << 1)\r\n#define ADL_DISPLAYDDCINFOEX_FLAG_DIGITALDEVICE         (1 << 2)\r\n#define ADL_DISPLAYDDCINFOEX_FLAG_HDMIAUDIODEVICE       (1 << 3)\r\n#define ADL_DISPLAYDDCINFOEX_FLAG_SUPPORTS_AI           (1 << 4)\r\n#define ADL_DISPLAYDDCINFOEX_FLAG_SUPPORT_xvYCC601      (1 << 5)\r\n#define ADL_DISPLAYDDCINFOEX_FLAG_SUPPORT_xvYCC709      (1 << 6)\r\n/// @}\r\n\r\n/// \\defgroup define_displayinfo_connector Display Connector Type\r\n/// defines for ADLDisplayInfo.iDisplayConnector\r\n/// @{\r\n#define ADL_DISPLAY_CONTYPE_UNKNOWN                 0\r\n#define ADL_DISPLAY_CONTYPE_VGA                     1\r\n#define ADL_DISPLAY_CONTYPE_DVI_D                   2\r\n#define ADL_DISPLAY_CONTYPE_DVI_I                   3\r\n#define ADL_DISPLAY_CONTYPE_ATICVDONGLE_NTSC        4\r\n#define ADL_DISPLAY_CONTYPE_ATICVDONGLE_JPN         5\r\n#define ADL_DISPLAY_CONTYPE_ATICVDONGLE_NONI2C_JPN  6\r\n#define ADL_DISPLAY_CONTYPE_ATICVDONGLE_NONI2C_NTSC 7\r\n#define ADL_DISPLAY_CONTYPE_PROPRIETARY                8\r\n#define ADL_DISPLAY_CONTYPE_HDMI_TYPE_A             10\r\n#define ADL_DISPLAY_CONTYPE_HDMI_TYPE_B             11\r\n#define ADL_DISPLAY_CONTYPE_SVIDEO                   12\r\n#define ADL_DISPLAY_CONTYPE_COMPOSITE               13\r\n#define ADL_DISPLAY_CONTYPE_RCA_3COMPONENT          14\r\n#define ADL_DISPLAY_CONTYPE_DISPLAYPORT             15\r\n#define ADL_DISPLAY_CONTYPE_EDP                     16\r\n#define ADL_DISPLAY_CONTYPE_WIRELESSDISPLAY         17\r\n#define ADL_DISPLAY_CONTYPE_USB_TYPE_C              18\r\n/// @}\r\n\r\n/// TV Capabilities and Standards\r\n/// \\defgroup define_tv_caps TV Capabilities and Standards\r\n/// \\deprecated Dropping support for TV displays\r\n/// @{\r\n#define ADL_TV_STANDARDS            (1 << 0)\r\n#define ADL_TV_SCART                (1 << 1)\r\n\r\n/// TV Standards Definitions\r\n#define ADL_STANDARD_NTSC_M        (1 << 0)\r\n#define ADL_STANDARD_NTSC_JPN        (1 << 1)\r\n#define ADL_STANDARD_NTSC_N        (1 << 2)\r\n#define ADL_STANDARD_PAL_B        (1 << 3)\r\n#define ADL_STANDARD_PAL_COMB_N        (1 << 4)\r\n#define ADL_STANDARD_PAL_D        (1 << 5)\r\n#define ADL_STANDARD_PAL_G        (1 << 6)\r\n#define ADL_STANDARD_PAL_H        (1 << 7)\r\n#define ADL_STANDARD_PAL_I        (1 << 8)\r\n#define ADL_STANDARD_PAL_K        (1 << 9)\r\n#define ADL_STANDARD_PAL_K1        (1 << 10)\r\n#define ADL_STANDARD_PAL_L        (1 << 11)\r\n#define ADL_STANDARD_PAL_M        (1 << 12)\r\n#define ADL_STANDARD_PAL_N        (1 << 13)\r\n#define ADL_STANDARD_PAL_SECAM_D    (1 << 14)\r\n#define ADL_STANDARD_PAL_SECAM_K    (1 << 15)\r\n#define ADL_STANDARD_PAL_SECAM_K1    (1 << 16)\r\n#define ADL_STANDARD_PAL_SECAM_L    (1 << 17)\r\n/// @}\r\n\r\n\r\n/// \\defgroup define_video_custom_mode Video Custom Mode flags\r\n/// Component Video Custom Mode flags.  This is used by the iFlags parameter in ADLCustomMode\r\n/// @{\r\n#define ADL_CUSTOMIZEDMODEFLAG_MODESUPPORTED    (1 << 0)\r\n#define ADL_CUSTOMIZEDMODEFLAG_NOTDELETETABLE    (1 << 1)\r\n#define ADL_CUSTOMIZEDMODEFLAG_INSERTBYDRIVER    (1 << 2)\r\n#define ADL_CUSTOMIZEDMODEFLAG_INTERLACED    (1 << 3)\r\n#define ADL_CUSTOMIZEDMODEFLAG_BASEMODE        (1 << 4)\r\n/// @}\r\n\r\n/// \\defgroup define_ddcinfoflag Values used for DDCInfoFlag\r\n/// ulDDCInfoFlag field values used by the ADLDDCInfo structure\r\n/// @{\r\n#define ADL_DISPLAYDDCINFOEX_FLAG_PROJECTORDEVICE    (1 << 0)\r\n#define ADL_DISPLAYDDCINFOEX_FLAG_EDIDEXTENSION        (1 << 1)\r\n#define ADL_DISPLAYDDCINFOEX_FLAG_DIGITALDEVICE        (1 << 2)\r\n#define ADL_DISPLAYDDCINFOEX_FLAG_HDMIAUDIODEVICE    (1 << 3)\r\n#define ADL_DISPLAYDDCINFOEX_FLAG_SUPPORTS_AI        (1 << 4)\r\n#define ADL_DISPLAYDDCINFOEX_FLAG_SUPPORT_xvYCC601    (1 << 5)\r\n#define ADL_DISPLAYDDCINFOEX_FLAG_SUPPORT_xvYCC709    (1 << 6)\r\n/// @}\r\n\r\n/// \\defgroup define_cv_dongle Values used by ADL_CV_DongleSettings_xxx\r\n/// The following is applicable to ADL_DISPLAY_CONTYPE_ATICVDONGLE_JP and ADL_DISPLAY_CONTYPE_ATICVDONGLE_NONI2C_D only\r\n/// \\deprecated Dropping support for Component Video displays\r\n/// @{\r\n#define ADL_DISPLAY_CV_DONGLE_D1          (1 << 0)\r\n#define ADL_DISPLAY_CV_DONGLE_D2          (1 << 1)\r\n#define ADL_DISPLAY_CV_DONGLE_D3          (1 << 2)\r\n#define ADL_DISPLAY_CV_DONGLE_D4          (1 << 3)\r\n#define ADL_DISPLAY_CV_DONGLE_D5          (1 << 4)\r\n\r\n/// The following is applicable to ADL_DISPLAY_CONTYPE_ATICVDONGLE_NA and ADL_DISPLAY_CONTYPE_ATICVDONGLE_NONI2C only\r\n\r\n#define ADL_DISPLAY_CV_DONGLE_480I        (1 << 0)\r\n#define ADL_DISPLAY_CV_DONGLE_480P        (1 << 1)\r\n#define ADL_DISPLAY_CV_DONGLE_540P        (1 << 2)\r\n#define ADL_DISPLAY_CV_DONGLE_720P        (1 << 3)\r\n#define ADL_DISPLAY_CV_DONGLE_1080I       (1 << 4)\r\n#define ADL_DISPLAY_CV_DONGLE_1080P       (1 << 5)\r\n#define ADL_DISPLAY_CV_DONGLE_16_9        (1 << 6)\r\n#define ADL_DISPLAY_CV_DONGLE_720P50      (1 << 7)\r\n#define ADL_DISPLAY_CV_DONGLE_1080I25     (1 << 8)\r\n#define ADL_DISPLAY_CV_DONGLE_576I25      (1 << 9)\r\n#define ADL_DISPLAY_CV_DONGLE_576P50      (1 << 10)\r\n#define ADL_DISPLAY_CV_DONGLE_1080P24      (1 << 11)\r\n#define ADL_DISPLAY_CV_DONGLE_1080P25      (1 << 12)\r\n#define ADL_DISPLAY_CV_DONGLE_1080P30      (1 << 13)\r\n#define ADL_DISPLAY_CV_DONGLE_1080P50      (1 << 14)\r\n/// @}\r\n\r\n/// \\defgroup define_formats_ovr    Formats Override Settings\r\n/// Display force modes flags\r\n/// @{\r\n///\r\n#define ADL_DISPLAY_FORMAT_FORCE_720P        0x00000001\r\n#define ADL_DISPLAY_FORMAT_FORCE_1080I        0x00000002\r\n#define ADL_DISPLAY_FORMAT_FORCE_1080P        0x00000004\r\n#define ADL_DISPLAY_FORMAT_FORCE_720P50        0x00000008\r\n#define ADL_DISPLAY_FORMAT_FORCE_1080I25    0x00000010\r\n#define ADL_DISPLAY_FORMAT_FORCE_576I25        0x00000020\r\n#define ADL_DISPLAY_FORMAT_FORCE_576P50        0x00000040\r\n#define ADL_DISPLAY_FORMAT_FORCE_1080P24    0x00000080\r\n#define ADL_DISPLAY_FORMAT_FORCE_1080P25    0x00000100\r\n#define ADL_DISPLAY_FORMAT_FORCE_1080P30    0x00000200\r\n#define ADL_DISPLAY_FORMAT_FORCE_1080P50    0x00000400\r\n\r\n///< Below are \\b EXTENDED display mode flags\r\n\r\n#define ADL_DISPLAY_FORMAT_CVDONGLEOVERIDE  0x00000001\r\n#define ADL_DISPLAY_FORMAT_CVMODEUNDERSCAN  0x00000002\r\n#define ADL_DISPLAY_FORMAT_FORCECONNECT_SUPPORTED  0x00000004\r\n#define ADL_DISPLAY_FORMAT_RESTRICT_FORMAT_SELECTION 0x00000008\r\n#define ADL_DISPLAY_FORMAT_SETASPECRATIO 0x00000010\r\n#define ADL_DISPLAY_FORMAT_FORCEMODES    0x00000020\r\n#define ADL_DISPLAY_FORMAT_LCDRTCCOEFF   0x00000040\r\n/// @}\r\n\r\n/// Defines used by OD5\r\n#define ADL_PM_PARAM_DONT_CHANGE    0\r\n\r\n/// The following defines Bus types\r\n/// @{\r\n#define ADL_BUSTYPE_PCI           0       /* PCI bus                          */\r\n#define ADL_BUSTYPE_AGP           1       /* AGP bus                          */\r\n#define ADL_BUSTYPE_PCIE          2       /* PCI Express bus                  */\r\n#define ADL_BUSTYPE_PCIE_GEN2     3       /* PCI Express 2nd generation bus   */\r\n#define ADL_BUSTYPE_PCIE_GEN3     4       /* PCI Express 3rd generation bus   */\r\n#define ADL_BUSTYPE_PCIE_GEN4     5       /* PCI Express 4th generation bus   */\r\n/// @}\r\n\r\n/// \\defgroup define_ws_caps    Workstation Capabilities\r\n/// Workstation values\r\n/// @{\r\n\r\n/// This value indicates that the workstation card supports active stereo though stereo output connector\r\n#define ADL_STEREO_SUPPORTED        (1 << 2)\r\n/// This value indicates that the workstation card supports active stereo via \"blue-line\"\r\n#define ADL_STEREO_BLUE_LINE        (1 << 3)\r\n/// This value is used to turn off stereo mode.\r\n#define ADL_STEREO_OFF                0\r\n/// This value indicates that the workstation card supports active stereo.  This is also used to set the stereo mode to active though the stereo output connector\r\n#define ADL_STEREO_ACTIVE             (1 << 1)\r\n/// This value indicates that the workstation card supports auto-stereo monitors with horizontal interleave. This is also used to set the stereo mode to use the auto-stereo monitor with horizontal interleave\r\n#define ADL_STEREO_AUTO_HORIZONTAL    (1 << 30)\r\n/// This value indicates that the workstation card supports auto-stereo monitors with vertical interleave. This is also used to set the stereo mode to use the auto-stereo monitor with vertical interleave\r\n#define ADL_STEREO_AUTO_VERTICAL    (1 << 31)\r\n/// This value indicates that the workstation card supports passive stereo, ie. non stereo sync\r\n#define ADL_STEREO_PASSIVE              (1 << 6)\r\n/// This value indicates that the workstation card supports auto-stereo monitors with vertical interleave. This is also used to set the stereo mode to use the auto-stereo monitor with vertical interleave\r\n#define ADL_STEREO_PASSIVE_HORIZ        (1 << 7)\r\n/// This value indicates that the workstation card supports auto-stereo monitors with vertical interleave. This is also used to set the stereo mode to use the auto-stereo monitor with vertical interleave\r\n#define ADL_STEREO_PASSIVE_VERT         (1 << 8)\r\n/// This value indicates that the workstation card supports auto-stereo monitors with Samsung.\r\n#define ADL_STEREO_AUTO_SAMSUNG        (1 << 11)\r\n/// This value indicates that the workstation card supports auto-stereo monitors with Tridility.\r\n#define ADL_STEREO_AUTO_TSL         (1 << 12)\r\n/// This value indicates that the workstation card supports DeepBitDepth (10 bpp)\r\n#define ADL_DEEPBITDEPTH_10BPP_SUPPORTED   (1 << 5)\r\n\r\n/// This value indicates that the workstation supports 8-Bit Grayscale\r\n#define ADL_8BIT_GREYSCALE_SUPPORTED   (1 << 9)\r\n/// This value indicates that the workstation supports CUSTOM TIMING\r\n#define ADL_CUSTOM_TIMING_SUPPORTED   (1 << 10)\r\n\r\n/// Load balancing is supported.\r\n#define ADL_WORKSTATION_LOADBALANCING_SUPPORTED         0x00000001\r\n/// Load balancing is available.\r\n#define ADL_WORKSTATION_LOADBALANCING_AVAILABLE         0x00000002\r\n\r\n/// Load balancing is disabled.\r\n#define ADL_WORKSTATION_LOADBALANCING_DISABLED          0x00000000\r\n/// Load balancing is Enabled.\r\n#define ADL_WORKSTATION_LOADBALANCING_ENABLED           0x00000001\r\n\r\n\r\n\r\n/// @}\r\n\r\n/// \\defgroup define_adapterspeed speed setting from the adapter\r\n/// @{\r\n#define ADL_CONTEXT_SPEED_UNFORCED        0        /* default asic running speed */\r\n#define ADL_CONTEXT_SPEED_FORCEHIGH        1        /* asic running speed is forced to high */\r\n#define ADL_CONTEXT_SPEED_FORCELOW        2        /* asic running speed is forced to low */\r\n\r\n#define ADL_ADAPTER_SPEEDCAPS_SUPPORTED        (1 << 0)    /* change asic running speed setting is supported */\r\n/// @}\r\n\r\n/// \\defgroup define_glsync Genlock related values\r\n/// GL-Sync port types (unique values)\r\n/// @{\r\n/// Unknown port of GL-Sync module\r\n#define ADL_GLSYNC_PORT_UNKNOWN        0\r\n/// BNC port of of GL-Sync module\r\n#define ADL_GLSYNC_PORT_BNC            1\r\n/// RJ45(1) port of of GL-Sync module\r\n#define ADL_GLSYNC_PORT_RJ45PORT1    2\r\n/// RJ45(2) port of of GL-Sync module\r\n#define ADL_GLSYNC_PORT_RJ45PORT2    3\r\n\r\n// GL-Sync Genlock settings mask (bit-vector)\r\n\r\n/// None of the ADLGLSyncGenlockConfig members are valid\r\n#define ADL_GLSYNC_CONFIGMASK_NONE                0\r\n/// The ADLGLSyncGenlockConfig.lSignalSource member is valid\r\n#define ADL_GLSYNC_CONFIGMASK_SIGNALSOURCE        (1 << 0)\r\n/// The ADLGLSyncGenlockConfig.iSyncField member is valid\r\n#define ADL_GLSYNC_CONFIGMASK_SYNCFIELD            (1 << 1)\r\n/// The ADLGLSyncGenlockConfig.iSampleRate member is valid\r\n#define ADL_GLSYNC_CONFIGMASK_SAMPLERATE        (1 << 2)\r\n/// The ADLGLSyncGenlockConfig.lSyncDelay member is valid\r\n#define ADL_GLSYNC_CONFIGMASK_SYNCDELAY            (1 << 3)\r\n/// The ADLGLSyncGenlockConfig.iTriggerEdge member is valid\r\n#define ADL_GLSYNC_CONFIGMASK_TRIGGEREDGE        (1 << 4)\r\n/// The ADLGLSyncGenlockConfig.iScanRateCoeff member is valid\r\n#define ADL_GLSYNC_CONFIGMASK_SCANRATECOEFF        (1 << 5)\r\n/// The ADLGLSyncGenlockConfig.lFramelockCntlVector member is valid\r\n#define ADL_GLSYNC_CONFIGMASK_FRAMELOCKCNTL        (1 << 6)\r\n\r\n\r\n// GL-Sync Framelock control mask (bit-vector)\r\n\r\n/// Framelock is disabled\r\n#define ADL_GLSYNC_FRAMELOCKCNTL_NONE            0\r\n/// Framelock is enabled\r\n#define ADL_GLSYNC_FRAMELOCKCNTL_ENABLE            ( 1 << 0)\r\n\r\n#define ADL_GLSYNC_FRAMELOCKCNTL_DISABLE        ( 1 << 1)\r\n#define ADL_GLSYNC_FRAMELOCKCNTL_SWAP_COUNTER_RESET    ( 1 << 2)\r\n#define ADL_GLSYNC_FRAMELOCKCNTL_SWAP_COUNTER_ACK    ( 1 << 3)\r\n#define ADL_GLSYNC_FRAMELOCKCNTL_VERSION_KMD    (1 << 4)\r\n\r\n#define ADL_GLSYNC_FRAMELOCKCNTL_STATE_ENABLE        ( 1 << 0)\r\n#define ADL_GLSYNC_FRAMELOCKCNTL_STATE_KMD        (1 << 4)\r\n\r\n// GL-Sync Framelock counters mask (bit-vector)\r\n#define ADL_GLSYNC_COUNTER_SWAP                ( 1 << 0 )\r\n\r\n// GL-Sync Signal Sources (unique values)\r\n\r\n/// GL-Sync signal source is undefined\r\n#define ADL_GLSYNC_SIGNALSOURCE_UNDEFINED    0x00000100\r\n/// GL-Sync signal source is Free Run\r\n#define ADL_GLSYNC_SIGNALSOURCE_FREERUN      0x00000101\r\n/// GL-Sync signal source is the BNC GL-Sync port\r\n#define ADL_GLSYNC_SIGNALSOURCE_BNCPORT      0x00000102\r\n/// GL-Sync signal source is the RJ45(1) GL-Sync port\r\n#define ADL_GLSYNC_SIGNALSOURCE_RJ45PORT1    0x00000103\r\n/// GL-Sync signal source is the RJ45(2) GL-Sync port\r\n#define ADL_GLSYNC_SIGNALSOURCE_RJ45PORT2    0x00000104\r\n\r\n\r\n// GL-Sync Signal Types (unique values)\r\n\r\n/// GL-Sync signal type is unknown\r\n#define ADL_GLSYNC_SIGNALTYPE_UNDEFINED      0\r\n/// GL-Sync signal type is 480I\r\n#define ADL_GLSYNC_SIGNALTYPE_480I           1\r\n/// GL-Sync signal type is 576I\r\n#define ADL_GLSYNC_SIGNALTYPE_576I           2\r\n/// GL-Sync signal type is 480P\r\n#define ADL_GLSYNC_SIGNALTYPE_480P           3\r\n/// GL-Sync signal type is 576P\r\n#define ADL_GLSYNC_SIGNALTYPE_576P           4\r\n/// GL-Sync signal type is 720P\r\n#define ADL_GLSYNC_SIGNALTYPE_720P           5\r\n/// GL-Sync signal type is 1080P\r\n#define ADL_GLSYNC_SIGNALTYPE_1080P          6\r\n/// GL-Sync signal type is 1080I\r\n#define ADL_GLSYNC_SIGNALTYPE_1080I          7\r\n/// GL-Sync signal type is SDI\r\n#define ADL_GLSYNC_SIGNALTYPE_SDI            8\r\n/// GL-Sync signal type is TTL\r\n#define ADL_GLSYNC_SIGNALTYPE_TTL            9\r\n/// GL_Sync signal type is Analog\r\n#define ADL_GLSYNC_SIGNALTYPE_ANALOG        10\r\n\r\n// GL-Sync Sync Field options (unique values)\r\n\r\n///GL-Sync sync field option is undefined\r\n#define ADL_GLSYNC_SYNCFIELD_UNDEFINED        0\r\n///GL-Sync sync field option is Sync to Field 1 (used for Interlaced signal types)\r\n#define ADL_GLSYNC_SYNCFIELD_BOTH            1\r\n///GL-Sync sync field option is Sync to Both fields (used for Interlaced signal types)\r\n#define ADL_GLSYNC_SYNCFIELD_1                2\r\n\r\n\r\n// GL-Sync trigger edge options (unique values)\r\n\r\n/// GL-Sync trigger edge is undefined\r\n#define ADL_GLSYNC_TRIGGEREDGE_UNDEFINED     0\r\n/// GL-Sync trigger edge is the rising edge\r\n#define ADL_GLSYNC_TRIGGEREDGE_RISING        1\r\n/// GL-Sync trigger edge is the falling edge\r\n#define ADL_GLSYNC_TRIGGEREDGE_FALLING       2\r\n/// GL-Sync trigger edge is both the rising and the falling edge\r\n#define ADL_GLSYNC_TRIGGEREDGE_BOTH          3\r\n\r\n\r\n// GL-Sync scan rate coefficient/multiplier options (unique values)\r\n\r\n/// GL-Sync scan rate coefficient/multiplier is undefined\r\n#define ADL_GLSYNC_SCANRATECOEFF_UNDEFINED   0\r\n/// GL-Sync scan rate coefficient/multiplier is 5\r\n#define ADL_GLSYNC_SCANRATECOEFF_x5          1\r\n/// GL-Sync scan rate coefficient/multiplier is 4\r\n#define ADL_GLSYNC_SCANRATECOEFF_x4          2\r\n/// GL-Sync scan rate coefficient/multiplier is 3\r\n#define ADL_GLSYNC_SCANRATECOEFF_x3          3\r\n/// GL-Sync scan rate coefficient/multiplier is 5:2 (SMPTE)\r\n#define ADL_GLSYNC_SCANRATECOEFF_x5_DIV_2    4\r\n/// GL-Sync scan rate coefficient/multiplier is 2\r\n#define ADL_GLSYNC_SCANRATECOEFF_x2          5\r\n/// GL-Sync scan rate coefficient/multiplier is 3 : 2\r\n#define ADL_GLSYNC_SCANRATECOEFF_x3_DIV_2    6\r\n/// GL-Sync scan rate coefficient/multiplier is 5 : 4\r\n#define ADL_GLSYNC_SCANRATECOEFF_x5_DIV_4    7\r\n/// GL-Sync scan rate coefficient/multiplier is 1 (default)\r\n#define ADL_GLSYNC_SCANRATECOEFF_x1          8\r\n/// GL-Sync scan rate coefficient/multiplier is 4 : 5\r\n#define ADL_GLSYNC_SCANRATECOEFF_x4_DIV_5    9\r\n/// GL-Sync scan rate coefficient/multiplier is 2 : 3\r\n#define ADL_GLSYNC_SCANRATECOEFF_x2_DIV_3    10\r\n/// GL-Sync scan rate coefficient/multiplier is 1 : 2\r\n#define ADL_GLSYNC_SCANRATECOEFF_x1_DIV_2    11\r\n/// GL-Sync scan rate coefficient/multiplier is 2 : 5 (SMPTE)\r\n#define ADL_GLSYNC_SCANRATECOEFF_x2_DIV_5    12\r\n/// GL-Sync scan rate coefficient/multiplier is 1 : 3\r\n#define ADL_GLSYNC_SCANRATECOEFF_x1_DIV_3    13\r\n/// GL-Sync scan rate coefficient/multiplier is 1 : 4\r\n#define ADL_GLSYNC_SCANRATECOEFF_x1_DIV_4    14\r\n/// GL-Sync scan rate coefficient/multiplier is 1 : 5\r\n#define ADL_GLSYNC_SCANRATECOEFF_x1_DIV_5    15\r\n\r\n\r\n// GL-Sync port (signal presence) states (unique values)\r\n\r\n/// GL-Sync port state is undefined\r\n#define ADL_GLSYNC_PORTSTATE_UNDEFINED       0\r\n/// GL-Sync port is not connected\r\n#define ADL_GLSYNC_PORTSTATE_NOCABLE         1\r\n/// GL-Sync port is Idle\r\n#define ADL_GLSYNC_PORTSTATE_IDLE            2\r\n/// GL-Sync port has an Input signal\r\n#define ADL_GLSYNC_PORTSTATE_INPUT           3\r\n/// GL-Sync port is Output\r\n#define ADL_GLSYNC_PORTSTATE_OUTPUT          4\r\n\r\n\r\n// GL-Sync LED types (used index within ADL_Workstation_GLSyncPortState_Get returned ppGlSyncLEDs array) (unique values)\r\n\r\n/// Index into the ADL_Workstation_GLSyncPortState_Get returned ppGlSyncLEDs array for the one LED of the BNC port\r\n#define ADL_GLSYNC_LEDTYPE_BNC               0\r\n/// Index into the ADL_Workstation_GLSyncPortState_Get returned ppGlSyncLEDs array for the Left LED of the RJ45(1) or RJ45(2) port\r\n#define ADL_GLSYNC_LEDTYPE_RJ45_LEFT         0\r\n/// Index into the ADL_Workstation_GLSyncPortState_Get returned ppGlSyncLEDs array for the Right LED of the RJ45(1) or RJ45(2) port\r\n#define ADL_GLSYNC_LEDTYPE_RJ45_RIGHT        1\r\n\r\n\r\n// GL-Sync LED colors (unique values)\r\n\r\n/// GL-Sync LED undefined color\r\n#define ADL_GLSYNC_LEDCOLOR_UNDEFINED        0\r\n/// GL-Sync LED is unlit\r\n#define ADL_GLSYNC_LEDCOLOR_NOLIGHT          1\r\n/// GL-Sync LED is yellow\r\n#define ADL_GLSYNC_LEDCOLOR_YELLOW           2\r\n/// GL-Sync LED is red\r\n#define ADL_GLSYNC_LEDCOLOR_RED              3\r\n/// GL-Sync LED is green\r\n#define ADL_GLSYNC_LEDCOLOR_GREEN            4\r\n/// GL-Sync LED is flashing green\r\n#define ADL_GLSYNC_LEDCOLOR_FLASH_GREEN      5\r\n\r\n\r\n// GL-Sync Port Control (refers one GL-Sync Port) (unique values)\r\n\r\n/// Used to configure the RJ54(1) or RJ42(2) port of GL-Sync is as Idle\r\n#define ADL_GLSYNC_PORTCNTL_NONE             0x00000000\r\n/// Used to configure the RJ54(1) or RJ42(2) port of GL-Sync is as Output\r\n#define ADL_GLSYNC_PORTCNTL_OUTPUT           0x00000001\r\n\r\n\r\n// GL-Sync Mode Control (refers one Display/Controller) (bitfields)\r\n\r\n/// Used to configure the display to use internal timing (not genlocked)\r\n#define ADL_GLSYNC_MODECNTL_NONE             0x00000000\r\n/// Bitfield used to configure the display as genlocked (either as Timing Client or as Timing Server)\r\n#define ADL_GLSYNC_MODECNTL_GENLOCK          0x00000001\r\n/// Bitfield used to configure the display as Timing Server\r\n#define ADL_GLSYNC_MODECNTL_TIMINGSERVER     0x00000002\r\n\r\n// GL-Sync Mode Status\r\n/// Display is currently not genlocked\r\n#define ADL_GLSYNC_MODECNTL_STATUS_NONE         0x00000000\r\n/// Display is currently genlocked\r\n#define ADL_GLSYNC_MODECNTL_STATUS_GENLOCK   0x00000001\r\n/// Display requires a mode switch\r\n#define ADL_GLSYNC_MODECNTL_STATUS_SETMODE_REQUIRED 0x00000002\r\n/// Display is capable of being genlocked\r\n#define ADL_GLSYNC_MODECNTL_STATUS_GENLOCK_ALLOWED 0x00000004\r\n\r\n#define ADL_MAX_GLSYNC_PORTS                            8\r\n#define ADL_MAX_GLSYNC_PORT_LEDS                        8\r\n\r\n/// @}\r\n\r\n/// \\defgroup define_crossfirestate CrossfireX state of a particular adapter CrossfireX combination\r\n/// @{\r\n#define ADL_XFIREX_STATE_NOINTERCONNECT            ( 1 << 0 )    /* Dongle / cable is missing */\r\n#define ADL_XFIREX_STATE_DOWNGRADEPIPES            ( 1 << 1 )    /* CrossfireX can be enabled if pipes are downgraded */\r\n#define ADL_XFIREX_STATE_DOWNGRADEMEM            ( 1 << 2 )    /* CrossfireX cannot be enabled unless mem downgraded */\r\n#define ADL_XFIREX_STATE_REVERSERECOMMENDED        ( 1 << 3 )    /* Card reversal recommended, CrossfireX cannot be enabled. */\r\n#define ADL_XFIREX_STATE_3DACTIVE            ( 1 << 4 )    /* 3D client is active - CrossfireX cannot be safely enabled */\r\n#define ADL_XFIREX_STATE_MASTERONSLAVE            ( 1 << 5 )    /* Dongle is OK but master is on slave */\r\n#define ADL_XFIREX_STATE_NODISPLAYCONNECT        ( 1 << 6 )    /* No (valid) display connected to master card. */\r\n#define ADL_XFIREX_STATE_NOPRIMARYVIEW            ( 1 << 7 )    /* CrossfireX is enabled but master is not current primary device */\r\n#define ADL_XFIREX_STATE_DOWNGRADEVISMEM        ( 1 << 8 )    /* CrossfireX cannot be enabled unless visible mem downgraded */\r\n#define ADL_XFIREX_STATE_LESSTHAN8LANE_MASTER        ( 1 << 9 )     /* CrossfireX can be enabled however performance not optimal due to <8 lanes */\r\n#define ADL_XFIREX_STATE_LESSTHAN8LANE_SLAVE        ( 1 << 10 )    /* CrossfireX can be enabled however performance not optimal due to <8 lanes */\r\n#define ADL_XFIREX_STATE_PEERTOPEERFAILED        ( 1 << 11 )    /* CrossfireX cannot be enabled due to failed peer to peer test */\r\n#define ADL_XFIREX_STATE_MEMISDOWNGRADED        ( 1 << 16 )    /* Notification that memory is currently downgraded */\r\n#define ADL_XFIREX_STATE_PIPESDOWNGRADED        ( 1 << 17 )    /* Notification that pipes are currently downgraded */\r\n#define ADL_XFIREX_STATE_XFIREXACTIVE            ( 1 << 18 )    /* CrossfireX is enabled on current device */\r\n#define ADL_XFIREX_STATE_VISMEMISDOWNGRADED        ( 1 << 19 )    /* Notification that visible FB memory is currently downgraded */\r\n#define ADL_XFIREX_STATE_INVALIDINTERCONNECTION        ( 1 << 20 )    /* Cannot support current inter-connection configuration */\r\n#define ADL_XFIREX_STATE_NONP2PMODE            ( 1 << 21 )    /* CrossfireX will only work with clients supporting non P2P mode */\r\n#define ADL_XFIREX_STATE_DOWNGRADEMEMBANKS        ( 1 << 22 )    /* CrossfireX cannot be enabled unless memory banks downgraded */\r\n#define ADL_XFIREX_STATE_MEMBANKSDOWNGRADED        ( 1 << 23 )    /* Notification that memory banks are currently downgraded */\r\n#define ADL_XFIREX_STATE_DUALDISPLAYSALLOWED        ( 1 << 24 )    /* Extended desktop or clone mode is allowed. */\r\n#define ADL_XFIREX_STATE_P2P_APERTURE_MAPPING        ( 1 << 25 )    /* P2P mapping was through peer aperture */\r\n#define ADL_XFIREX_STATE_P2PFLUSH_REQUIRED        ADL_XFIREX_STATE_P2P_APERTURE_MAPPING    /* For back compatible */\r\n#define ADL_XFIREX_STATE_XSP_CONNECTED            ( 1 << 26 )    /* There is CrossfireX side port connection between GPUs */\r\n#define ADL_XFIREX_STATE_ENABLE_CF_REBOOT_REQUIRED    ( 1 << 27 )    /* System needs a reboot bofore enable CrossfireX */\r\n#define ADL_XFIREX_STATE_DISABLE_CF_REBOOT_REQUIRED    ( 1 << 28 )    /* System needs a reboot after disable CrossfireX */\r\n#define ADL_XFIREX_STATE_DRV_HANDLE_DOWNGRADE_KEY    ( 1 << 29 )    /* Indicate base driver handles the downgrade key updating */\r\n#define ADL_XFIREX_STATE_CF_RECONFIG_REQUIRED        ( 1 << 30 )    /* CrossfireX need to be reconfigured by CCC because of a LDA chain broken */\r\n#define ADL_XFIREX_STATE_ERRORGETTINGSTATUS        ( 1 << 31 )    /* Could not obtain current status */\r\n/// @}\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADL_DISPLAY_ADJUSTMENT_PIXELFORMAT adjustment values\r\n// (bit-vector)\r\n///////////////////////////////////////////////////////////////////////////\r\n/// \\defgroup define_pixel_formats Pixel Formats values\r\n/// This group defines the various Pixel Formats that a particular digital display can support. \\n\r\n/// Since a display can support multiple formats, these values can be bit-or'ed to indicate the various formats \\n\r\n/// @{\r\n#define ADL_DISPLAY_PIXELFORMAT_UNKNOWN             0\r\n#define ADL_DISPLAY_PIXELFORMAT_RGB                       (1 << 0)\r\n#define ADL_DISPLAY_PIXELFORMAT_YCRCB444                  (1 << 1)    //Limited range\r\n#define ADL_DISPLAY_PIXELFORMAT_YCRCB422                 (1 << 2)    //Limited range\r\n#define ADL_DISPLAY_PIXELFORMAT_RGB_LIMITED_RANGE      (1 << 3)\r\n#define ADL_DISPLAY_PIXELFORMAT_RGB_FULL_RANGE    ADL_DISPLAY_PIXELFORMAT_RGB  //Full range\r\n#define ADL_DISPLAY_PIXELFORMAT_YCRCB420              (1 << 4)\r\n/// @}\r\n\r\n/// \\defgroup define_contype Connector Type Values\r\n/// ADLDisplayConfig.ulConnectorType defines\r\n/// @{\r\n#define ADL_DL_DISPLAYCONFIG_CONTYPE_UNKNOWN      0\r\n#define ADL_DL_DISPLAYCONFIG_CONTYPE_CV_NONI2C_JP 1\r\n#define ADL_DL_DISPLAYCONFIG_CONTYPE_CV_JPN       2\r\n#define ADL_DL_DISPLAYCONFIG_CONTYPE_CV_NA        3\r\n#define ADL_DL_DISPLAYCONFIG_CONTYPE_CV_NONI2C_NA 4\r\n#define ADL_DL_DISPLAYCONFIG_CONTYPE_VGA          5\r\n#define ADL_DL_DISPLAYCONFIG_CONTYPE_DVI_D        6\r\n#define ADL_DL_DISPLAYCONFIG_CONTYPE_DVI_I        7\r\n#define ADL_DL_DISPLAYCONFIG_CONTYPE_HDMI_TYPE_A  8\r\n#define ADL_DL_DISPLAYCONFIG_CONTYPE_HDMI_TYPE_B  9\r\n#define ADL_DL_DISPLAYCONFIG_CONTYPE_DISPLAYPORT  10\r\n/// @}\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADL_DISPLAY_DISPLAYINFO_ Definitions\r\n// for ADLDisplayInfo.iDisplayInfoMask and ADLDisplayInfo.iDisplayInfoValue\r\n// (bit-vector)\r\n///////////////////////////////////////////////////////////////////////////\r\n/// \\defgroup define_displayinfomask Display Info Mask Values\r\n/// @{\r\n#define ADL_DISPLAY_DISPLAYINFO_DISPLAYCONNECTED            0x00000001\r\n#define ADL_DISPLAY_DISPLAYINFO_DISPLAYMAPPED                0x00000002\r\n#define ADL_DISPLAY_DISPLAYINFO_NONLOCAL                    0x00000004\r\n#define ADL_DISPLAY_DISPLAYINFO_FORCIBLESUPPORTED            0x00000008\r\n#define ADL_DISPLAY_DISPLAYINFO_GENLOCKSUPPORTED            0x00000010\r\n#define ADL_DISPLAY_DISPLAYINFO_MULTIVPU_SUPPORTED            0x00000020\r\n#define ADL_DISPLAY_DISPLAYINFO_LDA_DISPLAY                    0x00000040\r\n#define ADL_DISPLAY_DISPLAYINFO_MODETIMING_OVERRIDESSUPPORTED            0x00000080\r\n\r\n#define ADL_DISPLAY_DISPLAYINFO_MANNER_SUPPORTED_SINGLE            0x00000100\r\n#define ADL_DISPLAY_DISPLAYINFO_MANNER_SUPPORTED_CLONE            0x00000200\r\n\r\n/// Legacy support for XP\r\n#define ADL_DISPLAY_DISPLAYINFO_MANNER_SUPPORTED_2VSTRETCH        0x00000400\r\n#define ADL_DISPLAY_DISPLAYINFO_MANNER_SUPPORTED_2HSTRETCH        0x00000800\r\n#define ADL_DISPLAY_DISPLAYINFO_MANNER_SUPPORTED_EXTENDED        0x00001000\r\n\r\n/// More support manners\r\n#define ADL_DISPLAY_DISPLAYINFO_MANNER_SUPPORTED_NSTRETCH1GPU    0x00010000\r\n#define ADL_DISPLAY_DISPLAYINFO_MANNER_SUPPORTED_NSTRETCHNGPU    0x00020000\r\n#define ADL_DISPLAY_DISPLAYINFO_MANNER_SUPPORTED_RESERVED2        0x00040000\r\n#define ADL_DISPLAY_DISPLAYINFO_MANNER_SUPPORTED_RESERVED3        0x00080000\r\n\r\n/// Projector display type\r\n#define ADL_DISPLAY_DISPLAYINFO_SHOWTYPE_PROJECTOR                0x00100000\r\n\r\n/// @}\r\n\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADL_ADAPTER_DISPLAY_MANNER_SUPPORTED_ Definitions\r\n// for ADLAdapterDisplayCap of ADL_Adapter_Display_Cap()\r\n// (bit-vector)\r\n///////////////////////////////////////////////////////////////////////////\r\n/// \\defgroup define_adaptermanner Adapter Manner Support Values\r\n/// @{\r\n#define ADL_ADAPTER_DISPLAYCAP_MANNER_SUPPORTED_NOTACTIVE        0x00000001\r\n#define ADL_ADAPTER_DISPLAYCAP_MANNER_SUPPORTED_SINGLE            0x00000002\r\n#define ADL_ADAPTER_DISPLAYCAP_MANNER_SUPPORTED_CLONE            0x00000004\r\n#define ADL_ADAPTER_DISPLAYCAP_MANNER_SUPPORTED_NSTRETCH1GPU    0x00000008\r\n#define ADL_ADAPTER_DISPLAYCAP_MANNER_SUPPORTED_NSTRETCHNGPU    0x00000010\r\n\r\n/// Legacy support for XP\r\n#define ADL_ADAPTER_DISPLAYCAP_MANNER_SUPPORTED_2VSTRETCH        0x00000020\r\n#define ADL_ADAPTER_DISPLAYCAP_MANNER_SUPPORTED_2HSTRETCH        0x00000040\r\n#define ADL_ADAPTER_DISPLAYCAP_MANNER_SUPPORTED_EXTENDED        0x00000080\r\n\r\n#define ADL_ADAPTER_DISPLAYCAP_PREFERDISPLAY_SUPPORTED            0x00000100\r\n#define ADL_ADAPTER_DISPLAYCAP_BEZEL_SUPPORTED                    0x00000200\r\n\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADL_DISPLAY_DISPLAYMAP_MANNER_ Definitions\r\n// for ADLDisplayMap.iDisplayMapMask and ADLDisplayMap.iDisplayMapValue\r\n// (bit-vector)\r\n///////////////////////////////////////////////////////////////////////////\r\n#define ADL_DISPLAY_DISPLAYMAP_MANNER_RESERVED            0x00000001\r\n#define ADL_DISPLAY_DISPLAYMAP_MANNER_NOTACTIVE            0x00000002\r\n#define ADL_DISPLAY_DISPLAYMAP_MANNER_SINGLE            0x00000004\r\n#define ADL_DISPLAY_DISPLAYMAP_MANNER_CLONE                0x00000008\r\n#define ADL_DISPLAY_DISPLAYMAP_MANNER_RESERVED1            0x00000010  // Removed NSTRETCH\r\n#define ADL_DISPLAY_DISPLAYMAP_MANNER_HSTRETCH            0x00000020\r\n#define ADL_DISPLAY_DISPLAYMAP_MANNER_VSTRETCH            0x00000040\r\n#define ADL_DISPLAY_DISPLAYMAP_MANNER_VLD                0x00000080\r\n\r\n/// @}\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADL_DISPLAY_DISPLAYMAP_OPTION_ Definitions\r\n// for iOption in function ADL_Display_DisplayMapConfig_Get\r\n// (bit-vector)\r\n///////////////////////////////////////////////////////////////////////////\r\n#define ADL_DISPLAY_DISPLAYMAP_OPTION_GPUINFO            0x00000001\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADL_DISPLAY_DISPLAYTARGET_ Definitions\r\n// for ADLDisplayTarget.iDisplayTargetMask and ADLDisplayTarget.iDisplayTargetValue\r\n// (bit-vector)\r\n///////////////////////////////////////////////////////////////////////////\r\n#define ADL_DISPLAY_DISPLAYTARGET_PREFERRED            0x00000001\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADL_DISPLAY_POSSIBLEMAPRESULT_VALID Definitions\r\n// for ADLPossibleMapResult.iPossibleMapResultMask and ADLPossibleMapResult.iPossibleMapResultValue\r\n// (bit-vector)\r\n///////////////////////////////////////////////////////////////////////////\r\n#define ADL_DISPLAY_POSSIBLEMAPRESULT_VALID                0x00000001\r\n#define ADL_DISPLAY_POSSIBLEMAPRESULT_BEZELSUPPORTED    0x00000002\r\n#define ADL_DISPLAY_POSSIBLEMAPRESULT_OVERLAPSUPPORTED    0x00000004\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADL_DISPLAY_MODE_ Definitions\r\n// for ADLMode.iModeMask, ADLMode.iModeValue, and ADLMode.iModeFlag\r\n// (bit-vector)\r\n///////////////////////////////////////////////////////////////////////////\r\n/// \\defgroup define_displaymode Display Mode Values\r\n/// @{\r\n#define ADL_DISPLAY_MODE_COLOURFORMAT_565                0x00000001\r\n#define ADL_DISPLAY_MODE_COLOURFORMAT_8888                0x00000002\r\n#define ADL_DISPLAY_MODE_ORIENTATION_SUPPORTED_000        0x00000004\r\n#define ADL_DISPLAY_MODE_ORIENTATION_SUPPORTED_090        0x00000008\r\n#define ADL_DISPLAY_MODE_ORIENTATION_SUPPORTED_180        0x00000010\r\n#define ADL_DISPLAY_MODE_ORIENTATION_SUPPORTED_270        0x00000020\r\n#define ADL_DISPLAY_MODE_REFRESHRATE_ROUNDED            0x00000040\r\n#define ADL_DISPLAY_MODE_REFRESHRATE_ONLY                0x00000080\r\n\r\n#define ADL_DISPLAY_MODE_PROGRESSIVE_FLAG    0\r\n#define ADL_DISPLAY_MODE_INTERLACED_FLAG    2\r\n/// @}\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADL_OSMODEINFO Definitions\r\n///////////////////////////////////////////////////////////////////////////\r\n/// \\defgroup define_osmode OS Mode Values\r\n/// @{\r\n#define ADL_OSMODEINFOXPOS_DEFAULT                -640\r\n#define ADL_OSMODEINFOYPOS_DEFAULT                0\r\n#define ADL_OSMODEINFOXRES_DEFAULT                640\r\n#define ADL_OSMODEINFOYRES_DEFAULT                480\r\n#define ADL_OSMODEINFOXRES_DEFAULT800            800\r\n#define ADL_OSMODEINFOYRES_DEFAULT600            600\r\n#define ADL_OSMODEINFOREFRESHRATE_DEFAULT        60\r\n#define ADL_OSMODEINFOCOLOURDEPTH_DEFAULT        8\r\n#define ADL_OSMODEINFOCOLOURDEPTH_DEFAULT16        16\r\n#define ADL_OSMODEINFOCOLOURDEPTH_DEFAULT24        24\r\n#define ADL_OSMODEINFOCOLOURDEPTH_DEFAULT32        32\r\n#define ADL_OSMODEINFOORIENTATION_DEFAULT        0\r\n#define ADL_OSMODEINFOORIENTATION_DEFAULT_WIN7    DISPLAYCONFIG_ROTATION_FORCE_UINT32\r\n#define ADL_OSMODEFLAG_DEFAULT                    0\r\n/// @}\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADLThreadingModel Enumeration\r\n///////////////////////////////////////////////////////////////////////////\r\n/// \\defgroup thread_model\r\n/// Used with \\ref ADL_Main_ControlX2_Create and \\ref ADL2_Main_ControlX2_Create to specify how ADL handles API calls when executed by multiple threads concurrently.\r\n/// \\brief Declares ADL threading behavior.\r\n/// @{\r\ntypedef enum ADLThreadingModel\r\n{\r\n    ADL_THREADING_UNLOCKED    = 0, /*!< Default behavior. ADL will not enforce serialization of ADL API executions by multiple threads.  Multiple threads will be allowed to enter to ADL at the same time. Note that ADL library is not guaranteed to be thread-safe. Client that calls ADL_Main_Control_Create have to provide its own mechanism for ADL calls serialization. */\r\n    ADL_THREADING_LOCKED     /*!< ADL will enforce serialization of ADL API when called by multiple threads.  Only single thread will be allowed to enter ADL API at the time. This option makes ADL calls thread-safe. You shouldn't use this option if ADL calls will be executed on Linux on x-server rendering thread. It can cause the application to hung.  */\r\n}ADLThreadingModel;\r\n\r\n/// @}\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADLPurposeCode Enumeration\r\n///////////////////////////////////////////////////////////////////////////\r\nenum ADLPurposeCode\r\n{\r\n    ADL_PURPOSECODE_NORMAL    = 0,\r\n    ADL_PURPOSECODE_HIDE_MODE_SWITCH,\r\n    ADL_PURPOSECODE_MODE_SWITCH,\r\n    ADL_PURPOSECODE_ATTATCH_DEVICE,\r\n    ADL_PURPOSECODE_DETACH_DEVICE,\r\n    ADL_PURPOSECODE_SETPRIMARY_DEVICE,\r\n    ADL_PURPOSECODE_GDI_ROTATION,\r\n    ADL_PURPOSECODE_ATI_ROTATION\r\n};\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADLAngle Enumeration\r\n///////////////////////////////////////////////////////////////////////////\r\nenum ADLAngle\r\n{\r\n    ADL_ANGLE_LANDSCAPE = 0,\r\n    ADL_ANGLE_ROTATERIGHT = 90,\r\n    ADL_ANGLE_ROTATE180 = 180,\r\n    ADL_ANGLE_ROTATELEFT = 270,\r\n};\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADLOrientationDataType Enumeration\r\n///////////////////////////////////////////////////////////////////////////\r\nenum ADLOrientationDataType\r\n{\r\n    ADL_ORIENTATIONTYPE_OSDATATYPE,\r\n    ADL_ORIENTATIONTYPE_NONOSDATATYPE\r\n};\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADLPanningMode Enumeration\r\n///////////////////////////////////////////////////////////////////////////\r\nenum ADLPanningMode\r\n{\r\n    ADL_PANNINGMODE_NO_PANNING = 0,\r\n    ADL_PANNINGMODE_AT_LEAST_ONE_NO_PANNING = 1,\r\n    ADL_PANNINGMODE_ALLOW_PANNING = 2,\r\n};\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADLLARGEDESKTOPTYPE Enumeration\r\n///////////////////////////////////////////////////////////////////////////\r\nenum ADLLARGEDESKTOPTYPE\r\n{\r\n    ADL_LARGEDESKTOPTYPE_NORMALDESKTOP = 0,\r\n    ADL_LARGEDESKTOPTYPE_PSEUDOLARGEDESKTOP = 1,\r\n    ADL_LARGEDESKTOPTYPE_VERYLARGEDESKTOP = 2\r\n};\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADLPlatform Enumeration\r\n///////////////////////////////////////////////////////////////////////////\r\nenum ADLPlatForm\r\n{\r\n    GRAPHICS_PLATFORM_DESKTOP  = 0,\r\n    GRAPHICS_PLATFORM_MOBILE   = 1\r\n};\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADLGraphicCoreGeneration Enumeration\r\n///////////////////////////////////////////////////////////////////////////\r\nenum ADLGraphicCoreGeneration\r\n{\r\n    ADL_GRAPHIC_CORE_GENERATION_UNDEFINED                   = 0,\r\n    ADL_GRAPHIC_CORE_GENERATION_PRE_GCN                     = 1,\r\n    ADL_GRAPHIC_CORE_GENERATION_GCN                         = 2,\r\n    ADL_GRAPHIC_CORE_GENERATION_RDNA                        = 3\r\n};\r\n\r\n// Other Definitions for internal use\r\n\r\n// Values for ADL_Display_WriteAndReadI2CRev_Get()\r\n\r\n#define ADL_I2C_MAJOR_API_REV           0x00000001\r\n#define ADL_I2C_MINOR_DEFAULT_API_REV   0x00000000\r\n#define ADL_I2C_MINOR_OEM_API_REV       0x00000001\r\n\r\n// Values for ADL_Display_WriteAndReadI2C()\r\n#define ADL_DL_I2C_LINE_OEM                0x00000001\r\n#define ADL_DL_I2C_LINE_OD_CONTROL         0x00000002\r\n#define ADL_DL_I2C_LINE_OEM2               0x00000003\r\n#define ADL_DL_I2C_LINE_OEM3               0x00000004\r\n#define ADL_DL_I2C_LINE_OEM4               0x00000005\r\n#define ADL_DL_I2C_LINE_OEM5               0x00000006\r\n#define ADL_DL_I2C_LINE_OEM6               0x00000007\r\n#define ADL_DL_I2C_LINE_GPIO               0x00000008\r\n\r\n// Max size of I2C data buffer\r\n#define ADL_DL_I2C_MAXDATASIZE             0x00000018\r\n#define ADL_DL_I2C_MAXWRITEDATASIZE        0x0000000C\r\n#define ADL_DL_I2C_MAXADDRESSLENGTH        0x00000006\r\n#define ADL_DL_I2C_MAXOFFSETLENGTH         0x00000004\r\n\r\n// I2C clock speed in KHz\r\n#define ADL_DL_I2C_SPEED_50K               50\r\n#define ADL_DL_I2C_SPEED_100K              100\r\n#define ALD_DL_I2C_SPEED_400K              400\r\n#define ADL_DL_I2C_SPEED_1M                1000\r\n#define ADL_DL_I2C_SPEED_2M                2300\r\n\r\n/// Values for ADLDisplayProperty.iPropertyType\r\n#define ADL_DL_DISPLAYPROPERTY_TYPE_UNKNOWN              0\r\n#define ADL_DL_DISPLAYPROPERTY_TYPE_EXPANSIONMODE        1\r\n#define ADL_DL_DISPLAYPROPERTY_TYPE_USEUNDERSCANSCALING     2\r\n/// Enables ITC processing for HDMI panels that are capable of the feature\r\n#define ADL_DL_DISPLAYPROPERTY_TYPE_ITCFLAGENABLE        9\r\n#define ADL_DL_DISPLAYPROPERTY_TYPE_DOWNSCALE            11\r\n#define ADL_DL_DISPLAYPROPERTY_TYPE_INTEGER_SCALING      12\r\n\r\n\r\n/// Values for ADLDisplayContent.iContentType\r\n/// Certain HDMI panels that support ITC have support for a feature such that, the display on the panel\r\n/// can be adjusted to optimize the view of the content being displayed, depending on the type of content.\r\n#define ADL_DL_DISPLAYCONTENT_TYPE_GRAPHICS        1\r\n#define ADL_DL_DISPLAYCONTENT_TYPE_PHOTO        2\r\n#define ADL_DL_DISPLAYCONTENT_TYPE_CINEMA        4\r\n#define ADL_DL_DISPLAYCONTENT_TYPE_GAME            8\r\n\r\n\r\n\r\n//values for ADLDisplayProperty.iExpansionMode\r\n#define ADL_DL_DISPLAYPROPERTY_EXPANSIONMODE_CENTER        0\r\n#define ADL_DL_DISPLAYPROPERTY_EXPANSIONMODE_FULLSCREEN    1\r\n#define ADL_DL_DISPLAYPROPERTY_EXPANSIONMODE_ASPECTRATIO   2\r\n\r\n\r\n///\\defgroup define_dither_states Dithering options\r\n/// @{\r\n/// Dithering disabled.\r\n#define ADL_DL_DISPLAY_DITHER_DISABLED              0\r\n/// Use default driver settings for dithering. Note that the default setting could be dithering disabled.\r\n#define ADL_DL_DISPLAY_DITHER_DRIVER_DEFAULT        1\r\n/// Temporal dithering to 6 bpc. Note that if the input is 12 bits, the two least significant bits will be truncated.\r\n#define ADL_DL_DISPLAY_DITHER_FM6                   2\r\n/// Temporal dithering to 8 bpc.\r\n#define ADL_DL_DISPLAY_DITHER_FM8                   3\r\n/// Temporal dithering to 10 bpc.\r\n#define ADL_DL_DISPLAY_DITHER_FM10                  4\r\n/// Spatial dithering to 6 bpc. Note that if the input is 12 bits, the two least significant bits will be truncated.\r\n#define ADL_DL_DISPLAY_DITHER_DITH6                 5\r\n/// Spatial dithering to 8 bpc.\r\n#define ADL_DL_DISPLAY_DITHER_DITH8                 6\r\n/// Spatial dithering to 10 bpc.\r\n#define ADL_DL_DISPLAY_DITHER_DITH10                7\r\n/// Spatial dithering to 6 bpc. Random number generators are reset every frame, so the same input value of a certain pixel will always be dithered to the same output value. Note that if the input is 12 bits, the two least significant bits will be truncated.\r\n#define ADL_DL_DISPLAY_DITHER_DITH6_NO_FRAME_RAND   8\r\n/// Spatial dithering to 8 bpc. Random number generators are reset every frame, so the same input value of a certain pixel will always be dithered to the same output value.\r\n#define ADL_DL_DISPLAY_DITHER_DITH8_NO_FRAME_RAND   9\r\n/// Spatial dithering to 10 bpc. Random number generators are reset every frame, so the same input value of a certain pixel will always be dithered to the same output value.\r\n#define ADL_DL_DISPLAY_DITHER_DITH10_NO_FRAME_RAND  10\r\n/// Truncation to 6 bpc.\r\n#define ADL_DL_DISPLAY_DITHER_TRUN6                 11\r\n/// Truncation to 8 bpc.\r\n#define ADL_DL_DISPLAY_DITHER_TRUN8                 12\r\n/// Truncation to 10 bpc.\r\n#define ADL_DL_DISPLAY_DITHER_TRUN10                13\r\n/// Truncation to 10 bpc followed by spatial dithering to 8 bpc.\r\n#define ADL_DL_DISPLAY_DITHER_TRUN10_DITH8          14\r\n/// Truncation to 10 bpc followed by spatial dithering to 6 bpc.\r\n#define ADL_DL_DISPLAY_DITHER_TRUN10_DITH6          15\r\n/// Truncation to 10 bpc followed by temporal dithering to 8 bpc.\r\n#define ADL_DL_DISPLAY_DITHER_TRUN10_FM8            16\r\n/// Truncation to 10 bpc followed by temporal dithering to 6 bpc.\r\n#define ADL_DL_DISPLAY_DITHER_TRUN10_FM6            17\r\n/// Truncation to 10 bpc followed by spatial dithering to 8 bpc and temporal dithering to 6 bpc.\r\n#define ADL_DL_DISPLAY_DITHER_TRUN10_DITH8_FM6      18\r\n/// Spatial dithering to 10 bpc followed by temporal dithering to 8 bpc.\r\n#define ADL_DL_DISPLAY_DITHER_DITH10_FM8            19\r\n/// Spatial dithering to 10 bpc followed by temporal dithering to 6 bpc.\r\n#define ADL_DL_DISPLAY_DITHER_DITH10_FM6            20\r\n/// Truncation to 8 bpc followed by spatial dithering to 6 bpc.\r\n#define ADL_DL_DISPLAY_DITHER_TRUN8_DITH6           21\r\n/// Truncation to 8 bpc followed by temporal dithering to 6 bpc.\r\n#define ADL_DL_DISPLAY_DITHER_TRUN8_FM6             22\r\n/// Spatial dithering to 8 bpc followed by temporal dithering to 6 bpc.\r\n#define ADL_DL_DISPLAY_DITHER_DITH8_FM6             23\r\n#define ADL_DL_DISPLAY_DITHER_LAST                  ADL_DL_DISPLAY_DITHER_DITH8_FM6\r\n/// @}\r\n\r\n\r\n/// Display Get Cached EDID flag\r\n#define ADL_MAX_EDIDDATA_SIZE              256 // number of UCHAR\r\n#define ADL_MAX_OVERRIDEEDID_SIZE          512 // number of UCHAR\r\n#define ADL_MAX_EDID_EXTENSION_BLOCKS      3\r\n\r\n#define ADL_DL_CONTROLLER_OVERLAY_ALPHA         0\r\n#define ADL_DL_CONTROLLER_OVERLAY_ALPHAPERPIX   1\r\n\r\n#define ADL_DL_DISPLAY_DATA_PACKET__INFO_PACKET_RESET      0x00000000\r\n#define ADL_DL_DISPLAY_DATA_PACKET__INFO_PACKET_SET        0x00000001\r\n#define ADL_DL_DISPLAY_DATA_PACKET__INFO_PACKET_SCAN       0x00000002\r\n\r\n///\\defgroup define_display_packet Display Data Packet Types\r\n/// @{\r\n#define ADL_DL_DISPLAY_DATA_PACKET__TYPE__AVI              0x00000001\r\n#define ADL_DL_DISPLAY_DATA_PACKET__TYPE__GAMMUT           0x00000002\r\n#define ADL_DL_DISPLAY_DATA_PACKET__TYPE__VENDORINFO       0x00000004\r\n#define ADL_DL_DISPLAY_DATA_PACKET__TYPE__HDR              0x00000008\r\n#define ADL_DL_DISPLAY_DATA_PACKET__TYPE__SPD              0x00000010\r\n/// @}\r\n\r\n// matrix types\r\n#define ADL_GAMUT_MATRIX_SD         1   // SD matrix i.e. BT601\r\n#define ADL_GAMUT_MATRIX_HD         2   // HD matrix i.e. BT709\r\n\r\n///\\defgroup define_clockinfo_flags Clock flags\r\n/// Used by ADLAdapterODClockInfo.iFlag\r\n/// @{\r\n#define ADL_DL_CLOCKINFO_FLAG_FULLSCREEN3DONLY         0x00000001\r\n#define ADL_DL_CLOCKINFO_FLAG_ALWAYSFULLSCREEN3D       0x00000002\r\n#define ADL_DL_CLOCKINFO_FLAG_VPURECOVERYREDUCED       0x00000004\r\n#define ADL_DL_CLOCKINFO_FLAG_THERMALPROTECTION        0x00000008\r\n/// @}\r\n\r\n// Supported GPUs\r\n// ADL_Display_PowerXpressActiveGPU_Get()\r\n#define ADL_DL_POWERXPRESS_GPU_INTEGRATED        1\r\n#define ADL_DL_POWERXPRESS_GPU_DISCRETE            2\r\n\r\n// Possible values for lpOperationResult\r\n// ADL_Display_PowerXpressActiveGPU_Get()\r\n#define ADL_DL_POWERXPRESS_SWITCH_RESULT_STARTED         1 // Switch procedure has been started - Windows platform only\r\n#define ADL_DL_POWERXPRESS_SWITCH_RESULT_DECLINED        2 // Switch procedure cannot be started - All platforms\r\n#define ADL_DL_POWERXPRESS_SWITCH_RESULT_ALREADY         3 // System already has required status  - All platforms\r\n#define ADL_DL_POWERXPRESS_SWITCH_RESULT_DEFERRED        5  // Switch was deferred and requires an X restart - Linux platform only\r\n\r\n// PowerXpress support version\r\n// ADL_Display_PowerXpressVersion_Get()\r\n#define ADL_DL_POWERXPRESS_VERSION_MAJOR            2    // Current PowerXpress support version 2.0\r\n#define ADL_DL_POWERXPRESS_VERSION_MINOR            0\r\n\r\n#define ADL_DL_POWERXPRESS_VERSION    (((ADL_DL_POWERXPRESS_VERSION_MAJOR) << 16) | ADL_DL_POWERXPRESS_VERSION_MINOR)\r\n\r\n//values for ADLThermalControllerInfo.iThermalControllerDomain\r\n#define ADL_DL_THERMAL_DOMAIN_OTHER      0\r\n#define ADL_DL_THERMAL_DOMAIN_GPU        1\r\n\r\n//values for ADLThermalControllerInfo.iFlags\r\n#define ADL_DL_THERMAL_FLAG_INTERRUPT    1\r\n#define ADL_DL_THERMAL_FLAG_FANCONTROL   2\r\n\r\n///\\defgroup define_fanctrl Fan speed cotrol\r\n/// Values for ADLFanSpeedInfo.iFlags\r\n/// @{\r\n#define ADL_DL_FANCTRL_SUPPORTS_PERCENT_READ     1\r\n#define ADL_DL_FANCTRL_SUPPORTS_PERCENT_WRITE    2\r\n#define ADL_DL_FANCTRL_SUPPORTS_RPM_READ         4\r\n#define ADL_DL_FANCTRL_SUPPORTS_RPM_WRITE        8\r\n/// @}\r\n\r\n//values for ADLFanSpeedValue.iSpeedType\r\n#define ADL_DL_FANCTRL_SPEED_TYPE_PERCENT    1\r\n#define ADL_DL_FANCTRL_SPEED_TYPE_RPM        2\r\n\r\n//values for ADLFanSpeedValue.iFlags\r\n#define ADL_DL_FANCTRL_FLAG_USER_DEFINED_SPEED   1\r\n\r\n// MVPU interfaces\r\n#define ADL_DL_MAX_MVPU_ADAPTERS   4\r\n#define MVPU_ADAPTER_0          0x00000001\r\n#define MVPU_ADAPTER_1          0x00000002\r\n#define MVPU_ADAPTER_2          0x00000004\r\n#define MVPU_ADAPTER_3          0x00000008\r\n#define ADL_DL_MAX_REGISTRY_PATH   256\r\n\r\n//values for ADLMVPUStatus.iStatus\r\n#define ADL_DL_MVPU_STATUS_OFF   0\r\n#define ADL_DL_MVPU_STATUS_ON    1\r\n\r\n// values for ASIC family\r\n///\\defgroup define_Asic_type Detailed asic types\r\n/// Defines for Adapter ASIC family type\r\n/// @{\r\n#define ADL_ASIC_UNDEFINED      0\r\n#define ADL_ASIC_DISCRETE       (1 << 0)\r\n#define ADL_ASIC_INTEGRATED     (1 << 1)\r\n#define ADL_ASIC_WORKSTATION    (1 << 2)\r\n#define ADL_ASIC_FIREMV         (1 << 3)\r\n#define ADL_ASIC_XGP            (1 << 4)\r\n#define ADL_ASIC_FUSION         (1 << 5)\r\n#define ADL_ASIC_FIRESTREAM     (1 << 6)\r\n#define ADL_ASIC_EMBEDDED       (1 << 7)\r\n// Backward compatibility\r\n#define ADL_ASIC_FIREGL  ADL_ASIC_WORKSTATION\r\n/// @}\r\n\r\n///\\defgroup define_detailed_timing_flags Detailed Timimg Flags\r\n/// Defines for ADLDetailedTiming.sTimingFlags field\r\n/// @{\r\n#define ADL_DL_TIMINGFLAG_DOUBLE_SCAN              0x0001\r\n//sTimingFlags is set when the mode is INTERLACED, if not PROGRESSIVE\r\n#define ADL_DL_TIMINGFLAG_INTERLACED               0x0002\r\n//sTimingFlags is set when the Horizontal Sync is POSITIVE, if not NEGATIVE\r\n#define ADL_DL_TIMINGFLAG_H_SYNC_POLARITY          0x0004\r\n//sTimingFlags is set when the Vertical Sync is POSITIVE, if not NEGATIVE\r\n#define ADL_DL_TIMINGFLAG_V_SYNC_POLARITY          0x0008\r\n/// @}\r\n\r\n///\\defgroup define_modetiming_standard Timing Standards\r\n/// Defines for ADLDisplayModeInfo.iTimingStandard field\r\n/// @{\r\n#define ADL_DL_MODETIMING_STANDARD_CVT             0x00000001 // CVT Standard\r\n#define ADL_DL_MODETIMING_STANDARD_GTF             0x00000002 // GFT Standard\r\n#define ADL_DL_MODETIMING_STANDARD_DMT             0x00000004 // DMT Standard\r\n#define ADL_DL_MODETIMING_STANDARD_CUSTOM          0x00000008 // User-defined standard\r\n#define ADL_DL_MODETIMING_STANDARD_DRIVER_DEFAULT  0x00000010 // Remove Mode from overriden list\r\n#define ADL_DL_MODETIMING_STANDARD_CVT_RB           0x00000020 // CVT-RB Standard\r\n/// @}\r\n\r\n// \\defgroup define_xserverinfo driver x-server info\r\n/// These flags are used by ADL_XServerInfo_Get()\r\n// @\r\n\r\n/// Xinerama is active in the x-server, Xinerama extension may report it to be active but it\r\n/// may not be active in x-server\r\n#define ADL_XSERVERINFO_XINERAMAACTIVE            (1<<0)\r\n\r\n/// RandR 1.2 is supported by driver, RandR extension may report version 1.2\r\n/// but driver may not support it\r\n#define ADL_XSERVERINFO_RANDR12SUPPORTED          (1<<1)\r\n// @\r\n\r\n\r\n///\\defgroup define_eyefinity_constants Eyefinity Definitions\r\n/// @{\r\n\r\n#define ADL_CONTROLLERVECTOR_0        1    // ADL_CONTROLLERINDEX_0 = 0, (1 << ADL_CONTROLLERINDEX_0)\r\n#define ADL_CONTROLLERVECTOR_1        2    // ADL_CONTROLLERINDEX_1 = 1, (1 << ADL_CONTROLLERINDEX_1)\r\n\r\n#define ADL_DISPLAY_SLSGRID_ORIENTATION_000        0x00000001\r\n#define ADL_DISPLAY_SLSGRID_ORIENTATION_090        0x00000002\r\n#define ADL_DISPLAY_SLSGRID_ORIENTATION_180        0x00000004\r\n#define ADL_DISPLAY_SLSGRID_ORIENTATION_270        0x00000008\r\n#define ADL_DISPLAY_SLSGRID_CAP_OPTION_RELATIVETO_LANDSCAPE     0x00000001\r\n#define ADL_DISPLAY_SLSGRID_CAP_OPTION_RELATIVETO_CURRENTANGLE     0x00000002\r\n#define ADL_DISPLAY_SLSGRID_PORTAIT_MODE                         0x00000004\r\n#define ADL_DISPLAY_SLSGRID_KEEPTARGETROTATION                  0x00000080\r\n\r\n#define ADL_DISPLAY_SLSGRID_SAMEMODESLS_SUPPORT        0x00000010\r\n#define ADL_DISPLAY_SLSGRID_MIXMODESLS_SUPPORT        0x00000020\r\n#define ADL_DISPLAY_SLSGRID_DISPLAYROTATION_SUPPORT    0x00000040\r\n#define ADL_DISPLAY_SLSGRID_DESKTOPROTATION_SUPPORT    0x00000080\r\n\r\n\r\n#define ADL_DISPLAY_SLSMAP_SLSLAYOUTMODE_FIT        0x0100\r\n#define ADL_DISPLAY_SLSMAP_SLSLAYOUTMODE_FILL       0x0200\r\n#define ADL_DISPLAY_SLSMAP_SLSLAYOUTMODE_EXPAND     0x0400\r\n\r\n#define ADL_DISPLAY_SLSMAP_IS_SLS        0x1000\r\n#define ADL_DISPLAY_SLSMAP_IS_SLSBUILDER 0x2000\r\n#define ADL_DISPLAY_SLSMAP_IS_CLONEVT     0x4000\r\n\r\n#define ADL_DISPLAY_SLSMAPCONFIG_GET_OPTION_RELATIVETO_LANDSCAPE         0x00000001\r\n#define ADL_DISPLAY_SLSMAPCONFIG_GET_OPTION_RELATIVETO_CURRENTANGLE     0x00000002\r\n\r\n#define ADL_DISPLAY_SLSMAPCONFIG_CREATE_OPTION_RELATIVETO_LANDSCAPE         0x00000001\r\n#define ADL_DISPLAY_SLSMAPCONFIG_CREATE_OPTION_RELATIVETO_CURRENTANGLE     0x00000002\r\n\r\n#define ADL_DISPLAY_SLSMAPCONFIG_REARRANGE_OPTION_RELATIVETO_LANDSCAPE     0x00000001\r\n#define ADL_DISPLAY_SLSMAPCONFIG_REARRANGE_OPTION_RELATIVETO_CURRENTANGLE     0x00000002\r\n\r\n#define ADL_SLS_SAMEMODESLS_SUPPORT         0x0001\r\n#define ADL_SLS_MIXMODESLS_SUPPORT          0x0002\r\n#define ADL_SLS_DISPLAYROTATIONSLS_SUPPORT  0x0004\r\n#define ADL_SLS_DESKTOPROTATIONSLS_SUPPORT  0x0008\r\n\r\n#define ADL_SLS_TARGETS_INVALID     0x0001\r\n#define ADL_SLS_MODES_INVALID       0x0002\r\n#define ADL_SLS_ROTATIONS_INVALID   0x0004\r\n#define ADL_SLS_POSITIONS_INVALID   0x0008\r\n#define ADL_SLS_LAYOUTMODE_INVALID  0x0010\r\n\r\n#define ADL_DISPLAY_SLSDISPLAYOFFSET_VALID        0x0002\r\n\r\n#define ADL_DISPLAY_SLSGRID_RELATIVETO_LANDSCAPE         0x00000010\r\n#define ADL_DISPLAY_SLSGRID_RELATIVETO_CURRENTANGLE     0x00000020\r\n\r\n\r\n/// The bit mask identifies displays is currently in bezel mode.\r\n#define ADL_DISPLAY_SLSMAP_BEZELMODE            0x00000010\r\n/// The bit mask identifies displays from this map is arranged.\r\n#define ADL_DISPLAY_SLSMAP_DISPLAYARRANGED        0x00000002\r\n/// The bit mask identifies this map is currently in used for the current adapter.\r\n#define ADL_DISPLAY_SLSMAP_CURRENTCONFIG        0x00000004\r\n\r\n///For onlay active SLS  map info\r\n#define ADL_DISPLAY_SLSMAPINDEXLIST_OPTION_ACTIVE        0x00000001\r\n\r\n///For Bezel\r\n#define ADL_DISPLAY_BEZELOFFSET_STEPBYSTEPSET            0x00000004\r\n#define ADL_DISPLAY_BEZELOFFSET_COMMIT                    0x00000008\r\n\r\ntypedef enum SLS_ImageCropType {\r\n    Fit = 1,\r\n    Fill = 2,\r\n    Expand = 3\r\n}SLS_ImageCropType;\r\n\r\n\r\ntypedef enum DceSettingsType {\r\n    DceSetting_HdmiLq,\r\n    DceSetting_DpSettings,\r\n    DceSetting_Protection\r\n\r\n} DceSettingsType;\r\n\r\ntypedef enum DpLinkRate {\r\n    DPLinkRate_Unknown,\r\n    DPLinkRate_RBR,\r\n\tDPLinkRate_2_16Gbps,\r\n\tDPLinkRate_2_43Gbps,\r\n    DPLinkRate_HBR,\r\n\tDPLinkRate_4_32Gbps,\r\n    DPLinkRate_HBR2,\r\n    DPLinkRate_HBR3,\r\n\tDPLinkRate_UHBR10,\r\n\tDPLinkRate_UHBR13D5,\r\n\tDPLinkRate_UHBR20\r\n\r\n} DpLinkRate;\r\n\r\n/// @}\r\n\r\n///\\defgroup define_powerxpress_constants PowerXpress Definitions\r\n/// @{\r\n\r\n/// The bit mask identifies PX caps for ADLPXConfigCaps.iPXConfigCapMask and ADLPXConfigCaps.iPXConfigCapValue\r\n#define    ADL_PX_CONFIGCAPS_SPLASHSCREEN_SUPPORT        0x0001\r\n#define    ADL_PX_CONFIGCAPS_CF_SUPPORT                0x0002\r\n#define    ADL_PX_CONFIGCAPS_MUXLESS                    0x0004\r\n#define    ADL_PX_CONFIGCAPS_PROFILE_COMPLIANT            0x0008\r\n#define    ADL_PX_CONFIGCAPS_NON_AMD_DRIVEN_DISPLAYS    0x0010\r\n#define ADL_PX_CONFIGCAPS_FIXED_SUPPORT             0x0020\r\n#define ADL_PX_CONFIGCAPS_DYNAMIC_SUPPORT           0x0040\r\n#define ADL_PX_CONFIGCAPS_HIDE_AUTO_SWITCH            0x0080\r\n\r\n/// The bit mask identifies PX schemes for ADLPXSchemeRange\r\n#define ADL_PX_SCHEMEMASK_FIXED                        0x0001\r\n#define ADL_PX_SCHEMEMASK_DYNAMIC                    0x0002\r\n\r\n/// PX Schemes\r\ntypedef enum ADLPXScheme\r\n{\r\n    ADL_PX_SCHEME_INVALID   = 0,\r\n    ADL_PX_SCHEME_FIXED     = ADL_PX_SCHEMEMASK_FIXED,\r\n    ADL_PX_SCHEME_DYNAMIC   = ADL_PX_SCHEMEMASK_DYNAMIC\r\n}ADLPXScheme;\r\n\r\n/// Just keep the old definitions for compatibility, need to be removed later\r\ntypedef enum PXScheme\r\n{\r\n    PX_SCHEME_INVALID   = 0,\r\n    PX_SCHEME_FIXED     = 1,\r\n    PX_SCHEME_DYNAMIC   = 2\r\n} PXScheme;\r\n\r\n\r\n/// @}\r\n\r\n///\\defgroup define_appprofiles For Application Profiles\r\n/// @{\r\n\r\n#define ADL_APP_PROFILE_FILENAME_LENGTH        256\r\n#define ADL_APP_PROFILE_TIMESTAMP_LENGTH    32\r\n#define ADL_APP_PROFILE_VERSION_LENGTH        32\r\n#define ADL_APP_PROFILE_PROPERTY_LENGTH        64\r\n\r\nenum ApplicationListType\r\n{\r\n    ADL_PX40_MRU,\r\n    ADL_PX40_MISSED,\r\n    ADL_PX40_DISCRETE,\r\n    ADL_PX40_INTEGRATED,\r\n    ADL_MMD_PROFILED,\r\n    ADL_PX40_TOTAL\r\n};\r\n\r\ntypedef enum ADLProfilePropertyType\r\n{\r\n    ADL_PROFILEPROPERTY_TYPE_BINARY        = 0,\r\n    ADL_PROFILEPROPERTY_TYPE_BOOLEAN,\r\n    ADL_PROFILEPROPERTY_TYPE_DWORD,\r\n    ADL_PROFILEPROPERTY_TYPE_QWORD,\r\n    ADL_PROFILEPROPERTY_TYPE_ENUMERATED,\r\n    ADL_PROFILEPROPERTY_TYPE_STRING\r\n}ADLProfilePropertyType;\r\n\r\n\r\n//Virtual display type returning virtual display type and for request for creating a dummy target ID (xInput or remote play)\r\ntypedef enum ADL_VIRTUALDISPLAY_TYPE\r\n{\r\n\tADL_VIRTUALDISPLAY_NONE = 0,\r\n\tADL_VIRTUALDISPLAY_XINPUT = 1,\t\t\t//Requested for xInput\r\n\tADL_VIRTUALDISPLAY_REMOTEPLAY = 2,\t\t//Requested for emulated display during remote play\r\n\tADL_VIRTUALDISPLAY_GENERIC = 10\t\t\t//Generic virtual display, af a type different than any of the above special ones\r\n}ADL_VIRTUALDISPLAY_TYPE;\r\n\r\n/// @}\r\n\r\n///\\defgroup define_dp12 For Display Port 1.2\r\n/// @{\r\n\r\n/// Maximum Relative Address Link\r\n#define ADL_MAX_RAD_LINK_COUNT    15\r\n\r\n/// @}\r\n\r\n///\\defgroup defines_gamutspace Driver Supported Gamut Space\r\n/// @{\r\n\r\n/// The flags desribes that gamut is related to source or to destination and to overlay or to graphics\r\n#define ADL_GAMUT_REFERENCE_SOURCE       (1 << 0)\r\n#define ADL_GAMUT_GAMUT_VIDEO_CONTENT    (1 << 1)\r\n\r\n/// The flags are used to describe the source of gamut and how read information from struct ADLGamutData\r\n#define ADL_CUSTOM_WHITE_POINT           (1 << 0)\r\n#define ADL_CUSTOM_GAMUT                 (1 << 1)\r\n#define ADL_GAMUT_REMAP_ONLY             (1 << 2)\r\n\r\n/// The define means the predefined gamut values  .\r\n///Driver uses to find entry in the table and apply appropriate gamut space.\r\n#define ADL_GAMUT_SPACE_CCIR_709     (1 << 0)\r\n#define ADL_GAMUT_SPACE_CCIR_601     (1 << 1)\r\n#define ADL_GAMUT_SPACE_ADOBE_RGB    (1 << 2)\r\n#define ADL_GAMUT_SPACE_CIE_RGB      (1 << 3)\r\n#define ADL_GAMUT_SPACE_CUSTOM       (1 << 4)\r\n#define ADL_GAMUT_SPACE_CCIR_2020    (1 << 5)\r\n#define ADL_GAMUT_SPACE_APPCTRL      (1 << 6)\r\n\r\n/// Predefine white point values are structed similar to gamut .\r\n#define ADL_WHITE_POINT_5000K       (1 << 0)\r\n#define ADL_WHITE_POINT_6500K       (1 << 1)\r\n#define ADL_WHITE_POINT_7500K       (1 << 2)\r\n#define ADL_WHITE_POINT_9300K       (1 << 3)\r\n#define ADL_WHITE_POINT_CUSTOM      (1 << 4)\r\n\r\n///gamut and white point coordinates are from 0.0 -1.0 and divider is used to find the real value .\r\n/// X float = X int /divider\r\n#define ADL_GAMUT_WHITEPOINT_DIVIDER           10000\r\n\r\n///gamma a0 coefficient uses the following divider:\r\n#define ADL_REGAMMA_COEFFICIENT_A0_DIVIDER       10000000\r\n///gamma a1 ,a2,a3 coefficients use the following divider:\r\n#define ADL_REGAMMA_COEFFICIENT_A1A2A3_DIVIDER   1000\r\n\r\n///describes whether the coefficients are from EDID or custom user values.\r\n#define ADL_EDID_REGAMMA_COEFFICIENTS          (1 << 0)\r\n///Used for struct ADLRegamma. Feature if set use gamma ramp, if missing use regamma coefficents\r\n#define ADL_USE_GAMMA_RAMP                     (1 << 4)\r\n///Used for struct ADLRegamma. If the gamma ramp flag is used then the driver could apply de gamma corretion to the supplied curve and this depends on this flag\r\n#define ADL_APPLY_DEGAMMA                      (1 << 5)\r\n///specifies that standard SRGB gamma should be applied\r\n#define ADL_EDID_REGAMMA_PREDEFINED_SRGB       (1 << 1)\r\n///specifies that PQ gamma curve should be applied\r\n#define ADL_EDID_REGAMMA_PREDEFINED_PQ         (1 << 2)\r\n///specifies that PQ gamma curve should be applied, lower max nits\r\n#define ADL_EDID_REGAMMA_PREDEFINED_PQ_2084_INTERIM (1 << 3)\r\n///specifies that 3.6 gamma should be applied\r\n#define ADL_EDID_REGAMMA_PREDEFINED_36         (1 << 6)\r\n///specifies that BT709 gama should be applied\r\n#define ADL_EDID_REGAMMA_PREDEFINED_BT709      (1 << 7)\r\n///specifies that regamma should be disabled, and application controls regamma content (of the whole screen)\r\n#define ADL_EDID_REGAMMA_PREDEFINED_APPCTRL    (1 << 8)\r\n\r\n/// @}\r\n\r\n/// \\defgroup define_ddcinfo_pixelformats DDCInfo Pixel Formats\r\n/// @{\r\n/// defines for iPanelPixelFormat  in struct ADLDDCInfo2\r\n#define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_RGB656                       0x00000001L\r\n#define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_RGB666                       0x00000002L\r\n#define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_RGB888                       0x00000004L\r\n#define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_RGB101010                    0x00000008L\r\n#define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_RGB161616                    0x00000010L\r\n#define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_RGB_RESERVED1                0x00000020L\r\n#define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_RGB_RESERVED2                0x00000040L\r\n#define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_RGB_RESERVED3                0x00000080L\r\n#define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_XRGB_BIAS101010              0x00000100L\r\n#define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_YCBCR444_8BPCC               0x00000200L\r\n#define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_YCBCR444_10BPCC              0x00000400L\r\n#define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_YCBCR444_12BPCC              0x00000800L\r\n#define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_YCBCR422_8BPCC               0x00001000L\r\n#define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_YCBCR422_10BPCC              0x00002000L\r\n#define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_YCBCR422_12BPCC              0x00004000L\r\n#define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_YCBCR420_8BPCC               0x00008000L\r\n#define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_YCBCR420_10BPCC              0x00010000L\r\n#define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_YCBCR420_12BPCC              0x00020000L\r\n/// @}\r\n\r\n/// \\defgroup define_source_content_TF ADLSourceContentAttributes transfer functions (gamma)\r\n/// @{\r\n/// defines for iTransferFunction in ADLSourceContentAttributes\r\n#define ADL_TF_sRGB                0x0001      ///< sRGB\r\n#define ADL_TF_BT709            0x0002      ///< BT.709\r\n#define ADL_TF_PQ2084            0x0004      ///< PQ2084\r\n#define ADL_TF_PQ2084_INTERIM    0x0008        ///< PQ2084-Interim\r\n#define ADL_TF_LINEAR_0_1        0x0010      ///< Linear 0 - 1\r\n#define ADL_TF_LINEAR_0_125        0x0020      ///< Linear 0 - 125\r\n#define ADL_TF_DOLBYVISION        0x0040      ///< DolbyVision\r\n#define ADL_TF_GAMMA_22         0x0080      ///< Plain 2.2 gamma curve\r\n/// @}\r\n\r\n/// \\defgroup define_source_content_CS ADLSourceContentAttributes color spaces\r\n/// @{\r\n/// defines for iColorSpace in ADLSourceContentAttributes\r\n#define ADL_CS_sRGB                0x0001      ///< sRGB\r\n#define ADL_CS_BT601             0x0002      ///< BT.601\r\n#define ADL_CS_BT709            0x0004      ///< BT.709\r\n#define ADL_CS_BT2020            0x0008      ///< BT.2020\r\n#define ADL_CS_ADOBE            0x0010      ///< Adobe RGB\r\n#define ADL_CS_P3                0x0020      ///< DCI-P3\r\n#define ADL_CS_scRGB_MS_REF        0x0040      ///< scRGB (MS Reference)\r\n#define ADL_CS_DISPLAY_NATIVE    0x0080      ///< Display Native\r\n#define ADL_CS_APP_CONTROL         0x0100      ///< Application Controlled\r\n#define ADL_CS_DOLBYVISION      0x0200      ///< DolbyVision\r\n/// @}\r\n\r\n/// \\defgroup define_HDR_support ADLDDCInfo2 HDR support options\r\n/// @{\r\n/// defines for iSupportedHDR in ADLDDCInfo2\r\n#define ADL_HDR_CEA861_3        0x0001      ///< HDR10/CEA861.3 HDR supported\r\n#define ADL_HDR_DOLBYVISION     0x0002      ///< \\deprecated DolbyVision HDR supported\r\n#define ADL_HDR_FREESYNC_HDR    0x0004      ///< FreeSync HDR supported\r\n/// @}\r\n\r\n/// \\defgroup define_FreesyncFlags ADLDDCInfo2 Freesync HDR flags\r\n/// @{\r\n/// defines for iFreesyncFlags in ADLDDCInfo2\r\n#define ADL_HDR_FREESYNC_BACKLIGHT_SUPPORT           0x0001      ///< Global backlight control supported\r\n#define ADL_HDR_FREESYNC_LOCAL_DIMMING               0x0002      ///< Local dimming supported\r\n/// @}\r\n\r\n/// \\defgroup define_source_content_flags ADLSourceContentAttributes flags\r\n/// @{\r\n/// defines for iFlags in ADLSourceContentAttributes\r\n#define ADL_SCA_LOCAL_DIMMING_DISABLE    0x0001      ///< Disable local dimming\r\n/// @}\r\n\r\n/// \\defgroup define_dbd_state Deep Bit Depth\r\n/// @{\r\n\r\n/// defines for ADL_Workstation_DeepBitDepth_Get and  ADL_Workstation_DeepBitDepth_Set functions\r\n// This value indicates that the deep bit depth state is forced off\r\n#define ADL_DEEPBITDEPTH_FORCEOFF     0\r\n/// This value indicates that the deep bit depth state  is set to auto, the driver will automatically enable the\r\n/// appropriate deep bit depth state depending on what connected display supports.\r\n#define ADL_DEEPBITDEPTH_10BPP_AUTO     1\r\n/// This value indicates that the deep bit depth state  is forced on to 10 bits per pixel, this is regardless if the display\r\n/// supports 10 bpp.\r\n#define ADL_DEEPBITDEPTH_10BPP_FORCEON     2\r\n\r\n/// defines for ADLAdapterConfigMemory of ADL_Adapter_ConfigMemory_Get\r\n/// If this bit is set, it indicates that the Deep Bit Depth pixel is set on the display\r\n#define ADL_ADAPTER_CONFIGMEMORY_DBD            (1 << 0)\r\n/// If this bit is set, it indicates that the display is rotated (90, 180 or 270)\r\n#define ADL_ADAPTER_CONFIGMEMORY_ROTATE            (1 << 1)\r\n/// If this bit is set, it indicates that passive stereo is set on the display\r\n#define ADL_ADAPTER_CONFIGMEMORY_STEREO_PASSIVE    (1 << 2)\r\n/// If this bit is set, it indicates that the active stereo is set on the display\r\n#define ADL_ADAPTER_CONFIGMEMORY_STEREO_ACTIVE    (1 << 3)\r\n/// If this bit is set, it indicates that the tear free vsync is set on the display\r\n#define ADL_ADAPTER_CONFIGMEMORY_ENHANCEDVSYNC    (1 << 4)\r\n#define ADL_ADAPTER_CONFIGMEMORY_TEARFREEVSYNC    (1 << 4)\r\n/// @}\r\n\r\n/// \\defgroup define_adl_validmemoryrequiredfields Memory Type\r\n/// @{\r\n\r\n///  This group defines memory types in ADLMemoryRequired struct \\n\r\n/// Indicates that this is the visible memory\r\n#define ADL_MEMORYREQTYPE_VISIBLE                (1 << 0)\r\n/// Indicates that this is the invisible memory.\r\n#define ADL_MEMORYREQTYPE_INVISIBLE                (1 << 1)\r\n/// Indicates that this is amount of visible memory per GPU that should be reserved for all other allocations.\r\n#define ADL_MEMORYREQTYPE_GPURESERVEDVISIBLE    (1 << 2)\r\n/// @}\r\n\r\n/// \\defgroup define_adapter_tear_free_status\r\n/// Used in ADL_Adapter_TEAR_FREE_Set and ADL_Adapter_TFD_Get functions to indicate the tear free\r\n/// desktop status.\r\n/// @{\r\n/// Tear free desktop is enabled.\r\n#define ADL_ADAPTER_TEAR_FREE_ON                1\r\n/// Tear free desktop can't be enabled due to a lack of graphic adapter memory.\r\n#define ADL_ADAPTER_TEAR_FREE_NOTENOUGHMEM        -1\r\n/// Tear free desktop can't be enabled due to quad buffer stereo being enabled.\r\n#define ADL_ADAPTER_TEAR_FREE_OFF_ERR_QUADBUFFERSTEREO    -2\r\n/// Tear free desktop can't be enabled due to MGPU-SLS being enabled.\r\n#define ADL_ADAPTER_TEAR_FREE_OFF_ERR_MGPUSLD    -3\r\n/// Tear free desktop is disabled.\r\n#define ADL_ADAPTER_TEAR_FREE_OFF                0\r\n/// @}\r\n\r\n/// \\defgroup define_adapter_crossdisplay_platforminfo\r\n/// Used in ADL_Adapter_CrossDisplayPlatformInfo_Get function to indicate the Crossdisplay platform info.\r\n/// @{\r\n/// CROSSDISPLAY platform.\r\n#define ADL_CROSSDISPLAY_PLATFORM                    (1 << 0)\r\n/// CROSSDISPLAY platform for Lasso station.\r\n#define ADL_CROSSDISPLAY_PLATFORM_LASSO                (1 << 1)\r\n/// CROSSDISPLAY platform for docking station.\r\n#define ADL_CROSSDISPLAY_PLATFORM_DOCKSTATION        (1 << 2)\r\n/// @}\r\n\r\n/// \\defgroup define_adapter_crossdisplay_option\r\n/// Used in ADL_Adapter_CrossdisplayInfoX2_Set function to indicate cross display options.\r\n/// @{\r\n/// Checking if 3D application is runnning. If yes, not to do switch, return ADL_OK_WAIT; otherwise do switch.\r\n#define ADL_CROSSDISPLAY_OPTION_NONE            0\r\n/// Force switching without checking for running 3D applications\r\n#define ADL_CROSSDISPLAY_OPTION_FORCESWITCH        (1 << 0)\r\n/// @}\r\n\r\n/// \\defgroup define_adapter_states Adapter Capabilities\r\n/// These defines the capabilities supported by an adapter. It is used by \\ref ADL_Adapter_ConfigureState_Get\r\n/// @{\r\n/// Indicates that the adapter is headless (i.e. no displays can be connected to it)\r\n#define ADL_ADAPTERCONFIGSTATE_HEADLESS ( 1 << 2 )\r\n/// Indicates that the adapter is configured to define the main rendering capabilities. For example, adapters\r\n/// in Crossfire(TM) configuration, this bit would only be set on the adapter driving the display(s).\r\n#define ADL_ADAPTERCONFIGSTATE_REQUISITE_RENDER ( 1 << 0 )\r\n/// Indicates that the adapter is configured to be used to unload some of the rendering work for a particular\r\n/// requisite rendering adapter. For eample, for adapters in a Crossfire configuration, this bit would be set\r\n/// on all adapters that are currently not driving the display(s)\r\n#define ADL_ADAPTERCONFIGSTATE_ANCILLARY_RENDER ( 1 << 1 )\r\n/// Indicates that scatter gather feature enabled on the adapter\r\n#define ADL_ADAPTERCONFIGSTATE_SCATTERGATHER ( 1 << 4 )\r\n/// @}\r\n\r\n/// \\defgroup define_controllermode_ulModifiers\r\n/// These defines the detailed actions supported by set viewport. It is used by \\ref ADL_Display_ViewPort_Set\r\n/// @{\r\n/// Indicate that the viewport set will change the view position\r\n#define ADL_CONTROLLERMODE_CM_MODIFIER_VIEW_POSITION       0x00000001\r\n/// Indicate that the viewport set will change the view PanLock\r\n#define ADL_CONTROLLERMODE_CM_MODIFIER_VIEW_PANLOCK        0x00000002\r\n/// Indicate that the viewport set will change the view size\r\n#define ADL_CONTROLLERMODE_CM_MODIFIER_VIEW_SIZE           0x00000008\r\n/// @}\r\n\r\n/// \\defgroup defines for Mirabilis\r\n/// These defines are used for the Mirabilis feature\r\n/// @{\r\n///\r\n/// Indicates the maximum number of audio sample rates\r\n#define ADL_MAX_AUDIO_SAMPLE_RATE_COUNT                    16\r\n/// @}\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADLMultiChannelSplitStateFlag Enumeration\r\n///////////////////////////////////////////////////////////////////////////\r\nenum ADLMultiChannelSplitStateFlag\r\n{\r\n    ADLMultiChannelSplit_Unitialized = 0,\r\n    ADLMultiChannelSplit_Disabled    = 1,\r\n    ADLMultiChannelSplit_Enabled     = 2,\r\n    ADLMultiChannelSplit_SaveProfile = 3\r\n};\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADLSampleRate Enumeration\r\n///////////////////////////////////////////////////////////////////////////\r\nenum ADLSampleRate\r\n{\r\n    ADLSampleRate_32KHz =0,\r\n    ADLSampleRate_44P1KHz,\r\n    ADLSampleRate_48KHz,\r\n    ADLSampleRate_88P2KHz,\r\n    ADLSampleRate_96KHz,\r\n    ADLSampleRate_176P4KHz,\r\n    ADLSampleRate_192KHz,\r\n    ADLSampleRate_384KHz, //DP1.2\r\n    ADLSampleRate_768KHz, //DP1.2\r\n    ADLSampleRate_Undefined\r\n};\r\n\r\n/// \\defgroup define_overdrive6_capabilities\r\n/// These defines the capabilities supported by Overdrive 6. It is used by \\ref ADL_Overdrive6_Capabilities_Get\r\n/// @{\r\n/// Indicate that core (engine) clock can be changed.\r\n#define ADL_OD6_CAPABILITY_SCLK_CUSTOMIZATION               0x00000001\r\n/// Indicate that memory clock can be changed.\r\n#define ADL_OD6_CAPABILITY_MCLK_CUSTOMIZATION               0x00000002\r\n/// Indicate that graphics activity reporting is supported.\r\n#define ADL_OD6_CAPABILITY_GPU_ACTIVITY_MONITOR             0x00000004\r\n/// Indicate that power limit can be customized.\r\n#define ADL_OD6_CAPABILITY_POWER_CONTROL                    0x00000008\r\n/// Indicate that SVI2 Voltage Control is supported.\r\n#define ADL_OD6_CAPABILITY_VOLTAGE_CONTROL                  0x00000010\r\n/// Indicate that OD6+ percentage adjustment is supported.\r\n#define ADL_OD6_CAPABILITY_PERCENT_ADJUSTMENT               0x00000020\r\n/// Indicate that Thermal Limit Unlock is supported.\r\n#define ADL_OD6_CAPABILITY_THERMAL_LIMIT_UNLOCK             0x00000040\r\n///Indicate that Fan speed needs to be displayed in RPM\r\n#define ADL_OD6_CAPABILITY_FANSPEED_IN_RPM                    0x00000080\r\n/// @}\r\n\r\n/// \\defgroup define_overdrive6_supported_states\r\n/// These defines the power states supported by Overdrive 6. It is used by \\ref ADL_Overdrive6_Capabilities_Get\r\n/// @{\r\n/// Indicate that overdrive is supported in the performance state.  This is currently the only state supported.\r\n#define ADL_OD6_SUPPORTEDSTATE_PERFORMANCE                  0x00000001\r\n/// Do not use.  Reserved for future use.\r\n#define ADL_OD6_SUPPORTEDSTATE_POWER_SAVING                 0x00000002\r\n/// @}\r\n\r\n/// \\defgroup define_overdrive6_getstateinfo\r\n/// These defines the power states to get information about. It is used by \\ref ADL_Overdrive6_StateInfo_Get\r\n/// @{\r\n/// Get default clocks for the performance state.\r\n#define ADL_OD6_GETSTATEINFO_DEFAULT_PERFORMANCE            0x00000001\r\n/// Do not use.  Reserved for future use.\r\n#define ADL_OD6_GETSTATEINFO_DEFAULT_POWER_SAVING           0x00000002\r\n/// Get clocks for current state.  Currently this is the same as \\ref ADL_OD6_GETSTATEINFO_CUSTOM_PERFORMANCE\r\n/// since only performance state is supported.\r\n#define ADL_OD6_GETSTATEINFO_CURRENT                        0x00000003\r\n/// Get the modified clocks (if any) for the performance state.  If clocks were not modified\r\n/// through Overdrive 6, then this will return the same clocks as \\ref ADL_OD6_GETSTATEINFO_DEFAULT_PERFORMANCE.\r\n#define ADL_OD6_GETSTATEINFO_CUSTOM_PERFORMANCE             0x00000004\r\n/// Do not use.  Reserved for future use.\r\n#define ADL_OD6_GETSTATEINFO_CUSTOM_POWER_SAVING            0x00000005\r\n/// @}\r\n\r\n/// \\defgroup define_overdrive6_getstate and define_overdrive6_getmaxclockadjust\r\n/// These defines the power states to get information about. It is used by \\ref ADL_Overdrive6_StateEx_Get and \\ref ADL_Overdrive6_MaxClockAdjust_Get\r\n/// @{\r\n/// Get default clocks for the performance state.  Only performance state is currently supported.\r\n#define ADL_OD6_STATE_PERFORMANCE            0x00000001\r\n/// @}\r\n\r\n/// \\defgroup define_overdrive6_setstate\r\n/// These define which power state to set customized clocks on. It is used by \\ref ADL_Overdrive6_State_Set\r\n/// @{\r\n/// Set customized clocks for the performance state.\r\n#define ADL_OD6_SETSTATE_PERFORMANCE                        0x00000001\r\n/// Do not use.  Reserved for future use.\r\n#define ADL_OD6_SETSTATE_POWER_SAVING                       0x00000002\r\n/// @}\r\n\r\n/// \\defgroup define_overdrive6_thermalcontroller_caps\r\n/// These defines the capabilities of the GPU thermal controller. It is used by \\ref ADL_Overdrive6_ThermalController_Caps\r\n/// @{\r\n/// GPU thermal controller is supported.\r\n#define ADL_OD6_TCCAPS_THERMAL_CONTROLLER                   0x00000001\r\n/// GPU fan speed control is supported.\r\n#define ADL_OD6_TCCAPS_FANSPEED_CONTROL                     0x00000002\r\n/// Fan speed percentage can be read.\r\n#define ADL_OD6_TCCAPS_FANSPEED_PERCENT_READ                0x00000100\r\n/// Fan speed can be set by specifying a percentage value.\r\n#define ADL_OD6_TCCAPS_FANSPEED_PERCENT_WRITE               0x00000200\r\n/// Fan speed RPM (revolutions-per-minute) can be read.\r\n#define ADL_OD6_TCCAPS_FANSPEED_RPM_READ                    0x00000400\r\n/// Fan speed can be set by specifying an RPM value.\r\n#define ADL_OD6_TCCAPS_FANSPEED_RPM_WRITE                   0x00000800\r\n/// @}\r\n\r\n/// \\defgroup define_overdrive6_fanspeed_type\r\n/// These defines the fan speed type being reported. It is used by \\ref ADL_Overdrive6_FanSpeed_Get\r\n/// @{\r\n/// Fan speed reported in percentage.\r\n#define ADL_OD6_FANSPEED_TYPE_PERCENT                       0x00000001\r\n/// Fan speed reported in RPM.\r\n#define ADL_OD6_FANSPEED_TYPE_RPM                           0x00000002\r\n/// Fan speed has been customized by the user, and fan is not running in automatic mode.\r\n#define ADL_OD6_FANSPEED_USER_DEFINED                       0x00000100\r\n/// @}\r\n\r\n/// \\defgroup define_overdrive_EventCounter_type\r\n/// These defines the EventCounter type being reported. It is used by \\ref ADL2_OverdriveN_CountOfEvents_Get ,can be used on older OD version supported ASICs also.\r\n/// @{\r\n#define ADL_ODN_EVENTCOUNTER_THERMAL        0\r\n#define ADL_ODN_EVENTCOUNTER_VPURECOVERY    1\r\n/// @}\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADLODNControlType Enumeration\r\n///////////////////////////////////////////////////////////////////////////\r\nenum ADLODNControlType\r\n{\r\n    ODNControlType_Current = 0,\r\n    ODNControlType_Default,\r\n    ODNControlType_Auto,\r\n    ODNControlType_Manual\r\n};\r\n\r\nenum ADLODNDPMMaskType\r\n{\r\n     ADL_ODN_DPM_CLOCK               = 1 << 0,\r\n     ADL_ODN_DPM_VDDC                = 1 << 1,\r\n     ADL_ODN_DPM_MASK                = 1 << 2,\r\n};\r\n\r\n//ODN features Bits for ADLODNCapabilitiesX2\r\nenum ADLODNFeatureControl\r\n{\r\n     ADL_ODN_SCLK_DPM                = 1 << 0,\r\n     ADL_ODN_MCLK_DPM                = 1 << 1,\r\n     ADL_ODN_SCLK_VDD                = 1 << 2,\r\n     ADL_ODN_MCLK_VDD                = 1 << 3,\r\n     ADL_ODN_FAN_SPEED_MIN           = 1 << 4,\r\n     ADL_ODN_FAN_SPEED_TARGET        = 1 << 5,\r\n     ADL_ODN_ACOUSTIC_LIMIT_SCLK     = 1 << 6,\r\n     ADL_ODN_TEMPERATURE_FAN_MAX     = 1 << 7,\r\n     ADL_ODN_TEMPERATURE_SYSTEM      = 1 << 8,\r\n     ADL_ODN_POWER_LIMIT             = 1 << 9,\r\n     ADL_ODN_SCLK_AUTO_LIMIT             = 1 << 10,\r\n     ADL_ODN_MCLK_AUTO_LIMIT             = 1 << 11,\r\n     ADL_ODN_SCLK_DPM_MASK_ENABLE        = 1 << 12,\r\n     ADL_ODN_MCLK_DPM_MASK_ENABLE        = 1 << 13,\r\n     ADL_ODN_MCLK_UNDERCLOCK_ENABLE      = 1 << 14,\r\n     ADL_ODN_SCLK_DPM_THROTTLE_NOTIFY    = 1 << 15,\r\n     ADL_ODN_POWER_UTILIZATION           = 1 << 16,\r\n     ADL_ODN_PERF_TUNING_SLIDER          = 1 << 17,\r\n     ADL_ODN_REMOVE_WATTMAN_PAGE         = 1 << 31 // Internal Only\r\n};\r\n\r\n//If any new feature is added, PPLIB only needs to add ext feature ID and Item ID(Seeting ID). These IDs should match the drive defined in CWDDEPM.h\r\nenum ADLODNExtFeatureControl\r\n{\r\n\tADL_ODN_EXT_FEATURE_MEMORY_TIMING_TUNE = 1 << 0,\r\n\tADL_ODN_EXT_FEATURE_FAN_ZERO_RPM_CONTROL = 1 << 1,\r\n\tADL_ODN_EXT_FEATURE_AUTO_UV_ENGINE = 1 << 2,   //Auto under voltage\r\n\tADL_ODN_EXT_FEATURE_AUTO_OC_ENGINE = 1 << 3,   //Auto OC Enine\r\n\tADL_ODN_EXT_FEATURE_AUTO_OC_MEMORY = 1 << 4,   //Auto OC memory\r\n\tADL_ODN_EXT_FEATURE_FAN_CURVE = 1 << 5    //Fan curve\r\n\r\n};\r\n\r\n//If any new feature is added, PPLIB only needs to add ext feature ID and Item ID(Seeting ID).These IDs should match the drive defined in CWDDEPM.h\r\nenum ADLODNExtSettingId\r\n{\r\n\tADL_ODN_PARAMETER_AC_TIMING = 0,\r\n\tADL_ODN_PARAMETER_FAN_ZERO_RPM_CONTROL,\r\n\tADL_ODN_PARAMETER_AUTO_UV_ENGINE,\r\n\tADL_ODN_PARAMETER_AUTO_OC_ENGINE,\r\n\tADL_ODN_PARAMETER_AUTO_OC_MEMORY,\r\n\tADL_ODN_PARAMETER_FAN_CURVE_TEMPERATURE_1,\r\n\tADL_ODN_PARAMETER_FAN_CURVE_SPEED_1,\r\n\tADL_ODN_PARAMETER_FAN_CURVE_TEMPERATURE_2,\r\n\tADL_ODN_PARAMETER_FAN_CURVE_SPEED_2,\r\n\tADL_ODN_PARAMETER_FAN_CURVE_TEMPERATURE_3,\r\n\tADL_ODN_PARAMETER_FAN_CURVE_SPEED_3,\r\n\tADL_ODN_PARAMETER_FAN_CURVE_TEMPERATURE_4,\r\n\tADL_ODN_PARAMETER_FAN_CURVE_SPEED_4,\r\n\tADL_ODN_PARAMETER_FAN_CURVE_TEMPERATURE_5,\r\n\tADL_ODN_PARAMETER_FAN_CURVE_SPEED_5,\r\n    ADL_ODN_POWERGAUGE,\r\n\tODN_COUNT\r\n\r\n} ;\r\n\r\n//OD8 Capability features bits\r\nenum ADLOD8FeatureControl\r\n{\r\n    ADL_OD8_GFXCLK_LIMITS = 1 << 0,\r\n    ADL_OD8_GFXCLK_CURVE = 1 << 1,\r\n    ADL_OD8_UCLK_MAX = 1 << 2,\r\n    ADL_OD8_POWER_LIMIT = 1 << 3,\r\n    ADL_OD8_ACOUSTIC_LIMIT_SCLK = 1 << 4,   //FanMaximumRpm\r\n    ADL_OD8_FAN_SPEED_MIN = 1 << 5,   //FanMinimumPwm\r\n    ADL_OD8_TEMPERATURE_FAN = 1 << 6,   //FanTargetTemperature\r\n    ADL_OD8_TEMPERATURE_SYSTEM = 1 << 7,    //MaxOpTemp\r\n    ADL_OD8_MEMORY_TIMING_TUNE = 1 << 8,\r\n    ADL_OD8_FAN_ZERO_RPM_CONTROL = 1 << 9 ,\r\n\tADL_OD8_AUTO_UV_ENGINE = 1 << 10,  //Auto under voltage\r\n\tADL_OD8_AUTO_OC_ENGINE = 1 << 11,  //Auto overclock engine\r\n\tADL_OD8_AUTO_OC_MEMORY = 1 << 12,  //Auto overclock memory\r\n\tADL_OD8_FAN_CURVE = 1 << 13,   //Fan curve\r\n\tADL_OD8_WS_AUTO_FAN_ACOUSTIC_LIMIT = 1 << 14, //Workstation Manual Fan controller\r\n    ADL_OD8_GFXCLK_QUADRATIC_CURVE = 1 << 15,\r\n    ADL_OD8_OPTIMIZED_GPU_POWER_MODE = 1 << 16,\r\n    ADL_OD8_ODVOLTAGE_LIMIT = 1 << 17,\r\n    ADL_OD8_ADV_OC_LIMITS = 1 << 18,  //Advanced OC limits.\r\n    ADL_OD8_PER_ZONE_GFX_VOLTAGE_OFFSET = 1 << 19,  //Per Zone gfx voltage offset feature\r\n    ADL_OD8_AUTO_CURVE_OPTIMIZER = 1 << 20,  //Auto per zone tuning.\r\n    ADL_OD8_GFX_VOLTAGE_LIMIT = 1 << 21,  //Voltage limit slider\r\n    ADL_OD8_TDC_LIMIT = 1 << 22,  //TDC slider\r\n    ADL_OD8_FULL_CONTROL_MODE = 1 << 23,  //Full control\r\n    ADL_OD8_POWER_SAVING_FEATURE_CONTROL = 1 << 24,  //Power saving feature control\r\n    ADL_OD8_POWER_GAUGE = 1 << 25 //Power Gauge\r\n};\r\n\r\n\r\ntypedef enum ADLOD8SettingId\r\n{\r\n\tOD8_GFXCLK_FMAX = 0,\r\n\tOD8_GFXCLK_FMIN,\r\n\tOD8_GFXCLK_FREQ1,\r\n\tOD8_GFXCLK_VOLTAGE1,\r\n\tOD8_GFXCLK_FREQ2,\r\n\tOD8_GFXCLK_VOLTAGE2,\r\n\tOD8_GFXCLK_FREQ3,\r\n\tOD8_GFXCLK_VOLTAGE3,\r\n\tOD8_UCLK_FMAX,\r\n\tOD8_POWER_PERCENTAGE,\r\n\tOD8_FAN_MIN_SPEED,\r\n\tOD8_FAN_ACOUSTIC_LIMIT,\r\n\tOD8_FAN_TARGET_TEMP,\r\n\tOD8_OPERATING_TEMP_MAX,\r\n\tOD8_AC_TIMING,\r\n\tOD8_FAN_ZERORPM_CONTROL,\r\n\tOD8_AUTO_UV_ENGINE_CONTROL,\r\n\tOD8_AUTO_OC_ENGINE_CONTROL,\r\n\tOD8_AUTO_OC_MEMORY_CONTROL,\r\n\tOD8_FAN_CURVE_TEMPERATURE_1,\r\n\tOD8_FAN_CURVE_SPEED_1,\r\n\tOD8_FAN_CURVE_TEMPERATURE_2,\r\n\tOD8_FAN_CURVE_SPEED_2,\r\n\tOD8_FAN_CURVE_TEMPERATURE_3,\r\n\tOD8_FAN_CURVE_SPEED_3,\r\n\tOD8_FAN_CURVE_TEMPERATURE_4,\r\n\tOD8_FAN_CURVE_SPEED_4,\r\n\tOD8_FAN_CURVE_TEMPERATURE_5,\r\n\tOD8_FAN_CURVE_SPEED_5,\r\n\tOD8_WS_FAN_AUTO_FAN_ACOUSTIC_LIMIT,\r\n    OD8_GFXCLK_CURVE_COEFFICIENT_A, // As part of the agreement with UI team, the min/max voltage limits for the\r\n    OD8_GFXCLK_CURVE_COEFFICIENT_B, // quadratic curve graph will be stored in the min and max limits of\r\n    OD8_GFXCLK_CURVE_COEFFICIENT_C, // coefficient a, b and c. A, b and c themselves do not have limits.\r\n    OD8_GFXCLK_CURVE_VFT_FMIN,\r\n    OD8_UCLK_FMIN,\r\n    OD8_FAN_ZERO_RPM_STOP_TEMPERATURE,\r\n    OD8_OPTIMZED_POWER_MODE,\r\n    OD8_OD_VOLTAGE,// RSX - voltage offset feature\r\n    OD8_ADV_OC_LIMITS_SETTING,\r\n    OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_POINT_1,\r\n    OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_POINT_2,\r\n    OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_POINT_3,\r\n    OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_POINT_4,\r\n    OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_POINT_5,\r\n    OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_POINT_6,\r\n    OD8_AUTO_CURVE_OPTIMIZER_SETTING,\r\n    OD8_GFX_VOLTAGE_LIMIT_SETTING,\r\n    OD8_TDC_PERCENTAGE,\r\n    OD8_FULL_CONTROL_MODE_SETTING,\r\n    OD8_IDLE_POWER_SAVING_FEATURE_CONTROL,\r\n    OD8_RUNTIME_POWER_SAVING_FEATURE_CONTROL,\r\n    OD8_POWER_GAUGE,\r\n    OD8_COUNT\r\n} ADLOD8SettingId;\r\n\r\n\r\n//Define Performance Metrics Log max sensors number\r\n#define ADL_PMLOG_MAX_SENSORS  256\r\n\r\n/// \\deprecated Replaced with ADL_PMLOG_SENSORS\r\ntypedef enum ADLSensorType\r\n{\r\n    SENSOR_MAXTYPES             = 0,\r\n    PMLOG_CLK_GFXCLK            = 1,    // Current graphic clock value in MHz\r\n    PMLOG_CLK_MEMCLK            = 2,    // Current memory clock value in MHz\r\n    PMLOG_CLK_SOCCLK            = 3,\r\n    PMLOG_CLK_UVDCLK1           = 4,\r\n    PMLOG_CLK_UVDCLK2           = 5,\r\n    PMLOG_CLK_VCECLK            = 6,\r\n    PMLOG_CLK_VCNCLK            = 7,\r\n    PMLOG_TEMPERATURE_EDGE      = 8,    // Current edge of the die temperature value in C\r\n    PMLOG_TEMPERATURE_MEM       = 9,\r\n    PMLOG_TEMPERATURE_VRVDDC    = 10,\r\n    PMLOG_TEMPERATURE_VRMVDD    = 11,\r\n    PMLOG_TEMPERATURE_LIQUID    = 12,\r\n    PMLOG_TEMPERATURE_PLX       = 13,\r\n    PMLOG_FAN_RPM               = 14,   // Current fan RPM value\r\n    PMLOG_FAN_PERCENTAGE        = 15,   // Current ratio of fan RPM and max RPM\r\n    PMLOG_SOC_VOLTAGE           = 16,\r\n    PMLOG_SOC_POWER             = 17,\r\n    PMLOG_SOC_CURRENT           = 18,\r\n    PMLOG_INFO_ACTIVITY_GFX     = 19,   // Current graphic activity level in percentage\r\n    PMLOG_INFO_ACTIVITY_MEM     = 20,   // Current memory activity level in percentage\r\n    PMLOG_GFX_VOLTAGE           = 21,   // Current graphic voltage in mV\r\n    PMLOG_MEM_VOLTAGE           = 22,\r\n    PMLOG_ASIC_POWER            = 23,   // Current ASIC power draw in Watt\r\n    PMLOG_TEMPERATURE_VRSOC     = 24,\r\n    PMLOG_TEMPERATURE_VRMVDD0   = 25,\r\n    PMLOG_TEMPERATURE_VRMVDD1   = 26,\r\n    PMLOG_TEMPERATURE_HOTSPOT   = 27,   // Current center of the die temperature value in C\r\n    PMLOG_TEMPERATURE_GFX       = 28,\r\n    PMLOG_TEMPERATURE_SOC       = 29,\r\n    PMLOG_GFX_POWER             = 30,\r\n    PMLOG_GFX_CURRENT           = 31,\r\n    PMLOG_TEMPERATURE_CPU       = 32,\r\n    PMLOG_CPU_POWER             = 33,\r\n    PMLOG_CLK_CPUCLK            = 34,\r\n    PMLOG_THROTTLER_STATUS      = 35,   // A bit map of GPU throttle information. If a bit is set, the bit represented type of thorttling occurred in the last metrics sampling period\r\n    PMLOG_CLK_VCN1CLK1          = 36,\r\n    PMLOG_CLK_VCN1CLK2          = 37,\r\n    PMLOG_SMART_POWERSHIFT_CPU  = 38,\r\n    PMLOG_SMART_POWERSHIFT_DGPU = 39,\r\n    PMLOG_BUS_SPEED             = 40,   // Current PCIE bus speed running\r\n    PMLOG_BUS_LANES             = 41,   // Current PCIE bus lanes using\r\n    PMLOG_TEMPERATURE_LIQUID0   = 42,\r\n    PMLOG_TEMPERATURE_LIQUID1   = 43,\r\n    PMLOG_CLK_FCLK              = 44,\r\n    PMLOG_THROTTLER_STATUS_CPU  = 45,\r\n    PMLOG_SSPAIRED_ASICPOWER    = 46, // apuPower\r\n    PMLOG_SSTOTAL_POWERLIMIT    = 47, // Total Power limit    \r\n    PMLOG_SSAPU_POWERLIMIT      = 48, // APU Power limit\r\n    PMLOG_SSDGPU_POWERLIMIT     = 49, // DGPU Power limit\r\n    PMLOG_TEMPERATURE_HOTSPOT_GCD      = 50,\r\n    PMLOG_TEMPERATURE_HOTSPOT_MCD      = 51,\r\n    PMLOG_THROTTLER_TEMP_EDGE_PERCENTAGE        = 52,\r\n    PMLOG_THROTTLER_TEMP_HOTSPOT_PERCENTAGE     = 53,\r\n    PMLOG_THROTTLER_TEMP_HOTSPOT_GCD_PERCENTAGE = 54,\r\n    PMLOG_THROTTLER_TEMP_HOTSPOT_MCD_PERCENTAGE = 55,\r\n    PMLOG_THROTTLER_TEMP_MEM_PERCENTAGE     = 56,\r\n    PMLOG_THROTTLER_TEMP_VR_GFX_PERCENTAGE  = 57,\r\n    PMLOG_THROTTLER_TEMP_VR_MEM0_PERCENTAGE = 58,\r\n    PMLOG_THROTTLER_TEMP_VR_MEM1_PERCENTAGE = 59,\r\n    PMLOG_THROTTLER_TEMP_VR_SOC_PERCENTAGE  = 60,\r\n    PMLOG_THROTTLER_TEMP_LIQUID0_PERCENTAGE = 61,\r\n    PMLOG_THROTTLER_TEMP_LIQUID1_PERCENTAGE = 62,\r\n    PMLOG_THROTTLER_TEMP_PLX_PERCENTAGE = 63,\r\n    PMLOG_THROTTLER_TDC_GFX_PERCENTAGE  = 64,\r\n    PMLOG_THROTTLER_TDC_SOC_PERCENTAGE  = 65,\r\n    PMLOG_THROTTLER_TDC_USR_PERCENTAGE  = 66,\r\n    PMLOG_THROTTLER_PPT0_PERCENTAGE     = 67,\r\n    PMLOG_THROTTLER_PPT1_PERCENTAGE     = 68,\r\n    PMLOG_THROTTLER_PPT2_PERCENTAGE     = 69,\r\n    PMLOG_THROTTLER_PPT3_PERCENTAGE     = 70,\r\n    PMLOG_THROTTLER_FIT_PERCENTAGE           = 71,\r\n    PMLOG_THROTTLER_GFX_APCC_PLUS_PERCENTAGE = 72,\r\n    PMLOG_BOARD_POWER                        = 73,\r\n    PMLOG_MAX_SENSORS_REAL\r\n} ADLSensorType;\r\n\r\n\r\n//Throttle Status\r\ntypedef enum ADL_THROTTLE_NOTIFICATION\r\n{\r\n\tADL_PMLOG_THROTTLE_POWER = 1 << 0,\r\n\tADL_PMLOG_THROTTLE_THERMAL = 1 << 1,\r\n\tADL_PMLOG_THROTTLE_CURRENT = 1 << 2,\r\n} ADL_THROTTLE_NOTIFICATION;\r\n\r\ntypedef enum ADL_PMLOG_SENSORS\r\n{\r\n    ADL_SENSOR_MAXTYPES             = 0,\r\n    ADL_PMLOG_CLK_GFXCLK            = 1,\r\n    ADL_PMLOG_CLK_MEMCLK            = 2,\r\n    ADL_PMLOG_CLK_SOCCLK            = 3,\r\n    ADL_PMLOG_CLK_UVDCLK1           = 4,\r\n    ADL_PMLOG_CLK_UVDCLK2           = 5,\r\n    ADL_PMLOG_CLK_VCECLK            = 6,\r\n    ADL_PMLOG_CLK_VCNCLK            = 7,\r\n    ADL_PMLOG_TEMPERATURE_EDGE      = 8,\r\n    ADL_PMLOG_TEMPERATURE_MEM       = 9,\r\n    ADL_PMLOG_TEMPERATURE_VRVDDC    = 10,\r\n    ADL_PMLOG_TEMPERATURE_VRMVDD    = 11,\r\n    ADL_PMLOG_TEMPERATURE_LIQUID    = 12,\r\n    ADL_PMLOG_TEMPERATURE_PLX       = 13,\r\n    ADL_PMLOG_FAN_RPM               = 14,\r\n    ADL_PMLOG_FAN_PERCENTAGE        = 15,\r\n    ADL_PMLOG_SOC_VOLTAGE           = 16,\r\n    ADL_PMLOG_SOC_POWER             = 17,\r\n    ADL_PMLOG_SOC_CURRENT           = 18,\r\n    ADL_PMLOG_INFO_ACTIVITY_GFX     = 19,\r\n    ADL_PMLOG_INFO_ACTIVITY_MEM     = 20,\r\n    ADL_PMLOG_GFX_VOLTAGE           = 21,\r\n    ADL_PMLOG_MEM_VOLTAGE           = 22,\r\n    ADL_PMLOG_ASIC_POWER            = 23,\r\n    ADL_PMLOG_TEMPERATURE_VRSOC     = 24,\r\n    ADL_PMLOG_TEMPERATURE_VRMVDD0   = 25,\r\n    ADL_PMLOG_TEMPERATURE_VRMVDD1   = 26,\r\n    ADL_PMLOG_TEMPERATURE_HOTSPOT   = 27,\r\n    ADL_PMLOG_TEMPERATURE_GFX       = 28,\r\n    ADL_PMLOG_TEMPERATURE_SOC       = 29,\r\n    ADL_PMLOG_GFX_POWER             = 30,\r\n    ADL_PMLOG_GFX_CURRENT           = 31,\r\n    ADL_PMLOG_TEMPERATURE_CPU       = 32,\r\n    ADL_PMLOG_CPU_POWER             = 33,\r\n    ADL_PMLOG_CLK_CPUCLK            = 34,\r\n    ADL_PMLOG_THROTTLER_STATUS      = 35,   // GFX\r\n    ADL_PMLOG_CLK_VCN1CLK1          = 36,\r\n    ADL_PMLOG_CLK_VCN1CLK2          = 37,\r\n    ADL_PMLOG_SMART_POWERSHIFT_CPU  = 38,\r\n    ADL_PMLOG_SMART_POWERSHIFT_DGPU = 39,\r\n    ADL_PMLOG_BUS_SPEED             = 40,\r\n    ADL_PMLOG_BUS_LANES             = 41,\r\n    ADL_PMLOG_TEMPERATURE_LIQUID0   = 42,\r\n    ADL_PMLOG_TEMPERATURE_LIQUID1   = 43,\r\n    ADL_PMLOG_CLK_FCLK              = 44,\r\n    ADL_PMLOG_THROTTLER_STATUS_CPU  = 45,\r\n    ADL_PMLOG_SSPAIRED_ASICPOWER    = 46, // apuPower\r\n    ADL_PMLOG_SSTOTAL_POWERLIMIT    = 47, // Total Power limit\r\n    ADL_PMLOG_SSAPU_POWERLIMIT      = 48, // APU Power limit\r\n    ADL_PMLOG_SSDGPU_POWERLIMIT     = 49, // DGPU Power limit\r\n    ADL_PMLOG_TEMPERATURE_HOTSPOT_GCD      = 50,\r\n    ADL_PMLOG_TEMPERATURE_HOTSPOT_MCD      = 51,\r\n    ADL_PMLOG_THROTTLER_TEMP_EDGE_PERCENTAGE        = 52,\r\n    ADL_PMLOG_THROTTLER_TEMP_HOTSPOT_PERCENTAGE     = 53,\r\n    ADL_PMLOG_THROTTLER_TEMP_HOTSPOT_GCD_PERCENTAGE = 54,\r\n    ADL_PMLOG_THROTTLER_TEMP_HOTSPOT_MCD_PERCENTAGE = 55,\r\n    ADL_PMLOG_THROTTLER_TEMP_MEM_PERCENTAGE     = 56,\r\n    ADL_PMLOG_THROTTLER_TEMP_VR_GFX_PERCENTAGE  = 57,\r\n    ADL_PMLOG_THROTTLER_TEMP_VR_MEM0_PERCENTAGE = 58,\r\n    ADL_PMLOG_THROTTLER_TEMP_VR_MEM1_PERCENTAGE = 59,\r\n    ADL_PMLOG_THROTTLER_TEMP_VR_SOC_PERCENTAGE  = 60,\r\n    ADL_PMLOG_THROTTLER_TEMP_LIQUID0_PERCENTAGE = 61,\r\n    ADL_PMLOG_THROTTLER_TEMP_LIQUID1_PERCENTAGE = 62,\r\n    ADL_PMLOG_THROTTLER_TEMP_PLX_PERCENTAGE = 63,\r\n    ADL_PMLOG_THROTTLER_TDC_GFX_PERCENTAGE  = 64,\r\n    ADL_PMLOG_THROTTLER_TDC_SOC_PERCENTAGE  = 65,\r\n    ADL_PMLOG_THROTTLER_TDC_USR_PERCENTAGE  = 66,\r\n    ADL_PMLOG_THROTTLER_PPT0_PERCENTAGE     = 67,\r\n    ADL_PMLOG_THROTTLER_PPT1_PERCENTAGE     = 68,\r\n    ADL_PMLOG_THROTTLER_PPT2_PERCENTAGE     = 69,\r\n    ADL_PMLOG_THROTTLER_PPT3_PERCENTAGE     = 70,\r\n    ADL_PMLOG_THROTTLER_FIT_PERCENTAGE           = 71,\r\n    ADL_PMLOG_THROTTLER_GFX_APCC_PLUS_PERCENTAGE = 72,\r\n    ADL_PMLOG_BOARD_POWER                        = 73,\r\n    ADL_PMLOG_MAX_SENSORS_REAL\r\n} ADL_PMLOG_SENSORS;\r\n\r\n/// \\defgroup define_ecc_mode_states\r\n/// These defines the ECC(Error Correction Code) state. It is used by \\ref ADL_Workstation_ECC_Get,ADL_Workstation_ECC_Set\r\n/// @{\r\n/// Error Correction is OFF.\r\n#define ECC_MODE_OFF 0\r\n/// Error Correction is ECCV2.\r\n#define ECC_MODE_ON 2\r\n/// Error Correction is HBM.\r\n#define ECC_MODE_HBM 3\r\n/// @}\r\n\r\n/// \\defgroup define_board_layout_flags\r\n/// These defines are the board layout flags state which indicates what are the valid properties of \\ref ADLBoardLayoutInfo . It is used by \\ref ADL_Adapter_BoardLayout_Get\r\n/// @{\r\n/// Indicates the number of slots is valid.\r\n#define ADL_BLAYOUT_VALID_NUMBER_OF_SLOTS 0x1\r\n/// Indicates the slot sizes are valid. Size of the slot consists of the length and width.\r\n#define ADL_BLAYOUT_VALID_SLOT_SIZES 0x2\r\n/// Indicates the connector offsets are valid.\r\n#define ADL_BLAYOUT_VALID_CONNECTOR_OFFSETS 0x4\r\n/// Indicates the connector lengths is valid.\r\n#define ADL_BLAYOUT_VALID_CONNECTOR_LENGTHS 0x8\r\n/// @}\r\n\r\n/// \\defgroup define_max_constants\r\n/// These defines are the maximum value constants.\r\n/// @{\r\n/// Indicates the Maximum supported slots on board.\r\n#define ADL_ADAPTER_MAX_SLOTS 4\r\n/// Indicates the Maximum supported connectors on slot.\r\n#define ADL_ADAPTER_MAX_CONNECTORS 10\r\n/// Indicates the Maximum supported properties of connection\r\n#define ADL_MAX_CONNECTION_TYPES 32\r\n/// Indicates the Maximum relative address link count.\r\n#define ADL_MAX_RELATIVE_ADDRESS_LINK_COUNT 15\r\n/// Indicates the Maximum size of EDID data block size\r\n#define ADL_MAX_DISPLAY_EDID_DATA_SIZE 1024\r\n/// Indicates the Maximum count of Error Records.\r\n#define ADL_MAX_ERROR_RECORDS_COUNT  256\r\n/// Indicates the maximum number of power states supported\r\n#define ADL_MAX_POWER_POLICY    6\r\n/// @}\r\n\r\n/// \\defgroup define_connection_types\r\n/// These defines are the connection types constants which indicates  what are the valid connection type of given connector. It is used by \\ref ADL_Adapter_SupportedConnections_Get\r\n/// @{\r\n/// Indicates the VGA connection type is valid.\r\n#define ADL_CONNECTION_TYPE_VGA 0\r\n/// Indicates the DVI_I connection type is valid.\r\n#define ADL_CONNECTION_TYPE_DVI 1\r\n/// Indicates the DVI_SL connection type is valid.\r\n#define ADL_CONNECTION_TYPE_DVI_SL 2\r\n/// Indicates the HDMI connection type is valid.\r\n#define ADL_CONNECTION_TYPE_HDMI 3\r\n/// Indicates the DISPLAY PORT connection type is valid.\r\n#define ADL_CONNECTION_TYPE_DISPLAY_PORT 4\r\n/// Indicates the Active dongle DP->DVI(single link) connection type is valid.\r\n#define ADL_CONNECTION_TYPE_ACTIVE_DONGLE_DP_DVI_SL 5\r\n/// Indicates the Active dongle DP->DVI(double link) connection type is valid.\r\n#define ADL_CONNECTION_TYPE_ACTIVE_DONGLE_DP_DVI_DL 6\r\n/// Indicates the Active dongle DP->HDMI connection type is valid.\r\n#define ADL_CONNECTION_TYPE_ACTIVE_DONGLE_DP_HDMI 7\r\n/// Indicates the Active dongle DP->VGA connection type is valid.\r\n#define ADL_CONNECTION_TYPE_ACTIVE_DONGLE_DP_VGA 8\r\n/// Indicates the Passive dongle DP->HDMI connection type is valid.\r\n#define ADL_CONNECTION_TYPE_PASSIVE_DONGLE_DP_HDMI 9\r\n/// Indicates the Active dongle DP->VGA connection type is valid.\r\n#define ADL_CONNECTION_TYPE_PASSIVE_DONGLE_DP_DVI 10\r\n/// Indicates the MST type is valid.\r\n#define ADL_CONNECTION_TYPE_MST 11\r\n/// Indicates the active dongle, all types.\r\n#define ADL_CONNECTION_TYPE_ACTIVE_DONGLE          12\r\n/// Indicates the Virtual Connection Type.\r\n#define ADL_CONNECTION_TYPE_VIRTUAL    13\r\n/// Macros for generating bitmask from index.\r\n#define ADL_CONNECTION_BITMAST_FROM_INDEX(index) (1 << index)\r\n/// @}\r\n\r\n/// \\defgroup define_connection_properties\r\n/// These defines are the connection properties which indicates what are the valid properties of given connection type. It is used by \\ref ADL_Adapter_SupportedConnections_Get\r\n/// @{\r\n/// Indicates the property Bitrate is valid.\r\n#define ADL_CONNECTION_PROPERTY_BITRATE 0x1\r\n/// Indicates the property number of lanes is valid.\r\n#define ADL_CONNECTION_PROPERTY_NUMBER_OF_LANES 0x2\r\n/// Indicates the property 3D caps is valid.\r\n#define ADL_CONNECTION_PROPERTY_3DCAPS  0x4\r\n/// Indicates the property output bandwidth is valid.\r\n#define ADL_CONNECTION_PROPERTY_OUTPUT_BANDWIDTH 0x8\r\n/// Indicates the property colordepth is valid.\r\n#define ADL_CONNECTION_PROPERTY_COLORDEPTH  0x10\r\n/// @}\r\n\r\n/// \\defgroup define_lanecount_constants\r\n/// These defines are the Lane count constants which will be used in DP & etc.\r\n/// @{\r\n/// Indicates if lane count is unknown\r\n#define ADL_LANECOUNT_UNKNOWN 0\r\n/// Indicates if lane count is 1\r\n#define ADL_LANECOUNT_ONE 1\r\n/// Indicates if lane count is 2\r\n#define ADL_LANECOUNT_TWO 2\r\n/// Indicates if lane count is 4\r\n#define ADL_LANECOUNT_FOUR 4\r\n/// Indicates if lane count is 8\r\n#define ADL_LANECOUNT_EIGHT 8\r\n/// Indicates default value of lane count\r\n#define ADL_LANECOUNT_DEF ADL_LANECOUNT_FOUR\r\n/// @}\r\n\r\n/// \\defgroup define_linkrate_constants\r\n/// These defines are the link rate constants which will be used in DP & etc.\r\n/// @{\r\n/// Indicates if link rate is unknown\r\n#define ADL_LINK_BITRATE_UNKNOWN 0\r\n/// Indicates if link rate is 1.62Ghz\r\n#define ADL_LINK_BITRATE_1_62_GHZ 0x06\r\n/// Indicates if link rate is 2.7Ghz\r\n#define ADL_LINK_BITRATE_2_7_GHZ 0x0A\r\n/// Indicates if link rate is 5.4Ghz\r\n#define ADL_LINK_BITRATE_5_4_GHZ 0x14\r\n\r\n/// Indicates if link rate is 8.1Ghz\r\n#define ADL_LINK_BITRATE_8_1_GHZ 0x1E\r\n/// Indicates default value of link rate\r\n#define ADL_LINK_BITRATE_DEF ADL_LINK_BITRATE_2_7_GHZ\r\n/// @}\r\n\r\n/// \\defgroup define_colordepth_constants\r\n/// These defines are the color depth constants which will be used in DP & etc.\r\n/// @{\r\n#define ADL_CONNPROP_S3D_ALTERNATE_TO_FRAME_PACK            0x00000001\r\n/// @}\r\n\r\n\r\n/// \\defgroup define_colordepth_constants\r\n/// These defines are the color depth constants which will be used in DP & etc.\r\n/// @{\r\n/// Indicates if color depth is unknown\r\n#define ADL_COLORDEPTH_UNKNOWN 0\r\n/// Indicates if color depth is 666\r\n#define ADL_COLORDEPTH_666 1\r\n/// Indicates if color depth is 888\r\n#define ADL_COLORDEPTH_888 2\r\n/// Indicates if color depth is 101010\r\n#define ADL_COLORDEPTH_101010 3\r\n/// Indicates if color depth is 121212\r\n#define ADL_COLORDEPTH_121212 4\r\n/// Indicates if color depth is 141414\r\n#define ADL_COLORDEPTH_141414 5\r\n/// Indicates if color depth is 161616\r\n#define ADL_COLORDEPTH_161616 6\r\n/// Indicates default value of color depth\r\n#define ADL_COLOR_DEPTH_DEF ADL_COLORDEPTH_888\r\n/// @}\r\n\r\n\r\n/// \\defgroup define_emulation_status\r\n/// These defines are the status of emulation\r\n/// @{\r\n/// Indicates if real device is connected.\r\n#define ADL_EMUL_STATUS_REAL_DEVICE_CONNECTED 0x1\r\n/// Indicates if emulated device is presented.\r\n#define ADL_EMUL_STATUS_EMULATED_DEVICE_PRESENT 0x2\r\n/// Indicates if emulated device is used.\r\n#define ADL_EMUL_STATUS_EMULATED_DEVICE_USED  0x4\r\n/// In case when last active real/emulated device used (when persistence is enabled but no emulation enforced then persistence will use last connected/emulated device).\r\n#define ADL_EMUL_STATUS_LAST_ACTIVE_DEVICE_USED 0x8\r\n/// @}\r\n\r\n/// \\defgroup define_emulation_mode\r\n/// These defines are the modes of emulation\r\n/// @{\r\n/// Indicates if no emulation is used\r\n#define ADL_EMUL_MODE_OFF 0\r\n/// Indicates if emulation is used when display connected\r\n#define ADL_EMUL_MODE_ON_CONNECTED 1\r\n/// Indicates if emulation is used when display dis connected\r\n#define ADL_EMUL_MODE_ON_DISCONNECTED 2\r\n/// Indicates if emulation is used always\r\n#define ADL_EMUL_MODE_ALWAYS 3\r\n/// @}\r\n\r\n/// \\defgroup define_emulation_query\r\n/// These defines are the modes of emulation\r\n/// @{\r\n/// Indicates Data from real device\r\n#define ADL_QUERY_REAL_DATA 0\r\n/// Indicates Emulated data\r\n#define ADL_QUERY_EMULATED_DATA 1\r\n/// Indicates Data currently in use\r\n#define ADL_QUERY_CURRENT_DATA 2\r\n/// @}\r\n\r\n/// \\defgroup define_persistence_state\r\n/// These defines are the states of persistence\r\n/// @{\r\n/// Indicates persistence is disabled\r\n#define ADL_EDID_PERSISTANCE_DISABLED 0\r\n/// Indicates persistence is enabled\r\n#define ADL_EDID_PERSISTANCE_ENABLED 1\r\n/// @}\r\n\r\n/// \\defgroup define_connector_types Connector Type\r\n/// defines for ADLConnectorInfo.iType\r\n/// @{\r\n/// Indicates unknown Connector type\r\n#define ADL_CONNECTOR_TYPE_UNKNOWN                 0\r\n/// Indicates VGA Connector type\r\n#define ADL_CONNECTOR_TYPE_VGA                     1\r\n/// Indicates DVI-D Connector type\r\n#define ADL_CONNECTOR_TYPE_DVI_D                   2\r\n/// Indicates DVI-I Connector type\r\n#define ADL_CONNECTOR_TYPE_DVI_I                   3\r\n/// Indicates Active Dongle-NA Connector type\r\n#define ADL_CONNECTOR_TYPE_ATICVDONGLE_NA          4\r\n/// Indicates Active Dongle-JP Connector type\r\n#define ADL_CONNECTOR_TYPE_ATICVDONGLE_JP          5\r\n/// Indicates Active Dongle-NONI2C Connector type\r\n#define ADL_CONNECTOR_TYPE_ATICVDONGLE_NONI2C      6\r\n/// Indicates Active Dongle-NONI2C-D Connector type\r\n#define ADL_CONNECTOR_TYPE_ATICVDONGLE_NONI2C_D    7\r\n/// Indicates HDMI-Type A Connector type\r\n#define ADL_CONNECTOR_TYPE_HDMI_TYPE_A             8\r\n/// Indicates HDMI-Type B Connector type\r\n#define ADL_CONNECTOR_TYPE_HDMI_TYPE_B             9\r\n/// Indicates Display port Connector type\r\n#define ADL_CONNECTOR_TYPE_DISPLAYPORT             10\r\n/// Indicates EDP Connector type\r\n#define ADL_CONNECTOR_TYPE_EDP                     11\r\n/// Indicates MiniDP Connector type\r\n#define ADL_CONNECTOR_TYPE_MINI_DISPLAYPORT        12\r\n/// Indicates Virtual Connector type\r\n#define ADL_CONNECTOR_TYPE_VIRTUAL                   13\r\n/// Indicates USB type C Connector type\r\n#define ADL_CONNECTOR_TYPE_USB_TYPE_C              14\r\n/// @}\r\n\r\n/// \\defgroup define_freesync_usecase\r\n/// These defines are to specify use cases in which FreeSync should be enabled\r\n/// They are a bit mask. To specify FreeSync for more than one use case, the input value\r\n/// should be set to include multiple bits set\r\n/// @{\r\n/// Indicates FreeSync is enabled for Static Screen case\r\n#define ADL_FREESYNC_USECASE_STATIC                 0x1\r\n/// Indicates FreeSync is enabled for Video use case\r\n#define ADL_FREESYNC_USECASE_VIDEO                  0x2\r\n/// Indicates FreeSync is enabled for Gaming use case\r\n#define ADL_FREESYNC_USECASE_GAMING                 0x4\r\n/// @}\r\n\r\n/// \\defgroup define_freesync_caps\r\n/// These defines are used to retrieve FreeSync display capabilities.\r\n/// GPU support flag also indicates whether the display is\r\n/// connected to a GPU that actually supports FreeSync\r\n/// @{\r\n#define ADL_FREESYNC_CAP_SUPPORTED                      (1 << 0)\r\n#define ADL_FREESYNC_CAP_GPUSUPPORTED                   (1 << 1)\r\n#define ADL_FREESYNC_CAP_DISPLAYSUPPORTED               (1 << 2)\r\n#define ADL_FREESYNC_CAP_CURRENTMODESUPPORTED           (1 << 3)\r\n#define ADL_FREESYNC_CAP_NOCFXORCFXSUPPORTED            (1 << 4)\r\n#define ADL_FREESYNC_CAP_NOGENLOCKORGENLOCKSUPPORTED    (1 << 5)\r\n#define ADL_FREESYNC_CAP_BORDERLESSWINDOWSUPPORTED      (1 << 6)\r\n/// @}\r\n\r\n/// \\defgroup define_freesync_labelIndex\r\n/// These defines are used to retrieve which FreeSync label to use\r\n/// @{\r\n#define ADL_FREESYNC_LABEL_UNSUPPORTED            0\r\n#define ADL_FREESYNC_LABEL_FREESYNC               1\r\n#define ADL_FREESYNC_LABEL_ADAPTIVE_SYNC          2\r\n#define ADL_FREESYNC_LABEL_VRR                    3\r\n#define ADL_FREESYNC_LABEL_FREESYNC_PREMIUM       4\r\n#define ADL_FREESYNC_LABEL_FREESYNC_PREMIUM_PRO   5\r\n/// @}\r\n\r\n/// Freesync Power optimization masks\r\n/// @{\r\n#define ADL_FREESYNC_POWEROPTIMIZATION_SUPPORTED_MASK\t\t(1 << 0)\r\n#define ADL_FREESYNC_POWEROPTIMIZATION_ENABLED_MASK\t\t\t(1 << 1)\r\n#define ADL_FREESYNC_POWEROPTIMIZATION_DEFAULT_VALUE_MASK\t(1 << 2)\r\n/// @}\r\n\r\n/// \\defgroup define_MST_CommandLine_execute\r\n/// @{\r\n/// Indicates the MST command line for branch message if the bit is set. Otherwise, it is display message\r\n#define ADL_MST_COMMANDLINE_PATH_MSG                 0x1\r\n/// Indicates the MST command line to send message in broadcast way it the bit is set\r\n#define ADL_MST_COMMANDLINE_BROADCAST                  0x2\r\n\r\n/// @}\r\n\r\n\r\n/// \\defgroup define_Adapter_CloneTypes_Get\r\n/// @{\r\n/// Indicates there is crossGPU clone with non-AMD dispalys\r\n#define ADL_CROSSGPUDISPLAYCLONE_AMD_WITH_NONAMD                 0x1\r\n/// Indicates there is crossGPU clone\r\n#define ADL_CROSSGPUDISPLAYCLONE                  0x2\r\n\r\n/// @}\r\n\r\n/// \\defgroup define_D3DKMT_HANDLE\r\n/// @{\r\n/// Handle can be used to create Device Handle when using CreateDevice()\r\ntypedef unsigned int ADL_D3DKMT_HANDLE;\r\n/// @}\r\n\r\n\r\n// End Bracket for Constants and Definitions. Add new groups ABOVE this line!\r\n\r\n/// @}\r\n\r\n\r\ntypedef enum ADL_RAS_ERROR_INJECTION_MODE\r\n{\r\n\tADL_RAS_ERROR_INJECTION_MODE_SINGLE = 1,\r\n\tADL_RAS_ERROR_INJECTION_MODE_MULTIPLE = 2\r\n}ADL_RAS_ERROR_INJECTION_MODE;\r\n\r\n\r\ntypedef enum ADL_RAS_BLOCK_ID\r\n{\r\n\tADL_RAS_BLOCK_ID_UMC = 0,\r\n\tADL_RAS_BLOCK_ID_SDMA,\r\n\tADL_RAS_BLOCK_ID_GFX_HUB,\r\n\tADL_RAS_BLOCK_ID_MMHUB,\r\n\tADL_RAS_BLOCK_ID_ATHUB,\r\n\tADL_RAS_BLOCK_ID_PCIE_BIF,\r\n\tADL_RAS_BLOCK_ID_HDP,\r\n\tADL_RAS_BLOCK_ID_XGMI_WAFL,\r\n\tADL_RAS_BLOCK_ID_DF,\r\n\tADL_RAS_BLOCK_ID_SMN,\r\n\tADL_RAS_BLOCK_ID_SEM,\r\n\tADL_RAS_BLOCK_ID_MP0,\r\n\tADL_RAS_BLOCK_ID_MP1,\r\n\tADL_RAS_BLOCK_ID_FUSE\r\n}ADL_RAS_BLOCK_ID;\r\n\r\ntypedef enum ADL_MEM_SUB_BLOCK_ID\r\n{\r\n\tADL_RAS__UMC_HBM = 0,\r\n\tADL_RAS__UMC_SRAM = 1\r\n}ADL_MEM_SUB_BLOCK_ID;\r\n\r\ntypedef enum  _ADL_RAS_ERROR_TYPE\r\n{\r\n\tADL_RAS_ERROR__NONE = 0,\r\n\tADL_RAS_ERROR__PARITY = 1,\r\n\tADL_RAS_ERROR__SINGLE_CORRECTABLE = 2,\r\n\tADL_RAS_ERROR__PARITY_SINGLE_CORRECTABLE = 3,\r\n\tADL_RAS_ERROR__MULTI_UNCORRECTABLE = 4,\r\n\tADL_RAS_ERROR__PARITY_MULTI_UNCORRECTABLE = 5,\r\n\tADL_RAS_ERROR__SINGLE_CORRECTABLE_MULTI_UNCORRECTABLE = 6,\r\n\tADL_RAS_ERROR__PARITY_SINGLE_CORRECTABLE_MULTI_UNCORRECTABLE = 7,\r\n\tADL_RAS_ERROR__POISON = 8,\r\n\tADL_RAS_ERROR__PARITY_POISON = 9,\r\n\tADL_RAS_ERROR__SINGLE_CORRECTABLE_POISON = 10,\r\n\tADL_RAS_ERROR__PARITY_SINGLE_CORRECTABLE_POISON = 11,\r\n\tADL_RAS_ERROR__MULTI_UNCORRECTABLE_POISON = 12,\r\n\tADL_RAS_ERROR__PARITY_MULTI_UNCORRECTABLE_POISON = 13,\r\n\tADL_RAS_ERROR__SINGLE_CORRECTABLE_MULTI_UNCORRECTABLE_POISON = 14,\r\n\tADL_RAS_ERROR__PARITY_SINGLE_CORRECTABLE_MULTI_UNCORRECTABLE_POISON = 15\r\n}ADL_RAS_ERROR_TYPE;\r\n\r\ntypedef enum ADL_RAS_INJECTION_METHOD\r\n{\r\n\tADL_RAS_ERROR__UMC_METH_COHERENT = 0,\r\n\tADL_RAS_ERROR__UMC_METH_SINGLE_SHOT = 1,\r\n\tADL_RAS_ERROR__UMC_METH_PERSISTENT = 2,\r\n\tADL_RAS_ERROR__UMC_METH_PERSISTENT_DISABLE = 3\r\n}ADL_RAS_INJECTION_METHOD;\r\n\r\n// Driver event types\r\ntypedef enum ADL_DRIVER_EVENT_TYPE\r\n{\r\n\tADL_EVENT_ID_AUTO_FEATURE_COMPLETED = 30,\r\n\tADL_EVENT_ID_FEATURE_AVAILABILITY = 31,\r\n\r\n} ADL_DRIVER_EVENT_TYPE;\r\n\r\n\r\n//UIFeature Ids\r\ntypedef enum ADL_UIFEATURES_GROUP\r\n{\r\n\tADL_UIFEATURES_GROUP_DVR = 0,\r\n\tADL_UIFEATURES_GROUP_TURBOSYNC = 1,\r\n\tADL_UIFEATURES_GROUP_FRAMEMETRICSMONITOR = 2,\r\n\tADL_UIFEATURES_GROUP_FRTC = 3,\r\n\tADL_UIFEATURES_GROUP_XVISION = 4,\r\n\tADL_UIFEATURES_GROUP_BLOCKCHAIN = 5,\r\n\tADL_UIFEATURES_GROUP_GAMEINTELLIGENCE = 6,\r\n\tADL_UIFEATURES_GROUP_CHILL = 7,\r\n\tADL_UIFEATURES_GROUP_DELAG = 8,\r\n\tADL_UIFEATURES_GROUP_BOOST = 9,\r\n\tADL_UIFEATURES_GROUP_USU = 10,\r\n\tADL_UIFEATURES_GROUP_XGMI = 11,\r\n\tADL_UIFEATURES_GROUP_PROVSR = 12,\r\n    ADL_UIFEATURES_GROUP_SMA = 13,\r\n    ADL_UIFEATURES_GROUP_CAMERA = 14,\r\n    ADL_UIFEATURES_GROUP_FRTCPRO = 15\r\n} ADL_UIFEATURES_GROUP;\r\n\r\n\r\n\r\n/// Maximum brightness supported by Radeon LED interface\r\n#define ADL_RADEON_LED_MAX_BRIGHTNESS\t\t2\r\n\r\n/// Maximum speed supported by Radeon LED interface\r\n#define ADL_RADEON_LED_MAX_SPEED\t        4\r\n\r\n/// Maximum RGB supported by Radeon LED interface\r\n#define ADL_RADEON_LED_MAX_RGB\t            255\r\n\r\n/// Maximum MORSE code supported string\r\n#define ADL_RADEON_LED_MAX_MORSE_CODE       260\r\n\r\n/// Maximum LED ROW ON GRID\r\n#define ADL_RADEON_LED_MAX_LED_ROW_ON_GRID      7\r\n\r\n/// Maximum LED COLUMN ON GRID\r\n#define ADL_RADEON_LED_MAX_LED_COLUMN_ON_GRID   24\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief\r\n///\r\n///\r\n///\r\n///\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef enum ADL_RADEON_USB_LED_BAR_CONTROLS\r\n{\r\n   RadeonLEDBarControl_OFF = 0,\r\n   RadeonLEDBarControl_Static,\r\n   RadeonLEDBarControl_Rainbow,\r\n   RadeonLEDBarControl_Swirl,\r\n   RadeonLEDBarControl_Chase,\r\n   RadeonLEDBarControl_Bounce,\r\n   RadeonLEDBarControl_MorseCode,\r\n   RadeonLEDBarControl_ColorCycle,\r\n   RadeonLEDBarControl_Breathing,\r\n   RadeonLEDBarControl_CustomPattern,\r\n   RadeonLEDBarControl_MAX\r\n}ADL_RADEON_USB_LED_BAR_CONTROLS;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief\r\n///\r\n///\r\n///\r\n///\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef unsigned int RadeonLEDBARSupportedControl;\r\n\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief\r\n///\r\n///\r\n///\r\n///\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef enum ADL_RADEON_USB_LED_CONTROL_CONFIGS\r\n{\r\n   RadeonLEDPattern_Speed = 0,\r\n   RadeonLEDPattern_Brightness,\r\n   RadeonLEDPattern_Direction,\r\n   RadeonLEDPattern_Color,\r\n   RadeonLEDPattern_MAX\r\n}ADL_RADEON_USB_LED_CONTROL_CONFIGS;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief\r\n///\r\n///\r\n///\r\n///\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef unsigned int RadeonLEDBARSupportedConfig;\r\n\r\n//User blob feature settings\r\ntypedef enum ADL_USER_SETTINGS\r\n{\r\n    ADL_USER_SETTINGS_ENHANCEDSYNC = 1 << 0,          //notify Enhanced Sync settings change\r\n    ADL_USER_SETTINGS_CHILL_PROFILE = 1 << 1,          //notify Chill settings change\r\n    ADL_USER_SETTINGS_DELAG_PROFILE = 1 << 2,          //notify Delag settings change\r\n    ADL_USER_SETTINGS_BOOST_PROFILE = 1 << 3,\t\t\t//notify Boost settings change\r\n    ADL_USER_SETTINGS_USU_PROFILE = 1 << 4,  \t\t//notify USU settings change\r\n    ADL_USER_SETTINGS_CVDC_PROFILE = 1 << 5,\t\t\t//notify Color Vision Deficiency Corretion settings change\r\n    ADL_USER_SETTINGS_SCE_PROFILE = 1 << 6,\r\n    ADL_USER_SETTINGS_PROVSR = 1 << 7\r\n   } ADL_USER_SETTINGS;\r\n\r\n#define ADL_REG_DEVICE_FUNCTION_1            0x00000001\r\n#endif /* ADL_DEFINES_H_ */\r\n\r\n\r\n"
  },
  {
    "path": "src/3rdparty/display-library/adl_sdk.h",
    "content": "//\r\n// Copyright (c) 2016 - 2022 Advanced Micro Devices, Inc. All rights reserved.\r\n//\r\n// MIT LICENSE:\r\n// Permission is hereby granted, free of charge, to any person obtaining a copy\r\n// of this software and associated documentation files (the \"Software\"), to deal\r\n// in the Software without restriction, including without limitation the rights\r\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n// copies of the Software, and to permit persons to whom the Software is\r\n// furnished to do so, subject to the following conditions:\r\n//\r\n// The above copyright notice and this permission notice shall be included in\r\n// all copies or substantial portions of the Software.\r\n//\r\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n// SOFTWARE.\r\n\r\n/// \\file adl_sdk.h\r\n/// \\brief Contains the definition of the Memory Allocation Callback.\\n <b>Included in ADL SDK</b>\r\n///\r\n/// \\n\\n\r\n/// This file contains the definition of the Memory Allocation Callback.\\n\r\n/// It also includes definitions of the respective structures and constants.\\n\r\n/// <b> This is the only header file to be included in a C/C++ project using ADL </b>\r\n\r\n#ifndef ADL_SDK_H_\r\n#define ADL_SDK_H_\r\n\r\n#include \"adl_structures.h\"\r\n\r\n#if defined (LINUX)\r\n#define __stdcall\r\n#endif /* (LINUX) */\r\n\r\n/// Memory Allocation Call back \r\ntypedef void* ( __stdcall *ADL_MAIN_MALLOC_CALLBACK )( int );\r\n\r\n#define ADL_SDK_MAJOR_VERSION 17\r\n#define ADL_SDK_MINOR_VERSION 1\r\n\r\n#endif /* ADL_SDK_H_ */\r\n"
  },
  {
    "path": "src/3rdparty/display-library/adl_structures.h",
    "content": "//\r\n// Copyright (c) 2016 - 2022 Advanced Micro Devices, Inc. All rights reserved.\r\n//\r\n// MIT LICENSE:\r\n// Permission is hereby granted, free of charge, to any person obtaining a copy\r\n// of this software and associated documentation files (the \"Software\"), to deal\r\n// in the Software without restriction, including without limitation the rights\r\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n// copies of the Software, and to permit persons to whom the Software is\r\n// furnished to do so, subject to the following conditions:\r\n//\r\n// The above copyright notice and this permission notice shall be included in\r\n// all copies or substantial portions of the Software.\r\n//\r\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n// SOFTWARE.\r\n\r\n/// \\file adl_structures.h\r\n///\\brief This file contains the structure declarations that are used by the public ADL interfaces for \\ALL platforms.\\n <b>Included in ADL SDK</b>\r\n///\r\n/// All data structures used in AMD Display Library (ADL) public interfaces should be defined in this header file.\r\n///\r\n\r\n#ifndef ADL_STRUCTURES_H_\r\n#define ADL_STRUCTURES_H_\r\n\r\n#include \"adl_defines.h\"\r\n#include <stdbool.h>\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about the graphics adapter.\r\n///\r\n/// This structure is used to store various information about the graphics adapter.  This\r\n/// information can be returned to the user. Alternatively, it can be used to access various driver calls to set\r\n/// or fetch various settings upon the user's request.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct AdapterInfo\r\n{\r\n/// \\ALL_STRUCT_MEM\r\n\r\n/// Size of the structure.\r\n    int iSize;\r\n/// The ADL index handle. One GPU may be associated with one or two index handles\r\n    int iAdapterIndex;\r\n/// The unique device ID associated with this adapter.\r\n    char strUDID[ADL_MAX_PATH];\r\n/// The BUS number associated with this adapter.\r\n    int iBusNumber;\r\n/// The driver number associated with this adapter.\r\n    int iDeviceNumber;\r\n/// The function number.\r\n    int iFunctionNumber;\r\n/// The vendor ID associated with this adapter.\r\n    int iVendorID;\r\n/// Adapter name.\r\n    char strAdapterName[ADL_MAX_PATH];\r\n/// Display name. For example, \"\\\\\\\\Display0\" for Windows or \":0:0\" for Linux.\r\n    char strDisplayName[ADL_MAX_PATH];\r\n/// Present or not; 1 if present and 0 if not present.It the logical adapter is present, the display name such as \\\\\\\\.\\\\Display1 can be found from OS\r\n    int iPresent;\r\n\r\n#if defined (_WIN32) || defined (_WIN64)\r\n/// \\WIN_STRUCT_MEM\r\n\r\n/// Exist or not; 1 is exist and 0 is not present.\r\n    int iExist;\r\n/// Driver registry path.\r\n    char strDriverPath[ADL_MAX_PATH];\r\n/// Driver registry path Ext for.\r\n    char strDriverPathExt[ADL_MAX_PATH];\r\n/// PNP string from Windows.\r\n    char strPNPString[ADL_MAX_PATH];\r\n/// It is generated from EnumDisplayDevices.\r\n    int iOSDisplayIndex;\r\n\r\n#endif /* (_WIN32) || (_WIN64) */\r\n\r\n#if defined (LINUX)\r\n/// \\LNX_STRUCT_MEM\r\n\r\n/// Internal X screen number from GPUMapInfo (DEPRICATED use XScreenInfo)\r\n    int iXScreenNum;\r\n/// Internal driver index from GPUMapInfo\r\n    int iDrvIndex;\r\n/// \\deprecated Internal x config file screen identifier name. Use XScreenInfo instead.\r\n    char strXScreenConfigName[ADL_MAX_PATH];\r\n\r\n#endif /* (LINUX) */\r\n} AdapterInfo, *LPAdapterInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about the Linux X screen information.\r\n///\r\n/// This structure is used to store the current screen number and xorg.conf ID name assoicated with an adapter index.\r\n/// This structure is updated during ADL_Main_Control_Refresh or ADL_ScreenInfo_Update.\r\n/// Note:  This structure should be used in place of iXScreenNum and strXScreenConfigName in AdapterInfo as they will be\r\n/// deprecated.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n#if defined (LINUX)\r\ntypedef struct XScreenInfo\r\n{\r\n/// Internal X screen number from GPUMapInfo.\r\n    int iXScreenNum;\r\n/// Internal x config file screen identifier name.\r\n    char strXScreenConfigName[ADL_MAX_PATH];\r\n} XScreenInfo, *LPXScreenInfo;\r\n#endif /* (LINUX) */\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about an controller mode\r\n///\r\n/// This structure is used to store information of an controller mode\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLAdapterCaps\r\n{\r\n\t/// AdapterID for this adapter\r\n\tint iAdapterID;\r\n\t/// Number of controllers for this adapter\r\n\tint iNumControllers;\r\n\t/// Number of displays for this adapter\r\n\tint iNumDisplays;\r\n\t/// Number of overlays for this adapter\r\n\tint iNumOverlays;\r\n\t/// Number of GLSyncConnectors\r\n\tint iNumOfGLSyncConnectors;\r\n\t/// The bit mask identifies the adapter caps\r\n\tint iCapsMask;\r\n\t/// The bit identifies the adapter caps \\ref define_adapter_caps\r\n\tint iCapsValue;\r\n}ADLAdapterCaps;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing additional information about the ASIC memory\r\n///\r\n/// This structure is used to store additional information about the ASIC memory.  This\r\n/// information can be returned to the user.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLMemoryInfo2\r\n{\r\n\t/// Memory size in bytes.\r\n\tlong long iMemorySize;\r\n\t/// Memory type in string.\r\n\tchar strMemoryType[ADL_MAX_PATH];\r\n\t/// Highest default performance level Memory bandwidth in Mbytes/s\r\n\tlong long iMemoryBandwidth;\r\n\t/// HyperMemory size in bytes.\r\n\tlong long iHyperMemorySize;\r\n\r\n\t/// Invisible Memory size in bytes.\r\n\tlong long iInvisibleMemorySize;\r\n\t/// Visible Memory size in bytes.\r\n\tlong long iVisibleMemorySize;\r\n} ADLMemoryInfo2, *LPADLMemoryInfo2;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing additional information about the ASIC memory\r\n///\r\n/// This structure is used to store additional information about the ASIC memory.  This\r\n/// information can be returned to the user.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLMemoryInfo3\r\n{\r\n    /// Memory size in bytes.\r\n    long long iMemorySize;\r\n    /// Memory type in string.\r\n    char strMemoryType[ADL_MAX_PATH];\r\n    /// Highest default performance level Memory bandwidth in Mbytes/s\r\n    long long iMemoryBandwidth;\r\n    /// HyperMemory size in bytes.\r\n    long long iHyperMemorySize;\r\n\r\n    /// Invisible Memory size in bytes.\r\n    long long iInvisibleMemorySize;\r\n    /// Visible Memory size in bytes.\r\n    long long iVisibleMemorySize;\r\n    /// Vram vendor ID\r\n    long long iVramVendorRevId;\r\n} ADLMemoryInfo3, *LPADLMemoryInfo3;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing additional information about the ASIC memory\r\n///\r\n/// This structure is used to store additional information about the ASIC memory.  This\r\n/// information can be returned to the user.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLMemoryInfoX4\r\n{\r\n    /// Memory size in bytes.\r\n    long long iMemorySize;\r\n    /// Memory type in string.\r\n    char strMemoryType[ADL_MAX_PATH];\r\n    /// Highest default performance level Memory bandwidth in Mbytes/s\r\n    long long iMemoryBandwidth;\r\n    /// HyperMemory size in bytes.\r\n    long long iHyperMemorySize;\r\n\r\n    /// Invisible Memory size in bytes.\r\n    long long iInvisibleMemorySize;\r\n    /// Visible Memory size in bytes.\r\n    long long iVisibleMemorySize;\r\n    /// Vram vendor ID\r\n    long long iVramVendorRevId;\r\n    /// Memory Bandiwidth that is calculated and finalized on the driver side, grab and go.\r\n    long long iMemoryBandwidthX2;\r\n    /// Memory Bit Rate that is calculated and finalized on the driver side, grab and go.\r\n    long long iMemoryBitRateX2;\r\n\r\n} ADLMemoryInfoX4, *LPADLMemoryInfoX4;\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// ADLvRamVendor Enumeration\r\n///////////////////////////////////////////////////////////////////////////\r\nenum ADLvRamVendors\r\n{\r\n    ADLvRamVendor_Unsupported = 0x0,\r\n    ADLvRamVendor_SAMSUNG,\r\n    ADLvRamVendor_INFINEON,\r\n    ADLvRamVendor_ELPIDA,\r\n    ADLvRamVendor_ETRON,\r\n    ADLvRamVendor_NANYA,\r\n    ADLvRamVendor_HYNIX,\r\n    ADLvRamVendor_MOSEL,\r\n    ADLvRamVendor_WINBOND,\r\n    ADLvRamVendor_ESMT,\r\n    ADLvRamVendor_MICRON = 0xF,\r\n    ADLvRamVendor_Undefined\r\n};\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about components of ASIC GCN architecture\r\n///\r\n///  Elements of GCN info are compute units, number of Tex (Texture filtering units)  , number of ROPs (render back-ends).\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n\r\ntypedef struct ADLGcnInfo\r\n{\r\n\tint CuCount; //Number of compute units on the ASIC.\r\n\tint TexCount; //Number of texture mapping units.\r\n\tint RopCount; //Number of Render backend Units.\r\n\tint ASICFamilyId; //Such SI, VI. See /inc/asic_reg/atiid.h for family ids\r\n\tint ASICRevisionId; //Such as Ellesmere, Fiji.   For example - VI family revision ids are stored in /inc/asic_reg/vi_id.h\r\n}ADLGcnInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information related virtual segment config information.\r\n///\r\n/// This structure is used to store information related virtual segment config\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLVirtualSegmentSettingsOutput\r\n{\r\n\tint                      virtualSegmentSupported;   // 1 - subsequent values are valid\r\n\tint                      virtualSegmentDefault;     //virtual segment default, 1: enable, 0: disable\r\n\tint                      virtualSegmentCurrent;     //virtual segment current, 1: enable, 0: disable\r\n\tint                      iMinSizeInMB;              //minimum value\r\n\tint                      iMaxSizeInMB;              //maximum value\r\n\tint                      icurrentSizeInMB;          //last configured otherwise same as factory default\r\n\tint                      idefaultSizeInMB;          //factory default\r\n\tint                      iMask;                     //fileds for extension in the future\r\n\tint                      iValue;                    //fileds for extension in the future\r\n} ADLVirtualSegmentSettingsOutput;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n/// \\brief Structure containing information about the Chipset.\r\n///\r\n/// This structure is used to store various information about the Chipset.  This\r\n/// information can be returned to the user.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLChipSetInfo\r\n{\r\n\tint iBusType; \t\t///< Bus type.\r\n\tint iBusSpeedType;\t///Maximum Bus Speed of the current platform\r\n\tint iMaxPCIELaneWidth; \t///< Number of PCIE lanes.\r\n\tint iCurrentPCIELaneWidth;  ///< Current PCIE Lane Width\r\n\tint iSupportedAGPSpeeds;    ///< Bit mask or AGP transfer speed.\r\n\tint iCurrentAGPSpeed;       ///< Current AGP speed\r\n} ADLChipSetInfo, *LPADLChipSetInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about the ASIC memory.\r\n///\r\n/// This structure is used to store various information about the ASIC memory.  This\r\n/// information can be returned to the user.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLMemoryInfo\r\n{\r\n/// Memory size in bytes.\r\n    long long iMemorySize;\r\n/// Memory type in string.\r\n    char strMemoryType[ADL_MAX_PATH];\r\n/// Memory bandwidth in Mbytes/s.\r\n    long long iMemoryBandwidth;\r\n} ADLMemoryInfo, *LPADLMemoryInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about memory required by type\r\n///\r\n/// This structure is returned by ADL_Adapter_ConfigMemory_Get, which given a desktop and display configuration\r\n/// will return the Memory used.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLMemoryRequired\r\n{\r\n    long long iMemoryReq;        /// Memory in bytes required\r\n    int iType;                    /// Type of Memory \\ref define_adl_validmemoryrequiredfields\r\n    int iDisplayFeatureValue;   /// Display features \\ref define_adl_visiblememoryfeatures that are using this type of memory\r\n} ADLMemoryRequired, *LPADLMemoryRequired;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about the features associated with a display\r\n///\r\n/// This structure is a parameter to ADL_Adapter_ConfigMemory_Get, which given a desktop and display configuration\r\n/// will return the Memory used.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLMemoryDisplayFeatures\r\n{\r\n    int iDisplayIndex;            /// ADL Display index\r\n    int iDisplayFeatureValue;    /// features that the display is using \\ref define_adl_visiblememoryfeatures\r\n} ADLMemoryDisplayFeatures, *LPADLMemoryDisplayFeatures;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing DDC information.\r\n///\r\n/// This structure is used to store various DDC information that can be returned to the user.\r\n/// Note that all fields of type int are actually defined as unsigned int types within the driver.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLDDCInfo\r\n{\r\n/// Size of the structure\r\n    int  ulSize;\r\n/// Indicates whether the attached display supports DDC. If this field is zero on return, no other DDC information fields will be used.\r\n    int  ulSupportsDDC;\r\n/// Returns the manufacturer ID of the display device. Should be zeroed if this information is not available.\r\n    int  ulManufacturerID;\r\n/// Returns the product ID of the display device. Should be zeroed if this information is not available.\r\n    int  ulProductID;\r\n/// Returns the name of the display device. Should be zeroed if this information is not available.\r\n    char cDisplayName[ADL_MAX_DISPLAY_NAME];\r\n/// Returns the maximum Horizontal supported resolution. Should be zeroed if this information is not available.\r\n    int  ulMaxHResolution;\r\n/// Returns the maximum Vertical supported resolution. Should be zeroed if this information is not available.\r\n    int  ulMaxVResolution;\r\n/// Returns the maximum supported refresh rate. Should be zeroed if this information is not available.\r\n    int  ulMaxRefresh;\r\n/// Returns the display device preferred timing mode's horizontal resolution.\r\n    int  ulPTMCx;\r\n/// Returns the display device preferred timing mode's vertical resolution.\r\n    int  ulPTMCy;\r\n/// Returns the display device preferred timing mode's refresh rate.\r\n    int  ulPTMRefreshRate;\r\n/// Return EDID flags.\r\n    int  ulDDCInfoFlag;\r\n} ADLDDCInfo, *LPADLDDCInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing DDC information.\r\n///\r\n/// This structure is used to store various DDC information that can be returned to the user.\r\n/// Note that all fields of type int are actually defined as unsigned int types within the driver.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLDDCInfo2\r\n{\r\n/// Size of the structure\r\n    int  ulSize;\r\n/// Indicates whether the attached display supports DDC. If this field is zero on return, no other DDC\r\n/// information fields will be used.\r\n    int  ulSupportsDDC;\r\n/// Returns the manufacturer ID of the display device. Should be zeroed if this information is not available.\r\n    int  ulManufacturerID;\r\n/// Returns the product ID of the display device. Should be zeroed if this information is not available.\r\n    int  ulProductID;\r\n/// Returns the name of the display device. Should be zeroed if this information is not available.\r\n    char cDisplayName[ADL_MAX_DISPLAY_NAME];\r\n/// Returns the maximum Horizontal supported resolution. Should be zeroed if this information is not available.\r\n    int  ulMaxHResolution;\r\n/// Returns the maximum Vertical supported resolution. Should be zeroed if this information is not available.\r\n    int  ulMaxVResolution;\r\n/// Returns the maximum supported refresh rate. Should be zeroed if this information is not available.\r\n    int  ulMaxRefresh;\r\n/// Returns the display device preferred timing mode's horizontal resolution.\r\n    int  ulPTMCx;\r\n/// Returns the display device preferred timing mode's vertical resolution.\r\n    int  ulPTMCy;\r\n/// Returns the display device preferred timing mode's refresh rate.\r\n    int  ulPTMRefreshRate;\r\n/// Return EDID flags.\r\n    int  ulDDCInfoFlag;\r\n/// Returns 1 if the display supported packed pixel, 0 otherwise\r\n    int bPackedPixelSupported;\r\n/// Returns the Pixel formats the display supports \\ref define_ddcinfo_pixelformats\r\n    int iPanelPixelFormat;\r\n/// Return EDID serial ID.\r\n    int  ulSerialID;\r\n/// Return minimum monitor luminance data\r\n    int ulMinLuminanceData;\r\n/// Return average monitor luminance data\r\n    int ulAvgLuminanceData;\r\n/// Return maximum monitor luminance data\r\n    int ulMaxLuminanceData;\r\n\r\n/// Bit vector of supported transfer functions \\ref define_source_content_TF\r\n    int iSupportedTransferFunction;\r\n\r\n/// Bit vector of supported color spaces \\ref define_source_content_CS\r\n    int iSupportedColorSpace;\r\n\r\n/// Display Red Chromaticity X coordinate multiplied by 10000\r\n    int iNativeDisplayChromaticityRedX;\r\n/// Display Red Chromaticity Y coordinate multiplied by 10000\r\n    int iNativeDisplayChromaticityRedY;\r\n/// Display Green Chromaticity X coordinate multiplied by 10000\r\n    int iNativeDisplayChromaticityGreenX;\r\n/// Display Green Chromaticity Y coordinate multiplied by 10000\r\n    int iNativeDisplayChromaticityGreenY;\r\n/// Display Blue Chromaticity X coordinate multiplied by 10000\r\n    int iNativeDisplayChromaticityBlueX;\r\n/// Display Blue Chromaticity Y coordinate multiplied by 10000\r\n    int iNativeDisplayChromaticityBlueY;\r\n/// Display White Point X coordinate multiplied by 10000\r\n    int iNativeDisplayChromaticityWhitePointX;\r\n/// Display White Point Y coordinate multiplied by 10000\r\n    int iNativeDisplayChromaticityWhitePointY;\r\n/// Display diffuse screen reflectance 0-1 (100%) in units of 0.01\r\n    int iDiffuseScreenReflectance;\r\n/// Display specular screen reflectance 0-1 (100%) in units of 0.01\r\n    int iSpecularScreenReflectance;\r\n/// Bit vector of supported color spaces \\ref define_HDR_support\r\n    int iSupportedHDR;\r\n/// Bit vector for freesync flags\r\n    int iFreesyncFlags;\r\n\r\n/// Return minimum monitor luminance without dimming data\r\n    int ulMinLuminanceNoDimmingData;\r\n\r\n    int ulMaxBacklightMaxLuminanceData;\r\n    int ulMinBacklightMaxLuminanceData;\r\n    int ulMaxBacklightMinLuminanceData;\r\n    int ulMinBacklightMinLuminanceData;\r\n\r\n    // Reserved for future use\r\n    int iReserved[4];\r\n} ADLDDCInfo2, *LPADLDDCInfo2;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information controller Gamma settings.\r\n///\r\n/// This structure is used to store the red, green and blue color channel information for the.\r\n/// controller gamma setting. This information is returned by ADL, and it can also be used to\r\n/// set the controller gamma setting.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLGamma\r\n{\r\n/// Red color channel gamma value.\r\n    float fRed;\r\n/// Green color channel gamma value.\r\n    float fGreen;\r\n/// Blue color channel gamma value.\r\n    float fBlue;\r\n} ADLGamma, *LPADLGamma;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about component video custom modes.\r\n///\r\n/// This structure is used to store the component video custom mode.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLCustomMode\r\n{\r\n/// Custom mode flags.  They are returned by the ADL driver.\r\n    int iFlags;\r\n/// Custom mode width.\r\n    int iModeWidth;\r\n/// Custom mode height.\r\n    int iModeHeight;\r\n/// Custom mode base width.\r\n    int iBaseModeWidth;\r\n/// Custom mode base height.\r\n    int iBaseModeHeight;\r\n/// Custom mode refresh rate.\r\n    int iRefreshRate;\r\n} ADLCustomMode, *LPADLCustomMode;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing Clock information for OD5 calls.\r\n///\r\n/// This structure is used to retrieve clock information for OD5 calls.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLGetClocksOUT\r\n{\r\n    long ulHighCoreClock;\r\n    long ulHighMemoryClock;\r\n    long ulHighVddc;\r\n    long ulCoreMin;\r\n    long ulCoreMax;\r\n    long ulMemoryMin;\r\n    long ulMemoryMax;\r\n    long ulActivityPercent;\r\n    long ulCurrentCoreClock;\r\n    long ulCurrentMemoryClock;\r\n    long ulReserved;\r\n} ADLGetClocksOUT;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing HDTV information for display calls.\r\n///\r\n/// This structure is used to retrieve HDTV information information for display calls.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLDisplayConfig\r\n{\r\n/// Size of the structure\r\n  long ulSize;\r\n/// HDTV connector type.\r\n  long ulConnectorType;\r\n/// HDTV capabilities.\r\n  long ulDeviceData;\r\n/// Overridden HDTV capabilities.\r\n  long ulOverridedDeviceData;\r\n/// Reserved field\r\n  long ulReserved;\r\n} ADLDisplayConfig;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about the display device.\r\n///\r\n/// This structure is used to store display device information\r\n/// such as display index, type, name, connection status, mapped adapter and controller indexes,\r\n/// whether or not multiple VPUs are supported, local display connections or not (through Lasso), etc.\r\n/// This information can be returned to the user. Alternatively, it can be used to access various driver calls to set\r\n/// or fetch various display device related settings upon the user's request.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLDisplayID\r\n{\r\n/// The logical display index belonging to this adapter.\r\n    int iDisplayLogicalIndex;\r\n\r\n///\\brief The physical display index.\r\n/// For example, display index 2 from adapter 2 can be used by current adapter 1.\\n\r\n/// So current adapter may enumerate this adapter as logical display 7 but the physical display\r\n/// index is still 2.\r\n    int iDisplayPhysicalIndex;\r\n\r\n/// The persistent logical adapter index for the display.\r\n    int iDisplayLogicalAdapterIndex;\r\n\r\n///\\brief The persistent physical adapter index for the display.\r\n/// It can be the current adapter or a non-local adapter. \\n\r\n/// If this adapter index is different than the current adapter,\r\n/// the Display Non Local flag is set inside DisplayInfoValue.\r\n    int iDisplayPhysicalAdapterIndex;\r\n} ADLDisplayID, *LPADLDisplayID;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about the display device.\r\n///\r\n/// This structure is used to store various information about the display device.  This\r\n/// information can be returned to the user, or used to access various driver calls to set\r\n/// or fetch various display-device-related settings upon the user's request\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLDisplayInfo\r\n{\r\n/// The DisplayID structure\r\n    ADLDisplayID displayID;\r\n\r\n///\\deprecated The controller index to which the display is mapped.\\n Will not be used in the future\\n\r\n    int  iDisplayControllerIndex;\r\n\r\n/// The display's EDID name.\r\n    char strDisplayName[ADL_MAX_PATH];\r\n\r\n/// The display's manufacturer name.\r\n    char strDisplayManufacturerName[ADL_MAX_PATH];\r\n\r\n/// The Display type. For example: CRT, TV, CV, DFP.\r\n    int  iDisplayType;\r\n\r\n/// The display output type. For example: HDMI, SVIDEO, COMPONMNET VIDEO.\r\n    int  iDisplayOutputType;\r\n\r\n/// The connector type for the device.\r\n    int  iDisplayConnector;\r\n\r\n///\\brief The bit mask identifies the number of bits ADLDisplayInfo is currently using. \\n\r\n/// It will be the sum all the bit definitions in ADL_DISPLAY_DISPLAYINFO_xxx.\r\n    int  iDisplayInfoMask;\r\n\r\n/// The bit mask identifies the display status. \\ref define_displayinfomask\r\n    int  iDisplayInfoValue;\r\n} ADLDisplayInfo, *LPADLDisplayInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about the display port MST device.\r\n///\r\n/// This structure is used to store various MST information about the display port device.  This\r\n/// information can be returned to the user, or used to access various driver calls to\r\n/// fetch various display-device-related settings upon the user's request\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLDisplayDPMSTInfo\r\n{\r\n    /// The ADLDisplayID structure\r\n    ADLDisplayID displayID;\r\n\r\n    /// total bandwidth available on the DP connector\r\n    int    iTotalAvailableBandwidthInMpbs;\r\n    /// bandwidth allocated to this display\r\n    int    iAllocatedBandwidthInMbps;\r\n\r\n    // info from DAL DpMstSinkInfo\r\n    /// string identifier for the display\r\n    char    strGlobalUniqueIdentifier[ADL_MAX_PATH];\r\n\r\n    /// The link count of relative address, rad[0] upto rad[linkCount] are valid\r\n    int        radLinkCount;\r\n    /// The physical connector ID, used to identify the physical DP port\r\n    int        iPhysicalConnectorID;\r\n\r\n    /// Relative address, address scheme starts from source side\r\n    char    rad[ADL_MAX_RAD_LINK_COUNT];\r\n} ADLDisplayDPMSTInfo, *LPADLDisplayDPMSTInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing the display mode definition used per controller.\r\n///\r\n/// This structure is used to store the display mode definition used per controller.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLDisplayMode\r\n{\r\n/// Vertical resolution (in pixels).\r\n   int  iPelsHeight;\r\n/// Horizontal resolution (in pixels).\r\n   int  iPelsWidth;\r\n/// Color depth.\r\n   int  iBitsPerPel;\r\n/// Refresh rate.\r\n   int  iDisplayFrequency;\r\n} ADLDisplayMode;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing detailed timing parameters.\r\n///\r\n/// This structure is used to store the detailed timing parameters.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLDetailedTiming\r\n{\r\n/// Size of the structure.\r\n     int   iSize;\r\n/// Timing flags. \\ref define_detailed_timing_flags\r\n     short sTimingFlags;\r\n/// Total width (columns).\r\n     short sHTotal;\r\n/// Displayed width.\r\n     short sHDisplay;\r\n/// Horizontal sync signal offset.\r\n     short sHSyncStart;\r\n/// Horizontal sync signal width.\r\n     short sHSyncWidth;\r\n/// Total height (rows).\r\n     short sVTotal;\r\n/// Displayed height.\r\n     short sVDisplay;\r\n/// Vertical sync signal offset.\r\n     short sVSyncStart;\r\n/// Vertical sync signal width.\r\n     short sVSyncWidth;\r\n/// Pixel clock value.\r\n     short sPixelClock;\r\n/// Overscan right.\r\n     short sHOverscanRight;\r\n/// Overscan left.\r\n     short sHOverscanLeft;\r\n/// Overscan bottom.\r\n     short sVOverscanBottom;\r\n/// Overscan top.\r\n     short sVOverscanTop;\r\n     short sOverscan8B;\r\n     short sOverscanGR;\r\n} ADLDetailedTiming;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing display mode information.\r\n///\r\n/// This structure is used to store the display mode information.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLDisplayModeInfo\r\n{\r\n/// Timing standard of the current mode. \\ref define_modetiming_standard\r\n  int  iTimingStandard;\r\n/// Applicable timing standards for the current mode.\r\n  int  iPossibleStandard;\r\n/// Refresh rate factor.\r\n  int  iRefreshRate;\r\n/// Num of pixels in a row.\r\n  int  iPelsWidth;\r\n/// Num of pixels in a column.\r\n  int  iPelsHeight;\r\n/// Detailed timing parameters.\r\n  ADLDetailedTiming  sDetailedTiming;\r\n} ADLDisplayModeInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n/// \\brief Structure containing information about display property.\r\n///\r\n/// This structure is used to store the display property for the current adapter.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLDisplayProperty\r\n{\r\n/// Must be set to sizeof the structure\r\n  int iSize;\r\n/// Must be set to \\ref ADL_DL_DISPLAYPROPERTY_TYPE_EXPANSIONMODE or \\ref ADL_DL_DISPLAYPROPERTY_TYPE_USEUNDERSCANSCALING\r\n  int iPropertyType;\r\n/// Get or Set \\ref ADL_DL_DISPLAYPROPERTY_EXPANSIONMODE_CENTER or \\ref ADL_DL_DISPLAYPROPERTY_EXPANSIONMODE_FULLSCREEN or \\ref ADL_DL_DISPLAYPROPERTY_EXPANSIONMODE_ASPECTRATIO or \\ref ADL_DL_DISPLAYPROPERTY_TYPE_ITCFLAGENABLE\r\n  int iExpansionMode;\r\n/// Display Property supported? 1: Supported, 0: Not supported\r\n  int iSupport;\r\n/// Display Property current value\r\n  int iCurrent;\r\n/// Display Property Default value\r\n  int iDefault;\r\n} ADLDisplayProperty;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Clock.\r\n///\r\n/// This structure is used to store the clock information for the current adapter\r\n/// such as core clock and memory clock info.\r\n///\\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLClockInfo\r\n{\r\n/// Core clock in 10 KHz.\r\n    int iCoreClock;\r\n/// Memory clock in 10 KHz.\r\n    int iMemoryClock;\r\n} ADLClockInfo, *LPADLClockInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about I2C.\r\n///\r\n/// This structure is used to store the I2C information for the current adapter.\r\n/// This structure is used by the ADL_Display_WriteAndReadI2C() function.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLI2C\r\n{\r\n/// Size of the structure\r\n    int iSize;\r\n/// Numerical value representing hardware I2C.\r\n    int iLine;\r\n/// The 7-bit I2C slave device address, shifted one bit to the left.\r\n    int iAddress;\r\n/// The offset of the data from the address.\r\n    int iOffset;\r\n/// Read from or write to slave device. \\ref ADL_DL_I2C_ACTIONREAD or \\ref ADL_DL_I2C_ACTIONWRITE or \\ref ADL_DL_I2C_ACTIONREAD_REPEATEDSTART\r\n    int iAction;\r\n/// I2C clock speed in KHz.\r\n    int iSpeed;\r\n/// A numerical value representing the number of bytes to be sent or received on the I2C bus.\r\n    int iDataSize;\r\n/// Address of the characters which are to be sent or received on the I2C bus.\r\n    char *pcData;\r\n} ADLI2C;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about EDID data.\r\n///\r\n/// This structure is used to store the information about EDID data for the adapter.\r\n/// This structure is used by the ADL_Display_EdidData_Get() and ADL_Display_EdidData_Set() functions.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLDisplayEDIDData\r\n{\r\n/// Size of the structure\r\n  int iSize;\r\n/// Set to 0\r\n  int iFlag;\r\n  /// Size of cEDIDData. Set by ADL_Display_EdidData_Get() upon return\r\n  int iEDIDSize;\r\n/// 0, 1 or 2. If set to 3 or above an error ADL_ERR_INVALID_PARAM is generated\r\n  int iBlockIndex;\r\n/// EDID data\r\n  char cEDIDData[ADL_MAX_EDIDDATA_SIZE];\r\n/// Reserved\r\n  int iReserved[4];\r\n}ADLDisplayEDIDData;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about input of controller overlay adjustment.\r\n///\r\n/// This structure is used to store the information about input of controller overlay adjustment for the adapter.\r\n/// This structure is used by the ADL_Display_ControllerOverlayAdjustmentCaps_Get, ADL_Display_ControllerOverlayAdjustmentData_Get, and\r\n/// ADL_Display_ControllerOverlayAdjustmentData_Set() functions.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLControllerOverlayInput\r\n{\r\n/// Should be set to the sizeof the structure\r\n  int  iSize;\r\n///\\ref ADL_DL_CONTROLLER_OVERLAY_ALPHA or \\ref ADL_DL_CONTROLLER_OVERLAY_ALPHAPERPIX\r\n  int  iOverlayAdjust;\r\n/// Data.\r\n  int  iValue;\r\n/// Should be 0.\r\n  int  iReserved;\r\n} ADLControllerOverlayInput;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about overlay adjustment.\r\n///\r\n/// This structure is used to store the information about overlay adjustment for the adapter.\r\n/// This structure is used by the ADLControllerOverlayInfo() function.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLAdjustmentinfo\r\n{\r\n/// Default value\r\n  int iDefault;\r\n/// Minimum value\r\n  int iMin;\r\n/// Maximum Value\r\n  int iMax;\r\n/// Step value\r\n  int iStep;\r\n} ADLAdjustmentinfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about controller overlay information.\r\n///\r\n/// This structure is used to store information about controller overlay info for the adapter.\r\n/// This structure is used by the ADL_Display_ControllerOverlayAdjustmentCaps_Get() function.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLControllerOverlayInfo\r\n{\r\n/// Should be set to the sizeof the structure\r\n  int                    iSize;\r\n/// Data.\r\n  ADLAdjustmentinfo        sOverlayInfo;\r\n/// Should be 0.\r\n  int                    iReserved[3];\r\n} ADLControllerOverlayInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing GL-Sync module information.\r\n///\r\n/// This structure is used to retrieve GL-Sync module information for\r\n/// Workstation Framelock/Genlock.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLGLSyncModuleID\r\n{\r\n/// Unique GL-Sync module ID.\r\n    int        iModuleID;\r\n/// GL-Sync GPU port index (to be passed into ADLGLSyncGenlockConfig.lSignalSource and ADLGlSyncPortControl.lSignalSource).\r\n    int        iGlSyncGPUPort;\r\n/// GL-Sync module firmware version of Boot Sector.\r\n    int        iFWBootSectorVersion;\r\n/// GL-Sync module firmware version of User Sector.\r\n    int        iFWUserSectorVersion;\r\n} ADLGLSyncModuleID , *LPADLGLSyncModuleID;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing GL-Sync ports capabilities.\r\n///\r\n/// This structure is used to retrieve hardware capabilities for the ports of the GL-Sync module\r\n/// for Workstation Framelock/Genlock (such as port type and number of associated LEDs).\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLGLSyncPortCaps\r\n{\r\n/// Port type. Bitfield of ADL_GLSYNC_PORTTYPE_*  \\ref define_glsync\r\n    int        iPortType;\r\n/// Number of LEDs associated for this port.\r\n    int        iNumOfLEDs;\r\n}ADLGLSyncPortCaps, *LPADLGLSyncPortCaps;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing GL-Sync Genlock settings.\r\n///\r\n/// This structure is used to get and set genlock settings for the GPU ports of the GL-Sync module\r\n/// for Workstation Framelock/Genlock.\\n\r\n/// \\see define_glsync\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLGLSyncGenlockConfig\r\n{\r\n/// Specifies what fields in this structure are valid \\ref define_glsync\r\n    int        iValidMask;\r\n/// Delay (ms) generating a sync signal.\r\n    int        iSyncDelay;\r\n/// Vector of framelock control bits. Bitfield of ADL_GLSYNC_FRAMELOCKCNTL_* \\ref define_glsync\r\n    int        iFramelockCntlVector;\r\n/// Source of the sync signal. Either GL_Sync GPU Port index or ADL_GLSYNC_SIGNALSOURCE_* \\ref define_glsync\r\n    int        iSignalSource;\r\n/// Use sampled sync signal. A value of 0 specifies no sampling.\r\n    int        iSampleRate;\r\n/// For interlaced sync signals, the value can be ADL_GLSYNC_SYNCFIELD_1 or *_BOTH \\ref define_glsync\r\n    int        iSyncField;\r\n/// The signal edge that should trigger synchronization. ADL_GLSYNC_TRIGGEREDGE_* \\ref define_glsync\r\n    int        iTriggerEdge;\r\n/// Scan rate multiplier applied to the sync signal. ADL_GLSYNC_SCANRATECOEFF_* \\ref define_glsync\r\n    int        iScanRateCoeff;\r\n}ADLGLSyncGenlockConfig, *LPADLGLSyncGenlockConfig;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing GL-Sync port information.\r\n///\r\n/// This structure is used to get status of the GL-Sync ports (BNC or RJ45s)\r\n/// for Workstation Framelock/Genlock.\r\n/// \\see define_glsync\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLGlSyncPortInfo\r\n{\r\n/// Type of GL-Sync port (ADL_GLSYNC_PORT_*).\r\n    int        iPortType;\r\n/// The number of LEDs for this port. It's also filled within ADLGLSyncPortCaps.\r\n    int        iNumOfLEDs;\r\n/// Port state ADL_GLSYNC_PORTSTATE_*  \\ref define_glsync\r\n    int        iPortState;\r\n/// Scanned frequency for this port (vertical refresh rate in milliHz; 60000 means 60 Hz).\r\n    int        iFrequency;\r\n/// Used for ADL_GLSYNC_PORT_BNC. It is ADL_GLSYNC_SIGNALTYPE_*   \\ref define_glsync\r\n    int        iSignalType;\r\n/// Used for ADL_GLSYNC_PORT_RJ45PORT*. It is GL_Sync GPU Port index or ADL_GLSYNC_SIGNALSOURCE_*.  \\ref define_glsync\r\n    int        iSignalSource;\r\n} ADLGlSyncPortInfo, *LPADLGlSyncPortInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing GL-Sync port control settings.\r\n///\r\n/// This structure is used to configure the GL-Sync ports (RJ45s only)\r\n/// for Workstation Framelock/Genlock.\r\n/// \\see define_glsync\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLGlSyncPortControl\r\n{\r\n/// Port to control ADL_GLSYNC_PORT_RJ45PORT1 or ADL_GLSYNC_PORT_RJ45PORT2   \\ref define_glsync\r\n    int        iPortType;\r\n/// Port control data ADL_GLSYNC_PORTCNTL_*   \\ref define_glsync\r\n    int        iControlVector;\r\n/// Source of the sync signal. Either GL_Sync GPU Port index or ADL_GLSYNC_SIGNALSOURCE_*   \\ref define_glsync\r\n    int        iSignalSource;\r\n} ADLGlSyncPortControl;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing GL-Sync mode of a display.\r\n///\r\n/// This structure is used to get and set GL-Sync mode settings for a display connected to\r\n/// an adapter attached to a GL-Sync module for Workstation Framelock/Genlock.\r\n/// \\see define_glsync\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLGlSyncMode\r\n{\r\n/// Mode control vector. Bitfield of ADL_GLSYNC_MODECNTL_*   \\ref define_glsync\r\n    int        iControlVector;\r\n/// Mode status vector. Bitfield of ADL_GLSYNC_MODECNTL_STATUS_*   \\ref define_glsync\r\n    int        iStatusVector;\r\n/// Index of GL-Sync connector used to genlock the display/controller.\r\n    int        iGLSyncConnectorIndex;\r\n} ADLGlSyncMode, *LPADLGlSyncMode;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing GL-Sync mode of a display.\r\n///\r\n/// This structure is used to get and set GL-Sync mode settings for a display connected to\r\n/// an adapter attached to a GL-Sync module for Workstation Framelock/Genlock.\r\n/// \\see define_glsync\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLGlSyncMode2\r\n{\r\n/// Mode control vector. Bitfield of ADL_GLSYNC_MODECNTL_*   \\ref define_glsync\r\n    int        iControlVector;\r\n/// Mode status vector. Bitfield of ADL_GLSYNC_MODECNTL_STATUS_*   \\ref define_glsync\r\n    int        iStatusVector;\r\n/// Index of GL-Sync connector used to genlock the display/controller.\r\n    int        iGLSyncConnectorIndex;\r\n/// Index of the display to which this GLSync applies to.\r\n    int        iDisplayIndex;\r\n} ADLGlSyncMode2, *LPADLGlSyncMode2;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing the packet info of a display.\r\n///\r\n/// This structure is used to get and set the packet information of a display.\r\n/// This structure is used by ADLDisplayDataPacket.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct  ADLInfoPacket\r\n{\r\n    char hb0;\r\n    char hb1;\r\n    char hb2;\r\n/// sb0~sb27\r\n    char sb[28];\r\n}ADLInfoPacket;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing the AVI packet info of a display.\r\n///\r\n/// This structure is used to get and set AVI the packet info of a display.\r\n/// This structure is used by ADLDisplayDataPacket.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLAVIInfoPacket  //Valid user defined data/\r\n{\r\n/// byte 3, bit 7\r\n   char bPB3_ITC;\r\n/// byte 5, bit [7:4].\r\n   char bPB5;\r\n}ADLAVIInfoPacket;\r\n\r\n// Overdrive clock setting structure definition.\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing the Overdrive clock setting.\r\n///\r\n/// This structure is used to get the Overdrive clock setting.\r\n/// This structure is used by ADLAdapterODClockInfo.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLODClockSetting\r\n{\r\n/// Deafult clock\r\n    int iDefaultClock;\r\n/// Current clock\r\n    int iCurrentClock;\r\n/// Maximum clcok\r\n    int iMaxClock;\r\n/// Minimum clock\r\n    int iMinClock;\r\n/// Requested clcock\r\n    int iRequestedClock;\r\n/// Step\r\n    int iStepClock;\r\n} ADLODClockSetting;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing the Overdrive clock information.\r\n///\r\n/// This structure is used to get the Overdrive clock information.\r\n/// This structure is used by the ADL_Display_ODClockInfo_Get() function.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLAdapterODClockInfo\r\n{\r\n/// Size of the structure\r\n    int iSize;\r\n/// Flag \\ref define_clockinfo_flags\r\n    int iFlags;\r\n/// Memory Clock\r\n    ADLODClockSetting sMemoryClock;\r\n/// Engine Clock\r\n    ADLODClockSetting sEngineClock;\r\n} ADLAdapterODClockInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing the Overdrive clock configuration.\r\n///\r\n/// This structure is used to set the Overdrive clock configuration.\r\n/// This structure is used by the ADL_Display_ODClockConfig_Set() function.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLAdapterODClockConfig\r\n{\r\n/// Size of the structure\r\n  int iSize;\r\n/// Flag \\ref define_clockinfo_flags\r\n  int iFlags;\r\n/// Memory Clock\r\n  int iMemoryClock;\r\n/// Engine Clock\r\n  int iEngineClock;\r\n} ADLAdapterODClockConfig;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about current power management related activity.\r\n///\r\n/// This structure is used to store information about current power management related activity.\r\n/// This structure (Overdrive 5 interfaces) is used by the ADL_PM_CurrentActivity_Get() function.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLPMActivity\r\n{\r\n/// Must be set to the size of the structure\r\n    int iSize;\r\n/// Current engine clock.\r\n    int iEngineClock;\r\n/// Current memory clock.\r\n    int iMemoryClock;\r\n/// Current core voltage.\r\n    int iVddc;\r\n/// GPU utilization.\r\n    int iActivityPercent;\r\n/// Performance level index.\r\n    int iCurrentPerformanceLevel;\r\n/// Current PCIE bus speed.\r\n    int iCurrentBusSpeed;\r\n/// Number of PCIE bus lanes.\r\n    int iCurrentBusLanes;\r\n/// Maximum number of PCIE bus lanes.\r\n    int iMaximumBusLanes;\r\n/// Reserved for future purposes.\r\n    int iReserved;\r\n} ADLPMActivity;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about thermal controller.\r\n///\r\n/// This structure is used to store information about thermal controller.\r\n/// This structure is used by ADL_PM_ThermalDevices_Enum.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLThermalControllerInfo\r\n{\r\n/// Must be set to the size of the structure\r\n  int iSize;\r\n/// Possible valies: \\ref ADL_DL_THERMAL_DOMAIN_OTHER or \\ref ADL_DL_THERMAL_DOMAIN_GPU.\r\n  int iThermalDomain;\r\n///    GPU 0, 1, etc.\r\n  int iDomainIndex;\r\n/// Possible valies: \\ref ADL_DL_THERMAL_FLAG_INTERRUPT or \\ref ADL_DL_THERMAL_FLAG_FANCONTROL\r\n  int iFlags;\r\n} ADLThermalControllerInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about thermal controller temperature.\r\n///\r\n/// This structure is used to store information about thermal controller temperature.\r\n/// This structure is used by the ADL_PM_Temperature_Get() function.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLTemperature\r\n{\r\n/// Must be set to the size of the structure\r\n  int iSize;\r\n/// Temperature in millidegrees Celsius.\r\n  int iTemperature;\r\n} ADLTemperature;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about thermal controller fan speed.\r\n///\r\n/// This structure is used to store information about thermal controller fan speed.\r\n/// This structure is used by the ADL_PM_FanSpeedInfo_Get() function.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLFanSpeedInfo\r\n{\r\n/// Must be set to the size of the structure\r\n  int iSize;\r\n/// \\ref define_fanctrl\r\n  int iFlags;\r\n/// Minimum possible fan speed value in percents.\r\n  int iMinPercent;\r\n/// Maximum possible fan speed value in percents.\r\n  int iMaxPercent;\r\n/// Minimum possible fan speed value in RPM.\r\n  int iMinRPM;\r\n/// Maximum possible fan speed value in RPM.\r\n  int iMaxRPM;\r\n} ADLFanSpeedInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about fan speed reported by thermal controller.\r\n///\r\n/// This structure is used to store information about fan speed reported by thermal controller.\r\n/// This structure is used by the ADL_Overdrive5_FanSpeed_Get() and ADL_Overdrive5_FanSpeed_Set() functions.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLFanSpeedValue\r\n{\r\n/// Must be set to the size of the structure\r\n  int iSize;\r\n/// Possible valies: \\ref ADL_DL_FANCTRL_SPEED_TYPE_PERCENT or \\ref ADL_DL_FANCTRL_SPEED_TYPE_RPM\r\n  int iSpeedType;\r\n/// Fan speed value\r\n  int iFanSpeed;\r\n/// The only flag for now is: \\ref ADL_DL_FANCTRL_FLAG_USER_DEFINED_SPEED\r\n  int iFlags;\r\n} ADLFanSpeedValue;\r\n\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing the range of Overdrive parameter.\r\n///\r\n/// This structure is used to store information about the range of Overdrive parameter.\r\n/// This structure is used by ADLODParameters.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLODParameterRange\r\n{\r\n/// Minimum parameter value.\r\n  int iMin;\r\n/// Maximum parameter value.\r\n  int iMax;\r\n/// Parameter step value.\r\n  int iStep;\r\n} ADLODParameterRange;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive parameters.\r\n///\r\n/// This structure is used to store information about Overdrive parameters.\r\n/// This structure is used by the ADL_Overdrive5_ODParameters_Get() function.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLODParameters\r\n{\r\n/// Must be set to the size of the structure\r\n  int iSize;\r\n/// Number of standard performance states.\r\n  int iNumberOfPerformanceLevels;\r\n/// Indicates whether the GPU is capable to measure its activity.\r\n  int iActivityReportingSupported;\r\n/// Indicates whether the GPU supports discrete performance levels or performance range.\r\n  int iDiscretePerformanceLevels;\r\n/// Reserved for future use.\r\n  int iReserved;\r\n/// Engine clock range.\r\n  ADLODParameterRange sEngineClock;\r\n/// Memory clock range.\r\n  ADLODParameterRange sMemoryClock;\r\n/// Core voltage range.\r\n  ADLODParameterRange sVddc;\r\n} ADLODParameters;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive level.\r\n///\r\n/// This structure is used to store information about Overdrive level.\r\n/// This structure is used by ADLODPerformanceLevels.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLODPerformanceLevel\r\n{\r\n/// Engine clock.\r\n  int iEngineClock;\r\n/// Memory clock.\r\n  int iMemoryClock;\r\n/// Core voltage.\r\n  int iVddc;\r\n} ADLODPerformanceLevel;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive performance levels.\r\n///\r\n/// This structure is used to store information about Overdrive performance levels.\r\n/// This structure is used by the ADL_Overdrive5_ODPerformanceLevels_Get() and ADL_Overdrive5_ODPerformanceLevels_Set() functions.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLODPerformanceLevels\r\n{\r\n/// Must be set to sizeof( \\ref ADLODPerformanceLevels ) + sizeof( \\ref ADLODPerformanceLevel ) * (ADLODParameters.iNumberOfPerformanceLevels - 1)\r\n  int iSize;\r\n  int iReserved;\r\n/// Array of performance state descriptors. Must have ADLODParameters.iNumberOfPerformanceLevels elements.\r\n  ADLODPerformanceLevel aLevels [1];\r\n} ADLODPerformanceLevels;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about the proper CrossfireX chains combinations.\r\n///\r\n/// This structure is used to store information about the CrossfireX chains combination for a particular adapter.\r\n/// This structure is used by the ADL_Adapter_Crossfire_Caps(), ADL_Adapter_Crossfire_Get(), and ADL_Adapter_Crossfire_Set() functions.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLCrossfireComb\r\n{\r\n/// Number of adapters in this combination.\r\n  int iNumLinkAdapter;\r\n/// A list of ADL indexes of the linked adapters in this combination.\r\n  int iAdaptLink[3];\r\n} ADLCrossfireComb;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing CrossfireX state and error information.\r\n///\r\n/// This structure is used to store state and error information about a particular adapter CrossfireX combination.\r\n/// This structure is used by the ADL_Adapter_Crossfire_Get() function.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLCrossfireInfo\r\n{\r\n/// Current error code of this CrossfireX combination.\r\n  int iErrorCode;\r\n/// Current \\ref define_crossfirestate\r\n  int iState;\r\n/// If CrossfireX is supported by this combination. The value is either \\ref ADL_TRUE or \\ref ADL_FALSE.\r\n  int iSupported;\r\n} ADLCrossfireInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n/// \\brief Structure containing information about the BIOS.\r\n///\r\n/// This structure is used to store various information about the Chipset.  This\r\n/// information can be returned to the user.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLBiosInfo\r\n{\r\n    char strPartNumber[ADL_MAX_PATH];    ///< Part number.\r\n    char strVersion[ADL_MAX_PATH];        ///< Version number.\r\n    char strDate[ADL_MAX_PATH];        ///< BIOS date in yyyy/mm/dd hh:mm format.\r\n} ADLBiosInfo, *LPADLBiosInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n/// \\brief Structure containing information about adapter location.\r\n///\r\n/// This structure is used to store information about adapter location.\r\n/// This structure is used by ADLMVPUStatus.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLAdapterLocation\r\n{\r\n/// PCI Bus number : 8 bits\r\n    int iBus;\r\n/// Device number : 5 bits\r\n    int iDevice;\r\n/// Function number : 3 bits\r\n    int iFunction;\r\n} ADLAdapterLocation,ADLBdf;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n/// \\brief Structure containing version information\r\n///\r\n/// This structure is used to store software version information, description of the display device and a web link to the latest installed Catalyst drivers.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLVersionsInfo\r\n{\r\n    /// Driver Release (Packaging) Version (e.g. 8.71-100128n-094835E-ATI)\r\n    char strDriverVer[ADL_MAX_PATH];\r\n    /// Catalyst Version(e.g. \"10.1\").\r\n    char strCatalystVersion[ADL_MAX_PATH];\r\n    /// Web link to an XML file with information about the latest AMD drivers and locations (e.g. \"http://www.amd.com/us/driverxml\" )\r\n    char strCatalystWebLink[ADL_MAX_PATH];\r\n} ADLVersionsInfo, *LPADLVersionsInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n/// \\brief Structure containing version information\r\n///\r\n/// This structure is used to store software version information, description of the display device and a web link to the latest installed Catalyst drivers.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLVersionsInfoX2\r\n{\r\n    /// Driver Release (Packaging) Version (e.g. \"16.20.1035-160621a-303814C\")\r\n    char strDriverVer[ADL_MAX_PATH];\r\n    /// Catalyst Version(e.g. \"15.8\").\r\n    char strCatalystVersion[ADL_MAX_PATH];\r\n    /// Crimson Version(e.g. \"16.6.2\").\r\n    char strCrimsonVersion[ADL_MAX_PATH];\r\n    /// Web link to an XML file with information about the latest AMD drivers and locations (e.g. \"http://support.amd.com/drivers/xml/driver_09_us.xml\" )\r\n    char strCatalystWebLink[ADL_MAX_PATH];\r\n} ADLVersionsInfoX2, *LPADLVersionsInfoX2;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n/// \\brief Structure containing information about MultiVPU capabilities.\r\n///\r\n/// This structure is used to store information about MultiVPU capabilities.\r\n/// This structure is used by the ADL_Display_MVPUCaps_Get() function.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLMVPUCaps\r\n{\r\n/// Must be set to sizeof( ADLMVPUCaps ).\r\n  int iSize;\r\n/// Number of adapters.\r\n  int iAdapterCount;\r\n/// Bits set for all possible MVPU masters. \\ref MVPU_ADAPTER_0 .. \\ref MVPU_ADAPTER_3\r\n  int iPossibleMVPUMasters;\r\n/// Bits set for all possible MVPU slaves. \\ref MVPU_ADAPTER_0 .. \\ref MVPU_ADAPTER_3\r\n  int iPossibleMVPUSlaves;\r\n/// Registry path for each adapter.\r\n  char cAdapterPath[ADL_DL_MAX_MVPU_ADAPTERS][ADL_DL_MAX_REGISTRY_PATH];\r\n} ADLMVPUCaps;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n/// \\brief Structure containing information about MultiVPU status.\r\n///\r\n/// This structure is used to store information about MultiVPU status.\r\n/// Ths structure is used by the ADL_Display_MVPUStatus_Get() function.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLMVPUStatus\r\n{\r\n/// Must be set to sizeof( ADLMVPUStatus ).\r\n  int iSize;\r\n/// Number of active adapters.\r\n  int iActiveAdapterCount;\r\n/// MVPU status.\r\n  int iStatus;\r\n/// PCI Bus/Device/Function for each active adapter participating in MVPU.\r\n  ADLAdapterLocation aAdapterLocation[ADL_DL_MAX_MVPU_ADAPTERS];\r\n} ADLMVPUStatus;\r\n\r\n// Displays Manager structures\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n/// \\brief Structure containing information about the activatable source.\r\n///\r\n/// This structure is used to store activatable source information\r\n/// This information can be returned to the user.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLActivatableSource\r\n{\r\n    /// The Persistent logical Adapter Index.\r\n    int iAdapterIndex;\r\n    /// The number of Activatable Sources.\r\n    int iNumActivatableSources;\r\n    /// The bit mask identifies the number of bits ActivatableSourceValue is using. (Not currnetly used)\r\n    int iActivatableSourceMask;\r\n    /// The bit mask identifies the status.  (Not currnetly used)\r\n    int iActivatableSourceValue;\r\n} ADLActivatableSource, *LPADLActivatableSource;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n/// \\brief Structure containing information about display mode.\r\n///\r\n/// This structure is used to store the display mode for the current adapter\r\n/// such as X, Y positions, screen resolutions, orientation,\r\n/// color depth, refresh rate, progressive or interlace mode, etc.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n\r\ntypedef struct ADLMode\r\n{\r\n/// Adapter index.\r\n    int iAdapterIndex;\r\n/// Display IDs.\r\n    ADLDisplayID displayID;\r\n/// Screen position X coordinate.\r\n    int iXPos;\r\n/// Screen position Y coordinate.\r\n    int iYPos;\r\n/// Screen resolution Width.\r\n    int iXRes;\r\n/// Screen resolution Height.\r\n    int iYRes;\r\n/// Screen Color Depth. E.g., 16, 32.\r\n    int iColourDepth;\r\n/// Screen refresh rate. Could be fractional E.g. 59.97\r\n    float fRefreshRate;\r\n/// Screen orientation. E.g., 0, 90, 180, 270.\r\n    int iOrientation;\r\n/// Vista mode flag indicating Progressive or Interlaced mode.\r\n    int iModeFlag;\r\n/// The bit mask identifying the number of bits this Mode is currently using. It is the sum of all the bit definitions defined in \\ref define_displaymode\r\n    int iModeMask;\r\n/// The bit mask identifying the display status. The detailed definition is in  \\ref define_displaymode\r\n    int iModeValue;\r\n} ADLMode, *LPADLMode;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n/// \\brief Structure containing information about display target information.\r\n///\r\n/// This structure is used to store the display target information.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLDisplayTarget\r\n{\r\n    /// The Display ID.\r\n    ADLDisplayID displayID;\r\n\r\n    /// The display map index identify this manner and the desktop surface.\r\n    int iDisplayMapIndex;\r\n\r\n    /// The bit mask identifies the number of bits DisplayTarget is currently using. It is the sum of all the bit definitions defined in \\ref ADL_DISPLAY_DISPLAYTARGET_PREFERRED.\r\n    int  iDisplayTargetMask;\r\n\r\n    /// The bit mask identifies the display status. The detailed definition is in \\ref ADL_DISPLAY_DISPLAYTARGET_PREFERRED.\r\n    int  iDisplayTargetValue;\r\n} ADLDisplayTarget, *LPADLDisplayTarget;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about the display SLS bezel Mode information.\r\n///\r\n/// This structure is used to store the display SLS bezel Mode information.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct tagADLBezelTransientMode\r\n{\r\n    /// Adapter Index\r\n    int iAdapterIndex;\r\n\r\n    /// SLS Map Index\r\n    int iSLSMapIndex;\r\n\r\n    /// The mode index\r\n    int iSLSModeIndex;\r\n\r\n    /// The mode\r\n    ADLMode displayMode;\r\n\r\n    /// The number of bezel offsets belongs to this map\r\n    int  iNumBezelOffset;\r\n\r\n    /// The first bezel offset array index in the native mode array\r\n    int  iFirstBezelOffsetArrayIndex;\r\n\r\n    /// The bit mask identifies the bits this structure is currently using. It will be the total OR of all the bit definitions.\r\n    int  iSLSBezelTransientModeMask;\r\n\r\n    /// The bit mask identifies the display status. The detail definition is defined below.\r\n    int  iSLSBezelTransientModeValue;\r\n} ADLBezelTransientMode, *LPADLBezelTransientMode;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n/// \\brief Structure containing information about the adapter display manner.\r\n///\r\n/// This structure is used to store adapter display manner information\r\n/// This information can be returned to the user. Alternatively, it can be used to access various driver calls to\r\n/// fetch various display device related display manner settings upon the user's request.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLAdapterDisplayCap\r\n{\r\n    /// The Persistent logical Adapter Index.\r\n    int iAdapterIndex;\r\n    /// The bit mask identifies the number of bits AdapterDisplayCap is currently using. Sum all the bits defined in ADL_ADAPTER_DISPLAYCAP_XXX\r\n    int  iAdapterDisplayCapMask;\r\n    /// The bit mask identifies the status. Refer to ADL_ADAPTER_DISPLAYCAP_XXX\r\n    int  iAdapterDisplayCapValue;\r\n} ADLAdapterDisplayCap, *LPADLAdapterDisplayCap;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about display mapping.\r\n///\r\n/// This structure is used to store the display mapping data such as display manner.\r\n/// For displays with horizontal or vertical stretch manner,\r\n/// this structure also stores the display order, display row, and column data.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLDisplayMap\r\n{\r\n/// The current display map index. It is the OS desktop index. For example, if the OS index 1 is showing clone mode, the display map will be 1.\r\n    int iDisplayMapIndex;\r\n\r\n/// The Display Mode for the current map\r\n    ADLMode displayMode;\r\n\r\n/// The number of display targets belongs to this map\\n\r\n    int iNumDisplayTarget;\r\n\r\n/// The first target array index in the Target array\\n\r\n    int iFirstDisplayTargetArrayIndex;\r\n\r\n/// The bit mask identifies the number of bits DisplayMap is currently using. It is the sum of all the bit definitions defined in ADL_DISPLAY_DISPLAYMAP_MANNER_xxx.\r\n     int  iDisplayMapMask;\r\n\r\n///The bit mask identifies the display status. The detailed definition is in ADL_DISPLAY_DISPLAYMAP_MANNER_xxx.\r\n    int  iDisplayMapValue;\r\n} ADLDisplayMap, *LPADLDisplayMap;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n/// \\brief Structure containing information about the display device possible map for one GPU\r\n///\r\n/// This structure is used to store the display device possible map\r\n/// This information can be returned to the user.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLPossibleMap\r\n{\r\n    /// The current PossibleMap index. Each PossibleMap is assigned an index\r\n    int iIndex;\r\n    /// The adapter index identifying the GPU for which to validate these Maps & Targets\r\n    int iAdapterIndex;\r\n    /// Number of display Maps for this GPU to be validated\r\n    int iNumDisplayMap;\r\n    /// The display Maps list to validate\r\n    ADLDisplayMap* displayMap;\r\n    /// the number of display Targets for these display Maps\r\n    int iNumDisplayTarget;\r\n    /// The display Targets list for these display Maps to be validated.\r\n    ADLDisplayTarget* displayTarget;\r\n} ADLPossibleMap, *LPADLPossibleMap;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n/// \\brief Structure containing information about display possible mapping.\r\n///\r\n/// This structure is used to store the display possible mapping's controller index for the current display.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLPossibleMapping\r\n{\r\n    int iDisplayIndex;                ///< The display index. Each display is assigned an index.\r\n    int iDisplayControllerIndex;    ///< The controller index to which display is mapped.\r\n    int iDisplayMannerSupported;    ///< The supported display manner.\r\n} ADLPossibleMapping, *LPADLPossibleMapping;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n/// \\brief Structure containing information about the validated display device possible map result.\r\n///\r\n/// This structure is used to store the validated display device possible map result\r\n/// This information can be returned to the user.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLPossibleMapResult\r\n{\r\n    /// The current display map index. It is the OS Desktop index. For example, OS Index 1 showing clone mode. The Display Map will be 1.\r\n    int iIndex;\r\n    // The bit mask identifies the number of bits   PossibleMapResult is currently using. It will be the sum all the bit definitions defined in ADL_DISPLAY_POSSIBLEMAPRESULT_VALID.\r\n    int iPossibleMapResultMask;\r\n    /// The bit mask identifies the possible map result. The detail definition is defined in ADL_DISPLAY_POSSIBLEMAPRESULT_XXX.\r\n    int iPossibleMapResultValue;\r\n} ADLPossibleMapResult, *LPADLPossibleMapResult;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about the display SLS Grid information.\r\n///\r\n/// This structure is used to store the display SLS Grid information.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLSLSGrid\r\n{\r\n/// The Adapter index.\r\n    int iAdapterIndex;\r\n\r\n/// The grid index.\r\n    int  iSLSGridIndex;\r\n\r\n/// The grid row.\r\n    int  iSLSGridRow;\r\n\r\n/// The grid column.\r\n    int  iSLSGridColumn;\r\n\r\n/// The grid bit mask identifies the number of bits DisplayMap is currently using. Sum of all bits defined in ADL_DISPLAY_SLSGRID_ORIENTATION_XXX\r\n    int  iSLSGridMask;\r\n\r\n/// The grid bit value identifies the display status. Refer to ADL_DISPLAY_SLSGRID_ORIENTATION_XXX\r\n    int  iSLSGridValue;\r\n} ADLSLSGrid, *LPADLSLSGrid;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about the display SLS Map information.\r\n///\r\n/// This structure is used to store the display SLS Map information.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct    ADLSLSMap\r\n{\r\n    /// The Adapter Index\r\n    int iAdapterIndex;\r\n\r\n    /// The current display map index. It is the OS Desktop index. For example, OS Index 1 showing clone mode. The Display Map will be 1.\r\n    int iSLSMapIndex;\r\n\r\n    /// Indicate the current grid\r\n    ADLSLSGrid grid;\r\n\r\n    /// OS surface index\r\n    int  iSurfaceMapIndex;\r\n\r\n     ///  Screen orientation. E.g., 0, 90, 180, 270\r\n     int iOrientation;\r\n\r\n    /// The number of display targets belongs to this map\r\n    int  iNumSLSTarget;\r\n\r\n    /// The first target array index in the Target array\r\n    int  iFirstSLSTargetArrayIndex;\r\n\r\n    /// The number of native modes belongs to this map\r\n    int  iNumNativeMode;\r\n\r\n    /// The first native mode array index in the native mode array\r\n    int  iFirstNativeModeArrayIndex;\r\n\r\n    /// The number of bezel modes belongs to this map\r\n    int  iNumBezelMode;\r\n\r\n    /// The first bezel mode array index in the native mode array\r\n    int  iFirstBezelModeArrayIndex;\r\n\r\n    /// The number of bezel offsets belongs to this map\r\n    int  iNumBezelOffset;\r\n\r\n    /// The first bezel offset array index in the\r\n    int  iFirstBezelOffsetArrayIndex;\r\n\r\n    /// The bit mask identifies the number of bits DisplayMap is currently using. Sum all the bit definitions defined in ADL_DISPLAY_SLSMAP_XXX.\r\n    int  iSLSMapMask;\r\n\r\n    /// The bit mask identifies the display map status. Refer to ADL_DISPLAY_SLSMAP_XXX\r\n    int  iSLSMapValue;\r\n} ADLSLSMap, *LPADLSLSMap;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about the display SLS Offset information.\r\n///\r\n/// This structure is used to store the display SLS Offset information.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLSLSOffset\r\n{\r\n    /// The Adapter Index\r\n    int iAdapterIndex;\r\n\r\n    /// The current display map index. It is the OS Desktop index. For example, OS Index 1 showing clone mode. The Display Map will be 1.\r\n    int iSLSMapIndex;\r\n\r\n    /// The Display ID.\r\n    ADLDisplayID displayID;\r\n\r\n    /// SLS Bezel Mode Index\r\n    int iBezelModeIndex;\r\n\r\n    /// SLS Bezel Offset X\r\n    int iBezelOffsetX;\r\n\r\n    /// SLS Bezel Offset Y\r\n    int iBezelOffsetY;\r\n\r\n    /// SLS Display Width\r\n    int iDisplayWidth;\r\n\r\n    /// SLS Display Height\r\n    int iDisplayHeight;\r\n\r\n    /// The bit mask identifies the number of bits Offset is currently using.\r\n    int iBezelOffsetMask;\r\n\r\n    /// The bit mask identifies the display status.\r\n    int  iBezelffsetValue;\r\n} ADLSLSOffset, *LPADLSLSOffset;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about the display SLS Mode information.\r\n///\r\n/// This structure is used to store the display SLS Mode information.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLSLSMode\r\n{\r\n    /// The Adapter Index\r\n    int iAdapterIndex;\r\n\r\n    /// The current display map index. It is the OS Desktop index. For example, OS Index 1 showing clone mode. The Display Map will be 1.\r\n    int iSLSMapIndex;\r\n\r\n    /// The mode index\r\n    int iSLSModeIndex;\r\n\r\n    /// The mode for this map.\r\n    ADLMode displayMode;\r\n\r\n    /// The bit mask identifies the number of bits Mode is currently using.\r\n    int iSLSNativeModeMask;\r\n\r\n    /// The bit mask identifies the display status.\r\n    int iSLSNativeModeValue;\r\n} ADLSLSMode, *LPADLSLSMode;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about the display Possible SLS Map information.\r\n///\r\n/// This structure is used to store the display Possible SLS Map information.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLPossibleSLSMap\r\n{\r\n    /// The current display map index. It is the OS Desktop index.\r\n    /// For example, OS Index 1 showing clone mode. The Display Map will be 1.\r\n    int iSLSMapIndex;\r\n\r\n    /// Number of display map to be validated.\r\n    int iNumSLSMap;\r\n\r\n    /// The display map list for validation\r\n    ADLSLSMap* lpSLSMap;\r\n\r\n    /// the number of display map config to be validated.\r\n    int iNumSLSTarget;\r\n\r\n    /// The display target list for validation.\r\n    ADLDisplayTarget* lpDisplayTarget;\r\n} ADLPossibleSLSMap, *LPADLPossibleSLSMap;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about the SLS targets.\r\n///\r\n/// This structure is used to store the SLS targets information.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLSLSTarget\r\n{\r\n    /// the logic adapter index\r\n    int iAdapterIndex;\r\n\r\n    /// The SLS map index\r\n    int iSLSMapIndex;\r\n\r\n    /// The target ID\r\n    ADLDisplayTarget displayTarget;\r\n\r\n    /// Target postion X in SLS grid\r\n    int iSLSGridPositionX;\r\n\r\n    /// Target postion Y in SLS grid\r\n    int iSLSGridPositionY;\r\n\r\n    /// The view size width, height and rotation angle per SLS Target\r\n    ADLMode viewSize;\r\n\r\n    /// The bit mask identifies the bits in iSLSTargetValue are currently used\r\n    int iSLSTargetMask;\r\n\r\n    /// The bit mask identifies status info. It is for function extension purpose\r\n    int iSLSTargetValue;\r\n} ADLSLSTarget, *LPADLSLSTarget;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about the Adapter offset stepping size.\r\n///\r\n/// This structure is used to store the Adapter offset stepping size information.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLBezelOffsetSteppingSize\r\n{\r\n    /// the logic adapter index\r\n    int iAdapterIndex;\r\n\r\n    /// The SLS map index\r\n    int iSLSMapIndex;\r\n\r\n    /// Bezel X stepping size offset\r\n    int iBezelOffsetSteppingSizeX;\r\n\r\n    /// Bezel Y stepping size offset\r\n    int iBezelOffsetSteppingSizeY;\r\n\r\n    /// Identifies the bits this structure is currently using. It will be the total OR of all the bit definitions.\r\n    int iBezelOffsetSteppingSizeMask;\r\n\r\n    /// Bit mask identifies the display status.\r\n    int iBezelOffsetSteppingSizeValue;\r\n} ADLBezelOffsetSteppingSize, *LPADLBezelOffsetSteppingSize;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about the overlap offset info for all the displays for each SLS mode.\r\n///\r\n/// This structure is used to store the no. of overlapped modes for each SLS Mode once user finishes the configuration from Overlap Widget\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLSLSOverlappedMode\r\n{\r\n    /// the SLS mode for which the overlap is configured\r\n    ADLMode SLSMode;\r\n    /// the number of target displays in SLS.\r\n    int iNumSLSTarget;\r\n    /// the first target array index in the target array\r\n    int iFirstTargetArrayIndex;\r\n}ADLSLSTargetOverlap, *LPADLSLSTargetOverlap;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about driver supported PowerExpress Config Caps\r\n///\r\n/// This structure is used to store the driver supported PowerExpress Config Caps\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLPXConfigCaps\r\n{\r\n    /// The Persistent logical Adapter Index.\r\n    int iAdapterIndex;\r\n\r\n    /// The bit mask identifies the number of bits PowerExpress Config Caps is currently using. It is the sum of all the bit definitions defined in ADL_PX_CONFIGCAPS_XXXX /ref define_powerxpress_constants.\r\n    int  iPXConfigCapMask;\r\n\r\n    /// The bit mask identifies the PowerExpress Config Caps value. The detailed definition is in ADL_PX_CONFIGCAPS_XXXX /ref define_powerxpress_constants.\r\n    int  iPXConfigCapValue;\r\n} ADLPXConfigCaps, *LPADLPXConfigCaps;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Enum containing PX or HG type\r\n///\r\n/// This enum is used to get PX or hG type\r\n///\r\n/// \\nosubgrouping\r\n//////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef enum ADLPxType\r\n{\r\n\t//Not AMD related PX/HG or not PX or HG at all\r\n\tADL_PX_NONE = 0,\r\n\t//A+A PX\r\n\tADL_SWITCHABLE_AMDAMD = 1,\r\n\t// A+A HG\r\n\tADL_HG_AMDAMD = 2,\r\n\t//A+I PX\r\n\tADL_SWITCHABLE_AMDOTHER = 3,\r\n\t//A+I HG\r\n\tADL_HG_AMDOTHER = 4,\r\n}ADLPxType;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about an application\r\n///\r\n/// This structure is used to store basic information of an application\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLApplicationData\r\n{\r\n    /// Path Name\r\n    char strPathName[ADL_MAX_PATH];\r\n    /// File Name\r\n    char strFileName[ADL_APP_PROFILE_FILENAME_LENGTH];\r\n    /// Creation timestamp\r\n    char strTimeStamp[ADL_APP_PROFILE_TIMESTAMP_LENGTH];\r\n    /// Version\r\n    char strVersion[ADL_APP_PROFILE_VERSION_LENGTH];\r\n}ADLApplicationData;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about an application\r\n///\r\n/// This structure is used to store basic information of an application\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLApplicationDataX2\r\n{\r\n    /// Path Name\r\n    wchar_t strPathName[ADL_MAX_PATH];\r\n    /// File Name\r\n    wchar_t strFileName[ADL_APP_PROFILE_FILENAME_LENGTH];\r\n    /// Creation timestamp\r\n    wchar_t strTimeStamp[ADL_APP_PROFILE_TIMESTAMP_LENGTH];\r\n    /// Version\r\n    wchar_t strVersion[ADL_APP_PROFILE_VERSION_LENGTH];\r\n}ADLApplicationDataX2;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about an application\r\n///\r\n/// This structure is used to store basic information of an application including process id\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLApplicationDataX3\r\n{\r\n    /// Path Name\r\n    wchar_t strPathName[ADL_MAX_PATH];\r\n    /// File Name\r\n    wchar_t strFileName[ADL_APP_PROFILE_FILENAME_LENGTH];\r\n    /// Creation timestamp\r\n    wchar_t strTimeStamp[ADL_APP_PROFILE_TIMESTAMP_LENGTH];\r\n    /// Version\r\n    wchar_t strVersion[ADL_APP_PROFILE_VERSION_LENGTH];\r\n    //Application Process id\r\n    unsigned int iProcessId;\r\n}ADLApplicationDataX3;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information of a property of an application profile\r\n///\r\n/// This structure is used to store property information of an application profile\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct PropertyRecord\r\n{\r\n    /// Property Name\r\n    char strName [ADL_APP_PROFILE_PROPERTY_LENGTH];\r\n    /// Property Type\r\n    ADLProfilePropertyType eType;\r\n    /// Data Size in bytes\r\n    int iDataSize;\r\n    /// Property Value, can be any data type\r\n    unsigned char uData[1];\r\n}PropertyRecord;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about an application profile\r\n///\r\n/// This structure is used to store information of an application profile\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLApplicationProfile\r\n{\r\n    /// Number of properties\r\n    int iCount;\r\n    /// Buffer to store all property records\r\n    PropertyRecord record[1];\r\n}ADLApplicationProfile;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about an OD5 Power Control feature\r\n///\r\n/// This structure is used to store information of an Power Control feature\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLPowerControlInfo\r\n{\r\n/// Minimum value.\r\nint iMinValue;\r\n/// Maximum value.\r\nint iMaxValue;\r\n/// The minimum change in between minValue and maxValue.\r\nint iStepValue;\r\n } ADLPowerControlInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about an controller mode\r\n///\r\n/// This structure is used to store information of an controller mode\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLControllerMode\r\n{\r\n    /// This falg indicates actions that will be applied by set viewport\r\n    /// The value can be a combination of ADL_CONTROLLERMODE_CM_MODIFIER_VIEW_POSITION,\r\n    /// ADL_CONTROLLERMODE_CM_MODIFIER_VIEW_PANLOCK and ADL_CONTROLLERMODE_CM_MODIFIER_VIEW_SIZE\r\n    int iModifiers;\r\n\r\n    /// Horizontal view starting position\r\n    int iViewPositionCx;\r\n\r\n    /// Vertical view starting position\r\n    int iViewPositionCy;\r\n\r\n    /// Horizontal left panlock position\r\n    int iViewPanLockLeft;\r\n\r\n    /// Horizontal right panlock position\r\n    int iViewPanLockRight;\r\n\r\n    /// Vertical top panlock position\r\n    int iViewPanLockTop;\r\n\r\n    /// Vertical bottom panlock position\r\n    int iViewPanLockBottom;\r\n\r\n    /// View resolution in pixels (width)\r\n    int iViewResolutionCx;\r\n\r\n    /// View resolution in pixels (hight)\r\n    int iViewResolutionCy;\r\n}ADLControllerMode;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about a display\r\n///\r\n/// This structure is used to store information about a display\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLDisplayIdentifier\r\n{\r\n    /// ADL display index\r\n    long ulDisplayIndex;\r\n\r\n    /// manufacturer ID of the display\r\n    long ulManufacturerId;\r\n\r\n    /// product ID of the display\r\n    long ulProductId;\r\n\r\n    /// serial number of the display\r\n    long ulSerialNo;\r\n} ADLDisplayIdentifier;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive 6 clock range\r\n///\r\n/// This structure is used to store information about Overdrive 6 clock range\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLOD6ParameterRange\r\n{\r\n    /// The starting value of the clock range\r\n    int     iMin;\r\n    /// The ending value of the clock range\r\n    int     iMax;\r\n    /// The minimum increment between clock values\r\n    int     iStep;\r\n} ADLOD6ParameterRange;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive 6 capabilities\r\n///\r\n/// This structure is used to store information about Overdrive 6 capabilities\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLOD6Capabilities\r\n{\r\n    /// Contains a bitmap of the OD6 capability flags.  Possible values: \\ref ADL_OD6_CAPABILITY_SCLK_CUSTOMIZATION,\r\n    /// \\ref ADL_OD6_CAPABILITY_MCLK_CUSTOMIZATION, \\ref ADL_OD6_CAPABILITY_GPU_ACTIVITY_MONITOR\r\n    int     iCapabilities;\r\n    /// Contains a bitmap indicating the power states\r\n    /// supported by OD6.  Currently only the performance state\r\n    /// is supported. Possible Values: \\ref ADL_OD6_SUPPORTEDSTATE_PERFORMANCE\r\n    int     iSupportedStates;\r\n    /// Number of levels. OD6 will always use 2 levels, which describe\r\n    /// the minimum to maximum clock ranges.\r\n    /// The 1st level indicates the minimum clocks, and the 2nd level\r\n    /// indicates the maximum clocks.\r\n    int     iNumberOfPerformanceLevels;\r\n    /// Contains the hard limits of the sclk range.  Overdrive\r\n    /// clocks cannot be set outside this range.\r\n    ADLOD6ParameterRange     sEngineClockRange;\r\n    /// Contains the hard limits of the mclk range.  Overdrive\r\n    /// clocks cannot be set outside this range.\r\n    ADLOD6ParameterRange     sMemoryClockRange;\r\n\r\n    /// Value for future extension\r\n    int     iExtValue;\r\n    /// Mask for future extension\r\n    int     iExtMask;\r\n} ADLOD6Capabilities;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive 6 clock values.\r\n///\r\n/// This structure is used to store information about Overdrive 6 clock values.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLOD6PerformanceLevel\r\n{\r\n    /// Engine (core) clock.\r\n    int iEngineClock;\r\n    /// Memory clock.\r\n    int iMemoryClock;\r\n} ADLOD6PerformanceLevel;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive 6 clocks.\r\n///\r\n/// This structure is used to store information about Overdrive 6 clocks.  This is a\r\n/// variable-sized structure.  iNumberOfPerformanceLevels indicate how many elements\r\n/// are contained in the aLevels array.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLOD6StateInfo\r\n{\r\n    /// Number of levels.  OD6 uses clock ranges instead of discrete performance levels.\r\n    /// iNumberOfPerformanceLevels is always 2.  The 1st level indicates the minimum clocks\r\n    /// in the range.  The 2nd level indicates the maximum clocks in the range.\r\n    int     iNumberOfPerformanceLevels;\r\n\r\n    /// Value for future extension\r\n    int     iExtValue;\r\n    /// Mask for future extension\r\n    int     iExtMask;\r\n\r\n    /// Variable-sized array of levels.\r\n    /// The number of elements in the array is specified by iNumberofPerformanceLevels.\r\n    ADLOD6PerformanceLevel aLevels [1];\r\n} ADLOD6StateInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about current Overdrive 6 performance status.\r\n///\r\n/// This structure is used to store information about current Overdrive 6 performance status.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLOD6CurrentStatus\r\n{\r\n    /// Current engine clock in 10 KHz.\r\n    int     iEngineClock;\r\n    /// Current memory clock in 10 KHz.\r\n    int     iMemoryClock;\r\n    /// Current GPU activity in percent.  This\r\n    /// indicates how \"busy\" the GPU is.\r\n    int     iActivityPercent;\r\n    /// Not used.  Reserved for future use.\r\n    int     iCurrentPerformanceLevel;\r\n    /// Current PCI-E bus speed\r\n    int     iCurrentBusSpeed;\r\n    /// Current PCI-E bus # of lanes\r\n    int     iCurrentBusLanes;\r\n    /// Maximum possible PCI-E bus # of lanes\r\n    int     iMaximumBusLanes;\r\n\r\n    /// Value for future extension\r\n    int     iExtValue;\r\n    /// Mask for future extension\r\n    int     iExtMask;\r\n} ADLOD6CurrentStatus;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive 6 thermal contoller capabilities\r\n///\r\n/// This structure is used to store information about Overdrive 6 thermal controller capabilities\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLOD6ThermalControllerCaps\r\n{\r\n    /// Contains a bitmap of thermal controller capability flags. Possible values: \\ref ADL_OD6_TCCAPS_THERMAL_CONTROLLER, \\ref ADL_OD6_TCCAPS_FANSPEED_CONTROL,\r\n    /// \\ref ADL_OD6_TCCAPS_FANSPEED_PERCENT_READ, \\ref ADL_OD6_TCCAPS_FANSPEED_PERCENT_WRITE, \\ref ADL_OD6_TCCAPS_FANSPEED_RPM_READ, \\ref ADL_OD6_TCCAPS_FANSPEED_RPM_WRITE\r\n    int     iCapabilities;\r\n    /// Minimum fan speed expressed as a percentage\r\n    int     iFanMinPercent;\r\n    /// Maximum fan speed expressed as a percentage\r\n    int     iFanMaxPercent;\r\n    /// Minimum fan speed expressed in revolutions-per-minute\r\n    int     iFanMinRPM;\r\n    /// Maximum fan speed expressed in revolutions-per-minute\r\n    int     iFanMaxRPM;\r\n\r\n    /// Value for future extension\r\n    int     iExtValue;\r\n    /// Mask for future extension\r\n    int     iExtMask;\r\n} ADLOD6ThermalControllerCaps;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive 6 fan speed information\r\n///\r\n/// This structure is used to store information about Overdrive 6 fan speed information\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLOD6FanSpeedInfo\r\n{\r\n    /// Contains a bitmap of the valid fan speed type flags.  Possible values: \\ref ADL_OD6_FANSPEED_TYPE_PERCENT, \\ref ADL_OD6_FANSPEED_TYPE_RPM, \\ref ADL_OD6_FANSPEED_USER_DEFINED\r\n    int     iSpeedType;\r\n    /// Contains current fan speed in percent (if valid flag exists in iSpeedType)\r\n    int     iFanSpeedPercent;\r\n    /// Contains current fan speed in RPM (if valid flag exists in iSpeedType)\r\n    int        iFanSpeedRPM;\r\n\r\n    /// Value for future extension\r\n    int     iExtValue;\r\n    /// Mask for future extension\r\n    int     iExtMask;\r\n} ADLOD6FanSpeedInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive 6 fan speed value\r\n///\r\n/// This structure is used to store information about Overdrive 6 fan speed value\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLOD6FanSpeedValue\r\n{\r\n    /// Indicates the units of the fan speed.  Possible values: \\ref ADL_OD6_FANSPEED_TYPE_PERCENT, \\ref ADL_OD6_FANSPEED_TYPE_RPM\r\n    int     iSpeedType;\r\n    /// Fan speed value (units as indicated above)\r\n    int     iFanSpeed;\r\n\r\n    /// Value for future extension\r\n    int     iExtValue;\r\n    /// Mask for future extension\r\n    int     iExtMask;\r\n} ADLOD6FanSpeedValue;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive 6 PowerControl settings.\r\n///\r\n/// This structure is used to store information about Overdrive 6 PowerControl settings.\r\n/// PowerControl is the feature which allows the performance characteristics of the GPU\r\n/// to be adjusted by changing the PowerTune power limits.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLOD6PowerControlInfo\r\n{\r\n    /// The minimum PowerControl adjustment value\r\n    int     iMinValue;\r\n    /// The maximum PowerControl adjustment value\r\n    int     iMaxValue;\r\n    /// The minimum difference between PowerControl adjustment values\r\n    int     iStepValue;\r\n\r\n    /// Value for future extension\r\n    int     iExtValue;\r\n    /// Mask for future extension\r\n    int     iExtMask;\r\n} ADLOD6PowerControlInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive 6 PowerControl settings.\r\n///\r\n/// This structure is used to store information about Overdrive 6 PowerControl settings.\r\n/// PowerControl is the feature which allows the performance characteristics of the GPU\r\n/// to be adjusted by changing the PowerTune power limits.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLOD6VoltageControlInfo\r\n{\r\n    /// The minimum VoltageControl adjustment value\r\n    int     iMinValue;\r\n    /// The maximum VoltageControl adjustment value\r\n    int     iMaxValue;\r\n    /// The minimum difference between VoltageControl adjustment values\r\n    int     iStepValue;\r\n\r\n    /// Value for future extension\r\n    int     iExtValue;\r\n    /// Mask for future extension\r\n    int     iExtMask;\r\n} ADLOD6VoltageControlInfo;\r\n\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing ECC statistics namely SEC counts and DED counts\r\n/// Single error count - count of errors that can be corrected\r\n/// Doubt Error Detect -  count of errors that cannot be corrected\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLECCData\r\n{\r\n    // Single error count - count of errors that can be corrected\r\n    int iSec;\r\n    // Double error detect - count of errors that cannot be corrected\r\n    int iDed;\r\n} ADLECCData;\r\n\r\n/// \\brief Handle to ADL client context.\r\n///\r\n///  ADL clients obtain context handle from initial call to \\ref ADL2_Main_Control_Create.\r\n///  Clients have to pass the handle to each subsequent ADL call and finally destroy\r\n///  the context with call to \\ref ADL2_Main_Control_Destroy\r\n/// \\nosubgrouping\r\ntypedef void *ADL_CONTEXT_HANDLE;\r\n\r\n/// \\brief Handle to ADL Frame Monitor Token.\r\n///\r\n///  Frame Monitor clients obtain handle from initial call to \\ref ADL2_Adapter_FrameMetrics_FrameDuration_Enable\r\n///  Clients have to pass the handle to each subsequent ADL call to \\ref ADL2_Adapter_FrameMetrics_FrameDuration_Get\r\n///  and finally destroy the token with call to \\ref ADL2_Adapter_FrameMetrics_FrameDuration_Disable\r\n/// \\nosubgrouping\r\ntypedef void *ADL_FRAME_DURATION_HANDLE;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing the display mode definition used per controller.\r\n///\r\n/// This structure is used to store the display mode definition used per controller.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLDisplayModeX2\r\n{\r\n/// Horizontal resolution (in pixels).\r\n   int  iWidth;\r\n/// Vertical resolution (in lines).\r\n   int  iHeight;\r\n/// Interlaced/Progressive. The value will be set for Interlaced as ADL_DL_TIMINGFLAG_INTERLACED. If not set it is progressive. Refer define_detailed_timing_flags.\r\n   int  iScanType;\r\n/// Refresh rate.\r\n   int  iRefreshRate;\r\n/// Timing Standard. Refer define_modetiming_standard.\r\n   int  iTimingStandard;\r\n} ADLDisplayModeX2;\r\n\r\ntypedef enum ADLAppProcessState\r\n{\r\n\tAPP_PROC_INVALID = 0,          // Invalid Application\r\n\tAPP_PROC_PREMPTION = 1,          // The Application is being set up for Process Creation\r\n\tAPP_PROC_CREATION = 2,          // The Application's Main Process is created by the OS\r\n\tAPP_PROC_READ = 3,          // The Application's Data is ready to be read\r\n\tAPP_PROC_WAIT = 4,          // The Application is waiting for Timeout or Notification to Resume\r\n\tAPP_PROC_RUNNING = 5,          // The Application is running\r\n\tAPP_PROC_TERMINATE = 6           // The Application is about to terminate\r\n}ADLAppProcessState;\r\n\r\ntypedef enum ADLAppInterceptionListType\r\n{\r\n\tADL_INVALID_FORMAT = 0,\r\n\tADL_IMAGEFILEFORMAT = 1,\r\n\tADL_ENVVAR = 2\r\n}ADLAppInterceptionListType;\r\n\r\ntypedef struct ADLAppInterceptionInfo\r\n{\r\n\twchar_t                     AppName[ADL_MAX_PATH]; // the file name of the application or env var\r\n\tunsigned int                ProcessId;\r\n\tADLAppInterceptionListType  AppFormat;\r\n\tADLAppProcessState          AppState;\r\n} ADLAppInterceptionInfo;\r\n\r\ntypedef enum ADL_AP_DATABASE // same as _SHARED_AP_DATABASE in \"inc/shared/shared_escape.h\"\r\n{\r\n\tADL_AP_DATABASE__SYSTEM,\r\n\tADL_AP_DATABASE__USER,\r\n\tADL_AP_DATABASE__OEM\r\n} ADL_AP_DATABASE;\r\n\r\ntypedef struct ADLAppInterceptionInfoX2\r\n{\r\n\twchar_t                     AppName[ADL_MAX_PATH]; // the file name of the application or env var\r\n\tunsigned int                ProcessId;\r\n\tunsigned int                WaitForResumeNeeded;\r\n\twchar_t                     CommandLine[ADL_MAX_PATH]; // The command line on app start/stop event\r\n\tADLAppInterceptionListType  AppFormat;\r\n\tADLAppProcessState          AppState;\r\n} ADLAppInterceptionInfoX2;\r\n\r\ntypedef struct ADLAppInterceptionInfoX3\r\n{\r\n    wchar_t                     AppName[ADL_MAX_PATH]; // the file name of the application or env var\r\n    unsigned int                ProcessId;\r\n    unsigned int                WaitForResumeNeeded;\r\n    unsigned int                RayTracingStatus; // returns the Ray Tracing status if it is enabled atleast once in session.\r\n    wchar_t                     CommandLine[ADL_MAX_PATH]; // The command line on app start/stop event\r\n    ADLAppInterceptionListType  AppFormat;\r\n    ADLAppProcessState          AppState;\r\n} ADLAppInterceptionInfoX3;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information info for a property record in a profile\r\n///\r\n/// This structure is used to store info for a property record in a profile\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLPropertyRecordCreate\r\n{\r\n\t/// Name of the property\r\n\twchar_t * strPropertyName;\r\n\t/// Data type of the property\r\n\tADLProfilePropertyType eType;\r\n\t// Value of the property\r\n\twchar_t * strPropertyValue;\r\n} ADLPropertyRecordCreate;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information info for an application record\r\n///\r\n/// This structure is used to store info for an application record\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLApplicationRecord\r\n{\r\n    /// Title of the application\r\n    wchar_t * strTitle;\r\n    /// File path of the application\r\n    wchar_t * strPathName;\r\n    /// File name of the application\r\n    wchar_t * strFileName;\r\n    /// File versin the application\r\n    wchar_t * strVersion;\r\n    /// Nostes on the application\r\n    wchar_t * strNotes;\r\n    /// Driver area which the application uses\r\n    wchar_t * strArea;\r\n    /// Name of profile assigned to the application\r\n    wchar_t * strProfileName;\r\n    // Source where this application record come from\r\n    ADL_AP_DATABASE recordSource;\r\n} ADLApplicationRecord;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive 6 extension capabilities\r\n///\r\n/// This structure is used to store information about Overdrive 6 extension capabilities\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLOD6CapabilitiesEx\r\n{\r\n    /// Contains a bitmap of the OD6 extension capability flags.  Possible values: \\ref ADL_OD6_CAPABILITY_SCLK_CUSTOMIZATION,\r\n    /// \\ref ADL_OD6_CAPABILITY_MCLK_CUSTOMIZATION, \\ref ADL_OD6_CAPABILITY_GPU_ACTIVITY_MONITOR,\r\n    /// \\ref ADL_OD6_CAPABILITY_POWER_CONTROL, \\ref ADL_OD6_CAPABILITY_VOLTAGE_CONTROL, \\ref ADL_OD6_CAPABILITY_PERCENT_ADJUSTMENT,\r\n    //// \\ref ADL_OD6_CAPABILITY_THERMAL_LIMIT_UNLOCK\r\n    int iCapabilities;\r\n    /// The Power states that support clock and power customization.  Only performance state is currently supported.\r\n    /// Possible Values: \\ref ADL_OD6_SUPPORTEDSTATE_PERFORMANCE\r\n    int iSupportedStates;\r\n    /// Returns the hard limits of the SCLK overdrive adjustment range.  Overdrive clocks should not be adjusted outside of this range.  The values are specified as +/- percentages.\r\n    ADLOD6ParameterRange sEngineClockPercent;\r\n    /// Returns the hard limits of the MCLK overdrive adjustment range.  Overdrive clocks should not be adjusted outside of this range.  The values are specified as +/- percentages.\r\n    ADLOD6ParameterRange sMemoryClockPercent;\r\n    /// Returns the hard limits of the Power Limit adjustment range.  Power limit should not be adjusted outside this range.  The values are specified as +/- percentages.\r\n    ADLOD6ParameterRange sPowerControlPercent;\r\n    /// Reserved for future expansion of the structure.\r\n    int iExtValue;\r\n    /// Reserved for future expansion of the structure.\r\n    int iExtMask;\r\n} ADLOD6CapabilitiesEx;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive 6 extension state information\r\n///\r\n/// This structure is used to store information about Overdrive 6 extension state information\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLOD6StateEx\r\n{\r\n    /// The current engine clock adjustment value, specified as a +/- percent.\r\n    int iEngineClockPercent;\r\n    /// The current memory clock adjustment value, specified as a +/- percent.\r\n    int iMemoryClockPercent;\r\n    /// The current power control adjustment value, specified as a +/- percent.\r\n    int iPowerControlPercent;\r\n    /// Reserved for future expansion of the structure.\r\n    int iExtValue;\r\n    /// Reserved for future expansion of the structure.\r\n    int iExtMask;\r\n} ADLOD6StateEx;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive 6 extension recommended maximum clock adjustment values\r\n///\r\n/// This structure is used to store information about Overdrive 6 extension recommended maximum clock adjustment values\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLOD6MaxClockAdjust\r\n{\r\n    /// The recommended maximum engine clock adjustment in percent, for the specified power limit value.\r\n    int iEngineClockMax;\r\n    /// The recommended maximum memory clock adjustment in percent, for the specified power limit value.\r\n    /// Currently the memory is independent of the Power Limit setting, so iMemoryClockMax will always return the maximum\r\n    /// possible adjustment value.  This field is here for future enhancement in case we add a dependency between Memory Clock\r\n    /// adjustment and Power Limit setting.\r\n    int iMemoryClockMax;\r\n    /// Reserved for future expansion of the structure.\r\n    int iExtValue;\r\n    /// Reserved for future expansion of the structure.\r\n    int iExtMask;\r\n} ADLOD6MaxClockAdjust;\r\n\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing the Connector information\r\n///\r\n/// this structure is used to get the connector information like length, positions & etc.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLConnectorInfo\r\n{\r\n    ///index of the connector(0-based)\r\n    int iConnectorIndex;\r\n    ///used for disply identification/ordering\r\n    int iConnectorId;\r\n    ///index of the slot, 0-based index.\r\n    int iSlotIndex;\r\n    ///Type of the connector. \\ref define_connector_types\r\n    int iType;\r\n    ///Position of the connector(in millimeters), from the right side of the slot.\r\n    int iOffset;\r\n    ///Length of the connector(in millimeters).\r\n    int iLength;\r\n} ADLConnectorInfo;\r\n\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing the slot information\r\n///\r\n/// this structure is used to get the slot information like length of the slot, no of connectors on the slot & etc.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLBracketSlotInfo\r\n{\r\n    ///index of the slot, 0-based index.\r\n    int iSlotIndex;\r\n    ///length of the slot(in millimeters).\r\n    int iLength;\r\n    ///width of the slot(in millimeters).\r\n    int iWidth;\r\n} ADLBracketSlotInfo;\r\n\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing MST branch information\r\n///\r\n/// this structure is used to store the MST branch information\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLMSTRad\r\n{\r\n    ///depth of the link.\r\n    int iLinkNumber;\r\n    /// Relative address, address scheme starts from source side\r\n    char rad[ADL_MAX_RAD_LINK_COUNT];\r\n} ADLMSTRad;\r\n\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing port information\r\n///\r\n/// this structure is used to get the display or MST branch information\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLDevicePort\r\n{\r\n    ///index of the connector.\r\n    int iConnectorIndex;\r\n    ///Relative MST address. If MST RAD contains 0 it means DP or Root of the MST topology. For non DP connectors MST RAD is ignored.\r\n    ADLMSTRad aMSTRad;\r\n} ADLDevicePort;\r\n\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing supported connection types and properties\r\n///\r\n/// this structure is used to get the supported connection types and supported properties of given connector\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLSupportedConnections\r\n{\r\n    ///Bit vector of supported connections. Bitmask is defined in constants section. \\ref define_connection_types\r\n    int iSupportedConnections;\r\n    ///Array of bitvectors. Each bit vector represents supported properties for one connection type. Index of this array is connection type (bit number in mask).\r\n    int iSupportedProperties[ADL_MAX_CONNECTION_TYPES];\r\n} ADLSupportedConnections;\r\n\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing connection state of the connector\r\n///\r\n/// this structure is used to get the current Emulation status and mode of the given connector\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLConnectionState\r\n{\r\n    ///The value is bit vector. Each bit represents status. See masks constants for details. \\ref define_emulation_status\r\n    int iEmulationStatus;\r\n    ///It contains information about current emulation mode. See constants for details. \\ref define_emulation_mode\r\n    int iEmulationMode;\r\n    ///If connection is active it will contain display id, otherwise CWDDEDI_INVALID_DISPLAY_INDEX\r\n    int iDisplayIndex;\r\n} ADLConnectionState;\r\n\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing connection properties information\r\n///\r\n/// this structure is used to retrieve the properties of connection type\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLConnectionProperties\r\n{\r\n    //Bit vector. Represents actual properties. Supported properties for specific connection type. \\ref define_connection_properties\r\n    int iValidProperties;\r\n    //Bitrate(in MHz). Could be used for MST branch, DP or DP active dongle. \\ref define_linkrate_constants\r\n    int iBitrate;\r\n    //Number of lanes in DP connection. \\ref define_lanecount_constants\r\n    int iNumberOfLanes;\r\n    //Color depth(in bits). \\ref define_colordepth_constants\r\n    int iColorDepth;\r\n    //3D capabilities. It could be used for some dongles. For instance: alternate framepack. Value of this property is bit vector.\r\n    int iStereo3DCaps;\r\n    ///Output Bandwidth. Could be used for MST branch, DP or DP Active dongle. \\ref define_linkrate_constants\r\n    int iOutputBandwidth;\r\n} ADLConnectionProperties;\r\n\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing connection information\r\n///\r\n/// this structure is used to retrieve the data from driver which includes\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLConnectionData\r\n{\r\n    ///Connection type. based on the connection type either iNumberofPorts or IDataSize,EDIDdata is valid, \\ref define_connection_types\r\n    int iConnectionType;\r\n    ///Specifies the connection properties.\r\n    ADLConnectionProperties aConnectionProperties;\r\n    ///Number of ports\r\n    int iNumberofPorts;\r\n    ///Number of Active Connections\r\n    int iActiveConnections;\r\n    ///actual size of EDID data block size.\r\n    int iDataSize;\r\n    ///EDID Data\r\n    char EdidData[ADL_MAX_DISPLAY_EDID_DATA_SIZE];\r\n} ADLConnectionData;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about an controller mode including Number of Connectors\r\n///\r\n/// This structure is used to store information of an controller mode\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLAdapterCapsX2\r\n{\r\n    /// AdapterID for this adapter\r\n    int iAdapterID;\r\n    /// Number of controllers for this adapter\r\n    int iNumControllers;\r\n    /// Number of displays for this adapter\r\n    int iNumDisplays;\r\n    /// Number of overlays for this adapter\r\n    int iNumOverlays;\r\n    /// Number of GLSyncConnectors\r\n    int iNumOfGLSyncConnectors;\r\n    /// The bit mask identifies the adapter caps\r\n    int iCapsMask;\r\n    /// The bit identifies the adapter caps \\ref define_adapter_caps\r\n    int iCapsValue;\r\n    /// Number of Connectors for this adapter\r\n    int iNumConnectors;\r\n}ADLAdapterCapsX2;\r\n\r\ntypedef enum ADL_ERROR_RECORD_SEVERITY\r\n{\r\n    ADL_GLOBALLY_UNCORRECTED  = 1,\r\n    ADL_LOCALLY_UNCORRECTED   = 2,\r\n    ADL_DEFFERRED             = 3,\r\n    ADL_CORRECTED             = 4\r\n}ADL_ERROR_RECORD_SEVERITY;\r\n\r\ntypedef union _ADL_ECC_EDC_FLAG\r\n{\r\n    struct\r\n    {\r\n        unsigned int isEccAccessing        : 1;\r\n        unsigned int reserved              : 31;\r\n    }bits;\r\n    unsigned int u32All;\r\n}ADL_ECC_EDC_FLAG;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about EDC Error Record\r\n///\r\n/// This structure is used to store EDC Error Record\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLErrorRecord\r\n{\r\n    // Severity of error\r\n    ADL_ERROR_RECORD_SEVERITY Severity;\r\n\r\n    // Is the counter valid?\r\n    int  countValid;\r\n\r\n    // Counter value, if valid\r\n    unsigned int count;\r\n\r\n    // Is the location information valid?\r\n    int locationValid;\r\n\r\n    // Physical location of error\r\n    unsigned int CU; // CU number on which error occurred, if known\r\n    char StructureName[32]; // e.g. LDS, TCC, etc.\r\n\r\n    // Time of error record creation (e.g. time of query, or time of poison interrupt)\r\n    char tiestamp[32];\r\n\r\n    unsigned int padding[3];\r\n}ADLErrorRecord;\r\n\r\ntypedef enum ADL_EDC_BLOCK_ID\r\n{\r\n    ADL_EDC_BLOCK_ID_SQCIS = 1,\r\n    ADL_EDC_BLOCK_ID_SQCDS = 2,\r\n    ADL_EDC_BLOCK_ID_SGPR  = 3,\r\n    ADL_EDC_BLOCK_ID_VGPR  = 4,\r\n    ADL_EDC_BLOCK_ID_LDS   = 5,\r\n    ADL_EDC_BLOCK_ID_GDS   = 6,\r\n    ADL_EDC_BLOCK_ID_TCL1  = 7,\r\n    ADL_EDC_BLOCK_ID_TCL2  = 8\r\n}ADL_EDC_BLOCK_ID;\r\n\r\ntypedef enum ADL_ERROR_INJECTION_MODE\r\n{\r\n    ADL_ERROR_INJECTION_MODE_SINGLE      = 1,\r\n    ADL_ERROR_INJECTION_MODE_MULTIPLE    = 2,\r\n    ADL_ERROR_INJECTION_MODE_ADDRESS     = 3\r\n}ADL_ERROR_INJECTION_MODE;\r\n\r\ntypedef union _ADL_ERROR_PATTERN\r\n{\r\n    struct\r\n    {\r\n        unsigned long  EccInjVector         :  16;\r\n        unsigned long  EccInjEn             :  9;\r\n        unsigned long  EccBeatEn            :  4;\r\n        unsigned long  EccChEn              :  4;\r\n        unsigned long  reserved             :  31;\r\n    } bits;\r\n    unsigned long long u64Value;\r\n} ADL_ERROR_PATTERN;\r\n\r\ntypedef struct ADL_ERROR_INJECTION_DATA\r\n{\r\n    unsigned long long errorAddress;\r\n    ADL_ERROR_PATTERN errorPattern;\r\n}ADL_ERROR_INJECTION_DATA;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about EDC Error Injection\r\n///\r\n/// This structure is used to store EDC Error Injection\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLErrorInjection\r\n{\r\n    ADL_EDC_BLOCK_ID blockId;\r\n    ADL_ERROR_INJECTION_MODE errorInjectionMode;\r\n}ADLErrorInjection;\r\n\r\ntypedef struct ADLErrorInjectionX2\r\n{\r\n    ADL_EDC_BLOCK_ID blockId;\r\n    ADL_ERROR_INJECTION_MODE errorInjectionMode;\r\n    ADL_ERROR_INJECTION_DATA errorInjectionData;\r\n}ADLErrorInjectionX2;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing per display FreeSync capability information.\r\n///\r\n/// This structure is used to store the FreeSync capability of both the display and\r\n/// the GPU the display is connected to.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLFreeSyncCap\r\n{\r\n    /// FreeSync capability flags. \\ref define_freesync_caps\r\n    int iCaps;\r\n    /// Reports minimum FreeSync refresh rate supported by the display in micro hertz\r\n    int iMinRefreshRateInMicroHz;\r\n    /// Reports maximum FreeSync refresh rate supported by the display in micro hertz\r\n    int iMaxRefreshRateInMicroHz;\r\n    /// Index of FreeSync Label to use:  ADL_FREESYNC_LABEL_*\r\n    unsigned char ucLabelIndex;\r\n    /// Reserved\r\n    char cReserved[3];\r\n    int iReserved[4];\r\n} ADLFreeSyncCap;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing per display Display Connectivty Experience Settings\r\n///\r\n/// This structure is used to store the Display Connectivity Experience settings of a\r\n/// display\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLDceSettings\r\n{\r\n    DceSettingsType type;                       // Defines which structure is in the union below\r\n    union\r\n    {\r\n        struct\r\n        {\r\n            bool qualityDetectionEnabled;\r\n        } HdmiLq;\r\n        struct\r\n        {\r\n            DpLinkRate linkRate;                // Read-only\r\n            unsigned int numberOfActiveLanes;   // Read-only\r\n            unsigned int numberofTotalLanes;    // Read-only\r\n            int relativePreEmphasis;            // Allowable values are -2 to +2\r\n            int relativeVoltageSwing;           // Allowable values are -2 to +2\r\n            int persistFlag;\r\n        } DpLink;\r\n        struct\r\n        {\r\n            bool linkProtectionEnabled;         // Read-only\r\n        } Protection;\r\n    } Settings;\r\n    int iReserved[15];\r\n} ADLDceSettings;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Graphic Core\r\n///\r\n/// This structure is used to get Graphic Core Info\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLGraphicCoreInfo\r\n{\r\n    /// indicate the graphic core generation\r\n    int iGCGen;\r\n\r\n    union\r\n    {\r\n        /// Total number of CUs. Valid for GCN (iGCGen == GCN)\r\n        int iNumCUs;\r\n        /// Total number of WGPs. Valid for RDNA (iGCGen == RDNA)\r\n        int iNumWGPs;\r\n    };\r\n\r\n    union\r\n    {\r\n        /// Number of processing elements per CU. Valid for GCN (iGCGen == GCN)\r\n        int iNumPEsPerCU;\r\n        /// Number of processing elements per WGP. Valid for RDNA (iGCGen == RDNA)\r\n        int iNumPEsPerWGP;\r\n    };\r\n\r\n    /// Total number of SIMDs. Valid for Pre GCN (iGCGen == Pre-GCN)\r\n    int iNumSIMDs;\r\n\r\n    /// Total number of ROPs. Valid for both GCN and Pre GCN\r\n    int iNumROPs;\r\n\r\n    /// reserved for future use\r\n    int iReserved[11];\r\n}ADLGraphicCoreInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive N clock range\r\n///\r\n/// This structure is used to store information about Overdrive N clock range\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLODNParameterRange\r\n{\r\n    /// The starting value of the clock range\r\n    int     iMode;\r\n    /// The starting value of the clock range\r\n    int     iMin;\r\n    /// The ending value of the clock range\r\n    int     iMax;\r\n    /// The minimum increment between clock values\r\n    int     iStep;\r\n    /// The default clock values\r\n    int     iDefault;\r\n} ADLODNParameterRange;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive N capabilities\r\n///\r\n/// This structure is used to store information about Overdrive N capabilities\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLODNCapabilities\r\n{\r\n    /// Number of levels which describe the minimum to maximum clock ranges.\r\n    /// The 1st level indicates the minimum clocks, and the 2nd level\r\n    /// indicates the maximum clocks.\r\n    int     iMaximumNumberOfPerformanceLevels;\r\n    /// Contains the hard limits of the sclk range.  Overdrive\r\n    /// clocks cannot be set outside this range.\r\n    ADLODNParameterRange     sEngineClockRange;\r\n    /// Contains the hard limits of the mclk range.  Overdrive\r\n    /// clocks cannot be set outside this range.\r\n    ADLODNParameterRange     sMemoryClockRange;\r\n    /// Contains the hard limits of the vddc range.  Overdrive\r\n    /// clocks cannot be set outside this range.\r\n    ADLODNParameterRange     svddcRange;\r\n    /// Contains the hard limits of the power range.  Overdrive\r\n    /// clocks cannot be set outside this range.\r\n    ADLODNParameterRange     power;\r\n    /// Contains the hard limits of the power range.  Overdrive\r\n    /// clocks cannot be set outside this range.\r\n    ADLODNParameterRange     powerTuneTemperature;\r\n    /// Contains the hard limits of the Temperature range.  Overdrive\r\n    /// clocks cannot be set outside this range.\r\n    ADLODNParameterRange     fanTemperature;\r\n    /// Contains the hard limits of the Fan range.  Overdrive\r\n    /// clocks cannot be set outside this range.\r\n    ADLODNParameterRange     fanSpeed;\r\n    /// Contains the hard limits of the Fan range.  Overdrive\r\n    /// clocks cannot be set outside this range.\r\n    ADLODNParameterRange     minimumPerformanceClock;\r\n} ADLODNCapabilities;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive N capabilities\r\n///\r\n/// This structure is used to store information about Overdrive N capabilities\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLODNCapabilitiesX2\r\n{\r\n    /// Number of levels which describe the minimum to maximum clock ranges.\r\n    /// The 1st level indicates the minimum clocks, and the 2nd level\r\n    /// indicates the maximum clocks.\r\n    int     iMaximumNumberOfPerformanceLevels;\r\n    /// bit vector, which tells what are the features are supported.\r\n    /// \\ref: ADLODNFEATURECONTROL\r\n    int iFlags;\r\n    /// Contains the hard limits of the sclk range.  Overdrive\r\n    /// clocks cannot be set outside this range.\r\n    ADLODNParameterRange     sEngineClockRange;\r\n    /// Contains the hard limits of the mclk range.  Overdrive\r\n    /// clocks cannot be set outside this range.\r\n    ADLODNParameterRange     sMemoryClockRange;\r\n    /// Contains the hard limits of the vddc range.  Overdrive\r\n    /// clocks cannot be set outside this range.\r\n    ADLODNParameterRange     svddcRange;\r\n    /// Contains the hard limits of the power range.  Overdrive\r\n    /// clocks cannot be set outside this range.\r\n    ADLODNParameterRange     power;\r\n    /// Contains the hard limits of the power range.  Overdrive\r\n    /// clocks cannot be set outside this range.\r\n    ADLODNParameterRange     powerTuneTemperature;\r\n    /// Contains the hard limits of the Temperature range.  Overdrive\r\n    /// clocks cannot be set outside this range.\r\n    ADLODNParameterRange     fanTemperature;\r\n    /// Contains the hard limits of the Fan range.  Overdrive\r\n    /// clocks cannot be set outside this range.\r\n    ADLODNParameterRange     fanSpeed;\r\n    /// Contains the hard limits of the Fan range.  Overdrive\r\n    /// clocks cannot be set outside this range.\r\n    ADLODNParameterRange     minimumPerformanceClock;\r\n    /// Contains the hard limits of the throttleNotification\r\n    ADLODNParameterRange throttleNotificaion;\r\n    /// Contains the hard limits of the Auto Systemclock\r\n    ADLODNParameterRange autoSystemClock;\r\n} ADLODNCapabilitiesX2;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive level.\r\n///\r\n/// This structure is used to store information about Overdrive level.\r\n/// This structure is used by ADLODPerformanceLevels.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLODNPerformanceLevel\r\n{\r\n    /// clock.\r\n    int iClock;\r\n    /// VDCC.\r\n    int iVddc;\r\n    /// enabled\r\n    int iEnabled;\r\n} ADLODNPerformanceLevel;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive N performance levels.\r\n///\r\n/// This structure is used to store information about Overdrive performance levels.\r\n/// This structure is used by the ADL_OverdriveN_ODPerformanceLevels_Get() and ADL_OverdriveN_ODPerformanceLevels_Set() functions.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLODNPerformanceLevels\r\n{\r\n    int iSize;\r\n    //Automatic/manual\r\n    int iMode;\r\n    /// Must be set to sizeof( \\ref ADLODPerformanceLevels ) + sizeof( \\ref ADLODPerformanceLevel ) * (ADLODParameters.iNumberOfPerformanceLevels - 1)\r\n    int iNumberOfPerformanceLevels;\r\n    /// Array of performance state descriptors. Must have ADLODParameters.iNumberOfPerformanceLevels elements.\r\n    ADLODNPerformanceLevel aLevels[1];\r\n} ADLODNPerformanceLevels;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive N Fan Speed.\r\n///\r\n/// This structure is used to store information about Overdrive Fan control .\r\n/// This structure is used by the ADL_OverdriveN_ODPerformanceLevels_Get() and ADL_OverdriveN_ODPerformanceLevels_Set() functions.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLODNFanControl\r\n{\r\n    int iMode;\r\n    int iFanControlMode;\r\n    int iCurrentFanSpeedMode;\r\n    int iCurrentFanSpeed;\r\n    int iTargetFanSpeed;\r\n    int iTargetTemperature;\r\n    int iMinPerformanceClock;\r\n    int iMinFanLimit;\r\n} ADLODNFanControl;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive N power limit.\r\n///\r\n/// This structure is used to store information about Overdrive power limit.\r\n/// This structure is used by the ADL_OverdriveN_ODPerformanceLevels_Get() and ADL_OverdriveN_ODPerformanceLevels_Set() functions.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLODNPowerLimitSetting\r\n{\r\n    int iMode;\r\n    int iTDPLimit;\r\n    int iMaxOperatingTemperature;\r\n} ADLODNPowerLimitSetting;\r\n\r\ntypedef struct ADLODNPerformanceStatus\r\n{\r\n    int iCoreClock;\r\n    int iMemoryClock;\r\n    int iDCEFClock;\r\n    int iGFXClock;\r\n    int iUVDClock;\r\n    int iVCEClock;\r\n    int iGPUActivityPercent;\r\n    int iCurrentCorePerformanceLevel;\r\n    int iCurrentMemoryPerformanceLevel;\r\n    int iCurrentDCEFPerformanceLevel;\r\n    int iCurrentGFXPerformanceLevel;\r\n    int iUVDPerformanceLevel;\r\n    int iVCEPerformanceLevel;\r\n    int iCurrentBusSpeed;\r\n    int iCurrentBusLanes;\r\n    int iMaximumBusLanes;\r\n    int iVDDC;\r\n    int iVDDCI;\r\n} ADLODNPerformanceStatus;\r\n\r\n///\\brief Structure containing information about Overdrive level.\r\n///\r\n/// This structure is used to store information about Overdrive level.\r\n/// This structure is used by ADLODPerformanceLevels.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLODNPerformanceLevelX2\r\n{\r\n    /// clock.\r\n    int iClock;\r\n    /// VDCC.\r\n    int iVddc;\r\n    /// enabled\r\n    int iEnabled;\r\n    /// MASK\r\n    int iControl;\r\n} ADLODNPerformanceLevelX2;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive N performance levels.\r\n///\r\n/// This structure is used to store information about Overdrive performance levels.\r\n/// This structure is used by the ADL_OverdriveN_ODPerformanceLevels_Get() and ADL_OverdriveN_ODPerformanceLevels_Set() functions.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLODNPerformanceLevelsX2\r\n{\r\n    int iSize;\r\n    //Automatic/manual\r\n    int iMode;\r\n    /// Must be set to sizeof( \\ref ADLODPerformanceLevels ) + sizeof( \\ref ADLODPerformanceLevel ) * (ADLODParameters.iNumberOfPerformanceLevels - 1)\r\n    int iNumberOfPerformanceLevels;\r\n    /// Array of performance state descriptors. Must have ADLODParameters.iNumberOfPerformanceLevels elements.\r\n    ADLODNPerformanceLevelX2 aLevels[1];\r\n} ADLODNPerformanceLevelsX2;\r\n\r\ntypedef enum ADLODNCurrentPowerType\r\n{\r\n    ODN_GPU_TOTAL_POWER = 0,\r\n    ODN_GPU_PPT_POWER,\r\n    ODN_GPU_SOCKET_POWER,\r\n    ODN_GPU_CHIP_POWER\r\n} ADLODNCurrentPowerType;\r\n\r\n// in/out: CWDDEPM_CURRENTPOWERPARAMETERS\r\ntypedef struct ADLODNCurrentPowerParameters\r\n{\r\n    int   size;\r\n    ADLODNCurrentPowerType   powerType;\r\n    int  currentPower;\r\n} ADLODNCurrentPowerParameters;\r\n\r\n//ODN Ext range data structure\r\ntypedef struct ADLODNExtSingleInitSetting\r\n{\r\n\tint mode;\r\n\tint minValue;\r\n\tint maxValue;\r\n\tint step;\r\n\tint defaultValue;\r\n} ADLODNExtSingleInitSetting;\r\n\r\n//OD8 Ext range data structure\r\ntypedef struct ADLOD8SingleInitSetting\r\n{\r\n    int featureID;\r\n    int minValue;\r\n    int maxValue;\r\n    int defaultValue;\r\n} ADLOD8SingleInitSetting;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive8 initial setting\r\n///\r\n/// This structure is used to store information about Overdrive8 initial setting\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLOD8InitSetting\r\n{\r\n    int count;\r\n    int overdrive8Capabilities;\r\n    ADLOD8SingleInitSetting  od8SettingTable[OD8_COUNT];\r\n} ADLOD8InitSetting;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive8 current setting\r\n///\r\n/// This structure is used to store information about Overdrive8 current setting\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLOD8CurrentSetting\r\n{\r\n    int count;\r\n    int Od8SettingTable[OD8_COUNT];\r\n} ADLOD8CurrentSetting;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Overdrive8 set setting\r\n///\r\n/// This structure is used to store information about Overdrive8 set setting\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n\r\ntypedef struct ADLOD8SingleSetSetting\r\n{\r\n    int value;\r\n    int requested;      // 0 - default , 1 - requested\r\n    int reset;          // 0 - do not reset , 1 - reset setting back to default\r\n} ADLOD8SingleSetSetting;\r\n\r\ntypedef struct ADLOD8SetSetting\r\n{\r\n    int count;\r\n    ADLOD8SingleSetSetting  od8SettingTable[OD8_COUNT];\r\n} ADLOD8SetSetting;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Performance Metrics data\r\n///\r\n/// This structure is used to store information about Performance Metrics data output\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLSingleSensorData\r\n{\r\n    int supported;\r\n    int  value;\r\n} ADLSingleSensorData;\r\n\r\ntypedef struct ADLPMLogDataOutput\r\n{\r\n    int size;\r\n    ADLSingleSensorData sensors[ADL_PMLOG_MAX_SENSORS];\r\n}ADLPMLogDataOutput;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about PPLog settings.\r\n///\r\n/// This structure is used to store information about PPLog settings.\r\n/// This structure is used by the ADL2_PPLogSettings_Set() and ADL2_PPLogSettings_Get() functions.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLPPLogSettings\r\n{\r\n    int BreakOnAssert;\r\n    int BreakOnWarn;\r\n    int LogEnabled;\r\n    int LogFieldMask;\r\n    int LogDestinations;\r\n    int LogSeverityEnabled;\r\n    int LogSourceMask;\r\n    int PowerProfilingEnabled;\r\n    int PowerProfilingTimeInterval;\r\n}ADLPPLogSettings;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information related Frames Per Second for AC and DC.\r\n///\r\n/// This structure is used to store information related AC and DC Frames Per Second settings\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLFPSSettingsOutput\r\n{\r\n    /// size\r\n    int ulSize;\r\n    /// FPS Monitor is enabled in the AC state if 1\r\n    int bACFPSEnabled;\r\n    /// FPS Monitor is enabled in the DC state if 1\r\n    int bDCFPSEnabled;\r\n    /// Current Value of FPS Monitor in AC state\r\n    int ulACFPSCurrent;\r\n    /// Current Value of FPS Monitor in DC state\r\n    int ulDCFPSCurrent;\r\n    /// Maximum FPS Threshold allowed in PPLib for AC\r\n    int ulACFPSMaximum;\r\n    /// Minimum FPS Threshold allowed in PPLib for AC\r\n    int ulACFPSMinimum;\r\n    /// Maximum FPS Threshold allowed in PPLib for DC\r\n    int ulDCFPSMaximum;\r\n    /// Minimum FPS Threshold allowed in PPLib for DC\r\n    int ulDCFPSMinimum;\r\n} ADLFPSSettingsOutput;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information related Frames Per Second for AC and DC.\r\n///\r\n/// This structure is used to store information related AC and DC Frames Per Second settings\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLFPSSettingsInput\r\n{\r\n    /// size\r\n    int ulSize;\r\n    /// Settings are for Global FPS (used by CCC)\r\n    int bGlobalSettings;\r\n    /// Current Value of FPS Monitor in AC state\r\n    int ulACFPSCurrent;\r\n    /// Current Value of FPS Monitor in DC state\r\n    int ulDCFPSCurrent;\r\n    /// Reserved\r\n    int ulReserved[6];\r\n} ADLFPSSettingsInput;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information related power management logging.\r\n///\r\n/// This structure is used to store support information for power management logging.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\nenum { ADL_PMLOG_MAX_SUPPORTED_SENSORS = 256 };\r\n\r\ntypedef struct ADLPMLogSupportInfo\r\n{\r\n    /// list of sensors defined by ADL_PMLOG_SENSORS\r\n    unsigned short usSensors[ADL_PMLOG_MAX_SUPPORTED_SENSORS];\r\n    /// Reserved\r\n    int ulReserved[16];\r\n} ADLPMLogSupportInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information to start power management logging.\r\n///\r\n/// This structure is used as input to ADL2_Adapter_PMLog_Start\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLPMLogStartInput\r\n{\r\n    /// list of sensors defined by ADL_PMLOG_SENSORS\r\n    unsigned short usSensors[ADL_PMLOG_MAX_SUPPORTED_SENSORS];\r\n    /// Sample rate in milliseconds\r\n    unsigned long ulSampleRate;\r\n    /// Reserved\r\n    int ulReserved[15];\r\n} ADLPMLogStartInput;\r\n\r\ntypedef struct ADLPMLogData\r\n{\r\n    /// Structure version\r\n    unsigned int ulVersion;\r\n    /// Current driver sample rate\r\n    unsigned int ulActiveSampleRate;\r\n    /// Timestamp of last update\r\n    unsigned long long ulLastUpdated;\r\n    /// 2D array of senesor and values\r\n    unsigned int ulValues[ADL_PMLOG_MAX_SUPPORTED_SENSORS][2];\r\n    /// Reserved\r\n    unsigned int ulReserved[256];\r\n} ADLPMLogData;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information to start power management logging.\r\n///\r\n/// This structure is returned as output from ADL2_Adapter_PMLog_Start\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLPMLogStartOutput\r\n{\r\n    /// Pointer to memory address containing logging data\r\n    union\r\n    {\r\n        void* pLoggingAddress;\r\n        unsigned long long ptr_LoggingAddress;\r\n    };\r\n    /// Reserved\r\n    int ulReserved[14];\r\n} ADLPMLogStartOutput;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information to query limts of power management logging.\r\n///\r\n/// This structure is returned as output from ADL2_Adapter_PMLog_SensorLimits_Get\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLPMLogSensorLimits\r\n{\r\n    int SensorLimits[ADL_PMLOG_MAX_SENSORS][2]; //index 0: min, 1: max\r\n} ADLPMLogSensorLimits;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information related RAS Get Error Counts Information\r\n///\r\n/// This structure is used to store RAS Error Counts Get Input Information\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n\r\ntypedef struct ADLRASGetErrorCountsInput\r\n{\r\n    unsigned int                Reserved[16];\r\n} ADLRASGetErrorCountsInput;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information related RAS Get Error Counts Information\r\n///\r\n/// This structure is used to store RAS Error Counts Get Output Information\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n\r\ntypedef struct ADLRASGetErrorCountsOutput\r\n{\r\n    unsigned int                CorrectedErrors;    // includes both DRAM and SRAM ECC\r\n    unsigned int                UnCorrectedErrors;  // includes both DRAM and SRAM ECC\r\n    unsigned int                Reserved[14];\r\n} ADLRASGetErrorCountsOutput;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information related RAS Get Error Counts Information\r\n///\r\n/// This structure is used to store RAS Error Counts Get Information\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n\r\ntypedef struct ADLRASGetErrorCounts\r\n{\r\n    unsigned int                InputSize;\r\n    ADLRASGetErrorCountsInput   Input;\r\n    unsigned int                OutputSize;\r\n    ADLRASGetErrorCountsOutput  Output;\r\n} ADLRASGetErrorCounts;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information related RAS Error Counts Reset Information\r\n///\r\n/// This structure is used to store RAS Error Counts Reset Input Information\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n\r\ntypedef struct ADLRASResetErrorCountsInput\r\n{\r\n    unsigned int                Reserved[8];\r\n} ADLRASResetErrorCountsInput;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information related RAS Error Counts Reset Information\r\n///\r\n/// This structure is used to store RAS Error Counts Reset Output Information\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n\r\ntypedef struct ADLRASResetErrorCountsOutput\r\n{\r\n    unsigned int                Reserved[8];\r\n} ADLRASResetErrorCountsOutput;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information related RAS Error Counts Reset Information\r\n///\r\n/// This structure is used to store RAS Error Counts Reset Information\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n\r\ntypedef struct ADLRASResetErrorCounts\r\n{\r\n    unsigned int                    InputSize;\r\n    ADLRASResetErrorCountsInput     Input;\r\n    unsigned int                    OutputSize;\r\n    ADLRASResetErrorCountsOutput    Output;\r\n} ADLRASResetErrorCounts;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information related RAS Error Injection information\r\n///\r\n/// This structure is used to store RAS Error Injection input information\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n\r\ntypedef struct ADLRASErrorInjectonInput\r\n{\r\n    unsigned long long Address;\r\n    ADL_RAS_INJECTION_METHOD Value;\r\n    ADL_RAS_BLOCK_ID BlockId;\r\n    ADL_RAS_ERROR_TYPE InjectErrorType;\r\n    ADL_MEM_SUB_BLOCK_ID SubBlockIndex;\r\n    unsigned int padding[9];\r\n} ADLRASErrorInjectonInput;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information related RAS Error Injection information\r\n///\r\n/// This structure is used to store RAS Error Injection output information\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n\r\ntypedef struct ADLRASErrorInjectionOutput\r\n{\r\n    unsigned int ErrorInjectionStatus;\r\n    unsigned int padding[15];\r\n} ADLRASErrorInjectionOutput;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information related RAS Error Injection information\r\n///\r\n/// This structure is used to store RAS Error Injection information\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n\r\ntypedef struct ADLRASErrorInjection\r\n{\r\n    unsigned int                           InputSize;\r\n    ADLRASErrorInjectonInput               Input;\r\n    unsigned int                           OutputSize;\r\n    ADLRASErrorInjectionOutput             Output;\r\n} ADLRASErrorInjection;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about an application\r\n///\r\n/// This structure is used to store basic information of a recently ran or currently running application\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLSGApplicationInfo\r\n{\r\n    /// Application file name\r\n    wchar_t strFileName[ADL_MAX_PATH];\r\n    /// Application file path\r\n    wchar_t strFilePath[ADL_MAX_PATH];\r\n    /// Application version\r\n    wchar_t strVersion[ADL_MAX_PATH];\r\n    /// Timestamp at which application has run\r\n    long long int timeStamp;\r\n    /// Holds whether the applicaition profile exists or not\r\n    unsigned int iProfileExists;\r\n    /// The GPU on which application runs\r\n    unsigned int iGPUAffinity;\r\n    /// The BDF of the GPU on which application runs\r\n    ADLBdf GPUBdf;\r\n} ADLSGApplicationInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information related Frames Per Second for AC and DC.\r\n///\r\n/// This structure is used to store information related AC and DC Frames Per Second settings\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\nenum { ADLPreFlipPostProcessingInfoInvalidLUTIndex = 0xFFFFFFFF };\r\n\r\nenum ADLPreFlipPostProcessingLUTAlgorithm\r\n{\r\n    ADLPreFlipPostProcessingLUTAlgorithm_Default = 0,\r\n    ADLPreFlipPostProcessingLUTAlgorithm_Full,\r\n    ADLPreFlipPostProcessingLUTAlgorithm_Approximation\r\n};\r\n\r\ntypedef struct ADLPreFlipPostProcessingInfo\r\n{\r\n    /// size\r\n    int ulSize;\r\n    /// Current active state\r\n    int bEnabled;\r\n    /// Current selected LUT index.  0xFFFFFFF returned if nothing selected.\r\n    int ulSelectedLUTIndex;\r\n    /// Current selected LUT Algorithm\r\n    int ulSelectedLUTAlgorithm;\r\n    /// Reserved\r\n    int ulReserved[12];\r\n} ADLPreFlipPostProcessingInfo;\r\n\r\ntypedef struct ADL_ERROR_REASON\r\n{\r\n    int boost; //ON, when boost is Enabled\r\n    int delag; //ON, when delag is Enabled\r\n    int chill; //ON, when chill is Enabled\r\n    int proVsr; //ON, when proVsr is Enabled\r\n}ADL_ERROR_REASON;\r\n\r\ntypedef struct ADL_ERROR_REASON2\r\n{\r\n    int boost; //ON, when boost is Enabled\r\n    int delag; //ON, when delag is Enabled\r\n    int chill; //ON, when chill is Enabled\r\n    int proVsr; //ON, when proVsr is Enabled\r\n    int upscale; //ON, when RSR is Enabled\r\n}ADL_ERROR_REASON2;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about DELAG Settings change reason\r\n///\r\n///  Elements of DELAG settings changed reason.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_DELAG_NOTFICATION_REASON\r\n{\r\n\tint HotkeyChanged; //Set when Hotkey value is changed\r\n\tint GlobalEnableChanged; //Set when Global enable value is changed\r\n\tint GlobalLimitFPSChanged; //Set when Global enable value is changed\r\n}ADL_DELAG_NOTFICATION_REASON;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about DELAG Settings\r\n///\r\n///  Elements of DELAG settings.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_DELAG_SETTINGS\r\n{\r\n\tint Hotkey; // Hotkey value\r\n\tint GlobalEnable; //Global enable value\r\n\tint GlobalLimitFPS; //Global Limit FPS\r\n\tint GlobalLimitFPS_MinLimit; //Gloabl Limit FPS slider min limit value\r\n\tint GlobalLimitFPS_MaxLimit; //Gloabl Limit FPS slider max limit value\r\n\tint GlobalLimitFPS_Step; //Gloabl Limit FPS step  value\r\n}ADL_DELAG_SETTINGS;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about BOOST Settings change reason\r\n///\r\n///  Elements of BOOST settings changed reason.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_BOOST_NOTFICATION_REASON\r\n{\r\n\tint HotkeyChanged; //Set when Hotkey value is changed\r\n\tint GlobalEnableChanged; //Set when Global enable value is changed\r\n\tint GlobalMinResChanged; //Set when Global min resolution value is changed\r\n}ADL_BOOST_NOTFICATION_REASON;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about BOOST Settings\r\n///\r\n///  Elements of BOOST settings.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_BOOST_SETTINGS\r\n{\r\n\tint Hotkey; // Hotkey value\r\n\tint GlobalEnable; //Global enable value\r\n\tint GlobalMinRes; //Gloabl Min Resolution value\r\n\tint GlobalMinRes_MinLimit; //Gloabl Min Resolution slider min limit value\r\n\tint GlobalMinRes_MaxLimit; //Gloabl Min Resolution slider max limit value\r\n\tint GlobalMinRes_Step; //Gloabl Min Resolution step  value\r\n}ADL_BOOST_SETTINGS;\r\n\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about ProVSR Settings change reason\r\n///\r\n///  Elements of ProVSR settings changed reason.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_PROVSR_NOTFICATION_REASON\r\n{\r\n\tint HotkeyChanged; //Set when Hotkey value is changed\r\n\tint GlobalEnableChanged; //Set when Global enable value is changed\r\n}ADL_PROVSR_NOTFICATION_REASON;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Pro VSR Settings\r\n///\r\n///  Elements of ProVSR settings.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_PROVSR_SETTINGS\r\n{\r\n\tint Hotkey; // Hotkey value\r\n\tint GlobalEnable; //Global enable value\r\n}ADL_PROVSR_SETTINGS;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about Image Boost(OGL) Settings change reason\r\n///\r\n///  Elements of Image Boost settings changed reason.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_IMAGE_BOOST_NOTFICATION_REASON\r\n{\r\n    int HotkeyChanged; //Set when Hotkey value is changed\r\n    int GlobalEnableChanged; //Set when Global enable value is changed\r\n}ADL_IMAGE_BOOST_NOTFICATION_REASON;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about OGL IMAGE BOOST Settings\r\n///\r\n///  Elements of OGL IMAGE BOOST settings.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_IMAGE_BOOST_SETTINGS\r\n{\r\n    int Hotkey; // Hotkey value\r\n    int GlobalEnable; //Global enable value\r\n}ADL_IMAGE_BOOST_SETTINGS;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about RIS Settings change reason\r\n///\r\n///  Elements of RIS settings changed reason.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_RIS_NOTFICATION_REASON\r\n{\r\n\tunsigned int GlobalEnableChanged; //Set when Global enable value is changed\r\n\tunsigned int GlobalSharpeningDegreeChanged; //Set when Global sharpening Degree value is changed\r\n}ADL_RIS_NOTFICATION_REASON;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about RIS Settings\r\n///\r\n///  Elements of RIS settings.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_RIS_SETTINGS\r\n{\r\n\tint GlobalEnable; //Global enable value\r\n\tint GlobalSharpeningDegree; //Global sharpening value\r\n\tint GlobalSharpeningDegree_MinLimit; //Gloabl sharpening slider min limit value\r\n\tint GlobalSharpeningDegree_MaxLimit; //Gloabl sharpening slider max limit value\r\n\tint GlobalSharpeningDegree_Step; //Gloabl sharpening step  value\r\n}ADL_RIS_SETTINGS;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about CHILL Settings change reason\r\n///\r\n///  Elements of Chiil settings changed reason.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_CHILL_NOTFICATION_REASON\r\n{\r\n\tint HotkeyChanged; //Set when Hotkey value is changed\r\n\tint GlobalEnableChanged; //Set when Global enable value is changed\r\n\tint GlobalMinFPSChanged; //Set when Global min FPS value is changed\r\n\tint GlobalMaxFPSChanged; //Set when Global max FPS value is changed\r\n}ADL_CHILL_NOTFICATION_REASON;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about CHILL Settings\r\n///\r\n///  Elements of Chill settings.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_CHILL_SETTINGS\r\n{\r\n\tint Hotkey; // Hotkey value\r\n\tint GlobalEnable; //Global enable value\r\n\tint GlobalMinFPS; //Global Min FPS value\r\n\tint GlobalMaxFPS; //Global Max FPS value\r\n\tint GlobalFPS_MinLimit; //Gloabl FPS slider min limit value\r\n\tint GlobalFPS_MaxLimit; //Gloabl FPS slider max limit value\r\n\tint GlobalFPS_Step; //Gloabl FPS Slider step  value\r\n}ADL_CHILL_SETTINGS;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about DRIVERUPSCALE Settings change reason\r\n///\r\n///  Elements of DRIVERUPSCALE settings changed reason.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_DRIVERUPSCALE_NOTFICATION_REASON\r\n{\r\n    int ModeOverrideEnabledChanged;     //Set when Global min resolution value is changed\r\n    int GlobalEnabledChanged;           //Set when Global enable value is changed\r\n}ADL_DRIVERUPSCALE_NOTFICATION_REASON;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about DRIVERUPSCALE Settings\r\n///\r\n///  Elements of DRIVERUPSCALE settings.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_DRIVERUPSCALE_SETTINGS\r\n{\r\n    int ModeOverrideEnabled;\r\n    int GlobalEnabled;\r\n}ADL_DRIVERUPSCALE_SETTINGS;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief  Structure Containing R G B values for Radeon USB LED Bar\r\n///\r\n/// Elements of RGB Values.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_RADEON_LED_COLOR_CONFIG\r\n{\r\n\tunsigned short R : 8; // Red Value\r\n\tunsigned short G : 8; // Green Value\r\n\tunsigned short B : 8; // Blue Value\r\n}ADL_RADEON_LED_COLOR_CONFIG;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure Containing All Generic LED configuration for user requested LED pattern. The driver will apply the confgiuration as requested\r\n///\r\n///  Elements of Radeon USB LED configuration.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_RADEON_LED_PATTERN_CONFIG_GENERIC\r\n{\r\n\tshort brightness : 8; // Brightness of LED\r\n\tshort speed : 8; // Speed of LED pattern\r\n\tbool directionCounterClockWise; //Direction of LED Pattern\r\n\tADL_RADEON_LED_COLOR_CONFIG colorConfig; // RGB value of LED pattern\r\n\tchar morseCodeText[ADL_RADEON_LED_MAX_MORSE_CODE]; // Morse Code user input for Morse Code LED pattern\r\n\tchar morseCodeTextOutPut[ADL_RADEON_LED_MAX_MORSE_CODE]; // Driver set output representation of Morse Code\r\n\tint  morseCodeTextOutPutLen; // Length of Morse Code output\r\n}ADL_RADEON_LED_PATTERN_CONFIG_GENERIC;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure Containing All custom grid pattern LED configuration for user requested LED grid pattern. The driver will apply the confgiuration as requested\r\n///\r\n///  Elements of Radeon USB LED custom grid configuration.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_RADEON_LED_CUSTOM_LED_CONFIG\r\n{\r\n\tshort brightness : 8; // Brightness of LED\r\n\tADL_RADEON_LED_COLOR_CONFIG colorConfig[ADL_RADEON_LED_MAX_LED_ROW_ON_GRID][ADL_RADEON_LED_MAX_LED_COLUMN_ON_GRID]; // Full grid array representation of Radeon LED to be populated by user\r\n}ADL_RADEON_LED_CUSTOM_GRID_LED_CONFIG;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure Containing All Radeon USB LED requests and controls.\r\n///\r\n/// Elements of Radeon USB LED Controls.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_RADEON_LED_PATTERN_CONFIG\r\n{\r\n\tADL_RADEON_USB_LED_BAR_CONTROLS control; //Requested LED pattern\r\n\r\n    union\r\n    {\r\n\t\tADL_RADEON_LED_PATTERN_CONFIG_GENERIC genericPararmeters; //Requested pattern configuration settings\r\n\t\tADL_RADEON_LED_CUSTOM_GRID_LED_CONFIG customGridConfig; //Requested custom grid configuration settings\r\n    };\r\n}ADL_RADEON_LED_PATTERN_CONFIG;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about the graphics adapter with extended caps\r\n///\r\n/// This structure is used to store various information about the graphics adapter.  This\r\n/// information can be returned to the user. Alternatively, it can be used to access various driver calls to set\r\n/// or fetch various settings upon the user's request.\r\n/// This AdapterInfoX2 struct extends the AdapterInfo struct in adl_structures.h\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct AdapterInfoX2\r\n{\r\n    /// \\ALL_STRUCT_MEM\r\n\r\n    /// Size of the structure.\r\n    int iSize;\r\n    /// The ADL index handle. One GPU may be associated with one or two index handles\r\n    int iAdapterIndex;\r\n    /// The unique device ID associated with this adapter.\r\n    char strUDID[ADL_MAX_PATH];\r\n    /// The BUS number associated with this adapter.\r\n    int iBusNumber;\r\n    /// The driver number associated with this adapter.\r\n    int iDeviceNumber;\r\n    /// The function number.\r\n    int iFunctionNumber;\r\n    /// The vendor ID associated with this adapter.\r\n    int iVendorID;\r\n    /// Adapter name.\r\n    char strAdapterName[ADL_MAX_PATH];\r\n    /// Display name. For example, \"\\\\\\\\Display0\"\r\n    char strDisplayName[ADL_MAX_PATH];\r\n    /// Present or not; 1 if present and 0 if not present.It the logical adapter is present, the display name such as \\\\\\\\.\\\\Display1 can be found from OS\r\n    int iPresent;\r\n    /// Exist or not; 1 is exist and 0 is not present.\r\n    int iExist;\r\n    /// Driver registry path.\r\n    char strDriverPath[ADL_MAX_PATH];\r\n    /// Driver registry path Ext for.\r\n    char strDriverPathExt[ADL_MAX_PATH];\r\n    /// PNP string from Windows.\r\n    char strPNPString[ADL_MAX_PATH];\r\n    /// It is generated from EnumDisplayDevices.\r\n    int iOSDisplayIndex;\r\n    /// The bit mask identifies the adapter info\r\n    int iInfoMask;\r\n    /// The bit identifies the adapter info \\ref define_adapter_info\r\n    int iInfoValue;\r\n} AdapterInfoX2, *LPAdapterInfoX2;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about driver  gamut space , whether it is related to source or to destination, overlay or graphics\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n\r\ntypedef  struct ADLGamutReference\r\n{\r\n    /// mask whether it is related to source or to destination, overlay or graphics\r\n    int      iGamutRef;\r\n}ADLGamutReference;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about driver supported gamut spaces , capability method\r\n///\r\n/// This structure is used to get driver all supported gamut spaces\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n\r\ntypedef struct ADLGamutInfo\r\n{\r\n    ///Any combination of following ADL_GAMUT_SPACE_CCIR_709 - ADL_GAMUT_SPACE_CUSTOM\r\n    int    SupportedGamutSpace;\r\n\r\n    ///Any combination of following ADL_WHITE_POINT_5000K - ADL_WHITE_POINT_CUSTOM\r\n    int    SupportedWhitePoint;\r\n} ADLGamutInfo;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about driver point coordinates\r\n///\r\n/// This structure is used to store the driver point coodinates for gamut and white point\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n\r\ntypedef struct ADLPoint\r\n{\r\n    /// x coordinate\r\n    int          iX;\r\n    /// y coordinate\r\n    int          iY;\r\n} ADLPoint;\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about driver supported gamut coordinates\r\n///\r\n/// This structure is used to store the driver supported supported gamut coordinates\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n\r\ntypedef struct ADLGamutCoordinates\r\n{\r\n    /// red channel chromasity coordinate\r\n    ADLPoint      Red;\r\n    /// green channel chromasity coordinate\r\n    ADLPoint      Green;\r\n    /// blue channel chromasity coordinate\r\n    ADLPoint      Blue;\r\n} ADLGamutCoordinates;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about driver current gamut space , parent struct for ADLGamutCoordinates and ADLWhitePoint\r\n/// This structure is used to get/set driver supported gamut space\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n\r\ntypedef  struct ADLGamutData\r\n{\r\n    ///used as mask and could be 4 options\r\n    ///BIT_0 If flag ADL_GAMUT_REFERENCE_SOURCE is asserted set operation is related to gamut source ,\r\n    ///if not gamut destination\r\n    ///BIT_1 If flag ADL_GAMUT_GAMUT_VIDEO_CONTENT is asserted\r\n    ///BIT_2,BIT_3 used as mask and could be 4 options custom (2) + predefined (2)\r\n    ///0.  Gamut predefined,        white point predefined -> 0                | 0\r\n    ///1.  Gamut predefined,        white point custom     -> 0                | ADL_CUSTOM_WHITE_POINT\r\n    ///2.  White point predefined,  gamut custom           -> 0                | ADL_CUSTOM_GAMUT\r\n    ///3.  White point custom,      gamut custom           -> ADL_CUSTOM_GAMUT | ADL_CUSTOM_WHITE_POINT\r\n    int        iFeature;\r\n\r\n    ///one of ADL_GAMUT_SPACE_CCIR_709 - ADL_GAMUT_SPACE_CIE_RGB\r\n    int         iPredefinedGamut;\r\n\r\n    ///one of ADL_WHITE_POINT_5000K - ADL_WHITE_POINT_9300K\r\n    int         iPredefinedWhitePoint;\r\n\r\n    ///valid when in mask avails ADL_CUSTOM_WHITE_POINT\r\n    ADLPoint             CustomWhitePoint;\r\n\r\n    ///valid when in mask avails ADL_CUSTOM_GAMUT\r\n    ADLGamutCoordinates  CustomGamut;\r\n} ADLGamutData;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing detailed timing parameters.\r\n///\r\n/// This structure is used to store the detailed timing parameters.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLDetailedTimingX2\r\n{\r\n    /// Size of the structure.\r\n    int   iSize;\r\n    /// Timing flags. \\ref define_detailed_timing_flags\r\n    int sTimingFlags;\r\n    /// Total width (columns).\r\n    int sHTotal;\r\n    /// Displayed width.\r\n    int sHDisplay;\r\n    /// Horizontal sync signal offset.\r\n    int sHSyncStart;\r\n    /// Horizontal sync signal width.\r\n    int sHSyncWidth;\r\n    /// Total height (rows).\r\n    int sVTotal;\r\n    /// Displayed height.\r\n    int sVDisplay;\r\n    /// Vertical sync signal offset.\r\n    int sVSyncStart;\r\n    /// Vertical sync signal width.\r\n    int sVSyncWidth;\r\n    /// Pixel clock value.\r\n    int sPixelClock;\r\n    /// Overscan right.\r\n    short sHOverscanRight;\r\n    /// Overscan left.\r\n    short sHOverscanLeft;\r\n    /// Overscan bottom.\r\n    short sVOverscanBottom;\r\n    /// Overscan top.\r\n    short sVOverscanTop;\r\n    short sOverscan8B;\r\n    short sOverscanGR;\r\n} ADLDetailedTimingX2;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing display mode information.\r\n///\r\n/// This structure is used to store the display mode information.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLDisplayModeInfoX2\r\n{\r\n    /// Timing standard of the current mode. \\ref define_modetiming_standard\r\n    int  iTimingStandard;\r\n    /// Applicable timing standards for the current mode.\r\n    int  iPossibleStandard;\r\n    /// Refresh rate factor.\r\n    int  iRefreshRate;\r\n    /// Num of pixels in a row.\r\n    int  iPelsWidth;\r\n    /// Num of pixels in a column.\r\n    int  iPelsHeight;\r\n    /// Detailed timing parameters.\r\n    ADLDetailedTimingX2  sDetailedTiming;\r\n} ADLDisplayModeInfoX2;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about I2C.\r\n///\r\n/// This structure is used to store the I2C information for the current adapter.\r\n/// This structure is used by \\ref ADL_Display_WriteAndReadI2CLargePayload\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLI2CLargePayload\r\n{\r\n    /// Size of the structure\r\n    int iSize;\r\n    /// Numerical value representing hardware I2C.\r\n    int iLine;\r\n    /// The 7-bit I2C slave device address.\r\n    int iAddress;\r\n    /// The offset of the data from the address.\r\n    int iOffset;\r\n    /// Read from or write to slave device. \\ref ADL_DL_I2C_ACTIONREAD or \\ref ADL_DL_I2C_ACTIONWRITE\r\n    int iAction;\r\n    /// I2C clock speed in KHz.\r\n    int iSpeed;\r\n    /// I2C option flags.  \\ref define_ADLI2CLargePayload\r\n    int iFlags;\r\n    /// A numerical value representing the number of bytes to be sent or received on the I2C bus.\r\n    int iDataSize;\r\n    /// Address of the characters which are to be sent or received on the I2C bus.\r\n    char *pcData;\r\n} ADLI2CLargePayload;\r\n\r\n/// Size in bytes of the Feature Name\r\n#define ADL_FEATURE_NAME_LENGTH \t16\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing the Multimedia Feature Name\r\n///\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLFeatureName\r\n{\r\n    /// The Feature Name\r\n    char FeatureName[ADL_FEATURE_NAME_LENGTH];\r\n}\tADLFeatureName, *LPADLFeatureName;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about MM Feature Capabilities.\r\n///\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLFeatureCaps\r\n{\r\n    /// The Feature Name\r\n    ADLFeatureName\tName;\r\n    //\tchar strFeatureName[ADL_FEATURE_NAME_LENGTH];\r\n\r\n    /// Group ID. All Features in the same group are shown sequentially in the same UI Page.\r\n    int  iGroupID;\r\n\r\n    /// Visual ID. Places one or more features in a Group Box. If zero, no Group Box is added.\r\n    int  iVisualID;\r\n\r\n    /// Page ID. All Features with the same Page ID value are shown together on the same UI page.\r\n    int iPageID;\r\n\r\n    /// Feature Property Mask. Indicates which are the valid bits for iFeatureProperties.\r\n    int iFeatureMask;\r\n\r\n    /// Feature Property Values. See definitions for ADL_FEATURE_PROPERTIES_XXX\r\n    int  iFeatureProperties;\r\n\r\n    /// Apperance of the User-Controlled Boolean.\r\n    int  iControlType;\r\n\r\n    /// Style of the User-Controlled Boolean.\r\n    int  iControlStyle;\r\n\r\n    /// Apperance of the Adjustment Controls.\r\n    int  iAdjustmentType;\r\n\r\n    /// Style of the Adjustment Controls.\r\n    int  iAdjustmentStyle;\r\n\r\n    /// Default user-controlled boolean value. Valid only if ADLFeatureCaps supports user-controlled boolean.\r\n    int bDefault;\r\n\r\n    /// Minimum integer value. Valid only if ADLFeatureCaps indicates support for integers.\r\n    int iMin;\r\n\r\n    /// Maximum integer value. Valid only if ADLFeatureCaps indicates support for integers.\r\n    int iMax;\r\n\r\n    /// Step integer value. Valid only if ADLFeatureCaps indicates support for integers.\r\n    int iStep;\r\n\r\n    /// Default integer value. Valid only if ADLFeatureCaps indicates support for integers.\r\n    int iDefault;\r\n\r\n    /// Minimum float value. Valid only if ADLFeatureCaps indicates support for floats.\r\n    float fMin;\r\n\r\n    /// Maximum float value. Valid only if ADLFeatureCaps indicates support for floats.\r\n    float fMax;\r\n\r\n    /// Step float value. Valid only if ADLFeatureCaps indicates support for floats.\r\n    float fStep;\r\n\r\n    /// Default float value. Valid only if ADLFeatureCaps indicates support for floats.\r\n    float fDefault;\r\n\r\n    /// The Mask for available bits for enumerated values.(If ADLFeatureCaps supports ENUM values)\r\n    int EnumMask;\r\n} ADLFeatureCaps, *LPADLFeatureCaps;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about MM Feature Values.\r\n///\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLFeatureValues\r\n{\r\n    /// The Feature Name\r\n    ADLFeatureName\tName;\r\n    //\tchar strFeatureName[ADL_FEATURE_NAME_LENGTH];\r\n\r\n    /// User controlled Boolean current value. Valid only if ADLFeatureCaps supports Boolean.\r\n    int bCurrent;\r\n\r\n    /// Current integer value. Valid only if ADLFeatureCaps indicates support for integers.\r\n    int iCurrent;\r\n\r\n    /// Current float value. Valid only if ADLFeatureCaps indicates support for floats.\r\n    float fCurrent;\r\n\r\n    /// The States for the available bits for enumerated values.\r\n    int EnumStates;\r\n} ADLFeatureValues, *LPADLFeatureValues;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing HDCP Settings info\r\n///\r\n/// This structure is used to store the HDCP settings of a\r\n/// display\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n\r\ntypedef struct ADLHDCPSettings\r\n{\r\n\tint iHDCPProtectionVersion; // Version, starting from 1\r\n\tint iHDCPCaps; //Caps used to ensure at least one protection scheme is supported, 1 is HDCP1X and 2 is HDCP22\r\n\tint iAllowAll; //Allow all is true, disable all is false\r\n\tint iHDCPVale;\r\n\tint iHDCPMask;\r\n} ADLHDCPSettings;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing Mantle App  info\r\n///\r\n/// This structure is used to store the Mantle Driver information\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n\r\ntypedef struct ADLMantleAppInfo\r\n{\r\n\t/// mantle api version\r\n\tint   apiVersion;\r\n\t/// mantle driver version\r\n\tlong   driverVersion;\r\n\t/// mantle vendroe id\r\n\tlong   vendorId;\r\n\t/// mantle device id\r\n\tlong   deviceId;\r\n\t/// mantle gpu type;\r\n\tint     gpuType;\r\n\t/// gpu name\r\n\tchar     gpuName[256];\r\n\t/// mem size\r\n\tint     maxMemRefsPerSubmission;\r\n\t/// virtual mem size\r\n\tlong long virtualMemPageSize;\r\n\t/// mem update\r\n\tlong long maxInlineMemoryUpdateSize;\r\n\t/// bound descriptot\r\n\tlong     maxBoundDescriptorSets;\r\n\t/// thread group size\r\n\tlong     maxThreadGroupSize;\r\n\t/// time stamp frequency\r\n\tlong  long timestampFrequency;\r\n\t/// color target\r\n\tlong     multiColorTargetClears;\r\n}ADLMantleAppInfo, *LPADLMantleAppInfo;\r\n\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about SDIData\r\n///This structure is used to store information about the state of the SDI whether it is on\r\n///or off and the current size of the segment or aperture size.\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLSDIData\r\n{\r\n\t/// The SDI state, ADL_SDI_ON or ADL_SDI_OFF, for the current SDI mode\r\n\tint iSDIState;\r\n\t/// Size of the memory segment for SDI (in MB).\r\n\tint iSizeofSDISegment;\r\n} ADLSDIData, *LPADLSDIData;\r\n\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about FRTCPRO Settings\r\n///\r\n///  Elements of FRTCPRO settings.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_FRTCPRO_Settings\r\n{\r\n    int DefaultState;              //The default status for FRTC pro\r\n    int CurrentState;              //The current enable/disable status for FRTC pro\r\n    unsigned int DefaultValue;     //The default FPS value for FRTC pro.\r\n    unsigned int CurrentValue;      //The current FPS value for FRTC pro.\r\n    unsigned int maxSupportedFps;      //The max value for FRTC pro.\r\n    unsigned int minSupportedFps;      //The min value for FRTC pro.\r\n}ADL_FRTCPRO_Settings, *LPADLFRTCProSettings;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information about FRTCPRO Settings changed reason\r\n///\r\n///  Reason of FRTCPRO changed.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_FRTCPRO_CHANGED_REASON\r\n{\r\n    int StateChanged;               // FRTCPro state changed\r\n    int ValueChanged;               // FRTCPro value changed\r\n}ADL_FRTCPRO_CHANGED_REASON;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n/// \\brief Structure containing the display mode definition used per controller.\r\n///\r\n/// This structure is used to store the display mode definition used per controller.\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADL_DL_DISPLAY_MODE\r\n{\r\n    int  iPelsHeight;                      // Vertical resolution (in pixels).\r\n    int  iPelsWidth;                       // Horizontal resolution (in pixels).\r\n    int  iBitsPerPel;                      // Color depth.\r\n    int  iDisplayFrequency;                // Refresh rate.\r\n} ADL_DL_DISPLAY_MODE;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n///\\brief Structure containing information related DCE support\r\n///\r\n/// This structure is used to store a bit vector of possible DCE support\r\n///\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef union _ADLDCESupport\r\n{\r\n    struct\r\n    {\r\n        unsigned int PrePhasis : 1;\r\n        unsigned int voltageSwing : 1;\r\n        unsigned int reserved : 30;\r\n    }bits;\r\n    unsigned int u32All;\r\n}ADLDCESupport;\r\n\r\n/////////////////////////////////////////////////////////////////////////////////////////////\r\n/// \\brief Structure for Smart shift 2.0 settings\r\n///\r\n/// This structure is used to return the smart shift settings\r\n/// \\nosubgrouping\r\n////////////////////////////////////////////////////////////////////////////////////////////\r\ntypedef struct ADLSmartShiftSettings\r\n{\r\n\tint iMinRange;\r\n\tint iMaxRange;\r\n\tint iDefaultMode; //Refer to CWDDEPM_ODN_CONTROL_TYPE\r\n\tint iDefaultValue;\r\n\tint iCurrentMode;\r\n\tint iCurrentValue;\r\n    int iFlags; //refer to define_smartshift_bits\r\n}ADLSmartShiftSettings, *LPADLSmartShiftSettings;\r\n#endif /* ADL_STRUCTURES_H_ */\r\n"
  },
  {
    "path": "src/3rdparty/display-library/repo.json",
    "content": "{\n    \"home\": \"https://github.com/GPUOpen-LibrariesAndSDKs/display-library\",\n    \"license\": \"MIT (embeded in source)\",\n    \"version\": \"ADL SDK 17.1\",\n    \"author\": \"Advanced Micro Devices, Inc\"\n}\n"
  },
  {
    "path": "src/3rdparty/widecharwidth/repo.json",
    "content": "{\n    \"home\": \"https://github.com/ridiculousfish/widecharwidth\",\n    \"license\": \"Public domain\",\n    \"version\": \"Unicode 17\",\n    \"author\": \"ridiculousfish\"\n}\n"
  },
  {
    "path": "src/3rdparty/widecharwidth/widechar_width_c.h",
    "content": "/**\n * widechar_width_c.h for Unicode 17.0.0\n * See https://github.com/ridiculousfish/widecharwidth/\n *\n * SHA1 file hashes:\n *  (\n *  the hashes for generate.py and the template are git object hashes,\n *  use `git log --all --find-object=<hash>` in the widecharwidth repository\n *  to see which commit they correspond to,\n *  or run `git hash-object` on the file to compare.\n *  The other hashes are simple `sha1sum` style hashes.\n *  )\n *\n *  generate.py:         b35da43f176cc0d5880c67356ebb064048c5bac4\n *  template.js:         1985fb56796d6d9627f9c5290d5dee9f9364bcf4\n *  UnicodeData.txt:     50dffef1b7d1f97b72e4c2adceb9b2245f0f34ba\n *  EastAsianWidth.txt:  2cadc5034b6206ad84b75898a1d4186bb38fc12b\n *  emoji-data.txt:      3d123e12f70f63e609c4281ce83dfdd9ac7443d2\n */\n\n#ifndef WIDECHAR_WIDTH_H\n#define WIDECHAR_WIDTH_H\n\n#include <stdlib.h>\n#include <stdint.h>\n#include <stdbool.h>\n\n#ifndef widechar_ARRAY_SIZE\n#define widechar_ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))\n#endif\n\n/* Special width values */\nenum {\n  widechar_nonprint = -1,     // The character is not printable.\n  widechar_combining = -2,    // The character is a zero-width combiner.\n  widechar_ambiguous = -3,    // The character is East-Asian ambiguous width.\n  widechar_private_use = -4,  // The character is for private use.\n  widechar_unassigned = -5,   // The character is unassigned.\n  widechar_widened_in_9 = -6, // Width is 1 in Unicode 8, 2 in Unicode 9+.\n  widechar_non_character = -7 // The character is a noncharacter.\n};\n\n/* An inclusive range of characters. */\nstruct widechar_range {\n  uint32_t lo;\n  uint32_t hi;\n};\n\n/* Simple ASCII characters - used a lot, so we check them first. */\nstatic const struct widechar_range widechar_ascii_table[] = {\n    {0x00020, 0x0007E}\n};\n\n/* Private usage range. */\nstatic const struct widechar_range widechar_private_table[] = {\n    {0x0E000, 0x0F8FF},\n    {0xF0000, 0xFFFFD},\n    {0x100000, 0x10FFFD}\n};\n\n/* Nonprinting characters. */\nstatic const struct widechar_range widechar_nonprint_table[] = {\n    {0x00000, 0x0001F},\n    {0x0007F, 0x0009F},\n    {0x000AD, 0x000AD},\n    {0x00600, 0x00605},\n    {0x0061C, 0x0061C},\n    {0x006DD, 0x006DD},\n    {0x0070F, 0x0070F},\n    {0x00890, 0x00891},\n    {0x008E2, 0x008E2},\n    {0x0180E, 0x0180E},\n    {0x0200B, 0x0200F},\n    {0x02028, 0x0202E},\n    {0x02060, 0x02064},\n    {0x02066, 0x0206F},\n    {0x0D800, 0x0DFFF},\n    {0x0FEFF, 0x0FEFF},\n    {0x0FFF9, 0x0FFFB},\n    {0x110BD, 0x110BD},\n    {0x110CD, 0x110CD},\n    {0x13430, 0x1343F},\n    {0x1BCA0, 0x1BCA3},\n    {0x1D173, 0x1D17A},\n    {0xE0001, 0xE0001},\n    {0xE0020, 0xE007F}\n};\n\n/* Width 0 combining marks. */\nstatic const struct widechar_range widechar_combining_table[] = {\n    {0x00300, 0x0036F},\n    {0x00483, 0x00489},\n    {0x00591, 0x005BD},\n    {0x005BF, 0x005BF},\n    {0x005C1, 0x005C2},\n    {0x005C4, 0x005C5},\n    {0x005C7, 0x005C7},\n    {0x00610, 0x0061A},\n    {0x0064B, 0x0065F},\n    {0x00670, 0x00670},\n    {0x006D6, 0x006DC},\n    {0x006DF, 0x006E4},\n    {0x006E7, 0x006E8},\n    {0x006EA, 0x006ED},\n    {0x00711, 0x00711},\n    {0x00730, 0x0074A},\n    {0x007A6, 0x007B0},\n    {0x007EB, 0x007F3},\n    {0x007FD, 0x007FD},\n    {0x00816, 0x00819},\n    {0x0081B, 0x00823},\n    {0x00825, 0x00827},\n    {0x00829, 0x0082D},\n    {0x00859, 0x0085B},\n    {0x00897, 0x0089F},\n    {0x008CA, 0x008E1},\n    {0x008E3, 0x00903},\n    {0x0093A, 0x0093C},\n    {0x0093E, 0x0094F},\n    {0x00951, 0x00957},\n    {0x00962, 0x00963},\n    {0x00981, 0x00983},\n    {0x009BC, 0x009BC},\n    {0x009BE, 0x009C4},\n    {0x009C7, 0x009C8},\n    {0x009CB, 0x009CD},\n    {0x009D7, 0x009D7},\n    {0x009E2, 0x009E3},\n    {0x009FE, 0x009FE},\n    {0x00A01, 0x00A03},\n    {0x00A3C, 0x00A3C},\n    {0x00A3E, 0x00A42},\n    {0x00A47, 0x00A48},\n    {0x00A4B, 0x00A4D},\n    {0x00A51, 0x00A51},\n    {0x00A70, 0x00A71},\n    {0x00A75, 0x00A75},\n    {0x00A81, 0x00A83},\n    {0x00ABC, 0x00ABC},\n    {0x00ABE, 0x00AC5},\n    {0x00AC7, 0x00AC9},\n    {0x00ACB, 0x00ACD},\n    {0x00AE2, 0x00AE3},\n    {0x00AFA, 0x00AFF},\n    {0x00B01, 0x00B03},\n    {0x00B3C, 0x00B3C},\n    {0x00B3E, 0x00B44},\n    {0x00B47, 0x00B48},\n    {0x00B4B, 0x00B4D},\n    {0x00B55, 0x00B57},\n    {0x00B62, 0x00B63},\n    {0x00B82, 0x00B82},\n    {0x00BBE, 0x00BC2},\n    {0x00BC6, 0x00BC8},\n    {0x00BCA, 0x00BCD},\n    {0x00BD7, 0x00BD7},\n    {0x00C00, 0x00C04},\n    {0x00C3C, 0x00C3C},\n    {0x00C3E, 0x00C44},\n    {0x00C46, 0x00C48},\n    {0x00C4A, 0x00C4D},\n    {0x00C55, 0x00C56},\n    {0x00C62, 0x00C63},\n    {0x00C81, 0x00C83},\n    {0x00CBC, 0x00CBC},\n    {0x00CBE, 0x00CC4},\n    {0x00CC6, 0x00CC8},\n    {0x00CCA, 0x00CCD},\n    {0x00CD5, 0x00CD6},\n    {0x00CE2, 0x00CE3},\n    {0x00CF3, 0x00CF3},\n    {0x00D00, 0x00D03},\n    {0x00D3B, 0x00D3C},\n    {0x00D3E, 0x00D44},\n    {0x00D46, 0x00D48},\n    {0x00D4A, 0x00D4D},\n    {0x00D57, 0x00D57},\n    {0x00D62, 0x00D63},\n    {0x00D81, 0x00D83},\n    {0x00DCA, 0x00DCA},\n    {0x00DCF, 0x00DD4},\n    {0x00DD6, 0x00DD6},\n    {0x00DD8, 0x00DDF},\n    {0x00DF2, 0x00DF3},\n    {0x00E31, 0x00E31},\n    {0x00E34, 0x00E3A},\n    {0x00E47, 0x00E4E},\n    {0x00EB1, 0x00EB1},\n    {0x00EB4, 0x00EBC},\n    {0x00EC8, 0x00ECE},\n    {0x00F18, 0x00F19},\n    {0x00F35, 0x00F35},\n    {0x00F37, 0x00F37},\n    {0x00F39, 0x00F39},\n    {0x00F3E, 0x00F3F},\n    {0x00F71, 0x00F84},\n    {0x00F86, 0x00F87},\n    {0x00F8D, 0x00F97},\n    {0x00F99, 0x00FBC},\n    {0x00FC6, 0x00FC6},\n    {0x0102B, 0x0103E},\n    {0x01056, 0x01059},\n    {0x0105E, 0x01060},\n    {0x01062, 0x01064},\n    {0x01067, 0x0106D},\n    {0x01071, 0x01074},\n    {0x01082, 0x0108D},\n    {0x0108F, 0x0108F},\n    {0x0109A, 0x0109D},\n    {0x0135D, 0x0135F},\n    {0x01712, 0x01715},\n    {0x01732, 0x01734},\n    {0x01752, 0x01753},\n    {0x01772, 0x01773},\n    {0x017B4, 0x017D3},\n    {0x017DD, 0x017DD},\n    {0x0180B, 0x0180D},\n    {0x0180F, 0x0180F},\n    {0x01885, 0x01886},\n    {0x018A9, 0x018A9},\n    {0x01920, 0x0192B},\n    {0x01930, 0x0193B},\n    {0x01A17, 0x01A1B},\n    {0x01A55, 0x01A5E},\n    {0x01A60, 0x01A7C},\n    {0x01A7F, 0x01A7F},\n    {0x01AB0, 0x01ADD},\n    {0x01AE0, 0x01AEB},\n    {0x01B00, 0x01B04},\n    {0x01B34, 0x01B44},\n    {0x01B6B, 0x01B73},\n    {0x01B80, 0x01B82},\n    {0x01BA1, 0x01BAD},\n    {0x01BE6, 0x01BF3},\n    {0x01C24, 0x01C37},\n    {0x01CD0, 0x01CD2},\n    {0x01CD4, 0x01CE8},\n    {0x01CED, 0x01CED},\n    {0x01CF4, 0x01CF4},\n    {0x01CF7, 0x01CF9},\n    {0x01DC0, 0x01DFF},\n    {0x020D0, 0x020F0},\n    {0x02CEF, 0x02CF1},\n    {0x02D7F, 0x02D7F},\n    {0x02DE0, 0x02DFF},\n    {0x0302A, 0x0302F},\n    {0x03099, 0x0309A},\n    {0x0A66F, 0x0A672},\n    {0x0A674, 0x0A67D},\n    {0x0A69E, 0x0A69F},\n    {0x0A6F0, 0x0A6F1},\n    {0x0A802, 0x0A802},\n    {0x0A806, 0x0A806},\n    {0x0A80B, 0x0A80B},\n    {0x0A823, 0x0A827},\n    {0x0A82C, 0x0A82C},\n    {0x0A880, 0x0A881},\n    {0x0A8B4, 0x0A8C5},\n    {0x0A8E0, 0x0A8F1},\n    {0x0A8FF, 0x0A8FF},\n    {0x0A926, 0x0A92D},\n    {0x0A947, 0x0A953},\n    {0x0A980, 0x0A983},\n    {0x0A9B3, 0x0A9C0},\n    {0x0A9E5, 0x0A9E5},\n    {0x0AA29, 0x0AA36},\n    {0x0AA43, 0x0AA43},\n    {0x0AA4C, 0x0AA4D},\n    {0x0AA7B, 0x0AA7D},\n    {0x0AAB0, 0x0AAB0},\n    {0x0AAB2, 0x0AAB4},\n    {0x0AAB7, 0x0AAB8},\n    {0x0AABE, 0x0AABF},\n    {0x0AAC1, 0x0AAC1},\n    {0x0AAEB, 0x0AAEF},\n    {0x0AAF5, 0x0AAF6},\n    {0x0ABE3, 0x0ABEA},\n    {0x0ABEC, 0x0ABED},\n    {0x0FB1E, 0x0FB1E},\n    {0x0FE00, 0x0FE0F},\n    {0x0FE20, 0x0FE2F},\n    {0x101FD, 0x101FD},\n    {0x102E0, 0x102E0},\n    {0x10376, 0x1037A},\n    {0x10A01, 0x10A03},\n    {0x10A05, 0x10A06},\n    {0x10A0C, 0x10A0F},\n    {0x10A38, 0x10A3A},\n    {0x10A3F, 0x10A3F},\n    {0x10AE5, 0x10AE6},\n    {0x10D24, 0x10D27},\n    {0x10D69, 0x10D6D},\n    {0x10EAB, 0x10EAC},\n    {0x10EFA, 0x10EFF},\n    {0x10F46, 0x10F50},\n    {0x10F82, 0x10F85},\n    {0x11000, 0x11002},\n    {0x11038, 0x11046},\n    {0x11070, 0x11070},\n    {0x11073, 0x11074},\n    {0x1107F, 0x11082},\n    {0x110B0, 0x110BA},\n    {0x110C2, 0x110C2},\n    {0x11100, 0x11102},\n    {0x11127, 0x11134},\n    {0x11145, 0x11146},\n    {0x11173, 0x11173},\n    {0x11180, 0x11182},\n    {0x111B3, 0x111C0},\n    {0x111C9, 0x111CC},\n    {0x111CE, 0x111CF},\n    {0x1122C, 0x11237},\n    {0x1123E, 0x1123E},\n    {0x11241, 0x11241},\n    {0x112DF, 0x112EA},\n    {0x11300, 0x11303},\n    {0x1133B, 0x1133C},\n    {0x1133E, 0x11344},\n    {0x11347, 0x11348},\n    {0x1134B, 0x1134D},\n    {0x11357, 0x11357},\n    {0x11362, 0x11363},\n    {0x11366, 0x1136C},\n    {0x11370, 0x11374},\n    {0x113B8, 0x113C0},\n    {0x113C2, 0x113C2},\n    {0x113C5, 0x113C5},\n    {0x113C7, 0x113CA},\n    {0x113CC, 0x113D0},\n    {0x113D2, 0x113D2},\n    {0x113E1, 0x113E2},\n    {0x11435, 0x11446},\n    {0x1145E, 0x1145E},\n    {0x114B0, 0x114C3},\n    {0x115AF, 0x115B5},\n    {0x115B8, 0x115C0},\n    {0x115DC, 0x115DD},\n    {0x11630, 0x11640},\n    {0x116AB, 0x116B7},\n    {0x1171D, 0x1172B},\n    {0x1182C, 0x1183A},\n    {0x11930, 0x11935},\n    {0x11937, 0x11938},\n    {0x1193B, 0x1193E},\n    {0x11940, 0x11940},\n    {0x11942, 0x11943},\n    {0x119D1, 0x119D7},\n    {0x119DA, 0x119E0},\n    {0x119E4, 0x119E4},\n    {0x11A01, 0x11A0A},\n    {0x11A33, 0x11A39},\n    {0x11A3B, 0x11A3E},\n    {0x11A47, 0x11A47},\n    {0x11A51, 0x11A5B},\n    {0x11A8A, 0x11A99},\n    {0x11B60, 0x11B67},\n    {0x11C2F, 0x11C36},\n    {0x11C38, 0x11C3F},\n    {0x11C92, 0x11CA7},\n    {0x11CA9, 0x11CB6},\n    {0x11D31, 0x11D36},\n    {0x11D3A, 0x11D3A},\n    {0x11D3C, 0x11D3D},\n    {0x11D3F, 0x11D45},\n    {0x11D47, 0x11D47},\n    {0x11D8A, 0x11D8E},\n    {0x11D90, 0x11D91},\n    {0x11D93, 0x11D97},\n    {0x11EF3, 0x11EF6},\n    {0x11F00, 0x11F01},\n    {0x11F03, 0x11F03},\n    {0x11F34, 0x11F3A},\n    {0x11F3E, 0x11F42},\n    {0x11F5A, 0x11F5A},\n    {0x13440, 0x13440},\n    {0x13447, 0x13455},\n    {0x1611E, 0x1612F},\n    {0x16AF0, 0x16AF4},\n    {0x16B30, 0x16B36},\n    {0x16F4F, 0x16F4F},\n    {0x16F51, 0x16F87},\n    {0x16F8F, 0x16F92},\n    {0x16FE4, 0x16FE4},\n    {0x16FF0, 0x16FF1},\n    {0x1BC9D, 0x1BC9E},\n    {0x1CF00, 0x1CF2D},\n    {0x1CF30, 0x1CF46},\n    {0x1D165, 0x1D169},\n    {0x1D16D, 0x1D172},\n    {0x1D17B, 0x1D182},\n    {0x1D185, 0x1D18B},\n    {0x1D1AA, 0x1D1AD},\n    {0x1D242, 0x1D244},\n    {0x1DA00, 0x1DA36},\n    {0x1DA3B, 0x1DA6C},\n    {0x1DA75, 0x1DA75},\n    {0x1DA84, 0x1DA84},\n    {0x1DA9B, 0x1DA9F},\n    {0x1DAA1, 0x1DAAF},\n    {0x1E000, 0x1E006},\n    {0x1E008, 0x1E018},\n    {0x1E01B, 0x1E021},\n    {0x1E023, 0x1E024},\n    {0x1E026, 0x1E02A},\n    {0x1E08F, 0x1E08F},\n    {0x1E130, 0x1E136},\n    {0x1E2AE, 0x1E2AE},\n    {0x1E2EC, 0x1E2EF},\n    {0x1E4EC, 0x1E4EF},\n    {0x1E5EE, 0x1E5EF},\n    {0x1E6E3, 0x1E6E3},\n    {0x1E6E6, 0x1E6E6},\n    {0x1E6EE, 0x1E6EF},\n    {0x1E6F5, 0x1E6F5},\n    {0x1E8D0, 0x1E8D6},\n    {0x1E944, 0x1E94A},\n    {0xE0100, 0xE01EF}\n};\n\n/* Width 0 combining letters. */\nstatic const struct widechar_range widechar_combiningletters_table[] = {\n    {0x01160, 0x011FF},\n    {0x0D7B0, 0x0D7FF}\n};\n\n/* Width 2 characters. */\nstatic const struct widechar_range widechar_doublewide_table[] = {\n    {0x01100, 0x0115F},\n    {0x02329, 0x0232A},\n    {0x02630, 0x02637},\n    {0x0268A, 0x0268F},\n    {0x02E80, 0x02E99},\n    {0x02E9B, 0x02EF3},\n    {0x02F00, 0x02FD5},\n    {0x02FF0, 0x0303E},\n    {0x03041, 0x03096},\n    {0x03099, 0x030FF},\n    {0x03105, 0x0312F},\n    {0x03131, 0x0318E},\n    {0x03190, 0x031E5},\n    {0x031EF, 0x0321E},\n    {0x03220, 0x03247},\n    {0x03250, 0x0A48C},\n    {0x0A490, 0x0A4C6},\n    {0x0A960, 0x0A97C},\n    {0x0AC00, 0x0D7A3},\n    {0x0F900, 0x0FAFF},\n    {0x0FE10, 0x0FE19},\n    {0x0FE30, 0x0FE52},\n    {0x0FE54, 0x0FE66},\n    {0x0FE68, 0x0FE6B},\n    {0x0FF01, 0x0FF60},\n    {0x0FFE0, 0x0FFE6},\n    {0x16FE0, 0x16FE4},\n    {0x16FF0, 0x16FF6},\n    {0x17000, 0x18CD5},\n    {0x18CFF, 0x18D1E},\n    {0x18D80, 0x18DF2},\n    {0x1AFF0, 0x1AFF3},\n    {0x1AFF5, 0x1AFFB},\n    {0x1AFFD, 0x1AFFE},\n    {0x1B000, 0x1B122},\n    {0x1B132, 0x1B132},\n    {0x1B150, 0x1B152},\n    {0x1B155, 0x1B155},\n    {0x1B164, 0x1B167},\n    {0x1B170, 0x1B2FB},\n    {0x1D300, 0x1D356},\n    {0x1D360, 0x1D376},\n    {0x1F200, 0x1F200},\n    {0x1F202, 0x1F202},\n    {0x1F210, 0x1F219},\n    {0x1F21B, 0x1F22E},\n    {0x1F230, 0x1F231},\n    {0x1F237, 0x1F237},\n    {0x1F23B, 0x1F23B},\n    {0x1F240, 0x1F248},\n    {0x1F260, 0x1F265},\n    {0x1F57A, 0x1F57A},\n    {0x1F5A4, 0x1F5A4},\n    {0x1F6D1, 0x1F6D2},\n    {0x1F6D5, 0x1F6D8},\n    {0x1F6DC, 0x1F6DF},\n    {0x1F6F4, 0x1F6FC},\n    {0x1F7E0, 0x1F7EB},\n    {0x1F7F0, 0x1F7F0},\n    {0x1F90C, 0x1F90F},\n    {0x1F919, 0x1F93A},\n    {0x1F93C, 0x1F945},\n    {0x1F947, 0x1F97F},\n    {0x1F985, 0x1F9BF},\n    {0x1F9C1, 0x1F9FF},\n    {0x1FA70, 0x1FA7C},\n    {0x1FA80, 0x1FA8A},\n    {0x1FA8E, 0x1FAC6},\n    {0x1FAC8, 0x1FAC8},\n    {0x1FACD, 0x1FADC},\n    {0x1FADF, 0x1FAEA},\n    {0x1FAEF, 0x1FAF8},\n    {0x20000, 0x2FFFD},\n    {0x30000, 0x3FFFD}\n};\n\n/* Ambiguous-width characters. */\nstatic const struct widechar_range widechar_ambiguous_table[] = {\n    {0x000A1, 0x000A1},\n    {0x000A4, 0x000A4},\n    {0x000A7, 0x000A8},\n    {0x000AA, 0x000AA},\n    {0x000AD, 0x000AE},\n    {0x000B0, 0x000B4},\n    {0x000B6, 0x000BA},\n    {0x000BC, 0x000BF},\n    {0x000C6, 0x000C6},\n    {0x000D0, 0x000D0},\n    {0x000D7, 0x000D8},\n    {0x000DE, 0x000E1},\n    {0x000E6, 0x000E6},\n    {0x000E8, 0x000EA},\n    {0x000EC, 0x000ED},\n    {0x000F0, 0x000F0},\n    {0x000F2, 0x000F3},\n    {0x000F7, 0x000FA},\n    {0x000FC, 0x000FC},\n    {0x000FE, 0x000FE},\n    {0x00101, 0x00101},\n    {0x00111, 0x00111},\n    {0x00113, 0x00113},\n    {0x0011B, 0x0011B},\n    {0x00126, 0x00127},\n    {0x0012B, 0x0012B},\n    {0x00131, 0x00133},\n    {0x00138, 0x00138},\n    {0x0013F, 0x00142},\n    {0x00144, 0x00144},\n    {0x00148, 0x0014B},\n    {0x0014D, 0x0014D},\n    {0x00152, 0x00153},\n    {0x00166, 0x00167},\n    {0x0016B, 0x0016B},\n    {0x001CE, 0x001CE},\n    {0x001D0, 0x001D0},\n    {0x001D2, 0x001D2},\n    {0x001D4, 0x001D4},\n    {0x001D6, 0x001D6},\n    {0x001D8, 0x001D8},\n    {0x001DA, 0x001DA},\n    {0x001DC, 0x001DC},\n    {0x00251, 0x00251},\n    {0x00261, 0x00261},\n    {0x002C4, 0x002C4},\n    {0x002C7, 0x002C7},\n    {0x002C9, 0x002CB},\n    {0x002CD, 0x002CD},\n    {0x002D0, 0x002D0},\n    {0x002D8, 0x002DB},\n    {0x002DD, 0x002DD},\n    {0x002DF, 0x002DF},\n    {0x00300, 0x0036F},\n    {0x00391, 0x003A1},\n    {0x003A3, 0x003A9},\n    {0x003B1, 0x003C1},\n    {0x003C3, 0x003C9},\n    {0x00401, 0x00401},\n    {0x00410, 0x0044F},\n    {0x00451, 0x00451},\n    {0x02010, 0x02010},\n    {0x02013, 0x02016},\n    {0x02018, 0x02019},\n    {0x0201C, 0x0201D},\n    {0x02020, 0x02022},\n    {0x02024, 0x02027},\n    {0x02030, 0x02030},\n    {0x02032, 0x02033},\n    {0x02035, 0x02035},\n    {0x0203B, 0x0203B},\n    {0x0203E, 0x0203E},\n    {0x02074, 0x02074},\n    {0x0207F, 0x0207F},\n    {0x02081, 0x02084},\n    {0x020AC, 0x020AC},\n    {0x02103, 0x02103},\n    {0x02105, 0x02105},\n    {0x02109, 0x02109},\n    {0x02113, 0x02113},\n    {0x02116, 0x02116},\n    {0x02121, 0x02122},\n    {0x02126, 0x02126},\n    {0x0212B, 0x0212B},\n    {0x02153, 0x02154},\n    {0x0215B, 0x0215E},\n    {0x02160, 0x0216B},\n    {0x02170, 0x02179},\n    {0x02189, 0x02189},\n    {0x02190, 0x02199},\n    {0x021B8, 0x021B9},\n    {0x021D2, 0x021D2},\n    {0x021D4, 0x021D4},\n    {0x021E7, 0x021E7},\n    {0x02200, 0x02200},\n    {0x02202, 0x02203},\n    {0x02207, 0x02208},\n    {0x0220B, 0x0220B},\n    {0x0220F, 0x0220F},\n    {0x02211, 0x02211},\n    {0x02215, 0x02215},\n    {0x0221A, 0x0221A},\n    {0x0221D, 0x02220},\n    {0x02223, 0x02223},\n    {0x02225, 0x02225},\n    {0x02227, 0x0222C},\n    {0x0222E, 0x0222E},\n    {0x02234, 0x02237},\n    {0x0223C, 0x0223D},\n    {0x02248, 0x02248},\n    {0x0224C, 0x0224C},\n    {0x02252, 0x02252},\n    {0x02260, 0x02261},\n    {0x02264, 0x02267},\n    {0x0226A, 0x0226B},\n    {0x0226E, 0x0226F},\n    {0x02282, 0x02283},\n    {0x02286, 0x02287},\n    {0x02295, 0x02295},\n    {0x02299, 0x02299},\n    {0x022A5, 0x022A5},\n    {0x022BF, 0x022BF},\n    {0x02312, 0x02312},\n    {0x02460, 0x024E9},\n    {0x024EB, 0x0254B},\n    {0x02550, 0x02573},\n    {0x02580, 0x0258F},\n    {0x02592, 0x02595},\n    {0x025A0, 0x025A1},\n    {0x025A3, 0x025A9},\n    {0x025B2, 0x025B3},\n    {0x025B6, 0x025B7},\n    {0x025BC, 0x025BD},\n    {0x025C0, 0x025C1},\n    {0x025C6, 0x025C8},\n    {0x025CB, 0x025CB},\n    {0x025CE, 0x025D1},\n    {0x025E2, 0x025E5},\n    {0x025EF, 0x025EF},\n    {0x02605, 0x02606},\n    {0x02609, 0x02609},\n    {0x0260E, 0x0260F},\n    {0x0261C, 0x0261C},\n    {0x0261E, 0x0261E},\n    {0x02640, 0x02640},\n    {0x02642, 0x02642},\n    {0x02660, 0x02661},\n    {0x02663, 0x02665},\n    {0x02667, 0x0266A},\n    {0x0266C, 0x0266D},\n    {0x0266F, 0x0266F},\n    {0x0269E, 0x0269F},\n    {0x026BF, 0x026BF},\n    {0x026C6, 0x026CD},\n    {0x026CF, 0x026D3},\n    {0x026D5, 0x026E1},\n    {0x026E3, 0x026E3},\n    {0x026E8, 0x026E9},\n    {0x026EB, 0x026F1},\n    {0x026F4, 0x026F4},\n    {0x026F6, 0x026F9},\n    {0x026FB, 0x026FC},\n    {0x026FE, 0x026FF},\n    {0x0273D, 0x0273D},\n    {0x02776, 0x0277F},\n    {0x02B56, 0x02B59},\n    {0x03248, 0x0324F},\n    {0x0E000, 0x0F8FF},\n    {0x0FE00, 0x0FE0F},\n    {0x0FFFD, 0x0FFFD},\n    {0x1F100, 0x1F10A},\n    {0x1F110, 0x1F12D},\n    {0x1F130, 0x1F169},\n    {0x1F170, 0x1F18D},\n    {0x1F18F, 0x1F190},\n    {0x1F19B, 0x1F1AC},\n    {0xE0100, 0xE01EF},\n    {0xF0000, 0xFFFFD},\n    {0x100000, 0x10FFFD}\n};\n\n/* Unassigned characters. */\nstatic const struct widechar_range widechar_unassigned_table[] = {\n    {0x00378, 0x00379},\n    {0x00380, 0x00383},\n    {0x0038B, 0x0038B},\n    {0x0038D, 0x0038D},\n    {0x003A2, 0x003A2},\n    {0x00530, 0x00530},\n    {0x00557, 0x00558},\n    {0x0058B, 0x0058C},\n    {0x00590, 0x00590},\n    {0x005C8, 0x005CF},\n    {0x005EB, 0x005EE},\n    {0x005F5, 0x005FF},\n    {0x0070E, 0x0070E},\n    {0x0074B, 0x0074C},\n    {0x007B2, 0x007BF},\n    {0x007FB, 0x007FC},\n    {0x0082E, 0x0082F},\n    {0x0083F, 0x0083F},\n    {0x0085C, 0x0085D},\n    {0x0085F, 0x0085F},\n    {0x0086B, 0x0086F},\n    {0x00892, 0x00896},\n    {0x00984, 0x00984},\n    {0x0098D, 0x0098E},\n    {0x00991, 0x00992},\n    {0x009A9, 0x009A9},\n    {0x009B1, 0x009B1},\n    {0x009B3, 0x009B5},\n    {0x009BA, 0x009BB},\n    {0x009C5, 0x009C6},\n    {0x009C9, 0x009CA},\n    {0x009CF, 0x009D6},\n    {0x009D8, 0x009DB},\n    {0x009DE, 0x009DE},\n    {0x009E4, 0x009E5},\n    {0x009FF, 0x00A00},\n    {0x00A04, 0x00A04},\n    {0x00A0B, 0x00A0E},\n    {0x00A11, 0x00A12},\n    {0x00A29, 0x00A29},\n    {0x00A31, 0x00A31},\n    {0x00A34, 0x00A34},\n    {0x00A37, 0x00A37},\n    {0x00A3A, 0x00A3B},\n    {0x00A3D, 0x00A3D},\n    {0x00A43, 0x00A46},\n    {0x00A49, 0x00A4A},\n    {0x00A4E, 0x00A50},\n    {0x00A52, 0x00A58},\n    {0x00A5D, 0x00A5D},\n    {0x00A5F, 0x00A65},\n    {0x00A77, 0x00A80},\n    {0x00A84, 0x00A84},\n    {0x00A8E, 0x00A8E},\n    {0x00A92, 0x00A92},\n    {0x00AA9, 0x00AA9},\n    {0x00AB1, 0x00AB1},\n    {0x00AB4, 0x00AB4},\n    {0x00ABA, 0x00ABB},\n    {0x00AC6, 0x00AC6},\n    {0x00ACA, 0x00ACA},\n    {0x00ACE, 0x00ACF},\n    {0x00AD1, 0x00ADF},\n    {0x00AE4, 0x00AE5},\n    {0x00AF2, 0x00AF8},\n    {0x00B00, 0x00B00},\n    {0x00B04, 0x00B04},\n    {0x00B0D, 0x00B0E},\n    {0x00B11, 0x00B12},\n    {0x00B29, 0x00B29},\n    {0x00B31, 0x00B31},\n    {0x00B34, 0x00B34},\n    {0x00B3A, 0x00B3B},\n    {0x00B45, 0x00B46},\n    {0x00B49, 0x00B4A},\n    {0x00B4E, 0x00B54},\n    {0x00B58, 0x00B5B},\n    {0x00B5E, 0x00B5E},\n    {0x00B64, 0x00B65},\n    {0x00B78, 0x00B81},\n    {0x00B84, 0x00B84},\n    {0x00B8B, 0x00B8D},\n    {0x00B91, 0x00B91},\n    {0x00B96, 0x00B98},\n    {0x00B9B, 0x00B9B},\n    {0x00B9D, 0x00B9D},\n    {0x00BA0, 0x00BA2},\n    {0x00BA5, 0x00BA7},\n    {0x00BAB, 0x00BAD},\n    {0x00BBA, 0x00BBD},\n    {0x00BC3, 0x00BC5},\n    {0x00BC9, 0x00BC9},\n    {0x00BCE, 0x00BCF},\n    {0x00BD1, 0x00BD6},\n    {0x00BD8, 0x00BE5},\n    {0x00BFB, 0x00BFF},\n    {0x00C0D, 0x00C0D},\n    {0x00C11, 0x00C11},\n    {0x00C29, 0x00C29},\n    {0x00C3A, 0x00C3B},\n    {0x00C45, 0x00C45},\n    {0x00C49, 0x00C49},\n    {0x00C4E, 0x00C54},\n    {0x00C57, 0x00C57},\n    {0x00C5B, 0x00C5B},\n    {0x00C5E, 0x00C5F},\n    {0x00C64, 0x00C65},\n    {0x00C70, 0x00C76},\n    {0x00C8D, 0x00C8D},\n    {0x00C91, 0x00C91},\n    {0x00CA9, 0x00CA9},\n    {0x00CB4, 0x00CB4},\n    {0x00CBA, 0x00CBB},\n    {0x00CC5, 0x00CC5},\n    {0x00CC9, 0x00CC9},\n    {0x00CCE, 0x00CD4},\n    {0x00CD7, 0x00CDB},\n    {0x00CDF, 0x00CDF},\n    {0x00CE4, 0x00CE5},\n    {0x00CF0, 0x00CF0},\n    {0x00CF4, 0x00CFF},\n    {0x00D0D, 0x00D0D},\n    {0x00D11, 0x00D11},\n    {0x00D45, 0x00D45},\n    {0x00D49, 0x00D49},\n    {0x00D50, 0x00D53},\n    {0x00D64, 0x00D65},\n    {0x00D80, 0x00D80},\n    {0x00D84, 0x00D84},\n    {0x00D97, 0x00D99},\n    {0x00DB2, 0x00DB2},\n    {0x00DBC, 0x00DBC},\n    {0x00DBE, 0x00DBF},\n    {0x00DC7, 0x00DC9},\n    {0x00DCB, 0x00DCE},\n    {0x00DD5, 0x00DD5},\n    {0x00DD7, 0x00DD7},\n    {0x00DE0, 0x00DE5},\n    {0x00DF0, 0x00DF1},\n    {0x00DF5, 0x00E00},\n    {0x00E3B, 0x00E3E},\n    {0x00E5C, 0x00E80},\n    {0x00E83, 0x00E83},\n    {0x00E85, 0x00E85},\n    {0x00E8B, 0x00E8B},\n    {0x00EA4, 0x00EA4},\n    {0x00EA6, 0x00EA6},\n    {0x00EBE, 0x00EBF},\n    {0x00EC5, 0x00EC5},\n    {0x00EC7, 0x00EC7},\n    {0x00ECF, 0x00ECF},\n    {0x00EDA, 0x00EDB},\n    {0x00EE0, 0x00EFF},\n    {0x00F48, 0x00F48},\n    {0x00F6D, 0x00F70},\n    {0x00F98, 0x00F98},\n    {0x00FBD, 0x00FBD},\n    {0x00FCD, 0x00FCD},\n    {0x00FDB, 0x00FFF},\n    {0x010C6, 0x010C6},\n    {0x010C8, 0x010CC},\n    {0x010CE, 0x010CF},\n    {0x01249, 0x01249},\n    {0x0124E, 0x0124F},\n    {0x01257, 0x01257},\n    {0x01259, 0x01259},\n    {0x0125E, 0x0125F},\n    {0x01289, 0x01289},\n    {0x0128E, 0x0128F},\n    {0x012B1, 0x012B1},\n    {0x012B6, 0x012B7},\n    {0x012BF, 0x012BF},\n    {0x012C1, 0x012C1},\n    {0x012C6, 0x012C7},\n    {0x012D7, 0x012D7},\n    {0x01311, 0x01311},\n    {0x01316, 0x01317},\n    {0x0135B, 0x0135C},\n    {0x0137D, 0x0137F},\n    {0x0139A, 0x0139F},\n    {0x013F6, 0x013F7},\n    {0x013FE, 0x013FF},\n    {0x0169D, 0x0169F},\n    {0x016F9, 0x016FF},\n    {0x01716, 0x0171E},\n    {0x01737, 0x0173F},\n    {0x01754, 0x0175F},\n    {0x0176D, 0x0176D},\n    {0x01771, 0x01771},\n    {0x01774, 0x0177F},\n    {0x017DE, 0x017DF},\n    {0x017EA, 0x017EF},\n    {0x017FA, 0x017FF},\n    {0x0181A, 0x0181F},\n    {0x01879, 0x0187F},\n    {0x018AB, 0x018AF},\n    {0x018F6, 0x018FF},\n    {0x0191F, 0x0191F},\n    {0x0192C, 0x0192F},\n    {0x0193C, 0x0193F},\n    {0x01941, 0x01943},\n    {0x0196E, 0x0196F},\n    {0x01975, 0x0197F},\n    {0x019AC, 0x019AF},\n    {0x019CA, 0x019CF},\n    {0x019DB, 0x019DD},\n    {0x01A1C, 0x01A1D},\n    {0x01A5F, 0x01A5F},\n    {0x01A7D, 0x01A7E},\n    {0x01A8A, 0x01A8F},\n    {0x01A9A, 0x01A9F},\n    {0x01AAE, 0x01AAF},\n    {0x01ADE, 0x01ADF},\n    {0x01AEC, 0x01AFF},\n    {0x01B4D, 0x01B4D},\n    {0x01BF4, 0x01BFB},\n    {0x01C38, 0x01C3A},\n    {0x01C4A, 0x01C4C},\n    {0x01C8B, 0x01C8F},\n    {0x01CBB, 0x01CBC},\n    {0x01CC8, 0x01CCF},\n    {0x01CFB, 0x01CFF},\n    {0x01F16, 0x01F17},\n    {0x01F1E, 0x01F1F},\n    {0x01F46, 0x01F47},\n    {0x01F4E, 0x01F4F},\n    {0x01F58, 0x01F58},\n    {0x01F5A, 0x01F5A},\n    {0x01F5C, 0x01F5C},\n    {0x01F5E, 0x01F5E},\n    {0x01F7E, 0x01F7F},\n    {0x01FB5, 0x01FB5},\n    {0x01FC5, 0x01FC5},\n    {0x01FD4, 0x01FD5},\n    {0x01FDC, 0x01FDC},\n    {0x01FF0, 0x01FF1},\n    {0x01FF5, 0x01FF5},\n    {0x01FFF, 0x01FFF},\n    {0x02065, 0x02065},\n    {0x02072, 0x02073},\n    {0x0208F, 0x0208F},\n    {0x0209D, 0x0209F},\n    {0x020C2, 0x020CF},\n    {0x020F1, 0x020FF},\n    {0x0218C, 0x0218F},\n    {0x0242A, 0x0243F},\n    {0x0244B, 0x0245F},\n    {0x02B74, 0x02B75},\n    {0x02CF4, 0x02CF8},\n    {0x02D26, 0x02D26},\n    {0x02D28, 0x02D2C},\n    {0x02D2E, 0x02D2F},\n    {0x02D68, 0x02D6E},\n    {0x02D71, 0x02D7E},\n    {0x02D97, 0x02D9F},\n    {0x02DA7, 0x02DA7},\n    {0x02DAF, 0x02DAF},\n    {0x02DB7, 0x02DB7},\n    {0x02DBF, 0x02DBF},\n    {0x02DC7, 0x02DC7},\n    {0x02DCF, 0x02DCF},\n    {0x02DD7, 0x02DD7},\n    {0x02DDF, 0x02DDF},\n    {0x02E5E, 0x02E7F},\n    {0x02E9A, 0x02E9A},\n    {0x02EF4, 0x02EFF},\n    {0x02FD6, 0x02FEF},\n    {0x03040, 0x03040},\n    {0x03097, 0x03098},\n    {0x03100, 0x03104},\n    {0x03130, 0x03130},\n    {0x0318F, 0x0318F},\n    {0x031E6, 0x031EE},\n    {0x0321F, 0x0321F},\n    {0x03401, 0x04DBE},\n    {0x04E01, 0x09FFE},\n    {0x0A48D, 0x0A48F},\n    {0x0A4C7, 0x0A4CF},\n    {0x0A62C, 0x0A63F},\n    {0x0A6F8, 0x0A6FF},\n    {0x0A7DD, 0x0A7F0},\n    {0x0A82D, 0x0A82F},\n    {0x0A83A, 0x0A83F},\n    {0x0A878, 0x0A87F},\n    {0x0A8C6, 0x0A8CD},\n    {0x0A8DA, 0x0A8DF},\n    {0x0A954, 0x0A95E},\n    {0x0A97D, 0x0A97F},\n    {0x0A9CE, 0x0A9CE},\n    {0x0A9DA, 0x0A9DD},\n    {0x0A9FF, 0x0A9FF},\n    {0x0AA37, 0x0AA3F},\n    {0x0AA4E, 0x0AA4F},\n    {0x0AA5A, 0x0AA5B},\n    {0x0AAC3, 0x0AADA},\n    {0x0AAF7, 0x0AB00},\n    {0x0AB07, 0x0AB08},\n    {0x0AB0F, 0x0AB10},\n    {0x0AB17, 0x0AB1F},\n    {0x0AB27, 0x0AB27},\n    {0x0AB2F, 0x0AB2F},\n    {0x0AB6C, 0x0AB6F},\n    {0x0ABEE, 0x0ABEF},\n    {0x0ABFA, 0x0ABFF},\n    {0x0AC01, 0x0D7A2},\n    {0x0D7A4, 0x0D7AF},\n    {0x0D7C7, 0x0D7CA},\n    {0x0D7FC, 0x0D7FF},\n    {0x0FA6E, 0x0FA6F},\n    {0x0FADA, 0x0FAFF},\n    {0x0FB07, 0x0FB12},\n    {0x0FB18, 0x0FB1C},\n    {0x0FB37, 0x0FB37},\n    {0x0FB3D, 0x0FB3D},\n    {0x0FB3F, 0x0FB3F},\n    {0x0FB42, 0x0FB42},\n    {0x0FB45, 0x0FB45},\n    {0x0FE1A, 0x0FE1F},\n    {0x0FE53, 0x0FE53},\n    {0x0FE67, 0x0FE67},\n    {0x0FE6C, 0x0FE6F},\n    {0x0FE75, 0x0FE75},\n    {0x0FEFD, 0x0FEFE},\n    {0x0FF00, 0x0FF00},\n    {0x0FFBF, 0x0FFC1},\n    {0x0FFC8, 0x0FFC9},\n    {0x0FFD0, 0x0FFD1},\n    {0x0FFD8, 0x0FFD9},\n    {0x0FFDD, 0x0FFDF},\n    {0x0FFE7, 0x0FFE7},\n    {0x0FFEF, 0x0FFF8},\n    {0x1000C, 0x1000C},\n    {0x10027, 0x10027},\n    {0x1003B, 0x1003B},\n    {0x1003E, 0x1003E},\n    {0x1004E, 0x1004F},\n    {0x1005E, 0x1007F},\n    {0x100FB, 0x100FF},\n    {0x10103, 0x10106},\n    {0x10134, 0x10136},\n    {0x1018F, 0x1018F},\n    {0x1019D, 0x1019F},\n    {0x101A1, 0x101CF},\n    {0x101FE, 0x1027F},\n    {0x1029D, 0x1029F},\n    {0x102D1, 0x102DF},\n    {0x102FC, 0x102FF},\n    {0x10324, 0x1032C},\n    {0x1034B, 0x1034F},\n    {0x1037B, 0x1037F},\n    {0x1039E, 0x1039E},\n    {0x103C4, 0x103C7},\n    {0x103D6, 0x103FF},\n    {0x1049E, 0x1049F},\n    {0x104AA, 0x104AF},\n    {0x104D4, 0x104D7},\n    {0x104FC, 0x104FF},\n    {0x10528, 0x1052F},\n    {0x10564, 0x1056E},\n    {0x1057B, 0x1057B},\n    {0x1058B, 0x1058B},\n    {0x10593, 0x10593},\n    {0x10596, 0x10596},\n    {0x105A2, 0x105A2},\n    {0x105B2, 0x105B2},\n    {0x105BA, 0x105BA},\n    {0x105BD, 0x105BF},\n    {0x105F4, 0x105FF},\n    {0x10737, 0x1073F},\n    {0x10756, 0x1075F},\n    {0x10768, 0x1077F},\n    {0x10786, 0x10786},\n    {0x107B1, 0x107B1},\n    {0x107BB, 0x107FF},\n    {0x10806, 0x10807},\n    {0x10809, 0x10809},\n    {0x10836, 0x10836},\n    {0x10839, 0x1083B},\n    {0x1083D, 0x1083E},\n    {0x10856, 0x10856},\n    {0x1089F, 0x108A6},\n    {0x108B0, 0x108DF},\n    {0x108F3, 0x108F3},\n    {0x108F6, 0x108FA},\n    {0x1091C, 0x1091E},\n    {0x1093A, 0x1093E},\n    {0x1095A, 0x1097F},\n    {0x109B8, 0x109BB},\n    {0x109D0, 0x109D1},\n    {0x10A04, 0x10A04},\n    {0x10A07, 0x10A0B},\n    {0x10A14, 0x10A14},\n    {0x10A18, 0x10A18},\n    {0x10A36, 0x10A37},\n    {0x10A3B, 0x10A3E},\n    {0x10A49, 0x10A4F},\n    {0x10A59, 0x10A5F},\n    {0x10AA0, 0x10ABF},\n    {0x10AE7, 0x10AEA},\n    {0x10AF7, 0x10AFF},\n    {0x10B36, 0x10B38},\n    {0x10B56, 0x10B57},\n    {0x10B73, 0x10B77},\n    {0x10B92, 0x10B98},\n    {0x10B9D, 0x10BA8},\n    {0x10BB0, 0x10BFF},\n    {0x10C49, 0x10C7F},\n    {0x10CB3, 0x10CBF},\n    {0x10CF3, 0x10CF9},\n    {0x10D28, 0x10D2F},\n    {0x10D3A, 0x10D3F},\n    {0x10D66, 0x10D68},\n    {0x10D86, 0x10D8D},\n    {0x10D90, 0x10E5F},\n    {0x10E7F, 0x10E7F},\n    {0x10EAA, 0x10EAA},\n    {0x10EAE, 0x10EAF},\n    {0x10EB2, 0x10EC1},\n    {0x10EC8, 0x10ECF},\n    {0x10ED9, 0x10EF9},\n    {0x10F28, 0x10F2F},\n    {0x10F5A, 0x10F6F},\n    {0x10F8A, 0x10FAF},\n    {0x10FCC, 0x10FDF},\n    {0x10FF7, 0x10FFF},\n    {0x1104E, 0x11051},\n    {0x11076, 0x1107E},\n    {0x110C3, 0x110CC},\n    {0x110CE, 0x110CF},\n    {0x110E9, 0x110EF},\n    {0x110FA, 0x110FF},\n    {0x11135, 0x11135},\n    {0x11148, 0x1114F},\n    {0x11177, 0x1117F},\n    {0x111E0, 0x111E0},\n    {0x111F5, 0x111FF},\n    {0x11212, 0x11212},\n    {0x11242, 0x1127F},\n    {0x11287, 0x11287},\n    {0x11289, 0x11289},\n    {0x1128E, 0x1128E},\n    {0x1129E, 0x1129E},\n    {0x112AA, 0x112AF},\n    {0x112EB, 0x112EF},\n    {0x112FA, 0x112FF},\n    {0x11304, 0x11304},\n    {0x1130D, 0x1130E},\n    {0x11311, 0x11312},\n    {0x11329, 0x11329},\n    {0x11331, 0x11331},\n    {0x11334, 0x11334},\n    {0x1133A, 0x1133A},\n    {0x11345, 0x11346},\n    {0x11349, 0x1134A},\n    {0x1134E, 0x1134F},\n    {0x11351, 0x11356},\n    {0x11358, 0x1135C},\n    {0x11364, 0x11365},\n    {0x1136D, 0x1136F},\n    {0x11375, 0x1137F},\n    {0x1138A, 0x1138A},\n    {0x1138C, 0x1138D},\n    {0x1138F, 0x1138F},\n    {0x113B6, 0x113B6},\n    {0x113C1, 0x113C1},\n    {0x113C3, 0x113C4},\n    {0x113C6, 0x113C6},\n    {0x113CB, 0x113CB},\n    {0x113D6, 0x113D6},\n    {0x113D9, 0x113E0},\n    {0x113E3, 0x113FF},\n    {0x1145C, 0x1145C},\n    {0x11462, 0x1147F},\n    {0x114C8, 0x114CF},\n    {0x114DA, 0x1157F},\n    {0x115B6, 0x115B7},\n    {0x115DE, 0x115FF},\n    {0x11645, 0x1164F},\n    {0x1165A, 0x1165F},\n    {0x1166D, 0x1167F},\n    {0x116BA, 0x116BF},\n    {0x116CA, 0x116CF},\n    {0x116E4, 0x116FF},\n    {0x1171B, 0x1171C},\n    {0x1172C, 0x1172F},\n    {0x11747, 0x117FF},\n    {0x1183C, 0x1189F},\n    {0x118F3, 0x118FE},\n    {0x11907, 0x11908},\n    {0x1190A, 0x1190B},\n    {0x11914, 0x11914},\n    {0x11917, 0x11917},\n    {0x11936, 0x11936},\n    {0x11939, 0x1193A},\n    {0x11947, 0x1194F},\n    {0x1195A, 0x1199F},\n    {0x119A8, 0x119A9},\n    {0x119D8, 0x119D9},\n    {0x119E5, 0x119FF},\n    {0x11A48, 0x11A4F},\n    {0x11AA3, 0x11AAF},\n    {0x11AF9, 0x11AFF},\n    {0x11B0A, 0x11B5F},\n    {0x11B68, 0x11BBF},\n    {0x11BE2, 0x11BEF},\n    {0x11BFA, 0x11BFF},\n    {0x11C09, 0x11C09},\n    {0x11C37, 0x11C37},\n    {0x11C46, 0x11C4F},\n    {0x11C6D, 0x11C6F},\n    {0x11C90, 0x11C91},\n    {0x11CA8, 0x11CA8},\n    {0x11CB7, 0x11CFF},\n    {0x11D07, 0x11D07},\n    {0x11D0A, 0x11D0A},\n    {0x11D37, 0x11D39},\n    {0x11D3B, 0x11D3B},\n    {0x11D3E, 0x11D3E},\n    {0x11D48, 0x11D4F},\n    {0x11D5A, 0x11D5F},\n    {0x11D66, 0x11D66},\n    {0x11D69, 0x11D69},\n    {0x11D8F, 0x11D8F},\n    {0x11D92, 0x11D92},\n    {0x11D99, 0x11D9F},\n    {0x11DAA, 0x11DAF},\n    {0x11DDC, 0x11DDF},\n    {0x11DEA, 0x11EDF},\n    {0x11EF9, 0x11EFF},\n    {0x11F11, 0x11F11},\n    {0x11F3B, 0x11F3D},\n    {0x11F5B, 0x11FAF},\n    {0x11FB1, 0x11FBF},\n    {0x11FF2, 0x11FFE},\n    {0x1239A, 0x123FF},\n    {0x1246F, 0x1246F},\n    {0x12475, 0x1247F},\n    {0x12544, 0x12F8F},\n    {0x12FF3, 0x12FFF},\n    {0x13456, 0x1345F},\n    {0x143FB, 0x143FF},\n    {0x14647, 0x160FF},\n    {0x1613A, 0x167FF},\n    {0x16A39, 0x16A3F},\n    {0x16A5F, 0x16A5F},\n    {0x16A6A, 0x16A6D},\n    {0x16ABF, 0x16ABF},\n    {0x16ACA, 0x16ACF},\n    {0x16AEE, 0x16AEF},\n    {0x16AF6, 0x16AFF},\n    {0x16B46, 0x16B4F},\n    {0x16B5A, 0x16B5A},\n    {0x16B62, 0x16B62},\n    {0x16B78, 0x16B7C},\n    {0x16B90, 0x16D3F},\n    {0x16D7A, 0x16E3F},\n    {0x16E9B, 0x16E9F},\n    {0x16EB9, 0x16EBA},\n    {0x16ED4, 0x16EFF},\n    {0x16F4B, 0x16F4E},\n    {0x16F88, 0x16F8E},\n    {0x16FA0, 0x16FDF},\n    {0x16FE5, 0x16FEF},\n    {0x16FF7, 0x16FFF},\n    {0x17001, 0x187FE},\n    {0x18CD6, 0x18CFE},\n    {0x18D01, 0x18D1D},\n    {0x18D1F, 0x18D7F},\n    {0x18DF3, 0x1AFEF},\n    {0x1AFF4, 0x1AFF4},\n    {0x1AFFC, 0x1AFFC},\n    {0x1AFFF, 0x1AFFF},\n    {0x1B123, 0x1B131},\n    {0x1B133, 0x1B14F},\n    {0x1B153, 0x1B154},\n    {0x1B156, 0x1B163},\n    {0x1B168, 0x1B16F},\n    {0x1B2FC, 0x1BBFF},\n    {0x1BC6B, 0x1BC6F},\n    {0x1BC7D, 0x1BC7F},\n    {0x1BC89, 0x1BC8F},\n    {0x1BC9A, 0x1BC9B},\n    {0x1BCA4, 0x1CBFF},\n    {0x1CCFD, 0x1CCFF},\n    {0x1CEB4, 0x1CEB9},\n    {0x1CED1, 0x1CEDF},\n    {0x1CEF1, 0x1CEFF},\n    {0x1CF2E, 0x1CF2F},\n    {0x1CF47, 0x1CF4F},\n    {0x1CFC4, 0x1CFFF},\n    {0x1D0F6, 0x1D0FF},\n    {0x1D127, 0x1D128},\n    {0x1D1EB, 0x1D1FF},\n    {0x1D246, 0x1D2BF},\n    {0x1D2D4, 0x1D2DF},\n    {0x1D2F4, 0x1D2FF},\n    {0x1D357, 0x1D35F},\n    {0x1D379, 0x1D3FF},\n    {0x1D455, 0x1D455},\n    {0x1D49D, 0x1D49D},\n    {0x1D4A0, 0x1D4A1},\n    {0x1D4A3, 0x1D4A4},\n    {0x1D4A7, 0x1D4A8},\n    {0x1D4AD, 0x1D4AD},\n    {0x1D4BA, 0x1D4BA},\n    {0x1D4BC, 0x1D4BC},\n    {0x1D4C4, 0x1D4C4},\n    {0x1D506, 0x1D506},\n    {0x1D50B, 0x1D50C},\n    {0x1D515, 0x1D515},\n    {0x1D51D, 0x1D51D},\n    {0x1D53A, 0x1D53A},\n    {0x1D53F, 0x1D53F},\n    {0x1D545, 0x1D545},\n    {0x1D547, 0x1D549},\n    {0x1D551, 0x1D551},\n    {0x1D6A6, 0x1D6A7},\n    {0x1D7CC, 0x1D7CD},\n    {0x1DA8C, 0x1DA9A},\n    {0x1DAA0, 0x1DAA0},\n    {0x1DAB0, 0x1DEFF},\n    {0x1DF1F, 0x1DF24},\n    {0x1DF2B, 0x1DFFF},\n    {0x1E007, 0x1E007},\n    {0x1E019, 0x1E01A},\n    {0x1E022, 0x1E022},\n    {0x1E025, 0x1E025},\n    {0x1E02B, 0x1E02F},\n    {0x1E06E, 0x1E08E},\n    {0x1E090, 0x1E0FF},\n    {0x1E12D, 0x1E12F},\n    {0x1E13E, 0x1E13F},\n    {0x1E14A, 0x1E14D},\n    {0x1E150, 0x1E28F},\n    {0x1E2AF, 0x1E2BF},\n    {0x1E2FA, 0x1E2FE},\n    {0x1E300, 0x1E4CF},\n    {0x1E4FA, 0x1E5CF},\n    {0x1E5FB, 0x1E5FE},\n    {0x1E600, 0x1E6BF},\n    {0x1E6DF, 0x1E6DF},\n    {0x1E6F6, 0x1E6FD},\n    {0x1E700, 0x1E7DF},\n    {0x1E7E7, 0x1E7E7},\n    {0x1E7EC, 0x1E7EC},\n    {0x1E7EF, 0x1E7EF},\n    {0x1E7FF, 0x1E7FF},\n    {0x1E8C5, 0x1E8C6},\n    {0x1E8D7, 0x1E8FF},\n    {0x1E94C, 0x1E94F},\n    {0x1E95A, 0x1E95D},\n    {0x1E960, 0x1EC70},\n    {0x1ECB5, 0x1ED00},\n    {0x1ED3E, 0x1EDFF},\n    {0x1EE04, 0x1EE04},\n    {0x1EE20, 0x1EE20},\n    {0x1EE23, 0x1EE23},\n    {0x1EE25, 0x1EE26},\n    {0x1EE28, 0x1EE28},\n    {0x1EE33, 0x1EE33},\n    {0x1EE38, 0x1EE38},\n    {0x1EE3A, 0x1EE3A},\n    {0x1EE3C, 0x1EE41},\n    {0x1EE43, 0x1EE46},\n    {0x1EE48, 0x1EE48},\n    {0x1EE4A, 0x1EE4A},\n    {0x1EE4C, 0x1EE4C},\n    {0x1EE50, 0x1EE50},\n    {0x1EE53, 0x1EE53},\n    {0x1EE55, 0x1EE56},\n    {0x1EE58, 0x1EE58},\n    {0x1EE5A, 0x1EE5A},\n    {0x1EE5C, 0x1EE5C},\n    {0x1EE5E, 0x1EE5E},\n    {0x1EE60, 0x1EE60},\n    {0x1EE63, 0x1EE63},\n    {0x1EE65, 0x1EE66},\n    {0x1EE6B, 0x1EE6B},\n    {0x1EE73, 0x1EE73},\n    {0x1EE78, 0x1EE78},\n    {0x1EE7D, 0x1EE7D},\n    {0x1EE7F, 0x1EE7F},\n    {0x1EE8A, 0x1EE8A},\n    {0x1EE9C, 0x1EEA0},\n    {0x1EEA4, 0x1EEA4},\n    {0x1EEAA, 0x1EEAA},\n    {0x1EEBC, 0x1EEEF},\n    {0x1EEF2, 0x1EFFF},\n    {0x1F02C, 0x1F02F},\n    {0x1F094, 0x1F09F},\n    {0x1F0AF, 0x1F0B0},\n    {0x1F0C0, 0x1F0C0},\n    {0x1F0D0, 0x1F0D0},\n    {0x1F0F6, 0x1F0FF},\n    {0x1F1AE, 0x1F1E5},\n    {0x1F203, 0x1F20F},\n    {0x1F23C, 0x1F23F},\n    {0x1F249, 0x1F24F},\n    {0x1F252, 0x1F25F},\n    {0x1F266, 0x1F2FF},\n    {0x1F6D9, 0x1F6DB},\n    {0x1F6ED, 0x1F6EF},\n    {0x1F6FD, 0x1F6FF},\n    {0x1F7DA, 0x1F7DF},\n    {0x1F7EC, 0x1F7EF},\n    {0x1F7F1, 0x1F7FF},\n    {0x1F80C, 0x1F80F},\n    {0x1F848, 0x1F84F},\n    {0x1F85A, 0x1F85F},\n    {0x1F888, 0x1F88F},\n    {0x1F8AE, 0x1F8AF},\n    {0x1F8BC, 0x1F8BF},\n    {0x1F8C2, 0x1F8CF},\n    {0x1F8D9, 0x1F8FF},\n    {0x1FA58, 0x1FA5F},\n    {0x1FA6E, 0x1FA6F},\n    {0x1FA7D, 0x1FA7F},\n    {0x1FA8B, 0x1FA8D},\n    {0x1FAC7, 0x1FAC7},\n    {0x1FAC9, 0x1FACC},\n    {0x1FADD, 0x1FADE},\n    {0x1FAEB, 0x1FAEE},\n    {0x1FAF9, 0x1FAFF},\n    {0x1FB93, 0x1FB93},\n    {0x1FBFB, 0x1FFFD},\n    {0x20001, 0x2A6DE},\n    {0x2A6E0, 0x2A6FF},\n    {0x2A701, 0x2B73E},\n    {0x2B741, 0x2B81C},\n    {0x2B81E, 0x2B81F},\n    {0x2B821, 0x2CEAC},\n    {0x2CEAE, 0x2CEAF},\n    {0x2CEB1, 0x2EBDF},\n    {0x2EBE1, 0x2EBEF},\n    {0x2EBF1, 0x2EE5C},\n    {0x2EE5E, 0x2F7FF},\n    {0x2FA1E, 0x2FFFD},\n    {0x30001, 0x31349},\n    {0x3134B, 0x3134F},\n    {0x31351, 0x323AE},\n    {0x323B1, 0x33478},\n    {0x3347A, 0x3FFFD},\n    {0x40000, 0x4FFFD},\n    {0x50000, 0x5FFFD},\n    {0x60000, 0x6FFFD},\n    {0x70000, 0x7FFFD},\n    {0x80000, 0x8FFFD},\n    {0x90000, 0x9FFFD},\n    {0xA0000, 0xAFFFD},\n    {0xB0000, 0xBFFFD},\n    {0xC0000, 0xCFFFD},\n    {0xD0000, 0xDFFFD},\n    {0xE0000, 0xE0000},\n    {0xE0002, 0xE001F},\n    {0xE0080, 0xE00FF},\n    {0xE01F0, 0xEFFFD}\n};\n\n/* Non-characters. */\nstatic const struct widechar_range widechar_nonchar_table[] = {\n    {0x0FDD0, 0x0FDEF},\n    {0x0FFFE, 0x0FFFF},\n    {0x1FFFE, 0x1FFFF},\n    {0x2FFFE, 0x2FFFF},\n    {0x3FFFE, 0x3FFFF},\n    {0x4FFFE, 0x4FFFF},\n    {0x5FFFE, 0x5FFFF},\n    {0x6FFFE, 0x6FFFF},\n    {0x7FFFE, 0x7FFFF},\n    {0x8FFFE, 0x8FFFF},\n    {0x9FFFE, 0x9FFFF},\n    {0xAFFFE, 0xAFFFF},\n    {0xBFFFE, 0xBFFFF},\n    {0xCFFFE, 0xCFFFF},\n    {0xDFFFE, 0xDFFFF},\n    {0xEFFFE, 0xEFFFF},\n    {0xFFFFE, 0xFFFFF},\n    {0x10FFFE, 0x10FFFF}\n};\n\n/* Characters that were widened from width 1 to 2 in Unicode 9. */\nstatic const struct widechar_range widechar_widened_table[] = {\n    {0x0231A, 0x0231B},\n    {0x023E9, 0x023EC},\n    {0x023F0, 0x023F0},\n    {0x023F3, 0x023F3},\n    {0x025FD, 0x025FE},\n    {0x02614, 0x02615},\n    {0x02648, 0x02653},\n    {0x0267F, 0x0267F},\n    {0x02693, 0x02693},\n    {0x026A1, 0x026A1},\n    {0x026AA, 0x026AB},\n    {0x026BD, 0x026BE},\n    {0x026C4, 0x026C5},\n    {0x026CE, 0x026CE},\n    {0x026D4, 0x026D4},\n    {0x026EA, 0x026EA},\n    {0x026F2, 0x026F3},\n    {0x026F5, 0x026F5},\n    {0x026FA, 0x026FA},\n    {0x026FD, 0x026FD},\n    {0x02705, 0x02705},\n    {0x0270A, 0x0270B},\n    {0x02728, 0x02728},\n    {0x0274C, 0x0274C},\n    {0x0274E, 0x0274E},\n    {0x02753, 0x02755},\n    {0x02757, 0x02757},\n    {0x02795, 0x02797},\n    {0x027B0, 0x027B0},\n    {0x027BF, 0x027BF},\n    {0x02B1B, 0x02B1C},\n    {0x02B50, 0x02B50},\n    {0x02B55, 0x02B55},\n    {0x1F004, 0x1F004},\n    {0x1F0CF, 0x1F0CF},\n    {0x1F18E, 0x1F18E},\n    {0x1F191, 0x1F19A},\n    {0x1F201, 0x1F201},\n    {0x1F21A, 0x1F21A},\n    {0x1F22F, 0x1F22F},\n    {0x1F232, 0x1F236},\n    {0x1F238, 0x1F23A},\n    {0x1F250, 0x1F251},\n    {0x1F300, 0x1F320},\n    {0x1F32D, 0x1F335},\n    {0x1F337, 0x1F37C},\n    {0x1F37E, 0x1F393},\n    {0x1F3A0, 0x1F3CA},\n    {0x1F3CF, 0x1F3D3},\n    {0x1F3E0, 0x1F3F0},\n    {0x1F3F4, 0x1F3F4},\n    {0x1F3F8, 0x1F43E},\n    {0x1F440, 0x1F440},\n    {0x1F442, 0x1F4FC},\n    {0x1F4FF, 0x1F53D},\n    {0x1F54B, 0x1F54E},\n    {0x1F550, 0x1F567},\n    {0x1F595, 0x1F596},\n    {0x1F5FB, 0x1F64F},\n    {0x1F680, 0x1F6C5},\n    {0x1F6CC, 0x1F6CC},\n    {0x1F6D0, 0x1F6D0},\n    {0x1F6EB, 0x1F6EC},\n    {0x1F910, 0x1F918},\n    {0x1F980, 0x1F984},\n    {0x1F9C0, 0x1F9C0}\n};\n\nstatic inline bool widechar_in_table(const struct widechar_range* arr, size_t len, uint32_t c) {\n\n    size_t lo=0;\n    size_t hi=len;\n\n    if(c < arr[0].lo) return(0);\n    if(c > arr[len-1].hi) return(0);\n\n    while(1) {\n        size_t mid = ((hi-lo)/2)+lo;\n        if( (c >= arr[mid].lo) && (c <= arr[mid].hi)) {\n            return(1);\n        }\n        if(mid == lo) return(0);\n\n        if (c < arr[mid].lo) {\n            hi = mid;\n        } else {\n            lo = mid;\n        }\n    }\n    return(0);\n}\n\n\n\n/* Return the width of character c, or a special negative value. */\nint widechar_wcwidth(uint32_t c) {\n    if (widechar_in_table(widechar_ascii_table, widechar_ARRAY_SIZE(widechar_ascii_table), c))\n        return 1;\n    if (widechar_in_table(widechar_private_table, widechar_ARRAY_SIZE(widechar_private_table), c))\n        return widechar_private_use;\n    if (widechar_in_table(widechar_nonprint_table, widechar_ARRAY_SIZE(widechar_nonprint_table), c))\n        return widechar_nonprint;\n    if (widechar_in_table(widechar_nonchar_table, widechar_ARRAY_SIZE(widechar_nonchar_table), c))\n        return widechar_non_character;\n    if (widechar_in_table(widechar_combining_table, widechar_ARRAY_SIZE(widechar_combining_table), c))\n        return widechar_combining;\n    if (widechar_in_table(widechar_combiningletters_table, widechar_ARRAY_SIZE(widechar_combiningletters_table), c))\n        return widechar_combining;\n    if (widechar_in_table(widechar_doublewide_table, widechar_ARRAY_SIZE(widechar_doublewide_table), c))\n        return 2;\n    if (widechar_in_table(widechar_ambiguous_table, widechar_ARRAY_SIZE(widechar_ambiguous_table), c))\n        return widechar_ambiguous;\n    if (widechar_in_table(widechar_unassigned_table, widechar_ARRAY_SIZE(widechar_unassigned_table), c))\n        return widechar_unassigned;\n    if (widechar_in_table(widechar_widened_table, widechar_ARRAY_SIZE(widechar_widened_table), c))\n        return widechar_widened_in_9;\n    return 1;\n}\n\n#endif // WIDECHAR_WIDTH_H\n"
  },
  {
    "path": "src/3rdparty/yyjson/repo.json",
    "content": "{\n    \"home\": \"https://github.com/ibireme/yyjson\",\n    \"license\": \"MIT ( embed in source )\",\n    \"version\": \"0.12.0\",\n    \"author\": \"ibireme\"\n}\n"
  },
  {
    "path": "src/3rdparty/yyjson/yyjson.c",
    "content": "/*==============================================================================\n Copyright (c) 2020 YaoYuan <ibireme@gmail.com>\n\n Permission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to deal\n in the Software without restriction, including without limitation the rights\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\n The above copyright notice and this permission notice shall be included in all\n copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n SOFTWARE.\n *============================================================================*/\n\n#include \"yyjson.h\"\n#include <math.h> /* for `HUGE_VAL/INFINIY/NAN` macros, no libm required */\n\n\n\n/*==============================================================================\n * MARK: - Warning Suppress (Private)\n *============================================================================*/\n\n#if defined(__clang__)\n#   pragma clang diagnostic ignored \"-Wunused-function\"\n#   pragma clang diagnostic ignored \"-Wunused-parameter\"\n#   pragma clang diagnostic ignored \"-Wunused-label\"\n#   pragma clang diagnostic ignored \"-Wunused-macros\"\n#   pragma clang diagnostic ignored \"-Wunused-variable\"\n#elif defined(__GNUC__)\n#   pragma GCC diagnostic ignored \"-Wunused-function\"\n#   pragma GCC diagnostic ignored \"-Wunused-parameter\"\n#   pragma GCC diagnostic ignored \"-Wunused-label\"\n#   pragma GCC diagnostic ignored \"-Wunused-macros\"\n#   pragma GCC diagnostic ignored \"-Wunused-variable\"\n#elif defined(_MSC_VER)\n#   pragma warning(disable:4100) /* unreferenced formal parameter */\n#   pragma warning(disable:4101) /* unreferenced variable */\n#   pragma warning(disable:4102) /* unreferenced label */\n#   pragma warning(disable:4127) /* conditional expression is constant */\n#   pragma warning(disable:4706) /* assignment within conditional expression */\n#endif\n\n\n\n/*==============================================================================\n * MARK: - Version (Public)\n *============================================================================*/\n\nuint32_t yyjson_version(void) {\n    return YYJSON_VERSION_HEX;\n}\n\n\n\n/*==============================================================================\n * MARK: - Flags (Private)\n *============================================================================*/\n\n/* msvc intrinsic */\n#if YYJSON_MSC_VER >= 1400\n#   include <intrin.h>\n#   if defined(_M_AMD64) || defined(_M_ARM64)\n#       define MSC_HAS_BIT_SCAN_64 1\n#       pragma intrinsic(_BitScanForward64)\n#       pragma intrinsic(_BitScanReverse64)\n#   else\n#       define MSC_HAS_BIT_SCAN_64 0\n#   endif\n#   if defined(_M_AMD64) || defined(_M_ARM64) || \\\n        defined(_M_IX86) || defined(_M_ARM)\n#       define MSC_HAS_BIT_SCAN 1\n#       pragma intrinsic(_BitScanForward)\n#       pragma intrinsic(_BitScanReverse)\n#   else\n#       define MSC_HAS_BIT_SCAN 0\n#   endif\n#   if defined(_M_AMD64)\n#       define MSC_HAS_UMUL128 1\n#       pragma intrinsic(_umul128)\n#   else\n#       define MSC_HAS_UMUL128 0\n#   endif\n#else\n#   define MSC_HAS_BIT_SCAN_64 0\n#   define MSC_HAS_BIT_SCAN 0\n#   define MSC_HAS_UMUL128 0\n#endif\n\n/* gcc builtin */\n#if yyjson_has_builtin(__builtin_clzll) || yyjson_gcc_available(3, 4, 0)\n#   define GCC_HAS_CLZLL 1\n#else\n#   define GCC_HAS_CLZLL 0\n#endif\n\n#if yyjson_has_builtin(__builtin_ctzll) || yyjson_gcc_available(3, 4, 0)\n#   define GCC_HAS_CTZLL 1\n#else\n#   define GCC_HAS_CTZLL 0\n#endif\n\n/* int128 type */\n#if defined(__SIZEOF_INT128__) && (__SIZEOF_INT128__ == 16) && \\\n    (defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER))\n#    define YYJSON_HAS_INT128 1\n#else\n#    define YYJSON_HAS_INT128 0\n#endif\n\n/* IEEE 754 floating-point binary representation */\n#if defined(__STDC_IEC_559__) || defined(__STDC_IEC_60559_BFP__)\n#   define YYJSON_HAS_IEEE_754 1\n#elif FLT_RADIX == 2 && \\\n    FLT_MANT_DIG == 24 && FLT_DIG == 6 && \\\n    FLT_MIN_EXP == -125 && FLT_MAX_EXP == 128 && \\\n    FLT_MIN_10_EXP == -37 && FLT_MAX_10_EXP == 38 && \\\n    DBL_MANT_DIG == 53 && DBL_DIG == 15 && \\\n    DBL_MIN_EXP == -1021 && DBL_MAX_EXP == 1024 && \\\n    DBL_MIN_10_EXP == -307 && DBL_MAX_10_EXP == 308\n#   define YYJSON_HAS_IEEE_754 1\n#else\n#   define YYJSON_HAS_IEEE_754 0\n#   undef  YYJSON_DISABLE_FAST_FP_CONV\n#   define YYJSON_DISABLE_FAST_FP_CONV 1\n#endif\n\n/*\n Correct rounding in double number computations.\n\n On the x86 architecture, some compilers may use x87 FPU instructions for\n floating-point arithmetic. The x87 FPU loads all floating point number as\n 80-bit double-extended precision internally, then rounds the result to original\n precision, which may produce inaccurate results. For a more detailed\n explanation, see the paper: https://arxiv.org/abs/cs/0701192\n\n Here are some examples of double precision calculation error:\n\n     2877.0 / 1e6   == 0.002877,  but x87 returns 0.0028770000000000002\n     43683.0 * 1e21 == 4.3683e25, but x87 returns 4.3683000000000004e25\n\n Here are some examples of compiler flags to generate x87 instructions on x86:\n\n     clang -m32 -mno-sse\n     gcc/icc -m32 -mfpmath=387\n     msvc /arch:SSE or /arch:IA32\n\n If we are sure that there's no similar error described above, we can define the\n YYJSON_DOUBLE_MATH_CORRECT as 1 to enable the fast path calculation. This is\n not an accurate detection, it's just try to avoid the error at compile-time.\n An accurate detection can be done at run-time:\n\n     bool is_double_math_correct(void) {\n         volatile double r = 43683.0;\n         r *= 1e21;\n         return r == 4.3683e25;\n     }\n\n See also: utils.h in https://github.com/google/double-conversion/\n */\n#if !defined(FLT_EVAL_METHOD) && defined(__FLT_EVAL_METHOD__)\n#    define FLT_EVAL_METHOD __FLT_EVAL_METHOD__\n#endif\n\n#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0 && FLT_EVAL_METHOD != 1\n#    define YYJSON_DOUBLE_MATH_CORRECT 0\n#elif defined(i386) || defined(__i386) || defined(__i386__) || \\\n    defined(_X86_) || defined(__X86__) || defined(_M_IX86) || \\\n    defined(__I86__) || defined(__IA32__) || defined(__THW_INTEL)\n#   if (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 2) || \\\n        (defined(__SSE2_MATH__) && __SSE2_MATH__)\n#       define YYJSON_DOUBLE_MATH_CORRECT 1\n#   else\n#       define YYJSON_DOUBLE_MATH_CORRECT 0\n#   endif\n#elif defined(__mc68000__) || defined(__pnacl__) || defined(__native_client__)\n#   define YYJSON_DOUBLE_MATH_CORRECT 0\n#else\n#   define YYJSON_DOUBLE_MATH_CORRECT 1\n#endif\n\n/*\n Detect the endianness at compile-time.\n YYJSON_ENDIAN == YYJSON_BIG_ENDIAN\n YYJSON_ENDIAN == YYJSON_LITTLE_ENDIAN\n */\n#define YYJSON_BIG_ENDIAN       4321\n#define YYJSON_LITTLE_ENDIAN    1234\n\n#if yyjson_has_include(<sys/types.h>)\n#    include <sys/types.h> /* POSIX */\n#endif\n#if yyjson_has_include(<endian.h>)\n#    include <endian.h> /* Linux */\n#elif yyjson_has_include(<sys/endian.h>)\n#    include <sys/endian.h> /* BSD, Android */\n#elif yyjson_has_include(<machine/endian.h>)\n#    include <machine/endian.h> /* BSD, Darwin */\n#endif\n\n#if defined(BYTE_ORDER) && BYTE_ORDER\n#   if defined(BIG_ENDIAN) && (BYTE_ORDER == BIG_ENDIAN)\n#       define YYJSON_ENDIAN YYJSON_BIG_ENDIAN\n#   elif defined(LITTLE_ENDIAN) && (BYTE_ORDER == LITTLE_ENDIAN)\n#       define YYJSON_ENDIAN YYJSON_LITTLE_ENDIAN\n#   endif\n#elif defined(__BYTE_ORDER) && __BYTE_ORDER\n#   if defined(__BIG_ENDIAN) && (__BYTE_ORDER == __BIG_ENDIAN)\n#       define YYJSON_ENDIAN YYJSON_BIG_ENDIAN\n#   elif defined(__LITTLE_ENDIAN) && (__BYTE_ORDER == __LITTLE_ENDIAN)\n#       define YYJSON_ENDIAN YYJSON_LITTLE_ENDIAN\n#   endif\n#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__\n#   if defined(__ORDER_BIG_ENDIAN__) && \\\n        (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)\n#       define YYJSON_ENDIAN YYJSON_BIG_ENDIAN\n#   elif defined(__ORDER_LITTLE_ENDIAN__) && \\\n        (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)\n#       define YYJSON_ENDIAN YYJSON_LITTLE_ENDIAN\n#   endif\n#elif (defined(__LITTLE_ENDIAN__) && __LITTLE_ENDIAN__ == 1) || \\\n    defined(__i386) || defined(__i386__) || \\\n    defined(_X86_) || defined(__X86__) || \\\n    defined(_M_IX86) || defined(__THW_INTEL__) || \\\n    defined(__x86_64) || defined(__x86_64__) || \\\n    defined(__amd64) || defined(__amd64__) || \\\n    defined(_M_AMD64) || defined(_M_X64) || \\\n    defined(_M_ARM) || defined(_M_ARM64) || \\\n    defined(__ARMEL__) || defined(__THUMBEL__) || defined(__AARCH64EL__) || \\\n    defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) || \\\n    defined(__EMSCRIPTEN__) || defined(__wasm__) || \\\n    defined(__loongarch__)\n#   define YYJSON_ENDIAN YYJSON_LITTLE_ENDIAN\n#elif (defined(__BIG_ENDIAN__) && __BIG_ENDIAN__ == 1) || \\\n    defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \\\n    defined(_MIPSEB) || defined(__MIPSEB) || defined(__MIPSEB__) || \\\n    defined(__or1k__) || defined(__OR1K__)\n#   define YYJSON_ENDIAN YYJSON_BIG_ENDIAN\n#else\n#   define YYJSON_ENDIAN 0 /* unknown endian, detect at run-time */\n#endif\n\n/*\n This macro controls how yyjson handles unaligned memory accesses.\n\n By default, yyjson uses `memcpy()` for memory copying. This allows the compiler\n to optimize the code and emit unaligned memory access instructions when\n supported by the target architecture.\n\n However, on some older compilers or architectures where `memcpy()` is not\n well-optimized and may result in unnecessary function calls, defining this\n macro as 1 may help. In such cases, yyjson switches to manual byte-by-byte\n access, which can potentially improve performance.\n\n An example of the generated assembly code for ARM can be found here:\n https://godbolt.org/z/334jjhxPT\n\n This flag is already enabled for common architectures in the following code,\n so manual configuration is usually unnecessary. If unsure, you can check the\n generated assembly or run benchmarks to make an informed decision.\n */\n#ifndef YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS\n#   if defined(__ia64) || defined(_IA64) || defined(__IA64__) ||  \\\n        defined(__ia64__) || defined(_M_IA64) || defined(__itanium__)\n#       define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* Itanium */\n#   elif (defined(__arm__) || defined(__arm64__) || defined(__aarch64__)) && \\\n        (defined(__GNUC__) || defined(__clang__)) && \\\n        (!defined(__ARM_FEATURE_UNALIGNED) || !__ARM_FEATURE_UNALIGNED)\n#       define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* ARM */\n#   elif defined(__sparc) || defined(__sparc__)\n#       define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* SPARC */\n#   elif defined(__mips) || defined(__mips__) || defined(__MIPS__)\n#       define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* MIPS */\n#   elif defined(__m68k__) || defined(M68000)\n#       define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* M68K */\n#   else\n#       define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 0\n#   endif\n#endif\n\n/*\n Estimated initial ratio of the JSON data (data_size / value_count).\n For example:\n\n    data:        {\"id\":12345678,\"name\":\"Harry\"}\n    data_size:   30\n    value_count: 5\n    ratio:       6\n\n yyjson uses dynamic memory with a growth factor of 1.5 when reading and writing\n JSON, the ratios below are used to determine the initial memory size.\n\n A too large ratio will waste memory, and a too small ratio will cause multiple\n memory growths and degrade performance. Currently, these ratios are generated\n with some commonly used JSON datasets.\n */\n#define YYJSON_READER_ESTIMATED_PRETTY_RATIO 16\n#define YYJSON_READER_ESTIMATED_MINIFY_RATIO 6\n#define YYJSON_WRITER_ESTIMATED_PRETTY_RATIO 32\n#define YYJSON_WRITER_ESTIMATED_MINIFY_RATIO 18\n\n/* The initial and maximum size of the memory pool's chunk in yyjson_mut_doc. */\n#define YYJSON_MUT_DOC_STR_POOL_INIT_SIZE   0x100\n#define YYJSON_MUT_DOC_STR_POOL_MAX_SIZE    0x10000000\n#define YYJSON_MUT_DOC_VAL_POOL_INIT_SIZE   (0x10 * sizeof(yyjson_mut_val))\n#define YYJSON_MUT_DOC_VAL_POOL_MAX_SIZE    (0x1000000 * sizeof(yyjson_mut_val))\n\n/* The minimum size of the dynamic allocator's chunk. */\n#define YYJSON_ALC_DYN_MIN_SIZE             0x1000\n\n/* Default value for compile-time options. */\n#ifndef YYJSON_DISABLE_READER\n#define YYJSON_DISABLE_READER 0\n#endif\n#ifndef YYJSON_DISABLE_WRITER\n#define YYJSON_DISABLE_WRITER 0\n#endif\n#ifndef YYJSON_DISABLE_INCR_READER\n#define YYJSON_DISABLE_INCR_READER 0\n#endif\n#ifndef YYJSON_DISABLE_UTILS\n#define YYJSON_DISABLE_UTILS 0\n#endif\n#ifndef YYJSON_DISABLE_FAST_FP_CONV\n#define YYJSON_DISABLE_FAST_FP_CONV 0\n#endif\n#ifndef YYJSON_DISABLE_NON_STANDARD\n#define YYJSON_DISABLE_NON_STANDARD 0\n#endif\n#ifndef YYJSON_DISABLE_UTF8_VALIDATION\n#define YYJSON_DISABLE_UTF8_VALIDATION 0\n#endif\n\n\n\n/*==============================================================================\n * MARK: - Macros (Private)\n *============================================================================*/\n\n/* Macros used for loop unrolling and other purpose. */\n#define repeat2(x)  { x x }\n#define repeat4(x)  { x x x x }\n#define repeat8(x)  { x x x x x x x x }\n#define repeat16(x) { x x x x x x x x x x x x x x x x }\n\n#define repeat2_incr(x)   { x(0)  x(1) }\n#define repeat4_incr(x)   { x(0)  x(1)  x(2)  x(3) }\n#define repeat8_incr(x)   { x(0)  x(1)  x(2)  x(3)  x(4)  x(5)  x(6)  x(7)  }\n#define repeat16_incr(x)  { x(0)  x(1)  x(2)  x(3)  x(4)  x(5)  x(6)  x(7)  \\\n                            x(8)  x(9)  x(10) x(11) x(12) x(13) x(14) x(15) }\n#define repeat_in_1_18(x) { x(1)  x(2)  x(3)  x(4)  x(5)  x(6)  x(7)  x(8)  \\\n                            x(9)  x(10) x(11) x(12) x(13) x(14) x(15) x(16) \\\n                            x(17) x(18) }\n\n/* Macros used to provide branch prediction information for compiler. */\n#undef  likely\n#define likely(x)       yyjson_likely(x)\n#undef  unlikely\n#define unlikely(x)     yyjson_unlikely(x)\n\n/* Macros used to provide inline information for compiler. */\n#undef  static_inline\n#define static_inline   static yyjson_inline\n#undef  static_noinline\n#define static_noinline static yyjson_noinline\n\n/* Macros for min and max. */\n#undef  yyjson_min\n#define yyjson_min(x, y) ((x) < (y) ? (x) : (y))\n#undef  yyjson_max\n#define yyjson_max(x, y) ((x) > (y) ? (x) : (y))\n\n/* Used to write u64 literal for C89 which doesn't support \"ULL\" suffix. */\n#undef  U64\n#define U64(hi, lo) ((((u64)hi##UL) << 32U) + lo##UL)\n#undef  U32\n#define U32(hi) ((u32)(hi##UL))\n\n/* Used to cast away (remove) const qualifier. */\n#define constcast(type) (type)(void *)(size_t)(const void *)\n\n/*\n Compiler barriers for single variables.\n\n These macros inform GCC that a read or write access to the given memory\n location will occur, preventing certain compiler optimizations or reordering\n around the access to 'val'. They do not emit any actual instructions.\n\n This is useful when GCC's default optimization strategies are suboptimal and\n precise control over memory access patterns is required.\n These barriers are not needed when using Clang or MSVC.\n */\n#if YYJSON_IS_REAL_GCC\n#   define gcc_load_barrier(val)   __asm__ volatile(\"\"::\"m\"(val))\n#   define gcc_store_barrier(val)  __asm__ volatile(\"\":\"=m\"(val))\n#   define gcc_full_barrier(val)   __asm__ volatile(\"\":\"=m\"(val):\"m\"(val))\n#else\n#   define gcc_load_barrier(val)\n#   define gcc_store_barrier(val)\n#   define gcc_full_barrier(val)\n#endif\n\n\n\n/*==============================================================================\n * MARK: - Constants (Private)\n *============================================================================*/\n\n/* Common error messages. */\n#define MSG_FOPEN       \"failed to open file\"\n#define MSG_FREAD       \"failed to read file\"\n#define MSG_FWRITE      \"failed to write file\"\n#define MSG_FCLOSE      \"failed to close file\"\n#define MSG_MALLOC      \"failed to allocate memory\"\n#define MSG_CHAR_T      \"invalid literal, expected 'true'\"\n#define MSG_CHAR_F      \"invalid literal, expected 'false'\"\n#define MSG_CHAR_N      \"invalid literal, expected 'null'\"\n#define MSG_CHAR        \"unexpected character, expected a JSON value\"\n#define MSG_ARR_END     \"unexpected character, expected ',' or ']'\"\n#define MSG_OBJ_KEY     \"unexpected character, expected a string key\"\n#define MSG_OBJ_SEP     \"unexpected character, expected ':' after key\"\n#define MSG_OBJ_END     \"unexpected character, expected ',' or '}'\"\n#define MSG_GARBAGE     \"unexpected content after document\"\n#define MSG_NOT_END     \"unexpected end of data\"\n#define MSG_COMMENT     \"unclosed multiline comment\"\n#define MSG_COMMA       \"trailing comma is not allowed\"\n#define MSG_NAN_INF     \"nan or inf number is not allowed\"\n#define MSG_ERR_TYPE    \"invalid JSON value type\"\n#define MSG_ERR_BOM     \"UTF-8 byte order mark (BOM) is not supported\"\n#define MSG_ERR_UTF8    \"invalid utf-8 encoding in string\"\n#define MSG_ERR_UTF16   \"UTF-16 encoding is not supported\"\n#define MSG_ERR_UTF32   \"UTF-32 encoding is not supported\"\n\n/* U64 constant values */\n#undef  U64_MAX\n#define U64_MAX         U64(0xFFFFFFFF, 0xFFFFFFFF)\n#undef  I64_MAX\n#define I64_MAX         U64(0x7FFFFFFF, 0xFFFFFFFF)\n#undef  USIZE_MAX\n#define USIZE_MAX       ((usize)(~(usize)0))\n\n/* Maximum number of digits for reading u32/u64/usize safety (not overflow). */\n#undef  U32_SAFE_DIG\n#define U32_SAFE_DIG    9   /* u32 max is 4294967295, 10 digits */\n#undef  U64_SAFE_DIG\n#define U64_SAFE_DIG    19  /* u64 max is 18446744073709551615, 20 digits */\n#undef  USIZE_SAFE_DIG\n#define USIZE_SAFE_DIG  (sizeof(usize) == 8 ? U64_SAFE_DIG : U32_SAFE_DIG)\n\n/* Inf bits (positive) */\n#define F64_BITS_INF U64(0x7FF00000, 0x00000000)\n\n/* NaN bits (quiet NaN, no payload, no sign) */\n#if defined(__hppa__) || (defined(__mips__) && !defined(__mips_nan2008))\n#define F64_BITS_NAN U64(0x7FF7FFFF, 0xFFFFFFFF)\n#else\n#define F64_BITS_NAN U64(0x7FF80000, 0x00000000)\n#endif\n\n/* maximum significant digits count in decimal when reading double number */\n#define F64_MAX_DEC_DIG 768\n\n/* maximum decimal power of double number (1.7976931348623157e308) */\n#define F64_MAX_DEC_EXP 308\n\n/* minimum decimal power of double number (4.9406564584124654e-324) */\n#define F64_MIN_DEC_EXP (-324)\n\n/* maximum binary power of double number */\n#define F64_MAX_BIN_EXP 1024\n\n/* minimum binary power of double number */\n#define F64_MIN_BIN_EXP (-1021)\n\n/* float/double number bits */\n#define F32_BITS 32\n#define F64_BITS 64\n\n/* float/double number exponent part bits */\n#define F32_EXP_BITS 8\n#define F64_EXP_BITS 11\n\n/* float/double number significand part bits */\n#define F32_SIG_BITS 23\n#define F64_SIG_BITS 52\n\n/* float/double number significand part bits (with 1 hidden bit) */\n#define F32_SIG_FULL_BITS 24\n#define F64_SIG_FULL_BITS 53\n\n/* float/double number significand bit mask */\n#define F32_SIG_MASK U32(0x007FFFFF)\n#define F64_SIG_MASK U64(0x000FFFFF, 0xFFFFFFFF)\n\n/* float/double number exponent bit mask */\n#define F32_EXP_MASK U32(0x7F800000)\n#define F64_EXP_MASK U64(0x7FF00000, 0x00000000)\n\n/* float/double number exponent bias */\n#define F32_EXP_BIAS 127\n#define F64_EXP_BIAS 1023\n\n/* float/double number significant digits count in decimal */\n#define F32_DEC_DIG 9\n#define F64_DEC_DIG 17\n\n/* buffer length required for float/double number writer */\n#define FP_BUF_LEN 40\n\n/* maximum length of a number in incremental parsing */\n#define INCR_NUM_MAX_LEN 1024\n\n\n\n/*==============================================================================\n * MARK: - Types (Private)\n *============================================================================*/\n\n/** Type define for primitive types. */\ntypedef float       f32;\ntypedef double      f64;\ntypedef int8_t      i8;\ntypedef uint8_t     u8;\ntypedef int16_t     i16;\ntypedef uint16_t    u16;\ntypedef int32_t     i32;\ntypedef uint32_t    u32;\ntypedef int64_t     i64;\ntypedef uint64_t    u64;\ntypedef size_t      usize;\n\n/** 128-bit integer, used by floating-point number reader and writer. */\n#if YYJSON_HAS_INT128\n__extension__ typedef __int128          i128;\n__extension__ typedef unsigned __int128 u128;\n#endif\n\n/** 16/32/64-bit vector */\ntypedef struct v16 { char c[2]; } v16;\ntypedef struct v32 { char c[4]; } v32;\ntypedef struct v64 { char c[8]; } v64;\n\n/** 16/32/64-bit vector union */\ntypedef union v16_uni { v16 v; u16 u; } v16_uni;\ntypedef union v32_uni { v32 v; u32 u; } v32_uni;\ntypedef union v64_uni { v64 v; u64 u; } v64_uni;\n\n\n\n/*==============================================================================\n * MARK: - Load/Store Utils (Private)\n *============================================================================*/\n\n#define byte_move_idx(x) ((char *)dst)[x] = ((const char *)src)[x];\n#define byte_move_src(x) ((char *)tmp)[x] = ((const char *)src)[x];\n#define byte_move_dst(x) ((char *)dst)[x] = ((const char *)tmp)[x];\n\n/** Same as `memcpy(dst, src, 2)`, no overlap. */\nstatic_inline void byte_copy_2(void *dst, const void *src) {\n#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS\n    memcpy(dst, src, 2);\n#else\n    repeat2_incr(byte_move_idx)\n#endif\n}\n\n/** Same as `memcpy(dst, src, 4)`, no overlap. */\nstatic_inline void byte_copy_4(void *dst, const void *src) {\n#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS\n    memcpy(dst, src, 4);\n#else\n    repeat4_incr(byte_move_idx)\n#endif\n}\n\n/** Same as `memcpy(dst, src, 8)`, no overlap. */\nstatic_inline void byte_copy_8(void *dst, const void *src) {\n#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS\n    memcpy(dst, src, 8);\n#else\n    repeat8_incr(byte_move_idx)\n#endif\n}\n\n/** Same as `memcpy(dst, src, 16)`, no overlap. */\nstatic_inline void byte_copy_16(void *dst, const void *src) {\n#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS\n    memcpy(dst, src, 16);\n#else\n    repeat16_incr(byte_move_idx)\n#endif\n}\n\n/** Same as `memmove(dst, src, 2)`, allows overlap. */\nstatic_inline void byte_move_2(void *dst, const void *src) {\n#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS\n    u16 tmp;\n    memcpy(&tmp, src, 2);\n    memcpy(dst, &tmp, 2);\n#else\n    char tmp[2];\n    repeat2_incr(byte_move_src)\n    repeat2_incr(byte_move_dst)\n#endif\n}\n\n/** Same as `memmove(dst, src, 4)`, allows overlap. */\nstatic_inline void byte_move_4(void *dst, const void *src) {\n#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS\n    u32 tmp;\n    memcpy(&tmp, src, 4);\n    memcpy(dst, &tmp, 4);\n#else\n    char tmp[4];\n    repeat4_incr(byte_move_src)\n    repeat4_incr(byte_move_dst)\n#endif\n}\n\n/** Same as `memmove(dst, src, 8)`, allows overlap. */\nstatic_inline void byte_move_8(void *dst, const void *src) {\n#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS\n    u64 tmp;\n    memcpy(&tmp, src, 8);\n    memcpy(dst, &tmp, 8);\n#else\n    char tmp[8];\n    repeat8_incr(byte_move_src)\n    repeat8_incr(byte_move_dst)\n#endif\n}\n\n/** Same as `memmove(dst, src, 16)`, allows overlap. */\nstatic_inline void byte_move_16(void *dst, const void *src) {\n#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS\n    char *pdst = (char *)dst;\n    const char *psrc = (const char *)src;\n    u64 tmp1, tmp2;\n    memcpy(&tmp1, psrc, 8);\n    memcpy(&tmp2, psrc + 8, 8);\n    memcpy(pdst, &tmp1, 8);\n    memcpy(pdst + 8, &tmp2, 8);\n#else\n    char tmp[16];\n    repeat16_incr(byte_move_src)\n    repeat16_incr(byte_move_dst)\n#endif\n}\n\n/** Same as `memmove(dst, src, n)`, but only `dst <= src` and `n <= 16`. */\nstatic_inline void byte_move_forward(void *dst, void *src, usize n) {\n    char *d = (char *)dst, *s = (char *)src;\n    n += (n % 2); /* round up to even */\n    if (n == 16) { byte_move_16(d, s); return; }\n    if (n >= 8) { byte_move_8(d, s); n -= 8; d += 8; s += 8; }\n    if (n >= 4) { byte_move_4(d, s); n -= 4; d += 4; s += 4; }\n    if (n >= 2) { byte_move_2(d, s); }\n}\n\n/** Same as `memcmp(buf, pat, 2) == 0`. */\nstatic_inline bool byte_match_2(void *buf, const char *pat) {\n#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS\n    v16_uni u1, u2;\n    memcpy(&u1, buf, 2);\n    memcpy(&u2, pat, 2);\n    return u1.u == u2.u;\n#else\n    return ((char *)buf)[0] == ((const char *)pat)[0] &&\n           ((char *)buf)[1] == ((const char *)pat)[1];\n#endif\n}\n\n/** Same as `memcmp(buf, pat, 4) == 0`. */\nstatic_inline bool byte_match_4(void *buf, const char *pat) {\n#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS\n    v32_uni u1, u2;\n    memcpy(&u1, buf, 4);\n    memcpy(&u2, pat, 4);\n    return u1.u == u2.u;\n#else\n    return ((char *)buf)[0] == ((const char *)pat)[0] &&\n           ((char *)buf)[1] == ((const char *)pat)[1] &&\n           ((char *)buf)[2] == ((const char *)pat)[2] &&\n           ((char *)buf)[3] == ((const char *)pat)[3];\n#endif\n}\n\n/** Loads 2 bytes from `src` as a u16 (native-endian). */\nstatic_inline u16 byte_load_2(const void *src) {\n    v16_uni uni;\n#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS\n    memcpy(&uni, src, 2);\n#else\n    uni.v.c[0] = ((const char *)src)[0];\n    uni.v.c[1] = ((const char *)src)[1];\n#endif\n    return uni.u;\n}\n\n/** Loads 3 bytes from `src` as a u32 (native-endian). */\nstatic_inline u32 byte_load_3(const void *src) {\n    v32_uni uni;\n#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS\n    memcpy(&uni, src, 2);\n    uni.v.c[2] = ((const char *)src)[2];\n    uni.v.c[3] = 0;\n#else\n    uni.v.c[0] = ((const char *)src)[0];\n    uni.v.c[1] = ((const char *)src)[1];\n    uni.v.c[2] = ((const char *)src)[2];\n    uni.v.c[3] = 0;\n#endif\n    return uni.u;\n}\n\n/** Loads 4 bytes from `src` as a u32 (native-endian). */\nstatic_inline u32 byte_load_4(const void *src) {\n    v32_uni uni;\n#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS\n    memcpy(&uni, src, 4);\n#else\n    uni.v.c[0] = ((const char *)src)[0];\n    uni.v.c[1] = ((const char *)src)[1];\n    uni.v.c[2] = ((const char *)src)[2];\n    uni.v.c[3] = ((const char *)src)[3];\n#endif\n    return uni.u;\n}\n\n\n\n/*==============================================================================\n * MARK: - Character Utils (Private)\n * These lookup tables were generated by `misc/make_tables.c`.\n *============================================================================*/\n\n/* char_table1 */\n#define CHAR_TYPE_ASCII     (1 << 0) /* Except: [\"\\], [0x00-0x1F, 0x80-0xFF] */\n#define CHAR_TYPE_ASCII_SQ  (1 << 1) /* Except: ['\\], [0x00-0x1F, 0x80-0xFF] */\n#define CHAR_TYPE_SPACE     (1 << 2) /* Whitespace: [ \\t\\n\\r] */\n#define CHAR_TYPE_SPACE_EXT (1 << 3) /* Whitespace: [ \\t\\n\\r\\v\\f], JSON5 */\n#define CHAR_TYPE_NUM       (1 << 4) /* Number: [.-+0-9] */\n#define CHAR_TYPE_COMMENT   (1 << 5) /* Comment: [/] */\n\n/* char_table2 */\n#define CHAR_TYPE_EOL       (1 << 0) /* End of line: [\\r\\n] */\n#define CHAR_TYPE_EOL_EXT   (1 << 1) /* End of line: [\\r\\n], JSON5 */\n#define CHAR_TYPE_ID_START  (1 << 2) /* ID start: [_$A-Za-z\\], U+0080+ */\n#define CHAR_TYPE_ID_NEXT   (1 << 3) /* ID next: [_$A-Za-z0-9\\], U+0080+ */\n#define CHAR_TYPE_ID_ASCII  (1 << 4) /* ID next ASCII: [_$A-Za-z0-9] */\n\n/* char_table3 */\n#define CHAR_TYPE_SIGN      (1 << 0) /* [-+] */\n#define CHAR_TYPE_DIGIT     (1 << 1) /* [0-9] */\n#define CHAR_TYPE_NONZERO   (1 << 2) /* [1-9] */\n#define CHAR_TYPE_EXP       (1 << 3) /* [eE] */\n#define CHAR_TYPE_DOT       (1 << 4) /* [.] */\n\nstatic const u8 char_table1[256] = {\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x0C, 0x0C, 0x08, 0x08, 0x0C, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x0F, 0x03, 0x02, 0x03, 0x03, 0x03, 0x03, 0x01,\n    0x03, 0x03, 0x03, 0x13, 0x03, 0x13, 0x13, 0x23,\n    0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,\n    0x13, 0x13, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,\n    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,\n    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,\n    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,\n    0x03, 0x03, 0x03, 0x03, 0x00, 0x03, 0x03, 0x03,\n    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,\n    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,\n    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,\n    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00\n};\n\nstatic const u8 char_table2[256] = {\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,\n    0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C,\n    0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C,\n    0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C,\n    0x1C, 0x1C, 0x1C, 0x00, 0x0C, 0x00, 0x00, 0x1C,\n    0x00, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C,\n    0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C,\n    0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C,\n    0x1C, 0x1C, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,\n    0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,\n    0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,\n    0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,\n    0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,\n    0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,\n    0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,\n    0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,\n    0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,\n    0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,\n    0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,\n    0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,\n    0x0C, 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,\n    0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,\n    0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,\n    0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C\n};\n\nstatic const u8 char_table3[256] = {\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x10, 0x00,\n    0x02, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,\n    0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00\n};\n\n/** Match a whitespace: [ \\t\\n\\r]. */\nstatic_inline bool char_is_space(u8 c) {\n    return !!(char_table1[c] & CHAR_TYPE_SPACE);\n}\n\n/** Match an extended whitespace: [ \\t\\n\\r\\\\v\\\\f], JSON5 whitespace. */\nstatic_inline bool char_is_space_ext(u8 c) {\n    return !!(char_table1[c] & CHAR_TYPE_SPACE_EXT);\n}\n\n/** Match a JSON number: [.-+0-9]. */\nstatic_inline bool char_is_num(u8 c) {\n    return !!(char_table1[c] & CHAR_TYPE_NUM);\n}\n\n/** Match an ASCII character in string: [\"\\], [0x00-0x1F, 0x80-0xFF]. */\nstatic_inline bool char_is_ascii_skip(u8 c) {\n    return !!(char_table1[c] & CHAR_TYPE_ASCII);\n}\n\n/** Match an ASCII character single-quoted: ['\\], [0x00-0x1F, 0x80-0xFF]. */\nstatic_inline bool char_is_ascii_skip_sq(u8 c) {\n    return !!(char_table1[c] & CHAR_TYPE_ASCII_SQ);\n}\n\n/** Match a trivia character: extended whitespace or comment. */\nstatic_inline bool char_is_trivia(u8 c) {\n    return !!(char_table1[c] & (CHAR_TYPE_SPACE_EXT | CHAR_TYPE_COMMENT));\n}\n\n/** Match a line end character: [\\r\\n]. */\nstatic_inline bool char_is_eol(u8 c) {\n    return !!(char_table2[c] & CHAR_TYPE_EOL);\n}\n\n/** Match an extended line end character: [\\r\\n], JSON5 line terminator. */\nstatic_inline bool char_is_eol_ext(u8 c) {\n    return !!(char_table2[c] & CHAR_TYPE_EOL_EXT);\n}\n\n/** Match an identifier name start: [_$A-Za-z\\], U+0080+. */\nstatic_inline bool char_is_id_start(u8 c) {\n    return !!(char_table2[c] & CHAR_TYPE_ID_START);\n}\n\n/** Match an identifier name next: [_$A-Za-z0-9\\], U+0080+. */\nstatic_inline bool char_is_id_next(u8 c) {\n    return !!(char_table2[c] & CHAR_TYPE_ID_NEXT);\n}\n\n/** Match an identifier name ASCII: [_$A-Za-z0-9]. */\nstatic_inline bool char_is_id_ascii(u8 c) {\n    return !!(char_table2[c] & CHAR_TYPE_ID_ASCII);\n}\n\n/** Match a sign: [+-] */\nstatic_inline bool char_is_sign(u8 d) {\n    return !!(char_table3[d] & CHAR_TYPE_SIGN);\n}\n\n/** Match a none-zero digit: [1-9] */\nstatic_inline bool char_is_nonzero(u8 d) {\n    return !!(char_table3[d] & CHAR_TYPE_NONZERO);\n}\n\n/** Match a digit: [0-9] */\nstatic_inline bool char_is_digit(u8 d) {\n    return !!(char_table3[d] & CHAR_TYPE_DIGIT);\n}\n\n/** Match an exponent sign: [eE]. */\nstatic_inline bool char_is_exp(u8 d) {\n    return !!(char_table3[d] & CHAR_TYPE_EXP);\n}\n\n/** Match a floating point indicator: [.eE]. */\nstatic_inline bool char_is_fp(u8 d) {\n    return !!(char_table3[d] & (CHAR_TYPE_DOT | CHAR_TYPE_EXP));\n}\n\n/** Match a digit or floating point indicator: [0-9.eE]. */\nstatic_inline bool char_is_digit_or_fp(u8 d) {\n    return !!(char_table3[d] & (CHAR_TYPE_DIGIT | CHAR_TYPE_DOT |\n                                CHAR_TYPE_EXP));\n}\n\n/** Match a JSON container: `{` or `[`. */\nstatic_inline bool char_is_ctn(u8 c) {\n    return (c & 0xDF) == 0x5B; /* '[': 0x5B, '{': 0x7B */\n}\n\n/** Convert ASCII letter to lowercase; valid only for [A-Za-z]. */\nstatic_inline u8 char_to_lower(u8 c) {\n    return c | 0x20;\n}\n\n/** Match UTF-8 byte order mask. */\nstatic_inline bool is_utf8_bom(const u8 *cur) {\n    return byte_load_3(cur) == byte_load_3(\"\\xEF\\xBB\\xBF\");\n}\n\n/** Match UTF-16 byte order mask. */\nstatic_inline bool is_utf16_bom(const u8 *cur) {\n    return byte_load_2(cur) == byte_load_2(\"\\xFE\\xFF\") ||\n           byte_load_2(cur) == byte_load_2(\"\\xFF\\xFE\");\n}\n\n/** Match UTF-32 byte order mask, need length check to avoid zero padding. */\nstatic_inline bool is_utf32_bom(const u8 *cur) {\n    return byte_load_4(cur) == byte_load_4(\"\\x00\\x00\\xFE\\xFF\") ||\n           byte_load_4(cur) == byte_load_4(\"\\xFF\\xFE\\x00\\x00\");\n}\n\n/** Get the extended line end length. Used with `char_is_eol_ext`. */\nstatic_inline usize ext_eol_len(const u8 *cur) {\n    if (cur[0] < 0x80) return 1;\n    if (cur[1] == 0x80 && (cur[2] == 0xA8 || cur[2] == 0xA9)) return 3;\n    return 0;\n}\n\n/** Get the extended whitespace length. Used with `char_is_space_ext`. */\nstatic_inline usize ext_space_len(const u8 *cur) {\n    if (cur[0] < 0x80) {\n        return 1;\n    } else if (byte_load_2(cur) == byte_load_2(\"\\xC2\\xA0\")) {\n        return 2;\n    } else if (byte_load_2(cur) == byte_load_2(\"\\xE2\\x80\")) {\n        if (cur[2] >= 0x80 && cur[2] <= 0x8A) return 3;\n        if (cur[2] == 0xA8 || cur[2] == 0xA9 || cur[2] == 0xAF) return 3;\n    } else {\n        u32 uni = byte_load_3(cur);\n        if (uni == byte_load_3(\"\\xE1\\x9A\\x80\") ||\n            uni == byte_load_3(\"\\xE2\\x81\\x9F\") ||\n            uni == byte_load_3(\"\\xE3\\x80\\x80\") ||\n            uni == byte_load_3(\"\\xEF\\xBB\\xBF\")) return 3;\n    }\n    return 0;\n}\n\n\n\n/*==============================================================================\n * MARK: - Hex Character Reader (Private)\n * This function is used by JSON reader to read escaped characters.\n *============================================================================*/\n\n/**\n This table is used to convert 4 hex character sequence to a number.\n A valid hex character [0-9A-Fa-f] will mapped to it's raw number [0x00, 0x0F],\n an invalid hex character will mapped to [0xF0].\n (generate with misc/make_tables.c)\n */\nstatic const u8 hex_conv_table[256] = {\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,\n    0x08, 0x09, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0\n};\n\n/** Load 4 hex characters to `u16`, return true on valid input. */\nstatic_inline bool hex_load_4(const u8 *src, u16 *dst) {\n    u16 c0 = hex_conv_table[src[0]];\n    u16 c1 = hex_conv_table[src[1]];\n    u16 c2 = hex_conv_table[src[2]];\n    u16 c3 = hex_conv_table[src[3]];\n    u16 t0 = (u16)((c0 << 8) | c2);\n    u16 t1 = (u16)((c1 << 8) | c3);\n    *dst = (u16)((t0 << 4) | t1);\n    return ((t0 | t1) & (u16)0xF0F0) == 0;\n}\n\n/** Load 2 hex characters to `u8`, return true on valid input. */\nstatic_inline bool hex_load_2(const u8 *src, u8 *dst) {\n    u8 c0 = hex_conv_table[src[0]];\n    u8 c1 = hex_conv_table[src[1]];\n    *dst = (u8)((c0 << 4) | c1);\n    return ((c0 | c1) & 0xF0) == 0;\n}\n\n/** Match a hexadecimal numeric character: [0-9a-fA-F]. */\nstatic_inline bool char_is_hex(u8 c) {\n    return hex_conv_table[c] != 0xF0;\n}\n\n\n\n/*==============================================================================\n * MARK: - UTF8 Validation (Private)\n * Each Unicode code point is encoded using 1 to 4 bytes in UTF-8.\n * Validation is performed using a 4-byte mask and pattern-based approach,\n * which requires the input data to be padded with four zero bytes at the end.\n *============================================================================*/\n\n/* Macro for concatenating four u8 into a u32 and keeping the byte order. */\n#if YYJSON_ENDIAN == YYJSON_LITTLE_ENDIAN\n#   define utf8_seq_def(name, a, b, c, d) \\\n        static const u32 utf8_seq_##name = 0x##d##c##b##a##UL;\n#   define utf8_seq(name) utf8_seq_##name\n#elif YYJSON_ENDIAN == YYJSON_BIG_ENDIAN\n#   define utf8_seq_def(name, a, b, c, d) \\\n        static const u32 utf8_seq_##name = 0x##a##b##c##d##UL;\n#   define utf8_seq(name) utf8_seq_##name\n#else\n#   define utf8_seq_def(name, a, b, c, d) \\\n        static const v32_uni utf8_uni_##name = {{ 0x##a, 0x##b, 0x##c, 0x##d }};\n#   define utf8_seq(name) utf8_uni_##name.u\n#endif\n\n/*\n 1-byte sequence (U+0000 to U+007F)\n bit min        [.......0] (U+0000)\n bit max        [.1111111] (U+007F)\n bit mask       [x.......] (80)\n bit pattern    [0.......] (00)\n */\nutf8_seq_def(b1_mask, 80, 00, 00, 00)\nutf8_seq_def(b1_patt, 00, 00, 00, 00)\n#define is_utf8_seq1(uni) ( \\\n    ((uni & utf8_seq(b1_mask)) == utf8_seq(b1_patt)) )\n\n/*\n 2-byte sequence (U+0080 to U+07FF)\n bit min        [......10 ..000000] (U+0080)\n bit max        [...11111 ..111111] (U+07FF)\n bit mask       [xxx..... xx......] (E0 C0)\n bit pattern    [110..... 10......] (C0 80)\n bit require    [...xxxx. ........] (1E 00)\n */\nutf8_seq_def(b2_mask, E0, C0, 00, 00)\nutf8_seq_def(b2_patt, C0, 80, 00, 00)\nutf8_seq_def(b2_requ, 1E, 00, 00, 00)\n#define is_utf8_seq2(uni) ( \\\n    ((uni & utf8_seq(b2_mask)) == utf8_seq(b2_patt)) && \\\n    ((uni & utf8_seq(b2_requ))) )\n\n/*\n 3-byte sequence (U+0800 to U+FFFF)\n bit min        [........ ..100000 ..000000] (U+0800)\n bit max        [....1111 ..111111 ..111111] (U+FFFF)\n bit mask       [xxxx.... xx...... xx......] (F0 C0 C0)\n bit pattern    [1110.... 10...... 10......] (E0 80 80)\n bit require    [....xxxx ..x..... ........] (0F 20 00)\n\n 3-byte invalid sequence, reserved for surrogate halves (U+D800 to U+DFFF)\n bit min        [....1101 ..100000 ..000000] (U+D800)\n bit max        [....1101 ..111111 ..111111] (U+DFFF)\n bit mask       [....xxxx ..x..... ........] (0F 20 00)\n bit pattern    [....1101 ..1..... ........] (0D 20 00)\n */\nutf8_seq_def(b3_mask, F0, C0, C0, 00)\nutf8_seq_def(b3_patt, E0, 80, 80, 00)\nutf8_seq_def(b3_requ, 0F, 20, 00, 00)\nutf8_seq_def(b3_erro, 0D, 20, 00, 00)\n#define is_utf8_seq3(uni) ( \\\n    ((uni & utf8_seq(b3_mask)) == utf8_seq(b3_patt)) && \\\n    ((tmp = (uni & utf8_seq(b3_requ)))) && \\\n    ((tmp != utf8_seq(b3_erro))) )\n\n/*\n 4-byte sequence (U+10000 to U+10FFFF)\n bit min        [........ ...10000 ..000000 ..000000] (U+10000)\n bit max        [.....100 ..001111 ..111111 ..111111] (U+10FFFF)\n bit mask       [xxxxx... xx...... xx...... xx......] (F8 C0 C0 C0)\n bit pattern    [11110... 10...... 10...... 10......] (F0 80 80 80)\n bit require    [.....xxx ..xx.... ........ ........] (07 30 00 00)\n bit require 1  [.....x.. ........ ........ ........] (04 00 00 00)\n bit require 2  [......xx ..xx.... ........ ........] (03 30 00 00)\n */\nutf8_seq_def(b4_mask, F8, C0, C0, C0)\nutf8_seq_def(b4_patt, F0, 80, 80, 80)\nutf8_seq_def(b4_requ, 07, 30, 00, 00)\nutf8_seq_def(b4_req1, 04, 00, 00, 00)\nutf8_seq_def(b4_req2, 03, 30, 00, 00)\n#define is_utf8_seq4(uni) ( \\\n    ((uni & utf8_seq(b4_mask)) == utf8_seq(b4_patt)) && \\\n    ((tmp = (uni & utf8_seq(b4_requ)))) && \\\n    ((tmp & utf8_seq(b4_req1)) == 0 || (tmp & utf8_seq(b4_req2)) == 0) )\n\n\n\n/*==============================================================================\n * MARK: - Power10 Lookup Table (Private)\n * These data are used by the floating-point number reader and writer.\n *============================================================================*/\n\n#if !YYJSON_DISABLE_FAST_FP_CONV\n\n/** Maximum pow10 exponent that can be represented exactly as a float64. */\n#define F64_POW10_MAX_EXACT_EXP 22\n\n/** Cached pow10 table. */\nstatic const f64 f64_pow10_table[F64_POW10_MAX_EXACT_EXP + 1] = {\n    1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12,\n    1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22\n};\n\n/** Maximum pow10 exponent that can be represented exactly as a uint64. */\n#define U64_POW10_MAX_EXACT_EXP 19\n\n/** Table: [ 10^0, ..., 10^19 ] (generate with misc/make_tables.c) */\nstatic const u64 u64_pow10_table[U64_POW10_MAX_EXACT_EXP + 1] = {\n    U64(0x00000000, 0x00000001), U64(0x00000000, 0x0000000A),\n    U64(0x00000000, 0x00000064), U64(0x00000000, 0x000003E8),\n    U64(0x00000000, 0x00002710), U64(0x00000000, 0x000186A0),\n    U64(0x00000000, 0x000F4240), U64(0x00000000, 0x00989680),\n    U64(0x00000000, 0x05F5E100), U64(0x00000000, 0x3B9ACA00),\n    U64(0x00000002, 0x540BE400), U64(0x00000017, 0x4876E800),\n    U64(0x000000E8, 0xD4A51000), U64(0x00000918, 0x4E72A000),\n    U64(0x00005AF3, 0x107A4000), U64(0x00038D7E, 0xA4C68000),\n    U64(0x002386F2, 0x6FC10000), U64(0x01634578, 0x5D8A0000),\n    U64(0x0DE0B6B3, 0xA7640000), U64(0x8AC72304, 0x89E80000)\n};\n\n/** Minimum decimal exponent in pow10_sig_table. */\n#define POW10_SIG_TABLE_MIN_EXP -343\n\n/** Maximum decimal exponent in pow10_sig_table. */\n#define POW10_SIG_TABLE_MAX_EXP 324\n\n/** Minimum exact decimal exponent in pow10_sig_table */\n#define POW10_SIG_TABLE_MIN_EXACT_EXP 0\n\n/** Maximum exact decimal exponent in pow10_sig_table */\n#define POW10_SIG_TABLE_MAX_EXACT_EXP 55\n\n/** Normalized significant 128 bits of pow10, no rounded up (size: 10.4KB).\n    This lookup table is used by both the double number reader and writer.\n    (generate with misc/make_tables.c) */\nstatic const u64 pow10_sig_table[] = {\n    U64(0xBF29DCAB, 0xA82FDEAE), U64(0x7432EE87, 0x3880FC33), /* ~= 10^-343 */\n    U64(0xEEF453D6, 0x923BD65A), U64(0x113FAA29, 0x06A13B3F), /* ~= 10^-342 */\n    U64(0x9558B466, 0x1B6565F8), U64(0x4AC7CA59, 0xA424C507), /* ~= 10^-341 */\n    U64(0xBAAEE17F, 0xA23EBF76), U64(0x5D79BCF0, 0x0D2DF649), /* ~= 10^-340 */\n    U64(0xE95A99DF, 0x8ACE6F53), U64(0xF4D82C2C, 0x107973DC), /* ~= 10^-339 */\n    U64(0x91D8A02B, 0xB6C10594), U64(0x79071B9B, 0x8A4BE869), /* ~= 10^-338 */\n    U64(0xB64EC836, 0xA47146F9), U64(0x9748E282, 0x6CDEE284), /* ~= 10^-337 */\n    U64(0xE3E27A44, 0x4D8D98B7), U64(0xFD1B1B23, 0x08169B25), /* ~= 10^-336 */\n    U64(0x8E6D8C6A, 0xB0787F72), U64(0xFE30F0F5, 0xE50E20F7), /* ~= 10^-335 */\n    U64(0xB208EF85, 0x5C969F4F), U64(0xBDBD2D33, 0x5E51A935), /* ~= 10^-334 */\n    U64(0xDE8B2B66, 0xB3BC4723), U64(0xAD2C7880, 0x35E61382), /* ~= 10^-333 */\n    U64(0x8B16FB20, 0x3055AC76), U64(0x4C3BCB50, 0x21AFCC31), /* ~= 10^-332 */\n    U64(0xADDCB9E8, 0x3C6B1793), U64(0xDF4ABE24, 0x2A1BBF3D), /* ~= 10^-331 */\n    U64(0xD953E862, 0x4B85DD78), U64(0xD71D6DAD, 0x34A2AF0D), /* ~= 10^-330 */\n    U64(0x87D4713D, 0x6F33AA6B), U64(0x8672648C, 0x40E5AD68), /* ~= 10^-329 */\n    U64(0xA9C98D8C, 0xCB009506), U64(0x680EFDAF, 0x511F18C2), /* ~= 10^-328 */\n    U64(0xD43BF0EF, 0xFDC0BA48), U64(0x0212BD1B, 0x2566DEF2), /* ~= 10^-327 */\n    U64(0x84A57695, 0xFE98746D), U64(0x014BB630, 0xF7604B57), /* ~= 10^-326 */\n    U64(0xA5CED43B, 0x7E3E9188), U64(0x419EA3BD, 0x35385E2D), /* ~= 10^-325 */\n    U64(0xCF42894A, 0x5DCE35EA), U64(0x52064CAC, 0x828675B9), /* ~= 10^-324 */\n    U64(0x818995CE, 0x7AA0E1B2), U64(0x7343EFEB, 0xD1940993), /* ~= 10^-323 */\n    U64(0xA1EBFB42, 0x19491A1F), U64(0x1014EBE6, 0xC5F90BF8), /* ~= 10^-322 */\n    U64(0xCA66FA12, 0x9F9B60A6), U64(0xD41A26E0, 0x77774EF6), /* ~= 10^-321 */\n    U64(0xFD00B897, 0x478238D0), U64(0x8920B098, 0x955522B4), /* ~= 10^-320 */\n    U64(0x9E20735E, 0x8CB16382), U64(0x55B46E5F, 0x5D5535B0), /* ~= 10^-319 */\n    U64(0xC5A89036, 0x2FDDBC62), U64(0xEB2189F7, 0x34AA831D), /* ~= 10^-318 */\n    U64(0xF712B443, 0xBBD52B7B), U64(0xA5E9EC75, 0x01D523E4), /* ~= 10^-317 */\n    U64(0x9A6BB0AA, 0x55653B2D), U64(0x47B233C9, 0x2125366E), /* ~= 10^-316 */\n    U64(0xC1069CD4, 0xEABE89F8), U64(0x999EC0BB, 0x696E840A), /* ~= 10^-315 */\n    U64(0xF148440A, 0x256E2C76), U64(0xC00670EA, 0x43CA250D), /* ~= 10^-314 */\n    U64(0x96CD2A86, 0x5764DBCA), U64(0x38040692, 0x6A5E5728), /* ~= 10^-313 */\n    U64(0xBC807527, 0xED3E12BC), U64(0xC6050837, 0x04F5ECF2), /* ~= 10^-312 */\n    U64(0xEBA09271, 0xE88D976B), U64(0xF7864A44, 0xC633682E), /* ~= 10^-311 */\n    U64(0x93445B87, 0x31587EA3), U64(0x7AB3EE6A, 0xFBE0211D), /* ~= 10^-310 */\n    U64(0xB8157268, 0xFDAE9E4C), U64(0x5960EA05, 0xBAD82964), /* ~= 10^-309 */\n    U64(0xE61ACF03, 0x3D1A45DF), U64(0x6FB92487, 0x298E33BD), /* ~= 10^-308 */\n    U64(0x8FD0C162, 0x06306BAB), U64(0xA5D3B6D4, 0x79F8E056), /* ~= 10^-307 */\n    U64(0xB3C4F1BA, 0x87BC8696), U64(0x8F48A489, 0x9877186C), /* ~= 10^-306 */\n    U64(0xE0B62E29, 0x29ABA83C), U64(0x331ACDAB, 0xFE94DE87), /* ~= 10^-305 */\n    U64(0x8C71DCD9, 0xBA0B4925), U64(0x9FF0C08B, 0x7F1D0B14), /* ~= 10^-304 */\n    U64(0xAF8E5410, 0x288E1B6F), U64(0x07ECF0AE, 0x5EE44DD9), /* ~= 10^-303 */\n    U64(0xDB71E914, 0x32B1A24A), U64(0xC9E82CD9, 0xF69D6150), /* ~= 10^-302 */\n    U64(0x892731AC, 0x9FAF056E), U64(0xBE311C08, 0x3A225CD2), /* ~= 10^-301 */\n    U64(0xAB70FE17, 0xC79AC6CA), U64(0x6DBD630A, 0x48AAF406), /* ~= 10^-300 */\n    U64(0xD64D3D9D, 0xB981787D), U64(0x092CBBCC, 0xDAD5B108), /* ~= 10^-299 */\n    U64(0x85F04682, 0x93F0EB4E), U64(0x25BBF560, 0x08C58EA5), /* ~= 10^-298 */\n    U64(0xA76C5823, 0x38ED2621), U64(0xAF2AF2B8, 0x0AF6F24E), /* ~= 10^-297 */\n    U64(0xD1476E2C, 0x07286FAA), U64(0x1AF5AF66, 0x0DB4AEE1), /* ~= 10^-296 */\n    U64(0x82CCA4DB, 0x847945CA), U64(0x50D98D9F, 0xC890ED4D), /* ~= 10^-295 */\n    U64(0xA37FCE12, 0x6597973C), U64(0xE50FF107, 0xBAB528A0), /* ~= 10^-294 */\n    U64(0xCC5FC196, 0xFEFD7D0C), U64(0x1E53ED49, 0xA96272C8), /* ~= 10^-293 */\n    U64(0xFF77B1FC, 0xBEBCDC4F), U64(0x25E8E89C, 0x13BB0F7A), /* ~= 10^-292 */\n    U64(0x9FAACF3D, 0xF73609B1), U64(0x77B19161, 0x8C54E9AC), /* ~= 10^-291 */\n    U64(0xC795830D, 0x75038C1D), U64(0xD59DF5B9, 0xEF6A2417), /* ~= 10^-290 */\n    U64(0xF97AE3D0, 0xD2446F25), U64(0x4B057328, 0x6B44AD1D), /* ~= 10^-289 */\n    U64(0x9BECCE62, 0x836AC577), U64(0x4EE367F9, 0x430AEC32), /* ~= 10^-288 */\n    U64(0xC2E801FB, 0x244576D5), U64(0x229C41F7, 0x93CDA73F), /* ~= 10^-287 */\n    U64(0xF3A20279, 0xED56D48A), U64(0x6B435275, 0x78C1110F), /* ~= 10^-286 */\n    U64(0x9845418C, 0x345644D6), U64(0x830A1389, 0x6B78AAA9), /* ~= 10^-285 */\n    U64(0xBE5691EF, 0x416BD60C), U64(0x23CC986B, 0xC656D553), /* ~= 10^-284 */\n    U64(0xEDEC366B, 0x11C6CB8F), U64(0x2CBFBE86, 0xB7EC8AA8), /* ~= 10^-283 */\n    U64(0x94B3A202, 0xEB1C3F39), U64(0x7BF7D714, 0x32F3D6A9), /* ~= 10^-282 */\n    U64(0xB9E08A83, 0xA5E34F07), U64(0xDAF5CCD9, 0x3FB0CC53), /* ~= 10^-281 */\n    U64(0xE858AD24, 0x8F5C22C9), U64(0xD1B3400F, 0x8F9CFF68), /* ~= 10^-280 */\n    U64(0x91376C36, 0xD99995BE), U64(0x23100809, 0xB9C21FA1), /* ~= 10^-279 */\n    U64(0xB5854744, 0x8FFFFB2D), U64(0xABD40A0C, 0x2832A78A), /* ~= 10^-278 */\n    U64(0xE2E69915, 0xB3FFF9F9), U64(0x16C90C8F, 0x323F516C), /* ~= 10^-277 */\n    U64(0x8DD01FAD, 0x907FFC3B), U64(0xAE3DA7D9, 0x7F6792E3), /* ~= 10^-276 */\n    U64(0xB1442798, 0xF49FFB4A), U64(0x99CD11CF, 0xDF41779C), /* ~= 10^-275 */\n    U64(0xDD95317F, 0x31C7FA1D), U64(0x40405643, 0xD711D583), /* ~= 10^-274 */\n    U64(0x8A7D3EEF, 0x7F1CFC52), U64(0x482835EA, 0x666B2572), /* ~= 10^-273 */\n    U64(0xAD1C8EAB, 0x5EE43B66), U64(0xDA324365, 0x0005EECF), /* ~= 10^-272 */\n    U64(0xD863B256, 0x369D4A40), U64(0x90BED43E, 0x40076A82), /* ~= 10^-271 */\n    U64(0x873E4F75, 0xE2224E68), U64(0x5A7744A6, 0xE804A291), /* ~= 10^-270 */\n    U64(0xA90DE353, 0x5AAAE202), U64(0x711515D0, 0xA205CB36), /* ~= 10^-269 */\n    U64(0xD3515C28, 0x31559A83), U64(0x0D5A5B44, 0xCA873E03), /* ~= 10^-268 */\n    U64(0x8412D999, 0x1ED58091), U64(0xE858790A, 0xFE9486C2), /* ~= 10^-267 */\n    U64(0xA5178FFF, 0x668AE0B6), U64(0x626E974D, 0xBE39A872), /* ~= 10^-266 */\n    U64(0xCE5D73FF, 0x402D98E3), U64(0xFB0A3D21, 0x2DC8128F), /* ~= 10^-265 */\n    U64(0x80FA687F, 0x881C7F8E), U64(0x7CE66634, 0xBC9D0B99), /* ~= 10^-264 */\n    U64(0xA139029F, 0x6A239F72), U64(0x1C1FFFC1, 0xEBC44E80), /* ~= 10^-263 */\n    U64(0xC9874347, 0x44AC874E), U64(0xA327FFB2, 0x66B56220), /* ~= 10^-262 */\n    U64(0xFBE91419, 0x15D7A922), U64(0x4BF1FF9F, 0x0062BAA8), /* ~= 10^-261 */\n    U64(0x9D71AC8F, 0xADA6C9B5), U64(0x6F773FC3, 0x603DB4A9), /* ~= 10^-260 */\n    U64(0xC4CE17B3, 0x99107C22), U64(0xCB550FB4, 0x384D21D3), /* ~= 10^-259 */\n    U64(0xF6019DA0, 0x7F549B2B), U64(0x7E2A53A1, 0x46606A48), /* ~= 10^-258 */\n    U64(0x99C10284, 0x4F94E0FB), U64(0x2EDA7444, 0xCBFC426D), /* ~= 10^-257 */\n    U64(0xC0314325, 0x637A1939), U64(0xFA911155, 0xFEFB5308), /* ~= 10^-256 */\n    U64(0xF03D93EE, 0xBC589F88), U64(0x793555AB, 0x7EBA27CA), /* ~= 10^-255 */\n    U64(0x96267C75, 0x35B763B5), U64(0x4BC1558B, 0x2F3458DE), /* ~= 10^-254 */\n    U64(0xBBB01B92, 0x83253CA2), U64(0x9EB1AAED, 0xFB016F16), /* ~= 10^-253 */\n    U64(0xEA9C2277, 0x23EE8BCB), U64(0x465E15A9, 0x79C1CADC), /* ~= 10^-252 */\n    U64(0x92A1958A, 0x7675175F), U64(0x0BFACD89, 0xEC191EC9), /* ~= 10^-251 */\n    U64(0xB749FAED, 0x14125D36), U64(0xCEF980EC, 0x671F667B), /* ~= 10^-250 */\n    U64(0xE51C79A8, 0x5916F484), U64(0x82B7E127, 0x80E7401A), /* ~= 10^-249 */\n    U64(0x8F31CC09, 0x37AE58D2), U64(0xD1B2ECB8, 0xB0908810), /* ~= 10^-248 */\n    U64(0xB2FE3F0B, 0x8599EF07), U64(0x861FA7E6, 0xDCB4AA15), /* ~= 10^-247 */\n    U64(0xDFBDCECE, 0x67006AC9), U64(0x67A791E0, 0x93E1D49A), /* ~= 10^-246 */\n    U64(0x8BD6A141, 0x006042BD), U64(0xE0C8BB2C, 0x5C6D24E0), /* ~= 10^-245 */\n    U64(0xAECC4991, 0x4078536D), U64(0x58FAE9F7, 0x73886E18), /* ~= 10^-244 */\n    U64(0xDA7F5BF5, 0x90966848), U64(0xAF39A475, 0x506A899E), /* ~= 10^-243 */\n    U64(0x888F9979, 0x7A5E012D), U64(0x6D8406C9, 0x52429603), /* ~= 10^-242 */\n    U64(0xAAB37FD7, 0xD8F58178), U64(0xC8E5087B, 0xA6D33B83), /* ~= 10^-241 */\n    U64(0xD5605FCD, 0xCF32E1D6), U64(0xFB1E4A9A, 0x90880A64), /* ~= 10^-240 */\n    U64(0x855C3BE0, 0xA17FCD26), U64(0x5CF2EEA0, 0x9A55067F), /* ~= 10^-239 */\n    U64(0xA6B34AD8, 0xC9DFC06F), U64(0xF42FAA48, 0xC0EA481E), /* ~= 10^-238 */\n    U64(0xD0601D8E, 0xFC57B08B), U64(0xF13B94DA, 0xF124DA26), /* ~= 10^-237 */\n    U64(0x823C1279, 0x5DB6CE57), U64(0x76C53D08, 0xD6B70858), /* ~= 10^-236 */\n    U64(0xA2CB1717, 0xB52481ED), U64(0x54768C4B, 0x0C64CA6E), /* ~= 10^-235 */\n    U64(0xCB7DDCDD, 0xA26DA268), U64(0xA9942F5D, 0xCF7DFD09), /* ~= 10^-234 */\n    U64(0xFE5D5415, 0x0B090B02), U64(0xD3F93B35, 0x435D7C4C), /* ~= 10^-233 */\n    U64(0x9EFA548D, 0x26E5A6E1), U64(0xC47BC501, 0x4A1A6DAF), /* ~= 10^-232 */\n    U64(0xC6B8E9B0, 0x709F109A), U64(0x359AB641, 0x9CA1091B), /* ~= 10^-231 */\n    U64(0xF867241C, 0x8CC6D4C0), U64(0xC30163D2, 0x03C94B62), /* ~= 10^-230 */\n    U64(0x9B407691, 0xD7FC44F8), U64(0x79E0DE63, 0x425DCF1D), /* ~= 10^-229 */\n    U64(0xC2109436, 0x4DFB5636), U64(0x985915FC, 0x12F542E4), /* ~= 10^-228 */\n    U64(0xF294B943, 0xE17A2BC4), U64(0x3E6F5B7B, 0x17B2939D), /* ~= 10^-227 */\n    U64(0x979CF3CA, 0x6CEC5B5A), U64(0xA705992C, 0xEECF9C42), /* ~= 10^-226 */\n    U64(0xBD8430BD, 0x08277231), U64(0x50C6FF78, 0x2A838353), /* ~= 10^-225 */\n    U64(0xECE53CEC, 0x4A314EBD), U64(0xA4F8BF56, 0x35246428), /* ~= 10^-224 */\n    U64(0x940F4613, 0xAE5ED136), U64(0x871B7795, 0xE136BE99), /* ~= 10^-223 */\n    U64(0xB9131798, 0x99F68584), U64(0x28E2557B, 0x59846E3F), /* ~= 10^-222 */\n    U64(0xE757DD7E, 0xC07426E5), U64(0x331AEADA, 0x2FE589CF), /* ~= 10^-221 */\n    U64(0x9096EA6F, 0x3848984F), U64(0x3FF0D2C8, 0x5DEF7621), /* ~= 10^-220 */\n    U64(0xB4BCA50B, 0x065ABE63), U64(0x0FED077A, 0x756B53A9), /* ~= 10^-219 */\n    U64(0xE1EBCE4D, 0xC7F16DFB), U64(0xD3E84959, 0x12C62894), /* ~= 10^-218 */\n    U64(0x8D3360F0, 0x9CF6E4BD), U64(0x64712DD7, 0xABBBD95C), /* ~= 10^-217 */\n    U64(0xB080392C, 0xC4349DEC), U64(0xBD8D794D, 0x96AACFB3), /* ~= 10^-216 */\n    U64(0xDCA04777, 0xF541C567), U64(0xECF0D7A0, 0xFC5583A0), /* ~= 10^-215 */\n    U64(0x89E42CAA, 0xF9491B60), U64(0xF41686C4, 0x9DB57244), /* ~= 10^-214 */\n    U64(0xAC5D37D5, 0xB79B6239), U64(0x311C2875, 0xC522CED5), /* ~= 10^-213 */\n    U64(0xD77485CB, 0x25823AC7), U64(0x7D633293, 0x366B828B), /* ~= 10^-212 */\n    U64(0x86A8D39E, 0xF77164BC), U64(0xAE5DFF9C, 0x02033197), /* ~= 10^-211 */\n    U64(0xA8530886, 0xB54DBDEB), U64(0xD9F57F83, 0x0283FDFC), /* ~= 10^-210 */\n    U64(0xD267CAA8, 0x62A12D66), U64(0xD072DF63, 0xC324FD7B), /* ~= 10^-209 */\n    U64(0x8380DEA9, 0x3DA4BC60), U64(0x4247CB9E, 0x59F71E6D), /* ~= 10^-208 */\n    U64(0xA4611653, 0x8D0DEB78), U64(0x52D9BE85, 0xF074E608), /* ~= 10^-207 */\n    U64(0xCD795BE8, 0x70516656), U64(0x67902E27, 0x6C921F8B), /* ~= 10^-206 */\n    U64(0x806BD971, 0x4632DFF6), U64(0x00BA1CD8, 0xA3DB53B6), /* ~= 10^-205 */\n    U64(0xA086CFCD, 0x97BF97F3), U64(0x80E8A40E, 0xCCD228A4), /* ~= 10^-204 */\n    U64(0xC8A883C0, 0xFDAF7DF0), U64(0x6122CD12, 0x8006B2CD), /* ~= 10^-203 */\n    U64(0xFAD2A4B1, 0x3D1B5D6C), U64(0x796B8057, 0x20085F81), /* ~= 10^-202 */\n    U64(0x9CC3A6EE, 0xC6311A63), U64(0xCBE33036, 0x74053BB0), /* ~= 10^-201 */\n    U64(0xC3F490AA, 0x77BD60FC), U64(0xBEDBFC44, 0x11068A9C), /* ~= 10^-200 */\n    U64(0xF4F1B4D5, 0x15ACB93B), U64(0xEE92FB55, 0x15482D44), /* ~= 10^-199 */\n    U64(0x99171105, 0x2D8BF3C5), U64(0x751BDD15, 0x2D4D1C4A), /* ~= 10^-198 */\n    U64(0xBF5CD546, 0x78EEF0B6), U64(0xD262D45A, 0x78A0635D), /* ~= 10^-197 */\n    U64(0xEF340A98, 0x172AACE4), U64(0x86FB8971, 0x16C87C34), /* ~= 10^-196 */\n    U64(0x9580869F, 0x0E7AAC0E), U64(0xD45D35E6, 0xAE3D4DA0), /* ~= 10^-195 */\n    U64(0xBAE0A846, 0xD2195712), U64(0x89748360, 0x59CCA109), /* ~= 10^-194 */\n    U64(0xE998D258, 0x869FACD7), U64(0x2BD1A438, 0x703FC94B), /* ~= 10^-193 */\n    U64(0x91FF8377, 0x5423CC06), U64(0x7B6306A3, 0x4627DDCF), /* ~= 10^-192 */\n    U64(0xB67F6455, 0x292CBF08), U64(0x1A3BC84C, 0x17B1D542), /* ~= 10^-191 */\n    U64(0xE41F3D6A, 0x7377EECA), U64(0x20CABA5F, 0x1D9E4A93), /* ~= 10^-190 */\n    U64(0x8E938662, 0x882AF53E), U64(0x547EB47B, 0x7282EE9C), /* ~= 10^-189 */\n    U64(0xB23867FB, 0x2A35B28D), U64(0xE99E619A, 0x4F23AA43), /* ~= 10^-188 */\n    U64(0xDEC681F9, 0xF4C31F31), U64(0x6405FA00, 0xE2EC94D4), /* ~= 10^-187 */\n    U64(0x8B3C113C, 0x38F9F37E), U64(0xDE83BC40, 0x8DD3DD04), /* ~= 10^-186 */\n    U64(0xAE0B158B, 0x4738705E), U64(0x9624AB50, 0xB148D445), /* ~= 10^-185 */\n    U64(0xD98DDAEE, 0x19068C76), U64(0x3BADD624, 0xDD9B0957), /* ~= 10^-184 */\n    U64(0x87F8A8D4, 0xCFA417C9), U64(0xE54CA5D7, 0x0A80E5D6), /* ~= 10^-183 */\n    U64(0xA9F6D30A, 0x038D1DBC), U64(0x5E9FCF4C, 0xCD211F4C), /* ~= 10^-182 */\n    U64(0xD47487CC, 0x8470652B), U64(0x7647C320, 0x0069671F), /* ~= 10^-181 */\n    U64(0x84C8D4DF, 0xD2C63F3B), U64(0x29ECD9F4, 0x0041E073), /* ~= 10^-180 */\n    U64(0xA5FB0A17, 0xC777CF09), U64(0xF4681071, 0x00525890), /* ~= 10^-179 */\n    U64(0xCF79CC9D, 0xB955C2CC), U64(0x7182148D, 0x4066EEB4), /* ~= 10^-178 */\n    U64(0x81AC1FE2, 0x93D599BF), U64(0xC6F14CD8, 0x48405530), /* ~= 10^-177 */\n    U64(0xA21727DB, 0x38CB002F), U64(0xB8ADA00E, 0x5A506A7C), /* ~= 10^-176 */\n    U64(0xCA9CF1D2, 0x06FDC03B), U64(0xA6D90811, 0xF0E4851C), /* ~= 10^-175 */\n    U64(0xFD442E46, 0x88BD304A), U64(0x908F4A16, 0x6D1DA663), /* ~= 10^-174 */\n    U64(0x9E4A9CEC, 0x15763E2E), U64(0x9A598E4E, 0x043287FE), /* ~= 10^-173 */\n    U64(0xC5DD4427, 0x1AD3CDBA), U64(0x40EFF1E1, 0x853F29FD), /* ~= 10^-172 */\n    U64(0xF7549530, 0xE188C128), U64(0xD12BEE59, 0xE68EF47C), /* ~= 10^-171 */\n    U64(0x9A94DD3E, 0x8CF578B9), U64(0x82BB74F8, 0x301958CE), /* ~= 10^-170 */\n    U64(0xC13A148E, 0x3032D6E7), U64(0xE36A5236, 0x3C1FAF01), /* ~= 10^-169 */\n    U64(0xF18899B1, 0xBC3F8CA1), U64(0xDC44E6C3, 0xCB279AC1), /* ~= 10^-168 */\n    U64(0x96F5600F, 0x15A7B7E5), U64(0x29AB103A, 0x5EF8C0B9), /* ~= 10^-167 */\n    U64(0xBCB2B812, 0xDB11A5DE), U64(0x7415D448, 0xF6B6F0E7), /* ~= 10^-166 */\n    U64(0xEBDF6617, 0x91D60F56), U64(0x111B495B, 0x3464AD21), /* ~= 10^-165 */\n    U64(0x936B9FCE, 0xBB25C995), U64(0xCAB10DD9, 0x00BEEC34), /* ~= 10^-164 */\n    U64(0xB84687C2, 0x69EF3BFB), U64(0x3D5D514F, 0x40EEA742), /* ~= 10^-163 */\n    U64(0xE65829B3, 0x046B0AFA), U64(0x0CB4A5A3, 0x112A5112), /* ~= 10^-162 */\n    U64(0x8FF71A0F, 0xE2C2E6DC), U64(0x47F0E785, 0xEABA72AB), /* ~= 10^-161 */\n    U64(0xB3F4E093, 0xDB73A093), U64(0x59ED2167, 0x65690F56), /* ~= 10^-160 */\n    U64(0xE0F218B8, 0xD25088B8), U64(0x306869C1, 0x3EC3532C), /* ~= 10^-159 */\n    U64(0x8C974F73, 0x83725573), U64(0x1E414218, 0xC73A13FB), /* ~= 10^-158 */\n    U64(0xAFBD2350, 0x644EEACF), U64(0xE5D1929E, 0xF90898FA), /* ~= 10^-157 */\n    U64(0xDBAC6C24, 0x7D62A583), U64(0xDF45F746, 0xB74ABF39), /* ~= 10^-156 */\n    U64(0x894BC396, 0xCE5DA772), U64(0x6B8BBA8C, 0x328EB783), /* ~= 10^-155 */\n    U64(0xAB9EB47C, 0x81F5114F), U64(0x066EA92F, 0x3F326564), /* ~= 10^-154 */\n    U64(0xD686619B, 0xA27255A2), U64(0xC80A537B, 0x0EFEFEBD), /* ~= 10^-153 */\n    U64(0x8613FD01, 0x45877585), U64(0xBD06742C, 0xE95F5F36), /* ~= 10^-152 */\n    U64(0xA798FC41, 0x96E952E7), U64(0x2C481138, 0x23B73704), /* ~= 10^-151 */\n    U64(0xD17F3B51, 0xFCA3A7A0), U64(0xF75A1586, 0x2CA504C5), /* ~= 10^-150 */\n    U64(0x82EF8513, 0x3DE648C4), U64(0x9A984D73, 0xDBE722FB), /* ~= 10^-149 */\n    U64(0xA3AB6658, 0x0D5FDAF5), U64(0xC13E60D0, 0xD2E0EBBA), /* ~= 10^-148 */\n    U64(0xCC963FEE, 0x10B7D1B3), U64(0x318DF905, 0x079926A8), /* ~= 10^-147 */\n    U64(0xFFBBCFE9, 0x94E5C61F), U64(0xFDF17746, 0x497F7052), /* ~= 10^-146 */\n    U64(0x9FD561F1, 0xFD0F9BD3), U64(0xFEB6EA8B, 0xEDEFA633), /* ~= 10^-145 */\n    U64(0xC7CABA6E, 0x7C5382C8), U64(0xFE64A52E, 0xE96B8FC0), /* ~= 10^-144 */\n    U64(0xF9BD690A, 0x1B68637B), U64(0x3DFDCE7A, 0xA3C673B0), /* ~= 10^-143 */\n    U64(0x9C1661A6, 0x51213E2D), U64(0x06BEA10C, 0xA65C084E), /* ~= 10^-142 */\n    U64(0xC31BFA0F, 0xE5698DB8), U64(0x486E494F, 0xCFF30A62), /* ~= 10^-141 */\n    U64(0xF3E2F893, 0xDEC3F126), U64(0x5A89DBA3, 0xC3EFCCFA), /* ~= 10^-140 */\n    U64(0x986DDB5C, 0x6B3A76B7), U64(0xF8962946, 0x5A75E01C), /* ~= 10^-139 */\n    U64(0xBE895233, 0x86091465), U64(0xF6BBB397, 0xF1135823), /* ~= 10^-138 */\n    U64(0xEE2BA6C0, 0x678B597F), U64(0x746AA07D, 0xED582E2C), /* ~= 10^-137 */\n    U64(0x94DB4838, 0x40B717EF), U64(0xA8C2A44E, 0xB4571CDC), /* ~= 10^-136 */\n    U64(0xBA121A46, 0x50E4DDEB), U64(0x92F34D62, 0x616CE413), /* ~= 10^-135 */\n    U64(0xE896A0D7, 0xE51E1566), U64(0x77B020BA, 0xF9C81D17), /* ~= 10^-134 */\n    U64(0x915E2486, 0xEF32CD60), U64(0x0ACE1474, 0xDC1D122E), /* ~= 10^-133 */\n    U64(0xB5B5ADA8, 0xAAFF80B8), U64(0x0D819992, 0x132456BA), /* ~= 10^-132 */\n    U64(0xE3231912, 0xD5BF60E6), U64(0x10E1FFF6, 0x97ED6C69), /* ~= 10^-131 */\n    U64(0x8DF5EFAB, 0xC5979C8F), U64(0xCA8D3FFA, 0x1EF463C1), /* ~= 10^-130 */\n    U64(0xB1736B96, 0xB6FD83B3), U64(0xBD308FF8, 0xA6B17CB2), /* ~= 10^-129 */\n    U64(0xDDD0467C, 0x64BCE4A0), U64(0xAC7CB3F6, 0xD05DDBDE), /* ~= 10^-128 */\n    U64(0x8AA22C0D, 0xBEF60EE4), U64(0x6BCDF07A, 0x423AA96B), /* ~= 10^-127 */\n    U64(0xAD4AB711, 0x2EB3929D), U64(0x86C16C98, 0xD2C953C6), /* ~= 10^-126 */\n    U64(0xD89D64D5, 0x7A607744), U64(0xE871C7BF, 0x077BA8B7), /* ~= 10^-125 */\n    U64(0x87625F05, 0x6C7C4A8B), U64(0x11471CD7, 0x64AD4972), /* ~= 10^-124 */\n    U64(0xA93AF6C6, 0xC79B5D2D), U64(0xD598E40D, 0x3DD89BCF), /* ~= 10^-123 */\n    U64(0xD389B478, 0x79823479), U64(0x4AFF1D10, 0x8D4EC2C3), /* ~= 10^-122 */\n    U64(0x843610CB, 0x4BF160CB), U64(0xCEDF722A, 0x585139BA), /* ~= 10^-121 */\n    U64(0xA54394FE, 0x1EEDB8FE), U64(0xC2974EB4, 0xEE658828), /* ~= 10^-120 */\n    U64(0xCE947A3D, 0xA6A9273E), U64(0x733D2262, 0x29FEEA32), /* ~= 10^-119 */\n    U64(0x811CCC66, 0x8829B887), U64(0x0806357D, 0x5A3F525F), /* ~= 10^-118 */\n    U64(0xA163FF80, 0x2A3426A8), U64(0xCA07C2DC, 0xB0CF26F7), /* ~= 10^-117 */\n    U64(0xC9BCFF60, 0x34C13052), U64(0xFC89B393, 0xDD02F0B5), /* ~= 10^-116 */\n    U64(0xFC2C3F38, 0x41F17C67), U64(0xBBAC2078, 0xD443ACE2), /* ~= 10^-115 */\n    U64(0x9D9BA783, 0x2936EDC0), U64(0xD54B944B, 0x84AA4C0D), /* ~= 10^-114 */\n    U64(0xC5029163, 0xF384A931), U64(0x0A9E795E, 0x65D4DF11), /* ~= 10^-113 */\n    U64(0xF64335BC, 0xF065D37D), U64(0x4D4617B5, 0xFF4A16D5), /* ~= 10^-112 */\n    U64(0x99EA0196, 0x163FA42E), U64(0x504BCED1, 0xBF8E4E45), /* ~= 10^-111 */\n    U64(0xC06481FB, 0x9BCF8D39), U64(0xE45EC286, 0x2F71E1D6), /* ~= 10^-110 */\n    U64(0xF07DA27A, 0x82C37088), U64(0x5D767327, 0xBB4E5A4C), /* ~= 10^-109 */\n    U64(0x964E858C, 0x91BA2655), U64(0x3A6A07F8, 0xD510F86F), /* ~= 10^-108 */\n    U64(0xBBE226EF, 0xB628AFEA), U64(0x890489F7, 0x0A55368B), /* ~= 10^-107 */\n    U64(0xEADAB0AB, 0xA3B2DBE5), U64(0x2B45AC74, 0xCCEA842E), /* ~= 10^-106 */\n    U64(0x92C8AE6B, 0x464FC96F), U64(0x3B0B8BC9, 0x0012929D), /* ~= 10^-105 */\n    U64(0xB77ADA06, 0x17E3BBCB), U64(0x09CE6EBB, 0x40173744), /* ~= 10^-104 */\n    U64(0xE5599087, 0x9DDCAABD), U64(0xCC420A6A, 0x101D0515), /* ~= 10^-103 */\n    U64(0x8F57FA54, 0xC2A9EAB6), U64(0x9FA94682, 0x4A12232D), /* ~= 10^-102 */\n    U64(0xB32DF8E9, 0xF3546564), U64(0x47939822, 0xDC96ABF9), /* ~= 10^-101 */\n    U64(0xDFF97724, 0x70297EBD), U64(0x59787E2B, 0x93BC56F7), /* ~= 10^-100 */\n    U64(0x8BFBEA76, 0xC619EF36), U64(0x57EB4EDB, 0x3C55B65A), /* ~= 10^-99 */\n    U64(0xAEFAE514, 0x77A06B03), U64(0xEDE62292, 0x0B6B23F1), /* ~= 10^-98 */\n    U64(0xDAB99E59, 0x958885C4), U64(0xE95FAB36, 0x8E45ECED), /* ~= 10^-97 */\n    U64(0x88B402F7, 0xFD75539B), U64(0x11DBCB02, 0x18EBB414), /* ~= 10^-96 */\n    U64(0xAAE103B5, 0xFCD2A881), U64(0xD652BDC2, 0x9F26A119), /* ~= 10^-95 */\n    U64(0xD59944A3, 0x7C0752A2), U64(0x4BE76D33, 0x46F0495F), /* ~= 10^-94 */\n    U64(0x857FCAE6, 0x2D8493A5), U64(0x6F70A440, 0x0C562DDB), /* ~= 10^-93 */\n    U64(0xA6DFBD9F, 0xB8E5B88E), U64(0xCB4CCD50, 0x0F6BB952), /* ~= 10^-92 */\n    U64(0xD097AD07, 0xA71F26B2), U64(0x7E2000A4, 0x1346A7A7), /* ~= 10^-91 */\n    U64(0x825ECC24, 0xC873782F), U64(0x8ED40066, 0x8C0C28C8), /* ~= 10^-90 */\n    U64(0xA2F67F2D, 0xFA90563B), U64(0x72890080, 0x2F0F32FA), /* ~= 10^-89 */\n    U64(0xCBB41EF9, 0x79346BCA), U64(0x4F2B40A0, 0x3AD2FFB9), /* ~= 10^-88 */\n    U64(0xFEA126B7, 0xD78186BC), U64(0xE2F610C8, 0x4987BFA8), /* ~= 10^-87 */\n    U64(0x9F24B832, 0xE6B0F436), U64(0x0DD9CA7D, 0x2DF4D7C9), /* ~= 10^-86 */\n    U64(0xC6EDE63F, 0xA05D3143), U64(0x91503D1C, 0x79720DBB), /* ~= 10^-85 */\n    U64(0xF8A95FCF, 0x88747D94), U64(0x75A44C63, 0x97CE912A), /* ~= 10^-84 */\n    U64(0x9B69DBE1, 0xB548CE7C), U64(0xC986AFBE, 0x3EE11ABA), /* ~= 10^-83 */\n    U64(0xC24452DA, 0x229B021B), U64(0xFBE85BAD, 0xCE996168), /* ~= 10^-82 */\n    U64(0xF2D56790, 0xAB41C2A2), U64(0xFAE27299, 0x423FB9C3), /* ~= 10^-81 */\n    U64(0x97C560BA, 0x6B0919A5), U64(0xDCCD879F, 0xC967D41A), /* ~= 10^-80 */\n    U64(0xBDB6B8E9, 0x05CB600F), U64(0x5400E987, 0xBBC1C920), /* ~= 10^-79 */\n    U64(0xED246723, 0x473E3813), U64(0x290123E9, 0xAAB23B68), /* ~= 10^-78 */\n    U64(0x9436C076, 0x0C86E30B), U64(0xF9A0B672, 0x0AAF6521), /* ~= 10^-77 */\n    U64(0xB9447093, 0x8FA89BCE), U64(0xF808E40E, 0x8D5B3E69), /* ~= 10^-76 */\n    U64(0xE7958CB8, 0x7392C2C2), U64(0xB60B1D12, 0x30B20E04), /* ~= 10^-75 */\n    U64(0x90BD77F3, 0x483BB9B9), U64(0xB1C6F22B, 0x5E6F48C2), /* ~= 10^-74 */\n    U64(0xB4ECD5F0, 0x1A4AA828), U64(0x1E38AEB6, 0x360B1AF3), /* ~= 10^-73 */\n    U64(0xE2280B6C, 0x20DD5232), U64(0x25C6DA63, 0xC38DE1B0), /* ~= 10^-72 */\n    U64(0x8D590723, 0x948A535F), U64(0x579C487E, 0x5A38AD0E), /* ~= 10^-71 */\n    U64(0xB0AF48EC, 0x79ACE837), U64(0x2D835A9D, 0xF0C6D851), /* ~= 10^-70 */\n    U64(0xDCDB1B27, 0x98182244), U64(0xF8E43145, 0x6CF88E65), /* ~= 10^-69 */\n    U64(0x8A08F0F8, 0xBF0F156B), U64(0x1B8E9ECB, 0x641B58FF), /* ~= 10^-68 */\n    U64(0xAC8B2D36, 0xEED2DAC5), U64(0xE272467E, 0x3D222F3F), /* ~= 10^-67 */\n    U64(0xD7ADF884, 0xAA879177), U64(0x5B0ED81D, 0xCC6ABB0F), /* ~= 10^-66 */\n    U64(0x86CCBB52, 0xEA94BAEA), U64(0x98E94712, 0x9FC2B4E9), /* ~= 10^-65 */\n    U64(0xA87FEA27, 0xA539E9A5), U64(0x3F2398D7, 0x47B36224), /* ~= 10^-64 */\n    U64(0xD29FE4B1, 0x8E88640E), U64(0x8EEC7F0D, 0x19A03AAD), /* ~= 10^-63 */\n    U64(0x83A3EEEE, 0xF9153E89), U64(0x1953CF68, 0x300424AC), /* ~= 10^-62 */\n    U64(0xA48CEAAA, 0xB75A8E2B), U64(0x5FA8C342, 0x3C052DD7), /* ~= 10^-61 */\n    U64(0xCDB02555, 0x653131B6), U64(0x3792F412, 0xCB06794D), /* ~= 10^-60 */\n    U64(0x808E1755, 0x5F3EBF11), U64(0xE2BBD88B, 0xBEE40BD0), /* ~= 10^-59 */\n    U64(0xA0B19D2A, 0xB70E6ED6), U64(0x5B6ACEAE, 0xAE9D0EC4), /* ~= 10^-58 */\n    U64(0xC8DE0475, 0x64D20A8B), U64(0xF245825A, 0x5A445275), /* ~= 10^-57 */\n    U64(0xFB158592, 0xBE068D2E), U64(0xEED6E2F0, 0xF0D56712), /* ~= 10^-56 */\n    U64(0x9CED737B, 0xB6C4183D), U64(0x55464DD6, 0x9685606B), /* ~= 10^-55 */\n    U64(0xC428D05A, 0xA4751E4C), U64(0xAA97E14C, 0x3C26B886), /* ~= 10^-54 */\n    U64(0xF5330471, 0x4D9265DF), U64(0xD53DD99F, 0x4B3066A8), /* ~= 10^-53 */\n    U64(0x993FE2C6, 0xD07B7FAB), U64(0xE546A803, 0x8EFE4029), /* ~= 10^-52 */\n    U64(0xBF8FDB78, 0x849A5F96), U64(0xDE985204, 0x72BDD033), /* ~= 10^-51 */\n    U64(0xEF73D256, 0xA5C0F77C), U64(0x963E6685, 0x8F6D4440), /* ~= 10^-50 */\n    U64(0x95A86376, 0x27989AAD), U64(0xDDE70013, 0x79A44AA8), /* ~= 10^-49 */\n    U64(0xBB127C53, 0xB17EC159), U64(0x5560C018, 0x580D5D52), /* ~= 10^-48 */\n    U64(0xE9D71B68, 0x9DDE71AF), U64(0xAAB8F01E, 0x6E10B4A6), /* ~= 10^-47 */\n    U64(0x92267121, 0x62AB070D), U64(0xCAB39613, 0x04CA70E8), /* ~= 10^-46 */\n    U64(0xB6B00D69, 0xBB55C8D1), U64(0x3D607B97, 0xC5FD0D22), /* ~= 10^-45 */\n    U64(0xE45C10C4, 0x2A2B3B05), U64(0x8CB89A7D, 0xB77C506A), /* ~= 10^-44 */\n    U64(0x8EB98A7A, 0x9A5B04E3), U64(0x77F3608E, 0x92ADB242), /* ~= 10^-43 */\n    U64(0xB267ED19, 0x40F1C61C), U64(0x55F038B2, 0x37591ED3), /* ~= 10^-42 */\n    U64(0xDF01E85F, 0x912E37A3), U64(0x6B6C46DE, 0xC52F6688), /* ~= 10^-41 */\n    U64(0x8B61313B, 0xBABCE2C6), U64(0x2323AC4B, 0x3B3DA015), /* ~= 10^-40 */\n    U64(0xAE397D8A, 0xA96C1B77), U64(0xABEC975E, 0x0A0D081A), /* ~= 10^-39 */\n    U64(0xD9C7DCED, 0x53C72255), U64(0x96E7BD35, 0x8C904A21), /* ~= 10^-38 */\n    U64(0x881CEA14, 0x545C7575), U64(0x7E50D641, 0x77DA2E54), /* ~= 10^-37 */\n    U64(0xAA242499, 0x697392D2), U64(0xDDE50BD1, 0xD5D0B9E9), /* ~= 10^-36 */\n    U64(0xD4AD2DBF, 0xC3D07787), U64(0x955E4EC6, 0x4B44E864), /* ~= 10^-35 */\n    U64(0x84EC3C97, 0xDA624AB4), U64(0xBD5AF13B, 0xEF0B113E), /* ~= 10^-34 */\n    U64(0xA6274BBD, 0xD0FADD61), U64(0xECB1AD8A, 0xEACDD58E), /* ~= 10^-33 */\n    U64(0xCFB11EAD, 0x453994BA), U64(0x67DE18ED, 0xA5814AF2), /* ~= 10^-32 */\n    U64(0x81CEB32C, 0x4B43FCF4), U64(0x80EACF94, 0x8770CED7), /* ~= 10^-31 */\n    U64(0xA2425FF7, 0x5E14FC31), U64(0xA1258379, 0xA94D028D), /* ~= 10^-30 */\n    U64(0xCAD2F7F5, 0x359A3B3E), U64(0x096EE458, 0x13A04330), /* ~= 10^-29 */\n    U64(0xFD87B5F2, 0x8300CA0D), U64(0x8BCA9D6E, 0x188853FC), /* ~= 10^-28 */\n    U64(0x9E74D1B7, 0x91E07E48), U64(0x775EA264, 0xCF55347D), /* ~= 10^-27 */\n    U64(0xC6120625, 0x76589DDA), U64(0x95364AFE, 0x032A819D), /* ~= 10^-26 */\n    U64(0xF79687AE, 0xD3EEC551), U64(0x3A83DDBD, 0x83F52204), /* ~= 10^-25 */\n    U64(0x9ABE14CD, 0x44753B52), U64(0xC4926A96, 0x72793542), /* ~= 10^-24 */\n    U64(0xC16D9A00, 0x95928A27), U64(0x75B7053C, 0x0F178293), /* ~= 10^-23 */\n    U64(0xF1C90080, 0xBAF72CB1), U64(0x5324C68B, 0x12DD6338), /* ~= 10^-22 */\n    U64(0x971DA050, 0x74DA7BEE), U64(0xD3F6FC16, 0xEBCA5E03), /* ~= 10^-21 */\n    U64(0xBCE50864, 0x92111AEA), U64(0x88F4BB1C, 0xA6BCF584), /* ~= 10^-20 */\n    U64(0xEC1E4A7D, 0xB69561A5), U64(0x2B31E9E3, 0xD06C32E5), /* ~= 10^-19 */\n    U64(0x9392EE8E, 0x921D5D07), U64(0x3AFF322E, 0x62439FCF), /* ~= 10^-18 */\n    U64(0xB877AA32, 0x36A4B449), U64(0x09BEFEB9, 0xFAD487C2), /* ~= 10^-17 */\n    U64(0xE69594BE, 0xC44DE15B), U64(0x4C2EBE68, 0x7989A9B3), /* ~= 10^-16 */\n    U64(0x901D7CF7, 0x3AB0ACD9), U64(0x0F9D3701, 0x4BF60A10), /* ~= 10^-15 */\n    U64(0xB424DC35, 0x095CD80F), U64(0x538484C1, 0x9EF38C94), /* ~= 10^-14 */\n    U64(0xE12E1342, 0x4BB40E13), U64(0x2865A5F2, 0x06B06FB9), /* ~= 10^-13 */\n    U64(0x8CBCCC09, 0x6F5088CB), U64(0xF93F87B7, 0x442E45D3), /* ~= 10^-12 */\n    U64(0xAFEBFF0B, 0xCB24AAFE), U64(0xF78F69A5, 0x1539D748), /* ~= 10^-11 */\n    U64(0xDBE6FECE, 0xBDEDD5BE), U64(0xB573440E, 0x5A884D1B), /* ~= 10^-10 */\n    U64(0x89705F41, 0x36B4A597), U64(0x31680A88, 0xF8953030), /* ~= 10^-9 */\n    U64(0xABCC7711, 0x8461CEFC), U64(0xFDC20D2B, 0x36BA7C3D), /* ~= 10^-8 */\n    U64(0xD6BF94D5, 0xE57A42BC), U64(0x3D329076, 0x04691B4C), /* ~= 10^-7 */\n    U64(0x8637BD05, 0xAF6C69B5), U64(0xA63F9A49, 0xC2C1B10F), /* ~= 10^-6 */\n    U64(0xA7C5AC47, 0x1B478423), U64(0x0FCF80DC, 0x33721D53), /* ~= 10^-5 */\n    U64(0xD1B71758, 0xE219652B), U64(0xD3C36113, 0x404EA4A8), /* ~= 10^-4 */\n    U64(0x83126E97, 0x8D4FDF3B), U64(0x645A1CAC, 0x083126E9), /* ~= 10^-3 */\n    U64(0xA3D70A3D, 0x70A3D70A), U64(0x3D70A3D7, 0x0A3D70A3), /* ~= 10^-2 */\n    U64(0xCCCCCCCC, 0xCCCCCCCC), U64(0xCCCCCCCC, 0xCCCCCCCC), /* ~= 10^-1 */\n    U64(0x80000000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^0 */\n    U64(0xA0000000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^1 */\n    U64(0xC8000000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^2 */\n    U64(0xFA000000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^3 */\n    U64(0x9C400000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^4 */\n    U64(0xC3500000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^5 */\n    U64(0xF4240000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^6 */\n    U64(0x98968000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^7 */\n    U64(0xBEBC2000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^8 */\n    U64(0xEE6B2800, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^9 */\n    U64(0x9502F900, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^10 */\n    U64(0xBA43B740, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^11 */\n    U64(0xE8D4A510, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^12 */\n    U64(0x9184E72A, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^13 */\n    U64(0xB5E620F4, 0x80000000), U64(0x00000000, 0x00000000), /* == 10^14 */\n    U64(0xE35FA931, 0xA0000000), U64(0x00000000, 0x00000000), /* == 10^15 */\n    U64(0x8E1BC9BF, 0x04000000), U64(0x00000000, 0x00000000), /* == 10^16 */\n    U64(0xB1A2BC2E, 0xC5000000), U64(0x00000000, 0x00000000), /* == 10^17 */\n    U64(0xDE0B6B3A, 0x76400000), U64(0x00000000, 0x00000000), /* == 10^18 */\n    U64(0x8AC72304, 0x89E80000), U64(0x00000000, 0x00000000), /* == 10^19 */\n    U64(0xAD78EBC5, 0xAC620000), U64(0x00000000, 0x00000000), /* == 10^20 */\n    U64(0xD8D726B7, 0x177A8000), U64(0x00000000, 0x00000000), /* == 10^21 */\n    U64(0x87867832, 0x6EAC9000), U64(0x00000000, 0x00000000), /* == 10^22 */\n    U64(0xA968163F, 0x0A57B400), U64(0x00000000, 0x00000000), /* == 10^23 */\n    U64(0xD3C21BCE, 0xCCEDA100), U64(0x00000000, 0x00000000), /* == 10^24 */\n    U64(0x84595161, 0x401484A0), U64(0x00000000, 0x00000000), /* == 10^25 */\n    U64(0xA56FA5B9, 0x9019A5C8), U64(0x00000000, 0x00000000), /* == 10^26 */\n    U64(0xCECB8F27, 0xF4200F3A), U64(0x00000000, 0x00000000), /* == 10^27 */\n    U64(0x813F3978, 0xF8940984), U64(0x40000000, 0x00000000), /* == 10^28 */\n    U64(0xA18F07D7, 0x36B90BE5), U64(0x50000000, 0x00000000), /* == 10^29 */\n    U64(0xC9F2C9CD, 0x04674EDE), U64(0xA4000000, 0x00000000), /* == 10^30 */\n    U64(0xFC6F7C40, 0x45812296), U64(0x4D000000, 0x00000000), /* == 10^31 */\n    U64(0x9DC5ADA8, 0x2B70B59D), U64(0xF0200000, 0x00000000), /* == 10^32 */\n    U64(0xC5371912, 0x364CE305), U64(0x6C280000, 0x00000000), /* == 10^33 */\n    U64(0xF684DF56, 0xC3E01BC6), U64(0xC7320000, 0x00000000), /* == 10^34 */\n    U64(0x9A130B96, 0x3A6C115C), U64(0x3C7F4000, 0x00000000), /* == 10^35 */\n    U64(0xC097CE7B, 0xC90715B3), U64(0x4B9F1000, 0x00000000), /* == 10^36 */\n    U64(0xF0BDC21A, 0xBB48DB20), U64(0x1E86D400, 0x00000000), /* == 10^37 */\n    U64(0x96769950, 0xB50D88F4), U64(0x13144480, 0x00000000), /* == 10^38 */\n    U64(0xBC143FA4, 0xE250EB31), U64(0x17D955A0, 0x00000000), /* == 10^39 */\n    U64(0xEB194F8E, 0x1AE525FD), U64(0x5DCFAB08, 0x00000000), /* == 10^40 */\n    U64(0x92EFD1B8, 0xD0CF37BE), U64(0x5AA1CAE5, 0x00000000), /* == 10^41 */\n    U64(0xB7ABC627, 0x050305AD), U64(0xF14A3D9E, 0x40000000), /* == 10^42 */\n    U64(0xE596B7B0, 0xC643C719), U64(0x6D9CCD05, 0xD0000000), /* == 10^43 */\n    U64(0x8F7E32CE, 0x7BEA5C6F), U64(0xE4820023, 0xA2000000), /* == 10^44 */\n    U64(0xB35DBF82, 0x1AE4F38B), U64(0xDDA2802C, 0x8A800000), /* == 10^45 */\n    U64(0xE0352F62, 0xA19E306E), U64(0xD50B2037, 0xAD200000), /* == 10^46 */\n    U64(0x8C213D9D, 0xA502DE45), U64(0x4526F422, 0xCC340000), /* == 10^47 */\n    U64(0xAF298D05, 0x0E4395D6), U64(0x9670B12B, 0x7F410000), /* == 10^48 */\n    U64(0xDAF3F046, 0x51D47B4C), U64(0x3C0CDD76, 0x5F114000), /* == 10^49 */\n    U64(0x88D8762B, 0xF324CD0F), U64(0xA5880A69, 0xFB6AC800), /* == 10^50 */\n    U64(0xAB0E93B6, 0xEFEE0053), U64(0x8EEA0D04, 0x7A457A00), /* == 10^51 */\n    U64(0xD5D238A4, 0xABE98068), U64(0x72A49045, 0x98D6D880), /* == 10^52 */\n    U64(0x85A36366, 0xEB71F041), U64(0x47A6DA2B, 0x7F864750), /* == 10^53 */\n    U64(0xA70C3C40, 0xA64E6C51), U64(0x999090B6, 0x5F67D924), /* == 10^54 */\n    U64(0xD0CF4B50, 0xCFE20765), U64(0xFFF4B4E3, 0xF741CF6D), /* == 10^55 */\n    U64(0x82818F12, 0x81ED449F), U64(0xBFF8F10E, 0x7A8921A4), /* ~= 10^56 */\n    U64(0xA321F2D7, 0x226895C7), U64(0xAFF72D52, 0x192B6A0D), /* ~= 10^57 */\n    U64(0xCBEA6F8C, 0xEB02BB39), U64(0x9BF4F8A6, 0x9F764490), /* ~= 10^58 */\n    U64(0xFEE50B70, 0x25C36A08), U64(0x02F236D0, 0x4753D5B4), /* ~= 10^59 */\n    U64(0x9F4F2726, 0x179A2245), U64(0x01D76242, 0x2C946590), /* ~= 10^60 */\n    U64(0xC722F0EF, 0x9D80AAD6), U64(0x424D3AD2, 0xB7B97EF5), /* ~= 10^61 */\n    U64(0xF8EBAD2B, 0x84E0D58B), U64(0xD2E08987, 0x65A7DEB2), /* ~= 10^62 */\n    U64(0x9B934C3B, 0x330C8577), U64(0x63CC55F4, 0x9F88EB2F), /* ~= 10^63 */\n    U64(0xC2781F49, 0xFFCFA6D5), U64(0x3CBF6B71, 0xC76B25FB), /* ~= 10^64 */\n    U64(0xF316271C, 0x7FC3908A), U64(0x8BEF464E, 0x3945EF7A), /* ~= 10^65 */\n    U64(0x97EDD871, 0xCFDA3A56), U64(0x97758BF0, 0xE3CBB5AC), /* ~= 10^66 */\n    U64(0xBDE94E8E, 0x43D0C8EC), U64(0x3D52EEED, 0x1CBEA317), /* ~= 10^67 */\n    U64(0xED63A231, 0xD4C4FB27), U64(0x4CA7AAA8, 0x63EE4BDD), /* ~= 10^68 */\n    U64(0x945E455F, 0x24FB1CF8), U64(0x8FE8CAA9, 0x3E74EF6A), /* ~= 10^69 */\n    U64(0xB975D6B6, 0xEE39E436), U64(0xB3E2FD53, 0x8E122B44), /* ~= 10^70 */\n    U64(0xE7D34C64, 0xA9C85D44), U64(0x60DBBCA8, 0x7196B616), /* ~= 10^71 */\n    U64(0x90E40FBE, 0xEA1D3A4A), U64(0xBC8955E9, 0x46FE31CD), /* ~= 10^72 */\n    U64(0xB51D13AE, 0xA4A488DD), U64(0x6BABAB63, 0x98BDBE41), /* ~= 10^73 */\n    U64(0xE264589A, 0x4DCDAB14), U64(0xC696963C, 0x7EED2DD1), /* ~= 10^74 */\n    U64(0x8D7EB760, 0x70A08AEC), U64(0xFC1E1DE5, 0xCF543CA2), /* ~= 10^75 */\n    U64(0xB0DE6538, 0x8CC8ADA8), U64(0x3B25A55F, 0x43294BCB), /* ~= 10^76 */\n    U64(0xDD15FE86, 0xAFFAD912), U64(0x49EF0EB7, 0x13F39EBE), /* ~= 10^77 */\n    U64(0x8A2DBF14, 0x2DFCC7AB), U64(0x6E356932, 0x6C784337), /* ~= 10^78 */\n    U64(0xACB92ED9, 0x397BF996), U64(0x49C2C37F, 0x07965404), /* ~= 10^79 */\n    U64(0xD7E77A8F, 0x87DAF7FB), U64(0xDC33745E, 0xC97BE906), /* ~= 10^80 */\n    U64(0x86F0AC99, 0xB4E8DAFD), U64(0x69A028BB, 0x3DED71A3), /* ~= 10^81 */\n    U64(0xA8ACD7C0, 0x222311BC), U64(0xC40832EA, 0x0D68CE0C), /* ~= 10^82 */\n    U64(0xD2D80DB0, 0x2AABD62B), U64(0xF50A3FA4, 0x90C30190), /* ~= 10^83 */\n    U64(0x83C7088E, 0x1AAB65DB), U64(0x792667C6, 0xDA79E0FA), /* ~= 10^84 */\n    U64(0xA4B8CAB1, 0xA1563F52), U64(0x577001B8, 0x91185938), /* ~= 10^85 */\n    U64(0xCDE6FD5E, 0x09ABCF26), U64(0xED4C0226, 0xB55E6F86), /* ~= 10^86 */\n    U64(0x80B05E5A, 0xC60B6178), U64(0x544F8158, 0x315B05B4), /* ~= 10^87 */\n    U64(0xA0DC75F1, 0x778E39D6), U64(0x696361AE, 0x3DB1C721), /* ~= 10^88 */\n    U64(0xC913936D, 0xD571C84C), U64(0x03BC3A19, 0xCD1E38E9), /* ~= 10^89 */\n    U64(0xFB587849, 0x4ACE3A5F), U64(0x04AB48A0, 0x4065C723), /* ~= 10^90 */\n    U64(0x9D174B2D, 0xCEC0E47B), U64(0x62EB0D64, 0x283F9C76), /* ~= 10^91 */\n    U64(0xC45D1DF9, 0x42711D9A), U64(0x3BA5D0BD, 0x324F8394), /* ~= 10^92 */\n    U64(0xF5746577, 0x930D6500), U64(0xCA8F44EC, 0x7EE36479), /* ~= 10^93 */\n    U64(0x9968BF6A, 0xBBE85F20), U64(0x7E998B13, 0xCF4E1ECB), /* ~= 10^94 */\n    U64(0xBFC2EF45, 0x6AE276E8), U64(0x9E3FEDD8, 0xC321A67E), /* ~= 10^95 */\n    U64(0xEFB3AB16, 0xC59B14A2), U64(0xC5CFE94E, 0xF3EA101E), /* ~= 10^96 */\n    U64(0x95D04AEE, 0x3B80ECE5), U64(0xBBA1F1D1, 0x58724A12), /* ~= 10^97 */\n    U64(0xBB445DA9, 0xCA61281F), U64(0x2A8A6E45, 0xAE8EDC97), /* ~= 10^98 */\n    U64(0xEA157514, 0x3CF97226), U64(0xF52D09D7, 0x1A3293BD), /* ~= 10^99 */\n    U64(0x924D692C, 0xA61BE758), U64(0x593C2626, 0x705F9C56), /* ~= 10^100 */\n    U64(0xB6E0C377, 0xCFA2E12E), U64(0x6F8B2FB0, 0x0C77836C), /* ~= 10^101 */\n    U64(0xE498F455, 0xC38B997A), U64(0x0B6DFB9C, 0x0F956447), /* ~= 10^102 */\n    U64(0x8EDF98B5, 0x9A373FEC), U64(0x4724BD41, 0x89BD5EAC), /* ~= 10^103 */\n    U64(0xB2977EE3, 0x00C50FE7), U64(0x58EDEC91, 0xEC2CB657), /* ~= 10^104 */\n    U64(0xDF3D5E9B, 0xC0F653E1), U64(0x2F2967B6, 0x6737E3ED), /* ~= 10^105 */\n    U64(0x8B865B21, 0x5899F46C), U64(0xBD79E0D2, 0x0082EE74), /* ~= 10^106 */\n    U64(0xAE67F1E9, 0xAEC07187), U64(0xECD85906, 0x80A3AA11), /* ~= 10^107 */\n    U64(0xDA01EE64, 0x1A708DE9), U64(0xE80E6F48, 0x20CC9495), /* ~= 10^108 */\n    U64(0x884134FE, 0x908658B2), U64(0x3109058D, 0x147FDCDD), /* ~= 10^109 */\n    U64(0xAA51823E, 0x34A7EEDE), U64(0xBD4B46F0, 0x599FD415), /* ~= 10^110 */\n    U64(0xD4E5E2CD, 0xC1D1EA96), U64(0x6C9E18AC, 0x7007C91A), /* ~= 10^111 */\n    U64(0x850FADC0, 0x9923329E), U64(0x03E2CF6B, 0xC604DDB0), /* ~= 10^112 */\n    U64(0xA6539930, 0xBF6BFF45), U64(0x84DB8346, 0xB786151C), /* ~= 10^113 */\n    U64(0xCFE87F7C, 0xEF46FF16), U64(0xE6126418, 0x65679A63), /* ~= 10^114 */\n    U64(0x81F14FAE, 0x158C5F6E), U64(0x4FCB7E8F, 0x3F60C07E), /* ~= 10^115 */\n    U64(0xA26DA399, 0x9AEF7749), U64(0xE3BE5E33, 0x0F38F09D), /* ~= 10^116 */\n    U64(0xCB090C80, 0x01AB551C), U64(0x5CADF5BF, 0xD3072CC5), /* ~= 10^117 */\n    U64(0xFDCB4FA0, 0x02162A63), U64(0x73D9732F, 0xC7C8F7F6), /* ~= 10^118 */\n    U64(0x9E9F11C4, 0x014DDA7E), U64(0x2867E7FD, 0xDCDD9AFA), /* ~= 10^119 */\n    U64(0xC646D635, 0x01A1511D), U64(0xB281E1FD, 0x541501B8), /* ~= 10^120 */\n    U64(0xF7D88BC2, 0x4209A565), U64(0x1F225A7C, 0xA91A4226), /* ~= 10^121 */\n    U64(0x9AE75759, 0x6946075F), U64(0x3375788D, 0xE9B06958), /* ~= 10^122 */\n    U64(0xC1A12D2F, 0xC3978937), U64(0x0052D6B1, 0x641C83AE), /* ~= 10^123 */\n    U64(0xF209787B, 0xB47D6B84), U64(0xC0678C5D, 0xBD23A49A), /* ~= 10^124 */\n    U64(0x9745EB4D, 0x50CE6332), U64(0xF840B7BA, 0x963646E0), /* ~= 10^125 */\n    U64(0xBD176620, 0xA501FBFF), U64(0xB650E5A9, 0x3BC3D898), /* ~= 10^126 */\n    U64(0xEC5D3FA8, 0xCE427AFF), U64(0xA3E51F13, 0x8AB4CEBE), /* ~= 10^127 */\n    U64(0x93BA47C9, 0x80E98CDF), U64(0xC66F336C, 0x36B10137), /* ~= 10^128 */\n    U64(0xB8A8D9BB, 0xE123F017), U64(0xB80B0047, 0x445D4184), /* ~= 10^129 */\n    U64(0xE6D3102A, 0xD96CEC1D), U64(0xA60DC059, 0x157491E5), /* ~= 10^130 */\n    U64(0x9043EA1A, 0xC7E41392), U64(0x87C89837, 0xAD68DB2F), /* ~= 10^131 */\n    U64(0xB454E4A1, 0x79DD1877), U64(0x29BABE45, 0x98C311FB), /* ~= 10^132 */\n    U64(0xE16A1DC9, 0xD8545E94), U64(0xF4296DD6, 0xFEF3D67A), /* ~= 10^133 */\n    U64(0x8CE2529E, 0x2734BB1D), U64(0x1899E4A6, 0x5F58660C), /* ~= 10^134 */\n    U64(0xB01AE745, 0xB101E9E4), U64(0x5EC05DCF, 0xF72E7F8F), /* ~= 10^135 */\n    U64(0xDC21A117, 0x1D42645D), U64(0x76707543, 0xF4FA1F73), /* ~= 10^136 */\n    U64(0x899504AE, 0x72497EBA), U64(0x6A06494A, 0x791C53A8), /* ~= 10^137 */\n    U64(0xABFA45DA, 0x0EDBDE69), U64(0x0487DB9D, 0x17636892), /* ~= 10^138 */\n    U64(0xD6F8D750, 0x9292D603), U64(0x45A9D284, 0x5D3C42B6), /* ~= 10^139 */\n    U64(0x865B8692, 0x5B9BC5C2), U64(0x0B8A2392, 0xBA45A9B2), /* ~= 10^140 */\n    U64(0xA7F26836, 0xF282B732), U64(0x8E6CAC77, 0x68D7141E), /* ~= 10^141 */\n    U64(0xD1EF0244, 0xAF2364FF), U64(0x3207D795, 0x430CD926), /* ~= 10^142 */\n    U64(0x8335616A, 0xED761F1F), U64(0x7F44E6BD, 0x49E807B8), /* ~= 10^143 */\n    U64(0xA402B9C5, 0xA8D3A6E7), U64(0x5F16206C, 0x9C6209A6), /* ~= 10^144 */\n    U64(0xCD036837, 0x130890A1), U64(0x36DBA887, 0xC37A8C0F), /* ~= 10^145 */\n    U64(0x80222122, 0x6BE55A64), U64(0xC2494954, 0xDA2C9789), /* ~= 10^146 */\n    U64(0xA02AA96B, 0x06DEB0FD), U64(0xF2DB9BAA, 0x10B7BD6C), /* ~= 10^147 */\n    U64(0xC83553C5, 0xC8965D3D), U64(0x6F928294, 0x94E5ACC7), /* ~= 10^148 */\n    U64(0xFA42A8B7, 0x3ABBF48C), U64(0xCB772339, 0xBA1F17F9), /* ~= 10^149 */\n    U64(0x9C69A972, 0x84B578D7), U64(0xFF2A7604, 0x14536EFB), /* ~= 10^150 */\n    U64(0xC38413CF, 0x25E2D70D), U64(0xFEF51385, 0x19684ABA), /* ~= 10^151 */\n    U64(0xF46518C2, 0xEF5B8CD1), U64(0x7EB25866, 0x5FC25D69), /* ~= 10^152 */\n    U64(0x98BF2F79, 0xD5993802), U64(0xEF2F773F, 0xFBD97A61), /* ~= 10^153 */\n    U64(0xBEEEFB58, 0x4AFF8603), U64(0xAAFB550F, 0xFACFD8FA), /* ~= 10^154 */\n    U64(0xEEAABA2E, 0x5DBF6784), U64(0x95BA2A53, 0xF983CF38), /* ~= 10^155 */\n    U64(0x952AB45C, 0xFA97A0B2), U64(0xDD945A74, 0x7BF26183), /* ~= 10^156 */\n    U64(0xBA756174, 0x393D88DF), U64(0x94F97111, 0x9AEEF9E4), /* ~= 10^157 */\n    U64(0xE912B9D1, 0x478CEB17), U64(0x7A37CD56, 0x01AAB85D), /* ~= 10^158 */\n    U64(0x91ABB422, 0xCCB812EE), U64(0xAC62E055, 0xC10AB33A), /* ~= 10^159 */\n    U64(0xB616A12B, 0x7FE617AA), U64(0x577B986B, 0x314D6009), /* ~= 10^160 */\n    U64(0xE39C4976, 0x5FDF9D94), U64(0xED5A7E85, 0xFDA0B80B), /* ~= 10^161 */\n    U64(0x8E41ADE9, 0xFBEBC27D), U64(0x14588F13, 0xBE847307), /* ~= 10^162 */\n    U64(0xB1D21964, 0x7AE6B31C), U64(0x596EB2D8, 0xAE258FC8), /* ~= 10^163 */\n    U64(0xDE469FBD, 0x99A05FE3), U64(0x6FCA5F8E, 0xD9AEF3BB), /* ~= 10^164 */\n    U64(0x8AEC23D6, 0x80043BEE), U64(0x25DE7BB9, 0x480D5854), /* ~= 10^165 */\n    U64(0xADA72CCC, 0x20054AE9), U64(0xAF561AA7, 0x9A10AE6A), /* ~= 10^166 */\n    U64(0xD910F7FF, 0x28069DA4), U64(0x1B2BA151, 0x8094DA04), /* ~= 10^167 */\n    U64(0x87AA9AFF, 0x79042286), U64(0x90FB44D2, 0xF05D0842), /* ~= 10^168 */\n    U64(0xA99541BF, 0x57452B28), U64(0x353A1607, 0xAC744A53), /* ~= 10^169 */\n    U64(0xD3FA922F, 0x2D1675F2), U64(0x42889B89, 0x97915CE8), /* ~= 10^170 */\n    U64(0x847C9B5D, 0x7C2E09B7), U64(0x69956135, 0xFEBADA11), /* ~= 10^171 */\n    U64(0xA59BC234, 0xDB398C25), U64(0x43FAB983, 0x7E699095), /* ~= 10^172 */\n    U64(0xCF02B2C2, 0x1207EF2E), U64(0x94F967E4, 0x5E03F4BB), /* ~= 10^173 */\n    U64(0x8161AFB9, 0x4B44F57D), U64(0x1D1BE0EE, 0xBAC278F5), /* ~= 10^174 */\n    U64(0xA1BA1BA7, 0x9E1632DC), U64(0x6462D92A, 0x69731732), /* ~= 10^175 */\n    U64(0xCA28A291, 0x859BBF93), U64(0x7D7B8F75, 0x03CFDCFE), /* ~= 10^176 */\n    U64(0xFCB2CB35, 0xE702AF78), U64(0x5CDA7352, 0x44C3D43E), /* ~= 10^177 */\n    U64(0x9DEFBF01, 0xB061ADAB), U64(0x3A088813, 0x6AFA64A7), /* ~= 10^178 */\n    U64(0xC56BAEC2, 0x1C7A1916), U64(0x088AAA18, 0x45B8FDD0), /* ~= 10^179 */\n    U64(0xF6C69A72, 0xA3989F5B), U64(0x8AAD549E, 0x57273D45), /* ~= 10^180 */\n    U64(0x9A3C2087, 0xA63F6399), U64(0x36AC54E2, 0xF678864B), /* ~= 10^181 */\n    U64(0xC0CB28A9, 0x8FCF3C7F), U64(0x84576A1B, 0xB416A7DD), /* ~= 10^182 */\n    U64(0xF0FDF2D3, 0xF3C30B9F), U64(0x656D44A2, 0xA11C51D5), /* ~= 10^183 */\n    U64(0x969EB7C4, 0x7859E743), U64(0x9F644AE5, 0xA4B1B325), /* ~= 10^184 */\n    U64(0xBC4665B5, 0x96706114), U64(0x873D5D9F, 0x0DDE1FEE), /* ~= 10^185 */\n    U64(0xEB57FF22, 0xFC0C7959), U64(0xA90CB506, 0xD155A7EA), /* ~= 10^186 */\n    U64(0x9316FF75, 0xDD87CBD8), U64(0x09A7F124, 0x42D588F2), /* ~= 10^187 */\n    U64(0xB7DCBF53, 0x54E9BECE), U64(0x0C11ED6D, 0x538AEB2F), /* ~= 10^188 */\n    U64(0xE5D3EF28, 0x2A242E81), U64(0x8F1668C8, 0xA86DA5FA), /* ~= 10^189 */\n    U64(0x8FA47579, 0x1A569D10), U64(0xF96E017D, 0x694487BC), /* ~= 10^190 */\n    U64(0xB38D92D7, 0x60EC4455), U64(0x37C981DC, 0xC395A9AC), /* ~= 10^191 */\n    U64(0xE070F78D, 0x3927556A), U64(0x85BBE253, 0xF47B1417), /* ~= 10^192 */\n    U64(0x8C469AB8, 0x43B89562), U64(0x93956D74, 0x78CCEC8E), /* ~= 10^193 */\n    U64(0xAF584166, 0x54A6BABB), U64(0x387AC8D1, 0x970027B2), /* ~= 10^194 */\n    U64(0xDB2E51BF, 0xE9D0696A), U64(0x06997B05, 0xFCC0319E), /* ~= 10^195 */\n    U64(0x88FCF317, 0xF22241E2), U64(0x441FECE3, 0xBDF81F03), /* ~= 10^196 */\n    U64(0xAB3C2FDD, 0xEEAAD25A), U64(0xD527E81C, 0xAD7626C3), /* ~= 10^197 */\n    U64(0xD60B3BD5, 0x6A5586F1), U64(0x8A71E223, 0xD8D3B074), /* ~= 10^198 */\n    U64(0x85C70565, 0x62757456), U64(0xF6872D56, 0x67844E49), /* ~= 10^199 */\n    U64(0xA738C6BE, 0xBB12D16C), U64(0xB428F8AC, 0x016561DB), /* ~= 10^200 */\n    U64(0xD106F86E, 0x69D785C7), U64(0xE13336D7, 0x01BEBA52), /* ~= 10^201 */\n    U64(0x82A45B45, 0x0226B39C), U64(0xECC00246, 0x61173473), /* ~= 10^202 */\n    U64(0xA34D7216, 0x42B06084), U64(0x27F002D7, 0xF95D0190), /* ~= 10^203 */\n    U64(0xCC20CE9B, 0xD35C78A5), U64(0x31EC038D, 0xF7B441F4), /* ~= 10^204 */\n    U64(0xFF290242, 0xC83396CE), U64(0x7E670471, 0x75A15271), /* ~= 10^205 */\n    U64(0x9F79A169, 0xBD203E41), U64(0x0F0062C6, 0xE984D386), /* ~= 10^206 */\n    U64(0xC75809C4, 0x2C684DD1), U64(0x52C07B78, 0xA3E60868), /* ~= 10^207 */\n    U64(0xF92E0C35, 0x37826145), U64(0xA7709A56, 0xCCDF8A82), /* ~= 10^208 */\n    U64(0x9BBCC7A1, 0x42B17CCB), U64(0x88A66076, 0x400BB691), /* ~= 10^209 */\n    U64(0xC2ABF989, 0x935DDBFE), U64(0x6ACFF893, 0xD00EA435), /* ~= 10^210 */\n    U64(0xF356F7EB, 0xF83552FE), U64(0x0583F6B8, 0xC4124D43), /* ~= 10^211 */\n    U64(0x98165AF3, 0x7B2153DE), U64(0xC3727A33, 0x7A8B704A), /* ~= 10^212 */\n    U64(0xBE1BF1B0, 0x59E9A8D6), U64(0x744F18C0, 0x592E4C5C), /* ~= 10^213 */\n    U64(0xEDA2EE1C, 0x7064130C), U64(0x1162DEF0, 0x6F79DF73), /* ~= 10^214 */\n    U64(0x9485D4D1, 0xC63E8BE7), U64(0x8ADDCB56, 0x45AC2BA8), /* ~= 10^215 */\n    U64(0xB9A74A06, 0x37CE2EE1), U64(0x6D953E2B, 0xD7173692), /* ~= 10^216 */\n    U64(0xE8111C87, 0xC5C1BA99), U64(0xC8FA8DB6, 0xCCDD0437), /* ~= 10^217 */\n    U64(0x910AB1D4, 0xDB9914A0), U64(0x1D9C9892, 0x400A22A2), /* ~= 10^218 */\n    U64(0xB54D5E4A, 0x127F59C8), U64(0x2503BEB6, 0xD00CAB4B), /* ~= 10^219 */\n    U64(0xE2A0B5DC, 0x971F303A), U64(0x2E44AE64, 0x840FD61D), /* ~= 10^220 */\n    U64(0x8DA471A9, 0xDE737E24), U64(0x5CEAECFE, 0xD289E5D2), /* ~= 10^221 */\n    U64(0xB10D8E14, 0x56105DAD), U64(0x7425A83E, 0x872C5F47), /* ~= 10^222 */\n    U64(0xDD50F199, 0x6B947518), U64(0xD12F124E, 0x28F77719), /* ~= 10^223 */\n    U64(0x8A5296FF, 0xE33CC92F), U64(0x82BD6B70, 0xD99AAA6F), /* ~= 10^224 */\n    U64(0xACE73CBF, 0xDC0BFB7B), U64(0x636CC64D, 0x1001550B), /* ~= 10^225 */\n    U64(0xD8210BEF, 0xD30EFA5A), U64(0x3C47F7E0, 0x5401AA4E), /* ~= 10^226 */\n    U64(0x8714A775, 0xE3E95C78), U64(0x65ACFAEC, 0x34810A71), /* ~= 10^227 */\n    U64(0xA8D9D153, 0x5CE3B396), U64(0x7F1839A7, 0x41A14D0D), /* ~= 10^228 */\n    U64(0xD31045A8, 0x341CA07C), U64(0x1EDE4811, 0x1209A050), /* ~= 10^229 */\n    U64(0x83EA2B89, 0x2091E44D), U64(0x934AED0A, 0xAB460432), /* ~= 10^230 */\n    U64(0xA4E4B66B, 0x68B65D60), U64(0xF81DA84D, 0x5617853F), /* ~= 10^231 */\n    U64(0xCE1DE406, 0x42E3F4B9), U64(0x36251260, 0xAB9D668E), /* ~= 10^232 */\n    U64(0x80D2AE83, 0xE9CE78F3), U64(0xC1D72B7C, 0x6B426019), /* ~= 10^233 */\n    U64(0xA1075A24, 0xE4421730), U64(0xB24CF65B, 0x8612F81F), /* ~= 10^234 */\n    U64(0xC94930AE, 0x1D529CFC), U64(0xDEE033F2, 0x6797B627), /* ~= 10^235 */\n    U64(0xFB9B7CD9, 0xA4A7443C), U64(0x169840EF, 0x017DA3B1), /* ~= 10^236 */\n    U64(0x9D412E08, 0x06E88AA5), U64(0x8E1F2895, 0x60EE864E), /* ~= 10^237 */\n    U64(0xC491798A, 0x08A2AD4E), U64(0xF1A6F2BA, 0xB92A27E2), /* ~= 10^238 */\n    U64(0xF5B5D7EC, 0x8ACB58A2), U64(0xAE10AF69, 0x6774B1DB), /* ~= 10^239 */\n    U64(0x9991A6F3, 0xD6BF1765), U64(0xACCA6DA1, 0xE0A8EF29), /* ~= 10^240 */\n    U64(0xBFF610B0, 0xCC6EDD3F), U64(0x17FD090A, 0x58D32AF3), /* ~= 10^241 */\n    U64(0xEFF394DC, 0xFF8A948E), U64(0xDDFC4B4C, 0xEF07F5B0), /* ~= 10^242 */\n    U64(0x95F83D0A, 0x1FB69CD9), U64(0x4ABDAF10, 0x1564F98E), /* ~= 10^243 */\n    U64(0xBB764C4C, 0xA7A4440F), U64(0x9D6D1AD4, 0x1ABE37F1), /* ~= 10^244 */\n    U64(0xEA53DF5F, 0xD18D5513), U64(0x84C86189, 0x216DC5ED), /* ~= 10^245 */\n    U64(0x92746B9B, 0xE2F8552C), U64(0x32FD3CF5, 0xB4E49BB4), /* ~= 10^246 */\n    U64(0xB7118682, 0xDBB66A77), U64(0x3FBC8C33, 0x221DC2A1), /* ~= 10^247 */\n    U64(0xE4D5E823, 0x92A40515), U64(0x0FABAF3F, 0xEAA5334A), /* ~= 10^248 */\n    U64(0x8F05B116, 0x3BA6832D), U64(0x29CB4D87, 0xF2A7400E), /* ~= 10^249 */\n    U64(0xB2C71D5B, 0xCA9023F8), U64(0x743E20E9, 0xEF511012), /* ~= 10^250 */\n    U64(0xDF78E4B2, 0xBD342CF6), U64(0x914DA924, 0x6B255416), /* ~= 10^251 */\n    U64(0x8BAB8EEF, 0xB6409C1A), U64(0x1AD089B6, 0xC2F7548E), /* ~= 10^252 */\n    U64(0xAE9672AB, 0xA3D0C320), U64(0xA184AC24, 0x73B529B1), /* ~= 10^253 */\n    U64(0xDA3C0F56, 0x8CC4F3E8), U64(0xC9E5D72D, 0x90A2741E), /* ~= 10^254 */\n    U64(0x88658996, 0x17FB1871), U64(0x7E2FA67C, 0x7A658892), /* ~= 10^255 */\n    U64(0xAA7EEBFB, 0x9DF9DE8D), U64(0xDDBB901B, 0x98FEEAB7), /* ~= 10^256 */\n    U64(0xD51EA6FA, 0x85785631), U64(0x552A7422, 0x7F3EA565), /* ~= 10^257 */\n    U64(0x8533285C, 0x936B35DE), U64(0xD53A8895, 0x8F87275F), /* ~= 10^258 */\n    U64(0xA67FF273, 0xB8460356), U64(0x8A892ABA, 0xF368F137), /* ~= 10^259 */\n    U64(0xD01FEF10, 0xA657842C), U64(0x2D2B7569, 0xB0432D85), /* ~= 10^260 */\n    U64(0x8213F56A, 0x67F6B29B), U64(0x9C3B2962, 0x0E29FC73), /* ~= 10^261 */\n    U64(0xA298F2C5, 0x01F45F42), U64(0x8349F3BA, 0x91B47B8F), /* ~= 10^262 */\n    U64(0xCB3F2F76, 0x42717713), U64(0x241C70A9, 0x36219A73), /* ~= 10^263 */\n    U64(0xFE0EFB53, 0xD30DD4D7), U64(0xED238CD3, 0x83AA0110), /* ~= 10^264 */\n    U64(0x9EC95D14, 0x63E8A506), U64(0xF4363804, 0x324A40AA), /* ~= 10^265 */\n    U64(0xC67BB459, 0x7CE2CE48), U64(0xB143C605, 0x3EDCD0D5), /* ~= 10^266 */\n    U64(0xF81AA16F, 0xDC1B81DA), U64(0xDD94B786, 0x8E94050A), /* ~= 10^267 */\n    U64(0x9B10A4E5, 0xE9913128), U64(0xCA7CF2B4, 0x191C8326), /* ~= 10^268 */\n    U64(0xC1D4CE1F, 0x63F57D72), U64(0xFD1C2F61, 0x1F63A3F0), /* ~= 10^269 */\n    U64(0xF24A01A7, 0x3CF2DCCF), U64(0xBC633B39, 0x673C8CEC), /* ~= 10^270 */\n    U64(0x976E4108, 0x8617CA01), U64(0xD5BE0503, 0xE085D813), /* ~= 10^271 */\n    U64(0xBD49D14A, 0xA79DBC82), U64(0x4B2D8644, 0xD8A74E18), /* ~= 10^272 */\n    U64(0xEC9C459D, 0x51852BA2), U64(0xDDF8E7D6, 0x0ED1219E), /* ~= 10^273 */\n    U64(0x93E1AB82, 0x52F33B45), U64(0xCABB90E5, 0xC942B503), /* ~= 10^274 */\n    U64(0xB8DA1662, 0xE7B00A17), U64(0x3D6A751F, 0x3B936243), /* ~= 10^275 */\n    U64(0xE7109BFB, 0xA19C0C9D), U64(0x0CC51267, 0x0A783AD4), /* ~= 10^276 */\n    U64(0x906A617D, 0x450187E2), U64(0x27FB2B80, 0x668B24C5), /* ~= 10^277 */\n    U64(0xB484F9DC, 0x9641E9DA), U64(0xB1F9F660, 0x802DEDF6), /* ~= 10^278 */\n    U64(0xE1A63853, 0xBBD26451), U64(0x5E7873F8, 0xA0396973), /* ~= 10^279 */\n    U64(0x8D07E334, 0x55637EB2), U64(0xDB0B487B, 0x6423E1E8), /* ~= 10^280 */\n    U64(0xB049DC01, 0x6ABC5E5F), U64(0x91CE1A9A, 0x3D2CDA62), /* ~= 10^281 */\n    U64(0xDC5C5301, 0xC56B75F7), U64(0x7641A140, 0xCC7810FB), /* ~= 10^282 */\n    U64(0x89B9B3E1, 0x1B6329BA), U64(0xA9E904C8, 0x7FCB0A9D), /* ~= 10^283 */\n    U64(0xAC2820D9, 0x623BF429), U64(0x546345FA, 0x9FBDCD44), /* ~= 10^284 */\n    U64(0xD732290F, 0xBACAF133), U64(0xA97C1779, 0x47AD4095), /* ~= 10^285 */\n    U64(0x867F59A9, 0xD4BED6C0), U64(0x49ED8EAB, 0xCCCC485D), /* ~= 10^286 */\n    U64(0xA81F3014, 0x49EE8C70), U64(0x5C68F256, 0xBFFF5A74), /* ~= 10^287 */\n    U64(0xD226FC19, 0x5C6A2F8C), U64(0x73832EEC, 0x6FFF3111), /* ~= 10^288 */\n    U64(0x83585D8F, 0xD9C25DB7), U64(0xC831FD53, 0xC5FF7EAB), /* ~= 10^289 */\n    U64(0xA42E74F3, 0xD032F525), U64(0xBA3E7CA8, 0xB77F5E55), /* ~= 10^290 */\n    U64(0xCD3A1230, 0xC43FB26F), U64(0x28CE1BD2, 0xE55F35EB), /* ~= 10^291 */\n    U64(0x80444B5E, 0x7AA7CF85), U64(0x7980D163, 0xCF5B81B3), /* ~= 10^292 */\n    U64(0xA0555E36, 0x1951C366), U64(0xD7E105BC, 0xC332621F), /* ~= 10^293 */\n    U64(0xC86AB5C3, 0x9FA63440), U64(0x8DD9472B, 0xF3FEFAA7), /* ~= 10^294 */\n    U64(0xFA856334, 0x878FC150), U64(0xB14F98F6, 0xF0FEB951), /* ~= 10^295 */\n    U64(0x9C935E00, 0xD4B9D8D2), U64(0x6ED1BF9A, 0x569F33D3), /* ~= 10^296 */\n    U64(0xC3B83581, 0x09E84F07), U64(0x0A862F80, 0xEC4700C8), /* ~= 10^297 */\n    U64(0xF4A642E1, 0x4C6262C8), U64(0xCD27BB61, 0x2758C0FA), /* ~= 10^298 */\n    U64(0x98E7E9CC, 0xCFBD7DBD), U64(0x8038D51C, 0xB897789C), /* ~= 10^299 */\n    U64(0xBF21E440, 0x03ACDD2C), U64(0xE0470A63, 0xE6BD56C3), /* ~= 10^300 */\n    U64(0xEEEA5D50, 0x04981478), U64(0x1858CCFC, 0xE06CAC74), /* ~= 10^301 */\n    U64(0x95527A52, 0x02DF0CCB), U64(0x0F37801E, 0x0C43EBC8), /* ~= 10^302 */\n    U64(0xBAA718E6, 0x8396CFFD), U64(0xD3056025, 0x8F54E6BA), /* ~= 10^303 */\n    U64(0xE950DF20, 0x247C83FD), U64(0x47C6B82E, 0xF32A2069), /* ~= 10^304 */\n    U64(0x91D28B74, 0x16CDD27E), U64(0x4CDC331D, 0x57FA5441), /* ~= 10^305 */\n    U64(0xB6472E51, 0x1C81471D), U64(0xE0133FE4, 0xADF8E952), /* ~= 10^306 */\n    U64(0xE3D8F9E5, 0x63A198E5), U64(0x58180FDD, 0xD97723A6), /* ~= 10^307 */\n    U64(0x8E679C2F, 0x5E44FF8F), U64(0x570F09EA, 0xA7EA7648), /* ~= 10^308 */\n    U64(0xB201833B, 0x35D63F73), U64(0x2CD2CC65, 0x51E513DA), /* ~= 10^309 */\n    U64(0xDE81E40A, 0x034BCF4F), U64(0xF8077F7E, 0xA65E58D1), /* ~= 10^310 */\n    U64(0x8B112E86, 0x420F6191), U64(0xFB04AFAF, 0x27FAF782), /* ~= 10^311 */\n    U64(0xADD57A27, 0xD29339F6), U64(0x79C5DB9A, 0xF1F9B563), /* ~= 10^312 */\n    U64(0xD94AD8B1, 0xC7380874), U64(0x18375281, 0xAE7822BC), /* ~= 10^313 */\n    U64(0x87CEC76F, 0x1C830548), U64(0x8F229391, 0x0D0B15B5), /* ~= 10^314 */\n    U64(0xA9C2794A, 0xE3A3C69A), U64(0xB2EB3875, 0x504DDB22), /* ~= 10^315 */\n    U64(0xD433179D, 0x9C8CB841), U64(0x5FA60692, 0xA46151EB), /* ~= 10^316 */\n    U64(0x849FEEC2, 0x81D7F328), U64(0xDBC7C41B, 0xA6BCD333), /* ~= 10^317 */\n    U64(0xA5C7EA73, 0x224DEFF3), U64(0x12B9B522, 0x906C0800), /* ~= 10^318 */\n    U64(0xCF39E50F, 0xEAE16BEF), U64(0xD768226B, 0x34870A00), /* ~= 10^319 */\n    U64(0x81842F29, 0xF2CCE375), U64(0xE6A11583, 0x00D46640), /* ~= 10^320 */\n    U64(0xA1E53AF4, 0x6F801C53), U64(0x60495AE3, 0xC1097FD0), /* ~= 10^321 */\n    U64(0xCA5E89B1, 0x8B602368), U64(0x385BB19C, 0xB14BDFC4), /* ~= 10^322 */\n    U64(0xFCF62C1D, 0xEE382C42), U64(0x46729E03, 0xDD9ED7B5), /* ~= 10^323 */\n    U64(0x9E19DB92, 0xB4E31BA9), U64(0x6C07A2C2, 0x6A8346D1)  /* ~= 10^324 */\n};\n\n/**\n Get the cached pow10 value from `pow10_sig_table`.\n @param exp10 The exponent of pow(10, e). This value must in range\n              `POW10_SIG_TABLE_MIN_EXP` to `POW10_SIG_TABLE_MAX_EXP`.\n @param hi    The highest 64 bits of pow(10, e).\n @param lo    The lower 64 bits after `hi`.\n */\nstatic_inline void pow10_table_get_sig(i32 exp10, u64 *hi, u64 *lo) {\n    i32 idx = exp10 - (POW10_SIG_TABLE_MIN_EXP);\n    *hi = pow10_sig_table[idx * 2];\n    *lo = pow10_sig_table[idx * 2 + 1];\n}\n\n/**\n Get the exponent (base 2) for highest 64 bits significand in `pow10_sig_table`.\n */\nstatic_inline void pow10_table_get_exp(i32 exp10, i32 *exp2) {\n    /* e2 = floor(log2(pow(10, e))) - 64 + 1 */\n    /*    = floor(e * log2(10) - 63)         */\n    *exp2 = (exp10 * 217706 - 4128768) >> 16;\n}\n\n#endif\n\n\n\n/*==============================================================================\n * MARK: - Number and Bit Utils (Private)\n *============================================================================*/\n\n/** Convert bits to double. */\nstatic_inline f64 f64_from_bits(u64 u) {\n    f64 f;\n    memcpy(&f, &u, sizeof(u));\n    return f;\n}\n\n/** Convert double to bits. */\nstatic_inline u64 f64_to_bits(f64 f) {\n    u64 u;\n    memcpy(&u, &f, sizeof(u));\n    return u;\n}\n\n/** Convert double to bits. */\nstatic_inline u32 f32_to_bits(f32 f) {\n    u32 u;\n    memcpy(&u, &f, sizeof(u));\n    return u;\n}\n\n/** Get 'infinity' bits with sign. */\nstatic_inline u64 f64_bits_inf(bool sign) {\n#if YYJSON_HAS_IEEE_754\n    return F64_BITS_INF | ((u64)sign << 63);\n#elif defined(INFINITY)\n    return f64_to_bits(sign ? -INFINITY : INFINITY);\n#else\n    return f64_to_bits(sign ? -HUGE_VAL : HUGE_VAL);\n#endif\n}\n\n/** Get 'nan' bits with sign. */\nstatic_inline u64 f64_bits_nan(bool sign) {\n#if YYJSON_HAS_IEEE_754\n    return F64_BITS_NAN | ((u64)sign << 63);\n#elif defined(NAN)\n    return f64_to_bits(sign ? (f64)-NAN : (f64)NAN);\n#else\n    return f64_to_bits((sign ? -0.0 : 0.0) / 0.0);\n#endif\n}\n\n/** Casting double to float, allow overflow. */\n#if yyjson_has_attribute(no_sanitize)\n__attribute__((no_sanitize(\"undefined\")))\n#elif yyjson_gcc_available(4, 9, 0)\n__attribute__((__no_sanitize_undefined__))\n#endif\nstatic_inline f32 f64_to_f32(f64 val) {\n    return (f32)val;\n}\n\n/** Returns the number of leading 0-bits in value (input should not be 0). */\nstatic_inline u32 u64_lz_bits(u64 v) {\n#if GCC_HAS_CLZLL\n    return (u32)__builtin_clzll(v);\n#elif MSC_HAS_BIT_SCAN_64\n    unsigned long r;\n    _BitScanReverse64(&r, v);\n    return (u32)63 - (u32)r;\n#elif MSC_HAS_BIT_SCAN\n    unsigned long hi, lo;\n    bool hi_set = _BitScanReverse(&hi, (u32)(v >> 32)) != 0;\n    _BitScanReverse(&lo, (u32)v);\n    hi |= 32;\n    return (u32)63 - (u32)(hi_set ? hi : lo);\n#else\n    /* branchless, use De Bruijn sequence */\n    /* see: https://www.chessprogramming.org/BitScan */\n    const u8 table[64] = {\n        63, 16, 62,  7, 15, 36, 61,  3,  6, 14, 22, 26, 35, 47, 60,  2,\n         9,  5, 28, 11, 13, 21, 42, 19, 25, 31, 34, 40, 46, 52, 59,  1,\n        17,  8, 37,  4, 23, 27, 48, 10, 29, 12, 43, 20, 32, 41, 53, 18,\n        38, 24, 49, 30, 44, 33, 54, 39, 50, 45, 55, 51, 56, 57, 58,  0\n    };\n    v |= v >> 1;\n    v |= v >> 2;\n    v |= v >> 4;\n    v |= v >> 8;\n    v |= v >> 16;\n    v |= v >> 32;\n    return table[(v * U64(0x03F79D71, 0xB4CB0A89)) >> 58];\n#endif\n}\n\n/** Returns the number of trailing 0-bits in value (input should not be 0). */\nstatic_inline u32 u64_tz_bits(u64 v) {\n#if GCC_HAS_CTZLL\n    return (u32)__builtin_ctzll(v);\n#elif MSC_HAS_BIT_SCAN_64\n    unsigned long r;\n    _BitScanForward64(&r, v);\n    return (u32)r;\n#elif MSC_HAS_BIT_SCAN\n    unsigned long lo, hi;\n    bool lo_set = _BitScanForward(&lo, (u32)(v)) != 0;\n    _BitScanForward(&hi, (u32)(v >> 32));\n    hi += 32;\n    return lo_set ? lo : hi;\n#else\n    /* branchless, use De Bruijn sequence */\n    /* see: https://www.chessprogramming.org/BitScan */\n    const u8 table[64] = {\n         0,  1,  2, 53,  3,  7, 54, 27,  4, 38, 41,  8, 34, 55, 48, 28,\n        62,  5, 39, 46, 44, 42, 22,  9, 24, 35, 59, 56, 49, 18, 29, 11,\n        63, 52,  6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10,\n        51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12\n    };\n    return table[((v & (~v + 1)) * U64(0x022FDD63, 0xCC95386D)) >> 58];\n#endif\n}\n\n/** Multiplies two 64-bit unsigned integers (a * b),\n    returns the 128-bit result as 'hi' and 'lo'. */\nstatic_inline void u128_mul(u64 a, u64 b, u64 *hi, u64 *lo) {\n#if YYJSON_HAS_INT128\n    u128 m = (u128)a * b;\n    *hi = (u64)(m >> 64);\n    *lo = (u64)(m);\n#elif MSC_HAS_UMUL128\n    *lo = _umul128(a, b, hi);\n#else\n    u32 a0 = (u32)(a), a1 = (u32)(a >> 32);\n    u32 b0 = (u32)(b), b1 = (u32)(b >> 32);\n    u64 p00 = (u64)a0 * b0, p01 = (u64)a0 * b1;\n    u64 p10 = (u64)a1 * b0, p11 = (u64)a1 * b1;\n    u64 m0 = p01 + (p00 >> 32);\n    u32 m00 = (u32)(m0), m01 = (u32)(m0 >> 32);\n    u64 m1 = p10 + m00;\n    u32 m10 = (u32)(m1), m11 = (u32)(m1 >> 32);\n    *hi = p11 + m01 + m11;\n    *lo = ((u64)m10 << 32) | (u32)p00;\n#endif\n}\n\n/** Multiplies two 64-bit unsigned integers and add a value (a * b + c),\n    returns the 128-bit result as 'hi' and 'lo'. */\nstatic_inline void u128_mul_add(u64 a, u64 b, u64 c, u64 *hi, u64 *lo) {\n#if YYJSON_HAS_INT128\n    u128 m = (u128)a * b + c;\n    *hi = (u64)(m >> 64);\n    *lo = (u64)(m);\n#else\n    u64 h, l, t;\n    u128_mul(a, b, &h, &l);\n    t = l + c;\n    h += (u64)(((t < l) | (t < c)));\n    *hi = h;\n    *lo = t;\n#endif\n}\n\n\n\n/*==============================================================================\n * MARK: - File Utils (Private)\n * These functions are used to read and write JSON files.\n *============================================================================*/\n\n#define YYJSON_FOPEN_E\n#if !defined(_MSC_VER) && defined(__GLIBC__) && defined(__GLIBC_PREREQ)\n#   if __GLIBC_PREREQ(2, 7)\n#       undef YYJSON_FOPEN_E\n#       define YYJSON_FOPEN_E \"e\" /* glibc extension to enable O_CLOEXEC */\n#   endif\n#endif\n\nstatic_inline FILE *fopen_safe(const char *path, const char *mode) {\n#if YYJSON_MSC_VER >= 1400\n    FILE *file = NULL;\n    if (fopen_s(&file, path, mode) != 0) return NULL;\n    return file;\n#else\n    return fopen(path, mode);\n#endif\n}\n\nstatic_inline FILE *fopen_readonly(const char *path) {\n    return fopen_safe(path, \"rb\" YYJSON_FOPEN_E);\n}\n\nstatic_inline FILE *fopen_writeonly(const char *path) {\n    return fopen_safe(path, \"wb\" YYJSON_FOPEN_E);\n}\n\nstatic_inline usize fread_safe(void *buf, usize size, FILE *file) {\n#if YYJSON_MSC_VER >= 1400\n    return fread_s(buf, size, 1, size, file);\n#else\n    return fread(buf, 1, size, file);\n#endif\n}\n\n\n\n/*==============================================================================\n * MARK: - Size Utils (Private)\n * These functions are used for memory allocation.\n *============================================================================*/\n\n/** Returns whether the size is overflow after increment. */\nstatic_inline bool size_add_is_overflow(usize size, usize add) {\n    return size > (size + add);\n}\n\n/** Returns whether the size is power of 2 (size should not be 0). */\nstatic_inline bool size_is_pow2(usize size) {\n    return (size & (size - 1)) == 0;\n}\n\n/** Align size upwards (may overflow). */\nstatic_inline usize size_align_up(usize size, usize align) {\n    if (size_is_pow2(align)) {\n        return (size + (align - 1)) & ~(align - 1);\n    } else {\n        return size + align - (size + align - 1) % align - 1;\n    }\n}\n\n/** Align size downwards. */\nstatic_inline usize size_align_down(usize size, usize align) {\n    if (size_is_pow2(align)) {\n        return size & ~(align - 1);\n    } else {\n        return size - (size % align);\n    }\n}\n\n/** Align address upwards (may overflow). */\nstatic_inline void *mem_align_up(void *mem, usize align) {\n    usize size;\n    memcpy(&size, &mem, sizeof(usize));\n    size = size_align_up(size, align);\n    memcpy(&mem, &size, sizeof(usize));\n    return mem;\n}\n\n\n\n/*==============================================================================\n * MARK: - Default Memory Allocator (Private)\n * This is a simple libc memory allocator wrapper.\n *============================================================================*/\n\nstatic void *default_malloc(void *ctx, usize size) {\n    return malloc(size);\n}\n\nstatic void *default_realloc(void *ctx, void *ptr, usize old_size, usize size) {\n    return realloc(ptr, size);\n}\n\nstatic void default_free(void *ctx, void *ptr) {\n    free(ptr);\n}\n\nstatic const yyjson_alc YYJSON_DEFAULT_ALC = {\n    default_malloc, default_realloc, default_free, NULL\n};\n\n\n\n/*==============================================================================\n * MARK: - Null Memory Allocator (Private)\n * This allocator is just a placeholder to ensure that the internal\n * malloc/realloc/free function pointers are not null.\n *============================================================================*/\n\nstatic void *null_malloc(void *ctx, usize size) {\n    return NULL;\n}\n\nstatic void *null_realloc(void *ctx, void *ptr, usize old_size, usize size) {\n    return NULL;\n}\n\nstatic void null_free(void *ctx, void *ptr) {\n    return;\n}\n\nstatic const yyjson_alc YYJSON_NULL_ALC = {\n    null_malloc, null_realloc, null_free, NULL\n};\n\n\n\n/*==============================================================================\n * MARK: - Pool Memory Allocator (Public)\n * This allocator is initialized with a fixed-size buffer.\n * The buffer is split into multiple memory chunks for memory allocation.\n *============================================================================*/\n\n/** memory chunk header */\ntypedef struct pool_chunk {\n    usize size; /* chunk memory size, include chunk header */\n    struct pool_chunk *next; /* linked list, nullable */\n    /* char mem[]; flexible array member */\n} pool_chunk;\n\n/** allocator ctx header */\ntypedef struct pool_ctx {\n    usize size; /* total memory size, include ctx header */\n    pool_chunk *free_list; /* linked list, nullable */\n    /* pool_chunk chunks[]; flexible array member */\n} pool_ctx;\n\n/** align up the input size to chunk size */\nstatic_inline void pool_size_align(usize *size) {\n    *size = size_align_up(*size, sizeof(pool_chunk)) + sizeof(pool_chunk);\n}\n\nstatic void *pool_malloc(void *ctx_ptr, usize size) {\n    /* assert(size != 0) */\n    pool_ctx *ctx = (pool_ctx *)ctx_ptr;\n    pool_chunk *next, *prev = NULL, *cur = ctx->free_list;\n\n    if (unlikely(size >= ctx->size)) return NULL;\n    pool_size_align(&size);\n\n    while (cur) {\n        if (cur->size < size) {\n            /* not enough space, try next chunk */\n            prev = cur;\n            cur = cur->next;\n            continue;\n        }\n        if (cur->size >= size + sizeof(pool_chunk) * 2) {\n            /* too much space, split this chunk */\n            next = (pool_chunk *)(void *)((u8 *)cur + size);\n            next->size = cur->size - size;\n            next->next = cur->next;\n            cur->size = size;\n        } else {\n            /* just enough space, use whole chunk */\n            next = cur->next;\n        }\n        if (prev) prev->next = next;\n        else ctx->free_list = next;\n        return (void *)(cur + 1);\n    }\n    return NULL;\n}\n\nstatic void pool_free(void *ctx_ptr, void *ptr) {\n    /* assert(ptr != NULL) */\n    pool_ctx *ctx = (pool_ctx *)ctx_ptr;\n    pool_chunk *cur = ((pool_chunk *)ptr) - 1;\n    pool_chunk *prev = NULL, *next = ctx->free_list;\n\n    while (next && next < cur) {\n        prev = next;\n        next = next->next;\n    }\n    if (prev) prev->next = cur;\n    else ctx->free_list = cur;\n    cur->next = next;\n\n    if (next && ((u8 *)cur + cur->size) == (u8 *)next) {\n        /* merge cur to higher chunk */\n        cur->size += next->size;\n        cur->next = next->next;\n    }\n    if (prev && ((u8 *)prev + prev->size) == (u8 *)cur) {\n        /* merge cur to lower chunk */\n        prev->size += cur->size;\n        prev->next = cur->next;\n    }\n}\n\nstatic void *pool_realloc(void *ctx_ptr, void *ptr,\n                          usize old_size, usize size) {\n    /* assert(ptr != NULL && size != 0 && old_size < size) */\n    pool_ctx *ctx = (pool_ctx *)ctx_ptr;\n    pool_chunk *cur = ((pool_chunk *)ptr) - 1, *prev, *next, *tmp;\n\n    /* check size */\n    if (unlikely(size >= ctx->size)) return NULL;\n    pool_size_align(&old_size);\n    pool_size_align(&size);\n    if (unlikely(old_size == size)) return ptr;\n\n    /* find next and prev chunk */\n    prev = NULL;\n    next = ctx->free_list;\n    while (next && next < cur) {\n        prev = next;\n        next = next->next;\n    }\n\n    if ((u8 *)cur + cur->size == (u8 *)next && cur->size + next->size >= size) {\n        /* merge to higher chunk if they are contiguous */\n        usize free_size = cur->size + next->size - size;\n        if (free_size > sizeof(pool_chunk) * 2) {\n            tmp = (pool_chunk *)(void *)((u8 *)cur + size);\n            if (prev) prev->next = tmp;\n            else ctx->free_list = tmp;\n            tmp->next = next->next;\n            tmp->size = free_size;\n            cur->size = size;\n        } else {\n            if (prev) prev->next = next->next;\n            else ctx->free_list = next->next;\n            cur->size += next->size;\n        }\n        return ptr;\n    } else {\n        /* fallback to malloc and memcpy */\n        void *new_ptr = pool_malloc(ctx_ptr, size - sizeof(pool_chunk));\n        if (new_ptr) {\n            memcpy(new_ptr, ptr, cur->size - sizeof(pool_chunk));\n            pool_free(ctx_ptr, ptr);\n        }\n        return new_ptr;\n    }\n}\n\nbool yyjson_alc_pool_init(yyjson_alc *alc, void *buf, usize size) {\n    pool_chunk *chunk;\n    pool_ctx *ctx;\n\n    if (unlikely(!alc)) return false;\n    *alc = YYJSON_NULL_ALC;\n    if (size < sizeof(pool_ctx) * 4) return false;\n    ctx = (pool_ctx *)mem_align_up(buf, sizeof(pool_ctx));\n    if (unlikely(!ctx)) return false;\n    size -= (usize)((u8 *)ctx - (u8 *)buf);\n    size = size_align_down(size, sizeof(pool_ctx));\n\n    chunk = (pool_chunk *)(ctx + 1);\n    chunk->size = size - sizeof(pool_ctx);\n    chunk->next = NULL;\n    ctx->size = size;\n    ctx->free_list = chunk;\n\n    alc->malloc = pool_malloc;\n    alc->realloc = pool_realloc;\n    alc->free = pool_free;\n    alc->ctx = (void *)ctx;\n    return true;\n}\n\n\n\n/*==============================================================================\n * MARK: - Dynamic Memory Allocator (Public)\n * This allocator allocates memory on demand and does not immediately release\n * unused memory. Instead, it places the unused memory into a freelist for\n * potential reuse in the future. It is only when the entire allocator is\n * destroyed that all previously allocated memory is released at once.\n *============================================================================*/\n\n/** memory chunk header */\ntypedef struct dyn_chunk {\n    usize size; /* chunk size, include header */\n    struct dyn_chunk *next;\n    /* char mem[]; flexible array member */\n} dyn_chunk;\n\n/** allocator ctx header */\ntypedef struct {\n    dyn_chunk free_list; /* dummy header, sorted from small to large */\n    dyn_chunk used_list; /* dummy header */\n} dyn_ctx;\n\n/** align up the input size to chunk size */\nstatic_inline bool dyn_size_align(usize *size) {\n    usize alc_size = *size + sizeof(dyn_chunk);\n    alc_size = size_align_up(alc_size, YYJSON_ALC_DYN_MIN_SIZE);\n    if (unlikely(alc_size < *size)) return false; /* overflow */\n    *size = alc_size;\n    return true;\n}\n\n/** remove a chunk from list (the chunk must already be in the list) */\nstatic_inline void dyn_chunk_list_remove(dyn_chunk *list, dyn_chunk *chunk) {\n    dyn_chunk *prev = list, *cur;\n    for (cur = prev->next; cur; cur = cur->next) {\n        if (cur == chunk) {\n            prev->next = cur->next;\n            cur->next = NULL;\n            return;\n        }\n        prev = cur;\n    }\n}\n\n/** add a chunk to list header (the chunk must not be in the list) */\nstatic_inline void dyn_chunk_list_add(dyn_chunk *list, dyn_chunk *chunk) {\n    chunk->next = list->next;\n    list->next = chunk;\n}\n\nstatic void *dyn_malloc(void *ctx_ptr, usize size) {\n    /* assert(size != 0) */\n    const yyjson_alc def = YYJSON_DEFAULT_ALC;\n    dyn_ctx *ctx = (dyn_ctx *)ctx_ptr;\n    dyn_chunk *chunk, *prev;\n    if (unlikely(!dyn_size_align(&size))) return NULL;\n\n    /* freelist is empty, create new chunk */\n    if (!ctx->free_list.next) {\n        chunk = (dyn_chunk *)def.malloc(def.ctx, size);\n        if (unlikely(!chunk)) return NULL;\n        chunk->size = size;\n        chunk->next = NULL;\n        dyn_chunk_list_add(&ctx->used_list, chunk);\n        return (void *)(chunk + 1);\n    }\n\n    /* find a large enough chunk, or resize the largest chunk */\n    prev = &ctx->free_list;\n    while (true) {\n        chunk = prev->next;\n        if (chunk->size >= size) { /* enough size, reuse this chunk */\n            prev->next = chunk->next;\n            dyn_chunk_list_add(&ctx->used_list, chunk);\n            return (void *)(chunk + 1);\n        }\n        if (!chunk->next) { /* resize the largest chunk */\n            chunk = (dyn_chunk *)def.realloc(def.ctx, chunk, chunk->size, size);\n            if (unlikely(!chunk)) return NULL;\n            prev->next = NULL;\n            chunk->size = size;\n            dyn_chunk_list_add(&ctx->used_list, chunk);\n            return (void *)(chunk + 1);\n        }\n        prev = chunk;\n    }\n}\n\nstatic void *dyn_realloc(void *ctx_ptr, void *ptr,\n                          usize old_size, usize size) {\n    /* assert(ptr != NULL && size != 0 && old_size < size) */\n    const yyjson_alc def = YYJSON_DEFAULT_ALC;\n    dyn_ctx *ctx = (dyn_ctx *)ctx_ptr;\n    dyn_chunk *new_chunk, *chunk = (dyn_chunk *)ptr - 1;\n    if (unlikely(!dyn_size_align(&size))) return NULL;\n    if (chunk->size >= size) return ptr;\n\n    dyn_chunk_list_remove(&ctx->used_list, chunk);\n    new_chunk = (dyn_chunk *)def.realloc(def.ctx, chunk, chunk->size, size);\n    if (likely(new_chunk)) {\n        new_chunk->size = size;\n        chunk = new_chunk;\n    }\n    dyn_chunk_list_add(&ctx->used_list, chunk);\n    return new_chunk ? (void *)(new_chunk + 1) : NULL;\n}\n\nstatic void dyn_free(void *ctx_ptr, void *ptr) {\n    /* assert(ptr != NULL) */\n    dyn_ctx *ctx = (dyn_ctx *)ctx_ptr;\n    dyn_chunk *chunk = (dyn_chunk *)ptr - 1, *prev;\n\n    dyn_chunk_list_remove(&ctx->used_list, chunk);\n    for (prev = &ctx->free_list; prev; prev = prev->next) {\n        if (!prev->next || prev->next->size >= chunk->size) {\n            chunk->next = prev->next;\n            prev->next = chunk;\n            break;\n        }\n    }\n}\n\nyyjson_alc *yyjson_alc_dyn_new(void) {\n    const yyjson_alc def = YYJSON_DEFAULT_ALC;\n    usize hdr_len = sizeof(yyjson_alc) + sizeof(dyn_ctx);\n    yyjson_alc *alc = (yyjson_alc *)def.malloc(def.ctx, hdr_len);\n    dyn_ctx *ctx = (dyn_ctx *)(void *)(alc + 1);\n    if (unlikely(!alc)) return NULL;\n    alc->malloc = dyn_malloc;\n    alc->realloc = dyn_realloc;\n    alc->free = dyn_free;\n    alc->ctx = alc + 1;\n    memset(ctx, 0, sizeof(*ctx));\n    return alc;\n}\n\nvoid yyjson_alc_dyn_free(yyjson_alc *alc) {\n    const yyjson_alc def = YYJSON_DEFAULT_ALC;\n    dyn_ctx *ctx = (dyn_ctx *)(void *)(alc + 1);\n    dyn_chunk *chunk, *next;\n    if (unlikely(!alc)) return;\n    for (chunk = ctx->free_list.next; chunk; chunk = next) {\n        next = chunk->next;\n        def.free(def.ctx, chunk);\n    }\n    for (chunk = ctx->used_list.next; chunk; chunk = next) {\n        next = chunk->next;\n        def.free(def.ctx, chunk);\n    }\n    def.free(def.ctx, alc);\n}\n\n\n\n/*==============================================================================\n * MARK: - JSON Struct Utils (Public)\n * These functions are used for creating, copying, releasing, and comparing\n * JSON documents and values. They are widely used throughout this library.\n *============================================================================*/\n\nstatic_inline void unsafe_yyjson_str_pool_release(yyjson_str_pool *pool,\n                                                  yyjson_alc *alc) {\n    yyjson_str_chunk *chunk = pool->chunks, *next;\n    while (chunk) {\n        next = chunk->next;\n        alc->free(alc->ctx, chunk);\n        chunk = next;\n    }\n}\n\nstatic_inline void unsafe_yyjson_val_pool_release(yyjson_val_pool *pool,\n                                                  yyjson_alc *alc) {\n    yyjson_val_chunk *chunk = pool->chunks, *next;\n    while (chunk) {\n        next = chunk->next;\n        alc->free(alc->ctx, chunk);\n        chunk = next;\n    }\n}\n\nbool unsafe_yyjson_str_pool_grow(yyjson_str_pool *pool,\n                                 const yyjson_alc *alc, usize len) {\n    yyjson_str_chunk *chunk;\n    usize size, max_len;\n\n    /* create a new chunk */\n    max_len = USIZE_MAX - sizeof(yyjson_str_chunk);\n    if (unlikely(len > max_len)) return false;\n    size = len + sizeof(yyjson_str_chunk);\n    size = yyjson_max(pool->chunk_size, size);\n    chunk = (yyjson_str_chunk *)alc->malloc(alc->ctx, size);\n    if (unlikely(!chunk)) return false;\n\n    /* insert the new chunk as the head of the linked list */\n    chunk->next = pool->chunks;\n    chunk->chunk_size = size;\n    pool->chunks = chunk;\n    pool->cur = (char *)chunk + sizeof(yyjson_str_chunk);\n    pool->end = (char *)chunk + size;\n\n    /* the next chunk is twice the size of the current one */\n    size = yyjson_min(pool->chunk_size * 2, pool->chunk_size_max);\n    if (size < pool->chunk_size) size = pool->chunk_size_max; /* overflow */\n    pool->chunk_size = size;\n    return true;\n}\n\nbool unsafe_yyjson_val_pool_grow(yyjson_val_pool *pool,\n                                 const yyjson_alc *alc, usize count) {\n    yyjson_val_chunk *chunk;\n    usize size, max_count;\n\n    /* create a new chunk */\n    max_count = USIZE_MAX / sizeof(yyjson_mut_val) - 1;\n    if (unlikely(count > max_count)) return false;\n    size = (count + 1) * sizeof(yyjson_mut_val);\n    size = yyjson_max(pool->chunk_size, size);\n    chunk = (yyjson_val_chunk *)alc->malloc(alc->ctx, size);\n    if (unlikely(!chunk)) return false;\n\n    /* insert the new chunk as the head of the linked list */\n    chunk->next = pool->chunks;\n    chunk->chunk_size = size;\n    pool->chunks = chunk;\n    pool->cur = (yyjson_mut_val *)(void *)((u8 *)chunk) + 1;\n    pool->end = (yyjson_mut_val *)(void *)((u8 *)chunk + size);\n\n    /* the next chunk is twice the size of the current one */\n    size = yyjson_min(pool->chunk_size * 2, pool->chunk_size_max);\n    if (size < pool->chunk_size) size = pool->chunk_size_max; /* overflow */\n    pool->chunk_size = size;\n    return true;\n}\n\nbool yyjson_mut_doc_set_str_pool_size(yyjson_mut_doc *doc, size_t len) {\n    usize max_size = USIZE_MAX - sizeof(yyjson_str_chunk);\n    if (!doc || !len || len > max_size) return false;\n    doc->str_pool.chunk_size = len + sizeof(yyjson_str_chunk);\n    return true;\n}\n\nbool yyjson_mut_doc_set_val_pool_size(yyjson_mut_doc *doc, size_t count) {\n    usize max_count = USIZE_MAX / sizeof(yyjson_mut_val) - 1;\n    if (!doc || !count || count > max_count) return false;\n    doc->val_pool.chunk_size = (count + 1) * sizeof(yyjson_mut_val);\n    return true;\n}\n\nvoid yyjson_mut_doc_free(yyjson_mut_doc *doc) {\n    if (doc) {\n        yyjson_alc alc = doc->alc;\n        memset(&doc->alc, 0, sizeof(alc));\n        unsafe_yyjson_str_pool_release(&doc->str_pool, &alc);\n        unsafe_yyjson_val_pool_release(&doc->val_pool, &alc);\n        alc.free(alc.ctx, doc);\n    }\n}\n\nyyjson_mut_doc *yyjson_mut_doc_new(const yyjson_alc *alc) {\n    yyjson_mut_doc *doc;\n    if (!alc) alc = &YYJSON_DEFAULT_ALC;\n    doc = (yyjson_mut_doc *)alc->malloc(alc->ctx, sizeof(yyjson_mut_doc));\n    if (!doc) return NULL;\n    memset(doc, 0, sizeof(yyjson_mut_doc));\n\n    doc->alc = *alc;\n    doc->str_pool.chunk_size = YYJSON_MUT_DOC_STR_POOL_INIT_SIZE;\n    doc->str_pool.chunk_size_max = YYJSON_MUT_DOC_STR_POOL_MAX_SIZE;\n    doc->val_pool.chunk_size = YYJSON_MUT_DOC_VAL_POOL_INIT_SIZE;\n    doc->val_pool.chunk_size_max = YYJSON_MUT_DOC_VAL_POOL_MAX_SIZE;\n    return doc;\n}\n\nyyjson_mut_doc *yyjson_doc_mut_copy(yyjson_doc *doc, const yyjson_alc *alc) {\n    yyjson_mut_doc *m_doc;\n    yyjson_mut_val *m_val;\n\n    if (!doc || !doc->root) return NULL;\n    m_doc = yyjson_mut_doc_new(alc);\n    if (!m_doc) return NULL;\n    m_val = yyjson_val_mut_copy(m_doc, doc->root);\n    if (!m_val) {\n        yyjson_mut_doc_free(m_doc);\n        return NULL;\n    }\n    yyjson_mut_doc_set_root(m_doc, m_val);\n    return m_doc;\n}\n\nyyjson_mut_doc *yyjson_mut_doc_mut_copy(yyjson_mut_doc *doc,\n                                        const yyjson_alc *alc) {\n    yyjson_mut_doc *m_doc;\n    yyjson_mut_val *m_val;\n\n    if (!doc) return NULL;\n    if (!doc->root) return yyjson_mut_doc_new(alc);\n\n    m_doc = yyjson_mut_doc_new(alc);\n    if (!m_doc) return NULL;\n    m_val = yyjson_mut_val_mut_copy(m_doc, doc->root);\n    if (!m_val) {\n        yyjson_mut_doc_free(m_doc);\n        return NULL;\n    }\n    yyjson_mut_doc_set_root(m_doc, m_val);\n    return m_doc;\n}\n\nyyjson_mut_val *yyjson_val_mut_copy(yyjson_mut_doc *m_doc,\n                                    yyjson_val *i_vals) {\n    /*\n     The immutable object or array stores all sub-values in a contiguous memory,\n     We copy them to another contiguous memory as mutable values,\n     then reconnect the mutable values with the original relationship.\n     */\n    usize i_vals_len;\n    yyjson_mut_val *m_vals, *m_val;\n    yyjson_val *i_val, *i_end;\n\n    if (!m_doc || !i_vals) return NULL;\n    i_end = unsafe_yyjson_get_next(i_vals);\n    i_vals_len = (usize)(unsafe_yyjson_get_next(i_vals) - i_vals);\n    m_vals = unsafe_yyjson_mut_val(m_doc, i_vals_len);\n    if (!m_vals) return NULL;\n    i_val = i_vals;\n    m_val = m_vals;\n\n    for (; i_val < i_end; i_val++, m_val++) {\n        yyjson_type type = unsafe_yyjson_get_type(i_val);\n        m_val->tag = i_val->tag;\n        m_val->uni.u64 = i_val->uni.u64;\n        if (type == YYJSON_TYPE_STR || type == YYJSON_TYPE_RAW) {\n            const char *str = i_val->uni.str;\n            usize str_len = unsafe_yyjson_get_len(i_val);\n            m_val->uni.str = unsafe_yyjson_mut_strncpy(m_doc, str, str_len);\n            if (!m_val->uni.str) return NULL;\n        } else if (type == YYJSON_TYPE_ARR) {\n            usize len = unsafe_yyjson_get_len(i_val);\n            if (len > 0) {\n                yyjson_val *ii_val = i_val + 1, *ii_next;\n                yyjson_mut_val *mm_val = m_val + 1, *mm_ctn = m_val, *mm_next;\n                while (len-- > 1) {\n                    ii_next = unsafe_yyjson_get_next(ii_val);\n                    mm_next = mm_val + (ii_next - ii_val);\n                    mm_val->next = mm_next;\n                    ii_val = ii_next;\n                    mm_val = mm_next;\n                }\n                mm_val->next = mm_ctn + 1;\n                mm_ctn->uni.ptr = mm_val;\n            }\n        } else if (type == YYJSON_TYPE_OBJ) {\n            usize len = unsafe_yyjson_get_len(i_val);\n            if (len > 0) {\n                yyjson_val *ii_key = i_val + 1, *ii_nextkey;\n                yyjson_mut_val *mm_key = m_val + 1, *mm_ctn = m_val;\n                yyjson_mut_val *mm_nextkey;\n                while (len-- > 1) {\n                    ii_nextkey = unsafe_yyjson_get_next(ii_key + 1);\n                    mm_nextkey = mm_key + (ii_nextkey - ii_key);\n                    mm_key->next = mm_key + 1;\n                    mm_key->next->next = mm_nextkey;\n                    ii_key = ii_nextkey;\n                    mm_key = mm_nextkey;\n                }\n                mm_key->next = mm_key + 1;\n                mm_key->next->next = mm_ctn + 1;\n                mm_ctn->uni.ptr = mm_key;\n            }\n        }\n    }\n    return m_vals;\n}\n\nstatic yyjson_mut_val *unsafe_yyjson_mut_val_mut_copy(yyjson_mut_doc *m_doc,\n                                                      yyjson_mut_val *m_vals) {\n    /*\n     The mutable object or array stores all sub-values in a circular linked\n     list, so we can traverse them in the same loop. The traversal starts from\n     the last item, continues with the first item in a list, and ends with the\n     second to last item, which needs to be linked to the last item to close the\n     circle.\n     */\n    yyjson_mut_val *m_val = unsafe_yyjson_mut_val(m_doc, 1);\n    if (unlikely(!m_val)) return NULL;\n    m_val->tag = m_vals->tag;\n\n    switch (unsafe_yyjson_get_type(m_vals)) {\n        case YYJSON_TYPE_OBJ:\n        case YYJSON_TYPE_ARR:\n            if (unsafe_yyjson_get_len(m_vals) > 0) {\n                yyjson_mut_val *last = (yyjson_mut_val *)m_vals->uni.ptr;\n                yyjson_mut_val *next = last->next, *prev;\n                prev = unsafe_yyjson_mut_val_mut_copy(m_doc, last);\n                if (!prev) return NULL;\n                m_val->uni.ptr = (void *)prev;\n                while (next != last) {\n                    prev->next = unsafe_yyjson_mut_val_mut_copy(m_doc, next);\n                    if (!prev->next) return NULL;\n                    prev = prev->next;\n                    next = next->next;\n                }\n                prev->next = (yyjson_mut_val *)m_val->uni.ptr;\n            }\n            break;\n        case YYJSON_TYPE_RAW:\n        case YYJSON_TYPE_STR: {\n            const char *str = m_vals->uni.str;\n            usize str_len = unsafe_yyjson_get_len(m_vals);\n            m_val->uni.str = unsafe_yyjson_mut_strncpy(m_doc, str, str_len);\n            if (!m_val->uni.str) return NULL;\n            break;\n        }\n        default:\n            m_val->uni = m_vals->uni;\n            break;\n    }\n    return m_val;\n}\n\nyyjson_mut_val *yyjson_mut_val_mut_copy(yyjson_mut_doc *doc,\n                                        yyjson_mut_val *val) {\n    if (doc && val) return unsafe_yyjson_mut_val_mut_copy(doc, val);\n    return NULL;\n}\n\n/* Count the number of values and the total length of the strings. */\nstatic void yyjson_mut_stat(yyjson_mut_val *val,\n                            usize *val_sum, usize *str_sum) {\n    yyjson_type type = unsafe_yyjson_get_type(val);\n    *val_sum += 1;\n    if (type == YYJSON_TYPE_ARR || type == YYJSON_TYPE_OBJ) {\n        yyjson_mut_val *child = (yyjson_mut_val *)val->uni.ptr;\n        usize len = unsafe_yyjson_get_len(val), i;\n        len <<= (u8)(type == YYJSON_TYPE_OBJ);\n        *val_sum += len;\n        for (i = 0; i < len; i++) {\n            yyjson_type stype = unsafe_yyjson_get_type(child);\n            if (stype == YYJSON_TYPE_STR || stype == YYJSON_TYPE_RAW) {\n                *str_sum += unsafe_yyjson_get_len(child) + 1;\n            } else if (stype == YYJSON_TYPE_ARR || stype == YYJSON_TYPE_OBJ) {\n                yyjson_mut_stat(child, val_sum, str_sum);\n                *val_sum -= 1;\n            }\n            child = child->next;\n        }\n    } else if (type == YYJSON_TYPE_STR || type == YYJSON_TYPE_RAW) {\n        *str_sum += unsafe_yyjson_get_len(val) + 1;\n    }\n}\n\n/* Copy mutable values to immutable value pool. */\nstatic usize yyjson_imut_copy(yyjson_val **val_ptr, char **buf_ptr,\n                              yyjson_mut_val *mval) {\n    yyjson_val *val = *val_ptr;\n    yyjson_type type = unsafe_yyjson_get_type(mval);\n    if (type == YYJSON_TYPE_ARR || type == YYJSON_TYPE_OBJ) {\n        yyjson_mut_val *child = (yyjson_mut_val *)mval->uni.ptr;\n        usize len = unsafe_yyjson_get_len(mval), i;\n        usize val_sum = 1;\n        if (type == YYJSON_TYPE_OBJ) {\n            if (len) child = child->next->next;\n            len <<= 1;\n        } else {\n            if (len) child = child->next;\n        }\n        *val_ptr = val + 1;\n        for (i = 0; i < len; i++) {\n            val_sum += yyjson_imut_copy(val_ptr, buf_ptr, child);\n            child = child->next;\n        }\n        val->tag = mval->tag;\n        val->uni.ofs = val_sum * sizeof(yyjson_val);\n        return val_sum;\n    } else if (type == YYJSON_TYPE_STR || type == YYJSON_TYPE_RAW) {\n        char *buf = *buf_ptr;\n        usize len = unsafe_yyjson_get_len(mval);\n        memcpy((void *)buf, (const void *)mval->uni.str, len);\n        buf[len] = '\\0';\n        val->tag = mval->tag;\n        val->uni.str = buf;\n        *val_ptr = val + 1;\n        *buf_ptr = buf + len + 1;\n        return 1;\n    } else {\n        val->tag = mval->tag;\n        val->uni = mval->uni;\n        *val_ptr = val + 1;\n        return 1;\n    }\n}\n\nyyjson_doc *yyjson_mut_doc_imut_copy(yyjson_mut_doc *mdoc,\n                                     const yyjson_alc *alc) {\n    if (!mdoc) return NULL;\n    return yyjson_mut_val_imut_copy(mdoc->root, alc);\n}\n\nyyjson_doc *yyjson_mut_val_imut_copy(yyjson_mut_val *mval,\n                                     const yyjson_alc *alc) {\n    usize val_num = 0, str_sum = 0, hdr_size, buf_size;\n    yyjson_doc *doc = NULL;\n    yyjson_val *val_hdr = NULL;\n\n    /* This value should be NULL here. Setting a non-null value suppresses\n       warning from the clang analyzer. */\n    char *str_hdr = (char *)(void *)&str_sum;\n    if (!mval) return NULL;\n    if (!alc) alc = &YYJSON_DEFAULT_ALC;\n\n    /* traverse the input value to get pool size */\n    yyjson_mut_stat(mval, &val_num, &str_sum);\n\n    /* create doc and val pool */\n    hdr_size = size_align_up(sizeof(yyjson_doc), sizeof(yyjson_val));\n    buf_size = hdr_size + val_num * sizeof(yyjson_val);\n    doc = (yyjson_doc *)alc->malloc(alc->ctx, buf_size);\n    if (!doc) return NULL;\n    memset(doc, 0, sizeof(yyjson_doc));\n    val_hdr = (yyjson_val *)(void *)((char *)(void *)doc + hdr_size);\n    doc->root = val_hdr;\n    doc->alc = *alc;\n\n    /* create str pool */\n    if (str_sum > 0) {\n        str_hdr = (char *)alc->malloc(alc->ctx, str_sum);\n        doc->str_pool = str_hdr;\n        if (!str_hdr) {\n            alc->free(alc->ctx, (void *)doc);\n            return NULL;\n        }\n    }\n\n    /* copy vals and strs */\n    doc->val_read = yyjson_imut_copy(&val_hdr, &str_hdr, mval);\n    doc->dat_read = str_sum + 1;\n    return doc;\n}\n\nstatic_inline bool unsafe_yyjson_num_equals(void *lhs, void *rhs) {\n    yyjson_val_uni *luni = &((yyjson_val *)lhs)->uni;\n    yyjson_val_uni *runi = &((yyjson_val *)rhs)->uni;\n    yyjson_subtype lt = unsafe_yyjson_get_subtype(lhs);\n    yyjson_subtype rt = unsafe_yyjson_get_subtype(rhs);\n    if (lt == rt) return luni->u64 == runi->u64;\n    if (lt == YYJSON_SUBTYPE_SINT && rt == YYJSON_SUBTYPE_UINT) {\n        return luni->i64 >= 0 && luni->u64 == runi->u64;\n    }\n    if (lt == YYJSON_SUBTYPE_UINT && rt == YYJSON_SUBTYPE_SINT) {\n        return runi->i64 >= 0 && luni->u64 == runi->u64;\n    }\n    return false;\n}\n\nstatic_inline bool unsafe_yyjson_str_equals(void *lhs, void *rhs) {\n    usize len = unsafe_yyjson_get_len(lhs);\n    if (len != unsafe_yyjson_get_len(rhs)) return false;\n    return !memcmp(unsafe_yyjson_get_str(lhs),\n                   unsafe_yyjson_get_str(rhs), len);\n}\n\nbool unsafe_yyjson_equals(yyjson_val *lhs, yyjson_val *rhs) {\n    yyjson_type type = unsafe_yyjson_get_type(lhs);\n    if (type != unsafe_yyjson_get_type(rhs)) return false;\n\n    switch (type) {\n        case YYJSON_TYPE_OBJ: {\n            usize len = unsafe_yyjson_get_len(lhs);\n            if (len != unsafe_yyjson_get_len(rhs)) return false;\n            if (len > 0) {\n                yyjson_obj_iter iter;\n                yyjson_obj_iter_init(rhs, &iter);\n                lhs = unsafe_yyjson_get_first(lhs);\n                while (len-- > 0) {\n                    rhs = yyjson_obj_iter_getn(&iter, lhs->uni.str,\n                                               unsafe_yyjson_get_len(lhs));\n                    if (!rhs) return false;\n                    if (!unsafe_yyjson_equals(lhs + 1, rhs)) return false;\n                    lhs = unsafe_yyjson_get_next(lhs + 1);\n                }\n            }\n            /* yyjson allows duplicate keys, so the check may be inaccurate */\n            return true;\n        }\n\n        case YYJSON_TYPE_ARR: {\n            usize len = unsafe_yyjson_get_len(lhs);\n            if (len != unsafe_yyjson_get_len(rhs)) return false;\n            if (len > 0) {\n                lhs = unsafe_yyjson_get_first(lhs);\n                rhs = unsafe_yyjson_get_first(rhs);\n                while (len-- > 0) {\n                    if (!unsafe_yyjson_equals(lhs, rhs)) return false;\n                    lhs = unsafe_yyjson_get_next(lhs);\n                    rhs = unsafe_yyjson_get_next(rhs);\n                }\n            }\n            return true;\n        }\n\n        case YYJSON_TYPE_NUM:\n            return unsafe_yyjson_num_equals(lhs, rhs);\n\n        case YYJSON_TYPE_RAW:\n        case YYJSON_TYPE_STR:\n            return unsafe_yyjson_str_equals(lhs, rhs);\n\n        case YYJSON_TYPE_NULL:\n        case YYJSON_TYPE_BOOL:\n            return lhs->tag == rhs->tag;\n\n        default:\n            return false;\n    }\n}\n\nbool unsafe_yyjson_mut_equals(yyjson_mut_val *lhs, yyjson_mut_val *rhs) {\n    yyjson_type type = unsafe_yyjson_get_type(lhs);\n    if (type != unsafe_yyjson_get_type(rhs)) return false;\n\n    switch (type) {\n        case YYJSON_TYPE_OBJ: {\n            usize len = unsafe_yyjson_get_len(lhs);\n            if (len != unsafe_yyjson_get_len(rhs)) return false;\n            if (len > 0) {\n                yyjson_mut_obj_iter iter;\n                yyjson_mut_obj_iter_init(rhs, &iter);\n                lhs = (yyjson_mut_val *)lhs->uni.ptr;\n                while (len-- > 0) {\n                    rhs = yyjson_mut_obj_iter_getn(&iter, lhs->uni.str,\n                                                   unsafe_yyjson_get_len(lhs));\n                    if (!rhs) return false;\n                    if (!unsafe_yyjson_mut_equals(lhs->next, rhs)) return false;\n                    lhs = lhs->next->next;\n                }\n            }\n            /* yyjson allows duplicate keys, so the check may be inaccurate */\n            return true;\n        }\n\n        case YYJSON_TYPE_ARR: {\n            usize len = unsafe_yyjson_get_len(lhs);\n            if (len != unsafe_yyjson_get_len(rhs)) return false;\n            if (len > 0) {\n                lhs = (yyjson_mut_val *)lhs->uni.ptr;\n                rhs = (yyjson_mut_val *)rhs->uni.ptr;\n                while (len-- > 0) {\n                    if (!unsafe_yyjson_mut_equals(lhs, rhs)) return false;\n                    lhs = lhs->next;\n                    rhs = rhs->next;\n                }\n            }\n            return true;\n        }\n\n        case YYJSON_TYPE_NUM:\n            return unsafe_yyjson_num_equals(lhs, rhs);\n\n        case YYJSON_TYPE_RAW:\n        case YYJSON_TYPE_STR:\n            return unsafe_yyjson_str_equals(lhs, rhs);\n\n        case YYJSON_TYPE_NULL:\n        case YYJSON_TYPE_BOOL:\n            return lhs->tag == rhs->tag;\n\n        default:\n            return false;\n    }\n}\n\nbool yyjson_locate_pos(const char *str, size_t len, size_t pos,\n                       size_t *line, size_t *col, size_t *chr) {\n    usize line_sum = 0, line_pos = 0, chr_sum = 0;\n    const u8 *cur = (const u8 *)str;\n    const u8 *end = cur + pos;\n\n    if (!str || pos > len) {\n        if (line) *line = 0;\n        if (col) *col = 0;\n        if (chr) *chr = 0;\n        return false;\n    }\n\n    if (pos >= 3 && is_utf8_bom(cur)) cur += 3; /* don't count BOM */\n    while (cur < end) {\n        u8 c = *cur;\n        chr_sum += 1;\n        if (likely(c < 0x80)) {         /* 0xxxxxxx (0x00-0x7F) ASCII */\n            if (c == '\\n') {\n                line_sum += 1;\n                line_pos = chr_sum;\n            }\n            cur += 1;\n        }\n        else if (c < 0xC0) cur += 1;    /* 10xxxxxx (0x80-0xBF) Invalid */\n        else if (c < 0xE0) cur += 2;    /* 110xxxxx (0xC0-0xDF) 2-byte UTF-8 */\n        else if (c < 0xF0) cur += 3;    /* 1110xxxx (0xE0-0xEF) 3-byte UTF-8 */\n        else if (c < 0xF8) cur += 4;    /* 11110xxx (0xF0-0xF7) 4-byte UTF-8 */\n        else               cur += 1;    /* 11111xxx (0xF8-0xFF) Invalid */\n    }\n    if (line) *line = line_sum + 1;\n    if (col) *col = chr_sum - line_pos + 1;\n    if (chr) *chr = chr_sum;\n    return true;\n}\n\n\n\n#if !YYJSON_DISABLE_READER /* reader begin */\n\n/* Check read flag, avoids `always false` warning when disabled. */\n#define has_flg(_flg) unlikely(has_rflag(flg, YYJSON_READ_##_flg, 0))\n#define has_allow(_flg) unlikely(has_rflag(flg, YYJSON_READ_ALLOW_##_flg, 1))\n#define YYJSON_READ_ALLOW_TRIVIA (YYJSON_READ_ALLOW_COMMENTS | \\\n                                  YYJSON_READ_ALLOW_EXT_WHITESPACE)\nstatic_inline bool has_rflag(yyjson_read_flag flg, yyjson_read_flag chk,\n                             bool non_standard) {\n#if YYJSON_DISABLE_NON_STANDARD\n    if (non_standard) return false;\n#endif\n    return (flg & chk) != 0;\n}\n\n\n\n/*==============================================================================\n * MARK: - JSON Reader Utils (Private)\n * These functions are used by JSON reader to read literals and comments.\n *============================================================================*/\n\n/** Read `true` literal, `*ptr[0]` should be `t`. */\nstatic_inline bool read_true(u8 **ptr, yyjson_val *val) {\n    u8 *cur = *ptr;\n    if (likely(byte_match_4(cur, \"true\"))) {\n        val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE;\n        *ptr = cur + 4;\n        return true;\n    }\n    return false;\n}\n\n/** Read `false` literal, `*ptr[0]` should be `f`. */\nstatic_inline bool read_false(u8 **ptr, yyjson_val *val) {\n    u8 *cur = *ptr;\n    if (likely(byte_match_4(cur + 1, \"alse\"))) {\n        val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE;\n        *ptr = cur + 5;\n        return true;\n    }\n    return false;\n}\n\n/** Read `null` literal, `*ptr[0]` should be `n`. */\nstatic_inline bool read_null(u8 **ptr, yyjson_val *val) {\n    u8 *cur = *ptr;\n    if (likely(byte_match_4(cur, \"null\"))) {\n        val->tag = YYJSON_TYPE_NULL;\n        *ptr = cur + 4;\n        return true;\n    }\n    return false;\n}\n\n/** Read `Inf` or `Infinity` literal (ignoring case). */\nstatic_inline bool read_inf(u8 **ptr, u8 **pre,\n                            yyjson_read_flag flg, yyjson_val *val) {\n    u8 *hdr = *ptr;\n    u8 *cur = *ptr;\n    u8 **end = ptr;\n    bool sign = (*cur == '-');\n    if (*cur == '+' && !has_allow(EXT_NUMBER)) return false;\n    cur += char_is_sign(*cur);\n    if (char_to_lower(cur[0]) == 'i' &&\n        char_to_lower(cur[1]) == 'n' &&\n        char_to_lower(cur[2]) == 'f') {\n        if (char_to_lower(cur[3]) == 'i') {\n            if (char_to_lower(cur[4]) == 'n' &&\n                char_to_lower(cur[5]) == 'i' &&\n                char_to_lower(cur[6]) == 't' &&\n                char_to_lower(cur[7]) == 'y') {\n                cur += 8;\n            } else {\n                return false;\n            }\n        } else {\n            cur += 3;\n        }\n        *end = cur;\n        if (has_flg(NUMBER_AS_RAW)) {\n            **pre = '\\0'; /* add null-terminator for previous raw string */\n            *pre = cur; /* save end position for current raw string */\n            val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW;\n            val->uni.str = (const char *)hdr;\n        } else {\n            val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL;\n            val->uni.u64 = f64_bits_inf(sign);\n        }\n        return true;\n    }\n    return false;\n}\n\n/** Read `NaN` literal (ignoring case). */\nstatic_inline bool read_nan(u8 **ptr, u8 **pre,\n                            yyjson_read_flag flg, yyjson_val *val) {\n    u8 *hdr = *ptr;\n    u8 *cur = *ptr;\n    u8 **end = ptr;\n    bool sign = (*cur == '-');\n    if (*cur == '+' && !has_allow(EXT_NUMBER)) return false;\n    cur += char_is_sign(*cur);\n    if (char_to_lower(cur[0]) == 'n' &&\n        char_to_lower(cur[1]) == 'a' &&\n        char_to_lower(cur[2]) == 'n') {\n        cur += 3;\n        *end = cur;\n        if (has_flg(NUMBER_AS_RAW)) {\n            **pre = '\\0'; /* add null-terminator for previous raw string */\n            *pre = cur; /* save end position for current raw string */\n            val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW;\n            val->uni.str = (const char *)hdr;\n        } else {\n            val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL;\n            val->uni.u64 = f64_bits_nan(sign);\n        }\n        return true;\n    }\n    return false;\n}\n\n/** Read `Inf`, `Infinity` or `NaN` literal (ignoring case). */\nstatic_inline bool read_inf_or_nan(u8 **ptr, u8 **pre,\n                                   yyjson_read_flag flg, yyjson_val *val) {\n    if (read_inf(ptr, pre, flg, val)) return true;\n    if (read_nan(ptr, pre, flg, val)) return true;\n    return false;\n}\n\n/** Read a JSON number as raw string. */\nstatic_noinline bool read_num_raw(u8 **ptr, u8 **pre, yyjson_read_flag flg,\n                                  yyjson_val *val, const char **msg) {\n#define return_err(_pos, _msg) do { \\\n    *msg = _msg; *end = _pos; return false; \\\n} while (false)\n\n#define return_raw() do { \\\n    val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; \\\n    val->uni.str = (const char *)hdr; \\\n    **pre = '\\0'; *pre = cur; *end = cur; return true; \\\n} while (false)\n\n    u8 *hdr = *ptr;\n    u8 *cur = *ptr;\n    u8 **end = ptr;\n\n    /* skip sign */\n    cur += (*cur == '-');\n\n    /* read first digit, check leading zero */\n    while (unlikely(!char_is_digit(*cur))) {\n        if (has_allow(EXT_NUMBER)) {\n            if (*cur == '+' && cur == hdr) { /* leading `+` sign */\n                cur++;\n                continue;\n            }\n            if (*cur == '.' && char_is_digit(cur[1])) { /* e.g. '.123' */\n                goto read_double;\n            }\n        }\n        if (has_allow(INF_AND_NAN)) {\n            if (read_inf_or_nan(ptr, pre, flg, val)) return true;\n        }\n        return_err(cur, \"no digit after sign\");\n    }\n\n    /* read integral part */\n    if (*cur == '0') {\n        cur++;\n        if (unlikely(char_is_digit(*cur))) {\n            return_err(cur - 1, \"number with leading zero is not allowed\");\n        }\n        if (!char_is_fp(*cur)) {\n            if (has_allow(EXT_NUMBER) && char_to_lower(*cur) == 'x') { /* hex */\n                if (!char_is_hex(*++cur)) return_err(cur, \"invalid hex number\");\n                while(char_is_hex(*cur)) cur++;\n            }\n            return_raw();\n        }\n    } else {\n        while (char_is_digit(*cur)) cur++;\n        if (!char_is_fp(*cur)) return_raw();\n    }\n\nread_double:\n    /* read fraction part */\n    if (*cur == '.') {\n        cur++;\n        if (!char_is_digit(*cur)) {\n            if (has_allow(EXT_NUMBER)) {\n                if (!char_is_exp(*cur)) return_raw();\n            } else {\n                return_err(cur, \"no digit after decimal point\");\n            }\n        }\n        while (char_is_digit(*cur)) cur++;\n    }\n\n    /* read exponent part */\n    if (char_is_exp(*cur)) {\n        cur += 1 + char_is_sign(cur[1]);\n        if (!char_is_digit(*cur++)) {\n            return_err(cur, \"no digit after exponent sign\");\n        }\n        while (char_is_digit(*cur)) cur++;\n    }\n\n    return_raw();\n\n#undef return_err\n#undef return_raw\n}\n\n/** Read a hex number. */\nstatic_noinline bool read_num_hex(u8 **ptr, u8 **pre, yyjson_read_flag flg,\n                                  yyjson_val *val, const char **msg) {\n    u8 *hdr = *ptr;\n    u8 *cur = *ptr;\n    u8 **end = ptr;\n    u64 sig = 0, i = 0;\n    bool sign;\n\n    /* skip sign and '0x' */\n    sign = (*cur == '-');\n    cur += (*cur == '-' || *cur == '+') + 2;\n\n    /* read hex */\n    for(; i < 16; i++) {\n        u8 c = hex_conv_table[cur[i]];\n        if (c == 0xF0) break;\n        sig <<= 4;\n        sig |= c;\n    }\n\n    /* check error */\n    if (unlikely(i == 0)) {\n        *msg = \"invalid hex number\";\n        return false;\n    }\n\n    /* check overflow */\n    if (unlikely(i == 16)) {\n        if (char_is_hex(cur[16]) || (sign && sig > ((u64)1 << 63))) {\n            if (!has_flg(BIGNUM_AS_RAW)) {\n                *msg = \"hex number overflow\";\n                return false;\n            }\n            cur += 16;\n            while (char_is_hex(*cur)) cur++;\n            **pre = '\\0';\n            val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW;\n            val->uni.str = (const char *)hdr;\n            *pre = cur; *end = cur;\n            return true;\n        }\n    }\n\n    val->tag = YYJSON_TYPE_NUM | (u64)((u8)sign << 3);\n    val->uni.u64 = (u64)(sign ? (u64)(~(sig) + 1) : (u64)(sig));\n    *end = cur + i;\n    return true;\n}\n\n/**\n Skip trivia (whitespace and comments).\n This function should be used only when `char_is_trivia()` returns true.\n @param ptr  (inout) Input current position, output end position.\n @param eof  JSON end position.\n @param flg  JSON read flags.\n @return true  if at least one character was skipped.\n         false if no characters were skipped,\n               or if a multi-line comment is unterminated;\n               in the latter case, `ptr` will be set to `eof`.\n */\nstatic_noinline bool skip_trivia(u8 **ptr, u8 *eof, yyjson_read_flag flg) {\n    u8 *hdr = *ptr, *cur = *ptr;\n    usize len;\n\n    while (cur < eof) {\n        u8 *loop_begin = cur;\n\n        /* skip standard whitespace */\n        while(char_is_space(*cur)) cur++;\n\n        /* skip extended whitespace */\n        if (has_allow(EXT_WHITESPACE)) {\n            while (char_is_space_ext(*cur)) {\n                cur += (len = ext_space_len(cur));\n                if (!len) break;\n            }\n        }\n\n        /* skip comment, do not validate encoding */\n        if (has_allow(COMMENTS) && cur[0] == '/') {\n            if (cur[1] == '/') { /* single-line comment */\n                cur += 2;\n                if (has_allow(EXT_WHITESPACE)) {\n                    while (cur < eof) {\n                        if (char_is_eol_ext(*cur)) {\n                            cur += (len = ext_eol_len(cur));\n                            if (len) break;\n                        }\n                        cur++;\n                    }\n                } else {\n                    while (cur < eof && !char_is_eol(*cur)) cur++;\n                }\n            } else if (cur[1] == '*') { /* multi-line comment */\n                cur += 2;\n                while (!byte_match_2(cur, \"*/\") && cur < eof) cur++;\n                if (cur == eof) {\n                    *ptr = eof;\n                    return false; /* unclosed comment */\n                }\n                cur += 2;\n            }\n        }\n        if (cur == loop_begin) break;\n    }\n    *ptr = cur;\n    return cur > hdr;\n}\n\n/**\n Check truncated UTF-8 character.\n Return true if `cur` starts a valid UTF-8 sequence that is truncated.\n */\nstatic bool is_truncated_utf8(u8 *cur, u8 *eof) {\n    u8 c0, c1, c2;\n    usize len = (usize)(eof - cur);\n    if (cur >= eof || len >= 4) return false;\n    c0 = cur[0]; c1 = cur[1]; c2 = cur[2];\n    /* 1-byte UTF-8, not truncated */\n    if (c0 < 0x80) return false;\n    if (len == 1) {\n        /* 2-byte UTF-8, truncated */\n        if ((c0 & 0xE0) == 0xC0 && (c0 & 0x1E) != 0x00) return true;\n        /* 3-byte UTF-8, truncated */\n        if ((c0 & 0xF0) == 0xE0) return true;\n        /* 4-byte UTF-8, truncated */\n        if ((c0 & 0xF8) == 0xF0 && (c0 & 0x07) <= 0x04) return true;\n    } else if (len == 2) {\n        /* 3-byte UTF-8, truncated */\n        if ((c0 & 0xF0) == 0xE0 && (c1 & 0xC0) == 0x80) {\n            u8 t = (u8)(((c0 & 0x0F) << 1) | ((c1 & 0x20) >> 5));\n            return 0x01 <= t && t != 0x1B;\n        }\n        /* 4-byte UTF-8, truncated */\n        if ((c0 & 0xF8) == 0xF0 && (c1 & 0xC0) == 0x80) {\n            u8 t = (u8)(((c0 & 0x07) << 2) | ((c1 & 0x30) >> 4));\n            return 0x01 <= t && t <= 0x10;\n        }\n    } else if (len == 3) {\n        /* 4 bytes UTF-8, truncated */\n        if ((c0 & 0xF8) == 0xF0 && (c1 & 0xC0) == 0x80 && (c2 & 0xC0) == 0x80) {\n            u8 t = (u8)(((c0 & 0x07) << 2) | ((c1 & 0x30) >> 4));\n            return 0x01 <= t && t <= 0x10;\n        }\n    }\n    return false;\n}\n\n/**\n Check truncated string.\n Returns true if `cur` match `str` but is truncated.\n The `str` should be lowercase ASCII letters.\n */\nstatic bool is_truncated_str(u8 *cur, u8 *eof, const char *str,\n                             bool case_sensitive) {\n    usize len = strlen(str);\n    if (cur + len <= eof || eof <= cur) return false;\n    if (case_sensitive) {\n        return memcmp(cur, str, (usize)(eof - cur)) == 0;\n    }\n    for (; cur < eof; cur++, str++) {\n        if (char_to_lower(*cur) != *(const u8 *)str) return false;\n    }\n    return true;\n}\n\n/**\n Check truncated JSON on parsing errors.\n Returns true if the input is valid but truncated.\n */\nstatic_noinline bool is_truncated_end(u8 *hdr, u8 *cur, u8 *eof,\n                                      yyjson_read_code code,\n                                      yyjson_read_flag flg) {\n    if (cur >= eof) return true;\n    if (code == YYJSON_READ_ERROR_LITERAL) {\n        if (is_truncated_str(cur, eof, \"true\", true) ||\n            is_truncated_str(cur, eof, \"false\", true) ||\n            is_truncated_str(cur, eof, \"null\", true)) {\n            return true;\n        }\n    }\n    if (code == YYJSON_READ_ERROR_UNEXPECTED_CHARACTER ||\n        code == YYJSON_READ_ERROR_INVALID_NUMBER ||\n        code == YYJSON_READ_ERROR_LITERAL) {\n        if (has_allow(INF_AND_NAN)) {\n            if (*cur == '-') cur++;\n            if (is_truncated_str(cur, eof, \"infinity\", false) ||\n                is_truncated_str(cur, eof, \"nan\", false)) {\n                return true;\n            }\n        }\n    }\n    if (code == YYJSON_READ_ERROR_UNEXPECTED_CONTENT) {\n        if (has_allow(INF_AND_NAN)) {\n            if (hdr + 3 <= cur &&\n                is_truncated_str(cur - 3, eof, \"infinity\", false)) {\n                return true; /* e.g. infin would be read as inf + in */\n            }\n        }\n    }\n    if (code == YYJSON_READ_ERROR_INVALID_STRING) {\n        usize len = (usize)(eof - cur);\n\n        /* unicode escape sequence */\n        if (*cur == '\\\\') {\n            if (len == 1) return true;\n            if (len <= 5) {\n                if (*++cur != 'u') return false;\n                for (++cur; cur < eof; cur++) {\n                    if (!char_is_hex(*cur)) return false;\n                }\n                return true;\n            } else if (len <= 11) {\n                /* incomplete surrogate pair? */\n                u16 hi;\n                if (*++cur != 'u') return false;\n                if (!hex_load_4(++cur, &hi)) return false;\n                if ((hi & 0xF800) != 0xD800) return false;\n                cur += 4;\n                if (cur >= eof) return true;\n                /* valid low surrogate is DC00...DFFF */\n                if (*cur != '\\\\') return false;\n                if (++cur >= eof) return true;\n                if (*cur != 'u') return false;\n                if (++cur >= eof) return true;\n                if (*cur != 'd' && *cur != 'D') return false;\n                if (++cur >= eof) return true;\n                if ((*cur < 'c' || *cur > 'f') && (*cur < 'C' || *cur > 'F'))\n                    return false;\n                if (++cur >= eof) return true;\n                if (!char_is_hex(*cur)) return false;\n                return true;\n            }\n            return false;\n        }\n\n        /* 2 to 4 bytes UTF-8 */\n        if (is_truncated_utf8(cur, eof)) {\n            return true;\n        }\n    }\n    if (has_allow(COMMENTS)) {\n        if (code == YYJSON_READ_ERROR_INVALID_COMMENT) {\n            /* unclosed multiline comment */\n            return true;\n        }\n        if (code == YYJSON_READ_ERROR_UNEXPECTED_CHARACTER &&\n            *cur == '/' && cur + 1 == eof) {\n            /* truncated beginning of comment */\n            return true;\n        }\n    }\n    if (code == YYJSON_READ_ERROR_UNEXPECTED_CHARACTER &&\n        has_allow(BOM)) {\n        /* truncated UTF-8 BOM */\n        usize len = (usize)(eof - cur);\n        if (cur == hdr && len < 3 && !memcmp(hdr, \"\\xEF\\xBB\\xBF\", len)) {\n            return true;\n        }\n    }\n    return false;\n}\n\n\n\n#if !YYJSON_DISABLE_FAST_FP_CONV /* FP_READER */\n\n/*==============================================================================\n * MARK: - BigInt For Floating Point Number Reader (Private)\n *\n * The bigint algorithm is used by floating-point number reader to get correctly\n * rounded result for numbers with lots of digits. This part of code is rarely\n * used for common numbers.\n *============================================================================*/\n\n/** Unsigned arbitrarily large integer */\ntypedef struct bigint {\n    u32 used; /* used chunks count, should not be 0 */\n    u64 bits[64]; /* chunks (58 is enough here) */\n} bigint;\n\n/**\n Evaluate 'big += val'.\n @param big A big number (can be 0).\n @param val An unsigned integer (can be 0).\n */\nstatic_inline void bigint_add_u64(bigint *big, u64 val) {\n    u32 idx, max;\n    u64 num = big->bits[0];\n    u64 add = num + val;\n    big->bits[0] = add;\n    if (likely((add >= num) || (add >= val))) return;\n    for ((void)(idx = 1), max = big->used; idx < max; idx++) {\n        if (likely(big->bits[idx] != U64_MAX)) {\n            big->bits[idx] += 1;\n            return;\n        }\n        big->bits[idx] = 0;\n    }\n    big->bits[big->used++] = 1;\n}\n\n/**\n Evaluate 'big *= val'.\n @param big A big number (can be 0).\n @param val An unsigned integer (cannot be 0).\n */\nstatic_inline void bigint_mul_u64(bigint *big, u64 val) {\n    u32 idx = 0, max = big->used;\n    u64 hi, lo, carry = 0;\n    for (; idx < max; idx++) {\n        if (big->bits[idx]) break;\n    }\n    for (; idx < max; idx++) {\n        u128_mul_add(big->bits[idx], val, carry, &hi, &lo);\n        big->bits[idx] = lo;\n        carry = hi;\n    }\n    if (carry) big->bits[big->used++] = carry;\n}\n\n/**\n Evaluate 'big *= 2^exp'.\n @param big A big number (can be 0).\n @param exp An exponent integer (can be 0).\n */\nstatic_inline void bigint_mul_pow2(bigint *big, u32 exp) {\n    u32 shft = exp % 64;\n    u32 move = exp / 64;\n    u32 idx = big->used;\n    if (unlikely(shft == 0)) {\n        for (; idx > 0; idx--) {\n            big->bits[idx + move - 1] = big->bits[idx - 1];\n        }\n        big->used += move;\n        while (move) big->bits[--move] = 0;\n    } else {\n        big->bits[idx] = 0;\n        for (; idx > 0; idx--) {\n            u64 num = big->bits[idx] << shft;\n            num |= big->bits[idx - 1] >> (64 - shft);\n            big->bits[idx + move] = num;\n        }\n        big->bits[move] = big->bits[0] << shft;\n        big->used += move + (big->bits[big->used + move] > 0);\n        while (move) big->bits[--move] = 0;\n    }\n}\n\n/**\n Evaluate 'big *= 10^exp'.\n @param big A big number (can be 0).\n @param exp An exponent integer (cannot be 0).\n */\nstatic_inline void bigint_mul_pow10(bigint *big, i32 exp) {\n    for (; exp >= U64_POW10_MAX_EXACT_EXP; exp -= U64_POW10_MAX_EXACT_EXP) {\n        bigint_mul_u64(big, u64_pow10_table[U64_POW10_MAX_EXACT_EXP]);\n    }\n    if (exp) {\n        bigint_mul_u64(big, u64_pow10_table[exp]);\n    }\n}\n\n/**\n Compare two bigint.\n @return -1 if 'a < b', +1 if 'a > b', 0 if 'a == b'.\n */\nstatic_inline i32 bigint_cmp(bigint *a, bigint *b) {\n    u32 idx = a->used;\n    if (a->used < b->used) return -1;\n    if (a->used > b->used) return +1;\n    while (idx-- > 0) {\n        u64 av = a->bits[idx];\n        u64 bv = b->bits[idx];\n        if (av < bv) return -1;\n        if (av > bv) return +1;\n    }\n    return 0;\n}\n\n/**\n Evaluate 'big = val'.\n @param big A big number (can be 0).\n @param val An unsigned integer (can be 0).\n */\nstatic_inline void bigint_set_u64(bigint *big, u64 val) {\n    big->used = 1;\n    big->bits[0] = val;\n}\n\n/** Set a bigint with floating point number string. */\nstatic_noinline void bigint_set_buf(bigint *big, u64 sig, i32 *exp,\n                                    u8 *sig_cut, u8 *sig_end, u8 *dot_pos) {\n\n    if (unlikely(!sig_cut)) {\n        /* no digit cut, set significant part only */\n        bigint_set_u64(big, sig);\n        return;\n\n    } else {\n        /* some digits were cut, read them from 'sig_cut' to 'sig_end' */\n        u8 *hdr = sig_cut;\n        u8 *cur = hdr;\n        u32 len = 0;\n        u64 val = 0;\n        bool dig_big_cut = false;\n        bool has_dot = (hdr < dot_pos) & (dot_pos < sig_end);\n        u32 dig_len_total = U64_SAFE_DIG + (u32)(sig_end - hdr) - has_dot;\n\n        sig -= (*sig_cut >= '5'); /* sig was rounded before */\n        if (dig_len_total > F64_MAX_DEC_DIG) {\n            dig_big_cut = true;\n            sig_end -= dig_len_total - (F64_MAX_DEC_DIG + 1);\n            sig_end -= (dot_pos + 1 == sig_end);\n            dig_len_total = (F64_MAX_DEC_DIG + 1);\n        }\n        *exp -= (i32)dig_len_total - U64_SAFE_DIG;\n\n        big->used = 1;\n        big->bits[0] = sig;\n        while (cur < sig_end) {\n            if (likely(cur != dot_pos)) {\n                val = val * 10 + (u8)(*cur++ - '0');\n                len++;\n                if (unlikely(cur == sig_end && dig_big_cut)) {\n                    /* The last digit must be non-zero,    */\n                    /* set it to '1' for correct rounding. */\n                    val = val - (val % 10) + 1;\n                }\n                if (len == U64_SAFE_DIG || cur == sig_end) {\n                    bigint_mul_pow10(big, (i32)len);\n                    bigint_add_u64(big, val);\n                    val = 0;\n                    len = 0;\n                }\n            } else {\n                cur++;\n            }\n        }\n    }\n}\n\n\n\n/*==============================================================================\n * MARK: - Diy Floating Point (Private)\n *============================================================================*/\n\n/** \"Do It Yourself Floating Point\" struct. */\ntypedef struct diy_fp {\n    u64 sig; /* significand */\n    i32 exp; /* exponent, base 2 */\n    i32 pad; /* padding, useless */\n} diy_fp;\n\n/** Get cached rounded diy_fp with pow(10, e) The input value must in range\n    [POW10_SIG_TABLE_MIN_EXP, POW10_SIG_TABLE_MAX_EXP]. */\nstatic_inline diy_fp diy_fp_get_cached_pow10(i32 exp10) {\n    diy_fp fp;\n    u64 sig_ext;\n    pow10_table_get_sig(exp10, &fp.sig, &sig_ext);\n    pow10_table_get_exp(exp10, &fp.exp);\n    fp.sig += (sig_ext >> 63);\n    return fp;\n}\n\n/** Returns fp * fp2. */\nstatic_inline diy_fp diy_fp_mul(diy_fp fp, diy_fp fp2) {\n    u64 hi, lo;\n    u128_mul(fp.sig, fp2.sig, &hi, &lo);\n    fp.sig = hi + (lo >> 63);\n    fp.exp += fp2.exp + 64;\n    return fp;\n}\n\n/** Convert diy_fp to IEEE-754 raw value. */\nstatic_inline u64 diy_fp_to_ieee_raw(diy_fp fp) {\n    u64 sig = fp.sig;\n    i32 exp = fp.exp;\n    u32 lz_bits;\n    if (unlikely(fp.sig == 0)) return 0;\n\n    lz_bits = u64_lz_bits(sig);\n    sig <<= lz_bits;\n    sig >>= F64_BITS - F64_SIG_FULL_BITS;\n    exp -= (i32)lz_bits;\n    exp += F64_BITS - F64_SIG_FULL_BITS;\n    exp += F64_SIG_BITS;\n\n    if (unlikely(exp >= F64_MAX_BIN_EXP)) {\n        /* overflow */\n        return F64_BITS_INF;\n    } else if (likely(exp >= F64_MIN_BIN_EXP - 1)) {\n        /* normal */\n        exp += F64_EXP_BIAS;\n        return ((u64)exp << F64_SIG_BITS) | (sig & F64_SIG_MASK);\n    } else if (likely(exp >= F64_MIN_BIN_EXP - F64_SIG_FULL_BITS)) {\n        /* subnormal */\n        return sig >> (F64_MIN_BIN_EXP - exp - 1);\n    } else {\n        /* underflow */\n        return 0;\n    }\n}\n\n\n\n/*==============================================================================\n * MARK: - Number Reader (Private)\n *============================================================================*/\n\n/**\n Read a JSON number.\n\n 1. This function assume that the floating-point number is in IEEE-754 format.\n 2. This function support uint64/int64/double number. If an integer number\n    cannot fit in uint64/int64, it will returns as a double number. If a double\n    number is infinite, the return value is based on flag.\n 3. This function (with inline attribute) may generate a lot of instructions.\n */\nstatic_inline bool read_num(u8 **ptr, u8 **pre, yyjson_read_flag flg,\n                            yyjson_val *val, const char **msg) {\n#define return_err(_pos, _msg) do { \\\n    *msg = _msg; \\\n    *end = _pos; \\\n    return false; \\\n} while (false)\n\n#define return_0() do { \\\n    val->tag = YYJSON_TYPE_NUM | (u8)((u8)sign << 3); \\\n    val->uni.u64 = 0; \\\n    *end = cur; return true; \\\n} while (false)\n\n#define return_i64(_v) do { \\\n    val->tag = YYJSON_TYPE_NUM | (u8)((u8)sign << 3); \\\n    val->uni.u64 = (u64)(sign ? (u64)(~(_v) + 1) : (u64)(_v)); \\\n    *end = cur; return true; \\\n} while (false)\n\n#define return_f64(_v) do { \\\n    val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; \\\n    val->uni.f64 = sign ? -(f64)(_v) : (f64)(_v); \\\n    *end = cur; return true; \\\n} while (false)\n\n#define return_f64_bin(_v) do { \\\n    val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; \\\n    val->uni.u64 = ((u64)sign << 63) | (u64)(_v); \\\n    *end = cur; return true; \\\n} while (false)\n\n#define return_inf() do { \\\n    if (has_flg(BIGNUM_AS_RAW)) return_raw(); \\\n    if (has_allow(INF_AND_NAN)) return_f64_bin(F64_BITS_INF); \\\n    else return_err(hdr, \"number is infinity when parsed as double\"); \\\n} while (false)\n\n#define return_raw() do { \\\n    **pre = '\\0'; /* add null-terminator for previous raw string */ \\\n    val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; \\\n    val->uni.str = (const char *)hdr; \\\n    *pre = cur; *end = cur; return true; \\\n} while (false)\n\n    u8 *sig_cut = NULL; /* significant part cutting position for long number */\n    u8 *sig_end = NULL; /* significant part ending position */\n    u8 *dot_pos = NULL; /* decimal point position */\n\n    u64 sig = 0; /* significant part of the number */\n    i32 exp = 0; /* exponent part of the number */\n\n    bool exp_sign; /* temporary exponent sign from literal part */\n    i64 exp_sig = 0; /* temporary exponent number from significant part */\n    i64 exp_lit = 0; /* temporary exponent number from exponent literal part */\n    u64 num; /* temporary number for reading */\n    u8 *tmp; /* temporary cursor for reading */\n\n    u8 *hdr = *ptr;\n    u8 *cur = *ptr;\n    u8 **end = ptr;\n    bool sign;\n\n    /* read number as raw string if has `YYJSON_READ_NUMBER_AS_RAW` flag */\n    if (has_flg(NUMBER_AS_RAW)) {\n        return read_num_raw(ptr, pre, flg, val, msg);\n    }\n\n    sign = (*hdr == '-');\n    cur += sign;\n\n    /* begin with a leading zero or non-digit */\n    while (unlikely(!char_is_nonzero(*cur))) { /* 0 or non-digit char */\n        if (unlikely(*cur != '0')) { /* non-digit char */\n            if (has_allow(EXT_NUMBER)) {\n                if (*cur == '+' && cur == hdr) { /* leading `+` sign */\n                    cur++;\n                    continue;\n                }\n                if (*cur == '.' && char_is_digit(cur[1])) { /* e.g. '.123' */\n                    goto leading_dot;\n                }\n            }\n            if (has_allow(INF_AND_NAN)) {\n                if (read_inf_or_nan(ptr, pre, flg, val)) return true;\n            }\n            return_err(cur, \"no digit after sign\");\n        }\n        /* begin with 0 */\n        if (likely(!char_is_digit_or_fp(*++cur))) {\n            if (has_allow(EXT_NUMBER) && char_to_lower(*cur) == 'x') { /* hex */\n                return read_num_hex(ptr, pre, flg, val, msg);\n            }\n            return_0();\n        }\n        if (likely(*cur == '.')) {\nleading_dot:\n            dot_pos = cur++;\n            if (unlikely(!char_is_digit(*cur))) {\n                if (has_allow(EXT_NUMBER)) {\n                    if (char_is_exp(*cur)) {\n                        goto digi_exp_more;\n                    } else {\n                        return_f64_bin(0);\n                    }\n                }\n                return_err(cur, \"no digit after decimal point\");\n            }\n            while (unlikely(*cur == '0')) cur++;\n            if (likely(char_is_digit(*cur))) {\n                /* first non-zero digit after decimal point */\n                sig = (u64)(*cur - '0'); /* read first digit */\n                cur--;\n                goto digi_frac_1; /* continue read fraction part */\n            }\n        }\n        if (unlikely(char_is_digit(*cur))) {\n            return_err(cur - 1, \"number with leading zero is not allowed\");\n        }\n        if (unlikely(char_is_exp(*cur))) { /* 0 with any exponent is still 0 */\n            cur += (usize)1 + char_is_sign(cur[1]);\n            if (unlikely(!char_is_digit(*cur))) {\n                return_err(cur, \"no digit after exponent sign\");\n            }\n            while (char_is_digit(*++cur));\n        }\n        return_f64_bin(0);\n    }\n\n    /* begin with non-zero digit */\n    sig = (u64)(*cur - '0');\n\n    /*\n     Read integral part, same as the following code.\n\n         for (int i = 1; i <= 18; i++) {\n            num = cur[i] - '0';\n            if (num <= 9) sig = num + sig * 10;\n            else goto digi_sepr_i;\n         }\n     */\n#define expr_intg(i) \\\n    if (likely((num = (u64)(cur[i] - (u8)'0')) <= 9)) sig = num + sig * 10; \\\n    else { goto digi_sepr_##i; }\n    repeat_in_1_18(expr_intg)\n#undef expr_intg\n\n\n    cur += 19; /* skip continuous 19 digits */\n    if (!char_is_digit_or_fp(*cur)) {\n        /* this number is an integer consisting of 19 digits */\n        if (sign && (sig > ((u64)1 << 63))) { /* overflow */\n            if (has_flg(BIGNUM_AS_RAW)) return_raw();\n            return_f64(unsafe_yyjson_u64_to_f64(sig));\n        }\n        return_i64(sig);\n    }\n    goto digi_intg_more; /* read more digits in integral part */\n\n\n    /* process first non-digit character */\n#define expr_sepr(i) \\\n    digi_sepr_##i: \\\n    if (likely(!char_is_fp(cur[i]))) { cur += i; return_i64(sig); } \\\n    dot_pos = cur + i; \\\n    if (likely(cur[i] == '.')) goto digi_frac_##i; \\\n    cur += i; sig_end = cur; goto digi_exp_more;\n    repeat_in_1_18(expr_sepr)\n#undef expr_sepr\n\n\n    /* read fraction part */\n#define expr_frac(i) \\\n    digi_frac_##i: \\\n    if (likely((num = (u64)(cur[i + 1] - (u8)'0')) <= 9)) \\\n        sig = num + sig * 10; \\\n    else { goto digi_stop_##i; }\n    repeat_in_1_18(expr_frac)\n#undef expr_frac\n\n    cur += 20; /* skip 19 digits and 1 decimal point */\n    if (!char_is_digit(*cur)) goto digi_frac_end; /* fraction part end */\n    goto digi_frac_more; /* read more digits in fraction part */\n\n\n    /* significant part end */\n#define expr_stop(i) \\\n    digi_stop_##i: \\\n    cur += i + 1; \\\n    goto digi_frac_end;\n    repeat_in_1_18(expr_stop)\n#undef expr_stop\n\n\n    /* read more digits in integral part */\ndigi_intg_more:\n    if (char_is_digit(*cur)) {\n        if (!char_is_digit_or_fp(cur[1])) {\n            /* this number is an integer consisting of 20 digits */\n            num = (u64)(*cur - '0');\n            if ((sig < (U64_MAX / 10)) ||\n                (sig == (U64_MAX / 10) && num <= (U64_MAX % 10))) {\n                sig = num + sig * 10;\n                cur++;\n                /* convert to double if overflow */\n                if (sign) {\n                    if (has_flg(BIGNUM_AS_RAW)) return_raw();\n                    return_f64(unsafe_yyjson_u64_to_f64(sig));\n                }\n                return_i64(sig);\n            }\n        }\n    }\n\n    if (char_is_exp(*cur)) {\n        dot_pos = cur;\n        goto digi_exp_more;\n    }\n\n    if (*cur == '.') {\n        dot_pos = cur++;\n        if (unlikely(!char_is_digit(*cur))) {\n            if (has_allow(EXT_NUMBER)) {\n                goto digi_frac_end;\n            }\n            return_err(cur, \"no digit after decimal point\");\n        }\n    }\n\n\n    /* read more digits in fraction part */\ndigi_frac_more:\n    sig_cut = cur; /* too large to fit in u64, excess digits need to be cut */\n    sig += (*cur >= '5'); /* round */\n    while (char_is_digit(*++cur));\n    if (!dot_pos) {\n        if (!char_is_fp(*cur) && has_flg(BIGNUM_AS_RAW)) {\n            return_raw(); /* it's a large integer */\n        }\n        dot_pos = cur;\n        if (*cur == '.') {\n            if (unlikely(!char_is_digit(*++cur))) {\n                if (!has_allow(EXT_NUMBER)) {\n                    return_err(cur, \"no digit after decimal point\");\n                }\n            }\n            while (char_is_digit(*cur)) cur++;\n        }\n    }\n    exp_sig = (i64)(dot_pos - sig_cut);\n    exp_sig += (dot_pos < sig_cut);\n\n    /* ignore trailing zeros */\n    tmp = cur - 1;\n    while ((*tmp == '0' || *tmp == '.') && tmp > hdr) tmp--;\n    if (tmp < sig_cut) {\n        sig_cut = NULL;\n    } else {\n        sig_end = cur;\n    }\n\n    if (char_is_exp(*cur)) goto digi_exp_more;\n    goto digi_exp_finish;\n\n\n    /* fraction part end */\ndigi_frac_end:\n    if (unlikely(dot_pos + 1 == cur)) {\n        if (!has_allow(EXT_NUMBER)) {\n            return_err(cur, \"no digit after decimal point\");\n        }\n    }\n    sig_end = cur;\n    exp_sig = -(i64)((u64)(cur - dot_pos) - 1);\n    if (likely(!char_is_exp(*cur))) {\n        if (unlikely(exp_sig < F64_MIN_DEC_EXP - 19)) {\n            return_f64_bin(0); /* underflow */\n        }\n        exp = (i32)exp_sig;\n        goto digi_finish;\n    } else {\n        goto digi_exp_more;\n    }\n\n\n    /* read exponent part */\ndigi_exp_more:\n    exp_sign = (*++cur == '-');\n    cur += char_is_sign(*cur);\n    if (unlikely(!char_is_digit(*cur))) {\n        return_err(cur, \"no digit after exponent sign\");\n    }\n    while (*cur == '0') cur++;\n\n    /* read exponent literal */\n    tmp = cur;\n    while (char_is_digit(*cur)) {\n        exp_lit = (i64)((u8)(*cur++ - '0') + (u64)exp_lit * 10);\n    }\n    if (unlikely(cur - tmp >= U64_SAFE_DIG)) {\n        if (exp_sign) {\n            return_f64_bin(0); /* underflow */\n        } else {\n            return_inf(); /* overflow */\n        }\n    }\n    exp_sig += exp_sign ? -exp_lit : exp_lit;\n\n\n    /* validate exponent value */\ndigi_exp_finish:\n    if (unlikely(exp_sig < F64_MIN_DEC_EXP - 19)) {\n        return_f64_bin(0); /* underflow */\n    }\n    if (unlikely(exp_sig > F64_MAX_DEC_EXP)) {\n        return_inf(); /* overflow */\n    }\n    exp = (i32)exp_sig;\n\n\n    /* all digit read finished */\ndigi_finish:\n\n    /*\n     Fast path 1:\n\n     1. The floating-point number calculation should be accurate, see the\n        comments of macro `YYJSON_DOUBLE_MATH_CORRECT`.\n     2. Correct rounding should be performed (fegetround() == FE_TONEAREST).\n     3. The input of floating point number calculation does not lose precision,\n        which means: 64 - leading_zero(input) - trailing_zero(input) < 53.\n\n     We don't check all available inputs here, because that would make the code\n     more complicated, and not friendly to branch predictor.\n     */\n#if YYJSON_DOUBLE_MATH_CORRECT\n    if (sig < ((u64)1 << 53) &&\n        exp >= -F64_POW10_MAX_EXACT_EXP &&\n        exp <= +F64_POW10_MAX_EXACT_EXP) {\n        f64 dbl = (f64)sig;\n        if (exp < 0) {\n            dbl /= f64_pow10_table[-exp];\n        } else {\n            dbl *= f64_pow10_table[+exp];\n        }\n        return_f64(dbl);\n    }\n#endif\n\n    /*\n     Fast path 2:\n\n     To keep it simple, we only accept normal number here,\n     let the slow path to handle subnormal and infinity number.\n     */\n    if (likely(!sig_cut &&\n               exp > -F64_MAX_DEC_EXP + 1 &&\n               exp < +F64_MAX_DEC_EXP - 20)) {\n        /*\n         The result value is exactly equal to (sig * 10^exp),\n         the exponent part (10^exp) can be converted to (sig2 * 2^exp2).\n\n         The sig2 can be an infinite length number, only the highest 128 bits\n         is cached in the pow10_sig_table.\n\n         Now we have these bits:\n         sig1 (normalized 64bit)        : aaaaaaaa\n         sig2 (higher 64bit)            : bbbbbbbb\n         sig2_ext (lower 64bit)         : cccccccc\n         sig2_cut (extra unknown bits)  : dddddddddddd....\n\n         And the calculation process is:\n         ----------------------------------------\n                 aaaaaaaa *\n                 bbbbbbbbccccccccdddddddddddd....\n         ----------------------------------------\n         abababababababab +\n                 acacacacacacacac +\n                         adadadadadadadadadad....\n         ----------------------------------------\n         [hi____][lo____] +\n                 [hi2___][lo2___] +\n                         [unknown___________....]\n         ----------------------------------------\n\n         The addition with carry may affect higher bits, but if there is a 0\n         in higher bits, the bits higher than 0 will not be affected.\n\n         `lo2` + `unknown` may get a carry bit and may affect `hi2`, the max\n         value of `hi2` is 0xFFFFFFFFFFFFFFFE, so `hi2` will not overflow.\n\n         `lo` + `hi2` may also get a carry bit and may affect `hi`, but only\n         the highest significant 53 bits of `hi` is needed. If there is a 0\n         in the lower bits of `hi`, then all the following bits can be dropped.\n\n         To convert the result to IEEE-754 double number, we need to perform\n         correct rounding:\n         1. if bit 54 is 0, round down,\n         2. if bit 54 is 1 and any bit beyond bit 54 is 1, round up,\n         3. if bit 54 is 1 and all bits beyond bit 54 are 0, round to even,\n            as the extra bits is unknown, this case will not be handled here.\n         */\n\n        u64 raw;\n        u64 sig1, sig2, sig2_ext, hi, lo, hi2, lo2, add, bits;\n        i32 exp2;\n        u32 lz;\n        bool exact = false, carry, round_up;\n\n        /* convert (10^exp) to (sig2 * 2^exp2) */\n        pow10_table_get_sig(exp, &sig2, &sig2_ext);\n        pow10_table_get_exp(exp, &exp2);\n\n        /* normalize and multiply */\n        lz = u64_lz_bits(sig);\n        sig1 = sig << lz;\n        exp2 -= (i32)lz;\n        u128_mul(sig1, sig2, &hi, &lo);\n\n        /*\n         The `hi` is in range [0x4000000000000000, 0xFFFFFFFFFFFFFFFE],\n         To get normalized value, `hi` should be shifted to the left by 0 or 1.\n\n         The highest significant 53 bits is used by IEEE-754 double number,\n         and the bit 54 is used to detect rounding direction.\n\n         The lowest (64 - 54 - 1) bits is used to check whether it contains 0.\n         */\n        bits = hi & (((u64)1 << (64 - 54 - 1)) - 1);\n        if (bits - 1 < (((u64)1 << (64 - 54 - 1)) - 2)) {\n            /*\n             (bits != 0 && bits != 0x1FF) => (bits - 1 < 0x1FF - 1)\n             The `bits` is not zero, so we don't need to check `round to even`\n             case. The `bits` contains bit `0`, so we can drop the extra bits\n             after `0`.\n             */\n            exact = true;\n\n        } else {\n            /*\n             (bits == 0 || bits == 0x1FF)\n             The `bits` is filled with all `0` or all `1`, so we need to check\n             lower bits with another 64-bit multiplication.\n             */\n            u128_mul(sig1, sig2_ext, &hi2, &lo2);\n\n            add = lo + hi2;\n            if (add + 1 > (u64)1) {\n                /*\n                 (add != 0 && add != U64_MAX) => (add + 1 > 1)\n                 The `add` is not zero, so we don't need to check `round to\n                 even` case. The `add` contains bit `0`, so we can drop the\n                 extra bits after `0`. The `hi` cannot be U64_MAX, so it will\n                 not overflow.\n                 */\n                carry = add < lo || add < hi2;\n                hi += carry;\n                exact = true;\n            }\n        }\n\n        if (exact) {\n            /* normalize */\n            lz = hi < ((u64)1 << 63);\n            hi <<= lz;\n            exp2 -= (i32)lz;\n            exp2 += 64;\n\n            /* test the bit 54 and get rounding direction */\n            round_up = (hi & ((u64)1 << (64 - 54))) > (u64)0;\n            hi += (round_up ? ((u64)1 << (64 - 54)) : (u64)0);\n\n            /* test overflow */\n            if (hi < ((u64)1 << (64 - 54))) {\n                hi = ((u64)1 << 63);\n                exp2 += 1;\n            }\n\n            /* This is a normal number, convert it to IEEE-754 format. */\n            hi >>= F64_BITS - F64_SIG_FULL_BITS;\n            exp2 += F64_BITS - F64_SIG_FULL_BITS + F64_SIG_BITS;\n            exp2 += F64_EXP_BIAS;\n            raw = ((u64)exp2 << F64_SIG_BITS) | (hi & F64_SIG_MASK);\n            return_f64_bin(raw);\n        }\n    }\n\n    /*\n     Slow path: read double number exactly with diyfp.\n     1. Use cached diyfp to get an approximation value.\n     2. Use bigcomp to check the approximation value if needed.\n\n     This algorithm refers to google's double-conversion project:\n     https://github.com/google/double-conversion\n     */\n    {\n        const i32 ERR_ULP_LOG = 3;\n        const i32 ERR_ULP = 1 << ERR_ULP_LOG;\n        const i32 ERR_CACHED_POW = ERR_ULP / 2;\n        const i32 ERR_MUL_FIXED = ERR_ULP / 2;\n        const i32 DIY_SIG_BITS = 64;\n        const i32 EXP_BIAS = F64_EXP_BIAS + F64_SIG_BITS;\n        const i32 EXP_SUBNORMAL = -EXP_BIAS + 1;\n\n        u64 fp_err;\n        u32 bits;\n        i32 order_of_magnitude;\n        i32 effective_significand_size;\n        i32 precision_digits_count;\n        u64 precision_bits;\n        u64 half_way;\n\n        u64 raw;\n        diy_fp fp, fp_upper;\n        bigint big_full, big_comp;\n        i32 cmp;\n\n        fp.sig = sig;\n        fp.exp = 0;\n        fp_err = sig_cut ? (u64)(ERR_ULP / 2) : (u64)0;\n\n        /* normalize */\n        bits = u64_lz_bits(fp.sig);\n        fp.sig <<= bits;\n        fp.exp -= (i32)bits;\n        fp_err <<= bits;\n\n        /* multiply and add error */\n        fp = diy_fp_mul(fp, diy_fp_get_cached_pow10(exp));\n        fp_err += (u64)ERR_CACHED_POW + (fp_err != 0) + (u64)ERR_MUL_FIXED;\n\n        /* normalize */\n        bits = u64_lz_bits(fp.sig);\n        fp.sig <<= bits;\n        fp.exp -= (i32)bits;\n        fp_err <<= bits;\n\n        /* effective significand */\n        order_of_magnitude = DIY_SIG_BITS + fp.exp;\n        if (likely(order_of_magnitude >= EXP_SUBNORMAL + F64_SIG_FULL_BITS)) {\n            effective_significand_size = F64_SIG_FULL_BITS;\n        } else if (order_of_magnitude <= EXP_SUBNORMAL) {\n            effective_significand_size = 0;\n        } else {\n            effective_significand_size = order_of_magnitude - EXP_SUBNORMAL;\n        }\n\n        /* precision digits count */\n        precision_digits_count = DIY_SIG_BITS - effective_significand_size;\n        if (unlikely(precision_digits_count + ERR_ULP_LOG >= DIY_SIG_BITS)) {\n            i32 shr = (precision_digits_count + ERR_ULP_LOG) - DIY_SIG_BITS + 1;\n            fp.sig >>= shr;\n            fp.exp += shr;\n            fp_err = (fp_err >> shr) + 1 + (u32)ERR_ULP;\n            precision_digits_count -= shr;\n        }\n\n        /* half way */\n        precision_bits = fp.sig & (((u64)1 << precision_digits_count) - 1);\n        precision_bits *= (u32)ERR_ULP;\n        half_way = (u64)1 << (precision_digits_count - 1);\n        half_way *= (u32)ERR_ULP;\n\n        /* rounding */\n        fp.sig >>= precision_digits_count;\n        fp.sig += (precision_bits >= half_way + fp_err);\n        fp.exp += precision_digits_count;\n\n        /* get IEEE double raw value */\n        raw = diy_fp_to_ieee_raw(fp);\n        if (unlikely(raw == F64_BITS_INF)) return_inf();\n        if (likely(precision_bits <= half_way - fp_err ||\n                   precision_bits >= half_way + fp_err)) {\n            return_f64_bin(raw); /* number is accurate */\n        }\n        /* now the number is the correct value, or the next lower value */\n\n        /* upper boundary */\n        if (raw & F64_EXP_MASK) {\n            fp_upper.sig = (raw & F64_SIG_MASK) + ((u64)1 << F64_SIG_BITS);\n            fp_upper.exp = (i32)((raw & F64_EXP_MASK) >> F64_SIG_BITS);\n        } else {\n            fp_upper.sig = (raw & F64_SIG_MASK);\n            fp_upper.exp = 1;\n        }\n        fp_upper.exp -= F64_EXP_BIAS + F64_SIG_BITS;\n        fp_upper.sig <<= 1;\n        fp_upper.exp -= 1;\n        fp_upper.sig += 1; /* add half ulp */\n\n        /* compare with bigint */\n        bigint_set_buf(&big_full, sig, &exp, sig_cut, sig_end, dot_pos);\n        bigint_set_u64(&big_comp, fp_upper.sig);\n        if (exp >= 0) {\n            bigint_mul_pow10(&big_full, +exp);\n        } else {\n            bigint_mul_pow10(&big_comp, -exp);\n        }\n        if (fp_upper.exp > 0) {\n            bigint_mul_pow2(&big_comp, (u32)+fp_upper.exp);\n        } else {\n            bigint_mul_pow2(&big_full, (u32)-fp_upper.exp);\n        }\n        cmp = bigint_cmp(&big_full, &big_comp);\n        if (likely(cmp != 0)) {\n            /* round down or round up */\n            raw += (cmp > 0);\n        } else {\n            /* falls midway, round to even */\n            raw += (raw & 1);\n        }\n\n        if (unlikely(raw == F64_BITS_INF)) return_inf();\n        return_f64_bin(raw);\n    }\n\n#undef return_err\n#undef return_inf\n#undef return_0\n#undef return_i64\n#undef return_f64\n#undef return_f64_bin\n#undef return_raw\n}\n\n\n\n#else /* FP_READER */\n\n/**\n Read a JSON number.\n This is a fallback function if the custom number reader is disabled.\n This function use libc's strtod() to read floating-point number.\n */\nstatic_inline bool read_num(u8 **ptr, u8 **pre, yyjson_read_flag flg,\n                            yyjson_val *val, const char **msg) {\n#define return_err(_pos, _msg) do { \\\n    *msg = _msg; \\\n    *end = _pos; \\\n    return false; \\\n} while (false)\n\n#define return_0() do { \\\n    val->tag = YYJSON_TYPE_NUM | (u64)((u8)sign << 3); \\\n    val->uni.u64 = 0; \\\n    *end = cur; return true; \\\n} while (false)\n\n#define return_i64(_v) do { \\\n    val->tag = YYJSON_TYPE_NUM | (u64)((u8)sign << 3); \\\n    val->uni.u64 = (u64)(sign ? (u64)(~(_v) + 1) : (u64)(_v)); \\\n    *end = cur; return true; \\\n} while (false)\n\n#define return_f64(_v) do { \\\n    val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; \\\n    val->uni.f64 = sign ? -(f64)(_v) : (f64)(_v); \\\n    *end = cur; return true; \\\n} while (false)\n\n#define return_f64_bin(_v) do { \\\n    val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; \\\n    val->uni.u64 = ((u64)sign << 63) | (u64)(_v); \\\n    *end = cur; return true; \\\n} while (false)\n\n#define return_inf() do { \\\n    if (has_flg(BIGNUM_AS_RAW)) return_raw(); \\\n    if (has_allow(INF_AND_NAN)) return_f64_bin(F64_BITS_INF); \\\n    else return_err(hdr, \"number is infinity when parsed as double\"); \\\n} while (false)\n\n#define return_raw() do { \\\n    val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; \\\n    val->uni.str = (const char *)hdr; \\\n    **pre = '\\0'; *pre = cur; *end = cur; return true; \\\n} while (false)\n\n    u64 sig, num;\n    u8 *hdr = *ptr;\n    u8 *cur = *ptr;\n    u8 **end = ptr;\n    u8 *dot = NULL;\n    u8 *f64_end = NULL;\n    bool sign;\n\n    /* read number as raw string if has `YYJSON_READ_NUMBER_AS_RAW` flag */\n    if (has_flg(NUMBER_AS_RAW)) {\n        return read_num_raw(ptr, pre, flg, val, msg);\n    }\n\n    sign = (*hdr == '-');\n    cur += sign;\n    sig = (u8)(*cur - '0');\n\n    /* read first digit, check leading zero */\n    while (unlikely(!char_is_digit(*cur))) {\n        if (has_allow(EXT_NUMBER)) {\n            if (*cur == '+' && cur == hdr) { /* leading `+` sign */\n                cur++;\n                sig = (u8)(*cur - '0');\n                continue;\n            }\n            if (*cur == '.' && char_is_num(cur[1])) { /* no integer part */\n                goto read_double; /* e.g. '.123' */\n            }\n        }\n        if (has_allow(INF_AND_NAN)) {\n            if (read_inf_or_nan(ptr, pre, flg, val)) return true;\n        }\n        return_err(cur, \"no digit after sign\");\n    }\n    if (*cur == '0') {\n        cur++;\n        if (unlikely(char_is_digit(*cur))) {\n            return_err(cur - 1, \"number with leading zero is not allowed\");\n        }\n        if (!char_is_fp(*cur)) {\n            if (has_allow(EXT_NUMBER) &&\n                (*cur == 'x' || *cur == 'X')) { /* hex integer */\n                return read_num_hex(ptr, pre, flg, val, msg);\n            }\n            return_0();\n        }\n        goto read_double;\n    }\n\n    /* read continuous digits, up to 19 characters */\n#define expr_intg(i) \\\n    if (likely((num = (u64)(cur[i] - (u8)'0')) <= 9)) sig = num + sig * 10; \\\n    else { cur += i; goto intg_end; }\n    repeat_in_1_18(expr_intg)\n#undef expr_intg\n\n    /* here are 19 continuous digits, skip them */\n    cur += 19;\n    if (char_is_digit(cur[0]) && !char_is_digit_or_fp(cur[1])) {\n        /* this number is an integer consisting of 20 digits */\n        num = (u8)(*cur - '0');\n        if ((sig < (U64_MAX / 10)) ||\n            (sig == (U64_MAX / 10) && num <= (U64_MAX % 10))) {\n            sig = num + sig * 10;\n            cur++;\n            if (sign) {\n                if (has_flg(BIGNUM_AS_RAW)) return_raw();\n                return_f64(unsafe_yyjson_u64_to_f64(sig));\n            }\n            return_i64(sig);\n        }\n    }\n\nintg_end:\n    /* continuous digits ended */\n    if (!char_is_digit_or_fp(*cur)) {\n        /* this number is an integer consisting of 1 to 19 digits */\n        if (sign && (sig > ((u64)1 << 63))) {\n            if (has_flg(BIGNUM_AS_RAW)) return_raw();\n            return_f64(unsafe_yyjson_u64_to_f64(sig));\n        }\n        return_i64(sig);\n    }\n\nread_double:\n    /* this number should be read as double */\n    while (char_is_digit(*cur)) cur++;\n    if (!char_is_fp(*cur) && has_flg(BIGNUM_AS_RAW)) {\n        return_raw(); /* it's a large integer */\n    }\n    while (*cur == '.') {\n        /* skip fraction part */\n        dot = cur;\n        cur++;\n        if (!char_is_digit(*cur)) {\n            if (has_allow(EXT_NUMBER)) {\n                break;\n            } else {\n                return_err(cur, \"no digit after decimal point\");\n            }\n        }\n        cur++;\n        while (char_is_digit(*cur)) cur++;\n        break;\n    }\n    if (char_is_exp(*cur)) {\n        /* skip exponent part */\n        cur += 1 + char_is_sign(cur[1]);\n        if (!char_is_digit(*cur)) {\n            return_err(cur, \"no digit after exponent sign\");\n        }\n        cur++;\n        while (char_is_digit(*cur)) cur++;\n    }\n\n    /*\n     libc's strtod() is used to parse the floating-point number.\n\n     Note that the decimal point character used by strtod() is locale-dependent,\n     and the rounding direction may affected by fesetround().\n\n     For currently known locales, (en, zh, ja, ko, am, he, hi) use '.' as the\n     decimal point, while other locales use ',' as the decimal point.\n\n     Here strtod() is called twice for different locales, but if another thread\n     happens calls setlocale() between two strtod(), parsing may still fail.\n     */\n    val->uni.f64 = strtod((const char *)hdr, (char **)&f64_end);\n    if (unlikely(f64_end != cur)) {\n        /* replace '.' with ',' for locale */\n        bool cut = (*cur == ',');\n        if (cut) *cur = ' ';\n        if (dot) *dot = ',';\n        val->uni.f64 = strtod((const char *)hdr, (char **)&f64_end);\n        /* restore ',' to '.' */\n        if (cut) *cur = ',';\n        if (dot) *dot = '.';\n        if (unlikely(f64_end != cur)) {\n            return_err(hdr, \"strtod() failed to parse the number\");\n        }\n    }\n    if (unlikely(val->uni.f64 >= HUGE_VAL || val->uni.f64 <= -HUGE_VAL)) {\n        return_inf();\n    }\n    val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL;\n    *end = cur;\n    return true;\n\n#undef return_err\n#undef return_0\n#undef return_i64\n#undef return_f64\n#undef return_f64_bin\n#undef return_inf\n#undef return_raw\n}\n\n#endif /* FP_READER */\n\n\n\n/*==============================================================================\n * MARK: - String Reader (Private)\n *============================================================================*/\n\n/** Read unicode escape sequence. */\nstatic_inline bool read_uni_esc(u8 **src_ptr, u8 **dst_ptr, const char **msg) {\n#define return_err(_end, _msg) *msg = _msg; *src_ptr = _end; return false\n\n    u8 *src = *src_ptr;\n    u8 *dst = *dst_ptr;\n    u16 hi, lo;\n    u32 uni;\n\n    src += 2; /* skip `\\u` */\n    if (unlikely(!hex_load_4(src, &hi))) {\n        return_err(src - 2, \"invalid escaped sequence in string\");\n    }\n    src += 4; /* skip hex */\n    if (likely((hi & 0xF800) != 0xD800)) {\n        /* a BMP character */\n        if (hi >= 0x800) {\n            *dst++ = (u8)(0xE0 | (hi >> 12));\n            *dst++ = (u8)(0x80 | ((hi >> 6) & 0x3F));\n            *dst++ = (u8)(0x80 | (hi & 0x3F));\n        } else if (hi >= 0x80) {\n            *dst++ = (u8)(0xC0 | (hi >> 6));\n            *dst++ = (u8)(0x80 | (hi & 0x3F));\n        } else {\n            *dst++ = (u8)hi;\n        }\n    } else {\n        /* a non-BMP character, represented as a surrogate pair */\n        if (unlikely((hi & 0xFC00) != 0xD800)) {\n            return_err(src - 6, \"invalid high surrogate in string\");\n        }\n        if (unlikely(!byte_match_2(src, \"\\\\u\"))) {\n            return_err(src - 6, \"no low surrogate in string\");\n        }\n        if (unlikely(!hex_load_4(src + 2, &lo))) {\n            return_err(src - 6, \"invalid escape in string\");\n        }\n        if (unlikely((lo & 0xFC00) != 0xDC00)) {\n            return_err(src - 6, \"invalid low surrogate in string\");\n        }\n        uni = ((((u32)hi - 0xD800) << 10) |\n                ((u32)lo - 0xDC00)) + 0x10000;\n        *dst++ = (u8)(0xF0 | (uni >> 18));\n        *dst++ = (u8)(0x80 | ((uni >> 12) & 0x3F));\n        *dst++ = (u8)(0x80 | ((uni >> 6) & 0x3F));\n        *dst++ = (u8)(0x80 | (uni & 0x3F));\n        src += 6;\n    }\n    *src_ptr = src;\n    *dst_ptr = dst;\n    return true;\n#undef return_err\n}\n\n/**\n Read a JSON string.\n @param quo The quote character (single quote or double quote).\n @param ptr The head pointer of string before quote (inout).\n @param eof JSON end position.\n @param flg JSON read flag.\n @param val The string value to be written.\n @param msg The error message pointer.\n @param con Continuation for incremental parsing.\n @return Whether success.\n */\nstatic_inline bool read_str_opt(u8 quo, u8 **ptr, u8 *eof, yyjson_read_flag flg,\n                                yyjson_val *val, const char **msg, u8 *con[2]) {\n    /*\n     GCC may sometimes load variables into registers too early, causing\n     unnecessary instructions and performance degradation. This inline assembly\n     serves as a hint to GCC: 'This variable will be modified, so avoid loading\n     it too early.' Other compilers like MSVC, Clang, and ICC can generate the\n     expected instructions without needing this hint.\n\n     Check out this example: https://godbolt.org/z/YG6a5W5Ec\n     */\n#define return_err(_end, _msg) do { \\\n    *msg = _msg; \\\n    *end = _end; \\\n    if (con) { con[0] = _end; con[1] = dst; } \\\n    return false; \\\n} while (false)\n\n    u8 *hdr = *ptr + 1;\n    u8 **end = ptr;\n    u8 *src = hdr, *dst = NULL, *pos;\n    u16 hi, lo;\n    u32 uni, tmp;\n\n    /* Resume incremental parsing. */\n    if (con && unlikely(con[0])) {\n        src = con[0];\n        dst = con[1];\n        if (dst) goto copy_ascii;\n    }\n\nskip_ascii:\n    /*\n     Most strings have no escaped characters, so we can jump them quickly.\n\n     We want to make loop unrolling, as shown in the following code. Some\n     compiler may not generate instructions as expected, so we rewrite it with\n     explicit goto statements. We hope the compiler can generate instructions\n     like this: https://godbolt.org/z/8vjsYq\n\n     while (true) repeat16({\n        if (likely((char_is_ascii_skip(*src)))) src++;\n        else break;\n     })\n     */\n    if (quo == '\"') {\n#define expr_jump(i) \\\n    if (likely(char_is_ascii_skip(src[i]))) {} \\\n    else goto skip_ascii_stop##i;\n\n#define expr_stop(i) \\\n    skip_ascii_stop##i: \\\n    src += i; \\\n    goto skip_ascii_end;\n\n    repeat16_incr(expr_jump)\n    src += 16;\n    goto skip_ascii;\n    repeat16_incr(expr_stop)\n\n#undef expr_jump\n#undef expr_stop\n    } else {\n#define expr_jump(i) \\\n    if (likely(char_is_ascii_skip_sq(src[i]))) {} \\\n    else goto skip_ascii_stop_sq##i;\n\n#define expr_stop(i) \\\n    skip_ascii_stop_sq##i: \\\n    src += i; \\\n    goto skip_ascii_end;\n\n    repeat16_incr(expr_jump)\n    src += 16;\n    goto skip_ascii;\n    repeat16_incr(expr_stop)\n\n#undef expr_jump\n#undef expr_stop\n    }\n\nskip_ascii_end:\n    gcc_store_barrier(*src);\n    if (likely(*src == quo)) {\n        val->tag = ((u64)(src - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_STR |\n                        (quo == '\"' ? YYJSON_SUBTYPE_NOESC : 0);\n        val->uni.str = (const char *)hdr;\n        *src = '\\0';\n        *end = src + 1;\n        if (con) con[0] = con[1] = NULL;\n        return true;\n    }\n\nskip_utf8:\n    if (*src & 0x80) { /* non-ASCII character */\n        /*\n         Non-ASCII character appears here, which means that the text is likely\n         to be written in non-English or emoticons. According to some common\n         data set statistics, byte sequences of the same length may appear\n         consecutively. We process the byte sequences of the same length in each\n         loop, which is more friendly to branch prediction.\n         */\n        pos = src;\n#if YYJSON_DISABLE_UTF8_VALIDATION\n        while (true) repeat8({\n            if (likely((*src & 0xF0) == 0xE0)) src += 3;\n            else break;\n        })\n        if (*src < 0x80) goto skip_ascii;\n        while (true) repeat8({\n            if (likely((*src & 0xE0) == 0xC0)) src += 2;\n            else break;\n        })\n        while (true) repeat8({\n            if (likely((*src & 0xF8) == 0xF0)) src += 4;\n            else break;\n        })\n#else\n        uni = byte_load_4(src);\n        while (is_utf8_seq3(uni)) {\n            src += 3;\n            uni = byte_load_4(src);\n        }\n        if (is_utf8_seq1(uni)) goto skip_ascii;\n        while (is_utf8_seq2(uni)) {\n            src += 2;\n            uni = byte_load_4(src);\n        }\n        while (is_utf8_seq4(uni)) {\n            src += 4;\n            uni = byte_load_4(src);\n        }\n#endif\n        if (unlikely(pos == src)) {\n            if (has_allow(INVALID_UNICODE)) ++src;\n            else return_err(src, \"invalid UTF-8 encoding in string\");\n        }\n        goto skip_ascii;\n    }\n\n    /* The escape character appears, we need to copy it. */\n    dst = src;\ncopy_escape:\n    if (likely(*src == '\\\\')) {\n        switch (*++src) {\n            case '\"':  *dst++ = '\"';  src++; break;\n            case '\\\\': *dst++ = '\\\\'; src++; break;\n            case '/':  *dst++ = '/';  src++; break;\n            case 'b':  *dst++ = '\\b'; src++; break;\n            case 'f':  *dst++ = '\\f'; src++; break;\n            case 'n':  *dst++ = '\\n'; src++; break;\n            case 'r':  *dst++ = '\\r'; src++; break;\n            case 't':  *dst++ = '\\t'; src++; break;\n            case 'u':\n                src--;\n                if (!read_uni_esc(&src, &dst, msg)) return_err(src, *msg);\n                break;\n            default: {\n                if (has_allow(EXT_ESCAPE)) {\n                    /* read extended escape (non-standard) */\n                    switch (*src) {\n                        case '\\'': *dst++ = '\\''; src++; break;\n                        case 'a':  *dst++ = '\\a'; src++; break;\n                        case 'v':  *dst++ = '\\v'; src++; break;\n                        case '?':  *dst++ = '\\?'; src++; break;\n                        case 'e':  *dst++ = 0x1B; src++; break;\n                        case '0':\n                            if (!char_is_digit(src[1])) {\n                                *dst++ = '\\0'; src++; break;\n                            }\n                            return_err(src - 1, \"octal escape is not allowed\");\n                        case '1': case '2': case '3': case '4':\n                        case '5': case '6': case '7': case '8': case '9':\n                            return_err(src - 1, \"invalid number escape\");\n                        case 'x': {\n                            u8 c;\n                            if (hex_load_2(src + 1, &c)) {\n                                src += 3;\n                                if (c <= 0x7F) { /* 1-byte ASCII */\n                                    *dst++ = c;\n                                } else { /* 2-byte UTF-8 */\n                                    *dst++ = (u8)(0xC0 | (c >> 6));\n                                    *dst++ = (u8)(0x80 | (c & 0x3F));\n                                }\n                                break;\n                            }\n                            return_err(src - 1, \"invalid hex escape\");\n                        }\n                        case '\\n': src++; break;\n                        case '\\r': src++; src += (*src == '\\n'); break;\n                        case 0xE2: /* Line terminator: U+2028, U+2029 */\n                            if ((src[1] == 0x80 && src[2] == 0xA8) ||\n                                (src[1] == 0x80 && src[2] == 0xA9)) {\n                                src += 3;\n                            }\n                            break;\n                        default:\n                            break; /* skip */\n                    }\n                } else if (quo == '\\'' && *src == '\\'') {\n                    *dst++ = '\\''; src++; break;\n                } else {\n                    return_err(src - 1, \"invalid escaped sequence in string\");\n                }\n            }\n        }\n    } else if (likely(*src == quo)) {\n        val->tag = ((u64)(dst - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_STR;\n        val->uni.str = (const char *)hdr;\n        *dst = '\\0';\n        *end = src + 1;\n        if (con) con[0] = con[1] = NULL;\n        return true;\n    } else {\n        if (!has_allow(INVALID_UNICODE)) {\n            return_err(src, \"unexpected control character in string\");\n        }\n        if (src >= eof) return_err(src, \"unclosed string\");\n        *dst++ = *src++;\n    }\n\ncopy_ascii:\n    /*\n     Copy continuous ASCII, loop unrolling, same as the following code:\n\n     while (true) repeat16({\n        if (char_is_ascii_skip(*src)) *dst++ = *src++;\n        else break;\n     })\n     */\n    if (quo == '\"') {\n#define expr_jump(i) \\\n    if (likely((char_is_ascii_skip(src[i])))) {} \\\n    else { gcc_store_barrier(src[i]); goto copy_ascii_stop_##i; }\n    repeat16_incr(expr_jump)\n#undef expr_jump\n    } else {\n#define expr_jump(i) \\\n    if (likely((char_is_ascii_skip_sq(src[i])))) {} \\\n    else { gcc_store_barrier(src[i]); goto copy_ascii_stop_##i; }\n    repeat16_incr(expr_jump)\n#undef expr_jump\n    }\n\n    byte_move_16(dst, src);\n    dst += 16; src += 16;\n    goto copy_ascii;\n\n    /*\n     The memory is copied forward since `dst < src`.\n     So it's safe to move one extra byte to reduce instruction count.\n     */\n#define expr_jump(i) \\\n    copy_ascii_stop_##i: \\\n    byte_move_forward(dst, src, i); \\\n    dst += i; src += i; \\\n    goto copy_utf8;\n    repeat16_incr(expr_jump)\n#undef expr_jump\n\ncopy_utf8:\n    if (*src & 0x80) { /* non-ASCII character */\n        pos = src;\n        uni = byte_load_4(src);\n#if YYJSON_DISABLE_UTF8_VALIDATION\n        while (true) repeat4({\n            if ((uni & utf8_seq(b3_mask)) == utf8_seq(b3_patt)) {\n                byte_copy_4(dst, &uni);\n                dst += 3; src += 3;\n                uni = byte_load_4(src);\n            } else break;\n        })\n        if ((uni & utf8_seq(b1_mask)) == utf8_seq(b1_patt)) goto copy_ascii;\n        while (true) repeat4({\n            if ((uni & utf8_seq(b2_mask)) == utf8_seq(b2_patt)) {\n                byte_copy_2(dst, &uni);\n                dst += 2; src += 2;\n                uni = byte_load_4(src);\n            } else break;\n        })\n        while (true) repeat4({\n            if ((uni & utf8_seq(b4_mask)) == utf8_seq(b4_patt)) {\n                byte_copy_4(dst, &uni);\n                dst += 4; src += 4;\n                uni = byte_load_4(src);\n            } else break;\n        })\n#else\n        while (is_utf8_seq3(uni)) {\n            byte_copy_4(dst, &uni);\n            dst += 3; src += 3;\n            uni = byte_load_4(src);\n        }\n        if (is_utf8_seq1(uni)) goto copy_ascii;\n        while (is_utf8_seq2(uni)) {\n            byte_copy_2(dst, &uni);\n            dst += 2; src += 2;\n            uni = byte_load_4(src);\n        }\n        while (is_utf8_seq4(uni)) {\n            byte_copy_4(dst, &uni);\n            dst += 4; src += 4;\n            uni = byte_load_4(src);\n        }\n#endif\n        if (unlikely(pos == src)) {\n            if (!has_allow(INVALID_UNICODE)) {\n                return_err(src, MSG_ERR_UTF8);\n            }\n            goto copy_ascii_stop_1;\n        }\n        goto copy_ascii;\n    }\n    goto copy_escape;\n\n#undef return_err\n}\n\nstatic_inline bool read_str(u8 **ptr, u8 *eof, yyjson_read_flag flg,\n                            yyjson_val *val, const char **msg) {\n    return read_str_opt('\\\"', ptr, eof, flg, val, msg, NULL);\n}\n\nstatic_inline bool read_str_con(u8 **ptr, u8 *eof, yyjson_read_flag flg,\n                                yyjson_val *val, const char **msg, u8 **con) {\n    return read_str_opt('\\\"', ptr, eof, flg, val, msg, con);\n}\n\nstatic_noinline bool read_str_sq(u8 **ptr, u8 *eof, yyjson_read_flag flg,\n                                 yyjson_val *val, const char **msg) {\n    return read_str_opt('\\'', ptr, eof, flg, val, msg, NULL);\n}\n\n/** Read unquoted key (identifier name). */\nstatic_noinline bool read_str_id(u8 **ptr, u8 *eof, yyjson_read_flag flg,\n                                 u8 **pre, yyjson_val *val, const char **msg) {\n#define return_err(_end, _msg) do { \\\n    *msg = _msg; \\\n    *end = _end; \\\n    return false; \\\n} while (false)\n\n#define return_suc(_str_end, _cur_end) do { \\\n    val->tag = ((u64)(_str_end - hdr) << YYJSON_TAG_BIT) | \\\n                (u64)(YYJSON_TYPE_STR); \\\n    val->uni.str = (const char *)hdr; \\\n    *pre = _str_end; *end = _cur_end; \\\n    return true; \\\n} while (false)\n\n    u8 *hdr = *ptr;\n    u8 **end = ptr;\n    u8 *src = hdr, *dst = NULL;\n    u16 hi, lo;\n    u32 uni, tmp;\n\n    /* add null-terminator for previous raw string */\n    **pre = '\\0';\n\nskip_ascii:\n#define expr_jump(i) \\\n    if (likely(char_is_id_ascii(src[i]))) {} \\\n    else goto skip_ascii_stop##i;\n\n#define expr_stop(i) \\\n    skip_ascii_stop##i: \\\n    src += i; \\\n    goto skip_ascii_end;\n\n    repeat16_incr(expr_jump)\n    src += 16;\n    goto skip_ascii;\n    repeat16_incr(expr_stop)\n\n#undef expr_jump\n#undef expr_stop\n\nskip_ascii_end:\n    gcc_store_barrier(*src);\n    if (likely(!char_is_id_next(*src))) {\n        return_suc(src, src);\n    }\n\nskip_utf8:\n    while (*src >= 0x80) {\n        if (has_allow(EXT_WHITESPACE)) {\n            if (char_is_space_ext(*src) && ext_space_len(src)) {\n                return_suc(src, src);\n            }\n        }\n        uni = byte_load_4(src);\n        if (is_utf8_seq2(uni)) {\n            src += 2;\n        } else if (is_utf8_seq3(uni)) {\n            src += 3;\n        } else if (is_utf8_seq4(uni)) {\n            src += 4;\n        } else {\n#if !YYJSON_DISABLE_UTF8_VALIDATION\n            if (!has_allow(INVALID_UNICODE)) return_err(src, MSG_ERR_UTF8);\n#endif\n            src += 1;\n        }\n    }\n    if (char_is_id_ascii(*src)) goto skip_ascii;\n\n    /* The escape character appears, we need to copy it. */\n    dst = src;\ncopy_escape:\n    if (byte_match_2(src, \"\\\\u\")) {\n        if (!read_uni_esc(&src, &dst, msg)) return_err(src, *msg);\n    } else {\n        if (!char_is_id_next(*src)) return_suc(dst, src);\n        return_err(src, \"unexpected character in key\");\n    }\n\ncopy_ascii:\n    /*\n     Copy continuous ASCII, loop unrolling, same as the following code:\n\n     while (true) repeat16({\n        if (char_is_ascii_skip(*src)) *dst++ = *src++;\n        else break;\n     })\n     */\n#define expr_jump(i) \\\n    if (likely((char_is_id_ascii(src[i])))) {} \\\n    else { gcc_store_barrier(src[i]); goto copy_ascii_stop_##i; }\n    repeat16_incr(expr_jump)\n#undef expr_jump\n\n    byte_move_16(dst, src);\n    dst += 16; src += 16;\n    goto copy_ascii;\n\n#define expr_jump(i) \\\n    copy_ascii_stop_##i: \\\n    byte_move_forward(dst, src, i); \\\n    dst += i; src += i; \\\n    goto copy_utf8;\n    repeat16_incr(expr_jump)\n#undef expr_jump\n\ncopy_utf8:\n    while (*src >= 0x80) { /* non-ASCII character */\n        if (has_allow(EXT_WHITESPACE)) {\n            if (char_is_space_ext(*src) && ext_space_len(src)) {\n                return_suc(dst, src);\n            }\n        }\n        uni = byte_load_4(src);\n        if (is_utf8_seq2(uni)) {\n            byte_copy_2(dst, &uni);\n            dst += 2; src += 2;\n        } else if (is_utf8_seq3(uni)) {\n            byte_copy_4(dst, &uni);\n            dst += 3; src += 3;\n        } else if (is_utf8_seq4(uni)) {\n            byte_copy_4(dst, &uni);\n            dst += 4; src += 4;\n        } else {\n#if !YYJSON_DISABLE_UTF8_VALIDATION\n            if (!has_allow(INVALID_UNICODE)) return_err(src, MSG_ERR_UTF8);\n#endif\n            *dst = *src;\n            dst += 1; src += 1;\n        }\n    }\n    if (char_is_id_ascii(*src)) goto copy_ascii;\n    goto copy_escape;\n\n#undef return_err\n#undef return_suc\n}\n\n\n\n/*==============================================================================\n * MARK: - JSON Reader Implementation (Private)\n *\n * We use goto statements to build the finite state machine (FSM).\n * The FSM's state was held by program counter (PC) and the 'goto' make the\n * state transitions.\n *============================================================================*/\n\n/** Read single value JSON document. */\nstatic_noinline yyjson_doc *read_root_single(u8 *hdr, u8 *cur, u8 *eof,\n                                             yyjson_alc alc,\n                                             yyjson_read_flag flg,\n                                             yyjson_read_err *err) {\n#define return_err(_pos, _code, _msg) do { \\\n    if (is_truncated_end(hdr, _pos, eof, YYJSON_READ_ERROR_##_code, flg)) { \\\n        err->pos = (usize)(eof - hdr); \\\n        err->code = YYJSON_READ_ERROR_UNEXPECTED_END; \\\n        err->msg = MSG_NOT_END; \\\n    } else { \\\n        err->pos = (usize)(_pos - hdr); \\\n        err->code = YYJSON_READ_ERROR_##_code; \\\n        err->msg = _msg; \\\n    } \\\n    if (val_hdr) alc.free(alc.ctx, val_hdr); \\\n    return NULL; \\\n} while (false)\n\n    usize hdr_len; /* value count used by doc */\n    usize alc_num; /* value count capacity */\n    yyjson_val *val_hdr; /* the head of allocated values */\n    yyjson_val *val; /* current value */\n    yyjson_doc *doc; /* the JSON document, equals to val_hdr */\n    const char *msg; /* error message */\n\n    u8 raw_end[1]; /* raw end for null-terminator */\n    u8 *raw_ptr = raw_end;\n    u8 **pre = &raw_ptr; /* previous raw end pointer */\n\n    hdr_len = sizeof(yyjson_doc) / sizeof(yyjson_val);\n    hdr_len += (sizeof(yyjson_doc) % sizeof(yyjson_val)) > 0;\n    alc_num = hdr_len + 1; /* single value */\n\n    val_hdr = (yyjson_val *)alc.malloc(alc.ctx, alc_num * sizeof(yyjson_val));\n    if (unlikely(!val_hdr)) goto fail_alloc;\n    val = val_hdr + hdr_len;\n\n    if (char_is_num(*cur)) {\n        if (likely(read_num(&cur, pre, flg, val, &msg))) goto doc_end;\n        goto fail_number;\n    }\n    if (*cur == '\"') {\n        if (likely(read_str(&cur, eof, flg, val, &msg))) goto doc_end;\n        goto fail_string;\n    }\n    if (*cur == 't') {\n        if (likely(read_true(&cur, val))) goto doc_end;\n        goto fail_literal_true;\n    }\n    if (*cur == 'f') {\n        if (likely(read_false(&cur, val))) goto doc_end;\n        goto fail_literal_false;\n    }\n    if (*cur == 'n') {\n        if (likely(read_null(&cur, val))) goto doc_end;\n        if (has_allow(INF_AND_NAN)) {\n            if (read_nan(&cur, pre, flg, val)) goto doc_end;\n        }\n        goto fail_literal_null;\n    }\n    if (has_allow(INF_AND_NAN)) {\n        if (read_inf_or_nan(&cur, pre, flg, val)) goto doc_end;\n    }\n    if (has_allow(SINGLE_QUOTED_STR) && *cur == '\\'') {\n        if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto doc_end;\n        goto fail_string;\n    }\n    goto fail_character;\n\ndoc_end:\n    /* check invalid contents after json document */\n    if (unlikely(cur < eof) && !has_flg(STOP_WHEN_DONE)) {\n        while (char_is_space(*cur)) cur++;\n        if (has_allow(TRIVIA) && char_is_trivia(*cur)) {\n            if (!skip_trivia(&cur, eof, flg) && cur == eof) {\n                goto fail_comment;\n            }\n        }\n        if (unlikely(cur < eof)) goto fail_garbage;\n    }\n\n    **pre = '\\0';\n    doc = (yyjson_doc *)val_hdr;\n    doc->root = val_hdr + hdr_len;\n    doc->alc = alc;\n    doc->dat_read = (usize)(cur - hdr);\n    doc->val_read = 1;\n    doc->str_pool = has_flg(INSITU) ? NULL : (char *)hdr;\n    return doc;\n\nfail_string:        return_err(cur, INVALID_STRING, msg);\nfail_number:        return_err(cur, INVALID_NUMBER, msg);\nfail_alloc:         return_err(cur, MEMORY_ALLOCATION, MSG_MALLOC);\nfail_literal_true:  return_err(cur, LITERAL, MSG_CHAR_T);\nfail_literal_false: return_err(cur, LITERAL, MSG_CHAR_F);\nfail_literal_null:  return_err(cur, LITERAL, MSG_CHAR_N);\nfail_character:     return_err(cur, UNEXPECTED_CHARACTER, MSG_CHAR);\nfail_comment:       return_err(cur, INVALID_COMMENT, MSG_COMMENT);\nfail_garbage:       return_err(cur, UNEXPECTED_CONTENT, MSG_GARBAGE);\n\n#undef return_err\n}\n\n/** Read JSON document (accept all style, but optimized for minify). */\nstatic_inline yyjson_doc *read_root_minify(u8 *hdr, u8 *cur, u8 *eof,\n                                           yyjson_alc alc,\n                                           yyjson_read_flag flg,\n                                           yyjson_read_err *err) {\n#define return_err(_pos, _code, _msg) do { \\\n    if (is_truncated_end(hdr, _pos, eof, YYJSON_READ_ERROR_##_code, flg)) { \\\n        err->pos = (usize)(eof - hdr); \\\n        err->code = YYJSON_READ_ERROR_UNEXPECTED_END; \\\n        err->msg = MSG_NOT_END; \\\n    } else { \\\n        err->pos = (usize)(_pos - hdr); \\\n        err->code = YYJSON_READ_ERROR_##_code; \\\n        err->msg = _msg; \\\n    } \\\n    if (val_hdr) alc.free(alc.ctx, val_hdr); \\\n    return NULL; \\\n} while (false)\n\n#define val_incr() do { \\\n    val++; \\\n    if (unlikely(val >= val_end)) { \\\n        usize alc_old = alc_len; \\\n        usize val_ofs = (usize)(val - val_hdr); \\\n        usize ctn_ofs = (usize)(ctn - val_hdr); \\\n        alc_len += alc_len / 2; \\\n        if ((sizeof(usize) < 8) && (alc_len >= alc_max)) goto fail_alloc; \\\n        val_tmp = (yyjson_val *)alc.realloc(alc.ctx, (void *)val_hdr, \\\n            alc_old * sizeof(yyjson_val), \\\n            alc_len * sizeof(yyjson_val)); \\\n        if ((!val_tmp)) goto fail_alloc; \\\n        val = val_tmp + val_ofs; \\\n        ctn = val_tmp + ctn_ofs; \\\n        val_hdr = val_tmp; \\\n        val_end = val_tmp + (alc_len - 2); \\\n    } \\\n} while (false)\n\n    usize dat_len; /* data length in bytes, hint for allocator */\n    usize hdr_len; /* value count used by yyjson_doc */\n    usize alc_len; /* value count allocated */\n    usize alc_max; /* maximum value count for allocator */\n    usize ctn_len; /* the number of elements in current container */\n    yyjson_val *val_hdr; /* the head of allocated values */\n    yyjson_val *val_end; /* the end of allocated values */\n    yyjson_val *val_tmp; /* temporary pointer for realloc */\n    yyjson_val *val; /* current JSON value */\n    yyjson_val *ctn; /* current container */\n    yyjson_val *ctn_parent; /* parent of current container */\n    yyjson_doc *doc; /* the JSON document, equals to val_hdr */\n    const char *msg; /* error message */\n\n    u8 raw_end[1]; /* raw end for null-terminator */\n    u8 *raw_ptr = raw_end;\n    u8 **pre = &raw_ptr; /* previous raw end pointer */\n\n    dat_len = has_flg(STOP_WHEN_DONE) ? 256 : (usize)(eof - cur);\n    hdr_len = sizeof(yyjson_doc) / sizeof(yyjson_val);\n    hdr_len += (sizeof(yyjson_doc) % sizeof(yyjson_val)) > 0;\n    alc_max = USIZE_MAX / sizeof(yyjson_val);\n    alc_len = hdr_len + (dat_len / YYJSON_READER_ESTIMATED_MINIFY_RATIO) + 4;\n    alc_len = yyjson_min(alc_len, alc_max);\n\n    val_hdr = (yyjson_val *)alc.malloc(alc.ctx, alc_len * sizeof(yyjson_val));\n    if (unlikely(!val_hdr)) goto fail_alloc;\n    val_end = val_hdr + (alc_len - 2); /* padding for key-value pair reading */\n    val = val_hdr + hdr_len;\n    ctn = val;\n    ctn_len = 0;\n\n    if (*cur++ == '{') {\n        ctn->tag = YYJSON_TYPE_OBJ;\n        ctn->uni.ofs = 0;\n        goto obj_key_begin;\n    } else {\n        ctn->tag = YYJSON_TYPE_ARR;\n        ctn->uni.ofs = 0;\n        goto arr_val_begin;\n    }\n\narr_begin:\n    /* save current container */\n    ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) |\n               (ctn->tag & YYJSON_TAG_MASK);\n\n    /* create a new array value, save parent container offset */\n    val_incr();\n    val->tag = YYJSON_TYPE_ARR;\n    val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn);\n\n    /* push the new array value as current container */\n    ctn = val;\n    ctn_len = 0;\n\narr_val_begin:\n    if (*cur == '{') {\n        cur++;\n        goto obj_begin;\n    }\n    if (*cur == '[') {\n        cur++;\n        goto arr_begin;\n    }\n    if (char_is_num(*cur)) {\n        val_incr();\n        ctn_len++;\n        if (likely(read_num(&cur, pre, flg, val, &msg))) goto arr_val_end;\n        goto fail_number;\n    }\n    if (*cur == '\"') {\n        val_incr();\n        ctn_len++;\n        if (likely(read_str(&cur, eof, flg, val, &msg))) goto arr_val_end;\n        goto fail_string;\n    }\n    if (*cur == 't') {\n        val_incr();\n        ctn_len++;\n        if (likely(read_true(&cur, val))) goto arr_val_end;\n        goto fail_literal_true;\n    }\n    if (*cur == 'f') {\n        val_incr();\n        ctn_len++;\n        if (likely(read_false(&cur, val))) goto arr_val_end;\n        goto fail_literal_false;\n    }\n    if (*cur == 'n') {\n        val_incr();\n        ctn_len++;\n        if (likely(read_null(&cur, val))) goto arr_val_end;\n        if (has_allow(INF_AND_NAN)) {\n            if (read_nan(&cur, pre, flg, val)) goto arr_val_end;\n        }\n        goto fail_literal_null;\n    }\n    if (*cur == ']') {\n        cur++;\n        if (likely(ctn_len == 0)) goto arr_end;\n        if (has_allow(TRAILING_COMMAS)) goto arr_end;\n        while (*cur != ',') cur--;\n        goto fail_trailing_comma;\n    }\n    if (char_is_space(*cur)) {\n        while (char_is_space(*++cur));\n        goto arr_val_begin;\n    }\n    if (has_allow(INF_AND_NAN) &&\n        (*cur == 'i' || *cur == 'I' || *cur == 'N')) {\n        val_incr();\n        ctn_len++;\n        if (read_inf_or_nan(&cur, pre, flg, val)) goto arr_val_end;\n        goto fail_character_val;\n    }\n    if (has_allow(SINGLE_QUOTED_STR) && *cur == '\\'') {\n        val_incr();\n        ctn_len++;\n        if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto arr_val_end;\n        goto fail_string;\n    }\n    if (has_allow(TRIVIA) && char_is_trivia(*cur)) {\n        if (skip_trivia(&cur, eof, flg)) goto arr_val_begin;\n        if (cur == eof) goto fail_comment;\n    }\n    goto fail_character_val;\n\narr_val_end:\n    if (*cur == ',') {\n        cur++;\n        goto arr_val_begin;\n    }\n    if (*cur == ']') {\n        cur++;\n        goto arr_end;\n    }\n    if (char_is_space(*cur)) {\n        while (char_is_space(*++cur));\n        goto arr_val_end;\n    }\n    if (has_allow(TRIVIA) && char_is_trivia(*cur)) {\n        if (skip_trivia(&cur, eof, flg)) goto arr_val_end;\n        if (cur == eof) goto fail_comment;\n    }\n    goto fail_character_arr_end;\n\narr_end:\n    /* get parent container */\n    ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs);\n\n    /* save the next sibling value offset */\n    ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val);\n    ctn->tag = ((ctn_len) << YYJSON_TAG_BIT) | YYJSON_TYPE_ARR;\n    if (unlikely(ctn == ctn_parent)) goto doc_end;\n\n    /* pop parent as current container */\n    ctn = ctn_parent;\n    ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT);\n    if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) {\n        goto obj_val_end;\n    } else {\n        goto arr_val_end;\n    }\n\nobj_begin:\n    /* push container */\n    ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) |\n               (ctn->tag & YYJSON_TAG_MASK);\n    val_incr();\n    val->tag = YYJSON_TYPE_OBJ;\n    /* offset to the parent */\n    val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn);\n    ctn = val;\n    ctn_len = 0;\n\nobj_key_begin:\n    if (likely(*cur == '\"')) {\n        val_incr();\n        ctn_len++;\n        if (likely(read_str(&cur, eof, flg, val, &msg))) goto obj_key_end;\n        goto fail_string;\n    }\n    if (likely(*cur == '}')) {\n        cur++;\n        if (likely(ctn_len == 0)) goto obj_end;\n        if (has_allow(TRAILING_COMMAS)) goto obj_end;\n        while (*cur != ',') cur--;\n        goto fail_trailing_comma;\n    }\n    if (char_is_space(*cur)) {\n        while (char_is_space(*++cur));\n        goto obj_key_begin;\n    }\n    if (has_allow(SINGLE_QUOTED_STR) && *cur == '\\'') {\n        val_incr();\n        ctn_len++;\n        if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto obj_key_end;\n        goto fail_string;\n    }\n    if (has_allow(UNQUOTED_KEY) && char_is_id_start(*cur)) {\n        val_incr();\n        ctn_len++;\n        if (read_str_id(&cur, eof, flg, pre, val, &msg)) goto obj_key_end;\n        goto fail_string;\n    }\n    if (has_allow(TRIVIA) && char_is_trivia(*cur)) {\n        if (skip_trivia(&cur, eof, flg)) goto obj_key_begin;\n        if (cur == eof) goto fail_comment;\n    }\n    goto fail_character_obj_key;\n\nobj_key_end:\n    if (*cur == ':') {\n        cur++;\n        goto obj_val_begin;\n    }\n    if (char_is_space(*cur)) {\n        while (char_is_space(*++cur));\n        goto obj_key_end;\n    }\n    if (has_allow(TRIVIA) && char_is_trivia(*cur)) {\n        if (skip_trivia(&cur, eof, flg)) goto obj_key_end;\n        if (cur == eof) goto fail_comment;\n    }\n    goto fail_character_obj_sep;\n\nobj_val_begin:\n    if (*cur == '\"') {\n        val++;\n        ctn_len++;\n        if (likely(read_str(&cur, eof, flg, val, &msg))) goto obj_val_end;\n        goto fail_string;\n    }\n    if (char_is_num(*cur)) {\n        val++;\n        ctn_len++;\n        if (likely(read_num(&cur, pre, flg, val, &msg))) goto obj_val_end;\n        goto fail_number;\n    }\n    if (*cur == '{') {\n        cur++;\n        goto obj_begin;\n    }\n    if (*cur == '[') {\n        cur++;\n        goto arr_begin;\n    }\n    if (*cur == 't') {\n        val++;\n        ctn_len++;\n        if (likely(read_true(&cur, val))) goto obj_val_end;\n        goto fail_literal_true;\n    }\n    if (*cur == 'f') {\n        val++;\n        ctn_len++;\n        if (likely(read_false(&cur, val))) goto obj_val_end;\n        goto fail_literal_false;\n    }\n    if (*cur == 'n') {\n        val++;\n        ctn_len++;\n        if (likely(read_null(&cur, val))) goto obj_val_end;\n        if (has_allow(INF_AND_NAN)) {\n            if (read_nan(&cur, pre, flg, val)) goto obj_val_end;\n        }\n        goto fail_literal_null;\n    }\n    if (char_is_space(*cur)) {\n        while (char_is_space(*++cur));\n        goto obj_val_begin;\n    }\n    if (has_allow(INF_AND_NAN) &&\n        (*cur == 'i' || *cur == 'I' || *cur == 'N')) {\n        val++;\n        ctn_len++;\n        if (read_inf_or_nan(&cur, pre, flg, val)) goto obj_val_end;\n        goto fail_character_val;\n    }\n    if (has_allow(SINGLE_QUOTED_STR) && *cur == '\\'') {\n        val++;\n        ctn_len++;\n        if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto obj_val_end;\n        goto fail_string;\n    }\n    if (has_allow(TRIVIA) && char_is_trivia(*cur)) {\n        if (skip_trivia(&cur, eof, flg)) goto obj_val_begin;\n        if (cur == eof) goto fail_comment;\n    }\n    goto fail_character_val;\n\nobj_val_end:\n    if (likely(*cur == ',')) {\n        cur++;\n        goto obj_key_begin;\n    }\n    if (likely(*cur == '}')) {\n        cur++;\n        goto obj_end;\n    }\n    if (char_is_space(*cur)) {\n        while (char_is_space(*++cur));\n        goto obj_val_end;\n    }\n    if (has_allow(TRIVIA) && char_is_trivia(*cur)) {\n        if (skip_trivia(&cur, eof, flg)) goto obj_val_end;\n        if (cur == eof) goto fail_comment;\n    }\n    goto fail_character_obj_end;\n\nobj_end:\n    /* pop container */\n    ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs);\n    /* point to the next value */\n    ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val);\n    ctn->tag = (ctn_len << (YYJSON_TAG_BIT - 1)) | YYJSON_TYPE_OBJ;\n    if (unlikely(ctn == ctn_parent)) goto doc_end;\n    ctn = ctn_parent;\n    ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT);\n    if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) {\n        goto obj_val_end;\n    } else {\n        goto arr_val_end;\n    }\n\ndoc_end:\n    /* check invalid contents after json document */\n    if (unlikely(cur < eof) && !has_flg(STOP_WHEN_DONE)) {\n        while (char_is_space(*cur)) cur++;\n        if (has_allow(TRIVIA) && char_is_trivia(*cur)) {\n            if (!skip_trivia(&cur, eof, flg) && cur == eof) {\n                goto fail_comment;\n            }\n        }\n        if (unlikely(cur < eof)) goto fail_garbage;\n    }\n\n    **pre = '\\0';\n    doc = (yyjson_doc *)val_hdr;\n    doc->root = val_hdr + hdr_len;\n    doc->alc = alc;\n    doc->dat_read = (usize)(cur - hdr);\n    doc->val_read = (usize)((val - doc->root) + 1);\n    doc->str_pool = has_flg(INSITU) ? NULL : (char *)hdr;\n    return doc;\n\nfail_string:            return_err(cur, INVALID_STRING, msg);\nfail_number:            return_err(cur, INVALID_NUMBER, msg);\nfail_alloc:             return_err(cur, MEMORY_ALLOCATION, MSG_MALLOC);\nfail_trailing_comma:    return_err(cur, JSON_STRUCTURE, MSG_COMMA);\nfail_literal_true:      return_err(cur, LITERAL, MSG_CHAR_T);\nfail_literal_false:     return_err(cur, LITERAL, MSG_CHAR_F);\nfail_literal_null:      return_err(cur, LITERAL, MSG_CHAR_N);\nfail_character_val:     return_err(cur, UNEXPECTED_CHARACTER, MSG_CHAR);\nfail_character_arr_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_ARR_END);\nfail_character_obj_key: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_KEY);\nfail_character_obj_sep: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_SEP);\nfail_character_obj_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_END);\nfail_comment:           return_err(cur, INVALID_COMMENT, MSG_COMMENT);\nfail_garbage:           return_err(cur, UNEXPECTED_CONTENT, MSG_GARBAGE);\n\n#undef val_incr\n#undef return_err\n}\n\n/** Read JSON document (accept all style, but optimized for pretty). */\nstatic_inline yyjson_doc *read_root_pretty(u8 *hdr, u8 *cur, u8 *eof,\n                                           yyjson_alc alc,\n                                           yyjson_read_flag flg,\n                                           yyjson_read_err *err) {\n#define return_err(_pos, _code, _msg) do { \\\n    if (is_truncated_end(hdr, _pos, eof, YYJSON_READ_ERROR_##_code, flg)) { \\\n        err->pos = (usize)(eof - hdr); \\\n        err->code = YYJSON_READ_ERROR_UNEXPECTED_END; \\\n        err->msg = MSG_NOT_END; \\\n    } else { \\\n        err->pos = (usize)(_pos - hdr); \\\n        err->code = YYJSON_READ_ERROR_##_code; \\\n        err->msg = _msg; \\\n    } \\\n    if (val_hdr) alc.free(alc.ctx, val_hdr); \\\n    return NULL; \\\n} while (false)\n\n#define val_incr() do { \\\n    val++; \\\n    if (unlikely(val >= val_end)) { \\\n        usize alc_old = alc_len; \\\n        usize val_ofs = (usize)(val - val_hdr); \\\n        usize ctn_ofs = (usize)(ctn - val_hdr); \\\n        alc_len += alc_len / 2; \\\n        if ((sizeof(usize) < 8) && (alc_len >= alc_max)) goto fail_alloc; \\\n        val_tmp = (yyjson_val *)alc.realloc(alc.ctx, (void *)val_hdr, \\\n            alc_old * sizeof(yyjson_val), \\\n            alc_len * sizeof(yyjson_val)); \\\n        if ((!val_tmp)) goto fail_alloc; \\\n        val = val_tmp + val_ofs; \\\n        ctn = val_tmp + ctn_ofs; \\\n        val_hdr = val_tmp; \\\n        val_end = val_tmp + (alc_len - 2); \\\n    } \\\n} while (false)\n\n    usize dat_len; /* data length in bytes, hint for allocator */\n    usize hdr_len; /* value count used by yyjson_doc */\n    usize alc_len; /* value count allocated */\n    usize alc_max; /* maximum value count for allocator */\n    usize ctn_len; /* the number of elements in current container */\n    yyjson_val *val_hdr; /* the head of allocated values */\n    yyjson_val *val_end; /* the end of allocated values */\n    yyjson_val *val_tmp; /* temporary pointer for realloc */\n    yyjson_val *val; /* current JSON value */\n    yyjson_val *ctn; /* current container */\n    yyjson_val *ctn_parent; /* parent of current container */\n    yyjson_doc *doc; /* the JSON document, equals to val_hdr */\n    const char *msg; /* error message */\n\n    u8 raw_end[1]; /* raw end for null-terminator */\n    u8 *raw_ptr = raw_end;\n    u8 **pre = &raw_ptr; /* previous raw end pointer */\n\n    dat_len = has_flg(STOP_WHEN_DONE) ? 256 : (usize)(eof - cur);\n    hdr_len = sizeof(yyjson_doc) / sizeof(yyjson_val);\n    hdr_len += (sizeof(yyjson_doc) % sizeof(yyjson_val)) > 0;\n    alc_max = USIZE_MAX / sizeof(yyjson_val);\n    alc_len = hdr_len + (dat_len / YYJSON_READER_ESTIMATED_PRETTY_RATIO) + 4;\n    alc_len = yyjson_min(alc_len, alc_max);\n\n    val_hdr = (yyjson_val *)alc.malloc(alc.ctx, alc_len * sizeof(yyjson_val));\n    if (unlikely(!val_hdr)) goto fail_alloc;\n    val_end = val_hdr + (alc_len - 2); /* padding for key-value pair reading */\n    val = val_hdr + hdr_len;\n    ctn = val;\n    ctn_len = 0;\n\n    if (*cur++ == '{') {\n        ctn->tag = YYJSON_TYPE_OBJ;\n        ctn->uni.ofs = 0;\n        if (*cur == '\\n') cur++;\n        goto obj_key_begin;\n    } else {\n        ctn->tag = YYJSON_TYPE_ARR;\n        ctn->uni.ofs = 0;\n        if (*cur == '\\n') cur++;\n        goto arr_val_begin;\n    }\n\narr_begin:\n    /* save current container */\n    ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) |\n               (ctn->tag & YYJSON_TAG_MASK);\n\n    /* create a new array value, save parent container offset */\n    val_incr();\n    val->tag = YYJSON_TYPE_ARR;\n    val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn);\n\n    /* push the new array value as current container */\n    ctn = val;\n    ctn_len = 0;\n    if (*cur == '\\n') cur++;\n\narr_val_begin:\n#if YYJSON_IS_REAL_GCC\n    while (true) repeat16({\n        if (byte_match_2(cur, \"  \")) cur += 2;\n        else break;\n    })\n#else\n    while (true) repeat16({\n        if (likely(byte_match_2(cur, \"  \"))) cur += 2;\n        else break;\n    })\n#endif\n\n    if (*cur == '{') {\n        cur++;\n        goto obj_begin;\n    }\n    if (*cur == '[') {\n        cur++;\n        goto arr_begin;\n    }\n    if (char_is_num(*cur)) {\n        val_incr();\n        ctn_len++;\n        if (likely(read_num(&cur, pre, flg, val, &msg))) goto arr_val_end;\n        goto fail_number;\n    }\n    if (*cur == '\"') {\n        val_incr();\n        ctn_len++;\n        if (likely(read_str(&cur, eof, flg, val, &msg))) goto arr_val_end;\n        goto fail_string;\n    }\n    if (*cur == 't') {\n        val_incr();\n        ctn_len++;\n        if (likely(read_true(&cur, val))) goto arr_val_end;\n        goto fail_literal_true;\n    }\n    if (*cur == 'f') {\n        val_incr();\n        ctn_len++;\n        if (likely(read_false(&cur, val))) goto arr_val_end;\n        goto fail_literal_false;\n    }\n    if (*cur == 'n') {\n        val_incr();\n        ctn_len++;\n        if (likely(read_null(&cur, val))) goto arr_val_end;\n        if (has_allow(INF_AND_NAN)) {\n            if (read_nan(&cur, pre, flg, val)) goto arr_val_end;\n        }\n        goto fail_literal_null;\n    }\n    if (*cur == ']') {\n        cur++;\n        if (likely(ctn_len == 0)) goto arr_end;\n        if (has_allow(TRAILING_COMMAS)) goto arr_end;\n        while (*cur != ',') cur--;\n        goto fail_trailing_comma;\n    }\n    if (char_is_space(*cur)) {\n        while (char_is_space(*++cur));\n        goto arr_val_begin;\n    }\n    if (has_allow(INF_AND_NAN) &&\n        (*cur == 'i' || *cur == 'I' || *cur == 'N')) {\n        val_incr();\n        ctn_len++;\n        if (read_inf_or_nan(&cur, pre, flg, val)) goto arr_val_end;\n        goto fail_character_val;\n    }\n    if (has_allow(SINGLE_QUOTED_STR) && *cur == '\\'') {\n        val_incr();\n        ctn_len++;\n        if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto arr_val_end;\n        goto fail_string;\n    }\n    if (has_allow(TRIVIA) && char_is_trivia(*cur)) {\n        if (skip_trivia(&cur, eof, flg)) goto arr_val_begin;\n        if (cur == eof) goto fail_comment;\n    }\n    goto fail_character_val;\n\narr_val_end:\n    if (byte_match_2(cur, \",\\n\")) {\n        cur += 2;\n        goto arr_val_begin;\n    }\n    if (*cur == ',') {\n        cur++;\n        goto arr_val_begin;\n    }\n    if (*cur == ']') {\n        cur++;\n        goto arr_end;\n    }\n    if (char_is_space(*cur)) {\n        while (char_is_space(*++cur));\n        goto arr_val_end;\n    }\n    if (has_allow(TRIVIA) && char_is_trivia(*cur)) {\n        if (skip_trivia(&cur, eof, flg)) goto arr_val_end;\n        if (cur == eof) goto fail_comment;\n    }\n    goto fail_character_arr_end;\n\narr_end:\n    /* get parent container */\n    ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs);\n\n    /* save the next sibling value offset */\n    ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val);\n    ctn->tag = ((ctn_len) << YYJSON_TAG_BIT) | YYJSON_TYPE_ARR;\n    if (unlikely(ctn == ctn_parent)) goto doc_end;\n\n    /* pop parent as current container */\n    ctn = ctn_parent;\n    ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT);\n    if (*cur == '\\n') cur++;\n    if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) {\n        goto obj_val_end;\n    } else {\n        goto arr_val_end;\n    }\n\nobj_begin:\n    /* push container */\n    ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) |\n               (ctn->tag & YYJSON_TAG_MASK);\n    val_incr();\n    val->tag = YYJSON_TYPE_OBJ;\n    /* offset to the parent */\n    val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn);\n    ctn = val;\n    ctn_len = 0;\n    if (*cur == '\\n') cur++;\n\nobj_key_begin:\n#if YYJSON_IS_REAL_GCC\n    while (true) repeat16({\n        if (byte_match_2(cur, \"  \")) cur += 2;\n        else break;\n    })\n#else\n    while (true) repeat16({\n        if (likely(byte_match_2(cur, \"  \"))) cur += 2;\n        else break;\n    })\n#endif\n    if (likely(*cur == '\"')) {\n        val_incr();\n        ctn_len++;\n        if (likely(read_str(&cur, eof, flg, val, &msg))) goto obj_key_end;\n        goto fail_string;\n    }\n    if (likely(*cur == '}')) {\n        cur++;\n        if (likely(ctn_len == 0)) goto obj_end;\n        if (has_allow(TRAILING_COMMAS)) goto obj_end;\n        while (*cur != ',') cur--;\n        goto fail_trailing_comma;\n    }\n    if (char_is_space(*cur)) {\n        while (char_is_space(*++cur));\n        goto obj_key_begin;\n    }\n    if (has_allow(SINGLE_QUOTED_STR) && *cur == '\\'') {\n        val_incr();\n        ctn_len++;\n        if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto obj_key_end;\n        goto fail_string;\n    }\n    if (has_allow(UNQUOTED_KEY) && char_is_id_start(*cur)) {\n        val_incr();\n        ctn_len++;\n        if (read_str_id(&cur, eof, flg, pre, val, &msg)) goto obj_key_end;\n        goto fail_string;\n    }\n    if (has_allow(TRIVIA) && char_is_trivia(*cur)) {\n        if (skip_trivia(&cur, eof, flg)) goto obj_key_begin;\n        if (cur == eof) goto fail_comment;\n    }\n    goto fail_character_obj_key;\n\nobj_key_end:\n    if (byte_match_2(cur, \": \")) {\n        cur += 2;\n        goto obj_val_begin;\n    }\n    if (*cur == ':') {\n        cur++;\n        goto obj_val_begin;\n    }\n    if (char_is_space(*cur)) {\n        while (char_is_space(*++cur));\n        goto obj_key_end;\n    }\n    if (has_allow(TRIVIA) && char_is_trivia(*cur)) {\n        if (skip_trivia(&cur, eof, flg)) goto obj_key_end;\n        if (cur == eof) goto fail_comment;\n    }\n    goto fail_character_obj_sep;\n\nobj_val_begin:\n    if (*cur == '\"') {\n        val++;\n        ctn_len++;\n        if (likely(read_str(&cur, eof, flg, val, &msg))) goto obj_val_end;\n        goto fail_string;\n    }\n    if (char_is_num(*cur)) {\n        val++;\n        ctn_len++;\n        if (likely(read_num(&cur, pre, flg, val, &msg))) goto obj_val_end;\n        goto fail_number;\n    }\n    if (*cur == '{') {\n        cur++;\n        goto obj_begin;\n    }\n    if (*cur == '[') {\n        cur++;\n        goto arr_begin;\n    }\n    if (*cur == 't') {\n        val++;\n        ctn_len++;\n        if (likely(read_true(&cur, val))) goto obj_val_end;\n        goto fail_literal_true;\n    }\n    if (*cur == 'f') {\n        val++;\n        ctn_len++;\n        if (likely(read_false(&cur, val))) goto obj_val_end;\n        goto fail_literal_false;\n    }\n    if (*cur == 'n') {\n        val++;\n        ctn_len++;\n        if (likely(read_null(&cur, val))) goto obj_val_end;\n        if (has_allow(INF_AND_NAN)) {\n            if (read_nan(&cur, pre, flg, val)) goto obj_val_end;\n        }\n        goto fail_literal_null;\n    }\n    if (char_is_space(*cur)) {\n        while (char_is_space(*++cur));\n        goto obj_val_begin;\n    }\n    if (has_allow(INF_AND_NAN) &&\n        (*cur == 'i' || *cur == 'I' || *cur == 'N')) {\n        val++;\n        ctn_len++;\n        if (read_inf_or_nan(&cur, pre, flg, val)) goto obj_val_end;\n        goto fail_character_val;\n    }\n    if (has_allow(SINGLE_QUOTED_STR) && *cur == '\\'') {\n        val++;\n        ctn_len++;\n        if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto obj_val_end;\n        goto fail_string;\n    }\n    if (has_allow(TRIVIA) && char_is_trivia(*cur)) {\n        if (skip_trivia(&cur, eof, flg)) goto obj_val_begin;\n        if (cur == eof) goto fail_comment;\n    }\n    goto fail_character_val;\n\nobj_val_end:\n    if (byte_match_2(cur, \",\\n\")) {\n        cur += 2;\n        goto obj_key_begin;\n    }\n    if (likely(*cur == ',')) {\n        cur++;\n        goto obj_key_begin;\n    }\n    if (likely(*cur == '}')) {\n        cur++;\n        goto obj_end;\n    }\n    if (char_is_space(*cur)) {\n        while (char_is_space(*++cur));\n        goto obj_val_end;\n    }\n    if (has_allow(TRIVIA) && char_is_trivia(*cur)) {\n        if (skip_trivia(&cur, eof, flg)) goto obj_val_end;\n        if (cur == eof) goto fail_comment;\n    }\n    goto fail_character_obj_end;\n\nobj_end:\n    /* pop container */\n    ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs);\n    /* point to the next value */\n    ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val);\n    ctn->tag = (ctn_len << (YYJSON_TAG_BIT - 1)) | YYJSON_TYPE_OBJ;\n    if (unlikely(ctn == ctn_parent)) goto doc_end;\n    ctn = ctn_parent;\n    ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT);\n    if (*cur == '\\n') cur++;\n    if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) {\n        goto obj_val_end;\n    } else {\n        goto arr_val_end;\n    }\n\ndoc_end:\n    /* check invalid contents after json document */\n    if (unlikely(cur < eof) && !has_flg(STOP_WHEN_DONE)) {\n        while (char_is_space(*cur)) cur++;\n        if (has_allow(TRIVIA) && char_is_trivia(*cur)) {\n            if (!skip_trivia(&cur, eof, flg) && cur == eof) {\n                goto fail_comment;\n            }\n        }\n        if (unlikely(cur < eof)) goto fail_garbage;\n    }\n\n    **pre = '\\0';\n    doc = (yyjson_doc *)val_hdr;\n    doc->root = val_hdr + hdr_len;\n    doc->alc = alc;\n    doc->dat_read = (usize)(cur - hdr);\n    doc->val_read = (usize)((val - doc->root) + 1);\n    doc->str_pool = has_flg(INSITU) ? NULL : (char *)hdr;\n    return doc;\n\nfail_string:            return_err(cur, INVALID_STRING, msg);\nfail_number:            return_err(cur, INVALID_NUMBER, msg);\nfail_alloc:             return_err(cur, MEMORY_ALLOCATION, MSG_MALLOC);\nfail_trailing_comma:    return_err(cur, JSON_STRUCTURE, MSG_COMMA);\nfail_literal_true:      return_err(cur, LITERAL, MSG_CHAR_T);\nfail_literal_false:     return_err(cur, LITERAL, MSG_CHAR_F);\nfail_literal_null:      return_err(cur, LITERAL, MSG_CHAR_N);\nfail_character_val:     return_err(cur, UNEXPECTED_CHARACTER, MSG_CHAR);\nfail_character_arr_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_ARR_END);\nfail_character_obj_key: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_KEY);\nfail_character_obj_sep: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_SEP);\nfail_character_obj_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_END);\nfail_comment:           return_err(cur, INVALID_COMMENT, MSG_COMMENT);\nfail_garbage:           return_err(cur, UNEXPECTED_CONTENT, MSG_GARBAGE);\n\n#undef val_incr\n#undef return_err\n}\n\n\n\n/*==============================================================================\n * MARK: - JSON Reader (Public)\n *============================================================================*/\n\nyyjson_doc *yyjson_read_opts(char *dat, usize len,\n                             yyjson_read_flag flg,\n                             const yyjson_alc *alc_ptr,\n                             yyjson_read_err *err) {\n#define return_err(_pos, _code, _msg) do { \\\n    err->pos = (usize)(_pos); \\\n    err->msg = _msg; \\\n    err->code = YYJSON_READ_ERROR_##_code; \\\n    if (!has_flg(INSITU) && hdr) alc.free(alc.ctx, (void *)hdr); \\\n    return NULL; \\\n} while (false)\n\n    yyjson_read_err tmp_err;\n    yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC;\n    yyjson_doc *doc;\n    u8 *hdr = NULL, *eof, *cur;\n\n    /* validate input parameters */\n    if (!err) err = &tmp_err;\n    if (unlikely(!dat)) return_err(0, INVALID_PARAMETER, \"input data is NULL\");\n    if (unlikely(!len)) return_err(0, INVALID_PARAMETER, \"input length is 0\");\n\n    /* add 4-byte zero padding for input data if necessary */\n    if (has_flg(INSITU)) {\n        hdr = (u8 *)dat;\n        eof = (u8 *)dat + len;\n        cur = (u8 *)dat;\n    } else {\n        if (unlikely(len >= USIZE_MAX - YYJSON_PADDING_SIZE)) {\n            return_err(0, MEMORY_ALLOCATION, MSG_MALLOC);\n        }\n        hdr = (u8 *)alc.malloc(alc.ctx, len + YYJSON_PADDING_SIZE);\n        if (unlikely(!hdr)) {\n            return_err(0, MEMORY_ALLOCATION, MSG_MALLOC);\n        }\n        eof = hdr + len;\n        cur = hdr;\n        memcpy(hdr, dat, len);\n    }\n    memset(eof, 0, YYJSON_PADDING_SIZE);\n\n    if (has_allow(BOM)) {\n        if (len >= 3 && is_utf8_bom(cur)) cur += 3;\n    }\n\n    /* skip empty contents before json document */\n    if (unlikely(!char_is_ctn(*cur))) {\n        while (char_is_space(*cur)) cur++;\n        if (unlikely(!char_is_ctn(*cur))) {\n            if (has_allow(TRIVIA) && char_is_trivia(*cur)) {\n                if (!skip_trivia(&cur, eof, flg) && cur == eof) {\n                    return_err(cur - hdr, INVALID_COMMENT, MSG_COMMENT);\n                }\n            }\n        }\n        if (unlikely(cur >= eof)) {\n            return_err(0, EMPTY_CONTENT, \"input data is empty\");\n        }\n    }\n\n    /* read json document */\n    if (likely(char_is_ctn(*cur))) {\n        if (char_is_space(cur[1]) && char_is_space(cur[2])) {\n            doc = read_root_pretty(hdr, cur, eof, alc, flg, err);\n        } else {\n            doc = read_root_minify(hdr, cur, eof, alc, flg, err);\n        }\n    } else {\n        doc = read_root_single(hdr, cur, eof, alc, flg, err);\n    }\n\n    /* check result */\n    if (likely(doc)) {\n        memset(err, 0, sizeof(yyjson_read_err));\n    } else {\n        /* RFC 8259: JSON text MUST be encoded using UTF-8 */\n        if (err->pos == 0 && err->code != YYJSON_READ_ERROR_MEMORY_ALLOCATION) {\n            if (is_utf8_bom(hdr)) err->msg = MSG_ERR_BOM;\n            else if (len >= 4 && is_utf32_bom(hdr)) err->msg = MSG_ERR_UTF32;\n            else if (len >= 2 && is_utf16_bom(hdr)) err->msg = MSG_ERR_UTF16;\n        }\n        if (!has_flg(INSITU)) alc.free(alc.ctx, hdr);\n    }\n    return doc;\n\n#undef return_err\n}\n\nyyjson_doc *yyjson_read_file(const char *path,\n                             yyjson_read_flag flg,\n                             const yyjson_alc *alc_ptr,\n                             yyjson_read_err *err) {\n#define return_err(_code, _msg) do { \\\n    err->pos = 0; \\\n    err->msg = _msg; \\\n    err->code = YYJSON_READ_ERROR_##_code; \\\n    return NULL; \\\n} while (false)\n\n    yyjson_read_err tmp_err;\n    yyjson_doc *doc;\n    FILE *file;\n\n    if (!err) err = &tmp_err;\n    if (unlikely(!path)) return_err(INVALID_PARAMETER, \"input path is NULL\");\n\n    file = fopen_readonly(path);\n    if (unlikely(!file)) return_err(FILE_OPEN, MSG_FREAD);\n\n    doc = yyjson_read_fp(file, flg, alc_ptr, err);\n    fclose(file);\n    return doc;\n\n#undef return_err\n}\n\nyyjson_doc *yyjson_read_fp(FILE *file,\n                           yyjson_read_flag flg,\n                           const yyjson_alc *alc_ptr,\n                           yyjson_read_err *err) {\n#define return_err(_code, _msg) do { \\\n    err->pos = 0; \\\n    err->msg = _msg; \\\n    err->code = YYJSON_READ_ERROR_##_code; \\\n    if (buf) alc.free(alc.ctx, buf); \\\n    return NULL; \\\n} while (false)\n\n    yyjson_read_err tmp_err;\n    yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC;\n    yyjson_doc *doc;\n\n    long file_size = 0, file_pos;\n    void *buf = NULL;\n    usize buf_size = 0;\n\n    /* validate input parameters */\n    if (!err) err = &tmp_err;\n    if (unlikely(!file)) return_err(INVALID_PARAMETER, \"input file is NULL\");\n\n    /* get current position */\n    file_pos = ftell(file);\n    if (file_pos != -1) {\n        /* get total file size, may fail */\n        if (fseek(file, 0, SEEK_END) == 0) file_size = ftell(file);\n        /* reset to original position, may fail */\n        if (fseek(file, file_pos, SEEK_SET) != 0) file_size = 0;\n        /* get file size from current postion to end */\n        if (file_size > 0) file_size -= file_pos;\n    }\n\n    /* read file */\n    if (file_size > 0) {\n        /* read the entire file in one call */\n        buf_size = (usize)file_size + YYJSON_PADDING_SIZE;\n        buf = alc.malloc(alc.ctx, buf_size);\n        if (buf == NULL) {\n            return_err(MEMORY_ALLOCATION, MSG_MALLOC);\n        }\n        if (fread_safe(buf, (usize)file_size, file) != (usize)file_size) {\n            return_err(FILE_READ, MSG_FREAD);\n        }\n    } else {\n        /* failed to get file size, read it as a stream */\n        usize chunk_min = (usize)64;\n        usize chunk_max = (usize)512 * 1024 * 1024;\n        usize chunk_now = chunk_min;\n        usize read_size;\n        void *tmp;\n\n        buf_size = YYJSON_PADDING_SIZE;\n        while (true) {\n            if (buf_size + chunk_now < buf_size) { /* overflow */\n                return_err(MEMORY_ALLOCATION, MSG_MALLOC);\n            }\n            buf_size += chunk_now;\n            if (!buf) {\n                buf = alc.malloc(alc.ctx, buf_size);\n                if (!buf) return_err(MEMORY_ALLOCATION, MSG_MALLOC);\n            } else {\n                tmp = alc.realloc(alc.ctx, buf, buf_size - chunk_now, buf_size);\n                if (!tmp) return_err(MEMORY_ALLOCATION, MSG_MALLOC);\n                buf = tmp;\n            }\n            tmp = ((u8 *)buf) + buf_size - YYJSON_PADDING_SIZE - chunk_now;\n            read_size = fread_safe(tmp, chunk_now, file);\n            file_size += (long)read_size;\n            if (read_size != chunk_now) break;\n\n            chunk_now *= 2;\n            if (chunk_now > chunk_max) chunk_now = chunk_max;\n        }\n    }\n\n    /* read JSON */\n    memset((u8 *)buf + file_size, 0, YYJSON_PADDING_SIZE);\n    flg |= YYJSON_READ_INSITU;\n    doc = yyjson_read_opts((char *)buf, (usize)file_size, flg, &alc, err);\n    if (doc) {\n        doc->str_pool = (char *)buf;\n        return doc;\n    } else {\n        alc.free(alc.ctx, buf);\n        return NULL;\n    }\n\n#undef return_err\n}\n\nconst char *yyjson_read_number(const char *dat,\n                               yyjson_val *val,\n                               yyjson_read_flag flg,\n                               const yyjson_alc *alc,\n                               yyjson_read_err *err) {\n#define return_err(_pos, _code, _msg) do { \\\n    err->pos = _pos > hdr ? (usize)(_pos - hdr) : 0; \\\n    err->msg = _msg; \\\n    err->code = YYJSON_READ_ERROR_##_code; \\\n    return NULL; \\\n} while (false)\n\n    u8 *hdr = constcast(u8 *)dat, *cur = hdr;\n    u8 raw_end[1]; /* raw end for null-terminator */\n    u8 *raw_ptr = raw_end;\n    u8 **pre = &raw_ptr; /* previous raw end pointer */\n    const char *msg;\n    yyjson_read_err tmp_err;\n\n#if YYJSON_DISABLE_FAST_FP_CONV\n    u8 buf[128];\n    usize dat_len;\n#endif\n\n    if (!err) err = &tmp_err;\n    if (unlikely(!dat)) {\n        return_err(cur, INVALID_PARAMETER, \"input data is NULL\");\n    }\n    if (unlikely(!val)) {\n        return_err(cur, INVALID_PARAMETER, \"output value is NULL\");\n    }\n\n#if YYJSON_DISABLE_FAST_FP_CONV\n    if (!alc) alc = &YYJSON_DEFAULT_ALC;\n    dat_len = strlen(dat);\n    if (dat_len < sizeof(buf)) {\n        memcpy(buf, dat, dat_len + 1);\n        hdr = buf;\n        cur = hdr;\n    } else {\n        hdr = (u8 *)alc->malloc(alc->ctx, dat_len + 1);\n        cur = hdr;\n        if (unlikely(!hdr)) {\n            return_err(cur, MEMORY_ALLOCATION, MSG_MALLOC);\n        }\n        memcpy(hdr, dat, dat_len + 1);\n    }\n    hdr[dat_len] = 0;\n#endif\n\n#if YYJSON_DISABLE_FAST_FP_CONV\n    if (!read_num(&cur, pre, flg, val, &msg)) {\n        if (dat_len >= sizeof(buf)) alc->free(alc->ctx, hdr);\n        return_err(cur, INVALID_NUMBER, msg);\n    }\n    if (dat_len >= sizeof(buf)) alc->free(alc->ctx, hdr);\n    if (yyjson_is_raw(val)) val->uni.str = dat;\n    return dat + (cur - hdr);\n#else\n    if (!read_num(&cur, pre, flg, val, &msg)) {\n        return_err(cur, INVALID_NUMBER, msg);\n    }\n    return (const char *)cur;\n#endif\n\n#undef return_err\n}\n\n\n\n/*==============================================================================\n * MARK: - Incremental JSON Reader (Public)\n *============================================================================*/\n\n#if !YYJSON_DISABLE_INCR_READER\n\n/* labels within yyjson_incr_read() to resume incremental parsing */\n#define LABEL_doc_begin     0\n#define LABEL_arr_val_begin 1\n#define LABEL_arr_val_end   2\n#define LABEL_obj_key_begin 3\n#define LABEL_obj_key_end   4\n#define LABEL_obj_val_begin 5\n#define LABEL_obj_val_end   6\n#define LABEL_doc_end       7\n\n/** State for incremental JSON reader, opaque in the API. */\nstruct yyjson_incr_state {\n    u32 label; /* current parser goto label */\n    yyjson_alc alc; /* allocator */\n    yyjson_read_flag flg; /* read flags */\n    u8 *hdr; /* JSON data header */\n    u8 *cur; /* current position in JSON data */\n    usize buf_len; /* total buffer length (without padding) */\n    usize hdr_len; /* value count used by yyjson_doc */\n    usize alc_len; /* value count allocated */\n    usize ctn_len; /* the number of elements in current container */\n    yyjson_val *val_hdr; /* the head of allocated values */\n    yyjson_val *val_end; /* the end of allocated values */\n    yyjson_val *val; /* current JSON value */\n    yyjson_val *ctn; /* current container */\n    u8 *str_con[2]; /* string parser incremental state */\n};\n\nyyjson_incr_state *yyjson_incr_new(char *buf, size_t buf_len,\n                                   yyjson_read_flag flg,\n                                   const yyjson_alc *alc_ptr) {\n    yyjson_incr_state *state = NULL;\n    yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC;\n\n    /* remove non-standard flags */\n    flg &= ~YYJSON_READ_JSON5;\n    flg &= ~YYJSON_READ_ALLOW_BOM;\n    flg &= ~YYJSON_READ_ALLOW_INVALID_UNICODE;\n\n    if (unlikely(!buf)) return NULL;\n    if (unlikely(buf_len >= USIZE_MAX - YYJSON_PADDING_SIZE)) return NULL;\n    state = (yyjson_incr_state *)alc.malloc(alc.ctx, sizeof(*state));\n    if (!state) return NULL;\n    memset(state, 0, sizeof(yyjson_incr_state));\n    state->alc = alc;\n    state->flg = flg;\n    state->buf_len = buf_len;\n\n    /* add 4-byte zero padding for input data if necessary */\n    if (has_flg(INSITU)) {\n        state->hdr = (u8 *)buf;\n    } else {\n        state->hdr = (u8 *)alc.malloc(alc.ctx, buf_len + YYJSON_PADDING_SIZE);\n        if (unlikely(!state->hdr)) {\n            alc.free(alc.ctx, state);\n            return NULL;\n        }\n        memcpy(state->hdr, buf, buf_len);\n    }\n    memset(state->hdr + buf_len, 0, YYJSON_PADDING_SIZE);\n    state->cur = state->hdr;\n    state->label = LABEL_doc_begin;\n    return state;\n}\n\nvoid yyjson_incr_free(yyjson_incr_state *state) {\n    if (state) {\n        yyjson_alc alc = state->alc;\n        memset(&state->alc, 0, sizeof(alc));\n        if (state->val_hdr) {\n            alc.free(alc.ctx, (void *)state->val_hdr);\n        }\n        if (state->hdr && !(state->flg & YYJSON_READ_INSITU)) {\n            alc.free(alc.ctx, state->hdr);\n        }\n        alc.free(alc.ctx, state);\n    }\n}\n\nyyjson_doc *yyjson_incr_read(yyjson_incr_state *state, size_t len,\n                             yyjson_read_err *err) {\n#define return_err_inv_param(_msg) do { \\\n    err->pos = 0; \\\n    err->msg = _msg; \\\n    err->code = YYJSON_READ_ERROR_INVALID_PARAMETER; \\\n    return NULL; \\\n} while (false)\n\n#define return_err(_pos, _code, _msg) do { \\\n    if (is_truncated_end(hdr, _pos, end, YYJSON_READ_ERROR_##_code, flg)) { \\\n        goto unexpected_end; \\\n    } else { \\\n        err->pos = (usize)(_pos - hdr); \\\n        err->code = YYJSON_READ_ERROR_##_code; \\\n        err->msg = _msg; \\\n    } \\\n    return NULL; \\\n} while (false)\n\n#define val_incr() do { \\\n    val++; \\\n    if (unlikely(val >= val_end)) { \\\n        usize alc_old = alc_len; \\\n        alc_len += alc_len / 2; \\\n        if ((sizeof(usize) < 8) && (alc_len >= alc_max)) goto fail_alloc; \\\n        val_tmp = (yyjson_val *)alc.realloc(alc.ctx, (void *)val_hdr, \\\n                                            alc_old * sizeof(yyjson_val), \\\n                                            alc_len * sizeof(yyjson_val)); \\\n        if ((!val_tmp)) goto fail_alloc; \\\n        val = val_tmp + (usize)(val - val_hdr); \\\n        ctn = val_tmp + (usize)(ctn - val_hdr); \\\n        state->val = val_tmp + (usize)(state->val - val_hdr); \\\n        state->val_hdr = val_hdr = val_tmp; \\\n        val_end = val_tmp + (alc_len - 2); \\\n        state->val_end = val_end; \\\n    } \\\n} while (false)\n\n    /* save position where it's possible to resume incremental parsing */\n#define save_incr_state(_label) do { \\\n    state->label = LABEL_##_label; \\\n    state->cur = cur; \\\n    state->val = val; \\\n    state->ctn_len = ctn_len; \\\n    state->hdr_len = hdr_len; \\\n    if (unlikely(cur >= end)) goto unexpected_end; \\\n} while (false)\n\n#define check_maybe_truncated_number() do { \\\n    if (unlikely(cur >= end)) { \\\n        if (unlikely(cur > state->cur + INCR_NUM_MAX_LEN)) { \\\n            msg = \"number too long\"; \\\n            goto fail_number; \\\n        } \\\n        goto unexpected_end; \\\n    } \\\n} while (false)\n\n    u8 *hdr = NULL, *end = NULL, *cur = NULL;\n    yyjson_read_flag flg;\n    yyjson_alc alc;\n    usize dat_len; /* data length in bytes, hint for allocator */\n    usize hdr_len; /* value count used by yyjson_doc */\n    usize alc_len; /* value count allocated */\n    usize alc_max; /* maximum value count for allocator */\n    usize ctn_len; /* the number of elements in current container */\n    yyjson_val *val_hdr; /* the head of allocated values */\n    yyjson_val *val_end; /* the end of allocated values */\n    yyjson_val *val_tmp; /* temporary pointer for realloc */\n    yyjson_val *val; /* current JSON value */\n    yyjson_val *ctn; /* current container */\n    yyjson_val *ctn_parent; /* parent of current container */\n    yyjson_doc *doc; /* the JSON document, equals to val_hdr */\n    const char *msg; /* error message */\n\n    yyjson_read_err tmp_err;\n    u8 raw_end[1]; /* raw end for null-terminator */\n    u8 *raw_ptr = raw_end;\n    u8 **pre = &raw_ptr; /* previous raw end pointer */\n    u8 **con = NULL; /* for incremental string parsing */\n    u8 saved_end = '\\0'; /* saved end char */\n\n    /* validate input parameters */\n    if (!err) err = &tmp_err;\n    if (unlikely(!state)) {\n        return_err_inv_param(\"input state is NULL\");\n    }\n    if (unlikely(!len)) {\n        return_err_inv_param(\"input length is 0\");\n    }\n    if (unlikely(len > state->buf_len)) {\n        return_err_inv_param(\"length is greater than total input length\");\n    }\n\n    /* restore state saved from the previous call */\n    hdr = state->hdr;\n    end = state->hdr + len;\n    cur = state->cur;\n    flg = state->flg;\n    alc = state->alc;\n    ctn_len = state->ctn_len;\n    hdr_len = state->hdr_len;\n    alc_len = state->alc_len;\n    val = state->val;\n    val_hdr = state->val_hdr;\n    val_end = state->val_end;\n    ctn = state->ctn;\n    con = state->str_con;\n    alc_max = USIZE_MAX / sizeof(yyjson_val);\n\n    /* insert null terminator to make us stop at the specified end, even if\n       the data contains more valid JSON */\n    saved_end = *end;\n    *end = '\\0';\n\n    /* resume parsing from the last save point */\n    switch (state->label) {\n    case LABEL_doc_begin: goto doc_begin;\n    case LABEL_arr_val_begin: goto arr_val_begin;\n    case LABEL_arr_val_end: goto arr_val_end;\n    case LABEL_obj_key_begin: goto obj_key_begin;\n    case LABEL_obj_key_end: goto obj_key_end;\n    case LABEL_obj_val_begin: goto obj_val_begin;\n    case LABEL_obj_val_end: goto obj_val_end;\n    case LABEL_doc_end: goto doc_end;\n    default: return_err_inv_param(\"invalid incremental state\");\n    }\n\ndoc_begin:\n    /* skip empty contents before json document */\n    if (unlikely(!char_is_ctn(*cur))) {\n        while (char_is_space(*cur)) cur++;\n        if (unlikely(cur >= end)) goto unexpected_end; /* input data is empty */\n    }\n\n    /* allocate memory for document */\n    if (!val_hdr) {\n        hdr_len = sizeof(yyjson_doc) / sizeof(yyjson_val);\n        hdr_len += (sizeof(yyjson_doc) % sizeof(yyjson_val)) > 0;\n        if (likely(char_is_ctn(*cur))) {\n            dat_len = has_flg(STOP_WHEN_DONE) ? 256 : state->buf_len;\n            alc_len = hdr_len +\n                    (dat_len / YYJSON_READER_ESTIMATED_MINIFY_RATIO) + 4;\n            alc_len = yyjson_min(alc_len, alc_max);\n        } else {\n            alc_len = hdr_len + 1; /* single value */\n        }\n        val_hdr = (yyjson_val *)alc.malloc(alc.ctx,\n                                           alc_len * sizeof(yyjson_val));\n        if (unlikely(!val_hdr)) goto fail_alloc;\n        val_end = val_hdr + (alc_len - 2); /* padding for kv pair reading */\n        val = val_hdr + hdr_len;\n        ctn = val;\n        ctn_len = 0;\n        state->val_hdr = val_hdr;\n        state->val_end = val_end;\n        save_incr_state(doc_begin);\n    }\n\n    /* read json document */\n    if (*cur == '{') {\n        cur++;\n        ctn->tag = YYJSON_TYPE_OBJ;\n        ctn->uni.ofs = 0;\n        goto obj_key_begin;\n    }\n    if (*cur == '[') {\n        cur++;\n        ctn->tag = YYJSON_TYPE_ARR;\n        ctn->uni.ofs = 0;\n        goto arr_val_begin;\n    }\n    if (char_is_num(*cur)) {\n        if (likely(read_num(&cur, pre, flg, val, &msg))) goto doc_end;\n        goto fail_number;\n    }\n    if (*cur == '\"') {\n        if (likely(read_str_con(&cur, end, flg, val, &msg, con))) goto doc_end;\n        goto fail_string;\n    }\n    if (*cur == 't') {\n        if (likely(read_true(&cur, val))) goto doc_end;\n        goto fail_literal_true;\n    }\n    if (*cur == 'f') {\n        if (likely(read_false(&cur, val))) goto doc_end;\n        goto fail_literal_false;\n    }\n    if (*cur == 'n') {\n        if (likely(read_null(&cur, val))) goto doc_end;\n        goto fail_literal_null;\n    }\n\n    msg = \"unexpected character, expected a valid root value\";\n    if (cur == hdr) {\n        /* RFC 8259: JSON text MUST be encoded using UTF-8 */\n        if (is_utf8_bom(hdr)) msg = MSG_ERR_BOM;\n        else if (len >= 4 && is_utf32_bom(hdr)) msg = MSG_ERR_UTF32;\n        else if (len >= 2 && is_utf16_bom(hdr)) msg = MSG_ERR_UTF16;\n    }\n    return_err(cur, UNEXPECTED_CHARACTER, msg);\n\narr_begin:\n    /* save current container */\n    ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) |\n               (ctn->tag & YYJSON_TAG_MASK);\n\n    /* create a new array value, save parent container offset */\n    val_incr();\n    val->tag = YYJSON_TYPE_ARR;\n    val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn);\n\n    /* push the new array value as current container */\n    ctn = val;\n    ctn_len = 0;\n\narr_val_begin:\n    save_incr_state(arr_val_begin);\narr_val_continue:\n    if (*cur == '{') {\n        cur++;\n        goto obj_begin;\n    }\n    if (*cur == '[') {\n        cur++;\n        goto arr_begin;\n    }\n    if (char_is_num(*cur)) {\n        val_incr();\n        ctn_len++;\n        if (likely(read_num(&cur, pre, flg, val, &msg))) goto arr_val_maybe_end;\n        goto fail_number;\n    }\n    if (*cur == '\"') {\n        val_incr();\n        ctn_len++;\n        if (likely(read_str_con(&cur, end, flg, val, &msg, con)))\n            goto arr_val_end;\n        goto fail_string;\n    }\n    if (*cur == 't') {\n        val_incr();\n        ctn_len++;\n        if (likely(read_true(&cur, val))) goto arr_val_end;\n        goto fail_literal_true;\n    }\n    if (*cur == 'f') {\n        val_incr();\n        ctn_len++;\n        if (likely(read_false(&cur, val))) goto arr_val_end;\n        goto fail_literal_false;\n    }\n    if (*cur == 'n') {\n        val_incr();\n        ctn_len++;\n        if (likely(read_null(&cur, val))) goto arr_val_end;\n        goto fail_literal_null;\n    }\n    if (*cur == ']') {\n        cur++;\n        if (likely(ctn_len == 0)) goto arr_end;\n        while (*cur != ',') cur--;\n        goto fail_trailing_comma;\n    }\n    if (char_is_space(*cur)) {\n        while (char_is_space(*++cur));\n        goto arr_val_continue;\n    }\n    goto fail_character_val;\n\narr_val_maybe_end:\n    /* if incremental parsing stops in the middle of a number, it may continue\n       with more digits, so arr val maybe didn't end yet */\n    check_maybe_truncated_number();\n\narr_val_end:\n    save_incr_state(arr_val_end);\n    if (*cur == ',') {\n        cur++;\n        goto arr_val_begin;\n    }\n    if (*cur == ']') {\n        cur++;\n        goto arr_end;\n    }\n    if (char_is_space(*cur)) {\n        while (char_is_space(*++cur));\n        goto arr_val_end;\n    }\n    goto fail_character_arr_end;\n\narr_end:\n    /* get parent container */\n    ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs);\n\n    /* save the next sibling value offset */\n    ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val);\n    ctn->tag = ((ctn_len) << YYJSON_TAG_BIT) | YYJSON_TYPE_ARR;\n    if (unlikely(ctn == ctn_parent)) goto doc_end;\n\n    /* pop parent as current container */\n    ctn = ctn_parent;\n    ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT);\n    if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) {\n        goto obj_val_end;\n    } else {\n        goto arr_val_end;\n    }\n\nobj_begin:\n    /* push container */\n    ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) |\n               (ctn->tag & YYJSON_TAG_MASK);\n    val_incr();\n    val->tag = YYJSON_TYPE_OBJ;\n    /* offset to the parent */\n    val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn);\n    ctn = val;\n    ctn_len = 0;\n\nobj_key_begin:\n    save_incr_state(obj_key_begin);\nobj_key_continue:\n    if (likely(*cur == '\"')) {\n        val_incr();\n        ctn_len++;\n        if (likely(read_str_con(&cur, end, flg, val, &msg, con)))\n            goto obj_key_end;\n        goto fail_string;\n    }\n    if (likely(*cur == '}')) {\n        cur++;\n        if (likely(ctn_len == 0)) goto obj_end;\n        while (*cur != ',') cur--;\n        goto fail_trailing_comma;\n    }\n    if (char_is_space(*cur)) {\n        while (char_is_space(*++cur));\n        goto obj_key_continue;\n    }\n    goto fail_character_obj_key;\n\nobj_key_end:\n    save_incr_state(obj_key_end);\n    if (*cur == ':') {\n        cur++;\n        goto obj_val_begin;\n    }\n    if (char_is_space(*cur)) {\n        while (char_is_space(*++cur));\n        goto obj_key_end;\n    }\n    goto fail_character_obj_sep;\n\nobj_val_begin:\n    save_incr_state(obj_val_begin);\nobj_val_continue:\n    if (*cur == '\"') {\n        val++;\n        ctn_len++;\n        if (likely(read_str_con(&cur, end, flg, val, &msg, con)))\n            goto obj_val_end;\n        goto fail_string;\n    }\n    if (char_is_num(*cur)) {\n        val++;\n        ctn_len++;\n        if (likely(read_num(&cur, pre, flg, val, &msg))) goto obj_val_maybe_end;\n        goto fail_number;\n    }\n    if (*cur == '{') {\n        cur++;\n        goto obj_begin;\n    }\n    if (*cur == '[') {\n        cur++;\n        goto arr_begin;\n    }\n    if (*cur == 't') {\n        val++;\n        ctn_len++;\n        if (likely(read_true(&cur, val))) goto obj_val_end;\n        goto fail_literal_true;\n    }\n    if (*cur == 'f') {\n        val++;\n        ctn_len++;\n        if (likely(read_false(&cur, val))) goto obj_val_end;\n        goto fail_literal_false;\n    }\n    if (*cur == 'n') {\n        val++;\n        ctn_len++;\n        if (likely(read_null(&cur, val))) goto obj_val_end;\n        goto fail_literal_null;\n    }\n    if (char_is_space(*cur)) {\n        while (char_is_space(*++cur));\n        goto obj_val_continue;\n    }\n    goto fail_character_val;\n\nobj_val_maybe_end:\n    /* if incremental parsing stops in the middle of a number, it may continue\n       with more digits, so obj val maybe didn't end yet */\n    check_maybe_truncated_number();\n\nobj_val_end:\n    save_incr_state(obj_val_end);\n    if (likely(*cur == ',')) {\n        cur++;\n        goto obj_key_begin;\n    }\n    if (likely(*cur == '}')) {\n        cur++;\n        goto obj_end;\n    }\n    if (char_is_space(*cur)) {\n        while (char_is_space(*++cur));\n        goto obj_val_end;\n    }\n    goto fail_character_obj_end;\n\nobj_end:\n    /* pop container */\n    ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs);\n    /* point to the next value */\n    ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val);\n    ctn->tag = (ctn_len << (YYJSON_TAG_BIT - 1)) | YYJSON_TYPE_OBJ;\n    if (unlikely(ctn == ctn_parent)) goto doc_end;\n    ctn = ctn_parent;\n    ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT);\n    if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) {\n        goto obj_val_end;\n    } else {\n        goto arr_val_end;\n    }\n\ndoc_end:\n    /* check invalid contents after json document */\n    if (unlikely(cur < end) && !has_flg(STOP_WHEN_DONE)) {\n        save_incr_state(doc_end);\n        while (char_is_space(*cur)) cur++;\n        if (unlikely(cur < end)) goto fail_garbage;\n    }\n\n    **pre = '\\0';\n    doc = (yyjson_doc *)val_hdr;\n    doc->root = val_hdr + hdr_len;\n    doc->alc = alc;\n    doc->dat_read = (usize)(cur - hdr);\n    doc->val_read = (usize)((val - doc->root) + 1);\n    doc->str_pool = has_flg(INSITU) ? NULL : (char *)hdr;\n    state->hdr = NULL;\n    state->val_hdr = NULL;\n    memset(err, 0, sizeof(yyjson_read_err));\n    return doc;\n\nunexpected_end:\n    err->pos = len;\n    /* if no nore data, stop the incr read */\n    if (unlikely(len >= state->buf_len)) {\n        err->code = YYJSON_READ_ERROR_UNEXPECTED_END;\n        err->msg = MSG_NOT_END;\n        return NULL;\n    }\n    /* save parser state in extended error struct, in addition to what was\n     * stored in the last save_incr_state */\n    err->code = YYJSON_READ_ERROR_MORE;\n    err->msg = \"need more data\";\n    state->val_end = val_end;\n    state->ctn = ctn;\n    state->alc_len = alc_len;\n    /* restore the end where we've inserted a null terminator */\n    *end = saved_end;\n    return NULL;\n\nfail_string:            return_err(cur, INVALID_STRING, msg);\nfail_number:            return_err(cur, INVALID_NUMBER, msg);\nfail_alloc:             return_err(cur, MEMORY_ALLOCATION, MSG_MALLOC);\nfail_trailing_comma:    return_err(cur, JSON_STRUCTURE, MSG_COMMA);\nfail_literal_true:      return_err(cur, LITERAL, MSG_CHAR_T);\nfail_literal_false:     return_err(cur, LITERAL, MSG_CHAR_F);\nfail_literal_null:      return_err(cur, LITERAL, MSG_CHAR_N);\nfail_character_val:     return_err(cur, UNEXPECTED_CHARACTER, MSG_CHAR);\nfail_character_arr_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_ARR_END);\nfail_character_obj_key: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_KEY);\nfail_character_obj_sep: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_SEP);\nfail_character_obj_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_END);\nfail_garbage:           return_err(cur, UNEXPECTED_CONTENT, MSG_GARBAGE);\n\n#undef val_incr\n#undef return_err\n#undef return_err_inv_param\n#undef save_incr_state\n#undef check_maybe_truncated_number\n}\n\n#endif /* YYJSON_DISABLE_INCR_READER */\n\n#undef has_flg\n#undef has_allow\n#endif /* YYJSON_DISABLE_READER */\n\n\n\n#if !YYJSON_DISABLE_WRITER /* writer begin */\n\n/* Check write flag, avoids `always false` warning when disabled. */\n#define has_flg(_flg) unlikely(has_wflag(flg, YYJSON_WRITE_##_flg, 0))\n#define has_allow(_flg) unlikely(has_wflag(flg, YYJSON_WRITE_ALLOW_##_flg, 1))\nstatic_inline bool has_wflag(yyjson_write_flag flg, yyjson_write_flag chk,\n                             bool non_standard) {\n#if YYJSON_DISABLE_NON_STANDARD\n    if (non_standard) return false;\n#endif\n    return (flg & chk) != 0;\n}\n\n/*==============================================================================\n * MARK: - Integer Writer (Private)\n *\n * The maximum value of uint32_t is 4294967295 (10 digits),\n * these digits are named as 'aabbccddee' here.\n *\n * Although most compilers may convert the \"division by constant value\" into\n * \"multiply and shift\", manual conversion can still help some compilers\n * generate fewer and better instructions.\n *\n * Reference:\n * Division by Invariant Integers using Multiplication, 1994.\n * https://gmplib.org/~tege/divcnst-pldi94.pdf\n * Improved division by invariant integers, 2011.\n * https://gmplib.org/~tege/division-paper.pdf\n *============================================================================*/\n\n/** Digit table from 00 to 99. */\nyyjson_align(2)\nstatic const char digit_table[200] = {\n    '0', '0', '0', '1', '0', '2', '0', '3', '0', '4',\n    '0', '5', '0', '6', '0', '7', '0', '8', '0', '9',\n    '1', '0', '1', '1', '1', '2', '1', '3', '1', '4',\n    '1', '5', '1', '6', '1', '7', '1', '8', '1', '9',\n    '2', '0', '2', '1', '2', '2', '2', '3', '2', '4',\n    '2', '5', '2', '6', '2', '7', '2', '8', '2', '9',\n    '3', '0', '3', '1', '3', '2', '3', '3', '3', '4',\n    '3', '5', '3', '6', '3', '7', '3', '8', '3', '9',\n    '4', '0', '4', '1', '4', '2', '4', '3', '4', '4',\n    '4', '5', '4', '6', '4', '7', '4', '8', '4', '9',\n    '5', '0', '5', '1', '5', '2', '5', '3', '5', '4',\n    '5', '5', '5', '6', '5', '7', '5', '8', '5', '9',\n    '6', '0', '6', '1', '6', '2', '6', '3', '6', '4',\n    '6', '5', '6', '6', '6', '7', '6', '8', '6', '9',\n    '7', '0', '7', '1', '7', '2', '7', '3', '7', '4',\n    '7', '5', '7', '6', '7', '7', '7', '8', '7', '9',\n    '8', '0', '8', '1', '8', '2', '8', '3', '8', '4',\n    '8', '5', '8', '6', '8', '7', '8', '8', '8', '9',\n    '9', '0', '9', '1', '9', '2', '9', '3', '9', '4',\n    '9', '5', '9', '6', '9', '7', '9', '8', '9', '9'\n};\n\nstatic_inline u8 *write_u32_len_8(u32 val, u8 *buf) {\n    u32 aa, bb, cc, dd, aabb, ccdd;                 /* 8 digits: aabbccdd */\n    aabb = (u32)(((u64)val * 109951163) >> 40);     /* (val / 10000) */\n    ccdd = val - aabb * 10000;                      /* (val % 10000) */\n    aa = (aabb * 5243) >> 19;                       /* (aabb / 100) */\n    cc = (ccdd * 5243) >> 19;                       /* (ccdd / 100) */\n    bb = aabb - aa * 100;                           /* (aabb % 100) */\n    dd = ccdd - cc * 100;                           /* (ccdd % 100) */\n    byte_copy_2(buf + 0, digit_table + aa * 2);\n    byte_copy_2(buf + 2, digit_table + bb * 2);\n    byte_copy_2(buf + 4, digit_table + cc * 2);\n    byte_copy_2(buf + 6, digit_table + dd * 2);\n    return buf + 8;\n}\n\nstatic_inline u8 *write_u32_len_4(u32 val, u8 *buf) {\n    u32 aa, bb;                                     /* 4 digits: aabb */\n    aa = (val * 5243) >> 19;                        /* (val / 100) */\n    bb = val - aa * 100;                            /* (val % 100) */\n    byte_copy_2(buf + 0, digit_table + aa * 2);\n    byte_copy_2(buf + 2, digit_table + bb * 2);\n    return buf + 4;\n}\n\nstatic_inline u8 *write_u32_len_1_to_8(u32 val, u8 *buf) {\n    u32 aa, bb, cc, dd, aabb, bbcc, ccdd, lz;\n\n    if (val < 100) {                                /* 1-2 digits: aa */\n        lz = val < 10;                              /* leading zero: 0 or 1 */\n        byte_copy_2(buf + 0, digit_table + val * 2 + lz);\n        buf -= lz;\n        return buf + 2;\n\n    } else if (val < 10000) {                       /* 3-4 digits: aabb */\n        aa = (val * 5243) >> 19;                    /* (val / 100) */\n        bb = val - aa * 100;                        /* (val % 100) */\n        lz = aa < 10;                               /* leading zero: 0 or 1 */\n        byte_copy_2(buf + 0, digit_table + aa * 2 + lz);\n        buf -= lz;\n        byte_copy_2(buf + 2, digit_table + bb * 2);\n        return buf + 4;\n\n    } else if (val < 1000000) {                     /* 5-6 digits: aabbcc */\n        aa = (u32)(((u64)val * 429497) >> 32);      /* (val / 10000) */\n        bbcc = val - aa * 10000;                    /* (val % 10000) */\n        bb = (bbcc * 5243) >> 19;                   /* (bbcc / 100) */\n        cc = bbcc - bb * 100;                       /* (bbcc % 100) */\n        lz = aa < 10;                               /* leading zero: 0 or 1 */\n        byte_copy_2(buf + 0, digit_table + aa * 2 + lz);\n        buf -= lz;\n        byte_copy_2(buf + 2, digit_table + bb * 2);\n        byte_copy_2(buf + 4, digit_table + cc * 2);\n        return buf + 6;\n\n    } else {                                        /* 7-8 digits: aabbccdd */\n        aabb = (u32)(((u64)val * 109951163) >> 40); /* (val / 10000) */\n        ccdd = val - aabb * 10000;                  /* (val % 10000) */\n        aa = (aabb * 5243) >> 19;                   /* (aabb / 100) */\n        cc = (ccdd * 5243) >> 19;                   /* (ccdd / 100) */\n        bb = aabb - aa * 100;                       /* (aabb % 100) */\n        dd = ccdd - cc * 100;                       /* (ccdd % 100) */\n        lz = aa < 10;                               /* leading zero: 0 or 1 */\n        byte_copy_2(buf + 0, digit_table + aa * 2 + lz);\n        buf -= lz;\n        byte_copy_2(buf + 2, digit_table + bb * 2);\n        byte_copy_2(buf + 4, digit_table + cc * 2);\n        byte_copy_2(buf + 6, digit_table + dd * 2);\n        return buf + 8;\n    }\n}\n\nstatic_inline u8 *write_u32_len_5_to_8(u32 val, u8 *buf) {\n    u32 aa, bb, cc, dd, aabb, bbcc, ccdd, lz;\n\n    if (val < 1000000) {                            /* 5-6 digits: aabbcc */\n        aa = (u32)(((u64)val * 429497) >> 32);      /* (val / 10000) */\n        bbcc = val - aa * 10000;                    /* (val % 10000) */\n        bb = (bbcc * 5243) >> 19;                   /* (bbcc / 100) */\n        cc = bbcc - bb * 100;                       /* (bbcc % 100) */\n        lz = aa < 10;                               /* leading zero: 0 or 1 */\n        byte_copy_2(buf + 0, digit_table + aa * 2 + lz);\n        buf -= lz;\n        byte_copy_2(buf + 2, digit_table + bb * 2);\n        byte_copy_2(buf + 4, digit_table + cc * 2);\n        return buf + 6;\n\n    } else {                                        /* 7-8 digits: aabbccdd */\n        aabb = (u32)(((u64)val * 109951163) >> 40); /* (val / 10000) */\n        ccdd = val - aabb * 10000;                  /* (val % 10000) */\n        aa = (aabb * 5243) >> 19;                   /* (aabb / 100) */\n        cc = (ccdd * 5243) >> 19;                   /* (ccdd / 100) */\n        bb = aabb - aa * 100;                       /* (aabb % 100) */\n        dd = ccdd - cc * 100;                       /* (ccdd % 100) */\n        lz = aa < 10;                               /* leading zero: 0 or 1 */\n        byte_copy_2(buf + 0, digit_table + aa * 2 + lz);\n        buf -= lz;\n        byte_copy_2(buf + 2, digit_table + bb * 2);\n        byte_copy_2(buf + 4, digit_table + cc * 2);\n        byte_copy_2(buf + 6, digit_table + dd * 2);\n        return buf + 8;\n    }\n}\n\nstatic_inline u8 *write_u64(u64 val, u8 *buf) {\n    u64 tmp, hgh;\n    u32 mid, low;\n\n    if (val < 100000000) {                          /* 1-8 digits */\n        buf = write_u32_len_1_to_8((u32)val, buf);\n        return buf;\n\n    } else if (val < (u64)100000000 * 100000000) {  /* 9-16 digits */\n        hgh = val / 100000000;                      /* (val / 100000000) */\n        low = (u32)(val - hgh * 100000000);         /* (val % 100000000) */\n        buf = write_u32_len_1_to_8((u32)hgh, buf);\n        buf = write_u32_len_8(low, buf);\n        return buf;\n\n    } else {                                        /* 17-20 digits */\n        tmp = val / 100000000;                      /* (val / 100000000) */\n        low = (u32)(val - tmp * 100000000);         /* (val % 100000000) */\n        hgh = (u32)(tmp / 10000);                   /* (tmp / 10000) */\n        mid = (u32)(tmp - hgh * 10000);             /* (tmp % 10000) */\n        buf = write_u32_len_5_to_8((u32)hgh, buf);\n        buf = write_u32_len_4(mid, buf);\n        buf = write_u32_len_8(low, buf);\n        return buf;\n    }\n}\n\n\n\n/*==============================================================================\n * MARK: - Number Writer (Private)\n *============================================================================*/\n\n#if !YYJSON_DISABLE_FAST_FP_CONV  /* FP_WRITER */\n\n/** Trailing zero count table for number 0 to 99.\n    (generate with misc/make_tables.c) */\nstatic const u8 dec_trailing_zero_table[] = {\n    2, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    1, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    1, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    1, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    1, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    1, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    1, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    1, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    1, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    1, 0, 0, 0, 0, 0, 0, 0, 0, 0\n};\n\nstatic_inline u8 *write_u64_len_1_to_16(u64 val, u8 *buf) {\n    u64 hgh;\n    u32 low;\n    if (val < 100000000) {                          /* 1-8 digits */\n        buf = write_u32_len_1_to_8((u32)val, buf);\n        return buf;\n    } else {                                        /* 9-16 digits */\n        hgh = val / 100000000;                      /* (val / 100000000) */\n        low = (u32)(val - hgh * 100000000);         /* (val % 100000000) */\n        buf = write_u32_len_1_to_8((u32)hgh, buf);\n        buf = write_u32_len_8(low, buf);\n        return buf;\n    }\n}\n\nstatic_inline u8 *write_u64_len_1_to_17(u64 val, u8 *buf) {\n    u64 hgh;\n    u32 mid, low, one;\n    if (val >= (u64)100000000 * 10000000) {         /* len: 16 to 17 */\n        hgh = val / 100000000;                      /* (val / 100000000) */\n        low = (u32)(val - hgh * 100000000);         /* (val % 100000000) */\n        one = (u32)(hgh / 100000000);               /* (hgh / 100000000) */\n        mid = (u32)(hgh - (u64)one * 100000000);    /* (hgh % 100000000) */\n        *buf = (u8)((u8)one + (u8)'0');\n        buf += one > 0;\n        buf = write_u32_len_8(mid, buf);\n        buf = write_u32_len_8(low, buf);\n        return buf;\n    } else if (val >= (u64)100000000){              /* len: 9 to 15 */\n        hgh = val / 100000000;                      /* (val / 100000000) */\n        low = (u32)(val - hgh * 100000000);         /* (val % 100000000) */\n        buf = write_u32_len_1_to_8((u32)hgh, buf);\n        buf = write_u32_len_8(low, buf);\n        return buf;\n    } else {                                        /* len: 1 to 8 */\n        buf = write_u32_len_1_to_8((u32)val, buf);\n        return buf;\n    }\n}\n\n/**\n Write an unsigned integer with a length of 7 to 9 with trailing zero trimmed.\n These digits are named as \"abbccddee\" here.\n For example, input 123456000, output \"123456\".\n */\nstatic_inline u8 *write_u32_len_7_to_9_trim(u32 val, u8 *buf) {\n    bool lz;\n    u32 tz, tz1, tz2;\n\n    u32 abbcc = val / 10000;                        /* (abbccddee / 10000) */\n    u32 ddee = val - abbcc * 10000;                 /* (abbccddee % 10000) */\n    u32 abb = (u32)(((u64)abbcc * 167773) >> 24);   /* (abbcc / 100) */\n    u32 a = (abb * 41) >> 12;                       /* (abb / 100) */\n    u32 bb = abb - a * 100;                         /* (abb % 100) */\n    u32 cc = abbcc - abb * 100;                     /* (abbcc % 100) */\n\n    /* write abbcc */\n    buf[0] = (u8)(a + '0');\n    buf += a > 0;\n    lz = bb < 10 && a == 0;\n    byte_copy_2(buf + 0, digit_table + bb * 2 + lz);\n    buf -= lz;\n    byte_copy_2(buf + 2, digit_table + cc * 2);\n\n    if (ddee) {\n        u32 dd = (ddee * 5243) >> 19;               /* (ddee / 100) */\n        u32 ee = ddee - dd * 100;                   /* (ddee % 100) */\n        byte_copy_2(buf + 4, digit_table + dd * 2);\n        byte_copy_2(buf + 6, digit_table + ee * 2);\n        tz1 = dec_trailing_zero_table[dd];\n        tz2 = dec_trailing_zero_table[ee];\n        tz = ee ? tz2 : (tz1 + 2);\n        buf += 8 - tz;\n        return buf;\n    } else {\n        tz1 = dec_trailing_zero_table[bb];\n        tz2 = dec_trailing_zero_table[cc];\n        tz = cc ? tz2 : (tz1 + tz2);\n        buf += 4 - tz;\n        return buf;\n    }\n}\n\n/**\n Write an unsigned integer with a length of 16 or 17 with trailing zero trimmed.\n These digits are named as \"abbccddeeffgghhii\" here.\n For example, input 1234567890123000, output \"1234567890123\".\n */\nstatic_inline u8 *write_u64_len_16_to_17_trim(u64 val, u8 *buf) {\n    u32 tz, tz1, tz2;\n\n    u32 abbccddee = (u32)(val / 100000000);\n    u32 ffgghhii = (u32)(val - (u64)abbccddee * 100000000);\n    u32 abbcc = abbccddee / 10000;\n    u32 ddee = abbccddee - abbcc * 10000;\n    u32 abb = (u32)(((u64)abbcc * 167773) >> 24);   /* (abbcc / 100) */\n    u32 a = (abb * 41) >> 12;                       /* (abb / 100) */\n    u32 bb = abb - a * 100;                         /* (abb % 100) */\n    u32 cc = abbcc - abb * 100;                     /* (abbcc % 100) */\n    buf[0] = (u8)(a + '0');\n    buf += a > 0;\n    byte_copy_2(buf + 0, digit_table + bb * 2);\n    byte_copy_2(buf + 2, digit_table + cc * 2);\n\n    if (ffgghhii) {\n        u32 dd = (ddee * 5243) >> 19;               /* (ddee / 100) */\n        u32 ee = ddee - dd * 100;                   /* (ddee % 100) */\n        u32 ffgg = (u32)(((u64)ffgghhii * 109951163) >> 40); /* (val / 10000) */\n        u32 hhii = ffgghhii - ffgg * 10000;         /* (val % 10000) */\n        u32 ff = (ffgg * 5243) >> 19;               /* (aabb / 100) */\n        u32 gg = ffgg - ff * 100;                   /* (aabb % 100) */\n        byte_copy_2(buf + 4, digit_table + dd * 2);\n        byte_copy_2(buf + 6, digit_table + ee * 2);\n        byte_copy_2(buf + 8, digit_table + ff * 2);\n        byte_copy_2(buf + 10, digit_table + gg * 2);\n        if (hhii) {\n            u32 hh = (hhii * 5243) >> 19;           /* (ccdd / 100) */\n            u32 ii = hhii - hh * 100;               /* (ccdd % 100) */\n            byte_copy_2(buf + 12, digit_table + hh * 2);\n            byte_copy_2(buf + 14, digit_table + ii * 2);\n            tz1 = dec_trailing_zero_table[hh];\n            tz2 = dec_trailing_zero_table[ii];\n            tz = ii ? tz2 : (tz1 + 2);\n            return buf + 16 - tz;\n        } else {\n            tz1 = dec_trailing_zero_table[ff];\n            tz2 = dec_trailing_zero_table[gg];\n            tz = gg ? tz2 : (tz1 + 2);\n            return buf + 12 - tz;\n        }\n    } else {\n        if (ddee) {\n            u32 dd = (ddee * 5243) >> 19;           /* (ddee / 100) */\n            u32 ee = ddee - dd * 100;               /* (ddee % 100) */\n            byte_copy_2(buf + 4, digit_table + dd * 2);\n            byte_copy_2(buf + 6, digit_table + ee * 2);\n            tz1 = dec_trailing_zero_table[dd];\n            tz2 = dec_trailing_zero_table[ee];\n            tz = ee ? tz2 : (tz1 + 2);\n            return buf + 8 - tz;\n        } else {\n            tz1 = dec_trailing_zero_table[bb];\n            tz2 = dec_trailing_zero_table[cc];\n            tz = cc ? tz2 : (tz1 + tz2);\n            return buf + 4 - tz;\n        }\n    }\n}\n\n/** Write exponent part in range `e-45` to `e38`. */\nstatic_inline u8 *write_f32_exp(i32 exp, u8 *buf) {\n    bool lz;\n    byte_copy_2(buf, \"e-\");\n    buf += 2 - (exp >= 0);\n    exp = exp < 0 ? -exp : exp;\n    lz = exp < 10;\n    byte_copy_2(buf + 0, digit_table + (u32)exp * 2 + lz);\n    return buf + 2 - lz;\n}\n\n/** Write exponent part in range `e-324` to `e308`. */\nstatic_inline u8 *write_f64_exp(i32 exp, u8 *buf) {\n    byte_copy_2(buf, \"e-\");\n    buf += 2 - (exp >= 0);\n    exp = exp < 0 ? -exp : exp;\n    if (exp < 100) {\n        bool lz = exp < 10;\n        byte_copy_2(buf + 0, digit_table + (u32)exp * 2 + lz);\n        return buf + 2 - lz;\n    } else {\n        u32 hi = ((u32)exp * 656) >> 16;    /* exp / 100 */\n        u32 lo = (u32)exp - hi * 100;       /* exp % 100 */\n        buf[0] = (u8)((u8)hi + (u8)'0');\n        byte_copy_2(buf + 1, digit_table + lo * 2);\n        return buf + 3;\n    }\n}\n\n/** Magic number for fast `divide by power of 10`. */\ntypedef struct {\n    u64 p10, mul;\n    u32 shr1, shr2;\n} div_pow10_magic;\n\n/** Generated with llvm, see https://github.com/llvm/llvm-project/\n    blob/main/llvm/lib/Support/DivisionByConstantInfo.cpp */\nstatic const div_pow10_magic div_pow10_table[] = {\n    { U64(0x00000000, 0x00000001), U64(0x00000000, 0x00000000), 0,  0  },\n    { U64(0x00000000, 0x0000000A), U64(0xCCCCCCCC, 0xCCCCCCCD), 0,  3  },\n    { U64(0x00000000, 0x00000064), U64(0x28F5C28F, 0x5C28F5C3), 2,  2  },\n    { U64(0x00000000, 0x000003E8), U64(0x20C49BA5, 0xE353F7CF), 3,  4  },\n    { U64(0x00000000, 0x00002710), U64(0x346DC5D6, 0x3886594B), 0,  11 },\n    { U64(0x00000000, 0x000186A0), U64(0x0A7C5AC4, 0x71B47843), 5,  7  },\n    { U64(0x00000000, 0x000F4240), U64(0x431BDE82, 0xD7B634DB), 0,  18 },\n    { U64(0x00000000, 0x00989680), U64(0xD6BF94D5, 0xE57A42BD), 0,  23 },\n    { U64(0x00000000, 0x05F5E100), U64(0xABCC7711, 0x8461CEFD), 0,  26 },\n    { U64(0x00000000, 0x3B9ACA00), U64(0x0044B82F, 0xA09B5A53), 9,  11 },\n    { U64(0x00000002, 0x540BE400), U64(0xDBE6FECE, 0xBDEDD5BF), 0,  33 },\n    { U64(0x00000017, 0x4876E800), U64(0xAFEBFF0B, 0xCB24AAFF), 0,  36 },\n    { U64(0x000000E8, 0xD4A51000), U64(0x232F3302, 0x5BD42233), 0,  37 },\n    { U64(0x00000918, 0x4E72A000), U64(0x384B84D0, 0x92ED0385), 0,  41 },\n    { U64(0x00005AF3, 0x107A4000), U64(0x0B424DC3, 0x5095CD81), 0,  42 },\n    { U64(0x00038D7E, 0xA4C68000), U64(0x00024075, 0xF3DCEAC3), 15, 20 },\n    { U64(0x002386F2, 0x6FC10000), U64(0x39A5652F, 0xB1137857), 0,  51 },\n    { U64(0x01634578, 0x5D8A0000), U64(0x00005C3B, 0xD5191B53), 17, 22 },\n    { U64(0x0DE0B6B3, 0xA7640000), U64(0x000049C9, 0x7747490F), 18, 24 },\n    { U64(0x8AC72304, 0x89E80000), U64(0x760F253E, 0xDB4AB0d3), 0,  62 },\n};\n\n/** Divide a number by power of 10. */\nstatic_inline void div_pow10(u64 num, u32 exp, u64 *div, u64 *mod, u64 *p10) {\n    u64 hi, lo;\n    div_pow10_magic m = div_pow10_table[exp];\n    u128_mul(num >> m.shr1, m.mul, &hi, &lo);\n    *div = hi >> m.shr2;\n    *mod = num - (*div * m.p10);\n    *p10 = m.p10;\n}\n\n/** Multiplies 64-bit integer and returns highest 64-bit rounded value. */\nstatic_inline u32 u64_round_to_odd(u64 u, u32 cp) {\n    u64 hi, lo;\n    u32 y_hi, y_lo;\n    u128_mul(cp, u, &hi, &lo);\n    y_hi = (u32)hi;\n    y_lo = (u32)(lo >> 32);\n    return y_hi | (y_lo > 1);\n}\n\n/** Multiplies 128-bit integer and returns highest 64-bit rounded value. */\nstatic_inline u64 u128_round_to_odd(u64 hi, u64 lo, u64 cp) {\n    u64 x_hi, x_lo, y_hi, y_lo;\n    u128_mul(cp, lo, &x_hi, &x_lo);\n    u128_mul_add(cp, hi, x_hi, &y_hi, &y_lo);\n    return y_hi | (y_lo > 1);\n}\n\n/** Convert f32 from binary to decimal (shortest but may have trailing zeros).\n    The input should not be 0, inf or nan. */\nstatic_inline void f32_bin_to_dec(u32 sig_raw, u32 exp_raw,\n                                  u32 sig_bin, i32 exp_bin,\n                                  u32 *sig_dec, i32 *exp_dec) {\n\n    bool is_even, irregular, round_up, trim;\n    bool u0_inside, u1_inside, w0_inside, w1_inside;\n    u64 p10_hi, p10_lo, hi, lo;\n    u32 s, sp, cb, cbl, cbr, vb, vbl, vbr, upper, lower, mid;\n    i32 k, h;\n\n    /* Fast path, see f64_bin_to_dec(). */\n    while (likely(sig_raw)) {\n        u32 mod, dec, add_1, add_10, s_hi, s_lo;\n        u32 c, half_ulp, t0, t1;\n\n        /* k = floor(exp_bin * log10(2)); */\n        /* h = exp_bin + floor(log2(10) * -k); (h = 0/1/2/3) */\n        k = (i32)(exp_bin * 315653) >> 20;\n        h = exp_bin + ((-k * 217707) >> 16);\n        pow10_table_get_sig(-k, &p10_hi, &p10_lo);\n\n        /* sig_bin << (1/2/3/4) */\n        cb = sig_bin << (h + 1);\n        u128_mul(cb, p10_hi, &hi, &lo);\n        s_hi = (u32)(hi);\n        s_lo = (u32)(lo >> 32);\n        mod = s_hi % 10;\n        dec = s_hi - mod;\n\n        /* right shift 4 to fit in u32 */\n        c = (mod << (32 - 4)) | (s_lo >> 4);\n        half_ulp = (u32)(p10_hi >> (32 + 4 - h));\n\n        /* check w1, u0, w0 range */\n        w1_inside = (s_lo >= ((u32)1 << 31));\n        if (unlikely(s_lo == ((u32)1 << 31))) break;\n        u0_inside = (half_ulp >= c);\n        if (unlikely(half_ulp == c)) break;\n        t0 = (u32)10 << (32 - 4);\n        t1 = c + half_ulp;\n        w0_inside = (t1 >= t0);\n        if (unlikely(t0 - t1 <= (u32)1)) break;\n\n        trim = (u0_inside | w0_inside);\n        add_10 = (w0_inside ? 10 : 0);\n        add_1 = mod + w1_inside;\n        *sig_dec = dec + (trim ? add_10 : add_1);\n        *exp_dec = k;\n        return;\n    }\n\n    /* Schubfach algorithm, see f64_bin_to_dec(). */\n    irregular = (sig_raw == 0 && exp_raw > 1);\n    is_even = !(sig_bin & 1);\n    cbl = 4 * sig_bin - 2 + irregular;\n    cb  = 4 * sig_bin;\n    cbr = 4 * sig_bin + 2;\n\n    /* k = floor(exp_bin * log10(2) + (irregular ? log10(3.0 / 4.0) : 0)); */\n    /* h = exp_bin + floor(log2(10) * -k) + 1; (h = 1/2/3/4) */\n    k = (i32)(exp_bin * 315653 - (irregular ? 131237 : 0)) >> 20;\n    h = exp_bin + ((-k * 217707) >> 16) + 1;\n    pow10_table_get_sig(-k, &p10_hi, &p10_lo);\n    p10_hi += 1;\n\n    vbl = u64_round_to_odd(p10_hi, cbl << h);\n    vb  = u64_round_to_odd(p10_hi, cb  << h);\n    vbr = u64_round_to_odd(p10_hi, cbr << h);\n    lower = vbl + !is_even;\n    upper = vbr - !is_even;\n\n    s = vb / 4;\n    if (s >= 10) {\n        sp = s / 10;\n        u0_inside = (lower <= 40 * sp);\n        w0_inside = (upper >= 40 * sp + 40);\n        if (u0_inside != w0_inside) {\n            *sig_dec = sp * 10 + (w0_inside ? 10 : 0);\n            *exp_dec = k;\n            return;\n        }\n    }\n    u1_inside = (lower <= 4 * s);\n    w1_inside = (upper >= 4 * s + 4);\n    mid = 4 * s + 2;\n    round_up = (vb > mid) || (vb == mid && (s & 1) != 0);\n    *sig_dec = s + ((u1_inside != w1_inside) ? w1_inside : round_up);\n    *exp_dec = k;\n}\n\n/** Convert f64 from binary to decimal (shortest but may have trailing zeros).\n    The input should not be 0, inf or nan. */\nstatic_inline void f64_bin_to_dec(u64 sig_raw, u32 exp_raw,\n                                  u64 sig_bin, i32 exp_bin,\n                                  u64 *sig_dec, i32 *exp_dec) {\n\n    bool is_even, irregular, round_up, trim;\n    bool u0_inside, u1_inside, w0_inside, w1_inside;\n    u64 s, sp, cb, cbl, cbr, vb, vbl, vbr, p10_hi, p10_lo, upper, lower, mid;\n    i32 k, h;\n\n    /*\n     Fast path:\n     For regular spacing significand 'c', there are 4 candidates:\n\n             u0             u1 c  w1                            w0\n     ----|----|----|----|----|-*--|----|----|----|----|----|----|----|----\n         9    0    1    2    3    4    5    6    7    8    9    0    1\n           |___________________|___________________|\n                             1ulp\n\n     The `1ulp` is in the range [1.0, 10.0).\n     If (c - 0.5ulp < u0), trim the last digit and round down.\n     If (c + 0.5ulp > w0), trim the last digit and round up.\n     If (c - 0.5ulp < u1), round down.\n     If (c + 0.5ulp > w1), round up.\n     */\n    while (likely(sig_raw)) {\n        u64 mod, dec, add_1, add_10, s_hi, s_lo;\n        u64 c, half_ulp, t0, t1;\n\n        /* k = floor(exp_bin * log10(2)); */\n        /* h = exp_bin + floor(log2(10) * -k); (h = 0/1/2/3) */\n        k = (i32)(exp_bin * 315653) >> 20;\n        h = exp_bin + ((-k * 217707) >> 16);\n        pow10_table_get_sig(-k, &p10_hi, &p10_lo);\n\n        /* sig_bin << (1/2/3/4) */\n        cb = sig_bin << (h + 1);\n        u128_mul(cb, p10_lo, &s_hi, &s_lo);\n        u128_mul_add(cb, p10_hi, s_hi, &s_hi, &s_lo);\n        mod = s_hi % 10;\n        dec = s_hi - mod;\n\n        /* right shift 4 to fit in u64 */\n        c = (mod << (64 - 4)) | (s_lo >> 4);\n        half_ulp = p10_hi >> (4 - h);\n\n        /* check w1, u0, w0 range */\n        w1_inside = (s_lo >= ((u64)1 << 63));\n        if (unlikely(s_lo == ((u64)1 << 63))) break;\n        u0_inside = (half_ulp >= c);\n        if (unlikely(half_ulp == c)) break;\n        t0 = ((u64)10 << (64 - 4));\n        t1 = c + half_ulp;\n        w0_inside = (t1 >= t0);\n        if (unlikely(t0 - t1 <= (u64)1)) break;\n\n        trim = (u0_inside | w0_inside);\n        add_10 = (w0_inside ? 10 : 0);\n        add_1 = mod + w1_inside;\n        *sig_dec = dec + (trim ? add_10 : add_1);\n        *exp_dec = k;\n        return;\n    }\n\n    /*\n     Schubfach algorithm:\n     Raffaello Giulietti, The Schubfach way to render doubles, 2022.\n     https://drive.google.com/file/d/1gp5xv4CAa78SVgCeWfGqqI4FfYYYuNFb (Paper)\n     https://github.com/openjdk/jdk/pull/3402 (Java implementation)\n     https://github.com/abolz/Drachennest (C++ implementation)\n     */\n    irregular = (sig_raw == 0 && exp_raw > 1);\n    is_even = !(sig_bin & 1);\n    cbl = 4 * sig_bin - 2 + irregular;\n    cb  = 4 * sig_bin;\n    cbr = 4 * sig_bin + 2;\n\n    /* k = floor(exp_bin * log10(2) + (irregular ? log10(3.0 / 4.0) : 0)); */\n    /* h = exp_bin + floor(log2(10) * -k) + 1; (h = 1/2/3/4) */\n    k = (i32)(exp_bin * 315653 - (irregular ? 131237 : 0)) >> 20;\n    h = exp_bin + ((-k * 217707) >> 16) + 1;\n    pow10_table_get_sig(-k, &p10_hi, &p10_lo);\n    p10_lo += 1;\n\n    vbl = u128_round_to_odd(p10_hi, p10_lo, cbl << h);\n    vb  = u128_round_to_odd(p10_hi, p10_lo, cb  << h);\n    vbr = u128_round_to_odd(p10_hi, p10_lo, cbr << h);\n    lower = vbl + !is_even;\n    upper = vbr - !is_even;\n\n    s = vb / 4;\n    if (s >= 10) {\n        sp = s / 10;\n        u0_inside = (lower <= 40 * sp);\n        w0_inside = (upper >= 40 * sp + 40);\n        if (u0_inside != w0_inside) {\n            *sig_dec = sp * 10 + (w0_inside ? 10 : 0);\n            *exp_dec = k;\n            return;\n        }\n    }\n    u1_inside = (lower <= 4 * s);\n    w1_inside = (upper >= 4 * s + 4);\n    mid = 4 * s + 2;\n    round_up = (vb > mid) || (vb == mid && (s & 1) != 0);\n    *sig_dec = s + ((u1_inside != w1_inside) ? w1_inside : round_up);\n    *exp_dec = k;\n}\n\n/** Convert f64 from binary to decimal (fast but not the shortest).\n    The input should not be 0, inf, nan. */\nstatic_inline void f64_bin_to_dec_fast(u64 sig_raw, u32 exp_raw,\n                                       u64 sig_bin, i32 exp_bin,\n                                       u64 *sig_dec, i32 *exp_dec,\n                                       bool *round_up) {\n    u64 cb, p10_hi, p10_lo, s_hi, s_lo;\n    i32 k, h;\n    bool irregular, u;\n\n    irregular = (sig_raw == 0 && exp_raw > 1);\n\n    /* k = floor(exp_bin * log10(2) + (irregular ? log10(3.0 / 4.0) : 0)); */\n    /* h = exp_bin + floor(log2(10) * -k) + 1; (h = 1/2/3/4) */\n    k = (i32)(exp_bin * 315653 - (irregular ? 131237 : 0)) >> 20;\n    h = exp_bin + ((-k * 217707) >> 16);\n    pow10_table_get_sig(-k, &p10_hi, &p10_lo);\n\n    /* sig_bin << (1/2/3/4) */\n    cb = sig_bin << (h + 1);\n    u128_mul(cb, p10_lo, &s_hi, &s_lo);\n    u128_mul_add(cb, p10_hi, s_hi, &s_hi, &s_lo);\n\n    /* round up */\n    u = s_lo >= (irregular ? U64(0x55555555, 0x55555555) : ((u64)1 << 63));\n\n    *sig_dec = s_hi + u;\n    *exp_dec = k;\n    *round_up = u;\n    return;\n}\n\n/** Write inf/nan if allowed. */\nstatic_inline u8 *write_inf_or_nan(u8 *buf, yyjson_write_flag flg,\n                                   u64 sig_raw, bool sign) {\n    if (has_flg(INF_AND_NAN_AS_NULL)) {\n        byte_copy_4(buf, \"null\");\n        return buf + 4;\n    }\n    if (has_allow(INF_AND_NAN)) {\n        if (sig_raw == 0) {\n            buf[0] = '-';\n            buf += sign;\n            byte_copy_8(buf, \"Infinity\");\n            return buf + 8;\n        } else {\n            byte_copy_4(buf, \"NaN\");\n            return buf + 3;\n        }\n    }\n    return NULL;\n}\n\n/**\n Write a float number (requires 40 bytes buffer).\n We follow the ECMAScript specification for printing floating-point numbers,\n similar to `Number.prototype.toString()`, but with the following changes:\n 1. Keep the negative sign of `-0.0` to preserve input information.\n 2. Keep decimal point to indicate the number is floating point.\n 3. Remove positive sign in the exponent part.\n */\nstatic_noinline u8 *write_f32_raw(u8 *buf, u64 raw_f64,\n                                  yyjson_write_flag flg) {\n    u32 sig_bin, sig_dec, sig_raw;\n    i32 exp_bin, exp_dec, sig_len, dot_ofs;\n    u32 exp_raw, raw;\n    u8 *end;\n    bool sign;\n\n    /* cast double to float */\n    raw = f32_to_bits(f64_to_f32(f64_from_bits(raw_f64)));\n\n    /* decode raw bytes from IEEE-754 double format. */\n    sign = (bool)(raw >> (F32_BITS - 1));\n    sig_raw = raw & F32_SIG_MASK;\n    exp_raw = (raw & F32_EXP_MASK) >> F32_SIG_BITS;\n\n    /* return inf or nan */\n    if (unlikely(exp_raw == ((u32)1 << F32_EXP_BITS) - 1)) {\n        return write_inf_or_nan(buf, flg, sig_raw, sign);\n    }\n\n    /* add sign for all finite number */\n    buf[0] = '-';\n    buf += sign;\n\n    /* return zero */\n    if ((raw << 1) == 0) {\n        byte_copy_4(buf, \"0.0\");\n        return buf + 3;\n    }\n\n    if (likely(exp_raw != 0)) {\n        /* normal number */\n        sig_bin = sig_raw | ((u32)1 << F32_SIG_BITS);\n        exp_bin = (i32)exp_raw - F32_EXP_BIAS - F32_SIG_BITS;\n\n        /* fast path for small integer number without fraction */\n        if ((-F32_SIG_BITS <= exp_bin && exp_bin <= 0) &&\n            (u64_tz_bits(sig_bin) >= (u32)-exp_bin)) {\n            sig_dec = sig_bin >> -exp_bin; /* range: [1, 0xFFFFFF] */\n            buf = write_u32_len_1_to_8(sig_dec, buf);\n            byte_copy_2(buf, \".0\");\n            return buf + 2;\n        }\n\n        /* binary to decimal */\n        f32_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec);\n\n        /* the sig length is 7 or 9 */\n        sig_len = 7 + (sig_dec >= (u32)10000000) + (sig_dec >= (u32)100000000);\n\n        /* the decimal point offset relative to the first digit */\n        dot_ofs = sig_len + exp_dec;\n\n        if (-6 < dot_ofs && dot_ofs <= 21) {\n            i32 num_sep_pos, dot_set_pos, pre_ofs;\n            u8 *num_hdr, *num_end, *num_sep, *dot_end;\n            bool no_pre_zero;\n\n            /* fill zeros */\n            memset(buf, '0', 32);\n\n            /* not prefixed with zero, e.g. 1.234, 1234.0 */\n            no_pre_zero = (dot_ofs > 0);\n\n            /* write the number as digits */\n            pre_ofs = no_pre_zero ? 0 : (2 - dot_ofs);\n            num_hdr = buf + pre_ofs;\n            num_end = write_u32_len_7_to_9_trim(sig_dec, num_hdr);\n\n            /* seperate these digits to leave a space for dot */\n            num_sep_pos = no_pre_zero ? dot_ofs : 0;\n            num_sep = num_hdr + num_sep_pos;\n            byte_move_8(num_sep + no_pre_zero, num_sep);\n            num_end += no_pre_zero;\n\n            /* write the dot */\n            dot_set_pos = yyjson_max(dot_ofs, 1);\n            buf[dot_set_pos] = '.';\n\n            /* return the ending */\n            dot_end = buf + dot_ofs + 2;\n            return yyjson_max(dot_end, num_end);\n\n        } else {\n            /* write with scientific notation, e.g. 1.234e56 */\n            end = write_u32_len_7_to_9_trim(sig_dec, buf + 1);\n            end -= (end == buf + 2); /* remove '.0', e.g. 2.0e34 -> 2e34 */\n            exp_dec += sig_len - 1;\n            buf[0] = buf[1];\n            buf[1] = '.';\n            return write_f32_exp(exp_dec, end);\n        }\n\n    } else {\n        /* subnormal number */\n        sig_bin = sig_raw;\n        exp_bin = 1 - F32_EXP_BIAS - F32_SIG_BITS;\n\n        /* binary to decimal */\n        f32_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec);\n\n        /* write significand part */\n        end = write_u32_len_1_to_8(sig_dec, buf + 1);\n        buf[0] = buf[1];\n        buf[1] = '.';\n        exp_dec += (i32)(end - buf) - 2;\n\n        /* trim trailing zeros */\n        end -= *(end - 1) == '0'; /* branchless for last zero */\n        end -= *(end - 1) == '0'; /* branchless for second last zero */\n        while (*(end - 1) == '0') end--; /* for unlikely more zeros */\n        end -= *(end - 1) == '.'; /* remove dot, e.g. 2.e-321 -> 2e-321 */\n\n        /* write exponent part */\n        return write_f32_exp(exp_dec, end);\n    }\n}\n\n/**\n Write a double number (requires 40 bytes buffer).\n We follow the ECMAScript specification for printing floating-point numbers,\n similar to `Number.prototype.toString()`, but with the following changes:\n 1. Keep the negative sign of `-0.0` to preserve input information.\n 2. Keep decimal point to indicate the number is floating point.\n 3. Remove positive sign in the exponent part.\n */\nstatic_noinline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) {\n    u64 sig_bin, sig_dec, sig_raw;\n    i32 exp_bin, exp_dec, sig_len, dot_ofs;\n    u32 exp_raw;\n    u8 *end;\n    bool sign;\n\n    /* decode raw bytes from IEEE-754 double format. */\n    sign = (bool)(raw >> (F64_BITS - 1));\n    sig_raw = raw & F64_SIG_MASK;\n    exp_raw = (u32)((raw & F64_EXP_MASK) >> F64_SIG_BITS);\n\n    /* return inf or nan */\n    if (unlikely(exp_raw == ((u32)1 << F64_EXP_BITS) - 1)) {\n        return write_inf_or_nan(buf, flg, sig_raw, sign);\n    }\n\n    /* add sign for all finite number */\n    buf[0] = '-';\n    buf += sign;\n\n    /* return zero */\n    if ((raw << 1) == 0) {\n        byte_copy_4(buf, \"0.0\");\n        return buf + 3;\n    }\n\n    if (likely(exp_raw != 0)) {\n        /* normal number */\n        sig_bin = sig_raw | ((u64)1 << F64_SIG_BITS);\n        exp_bin = (i32)exp_raw - F64_EXP_BIAS - F64_SIG_BITS;\n\n        /* fast path for small integer number without fraction */\n        if ((-F64_SIG_BITS <= exp_bin && exp_bin <= 0) &&\n            (u64_tz_bits(sig_bin) >= (u32)-exp_bin)) {\n            sig_dec = sig_bin >> -exp_bin; /* range: [1, 0x1FFFFFFFFFFFFF] */\n            buf = write_u64_len_1_to_16(sig_dec, buf);\n            byte_copy_2(buf, \".0\");\n            return buf + 2;\n        }\n\n        /* binary to decimal */\n        f64_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec);\n\n        /* the sig length is 16 or 17 */\n        sig_len = 16 + (sig_dec >= (u64)100000000 * 100000000);\n\n        /* the decimal point offset relative to the first digit */\n        dot_ofs = sig_len + exp_dec;\n\n        if (-6 < dot_ofs && dot_ofs <= 21) {\n            i32 num_sep_pos, dot_set_pos, pre_ofs;\n            u8 *num_hdr, *num_end, *num_sep, *dot_end;\n            bool no_pre_zero;\n\n            /* fill zeros */\n            memset(buf, '0', 32);\n\n            /* not prefixed with zero, e.g. 1.234, 1234.0 */\n            no_pre_zero = (dot_ofs > 0);\n\n            /* write the number as digits */\n            pre_ofs = no_pre_zero ? 0 : (2 - dot_ofs);\n            num_hdr = buf + pre_ofs;\n            num_end = write_u64_len_16_to_17_trim(sig_dec, num_hdr);\n\n            /* seperate these digits to leave a space for dot */\n            num_sep_pos = no_pre_zero ? dot_ofs : 0;\n            num_sep = num_hdr + num_sep_pos;\n            byte_move_16(num_sep + no_pre_zero, num_sep);\n            num_end += no_pre_zero;\n\n            /* write the dot */\n            dot_set_pos = yyjson_max(dot_ofs, 1);\n            buf[dot_set_pos] = '.';\n\n            /* return the ending */\n            dot_end = buf + dot_ofs + 2;\n            return yyjson_max(dot_end, num_end);\n\n        } else {\n            /* write with scientific notation, e.g. 1.234e56 */\n            end = write_u64_len_16_to_17_trim(sig_dec, buf + 1);\n            end -= (end == buf + 2); /* remove '.0', e.g. 2.0e34 -> 2e34 */\n            exp_dec += sig_len - 1;\n            buf[0] = buf[1];\n            buf[1] = '.';\n            return write_f64_exp(exp_dec, end);\n        }\n\n    } else {\n        /* subnormal number */\n        sig_bin = sig_raw;\n        exp_bin = 1 - F64_EXP_BIAS - F64_SIG_BITS;\n\n        /* binary to decimal */\n        f64_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec);\n\n        /* write significand part */\n        end = write_u64_len_1_to_17(sig_dec, buf + 1);\n        buf[0] = buf[1];\n        buf[1] = '.';\n        exp_dec += (i32)(end - buf) - 2;\n\n        /* trim trailing zeros */\n        end -= *(end - 1) == '0'; /* branchless for last zero */\n        end -= *(end - 1) == '0'; /* branchless for second last zero */\n        while (*(end - 1) == '0') end--; /* for unlikely more zeros */\n        end -= *(end - 1) == '.'; /* remove dot, e.g. 2.e-321 -> 2e-321 */\n\n        /* write exponent part */\n        return write_f64_exp(exp_dec, end);\n    }\n}\n\n/**\n Write a double number using fixed-point notation (requires 40 bytes buffer).\n\n We follow the ECMAScript specification for printing floating-point numbers,\n similar to `Number.prototype.toFixed(prec)`, but with the following changes:\n 1. Keep the negative sign of `-0.0` to preserve input information.\n 2. Keep decimal point to indicate the number is floating point.\n 3. Remove positive sign in the exponent part.\n 4. Remove trailing zeros and reduce unnecessary precision.\n */\nstatic_noinline u8 *write_f64_raw_fixed(u8 *buf, u64 raw, yyjson_write_flag flg,\n                                        u32 prec) {\n    u64 sig_bin, sig_dec, sig_raw;\n    i32 exp_bin, exp_dec, sig_len, dot_ofs;\n    u32 exp_raw;\n    u8 *end;\n    bool sign;\n\n    /* decode raw bytes from IEEE-754 double format. */\n    sign = (bool)(raw >> (F64_BITS - 1));\n    sig_raw = raw & F64_SIG_MASK;\n    exp_raw = (u32)((raw & F64_EXP_MASK) >> F64_SIG_BITS);\n\n    /* return inf or nan */\n    if (unlikely(exp_raw == ((u32)1 << F64_EXP_BITS) - 1)) {\n        return write_inf_or_nan(buf, flg, sig_raw, sign);\n    }\n\n    /* add sign for all finite number */\n    buf[0] = '-';\n    buf += sign;\n\n    /* return zero */\n    if ((raw << 1) == 0) {\n        byte_copy_4(buf, \"0.0\");\n        return buf + 3;\n    }\n\n    if (likely(exp_raw != 0)) {\n        /* normal number */\n        sig_bin = sig_raw | ((u64)1 << F64_SIG_BITS);\n        exp_bin = (i32)exp_raw - F64_EXP_BIAS - F64_SIG_BITS;\n\n        /* fast path for small integer number without fraction */\n        if ((-F64_SIG_BITS <= exp_bin && exp_bin <= 0) &&\n            (u64_tz_bits(sig_bin) >= (u32)-exp_bin)) {\n            sig_dec = sig_bin >> -exp_bin; /* range: [1, 0x1FFFFFFFFFFFFF] */\n            buf = write_u64_len_1_to_16(sig_dec, buf);\n            byte_copy_2(buf, \".0\");\n            return buf + 2;\n        }\n\n        /* only `fabs(num) < 1e21` are processed here. */\n        if ((raw << 1) < (U64(0x444B1AE4, 0xD6E2EF50) << 1)) {\n            i32 num_sep_pos, dot_set_pos, pre_ofs;\n            u8 *num_hdr, *num_end, *num_sep;\n            bool round_up, no_pre_zero;\n\n            /* binary to decimal */\n            f64_bin_to_dec_fast(sig_raw, exp_raw, sig_bin, exp_bin,\n                                &sig_dec, &exp_dec, &round_up);\n\n            /* the sig length is 16 or 17 */\n            sig_len = 16 + (sig_dec >= (u64)100000000 * 100000000);\n\n            /* limit the length of digits after the decimal point */\n            if (exp_dec < -1) {\n                i32 sig_len_cut = -exp_dec - (i32)prec;\n                if (sig_len_cut > sig_len) {\n                    byte_copy_4(buf, \"0.0\");\n                    return buf + 3;\n                }\n                if (sig_len_cut > 0) {\n                    u64 div, mod, p10;\n\n                    /* remove round up */\n                    sig_dec -= round_up;\n                    sig_len = 16 + (sig_dec >= (u64)100000000 * 100000000);\n\n                    /* cut off some digits */\n                    div_pow10(sig_dec, (u32)sig_len_cut, &div, &mod, &p10);\n\n                    /* add round up */\n                    sig_dec = div + (mod >= p10 / 2);\n\n                    /* update exp and sig length */\n                    exp_dec += sig_len_cut;\n                    sig_len -= sig_len_cut;\n                    sig_len += (sig_len >= 0) &&\n                               (sig_dec >= div_pow10_table[sig_len].p10);\n                }\n                if (sig_len <= 0) {\n                    byte_copy_4(buf, \"0.0\");\n                    return buf + 3;\n                }\n            }\n\n            /* fill zeros */\n            memset(buf, '0', 32);\n\n            /* the decimal point offset relative to the first digit */\n            dot_ofs = sig_len + exp_dec;\n\n            /* not prefixed with zero, e.g. 1.234, 1234.0 */\n            no_pre_zero = (dot_ofs > 0);\n\n            /* write the number as digits */\n            pre_ofs = no_pre_zero ? 0 : (1 - dot_ofs);\n            num_hdr = buf + pre_ofs;\n            num_end = write_u64_len_1_to_17(sig_dec, num_hdr);\n\n            /* seperate these digits to leave a space for dot */\n            num_sep_pos = no_pre_zero ? dot_ofs : -dot_ofs;\n            num_sep = buf + num_sep_pos;\n            byte_move_16(num_sep + 1, num_sep);\n            num_end += (exp_dec < 0);\n\n            /* write the dot */\n            dot_set_pos = yyjson_max(dot_ofs, 1);\n            buf[dot_set_pos] = '.';\n\n            /* remove trailing zeros */\n            buf += dot_set_pos + 2;\n            buf = yyjson_max(buf, num_end);\n            buf -= *(buf - 1) == '0'; /* branchless for last zero */\n            buf -= *(buf - 1) == '0'; /* branchless for second last zero */\n            while (*(buf - 1) == '0') buf--; /* for unlikely more zeros */\n            buf += *(buf - 1) == '.'; /* keep a zero after dot */\n            return buf;\n\n        } else {\n            /* binary to decimal */\n            f64_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin,\n                           &sig_dec, &exp_dec);\n\n            /* the sig length is 16 or 17 */\n            sig_len = 16 + (sig_dec >= (u64)100000000 * 100000000);\n\n            /* write with scientific notation, e.g. 1.234e56 */\n            end = write_u64_len_16_to_17_trim(sig_dec, buf + 1);\n            end -= (end == buf + 2); /* remove '.0', e.g. 2.0e34 -> 2e34 */\n            exp_dec += sig_len - 1;\n            buf[0] = buf[1];\n            buf[1] = '.';\n            return write_f64_exp(exp_dec, end);\n        }\n    } else {\n        /* subnormal number */\n        byte_copy_4(buf, \"0.0\");\n        return buf + 3;\n    }\n}\n\n#else /* FP_WRITER */\n\n#if YYJSON_MSC_VER >= 1400\n#define snprintf_num(buf, len, fmt, dig, val) \\\n    sprintf_s((char *)buf, len, fmt, dig, val)\n#elif defined(snprintf) || (YYJSON_STDC_VER >= 199901L)\n#define snprintf_num(buf, len, fmt, dig, val) \\\n    snprintf((char *)buf, len, fmt, dig, val)\n#else\n#define snprintf_num(buf, len, fmt, dig, val) \\\n    sprintf((char *)buf, fmt, dig, val)\n#endif\n\nstatic_noinline u8 *write_fp_reformat(u8 *buf, int len,\n                                    yyjson_write_flag flg, bool fixed) {\n    u8 *cur = buf;\n    if (unlikely(len < 1)) return NULL;\n    cur += (*cur == '-');\n    if (unlikely(!char_is_digit(*cur))) {\n        /* nan, inf, or bad output */\n        if (has_flg(INF_AND_NAN_AS_NULL)) {\n            byte_copy_4(buf, \"null\");\n            return buf + 4;\n        } else if (has_allow(INF_AND_NAN)) {\n            if (*cur == 'i') {\n                byte_copy_8(cur, \"Infinity\");\n                return cur + 8;\n            } else if (*cur == 'n') {\n                byte_copy_4(buf, \"NaN\");\n                return buf + 3;\n            }\n        }\n        return NULL;\n    } else {\n        /* finite number */\n        u8 *end = buf + len, *dot = NULL, *exp = NULL;\n\n        /*\n         The snprintf() function is locale-dependent. For currently known\n         locales, (en, zh, ja, ko, am, he, hi) use '.' as the decimal point,\n         while other locales use ',' as the decimal point. we need to replace\n         ',' with '.' to avoid the locale setting.\n         */\n        for (; cur < end; cur++) {\n            switch (*cur) {\n                case ',': *cur = '.'; /* fallthrough */\n                case '.': dot = cur; break;\n                case 'e': exp = cur; break;\n                default: break;\n            }\n        }\n        if (fixed) {\n            /* remove trailing zeros */\n            while (*(end - 1) == '0') end--;\n            end += *(end - 1) == '.';\n        } else {\n            if (!dot && !exp) {\n                /* add decimal point, e.g. 123 -> 123.0 */\n                byte_copy_2(end, \".0\");\n                end += 2;\n            } else if (exp) {\n                cur = exp + 1;\n                /* remove positive sign in the exponent part */\n                if (*cur == '+') {\n                    memmove(cur, cur + 1, (usize)(end - cur - 1));\n                    end--;\n                }\n                cur += (*cur == '-');\n                /* remove leading zeros in the exponent part */\n                if (*cur == '0') {\n                    u8 *hdr = cur++;\n                    while (*cur == '0') cur++;\n                    memmove(hdr, cur, (usize)(end - cur));\n                    end -= (usize)(cur - hdr);\n                }\n            }\n        }\n        return end;\n    }\n}\n\n/** Write a double number (requires 40 bytes buffer). */\nstatic_noinline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) {\n#if defined(DBL_DECIMAL_DIG) && DBL_DECIMAL_DIG < F64_DEC_DIG\n    int dig = DBL_DECIMAL_DIG;\n#else\n    int dig = F64_DEC_DIG;\n#endif\n    f64 val = f64_from_bits(raw);\n    int len = snprintf_num(buf, FP_BUF_LEN, \"%.*g\", dig, val);\n    return write_fp_reformat(buf, len, flg, false);\n}\n\n/** Write a double number (requires 40 bytes buffer). */\nstatic_noinline u8 *write_f32_raw(u8 *buf, u64 raw, yyjson_write_flag flg) {\n#if defined(FLT_DECIMAL_DIG) && FLT_DECIMAL_DIG < F32_DEC_DIG\n    int dig = FLT_DECIMAL_DIG;\n#else\n    int dig = F32_DEC_DIG;\n#endif\n    f64 val = (f64)f64_to_f32(f64_from_bits(raw));\n    int len = snprintf_num(buf, FP_BUF_LEN, \"%.*g\", dig, val);\n    return write_fp_reformat(buf, len, flg, false);\n}\n\n/** Write a double number (requires 40 bytes buffer). */\nstatic_noinline u8 *write_f64_raw_fixed(u8 *buf, u64 raw,\n                                        yyjson_write_flag flg, u32 prec) {\n    f64 val = (f64)f64_from_bits(raw);\n    if (-1e21 < val && val < 1e21) {\n        int len = snprintf_num(buf, FP_BUF_LEN, \"%.*f\", (int)prec, val);\n        return write_fp_reformat(buf, len, flg, true);\n    } else {\n        return write_f64_raw(buf, raw, flg);\n    }\n}\n\n#endif /* FP_WRITER */\n\n/** Write a JSON number (requires 40 bytes buffer). */\nstatic_inline u8 *write_num(u8 *cur, yyjson_val *val, yyjson_write_flag flg) {\n    if (!(val->tag & YYJSON_SUBTYPE_REAL)) {\n        u64 pos = val->uni.u64;\n        u64 neg = ~pos + 1;\n        usize sign = ((val->tag & YYJSON_SUBTYPE_SINT) > 0) & ((i64)pos < 0);\n        *cur = '-';\n        return write_u64(sign ? neg : pos, cur + sign);\n    } else {\n        u64 raw = val->uni.u64;\n        u32 val_fmt = (u32)(val->tag >> 32);\n        u32 all_fmt = flg;\n        u32 fmt = val_fmt | all_fmt;\n        if (likely(!(fmt >> (32 - YYJSON_WRITE_FP_FLAG_BITS)))) {\n            /* double to shortest */\n            return write_f64_raw(cur, raw, flg);\n        } else if (fmt >> (32 - YYJSON_WRITE_FP_PREC_BITS)) {\n            /* double to fixed */\n            u32 val_prec = val_fmt >> (32 - YYJSON_WRITE_FP_PREC_BITS);\n            u32 all_prec = all_fmt >> (32 - YYJSON_WRITE_FP_PREC_BITS);\n            u32 prec = val_prec ? val_prec : all_prec;\n            return write_f64_raw_fixed(cur, raw, flg, prec);\n        } else {\n            if (fmt & YYJSON_WRITE_FP_TO_FLOAT) {\n                /* float to shortest */\n                return write_f32_raw(cur, raw, flg);\n            } else {\n                /* double to shortest */\n                return write_f64_raw(cur, raw, flg);\n            }\n        }\n    }\n}\n\nchar *yyjson_write_number(const yyjson_val *val, char *buf) {\n    if (unlikely(!val || !buf)) return NULL;\n    switch (val->tag & YYJSON_TAG_MASK) {\n        case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT: {\n            buf = (char *)write_u64(val->uni.u64, (u8 *)buf);\n            *buf = '\\0';\n            return buf;\n        }\n        case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT: {\n            u64 pos = val->uni.u64;\n            u64 neg = ~pos + 1;\n            usize sign = ((i64)pos < 0);\n            *buf = '-';\n            buf = (char *)write_u64(sign ? neg : pos, (u8 *)buf + sign);\n            *buf = '\\0';\n            return buf;\n        }\n        case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL: {\n            u64 raw = val->uni.u64;\n            u32 fmt = (u32)(val->tag >> 32);\n            u32 flg = YYJSON_WRITE_ALLOW_INF_AND_NAN;\n            if (likely(!(fmt >> (32 - YYJSON_WRITE_FP_FLAG_BITS)))) {\n                buf = (char *)write_f64_raw((u8 *)buf, raw, flg);\n            } else if (fmt >> (32 - YYJSON_WRITE_FP_PREC_BITS)) {\n                u32 prec = fmt >> (32 - YYJSON_WRITE_FP_PREC_BITS);\n                buf = (char *)write_f64_raw_fixed((u8 *)buf, raw, flg, prec);\n            } else {\n                if (fmt & YYJSON_WRITE_FP_TO_FLOAT) {\n                    buf = (char *)write_f32_raw((u8 *)buf, raw, flg);\n                } else {\n                    buf = (char *)write_f64_raw((u8 *)buf, raw, flg);\n                }\n            }\n            if (buf) *buf = '\\0';\n            return buf;\n        }\n        default: return NULL;\n    }\n}\n\n\n\n/*==============================================================================\n * MARK: - String Writer (Private)\n *============================================================================*/\n\n/** Character encode type, if (type > CHAR_ENC_ERR_1) bytes = type / 2; */\ntypedef u8 char_enc_type;\n#define CHAR_ENC_CPY_1  0 /* 1-byte UTF-8, copy. */\n#define CHAR_ENC_ERR_1  1 /* 1-byte UTF-8, error. */\n#define CHAR_ENC_ESC_A  2 /* 1-byte ASCII, escaped as '\\x'. */\n#define CHAR_ENC_ESC_1  3 /* 1-byte UTF-8, escaped as '\\uXXXX'. */\n#define CHAR_ENC_CPY_2  4 /* 2-byte UTF-8, copy. */\n#define CHAR_ENC_ESC_2  5 /* 2-byte UTF-8, escaped as '\\uXXXX'. */\n#define CHAR_ENC_CPY_3  6 /* 3-byte UTF-8, copy. */\n#define CHAR_ENC_ESC_3  7 /* 3-byte UTF-8, escaped as '\\uXXXX'. */\n#define CHAR_ENC_CPY_4  8 /* 4-byte UTF-8, copy. */\n#define CHAR_ENC_ESC_4  9 /* 4-byte UTF-8, escaped as '\\uXXXX\\uXXXX'. */\n\n/** Character encode type table: don't escape unicode, don't escape '/'.\n    (generate with misc/make_tables.c) */\nstatic const char_enc_type enc_table_cpy[256] = {\n    3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3,\n    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,\n    0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,\n    4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,\n    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,\n    8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1\n};\n\n/** Character encode type table: don't escape unicode, escape '/'.\n    (generate with misc/make_tables.c) */\nstatic const char_enc_type enc_table_cpy_slash[256] = {\n    3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3,\n    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,\n    0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,\n    4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,\n    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,\n    8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1\n};\n\n/** Character encode type table: escape unicode, don't escape '/'.\n    (generate with misc/make_tables.c) */\nstatic const char_enc_type enc_table_esc[256] = {\n    3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3,\n    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,\n    0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,\n    5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,\n    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,\n    9, 9, 9, 9, 9, 9, 9, 9, 1, 1, 1, 1, 1, 1, 1, 1\n};\n\n/** Character encode type table: escape unicode, escape '/'.\n    (generate with misc/make_tables.c) */\nstatic const char_enc_type enc_table_esc_slash[256] = {\n    3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3,\n    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,\n    0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,\n    5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,\n    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,\n    9, 9, 9, 9, 9, 9, 9, 9, 1, 1, 1, 1, 1, 1, 1, 1\n};\n\n/** Escaped hex character table: [\"00\" \"01\" \"02\" ... \"FD\" \"FE\" \"FF\"].\n    (generate with misc/make_tables.c) */\nyyjson_align(2)\nstatic const u8 esc_hex_char_table[512] = {\n    '0', '0', '0', '1', '0', '2', '0', '3',\n    '0', '4', '0', '5', '0', '6', '0', '7',\n    '0', '8', '0', '9', '0', 'A', '0', 'B',\n    '0', 'C', '0', 'D', '0', 'E', '0', 'F',\n    '1', '0', '1', '1', '1', '2', '1', '3',\n    '1', '4', '1', '5', '1', '6', '1', '7',\n    '1', '8', '1', '9', '1', 'A', '1', 'B',\n    '1', 'C', '1', 'D', '1', 'E', '1', 'F',\n    '2', '0', '2', '1', '2', '2', '2', '3',\n    '2', '4', '2', '5', '2', '6', '2', '7',\n    '2', '8', '2', '9', '2', 'A', '2', 'B',\n    '2', 'C', '2', 'D', '2', 'E', '2', 'F',\n    '3', '0', '3', '1', '3', '2', '3', '3',\n    '3', '4', '3', '5', '3', '6', '3', '7',\n    '3', '8', '3', '9', '3', 'A', '3', 'B',\n    '3', 'C', '3', 'D', '3', 'E', '3', 'F',\n    '4', '0', '4', '1', '4', '2', '4', '3',\n    '4', '4', '4', '5', '4', '6', '4', '7',\n    '4', '8', '4', '9', '4', 'A', '4', 'B',\n    '4', 'C', '4', 'D', '4', 'E', '4', 'F',\n    '5', '0', '5', '1', '5', '2', '5', '3',\n    '5', '4', '5', '5', '5', '6', '5', '7',\n    '5', '8', '5', '9', '5', 'A', '5', 'B',\n    '5', 'C', '5', 'D', '5', 'E', '5', 'F',\n    '6', '0', '6', '1', '6', '2', '6', '3',\n    '6', '4', '6', '5', '6', '6', '6', '7',\n    '6', '8', '6', '9', '6', 'A', '6', 'B',\n    '6', 'C', '6', 'D', '6', 'E', '6', 'F',\n    '7', '0', '7', '1', '7', '2', '7', '3',\n    '7', '4', '7', '5', '7', '6', '7', '7',\n    '7', '8', '7', '9', '7', 'A', '7', 'B',\n    '7', 'C', '7', 'D', '7', 'E', '7', 'F',\n    '8', '0', '8', '1', '8', '2', '8', '3',\n    '8', '4', '8', '5', '8', '6', '8', '7',\n    '8', '8', '8', '9', '8', 'A', '8', 'B',\n    '8', 'C', '8', 'D', '8', 'E', '8', 'F',\n    '9', '0', '9', '1', '9', '2', '9', '3',\n    '9', '4', '9', '5', '9', '6', '9', '7',\n    '9', '8', '9', '9', '9', 'A', '9', 'B',\n    '9', 'C', '9', 'D', '9', 'E', '9', 'F',\n    'A', '0', 'A', '1', 'A', '2', 'A', '3',\n    'A', '4', 'A', '5', 'A', '6', 'A', '7',\n    'A', '8', 'A', '9', 'A', 'A', 'A', 'B',\n    'A', 'C', 'A', 'D', 'A', 'E', 'A', 'F',\n    'B', '0', 'B', '1', 'B', '2', 'B', '3',\n    'B', '4', 'B', '5', 'B', '6', 'B', '7',\n    'B', '8', 'B', '9', 'B', 'A', 'B', 'B',\n    'B', 'C', 'B', 'D', 'B', 'E', 'B', 'F',\n    'C', '0', 'C', '1', 'C', '2', 'C', '3',\n    'C', '4', 'C', '5', 'C', '6', 'C', '7',\n    'C', '8', 'C', '9', 'C', 'A', 'C', 'B',\n    'C', 'C', 'C', 'D', 'C', 'E', 'C', 'F',\n    'D', '0', 'D', '1', 'D', '2', 'D', '3',\n    'D', '4', 'D', '5', 'D', '6', 'D', '7',\n    'D', '8', 'D', '9', 'D', 'A', 'D', 'B',\n    'D', 'C', 'D', 'D', 'D', 'E', 'D', 'F',\n    'E', '0', 'E', '1', 'E', '2', 'E', '3',\n    'E', '4', 'E', '5', 'E', '6', 'E', '7',\n    'E', '8', 'E', '9', 'E', 'A', 'E', 'B',\n    'E', 'C', 'E', 'D', 'E', 'E', 'E', 'F',\n    'F', '0', 'F', '1', 'F', '2', 'F', '3',\n    'F', '4', 'F', '5', 'F', '6', 'F', '7',\n    'F', '8', 'F', '9', 'F', 'A', 'F', 'B',\n    'F', 'C', 'F', 'D', 'F', 'E', 'F', 'F'\n};\n\n/** Escaped single character table. (generate with misc/make_tables.c) */\nyyjson_align(2)\nstatic const u8 esc_single_char_table[512] = {\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    '\\\\', 'b', '\\\\', 't', '\\\\', 'n', ' ', ' ',\n    '\\\\', 'f', '\\\\', 'r', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', '\\\\', '\"', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', '\\\\', '/',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    '\\\\', '\\\\', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',\n    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '\n};\n\n/** Returns the encode table with options. */\nstatic_inline const char_enc_type *get_enc_table_with_flag(\n    yyjson_write_flag flg) {\n    if (has_flg(ESCAPE_UNICODE)) {\n        if (has_flg(ESCAPE_SLASHES)) {\n            return enc_table_esc_slash;\n        } else {\n            return enc_table_esc;\n        }\n    } else {\n        if (has_flg(ESCAPE_SLASHES)) {\n            return enc_table_cpy_slash;\n        } else {\n            return enc_table_cpy;\n        }\n    }\n}\n\n/** Write raw string. */\nstatic_inline u8 *write_raw(u8 *cur, const u8 *raw, usize raw_len) {\n    memcpy(cur, raw, raw_len);\n    return cur + raw_len;\n}\n\n/**\n Write string no-escape.\n @param cur Buffer cursor.\n @param str A UTF-8 string, null-terminator is not required.\n @param str_len Length of string in bytes.\n @return The buffer cursor after string.\n */\nstatic_inline u8 *write_str_noesc(u8 *cur, const u8 *str, usize str_len) {\n    *cur++ = '\"';\n    while (str_len >= 16) {\n        byte_copy_16(cur, str);\n        cur += 16;\n        str += 16;\n        str_len -= 16;\n    }\n    while (str_len >= 4) {\n        byte_copy_4(cur, str);\n        cur += 4;\n        str += 4;\n        str_len -= 4;\n    }\n    while (str_len) {\n        *cur++ = *str++;\n        str_len -= 1;\n    }\n    *cur++ = '\"';\n    return cur;\n}\n\n/**\n Write UTF-8 string (requires len * 6 + 2 bytes buffer).\n @param cur Buffer cursor.\n @param esc Escape unicode.\n @param inv Allow invalid unicode.\n @param str A UTF-8 string, null-terminator is not required.\n @param str_len Length of string in bytes.\n @param enc_table Encode type table for character.\n @return The buffer cursor after string, or NULL on invalid unicode.\n */\nstatic_inline u8 *write_str(u8 *cur, bool esc, bool inv,\n                            const u8 *str, usize str_len,\n                            const char_enc_type *enc_table) {\n    /* The replacement character U+FFFD, used to indicate invalid character. */\n    const v32 rep = {{ 'F', 'F', 'F', 'D' }};\n    const v32 pre = {{ '\\\\', 'u', '0', '0' }};\n\n    const u8 *src = str;\n    const u8 *end = str + str_len;\n    *cur++ = '\"';\n\ncopy_ascii:\n    /*\n     Copy continuous ASCII, loop unrolling, same as the following code:\n\n         while (end > src) (\n            if (unlikely(enc_table[*src])) break;\n            *cur++ = *src++;\n         );\n     */\n#define expr_jump(i) \\\n    if (unlikely(enc_table[src[i]])) goto stop_char_##i;\n\n#define expr_stop(i) \\\n    stop_char_##i: \\\n    memcpy(cur, src, i); \\\n    cur += i; src += i; goto copy_utf8;\n\n    while (end - src >= 16) {\n        repeat16_incr(expr_jump)\n        byte_copy_16(cur, src);\n        cur += 16; src += 16;\n    }\n\n    while (end - src >= 4) {\n        repeat4_incr(expr_jump)\n        byte_copy_4(cur, src);\n        cur += 4; src += 4;\n    }\n\n    while (end > src) {\n        expr_jump(0)\n        *cur++ = *src++;\n    }\n\n    *cur++ = '\"';\n    return cur;\n\n    repeat16_incr(expr_stop)\n\n#undef expr_jump\n#undef expr_stop\n\ncopy_utf8:\n    if (unlikely(src + 4 > end)) {\n        if (end == src) goto copy_end;\n        if (end - src < enc_table[*src] / 2) goto err_one;\n    }\n    switch (enc_table[*src]) {\n        case CHAR_ENC_CPY_1: {\n            *cur++ = *src++;\n            goto copy_ascii;\n        }\n        case CHAR_ENC_CPY_2: {\n#if YYJSON_DISABLE_UTF8_VALIDATION\n            byte_copy_2(cur, src);\n#else\n            u32 uni = 0;\n            byte_copy_2(&uni, src);\n            if (unlikely(!is_utf8_seq2(uni))) goto err_cpy;\n            byte_copy_2(cur, &uni);\n#endif\n            cur += 2;\n            src += 2;\n            goto copy_utf8;\n        }\n        case CHAR_ENC_CPY_3: {\n#if YYJSON_DISABLE_UTF8_VALIDATION\n            if (likely(src + 4 <= end)) {\n                byte_copy_4(cur, src);\n            } else {\n                byte_copy_2(cur, src);\n                cur[2] = src[2];\n            }\n#else\n            u32 uni, tmp;\n            if (likely(src + 4 <= end)) {\n                uni = byte_load_4(src);\n                if (unlikely(!is_utf8_seq3(uni))) goto err_cpy;\n                byte_copy_4(cur, src);\n            } else {\n                uni = byte_load_3(src);\n                if (unlikely(!is_utf8_seq3(uni))) goto err_cpy;\n                byte_copy_4(cur, &uni);\n            }\n#endif\n            cur += 3;\n            src += 3;\n            goto copy_utf8;\n        }\n        case CHAR_ENC_CPY_4: {\n#if YYJSON_DISABLE_UTF8_VALIDATION\n            byte_copy_4(cur, src);\n#else\n            u32 uni, tmp;\n            uni = byte_load_4(src);\n            if (unlikely(!is_utf8_seq4(uni))) goto err_cpy;\n            byte_copy_4(cur, src);\n#endif\n            cur += 4;\n            src += 4;\n            goto copy_utf8;\n        }\n        case CHAR_ENC_ESC_A: {\n            byte_copy_2(cur, &esc_single_char_table[*src * 2]);\n            cur += 2;\n            src += 1;\n            goto copy_utf8;\n        }\n        case CHAR_ENC_ESC_1: {\n            byte_copy_4(cur + 0, &pre);\n            byte_copy_2(cur + 4, &esc_hex_char_table[*src * 2]);\n            cur += 6;\n            src += 1;\n            goto copy_utf8;\n        }\n        case CHAR_ENC_ESC_2: {\n            u16 u;\n#if !YYJSON_DISABLE_UTF8_VALIDATION\n            u32 v4 = 0;\n            u16 v2 = byte_load_2(src);\n            byte_copy_2(&v4, &v2);\n            if (unlikely(!is_utf8_seq2(v4))) goto err_esc;\n#endif\n            u = (u16)(((u16)(src[0] & 0x1F) << 6) |\n                      ((u16)(src[1] & 0x3F) << 0));\n            byte_copy_2(cur + 0, &pre);\n            byte_copy_2(cur + 2, &esc_hex_char_table[(u >> 8) * 2]);\n            byte_copy_2(cur + 4, &esc_hex_char_table[(u & 0xFF) * 2]);\n            cur += 6;\n            src += 2;\n            goto copy_utf8;\n        }\n        case CHAR_ENC_ESC_3: {\n            u16 u;\n            u32 v, tmp;\n#if !YYJSON_DISABLE_UTF8_VALIDATION\n            v = byte_load_3(src);\n            if (unlikely(!is_utf8_seq3(v))) goto err_esc;\n#endif\n            u = (u16)(((u16)(src[0] & 0x0F) << 12) |\n                      ((u16)(src[1] & 0x3F) << 6) |\n                      ((u16)(src[2] & 0x3F) << 0));\n            byte_copy_2(cur + 0, &pre);\n            byte_copy_2(cur + 2, &esc_hex_char_table[(u >> 8) * 2]);\n            byte_copy_2(cur + 4, &esc_hex_char_table[(u & 0xFF) * 2]);\n            cur += 6;\n            src += 3;\n            goto copy_utf8;\n        }\n        case CHAR_ENC_ESC_4: {\n            u32 hi, lo, u, v, tmp;\n#if !YYJSON_DISABLE_UTF8_VALIDATION\n            v = byte_load_4(src);\n            if (unlikely(!is_utf8_seq4(v))) goto err_esc;\n#endif\n            u = ((u32)(src[0] & 0x07) << 18) |\n                ((u32)(src[1] & 0x3F) << 12) |\n                ((u32)(src[2] & 0x3F) << 6) |\n                ((u32)(src[3] & 0x3F) << 0);\n            u -= 0x10000;\n            hi = (u >> 10) + 0xD800;\n            lo = (u & 0x3FF) + 0xDC00;\n            byte_copy_2(cur + 0, &pre);\n            byte_copy_2(cur + 2, &esc_hex_char_table[(hi >> 8) * 2]);\n            byte_copy_2(cur + 4, &esc_hex_char_table[(hi & 0xFF) * 2]);\n            byte_copy_2(cur + 6, &pre);\n            byte_copy_2(cur + 8, &esc_hex_char_table[(lo >> 8) * 2]);\n            byte_copy_2(cur + 10, &esc_hex_char_table[(lo & 0xFF) * 2]);\n            cur += 12;\n            src += 4;\n            goto copy_utf8;\n        }\n        case CHAR_ENC_ERR_1: {\n            goto err_one;\n        }\n        default: break; /* unreachable */\n    }\n\ncopy_end:\n    *cur++ = '\"';\n    return cur;\n\nerr_one:\n    if (esc) goto err_esc;\n    else goto err_cpy;\n\nerr_cpy:\n    if (!inv) return NULL;\n    *cur++ = *src++;\n    goto copy_utf8;\n\nerr_esc:\n    if (!inv) return NULL;\n    byte_copy_2(cur + 0, &pre);\n    byte_copy_4(cur + 2, &rep);\n    cur += 6;\n    src += 1;\n    goto copy_utf8;\n}\n\n\n\n/*==============================================================================\n * MARK: - JSON Writer Utilities (Private)\n *============================================================================*/\n\n/** Write null (requires 8 bytes buffer). */\nstatic_inline u8 *write_null(u8 *cur) {\n    v64 v = {{ 'n', 'u', 'l', 'l', ',', '\\n', 0, 0 }};\n    byte_copy_8(cur, &v);\n    return cur + 4;\n}\n\n/** Write bool (requires 8 bytes buffer). */\nstatic_inline u8 *write_bool(u8 *cur, bool val) {\n    v64 v0 = {{ 'f', 'a', 'l', 's', 'e', ',', '\\n', 0 }};\n    v64 v1 = {{ 't', 'r', 'u', 'e', ',', '\\n', 0, 0 }};\n    if (val) {\n        byte_copy_8(cur, &v1);\n    } else {\n        byte_copy_8(cur, &v0);\n    }\n    return cur + 5 - val;\n}\n\n/** Write indent (requires level x 4 bytes buffer).\n    Param spaces should not larger than 4. */\nstatic_inline u8 *write_indent(u8 *cur, usize level, usize spaces) {\n    while (level-- > 0) {\n        byte_copy_4(cur, \"    \");\n        cur += spaces;\n    }\n    return cur;\n}\n\n/** Write data to file pointer. */\nstatic bool write_dat_to_fp(FILE *fp, u8 *dat, usize len,\n                            yyjson_write_err *err) {\n    if (fwrite(dat, len, 1, fp) != 1) {\n        err->msg = \"file writing failed\";\n        err->code = YYJSON_WRITE_ERROR_FILE_WRITE;\n        return false;\n    }\n    return true;\n}\n\n/** Write data to file. */\nstatic bool write_dat_to_file(const char *path, u8 *dat, usize len,\n                              yyjson_write_err *err) {\n#define return_err(_code, _msg) do { \\\n    err->msg = _msg; \\\n    err->code = YYJSON_WRITE_ERROR_##_code; \\\n    if (file) fclose(file); \\\n    return false; \\\n} while (false)\n\n    FILE *file = fopen_writeonly(path);\n    if (file == NULL) {\n        return_err(FILE_OPEN, MSG_FOPEN);\n    }\n    if (fwrite(dat, len, 1, file) != 1) {\n        return_err(FILE_WRITE, MSG_FWRITE);\n    }\n    if (fclose(file) != 0) {\n        file = NULL;\n        return_err(FILE_WRITE, MSG_FCLOSE);\n    }\n    return true;\n\n#undef return_err\n}\n\n\n\n/*==============================================================================\n * MARK: - JSON Writer Implementation (Private)\n *============================================================================*/\n\ntypedef struct yyjson_write_ctx {\n    usize tag;\n} yyjson_write_ctx;\n\nstatic_inline void yyjson_write_ctx_set(yyjson_write_ctx *ctx,\n                                        usize size, bool is_obj) {\n    ctx->tag = (size << 1) | (usize)is_obj;\n}\n\nstatic_inline void yyjson_write_ctx_get(yyjson_write_ctx *ctx,\n                                        usize *size, bool *is_obj) {\n    usize tag = ctx->tag;\n    *size = tag >> 1;\n    *is_obj = (bool)(tag & 1);\n}\n\n/** Write single JSON value. */\nstatic_inline u8 *yyjson_write_single(yyjson_val *val,\n                                      yyjson_write_flag flg,\n                                      yyjson_alc alc,\n                                      usize *dat_len,\n                                      yyjson_write_err *err) {\n#define return_err(_code, _msg) do { \\\n    if (hdr) alc.free(alc.ctx, (void *)hdr); \\\n    *dat_len = 0; \\\n    err->code = YYJSON_WRITE_ERROR_##_code; \\\n    err->msg = _msg; \\\n    return NULL; \\\n} while (false)\n\n#define incr_len(_len) do { \\\n    hdr = (u8 *)alc.malloc(alc.ctx, _len); \\\n    if (!hdr) goto fail_alloc; \\\n    cur = hdr; \\\n} while (false)\n\n#define check_str_len(_len) do { \\\n    if ((sizeof(usize) < 8) && (_len >= (USIZE_MAX - 16) / 6)) \\\n        goto fail_alloc; \\\n} while (false)\n\n    u8 *hdr = NULL, *cur;\n    usize str_len;\n    const u8 *str_ptr;\n    const char_enc_type *enc_table = get_enc_table_with_flag(flg);\n    bool cpy = (enc_table == enc_table_cpy);\n    bool esc = has_flg(ESCAPE_UNICODE) != 0;\n    bool inv = has_allow(INVALID_UNICODE) != 0;\n    bool newline = has_flg(NEWLINE_AT_END) != 0;\n    const usize end_len = 2; /* '\\n' and '\\0' */\n\n    switch (unsafe_yyjson_get_type(val)) {\n        case YYJSON_TYPE_RAW:\n            str_len = unsafe_yyjson_get_len(val);\n            str_ptr = (const u8 *)unsafe_yyjson_get_str(val);\n            check_str_len(str_len);\n            incr_len(str_len + end_len);\n            cur = write_raw(cur, str_ptr, str_len);\n            break;\n\n        case YYJSON_TYPE_STR:\n            str_len = unsafe_yyjson_get_len(val);\n            str_ptr = (const u8 *)unsafe_yyjson_get_str(val);\n            check_str_len(str_len);\n            incr_len(str_len * 6 + 2 + end_len);\n            if (likely(cpy) && unsafe_yyjson_get_subtype(val)) {\n                cur = write_str_noesc(cur, str_ptr, str_len);\n            } else {\n                cur = write_str(cur, esc, inv, str_ptr, str_len, enc_table);\n                if (unlikely(!cur)) goto fail_str;\n            }\n            break;\n\n        case YYJSON_TYPE_NUM:\n            incr_len(FP_BUF_LEN + end_len);\n            cur = write_num(cur, val, flg);\n            if (unlikely(!cur)) goto fail_num;\n            break;\n\n        case YYJSON_TYPE_BOOL:\n            incr_len(8);\n            cur = write_bool(cur, unsafe_yyjson_get_bool(val));\n            break;\n\n        case YYJSON_TYPE_NULL:\n            incr_len(8);\n            cur = write_null(cur);\n            break;\n\n        case YYJSON_TYPE_ARR:\n            incr_len(2 + end_len);\n            byte_copy_2(cur, \"[]\");\n            cur += 2;\n            break;\n\n        case YYJSON_TYPE_OBJ:\n            incr_len(2 + end_len);\n            byte_copy_2(cur, \"{}\");\n            cur += 2;\n            break;\n\n        default:\n            goto fail_type;\n    }\n\n    if (newline) *cur++ = '\\n';\n    *cur = '\\0';\n    *dat_len = (usize)(cur - hdr);\n    memset(err, 0, sizeof(yyjson_write_err));\n    return hdr;\n\nfail_alloc: return_err(MEMORY_ALLOCATION, MSG_MALLOC);\nfail_type:  return_err(INVALID_VALUE_TYPE, MSG_ERR_TYPE);\nfail_num:   return_err(NAN_OR_INF, MSG_NAN_INF);\nfail_str:   return_err(INVALID_STRING, MSG_ERR_UTF8);\n\n#undef return_err\n#undef check_str_len\n#undef incr_len\n}\n\n/** Write JSON document minify.\n    The root of this document should be a non-empty container. */\nstatic_inline u8 *yyjson_write_minify(const yyjson_val *root,\n                                      const yyjson_write_flag flg,\n                                      const yyjson_alc alc,\n                                      usize *dat_len,\n                                      yyjson_write_err *err) {\n#define return_err(_code, _msg) do { \\\n    *dat_len = 0; \\\n    err->code = YYJSON_WRITE_ERROR_##_code; \\\n    err->msg = _msg; \\\n    if (hdr) alc.free(alc.ctx, hdr); \\\n    return NULL; \\\n} while (false)\n\n#define incr_len(_len) do { \\\n    ext_len = (usize)(_len); \\\n    if (unlikely((u8 *)(cur + ext_len) >= (u8 *)ctx)) { \\\n        usize ctx_pos = (usize)((u8 *)ctx - hdr); \\\n        usize cur_pos = (usize)(cur - hdr); \\\n        ctx_len = (usize)(end - (u8 *)ctx); \\\n        alc_inc = yyjson_max(alc_len / 2, ext_len); \\\n        alc_inc = size_align_up(alc_inc, sizeof(yyjson_write_ctx)); \\\n        if ((sizeof(usize) < 8) && size_add_is_overflow(alc_len, alc_inc)) \\\n            goto fail_alloc; \\\n        alc_len += alc_inc; \\\n        tmp = (u8 *)alc.realloc(alc.ctx, hdr, alc_len - alc_inc, alc_len); \\\n        if (unlikely(!tmp)) goto fail_alloc; \\\n        ctx_tmp = (yyjson_write_ctx *)(void *)(tmp + (alc_len - ctx_len)); \\\n        memmove((void *)ctx_tmp, (void *)(tmp + ctx_pos), ctx_len); \\\n        ctx = ctx_tmp; \\\n        cur = tmp + cur_pos; \\\n        end = tmp + alc_len; \\\n        hdr = tmp; \\\n    } \\\n} while (false)\n\n#define check_str_len(_len) do { \\\n    if ((sizeof(usize) < 8) && (_len >= (USIZE_MAX - 16) / 6)) \\\n        goto fail_alloc; \\\n} while (false)\n\n    yyjson_val *val;\n    yyjson_type val_type;\n    usize ctn_len, ctn_len_tmp;\n    bool ctn_obj, ctn_obj_tmp, is_key;\n    u8 *hdr, *cur, *end, *tmp;\n    yyjson_write_ctx *ctx, *ctx_tmp;\n    usize alc_len, alc_inc, ctx_len, ext_len, str_len;\n    const u8 *str_ptr;\n    const char_enc_type *enc_table = get_enc_table_with_flag(flg);\n    bool cpy = (enc_table == enc_table_cpy);\n    bool esc = has_flg(ESCAPE_UNICODE) != 0;\n    bool inv = has_allow(INVALID_UNICODE) != 0;\n    bool newline = has_flg(NEWLINE_AT_END) != 0;\n\n    alc_len = root->uni.ofs / sizeof(yyjson_val);\n    alc_len = alc_len * YYJSON_WRITER_ESTIMATED_MINIFY_RATIO + 64;\n    alc_len = size_align_up(alc_len, sizeof(yyjson_write_ctx));\n    hdr = (u8 *)alc.malloc(alc.ctx, alc_len);\n    if (!hdr) goto fail_alloc;\n    cur = hdr;\n    end = hdr + alc_len;\n    ctx = (yyjson_write_ctx *)(void *)end;\n\ndoc_begin:\n    val = constcast(yyjson_val *)root;\n    val_type = unsafe_yyjson_get_type(val);\n    ctn_obj = (val_type == YYJSON_TYPE_OBJ);\n    ctn_len = unsafe_yyjson_get_len(val) << (u8)ctn_obj;\n    *cur++ = (u8)('[' | ((u8)ctn_obj << 5));\n    val++;\n\nval_begin:\n    val_type = unsafe_yyjson_get_type(val);\n    if (val_type == YYJSON_TYPE_STR) {\n        is_key = ((u8)ctn_obj & (u8)~ctn_len);\n        str_len = unsafe_yyjson_get_len(val);\n        str_ptr = (const u8 *)unsafe_yyjson_get_str(val);\n        check_str_len(str_len);\n        incr_len(str_len * 6 + 16);\n        if (likely(cpy) && unsafe_yyjson_get_subtype(val)) {\n            cur = write_str_noesc(cur, str_ptr, str_len);\n        } else {\n            cur = write_str(cur, esc, inv, str_ptr, str_len, enc_table);\n            if (unlikely(!cur)) goto fail_str;\n        }\n        *cur++ = is_key ? ':' : ',';\n        goto val_end;\n    }\n    if (val_type == YYJSON_TYPE_NUM) {\n        incr_len(FP_BUF_LEN);\n        cur = write_num(cur, val, flg);\n        if (unlikely(!cur)) goto fail_num;\n        *cur++ = ',';\n        goto val_end;\n    }\n    if ((val_type & (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) ==\n                    (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) {\n        ctn_len_tmp = unsafe_yyjson_get_len(val);\n        ctn_obj_tmp = (val_type == YYJSON_TYPE_OBJ);\n        incr_len(16);\n        if (unlikely(ctn_len_tmp == 0)) {\n            /* write empty container */\n            *cur++ = (u8)('[' | ((u8)ctn_obj_tmp << 5));\n            *cur++ = (u8)(']' | ((u8)ctn_obj_tmp << 5));\n            *cur++ = ',';\n            goto val_end;\n        } else {\n            /* push context, setup new container */\n            yyjson_write_ctx_set(--ctx, ctn_len, ctn_obj);\n            ctn_len = ctn_len_tmp << (u8)ctn_obj_tmp;\n            ctn_obj = ctn_obj_tmp;\n            *cur++ = (u8)('[' | ((u8)ctn_obj << 5));\n            val++;\n            goto val_begin;\n        }\n    }\n    if (val_type == YYJSON_TYPE_BOOL) {\n        incr_len(16);\n        cur = write_bool(cur, unsafe_yyjson_get_bool(val));\n        cur++;\n        goto val_end;\n    }\n    if (val_type == YYJSON_TYPE_NULL) {\n        incr_len(16);\n        cur = write_null(cur);\n        cur++;\n        goto val_end;\n    }\n    if (val_type == YYJSON_TYPE_RAW) {\n        str_len = unsafe_yyjson_get_len(val);\n        str_ptr = (const u8 *)unsafe_yyjson_get_str(val);\n        check_str_len(str_len);\n        incr_len(str_len + 2);\n        cur = write_raw(cur, str_ptr, str_len);\n        *cur++ = ',';\n        goto val_end;\n    }\n    goto fail_type;\n\nval_end:\n    val++;\n    ctn_len--;\n    if (unlikely(ctn_len == 0)) goto ctn_end;\n    goto val_begin;\n\nctn_end:\n    cur--;\n    *cur++ = (u8)(']' | ((u8)ctn_obj << 5));\n    *cur++ = ',';\n    if (unlikely((u8 *)ctx >= end)) goto doc_end;\n    yyjson_write_ctx_get(ctx++, &ctn_len, &ctn_obj);\n    ctn_len--;\n    if (likely(ctn_len > 0)) {\n        goto val_begin;\n    } else {\n        goto ctn_end;\n    }\n\ndoc_end:\n    if (newline) {\n        incr_len(2);\n        *(cur - 1) = '\\n';\n        cur++;\n    }\n    *--cur = '\\0';\n    *dat_len = (usize)(cur - hdr);\n    memset(err, 0, sizeof(yyjson_write_err));\n    return hdr;\n\nfail_alloc: return_err(MEMORY_ALLOCATION, MSG_MALLOC);\nfail_type:  return_err(INVALID_VALUE_TYPE, MSG_ERR_TYPE);\nfail_num:   return_err(NAN_OR_INF, MSG_NAN_INF);\nfail_str:   return_err(INVALID_STRING, MSG_ERR_UTF8);\n\n#undef return_err\n#undef incr_len\n#undef check_str_len\n}\n\n/** Write JSON document pretty.\n    The root of this document should be a non-empty container. */\nstatic_inline u8 *yyjson_write_pretty(const yyjson_val *root,\n                                      const yyjson_write_flag flg,\n                                      const yyjson_alc alc,\n                                      usize *dat_len,\n                                      yyjson_write_err *err) {\n#define return_err(_code, _msg) do { \\\n    *dat_len = 0; \\\n    err->code = YYJSON_WRITE_ERROR_##_code; \\\n    err->msg = _msg; \\\n    if (hdr) alc.free(alc.ctx, hdr); \\\n    return NULL; \\\n} while (false)\n\n#define incr_len(_len) do { \\\n    ext_len = (usize)(_len); \\\n    if (unlikely((u8 *)(cur + ext_len) >= (u8 *)ctx)) { \\\n        usize ctx_pos = (usize)((u8 *)ctx - hdr); \\\n        usize cur_pos = (usize)(cur - hdr); \\\n        ctx_len = (usize)(end - (u8 *)ctx); \\\n        alc_inc = yyjson_max(alc_len / 2, ext_len); \\\n        alc_inc = size_align_up(alc_inc, sizeof(yyjson_write_ctx)); \\\n        if ((sizeof(usize) < 8) && size_add_is_overflow(alc_len, alc_inc)) \\\n            goto fail_alloc; \\\n        alc_len += alc_inc; \\\n        tmp = (u8 *)alc.realloc(alc.ctx, hdr, alc_len - alc_inc, alc_len); \\\n        if (unlikely(!tmp)) goto fail_alloc; \\\n        ctx_tmp = (yyjson_write_ctx *)(void *)(tmp + (alc_len - ctx_len)); \\\n        memmove((void *)ctx_tmp, (void *)(tmp + ctx_pos), ctx_len); \\\n        ctx = ctx_tmp; \\\n        cur = tmp + cur_pos; \\\n        end = tmp + alc_len; \\\n        hdr = tmp; \\\n    } \\\n} while (false)\n\n#define check_str_len(_len) do { \\\n    if ((sizeof(usize) < 8) && (_len >= (USIZE_MAX - 16) / 6)) \\\n        goto fail_alloc; \\\n} while (false)\n\n    yyjson_val *val;\n    yyjson_type val_type;\n    usize ctn_len, ctn_len_tmp;\n    bool ctn_obj, ctn_obj_tmp, is_key, no_indent;\n    u8 *hdr, *cur, *end, *tmp;\n    yyjson_write_ctx *ctx, *ctx_tmp;\n    usize alc_len, alc_inc, ctx_len, ext_len, str_len, level;\n    const u8 *str_ptr;\n    const char_enc_type *enc_table = get_enc_table_with_flag(flg);\n    bool cpy = (enc_table == enc_table_cpy);\n    bool esc = has_flg(ESCAPE_UNICODE) != 0;\n    bool inv = has_allow(INVALID_UNICODE) != 0;\n    usize spaces = has_flg(PRETTY_TWO_SPACES) ? 2 : 4;\n    bool newline = has_flg(NEWLINE_AT_END) != 0;\n\n    alc_len = root->uni.ofs / sizeof(yyjson_val);\n    alc_len = alc_len * YYJSON_WRITER_ESTIMATED_PRETTY_RATIO + 64;\n    alc_len = size_align_up(alc_len, sizeof(yyjson_write_ctx));\n    hdr = (u8 *)alc.malloc(alc.ctx, alc_len);\n    if (!hdr) goto fail_alloc;\n    cur = hdr;\n    end = hdr + alc_len;\n    ctx = (yyjson_write_ctx *)(void *)end;\n\ndoc_begin:\n    val = constcast(yyjson_val *)root;\n    val_type = unsafe_yyjson_get_type(val);\n    ctn_obj = (val_type == YYJSON_TYPE_OBJ);\n    ctn_len = unsafe_yyjson_get_len(val) << (u8)ctn_obj;\n    *cur++ = (u8)('[' | ((u8)ctn_obj << 5));\n    *cur++ = '\\n';\n    val++;\n    level = 1;\n\nval_begin:\n    val_type = unsafe_yyjson_get_type(val);\n    if (val_type == YYJSON_TYPE_STR) {\n        is_key = (bool)((u8)ctn_obj & (u8)~ctn_len);\n        no_indent = (bool)((u8)ctn_obj & (u8)ctn_len);\n        str_len = unsafe_yyjson_get_len(val);\n        str_ptr = (const u8 *)unsafe_yyjson_get_str(val);\n        check_str_len(str_len);\n        incr_len(str_len * 6 + 16 + (no_indent ? 0 : level * 4));\n        cur = write_indent(cur, no_indent ? 0 : level, spaces);\n        if (likely(cpy) && unsafe_yyjson_get_subtype(val)) {\n            cur = write_str_noesc(cur, str_ptr, str_len);\n        } else {\n            cur = write_str(cur, esc, inv, str_ptr, str_len, enc_table);\n            if (unlikely(!cur)) goto fail_str;\n        }\n        *cur++ = is_key ? ':' : ',';\n        *cur++ = is_key ? ' ' : '\\n';\n        goto val_end;\n    }\n    if (val_type == YYJSON_TYPE_NUM) {\n        no_indent = (bool)((u8)ctn_obj & (u8)ctn_len);\n        incr_len(FP_BUF_LEN + (no_indent ? 0 : level * 4));\n        cur = write_indent(cur, no_indent ? 0 : level, spaces);\n        cur = write_num(cur, val, flg);\n        if (unlikely(!cur)) goto fail_num;\n        *cur++ = ',';\n        *cur++ = '\\n';\n        goto val_end;\n    }\n    if ((val_type & (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) ==\n                    (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) {\n        no_indent = (bool)((u8)ctn_obj & (u8)ctn_len);\n        ctn_len_tmp = unsafe_yyjson_get_len(val);\n        ctn_obj_tmp = (val_type == YYJSON_TYPE_OBJ);\n        if (unlikely(ctn_len_tmp == 0)) {\n            /* write empty container */\n            incr_len(16 + (no_indent ? 0 : level * 4));\n            cur = write_indent(cur, no_indent ? 0 : level, spaces);\n            *cur++ = (u8)('[' | ((u8)ctn_obj_tmp << 5));\n            *cur++ = (u8)(']' | ((u8)ctn_obj_tmp << 5));\n            *cur++ = ',';\n            *cur++ = '\\n';\n            goto val_end;\n        } else {\n            /* push context, setup new container */\n            incr_len(32 + (no_indent ? 0 : level * 4));\n            yyjson_write_ctx_set(--ctx, ctn_len, ctn_obj);\n            ctn_len = ctn_len_tmp << (u8)ctn_obj_tmp;\n            ctn_obj = ctn_obj_tmp;\n            cur = write_indent(cur, no_indent ? 0 : level, spaces);\n            level++;\n            *cur++ = (u8)('[' | ((u8)ctn_obj << 5));\n            *cur++ = '\\n';\n            val++;\n            goto val_begin;\n        }\n    }\n    if (val_type == YYJSON_TYPE_BOOL) {\n        no_indent = (bool)((u8)ctn_obj & (u8)ctn_len);\n        incr_len(16 + (no_indent ? 0 : level * 4));\n        cur = write_indent(cur, no_indent ? 0 : level, spaces);\n        cur = write_bool(cur, unsafe_yyjson_get_bool(val));\n        cur += 2;\n        goto val_end;\n    }\n    if (val_type == YYJSON_TYPE_NULL) {\n        no_indent = (bool)((u8)ctn_obj & (u8)ctn_len);\n        incr_len(16 + (no_indent ? 0 : level * 4));\n        cur = write_indent(cur, no_indent ? 0 : level, spaces);\n        cur = write_null(cur);\n        cur += 2;\n        goto val_end;\n    }\n    if (val_type == YYJSON_TYPE_RAW) {\n        no_indent = (bool)((u8)ctn_obj & (u8)ctn_len);\n        str_len = unsafe_yyjson_get_len(val);\n        str_ptr = (const u8 *)unsafe_yyjson_get_str(val);\n        check_str_len(str_len);\n        incr_len(str_len + 3 + (no_indent ? 0 : level * 4));\n        cur = write_indent(cur, no_indent ? 0 : level, spaces);\n        cur = write_raw(cur, str_ptr, str_len);\n        *cur++ = ',';\n        *cur++ = '\\n';\n        goto val_end;\n    }\n    goto fail_type;\n\nval_end:\n    val++;\n    ctn_len--;\n    if (unlikely(ctn_len == 0)) goto ctn_end;\n    goto val_begin;\n\nctn_end:\n    cur -= 2;\n    *cur++ = '\\n';\n    incr_len(level * 4);\n    cur = write_indent(cur, --level, spaces);\n    *cur++ = (u8)(']' | ((u8)ctn_obj << 5));\n    if (unlikely((u8 *)ctx >= end)) goto doc_end;\n    yyjson_write_ctx_get(ctx++, &ctn_len, &ctn_obj);\n    ctn_len--;\n    *cur++ = ',';\n    *cur++ = '\\n';\n    if (likely(ctn_len > 0)) {\n        goto val_begin;\n    } else {\n        goto ctn_end;\n    }\n\ndoc_end:\n    if (newline) {\n        incr_len(2);\n        *cur++ = '\\n';\n    }\n    *cur = '\\0';\n    *dat_len = (usize)(cur - hdr);\n    memset(err, 0, sizeof(yyjson_write_err));\n    return hdr;\n\nfail_alloc: return_err(MEMORY_ALLOCATION, MSG_MALLOC);\nfail_type:  return_err(INVALID_VALUE_TYPE, MSG_ERR_TYPE);\nfail_num:   return_err(NAN_OR_INF, MSG_NAN_INF);\nfail_str:   return_err(INVALID_STRING, MSG_ERR_UTF8);\n\n#undef return_err\n#undef incr_len\n#undef check_str_len\n}\n\n\n\n/*==============================================================================\n * MARK: - JSON Writer (Public)\n *============================================================================*/\n\nchar *yyjson_val_write_opts(const yyjson_val *val,\n                            yyjson_write_flag flg,\n                            const yyjson_alc *alc_ptr,\n                            usize *dat_len,\n                            yyjson_write_err *err) {\n    yyjson_write_err tmp_err;\n    usize tmp_dat_len;\n    yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC;\n    yyjson_val *root = constcast(yyjson_val *)val;\n\n    if (!err) err = &tmp_err;\n    if (!dat_len) dat_len = &tmp_dat_len;\n\n    if (unlikely(!root)) {\n        *dat_len = 0;\n        err->msg = \"input JSON is NULL\";\n        err->code = YYJSON_READ_ERROR_INVALID_PARAMETER;\n        return NULL;\n    }\n\n    if (!unsafe_yyjson_is_ctn(root) || unsafe_yyjson_get_len(root) == 0) {\n        return (char *)yyjson_write_single(root, flg, alc, dat_len, err);\n    } else if (flg & (YYJSON_WRITE_PRETTY | YYJSON_WRITE_PRETTY_TWO_SPACES)) {\n        return (char *)yyjson_write_pretty(root, flg, alc, dat_len, err);\n    } else {\n        return (char *)yyjson_write_minify(root, flg, alc, dat_len, err);\n    }\n}\n\nchar *yyjson_write_opts(const yyjson_doc *doc,\n                        yyjson_write_flag flg,\n                        const yyjson_alc *alc_ptr,\n                        usize *dat_len,\n                        yyjson_write_err *err) {\n    yyjson_val *root = doc ? doc->root : NULL;\n    return yyjson_val_write_opts(root, flg, alc_ptr, dat_len, err);\n}\n\nbool yyjson_val_write_file(const char *path,\n                           const yyjson_val *val,\n                           yyjson_write_flag flg,\n                           const yyjson_alc *alc_ptr,\n                           yyjson_write_err *err) {\n    yyjson_write_err tmp_err;\n    yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC;\n    u8 *dat;\n    usize dat_len = 0;\n    yyjson_val *root = constcast(yyjson_val *)val;\n    bool suc;\n\n    if (!err) err = &tmp_err;\n    if (unlikely(!path || !*path)) {\n        err->msg = \"input path is invalid\";\n        err->code = YYJSON_READ_ERROR_INVALID_PARAMETER;\n        return false;\n    }\n\n    dat = (u8 *)yyjson_val_write_opts(root, flg, &alc, &dat_len, err);\n    if (unlikely(!dat)) return false;\n    suc = write_dat_to_file(path, dat, dat_len, err);\n    alc.free(alc.ctx, dat);\n    return suc;\n}\n\nbool yyjson_val_write_fp(FILE *fp,\n                         const yyjson_val *val,\n                         yyjson_write_flag flg,\n                         const yyjson_alc *alc_ptr,\n                         yyjson_write_err *err) {\n    yyjson_write_err tmp_err;\n    yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC;\n    u8 *dat;\n    usize dat_len = 0;\n    yyjson_val *root = constcast(yyjson_val *)val;\n    bool suc;\n\n    if (!err) err = &tmp_err;\n    if (unlikely(!fp)) {\n        err->msg = \"input fp is invalid\";\n        err->code = YYJSON_READ_ERROR_INVALID_PARAMETER;\n        return false;\n    }\n\n    dat = (u8 *)yyjson_val_write_opts(root, flg, &alc, &dat_len, err);\n    if (unlikely(!dat)) return false;\n    suc = write_dat_to_fp(fp, dat, dat_len, err);\n    alc.free(alc.ctx, dat);\n    return suc;\n}\n\nbool yyjson_write_file(const char *path,\n                       const yyjson_doc *doc,\n                       yyjson_write_flag flg,\n                       const yyjson_alc *alc_ptr,\n                       yyjson_write_err *err) {\n    yyjson_val *root = doc ? doc->root : NULL;\n    return yyjson_val_write_file(path, root, flg, alc_ptr, err);\n}\n\nbool yyjson_write_fp(FILE *fp,\n                     const yyjson_doc *doc,\n                     yyjson_write_flag flg,\n                     const yyjson_alc *alc_ptr,\n                     yyjson_write_err *err) {\n    yyjson_val *root = doc ? doc->root : NULL;\n    return yyjson_val_write_fp(fp, root, flg, alc_ptr, err);\n}\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Writer Implementation (Private)\n *============================================================================*/\n\ntypedef struct yyjson_mut_write_ctx {\n    usize tag;\n    yyjson_mut_val *ctn;\n} yyjson_mut_write_ctx;\n\nstatic_inline void yyjson_mut_write_ctx_set(yyjson_mut_write_ctx *ctx,\n                                            yyjson_mut_val *ctn,\n                                            usize size, bool is_obj) {\n    ctx->tag = (size << 1) | (usize)is_obj;\n    ctx->ctn = ctn;\n}\n\nstatic_inline void yyjson_mut_write_ctx_get(yyjson_mut_write_ctx *ctx,\n                                            yyjson_mut_val **ctn,\n                                            usize *size, bool *is_obj) {\n    usize tag = ctx->tag;\n    *size = tag >> 1;\n    *is_obj = (bool)(tag & 1);\n    *ctn = ctx->ctn;\n}\n\n/** Get the estimated number of values for the mutable JSON document. */\nstatic_inline usize yyjson_mut_doc_estimated_val_num(\n    const yyjson_mut_doc *doc) {\n    usize sum = 0;\n    yyjson_val_chunk *chunk = doc->val_pool.chunks;\n    while (chunk) {\n        sum += chunk->chunk_size / sizeof(yyjson_mut_val) - 1;\n        if (chunk == doc->val_pool.chunks) {\n            sum -= (usize)(doc->val_pool.end - doc->val_pool.cur);\n        }\n        chunk = chunk->next;\n    }\n    return sum;\n}\n\n/** Write single JSON value. */\nstatic_inline u8 *yyjson_mut_write_single(yyjson_mut_val *val,\n                                          yyjson_write_flag flg,\n                                          yyjson_alc alc,\n                                          usize *dat_len,\n                                          yyjson_write_err *err) {\n    return yyjson_write_single((yyjson_val *)val, flg, alc, dat_len, err);\n}\n\n/** Write JSON document minify.\n    The root of this document should be a non-empty container. */\nstatic_inline u8 *yyjson_mut_write_minify(const yyjson_mut_val *root,\n                                          usize estimated_val_num,\n                                          yyjson_write_flag flg,\n                                          yyjson_alc alc,\n                                          usize *dat_len,\n                                          yyjson_write_err *err) {\n#define return_err(_code, _msg) do { \\\n    *dat_len = 0; \\\n    err->code = YYJSON_WRITE_ERROR_##_code; \\\n    err->msg = _msg; \\\n    if (hdr) alc.free(alc.ctx, hdr); \\\n    return NULL; \\\n} while (false)\n\n#define incr_len(_len) do { \\\n    ext_len = (usize)(_len); \\\n    if (unlikely((u8 *)(cur + ext_len) >= (u8 *)ctx)) { \\\n        usize ctx_pos = (usize)((u8 *)ctx - hdr); \\\n        usize cur_pos = (usize)(cur - hdr); \\\n        ctx_len = (usize)(end - (u8 *)ctx); \\\n        alc_inc = yyjson_max(alc_len / 2, ext_len); \\\n        alc_inc = size_align_up(alc_inc, sizeof(yyjson_mut_write_ctx)); \\\n        if ((sizeof(usize) < 8) && size_add_is_overflow(alc_len, alc_inc)) \\\n            goto fail_alloc; \\\n        alc_len += alc_inc; \\\n        tmp = (u8 *)alc.realloc(alc.ctx, hdr, alc_len - alc_inc, alc_len); \\\n        if (unlikely(!tmp)) goto fail_alloc; \\\n        ctx_tmp = (yyjson_mut_write_ctx *)(void *)(tmp + (alc_len - ctx_len)); \\\n        memmove((void *)ctx_tmp, (void *)(tmp + ctx_pos), ctx_len); \\\n        ctx = ctx_tmp; \\\n        cur = tmp + cur_pos; \\\n        end = tmp + alc_len; \\\n        hdr = tmp; \\\n    } \\\n} while (false)\n\n#define check_str_len(_len) do { \\\n    if ((sizeof(usize) < 8) && (_len >= (USIZE_MAX - 16) / 6)) \\\n        goto fail_alloc; \\\n} while (false)\n\n    yyjson_mut_val *val, *ctn;\n    yyjson_type val_type;\n    usize ctn_len, ctn_len_tmp;\n    bool ctn_obj, ctn_obj_tmp, is_key;\n    u8 *hdr, *cur, *end, *tmp;\n    yyjson_mut_write_ctx *ctx, *ctx_tmp;\n    usize alc_len, alc_inc, ctx_len, ext_len, str_len;\n    const u8 *str_ptr;\n    const char_enc_type *enc_table = get_enc_table_with_flag(flg);\n    bool cpy = (enc_table == enc_table_cpy);\n    bool esc = has_flg(ESCAPE_UNICODE) != 0;\n    bool inv = has_allow(INVALID_UNICODE) != 0;\n    bool newline = has_flg(NEWLINE_AT_END) != 0;\n\n    alc_len = estimated_val_num * YYJSON_WRITER_ESTIMATED_MINIFY_RATIO + 64;\n    alc_len = size_align_up(alc_len, sizeof(yyjson_mut_write_ctx));\n    hdr = (u8 *)alc.malloc(alc.ctx, alc_len);\n    if (!hdr) goto fail_alloc;\n    cur = hdr;\n    end = hdr + alc_len;\n    ctx = (yyjson_mut_write_ctx *)(void *)end;\n\ndoc_begin:\n    val = constcast(yyjson_mut_val *)root;\n    val_type = unsafe_yyjson_get_type(val);\n    ctn_obj = (val_type == YYJSON_TYPE_OBJ);\n    ctn_len = unsafe_yyjson_get_len(val) << (u8)ctn_obj;\n    *cur++ = (u8)('[' | ((u8)ctn_obj << 5));\n    ctn = val;\n    val = (yyjson_mut_val *)val->uni.ptr; /* tail */\n    val = ctn_obj ? val->next->next : val->next;\n\nval_begin:\n    val_type = unsafe_yyjson_get_type(val);\n    if (val_type == YYJSON_TYPE_STR) {\n        is_key = ((u8)ctn_obj & (u8)~ctn_len);\n        str_len = unsafe_yyjson_get_len(val);\n        str_ptr = (const u8 *)unsafe_yyjson_get_str(val);\n        check_str_len(str_len);\n        incr_len(str_len * 6 + 16);\n        if (likely(cpy) && unsafe_yyjson_get_subtype(val)) {\n            cur = write_str_noesc(cur, str_ptr, str_len);\n        } else {\n            cur = write_str(cur, esc, inv, str_ptr, str_len, enc_table);\n            if (unlikely(!cur)) goto fail_str;\n        }\n        *cur++ = is_key ? ':' : ',';\n        goto val_end;\n    }\n    if (val_type == YYJSON_TYPE_NUM) {\n        incr_len(FP_BUF_LEN);\n        cur = write_num(cur, (yyjson_val *)val, flg);\n        if (unlikely(!cur)) goto fail_num;\n        *cur++ = ',';\n        goto val_end;\n    }\n    if ((val_type & (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) ==\n                    (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) {\n        ctn_len_tmp = unsafe_yyjson_get_len(val);\n        ctn_obj_tmp = (val_type == YYJSON_TYPE_OBJ);\n        incr_len(16);\n        if (unlikely(ctn_len_tmp == 0)) {\n            /* write empty container */\n            *cur++ = (u8)('[' | ((u8)ctn_obj_tmp << 5));\n            *cur++ = (u8)(']' | ((u8)ctn_obj_tmp << 5));\n            *cur++ = ',';\n            goto val_end;\n        } else {\n            /* push context, setup new container */\n            yyjson_mut_write_ctx_set(--ctx, ctn, ctn_len, ctn_obj);\n            ctn_len = ctn_len_tmp << (u8)ctn_obj_tmp;\n            ctn_obj = ctn_obj_tmp;\n            *cur++ = (u8)('[' | ((u8)ctn_obj << 5));\n            ctn = val;\n            val = (yyjson_mut_val *)ctn->uni.ptr; /* tail */\n            val = ctn_obj ? val->next->next : val->next;\n            goto val_begin;\n        }\n    }\n    if (val_type == YYJSON_TYPE_BOOL) {\n        incr_len(16);\n        cur = write_bool(cur, unsafe_yyjson_get_bool(val));\n        cur++;\n        goto val_end;\n    }\n    if (val_type == YYJSON_TYPE_NULL) {\n        incr_len(16);\n        cur = write_null(cur);\n        cur++;\n        goto val_end;\n    }\n    if (val_type == YYJSON_TYPE_RAW) {\n        str_len = unsafe_yyjson_get_len(val);\n        str_ptr = (const u8 *)unsafe_yyjson_get_str(val);\n        check_str_len(str_len);\n        incr_len(str_len + 2);\n        cur = write_raw(cur, str_ptr, str_len);\n        *cur++ = ',';\n        goto val_end;\n    }\n    goto fail_type;\n\nval_end:\n    ctn_len--;\n    if (unlikely(ctn_len == 0)) goto ctn_end;\n    val = val->next;\n    goto val_begin;\n\nctn_end:\n    cur--;\n    *cur++ = (u8)(']' | ((u8)ctn_obj << 5));\n    *cur++ = ',';\n    if (unlikely((u8 *)ctx >= end)) goto doc_end;\n    val = ctn->next;\n    yyjson_mut_write_ctx_get(ctx++, &ctn, &ctn_len, &ctn_obj);\n    ctn_len--;\n    if (likely(ctn_len > 0)) {\n        goto val_begin;\n    } else {\n        goto ctn_end;\n    }\n\ndoc_end:\n    if (newline) {\n        incr_len(2);\n        *(cur - 1) = '\\n';\n        cur++;\n    }\n    *--cur = '\\0';\n    *dat_len = (usize)(cur - hdr);\n    err->code = YYJSON_WRITE_SUCCESS;\n    err->msg = NULL;\n    return hdr;\n\nfail_alloc: return_err(MEMORY_ALLOCATION, MSG_MALLOC);\nfail_type:  return_err(INVALID_VALUE_TYPE, MSG_ERR_TYPE);\nfail_num:   return_err(NAN_OR_INF, MSG_NAN_INF);\nfail_str:   return_err(INVALID_STRING, MSG_ERR_UTF8);\n\n#undef return_err\n#undef incr_len\n#undef check_str_len\n}\n\n/** Write JSON document pretty.\n    The root of this document should be a non-empty container. */\nstatic_inline u8 *yyjson_mut_write_pretty(const yyjson_mut_val *root,\n                                          usize estimated_val_num,\n                                          yyjson_write_flag flg,\n                                          yyjson_alc alc,\n                                          usize *dat_len,\n                                          yyjson_write_err *err) {\n#define return_err(_code, _msg) do { \\\n    *dat_len = 0; \\\n    err->code = YYJSON_WRITE_ERROR_##_code; \\\n    err->msg = _msg; \\\n    if (hdr) alc.free(alc.ctx, hdr); \\\n    return NULL; \\\n} while (false)\n\n#define incr_len(_len) do { \\\n    ext_len = (usize)(_len); \\\n    if (unlikely((u8 *)(cur + ext_len) >= (u8 *)ctx)) { \\\n        usize ctx_pos = (usize)((u8 *)ctx - hdr); \\\n        usize cur_pos = (usize)(cur - hdr); \\\n        ctx_len = (usize)(end - (u8 *)ctx); \\\n        alc_inc = yyjson_max(alc_len / 2, ext_len); \\\n        alc_inc = size_align_up(alc_inc, sizeof(yyjson_mut_write_ctx)); \\\n        if ((sizeof(usize) < 8) && size_add_is_overflow(alc_len, alc_inc)) \\\n            goto fail_alloc; \\\n        alc_len += alc_inc; \\\n        tmp = (u8 *)alc.realloc(alc.ctx, hdr, alc_len - alc_inc, alc_len); \\\n        if (unlikely(!tmp)) goto fail_alloc; \\\n        ctx_tmp = (yyjson_mut_write_ctx *)(void *)(tmp + (alc_len - ctx_len)); \\\n        memmove((void *)ctx_tmp, (void *)(tmp + ctx_pos), ctx_len); \\\n        ctx = ctx_tmp; \\\n        cur = tmp + cur_pos; \\\n        end = tmp + alc_len; \\\n        hdr = tmp; \\\n    } \\\n} while (false)\n\n#define check_str_len(_len) do { \\\n    if ((sizeof(usize) < 8) && (_len >= (USIZE_MAX - 16) / 6)) \\\n        goto fail_alloc; \\\n} while (false)\n\n    yyjson_mut_val *val, *ctn;\n    yyjson_type val_type;\n    usize ctn_len, ctn_len_tmp;\n    bool ctn_obj, ctn_obj_tmp, is_key, no_indent;\n    u8 *hdr, *cur, *end, *tmp;\n    yyjson_mut_write_ctx *ctx, *ctx_tmp;\n    usize alc_len, alc_inc, ctx_len, ext_len, str_len, level;\n    const u8 *str_ptr;\n    const char_enc_type *enc_table = get_enc_table_with_flag(flg);\n    bool cpy = (enc_table == enc_table_cpy);\n    bool esc = has_flg(ESCAPE_UNICODE) != 0;\n    bool inv = has_allow(INVALID_UNICODE) != 0;\n    usize spaces = has_flg(PRETTY_TWO_SPACES) ? 2 : 4;\n    bool newline = has_flg(NEWLINE_AT_END) != 0;\n\n    alc_len = estimated_val_num * YYJSON_WRITER_ESTIMATED_PRETTY_RATIO + 64;\n    alc_len = size_align_up(alc_len, sizeof(yyjson_mut_write_ctx));\n    hdr = (u8 *)alc.malloc(alc.ctx, alc_len);\n    if (!hdr) goto fail_alloc;\n    cur = hdr;\n    end = hdr + alc_len;\n    ctx = (yyjson_mut_write_ctx *)(void *)end;\n\ndoc_begin:\n    val = constcast(yyjson_mut_val *)root;\n    val_type = unsafe_yyjson_get_type(val);\n    ctn_obj = (val_type == YYJSON_TYPE_OBJ);\n    ctn_len = unsafe_yyjson_get_len(val) << (u8)ctn_obj;\n    *cur++ = (u8)('[' | ((u8)ctn_obj << 5));\n    *cur++ = '\\n';\n    ctn = val;\n    val = (yyjson_mut_val *)val->uni.ptr; /* tail */\n    val = ctn_obj ? val->next->next : val->next;\n    level = 1;\n\nval_begin:\n    val_type = unsafe_yyjson_get_type(val);\n    if (val_type == YYJSON_TYPE_STR) {\n        is_key = (bool)((u8)ctn_obj & (u8)~ctn_len);\n        no_indent = (bool)((u8)ctn_obj & (u8)ctn_len);\n        str_len = unsafe_yyjson_get_len(val);\n        str_ptr = (const u8 *)unsafe_yyjson_get_str(val);\n        check_str_len(str_len);\n        incr_len(str_len * 6 + 16 + (no_indent ? 0 : level * 4));\n        cur = write_indent(cur, no_indent ? 0 : level, spaces);\n        if (likely(cpy) && unsafe_yyjson_get_subtype(val)) {\n            cur = write_str_noesc(cur, str_ptr, str_len);\n        } else {\n            cur = write_str(cur, esc, inv, str_ptr, str_len, enc_table);\n            if (unlikely(!cur)) goto fail_str;\n        }\n        *cur++ = is_key ? ':' : ',';\n        *cur++ = is_key ? ' ' : '\\n';\n        goto val_end;\n    }\n    if (val_type == YYJSON_TYPE_NUM) {\n        no_indent = (bool)((u8)ctn_obj & (u8)ctn_len);\n        incr_len(FP_BUF_LEN + (no_indent ? 0 : level * 4));\n        cur = write_indent(cur, no_indent ? 0 : level, spaces);\n        cur = write_num(cur, (yyjson_val *)val, flg);\n        if (unlikely(!cur)) goto fail_num;\n        *cur++ = ',';\n        *cur++ = '\\n';\n        goto val_end;\n    }\n    if ((val_type & (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) ==\n                    (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) {\n        no_indent = (bool)((u8)ctn_obj & (u8)ctn_len);\n        ctn_len_tmp = unsafe_yyjson_get_len(val);\n        ctn_obj_tmp = (val_type == YYJSON_TYPE_OBJ);\n        if (unlikely(ctn_len_tmp == 0)) {\n            /* write empty container */\n            incr_len(16 + (no_indent ? 0 : level * 4));\n            cur = write_indent(cur, no_indent ? 0 : level, spaces);\n            *cur++ = (u8)('[' | ((u8)ctn_obj_tmp << 5));\n            *cur++ = (u8)(']' | ((u8)ctn_obj_tmp << 5));\n            *cur++ = ',';\n            *cur++ = '\\n';\n            goto val_end;\n        } else {\n            /* push context, setup new container */\n            incr_len(32 + (no_indent ? 0 : level * 4));\n            yyjson_mut_write_ctx_set(--ctx, ctn, ctn_len, ctn_obj);\n            ctn_len = ctn_len_tmp << (u8)ctn_obj_tmp;\n            ctn_obj = ctn_obj_tmp;\n            cur = write_indent(cur, no_indent ? 0 : level, spaces);\n            level++;\n            *cur++ = (u8)('[' | ((u8)ctn_obj << 5));\n            *cur++ = '\\n';\n            ctn = val;\n            val = (yyjson_mut_val *)ctn->uni.ptr; /* tail */\n            val = ctn_obj ? val->next->next : val->next;\n            goto val_begin;\n        }\n    }\n    if (val_type == YYJSON_TYPE_BOOL) {\n        no_indent = (bool)((u8)ctn_obj & (u8)ctn_len);\n        incr_len(16 + (no_indent ? 0 : level * 4));\n        cur = write_indent(cur, no_indent ? 0 : level, spaces);\n        cur = write_bool(cur, unsafe_yyjson_get_bool(val));\n        cur += 2;\n        goto val_end;\n    }\n    if (val_type == YYJSON_TYPE_NULL) {\n        no_indent = (bool)((u8)ctn_obj & (u8)ctn_len);\n        incr_len(16 + (no_indent ? 0 : level * 4));\n        cur = write_indent(cur, no_indent ? 0 : level, spaces);\n        cur = write_null(cur);\n        cur += 2;\n        goto val_end;\n    }\n    if (val_type == YYJSON_TYPE_RAW) {\n        no_indent = (bool)((u8)ctn_obj & (u8)ctn_len);\n        str_len = unsafe_yyjson_get_len(val);\n        str_ptr = (const u8 *)unsafe_yyjson_get_str(val);\n        check_str_len(str_len);\n        incr_len(str_len + 3 + (no_indent ? 0 : level * 4));\n        cur = write_indent(cur, no_indent ? 0 : level, spaces);\n        cur = write_raw(cur, str_ptr, str_len);\n        *cur++ = ',';\n        *cur++ = '\\n';\n        goto val_end;\n    }\n    goto fail_type;\n\nval_end:\n    ctn_len--;\n    if (unlikely(ctn_len == 0)) goto ctn_end;\n    val = val->next;\n    goto val_begin;\n\nctn_end:\n    cur -= 2;\n    *cur++ = '\\n';\n    incr_len(level * 4);\n    cur = write_indent(cur, --level, spaces);\n    *cur++ = (u8)(']' | ((u8)ctn_obj << 5));\n    if (unlikely((u8 *)ctx >= end)) goto doc_end;\n    val = ctn->next;\n    yyjson_mut_write_ctx_get(ctx++, &ctn, &ctn_len, &ctn_obj);\n    ctn_len--;\n    *cur++ = ',';\n    *cur++ = '\\n';\n    if (likely(ctn_len > 0)) {\n        goto val_begin;\n    } else {\n        goto ctn_end;\n    }\n\ndoc_end:\n    if (newline) {\n        incr_len(2);\n        *cur++ = '\\n';\n    }\n    *cur = '\\0';\n    *dat_len = (usize)(cur - hdr);\n    err->code = YYJSON_WRITE_SUCCESS;\n    err->msg = NULL;\n    return hdr;\n\nfail_alloc: return_err(MEMORY_ALLOCATION, MSG_MALLOC);\nfail_type:  return_err(INVALID_VALUE_TYPE, MSG_ERR_TYPE);\nfail_num:   return_err(NAN_OR_INF, MSG_NAN_INF);\nfail_str:   return_err(INVALID_STRING, MSG_ERR_UTF8);\n\n#undef return_err\n#undef incr_len\n#undef check_str_len\n}\n\nstatic char *yyjson_mut_write_opts_impl(const yyjson_mut_val *val,\n                                        usize estimated_val_num,\n                                        yyjson_write_flag flg,\n                                        const yyjson_alc *alc_ptr,\n                                        usize *dat_len,\n                                        yyjson_write_err *err) {\n    yyjson_write_err tmp_err;\n    usize tmp_dat_len;\n    yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC;\n    yyjson_mut_val *root = constcast(yyjson_mut_val *)val;\n\n    if (!err) err = &tmp_err;\n    if (!dat_len) dat_len = &tmp_dat_len;\n\n    if (unlikely(!root)) {\n        *dat_len = 0;\n        err->msg = \"input JSON is NULL\";\n        err->code = YYJSON_WRITE_ERROR_INVALID_PARAMETER;\n        return NULL;\n    }\n\n    if (!unsafe_yyjson_is_ctn(root) || unsafe_yyjson_get_len(root) == 0) {\n        return (char *)yyjson_mut_write_single(root, flg, alc, dat_len, err);\n    } else if (flg & (YYJSON_WRITE_PRETTY | YYJSON_WRITE_PRETTY_TWO_SPACES)) {\n        return (char *)yyjson_mut_write_pretty(root, estimated_val_num,\n                                               flg, alc, dat_len, err);\n    } else {\n        return (char *)yyjson_mut_write_minify(root, estimated_val_num,\n                                               flg, alc, dat_len, err);\n    }\n}\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Writer (Public)\n *============================================================================*/\n\nchar *yyjson_mut_val_write_opts(const yyjson_mut_val *val,\n                                yyjson_write_flag flg,\n                                const yyjson_alc *alc_ptr,\n                                usize *dat_len,\n                                yyjson_write_err *err) {\n    return yyjson_mut_write_opts_impl(val, 0, flg, alc_ptr, dat_len, err);\n}\n\nchar *yyjson_mut_write_opts(const yyjson_mut_doc *doc,\n                            yyjson_write_flag flg,\n                            const yyjson_alc *alc_ptr,\n                            usize *dat_len,\n                            yyjson_write_err *err) {\n    yyjson_mut_val *root;\n    usize estimated_val_num;\n    if (likely(doc)) {\n        root = doc->root;\n        estimated_val_num = yyjson_mut_doc_estimated_val_num(doc);\n    } else {\n        root = NULL;\n        estimated_val_num = 0;\n    }\n    return yyjson_mut_write_opts_impl(root, estimated_val_num,\n                                      flg, alc_ptr, dat_len, err);\n}\n\nbool yyjson_mut_val_write_file(const char *path,\n                               const yyjson_mut_val *val,\n                               yyjson_write_flag flg,\n                               const yyjson_alc *alc_ptr,\n                               yyjson_write_err *err) {\n    yyjson_write_err tmp_err;\n    yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC;\n    u8 *dat;\n    usize dat_len = 0;\n    yyjson_mut_val *root = constcast(yyjson_mut_val *)val;\n    bool suc;\n\n    if (!err) err = &tmp_err;\n    if (unlikely(!path || !*path)) {\n        err->msg = \"input path is invalid\";\n        err->code = YYJSON_WRITE_ERROR_INVALID_PARAMETER;\n        return false;\n    }\n\n    dat = (u8 *)yyjson_mut_val_write_opts(root, flg, &alc, &dat_len, err);\n    if (unlikely(!dat)) return false;\n    suc = write_dat_to_file(path, dat, dat_len, err);\n    alc.free(alc.ctx, dat);\n    return suc;\n}\n\nbool yyjson_mut_val_write_fp(FILE *fp,\n                             const yyjson_mut_val *val,\n                             yyjson_write_flag flg,\n                             const yyjson_alc *alc_ptr,\n                             yyjson_write_err *err) {\n    yyjson_write_err tmp_err;\n    yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC;\n    u8 *dat;\n    usize dat_len = 0;\n    yyjson_mut_val *root = constcast(yyjson_mut_val *)val;\n    bool suc;\n\n    if (!err) err = &tmp_err;\n    if (unlikely(!fp)) {\n        err->msg = \"input fp is invalid\";\n        err->code = YYJSON_WRITE_ERROR_INVALID_PARAMETER;\n        return false;\n    }\n\n    dat = (u8 *)yyjson_mut_val_write_opts(root, flg, &alc, &dat_len, err);\n    if (unlikely(!dat)) return false;\n    suc = write_dat_to_fp(fp, dat, dat_len, err);\n    alc.free(alc.ctx, dat);\n    return suc;\n}\n\nbool yyjson_mut_write_file(const char *path,\n                           const yyjson_mut_doc *doc,\n                           yyjson_write_flag flg,\n                           const yyjson_alc *alc_ptr,\n                           yyjson_write_err *err) {\n    yyjson_mut_val *root = doc ? doc->root : NULL;\n    return yyjson_mut_val_write_file(path, root, flg, alc_ptr, err);\n}\n\nbool yyjson_mut_write_fp(FILE *fp,\n                         const yyjson_mut_doc *doc,\n                         yyjson_write_flag flg,\n                         const yyjson_alc *alc_ptr,\n                         yyjson_write_err *err) {\n    yyjson_mut_val *root = doc ? doc->root : NULL;\n    return yyjson_mut_val_write_fp(fp, root, flg, alc_ptr, err);\n}\n\n#undef has_flg\n#undef has_allow\n#endif /* YYJSON_DISABLE_WRITER */\n\n\n\n#if !YYJSON_DISABLE_UTILS\n\n/*==============================================================================\n * MARK: - JSON Pointer API (RFC 6901) (Public)\n *============================================================================*/\n\n/**\n Get a token from JSON pointer string.\n @param ptr [in]  string that points to current token prefix `/`\n            [out] string that points to next token prefix `/`, or string end\n @param end [in] end of the entire JSON Pointer string\n @param len [out] unescaped token length\n @param esc [out] number of escaped characters in this token\n @return head of the token, or NULL if syntax error\n */\nstatic_inline const char *ptr_next_token(const char **ptr, const char *end,\n                                         usize *len, usize *esc) {\n    const char *hdr = *ptr + 1;\n    const char *cur = hdr;\n    /* skip unescaped characters */\n    while (cur < end && *cur != '/' && *cur != '~') cur++;\n    if (likely(cur == end || *cur != '~')) {\n        /* no escaped characters, return */\n        *ptr = cur;\n        *len = (usize)(cur - hdr);\n        *esc = 0;\n        return hdr;\n    } else {\n        /* handle escaped characters */\n        usize esc_num = 0;\n        while (cur < end && *cur != '/') {\n            if (*cur++ == '~') {\n                if (cur == end || (*cur != '0' && *cur != '1')) {\n                    *ptr = cur - 1;\n                    return NULL;\n                }\n                esc_num++;\n            }\n        }\n        *ptr = cur;\n        *len = (usize)(cur - hdr) - esc_num;\n        *esc = esc_num;\n        return hdr;\n    }\n}\n\n/**\n Convert token string to index.\n @param cur [in]  token head\n @param len [in]  token length\n @param idx [out] the index number, or USIZE_MAX if token is '-'\n @return true if token is a valid array index\n */\nstatic_inline bool ptr_token_to_idx(const char *cur, usize len, usize *idx) {\n    const char *end = cur + len;\n    usize num = 0, add;\n    if (unlikely(len == 0 || len > USIZE_SAFE_DIG)) return false;\n    if (*cur == '0') {\n        if (unlikely(len > 1)) return false;\n        *idx = 0;\n        return true;\n    }\n    if (*cur == '-') {\n        if (unlikely(len > 1)) return false;\n        *idx = USIZE_MAX;\n        return true;\n    }\n    for (; cur < end && (add = (usize)((u8)*cur - (u8)'0')) <= 9; cur++) {\n        num = num * 10 + add;\n    }\n    if (unlikely(num == 0 || cur < end)) return false;\n    *idx = num;\n    return true;\n}\n\n/**\n Compare JSON key with token.\n @param key a string key (yyjson_val or yyjson_mut_val)\n @param token a JSON pointer token\n @param len unescaped token length\n @param esc number of escaped characters in this token\n @return true if `str` is equals to `token`\n */\nstatic_inline bool ptr_token_eq(void *key,\n                                const char *token, usize len, usize esc) {\n    yyjson_val *val = (yyjson_val *)key;\n    if (unsafe_yyjson_get_len(val) != len) return false;\n    if (likely(!esc)) {\n        return memcmp(val->uni.str, token, len) == 0;\n    } else {\n        const char *str = val->uni.str;\n        for (; len-- > 0; token++, str++) {\n            if (*token == '~') {\n                if (*str != (*++token == '0' ? '~' : '/')) return false;\n            } else {\n                if (*str != *token) return false;\n            }\n        }\n        return true;\n    }\n}\n\n/**\n Get a value from array by token.\n @param arr   an array, should not be NULL or non-array type\n @param token a JSON pointer token\n @param len   unescaped token length\n @param esc   number of escaped characters in this token\n @return value at index, or NULL if token is not index or index is out of range\n */\nstatic_inline yyjson_val *ptr_arr_get(yyjson_val *arr, const char *token,\n                                      usize len, usize esc) {\n    yyjson_val *val = unsafe_yyjson_get_first(arr);\n    usize num = unsafe_yyjson_get_len(arr), idx = 0;\n    if (unlikely(num == 0)) return NULL;\n    if (unlikely(!ptr_token_to_idx(token, len, &idx))) return NULL;\n    if (unlikely(idx >= num)) return NULL;\n    if (unsafe_yyjson_arr_is_flat(arr)) {\n        return val + idx;\n    } else {\n        while (idx-- > 0) val = unsafe_yyjson_get_next(val);\n        return val;\n    }\n}\n\n/**\n Get a value from object by token.\n @param obj   [in] an object, should not be NULL or non-object type\n @param token [in] a JSON pointer token\n @param len   [in] unescaped token length\n @param esc   [in] number of escaped characters in this token\n @return value associated with the token, or NULL if no value\n */\nstatic_inline yyjson_val *ptr_obj_get(yyjson_val *obj, const char *token,\n                                      usize len, usize esc) {\n    yyjson_val *key = unsafe_yyjson_get_first(obj);\n    usize num = unsafe_yyjson_get_len(obj);\n    if (unlikely(num == 0)) return NULL;\n    for (; num > 0; num--, key = unsafe_yyjson_get_next(key + 1)) {\n        if (ptr_token_eq(key, token, len, esc)) return key + 1;\n    }\n    return NULL;\n}\n\n/**\n Get a value from array by token.\n @param arr   [in] an array, should not be NULL or non-array type\n @param token [in] a JSON pointer token\n @param len   [in] unescaped token length\n @param esc   [in] number of escaped characters in this token\n @param pre   [out] previous (sibling) value of the returned value\n @param last  [out] whether index is last\n @return value at index, or NULL if token is not index or index is out of range\n */\nstatic_inline yyjson_mut_val *ptr_mut_arr_get(yyjson_mut_val *arr,\n                                              const char *token,\n                                              usize len, usize esc,\n                                              yyjson_mut_val **pre,\n                                              bool *last) {\n    yyjson_mut_val *val = (yyjson_mut_val *)arr->uni.ptr; /* last (tail) */\n    usize num = unsafe_yyjson_get_len(arr), idx;\n    if (last) *last = false;\n    if (pre) *pre = NULL;\n    if (unlikely(num == 0)) {\n        if (last && len == 1 && (*token == '0' || *token == '-')) *last = true;\n        return NULL;\n    }\n    if (unlikely(!ptr_token_to_idx(token, len, &idx))) return NULL;\n    if (last) *last = (idx == num || idx == USIZE_MAX);\n    if (unlikely(idx >= num)) return NULL;\n    while (idx-- > 0) val = val->next;\n    if (pre) *pre = val;\n    return val->next;\n}\n\n/**\n Get a value from object by token.\n @param obj   [in] an object, should not be NULL or non-object type\n @param token [in] a JSON pointer token\n @param len   [in] unescaped token length\n @param esc   [in] number of escaped characters in this token\n @param pre   [out] previous (sibling) key of the returned value's key\n @return value associated with the token, or NULL if no value\n */\nstatic_inline yyjson_mut_val *ptr_mut_obj_get(yyjson_mut_val *obj,\n                                              const char *token,\n                                              usize len, usize esc,\n                                              yyjson_mut_val **pre) {\n    yyjson_mut_val *pre_key = (yyjson_mut_val *)obj->uni.ptr, *key;\n    usize num = unsafe_yyjson_get_len(obj);\n    if (pre) *pre = NULL;\n    if (unlikely(num == 0)) return NULL;\n    for (; num > 0; num--, pre_key = key) {\n        key = pre_key->next->next;\n        if (ptr_token_eq(key, token, len, esc)) {\n            if (pre) *pre = pre_key;\n            return key->next;\n        }\n    }\n    return NULL;\n}\n\n/**\n Create a string value with JSON pointer token.\n @param token [in] a JSON pointer token\n @param len   [in] unescaped token length\n @param esc   [in] number of escaped characters in this token\n @param doc   [in] used for memory allocation when creating value\n @return new string value, or NULL if memory allocation failed\n */\nstatic_inline yyjson_mut_val *ptr_new_key(const char *token,\n                                          usize len, usize esc,\n                                          yyjson_mut_doc *doc) {\n    const char *src = token;\n    if (likely(!esc)) {\n        return yyjson_mut_strncpy(doc, src, len);\n    } else {\n        const char *end = src + len + esc;\n        char *dst = unsafe_yyjson_mut_str_alc(doc, len + esc);\n        char *str = dst;\n        if (unlikely(!dst)) return NULL;\n        for (; src < end; src++, dst++) {\n            if (*src != '~') *dst = *src;\n            else *dst = (*++src == '0' ? '~' : '/');\n        }\n        *dst = '\\0';\n        return yyjson_mut_strn(doc, str, len);\n    }\n}\n\n/* macros for yyjson_ptr */\n#define return_err(_ret, _code, _pos, _msg) do { \\\n    if (err) { \\\n        err->code = YYJSON_PTR_ERR_##_code; \\\n        err->msg = _msg; \\\n        err->pos = (usize)(_pos); \\\n    } \\\n    return _ret; \\\n} while (false)\n\n#define return_err_resolve(_ret, _pos) \\\n    return_err(_ret, RESOLVE, _pos, \"JSON pointer cannot be resolved\")\n#define return_err_syntax(_ret, _pos) \\\n    return_err(_ret, SYNTAX, _pos, \"invalid escaped character\")\n#define return_err_alloc(_ret) \\\n    return_err(_ret, MEMORY_ALLOCATION, 0, \"failed to create value\")\n\nyyjson_val *unsafe_yyjson_ptr_getx(yyjson_val *val,\n                                   const char *ptr, size_t ptr_len,\n                                   yyjson_ptr_err *err) {\n\n    const char *hdr = ptr, *end = ptr + ptr_len, *token;\n    usize len, esc;\n    yyjson_type type;\n\n    while (true) {\n        token = ptr_next_token(&ptr, end, &len, &esc);\n        if (unlikely(!token)) return_err_syntax(NULL, ptr - hdr);\n        type = unsafe_yyjson_get_type(val);\n        if (type == YYJSON_TYPE_OBJ) {\n            val = ptr_obj_get(val, token, len, esc);\n        } else if (type == YYJSON_TYPE_ARR) {\n            val = ptr_arr_get(val, token, len, esc);\n        } else {\n            val = NULL;\n        }\n        if (!val) return_err_resolve(NULL, token - hdr);\n        if (ptr == end) return val;\n    }\n}\n\nyyjson_mut_val *unsafe_yyjson_mut_ptr_getx(\n    yyjson_mut_val *val, const char *ptr, size_t ptr_len,\n    yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) {\n\n    const char *hdr = ptr, *end = ptr + ptr_len, *token;\n    usize len, esc;\n    yyjson_mut_val *ctn, *pre = NULL;\n    yyjson_type type;\n    bool idx_is_last = false;\n\n    while (true) {\n        token = ptr_next_token(&ptr, end, &len, &esc);\n        if (unlikely(!token)) return_err_syntax(NULL, ptr - hdr);\n        ctn = val;\n        type = unsafe_yyjson_get_type(val);\n        if (type == YYJSON_TYPE_OBJ) {\n            val = ptr_mut_obj_get(val, token, len, esc, &pre);\n        } else if (type == YYJSON_TYPE_ARR) {\n            val = ptr_mut_arr_get(val, token, len, esc, &pre, &idx_is_last);\n        } else {\n            val = NULL;\n        }\n        if (ctx && (ptr == end)) {\n            if (type == YYJSON_TYPE_OBJ ||\n                (type == YYJSON_TYPE_ARR && (val || idx_is_last))) {\n                ctx->ctn = ctn;\n                ctx->pre = pre;\n            }\n        }\n        if (!val) return_err_resolve(NULL, token - hdr);\n        if (ptr == end) return val;\n    }\n}\n\nbool unsafe_yyjson_mut_ptr_putx(\n    yyjson_mut_val *val, const char *ptr, size_t ptr_len,\n    yyjson_mut_val *new_val, yyjson_mut_doc *doc, bool create_parent,\n    bool insert_new, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) {\n\n    const char *hdr = ptr, *end = ptr + ptr_len, *token;\n    usize token_len, esc, ctn_len;\n    yyjson_mut_val *ctn, *key, *pre = NULL;\n    yyjson_mut_val *sep_ctn = NULL, *sep_key = NULL, *sep_val = NULL;\n    yyjson_type ctn_type;\n    bool idx_is_last = false;\n\n    /* skip exist parent nodes */\n    while (true) {\n        token = ptr_next_token(&ptr, end, &token_len, &esc);\n        if (unlikely(!token)) return_err_syntax(false, ptr - hdr);\n        ctn = val;\n        ctn_type = unsafe_yyjson_get_type(ctn);\n        if (ctn_type == YYJSON_TYPE_OBJ) {\n            val = ptr_mut_obj_get(ctn, token, token_len, esc, &pre);\n        } else if (ctn_type == YYJSON_TYPE_ARR) {\n            val = ptr_mut_arr_get(ctn, token, token_len, esc, &pre,\n                                  &idx_is_last);\n        } else return_err_resolve(false, token - hdr);\n        if (!val) break;\n        if (ptr == end) break; /* is last token */\n    }\n\n    /* create parent nodes if not exist */\n    if (unlikely(ptr != end)) { /* not last token */\n        if (!create_parent) return_err_resolve(false, token - hdr);\n\n        /* add value at last index if container is array */\n        if (ctn_type == YYJSON_TYPE_ARR) {\n            if (!idx_is_last || !insert_new) {\n                return_err_resolve(false, token - hdr);\n            }\n            val = yyjson_mut_obj(doc);\n            if (!val) return_err_alloc(false);\n\n            /* delay attaching until all operations are completed */\n            sep_ctn = ctn;\n            sep_key = NULL;\n            sep_val = val;\n\n            /* move to next token */\n            ctn = val;\n            val = NULL;\n            ctn_type = YYJSON_TYPE_OBJ;\n            token = ptr_next_token(&ptr, end, &token_len, &esc);\n            if (unlikely(!token)) return_err_resolve(false, token - hdr);\n        }\n\n        /* container is object, create parent nodes */\n        while (ptr != end) { /* not last token */\n            key = ptr_new_key(token, token_len, esc, doc);\n            if (!key) return_err_alloc(false);\n            val = yyjson_mut_obj(doc);\n            if (!val) return_err_alloc(false);\n\n            /* delay attaching until all operations are completed */\n            if (!sep_ctn) {\n                sep_ctn = ctn;\n                sep_key = key;\n                sep_val = val;\n            } else {\n                yyjson_mut_obj_add(ctn, key, val);\n            }\n\n            /* move to next token */\n            ctn = val;\n            val = NULL;\n            token = ptr_next_token(&ptr, end, &token_len, &esc);\n            if (unlikely(!token)) return_err_syntax(false, ptr - hdr);\n        }\n    }\n\n    /* JSON pointer is resolved, insert or replace target value */\n    ctn_len = unsafe_yyjson_get_len(ctn);\n    if (ctn_type == YYJSON_TYPE_OBJ) {\n        if (ctx) ctx->ctn = ctn;\n        if (!val || insert_new) {\n            /* insert new key-value pair */\n            key = ptr_new_key(token, token_len, esc, doc);\n            if (unlikely(!key)) return_err_alloc(false);\n            if (ctx) ctx->pre = ctn_len ? (yyjson_mut_val *)ctn->uni.ptr : key;\n            unsafe_yyjson_mut_obj_add(ctn, key, new_val, ctn_len);\n        } else {\n            /* replace exist value */\n            key = pre->next->next;\n            if (ctx) ctx->pre = pre;\n            if (ctx) ctx->old = val;\n            yyjson_mut_obj_put(ctn, key, new_val);\n        }\n    } else {\n        /* array */\n        if (ctx && (val || idx_is_last)) ctx->ctn = ctn;\n        if (insert_new) {\n            /* append new value */\n            if (val) {\n                pre->next = new_val;\n                new_val->next = val;\n                if (ctx) ctx->pre = pre;\n                unsafe_yyjson_set_len(ctn, ctn_len + 1);\n            } else if (idx_is_last) {\n                if (ctx) ctx->pre = ctn_len ?\n                    (yyjson_mut_val *)ctn->uni.ptr : new_val;\n                yyjson_mut_arr_append(ctn, new_val);\n            } else {\n                return_err_resolve(false, token - hdr);\n            }\n        } else {\n            /* replace exist value */\n            if (!val) return_err_resolve(false, token - hdr);\n            if (ctn_len > 1) {\n                new_val->next = val->next;\n                pre->next = new_val;\n                if (ctn->uni.ptr == val) ctn->uni.ptr = new_val;\n            } else {\n                new_val->next = new_val;\n                ctn->uni.ptr = new_val;\n                pre = new_val;\n            }\n            if (ctx) ctx->pre = pre;\n            if (ctx) ctx->old = val;\n        }\n    }\n\n    /* all operations are completed, attach the new components to the target */\n    if (unlikely(sep_ctn)) {\n        if (sep_key) yyjson_mut_obj_add(sep_ctn, sep_key, sep_val);\n        else yyjson_mut_arr_append(sep_ctn, sep_val);\n    }\n    return true;\n}\n\nyyjson_mut_val *unsafe_yyjson_mut_ptr_replacex(\n    yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val,\n    yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) {\n\n    yyjson_mut_val *cur_val;\n    yyjson_ptr_ctx cur_ctx;\n    memset(&cur_ctx, 0, sizeof(cur_ctx));\n    if (!ctx) ctx = &cur_ctx;\n    cur_val = unsafe_yyjson_mut_ptr_getx(val, ptr, len, ctx, err);\n    if (!cur_val) return NULL;\n\n    if (yyjson_mut_is_obj(ctx->ctn)) {\n        yyjson_mut_val *key = ctx->pre->next->next;\n        yyjson_mut_obj_put(ctx->ctn, key, new_val);\n    } else {\n        yyjson_ptr_ctx_replace(ctx, new_val);\n    }\n    ctx->old = cur_val;\n    return cur_val;\n}\n\nyyjson_mut_val *unsafe_yyjson_mut_ptr_removex(\n    yyjson_mut_val *val, const char *ptr, size_t len,\n    yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) {\n\n    yyjson_mut_val *cur_val;\n    yyjson_ptr_ctx cur_ctx;\n    memset(&cur_ctx, 0, sizeof(cur_ctx));\n    if (!ctx) ctx = &cur_ctx;\n    cur_val = unsafe_yyjson_mut_ptr_getx(val, ptr, len, ctx, err);\n    if (cur_val) {\n        if (yyjson_mut_is_obj(ctx->ctn)) {\n            yyjson_mut_val *key = ctx->pre->next->next;\n            yyjson_mut_obj_put(ctx->ctn, key, NULL);\n        } else {\n            yyjson_ptr_ctx_remove(ctx);\n        }\n        ctx->pre = NULL;\n        ctx->old = cur_val;\n    }\n    return cur_val;\n}\n\n/* macros for yyjson_ptr */\n#undef return_err\n#undef return_err_resolve\n#undef return_err_syntax\n#undef return_err_alloc\n\n\n\n/*==============================================================================\n * MARK: - JSON Patch API (RFC 6902) (Public)\n *============================================================================*/\n\n/* JSON Patch operation */\ntypedef enum patch_op {\n    PATCH_OP_ADD,       /* path, value */\n    PATCH_OP_REMOVE,    /* path */\n    PATCH_OP_REPLACE,   /* path, value */\n    PATCH_OP_MOVE,      /* from, path */\n    PATCH_OP_COPY,      /* from, path */\n    PATCH_OP_TEST,      /* path, value */\n    PATCH_OP_NONE       /* invalid */\n} patch_op;\n\nstatic patch_op patch_op_get(yyjson_val *op) {\n    const char *str = op->uni.str;\n    switch (unsafe_yyjson_get_len(op)) {\n        case 3:\n            if (!memcmp(str, \"add\", 3)) return PATCH_OP_ADD;\n            return PATCH_OP_NONE;\n        case 4:\n            if (!memcmp(str, \"move\", 4)) return PATCH_OP_MOVE;\n            if (!memcmp(str, \"copy\", 4)) return PATCH_OP_COPY;\n            if (!memcmp(str, \"test\", 4)) return PATCH_OP_TEST;\n            return PATCH_OP_NONE;\n        case 6:\n            if (!memcmp(str, \"remove\", 6)) return PATCH_OP_REMOVE;\n            return PATCH_OP_NONE;\n        case 7:\n            if (!memcmp(str, \"replace\", 7)) return PATCH_OP_REPLACE;\n            return PATCH_OP_NONE;\n        default:\n            return PATCH_OP_NONE;\n    }\n}\n\n/* macros for yyjson_patch */\n#define return_err(_code, _msg) do { \\\n    if (err->ptr.code == YYJSON_PTR_ERR_MEMORY_ALLOCATION) { \\\n        err->code = YYJSON_PATCH_ERROR_MEMORY_ALLOCATION; \\\n        err->msg = _msg; \\\n        memset(&err->ptr, 0, sizeof(yyjson_ptr_err)); \\\n    } else { \\\n        err->code = YYJSON_PATCH_ERROR_##_code; \\\n        err->msg = _msg; \\\n        err->idx = iter.idx ? iter.idx - 1 : 0; \\\n    } \\\n    return NULL; \\\n} while (false)\n\n#define return_err_copy() \\\n    return_err(MEMORY_ALLOCATION, \"failed to copy value\")\n#define return_err_key(_key) \\\n    return_err(MISSING_KEY, \"missing key \" _key)\n#define return_err_val(_key) \\\n    return_err(INVALID_MEMBER, \"invalid member \" _key)\n\n#define ptr_get(_ptr) yyjson_mut_ptr_getx( \\\n    root, _ptr->uni.str, _ptr##_len, NULL, &err->ptr)\n#define ptr_add(_ptr, _val) yyjson_mut_ptr_addx( \\\n    root, _ptr->uni.str, _ptr##_len, _val, doc, false, NULL, &err->ptr)\n#define ptr_remove(_ptr) yyjson_mut_ptr_removex( \\\n    root, _ptr->uni.str, _ptr##_len, NULL, &err->ptr)\n#define ptr_replace(_ptr, _val)yyjson_mut_ptr_replacex( \\\n    root, _ptr->uni.str, _ptr##_len, _val, NULL, &err->ptr)\n\nyyjson_mut_val *yyjson_patch(yyjson_mut_doc *doc,\n                             yyjson_val *orig,\n                             yyjson_val *patch,\n                             yyjson_patch_err *err) {\n\n    yyjson_mut_val *root;\n    yyjson_val *obj;\n    yyjson_arr_iter iter;\n    yyjson_patch_err err_tmp;\n    if (!err) err = &err_tmp;\n    memset(err, 0, sizeof(*err));\n    memset(&iter, 0, sizeof(iter));\n\n    if (unlikely(!doc || !orig || !patch)) {\n        return_err(INVALID_PARAMETER, \"input parameter is NULL\");\n    }\n    if (unlikely(!yyjson_is_arr(patch))) {\n        return_err(INVALID_PARAMETER, \"input patch is not array\");\n    }\n    root = yyjson_val_mut_copy(doc, orig);\n    if (unlikely(!root)) return_err_copy();\n\n    /* iterate through the patch array */\n    yyjson_arr_iter_init(patch, &iter);\n    while ((obj = yyjson_arr_iter_next(&iter))) {\n        patch_op op_enum;\n        yyjson_val *op, *path, *from = NULL, *value;\n        yyjson_mut_val *val = NULL, *test;\n        usize path_len, from_len = 0;\n        if (unlikely(!unsafe_yyjson_is_obj(obj))) {\n            return_err(INVALID_OPERATION, \"JSON patch operation is not object\");\n        }\n\n        /* get required member: op */\n        op = yyjson_obj_get(obj, \"op\");\n        if (unlikely(!op)) return_err_key(\"`op`\");\n        if (unlikely(!yyjson_is_str(op))) return_err_val(\"`op`\");\n        op_enum = patch_op_get(op);\n\n        /* get required member: path */\n        path = yyjson_obj_get(obj, \"path\");\n        if (unlikely(!path)) return_err_key(\"`path`\");\n        if (unlikely(!yyjson_is_str(path))) return_err_val(\"`path`\");\n        path_len = unsafe_yyjson_get_len(path);\n\n        /* get required member: value, from */\n        switch ((int)op_enum) {\n            case PATCH_OP_ADD: case PATCH_OP_REPLACE: case PATCH_OP_TEST:\n                value = yyjson_obj_get(obj, \"value\");\n                if (unlikely(!value)) return_err_key(\"`value`\");\n                val = yyjson_val_mut_copy(doc, value);\n                if (unlikely(!val)) return_err_copy();\n                break;\n            case PATCH_OP_MOVE: case PATCH_OP_COPY:\n                from = yyjson_obj_get(obj, \"from\");\n                if (unlikely(!from)) return_err_key(\"`from`\");\n                if (unlikely(!yyjson_is_str(from))) return_err_val(\"`from`\");\n                from_len = unsafe_yyjson_get_len(from);\n                break;\n            default:\n                break;\n        }\n\n        /* perform an operation */\n        switch ((int)op_enum) {\n            case PATCH_OP_ADD: /* add(path, val) */\n                if (unlikely(path_len == 0)) { root = val; break; }\n                if (unlikely(!ptr_add(path, val))) {\n                    return_err(POINTER, \"failed to add `path`\");\n                }\n                break;\n            case PATCH_OP_REMOVE: /* remove(path) */\n                if (unlikely(!ptr_remove(path))) {\n                    return_err(POINTER, \"failed to remove `path`\");\n                }\n                break;\n            case PATCH_OP_REPLACE: /* replace(path, val) */\n                if (unlikely(path_len == 0)) { root = val; break; }\n                if (unlikely(!ptr_replace(path, val))) {\n                    return_err(POINTER, \"failed to replace `path`\");\n                }\n                break;\n            case PATCH_OP_MOVE: /* val = remove(from), add(path, val) */\n                if (unlikely(from_len == 0 && path_len == 0)) break;\n                val = ptr_remove(from);\n                if (unlikely(!val)) {\n                    return_err(POINTER, \"failed to remove `from`\");\n                }\n                if (unlikely(path_len == 0)) { root = val; break; }\n                if (unlikely(!ptr_add(path, val))) {\n                    return_err(POINTER, \"failed to add `path`\");\n                }\n                break;\n            case PATCH_OP_COPY: /* val = get(from).copy, add(path, val) */\n                val = ptr_get(from);\n                if (unlikely(!val)) {\n                    return_err(POINTER, \"failed to get `from`\");\n                }\n                if (unlikely(path_len == 0)) { root = val; break; }\n                val = yyjson_mut_val_mut_copy(doc, val);\n                if (unlikely(!val)) return_err_copy();\n                if (unlikely(!ptr_add(path, val))) {\n                    return_err(POINTER, \"failed to add `path`\");\n                }\n                break;\n            case PATCH_OP_TEST: /* test = get(path), test.eq(val) */\n                test = ptr_get(path);\n                if (unlikely(!test)) {\n                    return_err(POINTER, \"failed to get `path`\");\n                }\n                if (unlikely(!yyjson_mut_equals(val, test))) {\n                    return_err(EQUAL, \"failed to test equal\");\n                }\n                break;\n            default:\n                return_err(INVALID_MEMBER, \"unsupported `op`\");\n        }\n    }\n    return root;\n}\n\nyyjson_mut_val *yyjson_mut_patch(yyjson_mut_doc *doc,\n                                 yyjson_mut_val *orig,\n                                 yyjson_mut_val *patch,\n                                 yyjson_patch_err *err) {\n    yyjson_mut_val *root, *obj;\n    yyjson_mut_arr_iter iter;\n    yyjson_patch_err err_tmp;\n    if (!err) err = &err_tmp;\n    memset(err, 0, sizeof(*err));\n    memset(&iter, 0, sizeof(iter));\n\n    if (unlikely(!doc || !orig || !patch)) {\n        return_err(INVALID_PARAMETER, \"input parameter is NULL\");\n    }\n    if (unlikely(!yyjson_mut_is_arr(patch))) {\n        return_err(INVALID_PARAMETER, \"input patch is not array\");\n    }\n    root = yyjson_mut_val_mut_copy(doc, orig);\n    if (unlikely(!root)) return_err_copy();\n\n    /* iterate through the patch array */\n    yyjson_mut_arr_iter_init(patch, &iter);\n    while ((obj = yyjson_mut_arr_iter_next(&iter))) {\n        patch_op op_enum;\n        yyjson_mut_val *op, *path, *from = NULL, *value;\n        yyjson_mut_val *val = NULL, *test;\n        usize path_len, from_len = 0;\n        if (!unsafe_yyjson_is_obj(obj)) {\n            return_err(INVALID_OPERATION, \"JSON patch operation is not object\");\n        }\n\n        /* get required member: op */\n        op = yyjson_mut_obj_get(obj, \"op\");\n        if (unlikely(!op)) return_err_key(\"`op`\");\n        if (unlikely(!yyjson_mut_is_str(op))) return_err_val(\"`op`\");\n        op_enum = patch_op_get((yyjson_val *)(void *)op);\n\n        /* get required member: path */\n        path = yyjson_mut_obj_get(obj, \"path\");\n        if (unlikely(!path)) return_err_key(\"`path`\");\n        if (unlikely(!yyjson_mut_is_str(path))) return_err_val(\"`path`\");\n        path_len = unsafe_yyjson_get_len(path);\n\n        /* get required member: value, from */\n        switch ((int)op_enum) {\n            case PATCH_OP_ADD: case PATCH_OP_REPLACE: case PATCH_OP_TEST:\n                value = yyjson_mut_obj_get(obj, \"value\");\n                if (unlikely(!value)) return_err_key(\"`value`\");\n                val = yyjson_mut_val_mut_copy(doc, value);\n                if (unlikely(!val)) return_err_copy();\n                break;\n            case PATCH_OP_MOVE: case PATCH_OP_COPY:\n                from = yyjson_mut_obj_get(obj, \"from\");\n                if (unlikely(!from)) return_err_key(\"`from`\");\n                if (unlikely(!yyjson_mut_is_str(from))) {\n                    return_err_val(\"`from`\");\n                }\n                from_len = unsafe_yyjson_get_len(from);\n                break;\n            default:\n                break;\n        }\n\n        /* perform an operation */\n        switch ((int)op_enum) {\n            case PATCH_OP_ADD: /* add(path, val) */\n                if (unlikely(path_len == 0)) { root = val; break; }\n                if (unlikely(!ptr_add(path, val))) {\n                    return_err(POINTER, \"failed to add `path`\");\n                }\n                break;\n            case PATCH_OP_REMOVE: /* remove(path) */\n                if (unlikely(!ptr_remove(path))) {\n                    return_err(POINTER, \"failed to remove `path`\");\n                }\n                break;\n            case PATCH_OP_REPLACE: /* replace(path, val) */\n                if (unlikely(path_len == 0)) { root = val; break; }\n                if (unlikely(!ptr_replace(path, val))) {\n                    return_err(POINTER, \"failed to replace `path`\");\n                }\n                break;\n            case PATCH_OP_MOVE: /* val = remove(from), add(path, val) */\n                if (unlikely(from_len == 0 && path_len == 0)) break;\n                val = ptr_remove(from);\n                if (unlikely(!val)) {\n                    return_err(POINTER, \"failed to remove `from`\");\n                }\n                if (unlikely(path_len == 0)) { root = val; break; }\n                if (unlikely(!ptr_add(path, val))) {\n                    return_err(POINTER, \"failed to add `path`\");\n                }\n                break;\n            case PATCH_OP_COPY: /* val = get(from).copy, add(path, val) */\n                val = ptr_get(from);\n                if (unlikely(!val)) {\n                    return_err(POINTER, \"failed to get `from`\");\n                }\n                if (unlikely(path_len == 0)) { root = val; break; }\n                val = yyjson_mut_val_mut_copy(doc, val);\n                if (unlikely(!val)) return_err_copy();\n                if (unlikely(!ptr_add(path, val))) {\n                    return_err(POINTER, \"failed to add `path`\");\n                }\n                break;\n            case PATCH_OP_TEST: /* test = get(path), test.eq(val) */\n                test = ptr_get(path);\n                if (unlikely(!test)) {\n                    return_err(POINTER, \"failed to get `path`\");\n                }\n                if (unlikely(!yyjson_mut_equals(val, test))) {\n                    return_err(EQUAL, \"failed to test equal\");\n                }\n                break;\n            default:\n                return_err(INVALID_MEMBER, \"unsupported `op`\");\n        }\n    }\n    return root;\n}\n\n/* macros for yyjson_patch */\n#undef return_err\n#undef return_err_copy\n#undef return_err_key\n#undef return_err_val\n#undef ptr_get\n#undef ptr_add\n#undef ptr_remove\n#undef ptr_replace\n\n\n\n/*==============================================================================\n * MARK: - JSON Merge-Patch API (RFC 7386) (Public)\n *============================================================================*/\n\nyyjson_mut_val *yyjson_merge_patch(yyjson_mut_doc *doc,\n                                   yyjson_val *orig,\n                                   yyjson_val *patch) {\n    usize idx, max;\n    yyjson_val *key, *orig_val, *patch_val, local_orig;\n    yyjson_mut_val *builder, *mut_key, *mut_val, *merged_val;\n\n    if (unlikely(!yyjson_is_obj(patch))) {\n        return yyjson_val_mut_copy(doc, patch);\n    }\n\n    builder = yyjson_mut_obj(doc);\n    if (unlikely(!builder)) return NULL;\n\n    memset(&local_orig, 0, sizeof(local_orig));\n    if (!yyjson_is_obj(orig)) {\n        orig = &local_orig;\n        orig->tag = builder->tag;\n        orig->uni = builder->uni;\n    }\n\n    /* If orig is contributing, copy any items not modified by the patch */\n    if (orig != &local_orig) {\n        yyjson_obj_foreach(orig, idx, max, key, orig_val) {\n            patch_val = yyjson_obj_getn(patch,\n                                        unsafe_yyjson_get_str(key),\n                                        unsafe_yyjson_get_len(key));\n            if (!patch_val) {\n                mut_key = yyjson_val_mut_copy(doc, key);\n                mut_val = yyjson_val_mut_copy(doc, orig_val);\n                if (!yyjson_mut_obj_add(builder, mut_key, mut_val)) return NULL;\n            }\n        }\n    }\n\n    /* Merge items modified by the patch. */\n    yyjson_obj_foreach(patch, idx, max, key, patch_val) {\n        /* null indicates the field is removed. */\n        if (unsafe_yyjson_is_null(patch_val)) {\n            continue;\n        }\n        mut_key = yyjson_val_mut_copy(doc, key);\n        orig_val = yyjson_obj_getn(orig,\n                                   unsafe_yyjson_get_str(key),\n                                   unsafe_yyjson_get_len(key));\n        merged_val = yyjson_merge_patch(doc, orig_val, patch_val);\n        if (!yyjson_mut_obj_add(builder, mut_key, merged_val)) return NULL;\n    }\n\n    return builder;\n}\n\nyyjson_mut_val *yyjson_mut_merge_patch(yyjson_mut_doc *doc,\n                                       yyjson_mut_val *orig,\n                                       yyjson_mut_val *patch) {\n    usize idx, max;\n    yyjson_mut_val *key, *orig_val, *patch_val, local_orig;\n    yyjson_mut_val *builder, *mut_key, *mut_val, *merged_val;\n\n    if (unlikely(!yyjson_mut_is_obj(patch))) {\n        return yyjson_mut_val_mut_copy(doc, patch);\n    }\n\n    builder = yyjson_mut_obj(doc);\n    if (unlikely(!builder)) return NULL;\n\n    memset(&local_orig, 0, sizeof(local_orig));\n    if (!yyjson_mut_is_obj(orig)) {\n        orig = &local_orig;\n        orig->tag = builder->tag;\n        orig->uni = builder->uni;\n    }\n\n    /* If orig is contributing, copy any items not modified by the patch */\n    if (orig != &local_orig) {\n        yyjson_mut_obj_foreach(orig, idx, max, key, orig_val) {\n            patch_val = yyjson_mut_obj_getn(patch,\n                                            unsafe_yyjson_get_str(key),\n                                            unsafe_yyjson_get_len(key));\n            if (!patch_val) {\n                mut_key = yyjson_mut_val_mut_copy(doc, key);\n                mut_val = yyjson_mut_val_mut_copy(doc, orig_val);\n                if (!yyjson_mut_obj_add(builder, mut_key, mut_val)) return NULL;\n            }\n        }\n    }\n\n    /* Merge items modified by the patch. */\n    yyjson_mut_obj_foreach(patch, idx, max, key, patch_val) {\n        /* null indicates the field is removed. */\n        if (unsafe_yyjson_is_null(patch_val)) {\n            continue;\n        }\n        mut_key = yyjson_mut_val_mut_copy(doc, key);\n        orig_val = yyjson_mut_obj_getn(orig,\n                                       unsafe_yyjson_get_str(key),\n                                       unsafe_yyjson_get_len(key));\n        merged_val = yyjson_mut_merge_patch(doc, orig_val, patch_val);\n        if (!yyjson_mut_obj_add(builder, mut_key, merged_val)) return NULL;\n    }\n\n    return builder;\n}\n\n#endif /* YYJSON_DISABLE_UTILS */\n"
  },
  {
    "path": "src/3rdparty/yyjson/yyjson.h",
    "content": "/*==============================================================================\n Copyright (c) 2020 YaoYuan <ibireme@gmail.com>\n\n Permission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to deal\n in the Software without restriction, including without limitation the rights\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\n The above copyright notice and this permission notice shall be included in all\n copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n SOFTWARE.\n *============================================================================*/\n\n/**\n @file yyjson.h\n @date 2019-03-09\n @author YaoYuan\n */\n\n#ifndef YYJSON_H\n#define YYJSON_H\n\n\n\n/*==============================================================================\n * MARK: - Header Files\n *============================================================================*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stddef.h>\n#include <limits.h>\n#include <string.h>\n#include <float.h>\n\n\n\n/*==============================================================================\n * MARK: - Compile-time Options\n *============================================================================*/\n\n/*\n Define as 1 to disable JSON reader at compile-time.\n This disables functions with \"read\" in their name.\n Reduces binary size by about 60%.\n */\n#ifndef YYJSON_DISABLE_READER\n#endif\n\n/*\n Define as 1 to disable JSON writer at compile-time.\n This disables functions with \"write\" in their name.\n Reduces binary size by about 30%.\n */\n#ifndef YYJSON_DISABLE_WRITER\n#endif\n\n/*\n Define as 1 to disable JSON incremental reader at compile-time.\n This disables functions with \"incr\" in their name.\n */\n#ifndef YYJSON_DISABLE_INCR_READER\n#endif\n\n/*\n Define as 1 to disable JSON Pointer, JSON Patch and JSON Merge Patch supports.\n This disables functions with \"ptr\" or \"patch\" in their name.\n */\n#ifndef YYJSON_DISABLE_UTILS\n#endif\n\n/*\n Define as 1 to disable the fast floating-point number conversion in yyjson.\n Libc's `strtod/snprintf` will be used instead.\n\n This reduces binary size by about 30%, but significantly slows down the\n floating-point read/write speed.\n */\n#ifndef YYJSON_DISABLE_FAST_FP_CONV\n#endif\n\n/*\n Define as 1 to disable non-standard JSON features support at compile-time,\n such as YYJSON_READ_ALLOW_XXX and YYJSON_WRITE_ALLOW_XXX.\n\n This reduces binary size by about 10%, and slightly improves performance.\n */\n#ifndef YYJSON_DISABLE_NON_STANDARD\n#endif\n\n/*\n Define as 1 to disable UTF-8 validation at compile-time.\n\n Use this if all input strings are guaranteed to be valid UTF-8\n (e.g. language-level String types are already validated).\n\n Disabling UTF-8 validation improves performance for non-ASCII strings by about\n 3% to 7%.\n\n Note: If this flag is enabled while passing illegal UTF-8 strings,\n the following errors may occur:\n - Escaped characters may be ignored when parsing JSON strings.\n - Ending quotes may be ignored when parsing JSON strings, causing the\n   string to merge with the next value.\n - When serializing with `yyjson_mut_val`, the string's end may be accessed\n   out of bounds, potentially causing a segmentation fault.\n */\n#ifndef YYJSON_DISABLE_UTF8_VALIDATION\n#endif\n\n/*\n Define as 1 to improve performance on architectures that do not support\n unaligned memory access.\n\n Normally, this does not need to be set manually. See the C file for details.\n */\n#ifndef YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS\n#endif\n\n/* Define as 1 to export symbols when building this library as a Windows DLL. */\n#ifndef YYJSON_EXPORTS\n#endif\n\n/* Define as 1 to import symbols when using this library as a Windows DLL. */\n#ifndef YYJSON_IMPORTS\n#endif\n\n/* Define as 1 to include <stdint.h> for compilers without C99 support. */\n#ifndef YYJSON_HAS_STDINT_H\n#endif\n\n/* Define as 1 to include <stdbool.h> for compilers without C99 support. */\n#ifndef YYJSON_HAS_STDBOOL_H\n#endif\n\n\n\n/*==============================================================================\n * MARK: - Compiler Macros\n *============================================================================*/\n\n/** compiler version (MSVC) */\n#ifdef _MSC_VER\n#   define YYJSON_MSC_VER _MSC_VER\n#else\n#   define YYJSON_MSC_VER 0\n#endif\n\n/** compiler version (GCC) */\n#ifdef __GNUC__\n#   define YYJSON_GCC_VER __GNUC__\n#   if defined(__GNUC_PATCHLEVEL__)\n#       define yyjson_gcc_available(major, minor, patch) \\\n            ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) \\\n            >= (major * 10000 + minor * 100 + patch))\n#   else\n#       define yyjson_gcc_available(major, minor, patch) \\\n            ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100) \\\n            >= (major * 10000 + minor * 100 + patch))\n#   endif\n#else\n#   define YYJSON_GCC_VER 0\n#   define yyjson_gcc_available(major, minor, patch) 0\n#endif\n\n/** real gcc check */\n#if defined(__GNUC__) && defined(__GNUC_MINOR__) && \\\n    !defined(__clang__) && !defined(__llvm__) && \\\n    !defined(__INTEL_COMPILER) && !defined(__ICC) && \\\n    !defined(__NVCC__) && !defined(__PGI) && !defined(__TINYC__)\n#   define YYJSON_IS_REAL_GCC 1\n#else\n#   define YYJSON_IS_REAL_GCC 0\n#endif\n\n/** C version (STDC) */\n#if defined(__STDC__) && (__STDC__ >= 1) && defined(__STDC_VERSION__)\n#   define YYJSON_STDC_VER __STDC_VERSION__\n#else\n#   define YYJSON_STDC_VER 0\n#endif\n\n/** C++ version */\n#if defined(__cplusplus)\n#   define YYJSON_CPP_VER __cplusplus\n#else\n#   define YYJSON_CPP_VER 0\n#endif\n\n/** compiler builtin check (since gcc 10.0, clang 2.6, icc 2021) */\n#ifndef yyjson_has_builtin\n#   ifdef __has_builtin\n#       define yyjson_has_builtin(x) __has_builtin(x)\n#   else\n#       define yyjson_has_builtin(x) 0\n#   endif\n#endif\n\n/** compiler attribute check (since gcc 5.0, clang 2.9, icc 17) */\n#ifndef yyjson_has_attribute\n#   ifdef __has_attribute\n#       define yyjson_has_attribute(x) __has_attribute(x)\n#   else\n#       define yyjson_has_attribute(x) 0\n#   endif\n#endif\n\n/** compiler feature check (since clang 2.6, icc 17) */\n#ifndef yyjson_has_feature\n#   ifdef __has_feature\n#       define yyjson_has_feature(x) __has_feature(x)\n#   else\n#       define yyjson_has_feature(x) 0\n#   endif\n#endif\n\n/** include check (since gcc 5.0, clang 2.7, icc 16, msvc 2017 15.3) */\n#ifndef yyjson_has_include\n#   ifdef __has_include\n#       define yyjson_has_include(x) __has_include(x)\n#   else\n#       define yyjson_has_include(x) 0\n#   endif\n#endif\n\n/** inline for compiler */\n#ifndef yyjson_inline\n#   if YYJSON_MSC_VER >= 1200\n#       define yyjson_inline __forceinline\n#   elif defined(_MSC_VER)\n#       define yyjson_inline __inline\n#   elif yyjson_has_attribute(always_inline) || YYJSON_GCC_VER >= 4\n#       define yyjson_inline __inline__ __attribute__((always_inline))\n#   elif defined(__clang__) || defined(__GNUC__)\n#       define yyjson_inline __inline__\n#   elif defined(__cplusplus) || YYJSON_STDC_VER >= 199901L\n#       define yyjson_inline inline\n#   else\n#       define yyjson_inline\n#   endif\n#endif\n\n/** noinline for compiler */\n#ifndef yyjson_noinline\n#   if YYJSON_MSC_VER >= 1400\n#       define yyjson_noinline __declspec(noinline)\n#   elif yyjson_has_attribute(noinline) || YYJSON_GCC_VER >= 4\n#       define yyjson_noinline __attribute__((noinline))\n#   else\n#       define yyjson_noinline\n#   endif\n#endif\n\n/** align for compiler */\n#ifndef yyjson_align\n#   if YYJSON_MSC_VER >= 1300\n#       define yyjson_align(x) __declspec(align(x))\n#   elif yyjson_has_attribute(aligned) || defined(__GNUC__)\n#       define yyjson_align(x) __attribute__((aligned(x)))\n#   elif YYJSON_CPP_VER >= 201103L\n#       define yyjson_align(x) alignas(x)\n#   else\n#       define yyjson_align(x)\n#   endif\n#endif\n\n/** likely for compiler */\n#ifndef yyjson_likely\n#   if yyjson_has_builtin(__builtin_expect) || \\\n    (YYJSON_GCC_VER >= 4 && YYJSON_GCC_VER != 5)\n#       define yyjson_likely(expr) __builtin_expect(!!(expr), 1)\n#   else\n#       define yyjson_likely(expr) (expr)\n#   endif\n#endif\n\n/** unlikely for compiler */\n#ifndef yyjson_unlikely\n#   if yyjson_has_builtin(__builtin_expect) || \\\n    (YYJSON_GCC_VER >= 4 && YYJSON_GCC_VER != 5)\n#       define yyjson_unlikely(expr) __builtin_expect(!!(expr), 0)\n#   else\n#       define yyjson_unlikely(expr) (expr)\n#   endif\n#endif\n\n/** compile-time constant check for compiler */\n#ifndef yyjson_constant_p\n#   if yyjson_has_builtin(__builtin_constant_p) || (YYJSON_GCC_VER >= 3)\n#       define YYJSON_HAS_CONSTANT_P 1\n#       define yyjson_constant_p(value) __builtin_constant_p(value)\n#   else\n#       define YYJSON_HAS_CONSTANT_P 0\n#       define yyjson_constant_p(value) 0\n#   endif\n#endif\n\n/** deprecate warning */\n#ifndef yyjson_deprecated\n#   if YYJSON_MSC_VER >= 1400\n#       define yyjson_deprecated(msg) __declspec(deprecated(msg))\n#   elif yyjson_has_feature(attribute_deprecated_with_message) || \\\n        (YYJSON_GCC_VER > 4 || (YYJSON_GCC_VER == 4 && __GNUC_MINOR__ >= 5))\n#       define yyjson_deprecated(msg) __attribute__((deprecated(msg)))\n#   elif YYJSON_GCC_VER >= 3\n#       define yyjson_deprecated(msg) __attribute__((deprecated))\n#   else\n#       define yyjson_deprecated(msg)\n#   endif\n#endif\n\n/** function export */\n#ifndef yyjson_api\n#   if defined(_WIN32)\n#       if defined(YYJSON_EXPORTS) && YYJSON_EXPORTS\n#           define yyjson_api __declspec(dllexport)\n#       elif defined(YYJSON_IMPORTS) && YYJSON_IMPORTS\n#           define yyjson_api __declspec(dllimport)\n#       else\n#           define yyjson_api\n#       endif\n#   elif yyjson_has_attribute(visibility) || YYJSON_GCC_VER >= 4\n#       define yyjson_api __attribute__((visibility(\"default\")))\n#   else\n#       define yyjson_api\n#   endif\n#endif\n\n/** inline function export */\n#ifndef yyjson_api_inline\n#   define yyjson_api_inline static yyjson_inline\n#endif\n\n/** stdint (C89 compatible) */\n#if (defined(YYJSON_HAS_STDINT_H) && YYJSON_HAS_STDINT_H) || \\\n    YYJSON_MSC_VER >= 1600 || YYJSON_STDC_VER >= 199901L || \\\n    defined(_STDINT_H) || defined(_STDINT_H_) || \\\n    defined(__CLANG_STDINT_H) || defined(_STDINT_H_INCLUDED) || \\\n    yyjson_has_include(<stdint.h>)\n#   include <stdint.h>\n#elif defined(_MSC_VER)\n#   if _MSC_VER < 1300\n        typedef signed char         int8_t;\n        typedef signed short        int16_t;\n        typedef signed int          int32_t;\n        typedef unsigned char       uint8_t;\n        typedef unsigned short      uint16_t;\n        typedef unsigned int        uint32_t;\n        typedef signed __int64      int64_t;\n        typedef unsigned __int64    uint64_t;\n#   else\n        typedef signed __int8       int8_t;\n        typedef signed __int16      int16_t;\n        typedef signed __int32      int32_t;\n        typedef unsigned __int8     uint8_t;\n        typedef unsigned __int16    uint16_t;\n        typedef unsigned __int32    uint32_t;\n        typedef signed __int64      int64_t;\n        typedef unsigned __int64    uint64_t;\n#   endif\n#else\n#   if UCHAR_MAX == 0xFFU\n        typedef signed char     int8_t;\n        typedef unsigned char   uint8_t;\n#   else\n#       error cannot find 8-bit integer type\n#   endif\n#   if USHRT_MAX == 0xFFFFU\n        typedef unsigned short  uint16_t;\n        typedef signed short    int16_t;\n#   elif UINT_MAX == 0xFFFFU\n        typedef unsigned int    uint16_t;\n        typedef signed int      int16_t;\n#   else\n#       error cannot find 16-bit integer type\n#   endif\n#   if UINT_MAX == 0xFFFFFFFFUL\n        typedef unsigned int    uint32_t;\n        typedef signed int      int32_t;\n#   elif ULONG_MAX == 0xFFFFFFFFUL\n        typedef unsigned long   uint32_t;\n        typedef signed long     int32_t;\n#   elif USHRT_MAX == 0xFFFFFFFFUL\n        typedef unsigned short  uint32_t;\n        typedef signed short    int32_t;\n#   else\n#       error cannot find 32-bit integer type\n#   endif\n#   if defined(__INT64_TYPE__) && defined(__UINT64_TYPE__)\n        typedef __INT64_TYPE__  int64_t;\n        typedef __UINT64_TYPE__ uint64_t;\n#   elif defined(__GNUC__) || defined(__clang__)\n#       if !defined(_SYS_TYPES_H) && !defined(__int8_t_defined)\n        __extension__ typedef long long             int64_t;\n#       endif\n        __extension__ typedef unsigned long long    uint64_t;\n#   elif defined(_LONG_LONG) || defined(__MWERKS__) || defined(_CRAYC) || \\\n        defined(__SUNPRO_C) || defined(__SUNPRO_CC)\n        typedef long long           int64_t;\n        typedef unsigned long long  uint64_t;\n#   elif (defined(__BORLANDC__) && __BORLANDC__ > 0x460) || \\\n        defined(__WATCOM_INT64__) || defined (__alpha) || defined (__DECC)\n        typedef __int64             int64_t;\n        typedef unsigned __int64    uint64_t;\n#   else\n#       error cannot find 64-bit integer type\n#   endif\n#endif\n\n/** stdbool (C89 compatible) */\n#if (defined(YYJSON_HAS_STDBOOL_H) && YYJSON_HAS_STDBOOL_H) || \\\n    (yyjson_has_include(<stdbool.h>) && !defined(__STRICT_ANSI__)) || \\\n    YYJSON_MSC_VER >= 1800 || YYJSON_STDC_VER >= 199901L\n#   include <stdbool.h>\n#elif !defined(__bool_true_false_are_defined)\n#   define __bool_true_false_are_defined 1\n#   if defined(__cplusplus)\n#       if defined(__GNUC__) && !defined(__STRICT_ANSI__)\n#           define _Bool bool\n#           if __cplusplus < 201103L\n#               define bool bool\n#               define false false\n#               define true true\n#           endif\n#       endif\n#   else\n#       define bool unsigned char\n#       define true 1\n#       define false 0\n#   endif\n#endif\n\n/** char bit check */\n#if defined(CHAR_BIT)\n#   if CHAR_BIT != 8\n#       error non 8-bit char is not supported\n#   endif\n#endif\n\n/**\n Microsoft Visual C++ 6.0 doesn't support converting number from u64 to f64:\n error C2520: conversion from unsigned __int64 to double not implemented.\n */\n#ifndef YYJSON_U64_TO_F64_NO_IMPL\n#   if (0 < YYJSON_MSC_VER) && (YYJSON_MSC_VER <= 1200)\n#       define YYJSON_U64_TO_F64_NO_IMPL 1\n#   else\n#       define YYJSON_U64_TO_F64_NO_IMPL 0\n#   endif\n#endif\n\n\n\n/*==============================================================================\n * MARK: - Compile Hint Begin\n *============================================================================*/\n\n/* extern \"C\" begin */\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* warning suppress begin */\n#if defined(__clang__)\n#   pragma clang diagnostic push\n#   pragma clang diagnostic ignored \"-Wunused-function\"\n#   pragma clang diagnostic ignored \"-Wunused-parameter\"\n#elif defined(__GNUC__)\n#   if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)\n#       pragma GCC diagnostic push\n#   endif\n#   pragma GCC diagnostic ignored \"-Wunused-function\"\n#   pragma GCC diagnostic ignored \"-Wunused-parameter\"\n#elif defined(_MSC_VER)\n#   pragma warning(push)\n#   pragma warning(disable:4800) /* 'int': forcing value to 'true' or 'false' */\n#endif\n\n\n\n/*==============================================================================\n * MARK: - Version\n *============================================================================*/\n\n/** The major version of yyjson. */\n#define YYJSON_VERSION_MAJOR  0\n\n/** The minor version of yyjson. */\n#define YYJSON_VERSION_MINOR  12\n\n/** The patch version of yyjson. */\n#define YYJSON_VERSION_PATCH  0\n\n/** The version of yyjson in hex: `(major << 16) | (minor << 8) | (patch)`. */\n#define YYJSON_VERSION_HEX    0x000C00\n\n/** The version string of yyjson. */\n#define YYJSON_VERSION_STRING \"0.12.0\"\n\n/** The version of yyjson in hex, same as `YYJSON_VERSION_HEX`. */\nyyjson_api uint32_t yyjson_version(void);\n\n\n\n/*==============================================================================\n * MARK: - JSON Types\n *============================================================================*/\n\n/** Type of a JSON value (3 bit). */\ntypedef uint8_t yyjson_type;\n/** No type, invalid. */\n#define YYJSON_TYPE_NONE        ((uint8_t)0)        /* _____000 */\n/** Raw string type, no subtype. */\n#define YYJSON_TYPE_RAW         ((uint8_t)1)        /* _____001 */\n/** Null type: `null` literal, no subtype. */\n#define YYJSON_TYPE_NULL        ((uint8_t)2)        /* _____010 */\n/** Boolean type, subtype: TRUE, FALSE. */\n#define YYJSON_TYPE_BOOL        ((uint8_t)3)        /* _____011 */\n/** Number type, subtype: UINT, SINT, REAL. */\n#define YYJSON_TYPE_NUM         ((uint8_t)4)        /* _____100 */\n/** String type, subtype: NONE, NOESC. */\n#define YYJSON_TYPE_STR         ((uint8_t)5)        /* _____101 */\n/** Array type, no subtype. */\n#define YYJSON_TYPE_ARR         ((uint8_t)6)        /* _____110 */\n/** Object type, no subtype. */\n#define YYJSON_TYPE_OBJ         ((uint8_t)7)        /* _____111 */\n\n/** Subtype of a JSON value (2 bit). */\ntypedef uint8_t yyjson_subtype;\n/** No subtype. */\n#define YYJSON_SUBTYPE_NONE     ((uint8_t)(0 << 3)) /* ___00___ */\n/** False subtype: `false` literal. */\n#define YYJSON_SUBTYPE_FALSE    ((uint8_t)(0 << 3)) /* ___00___ */\n/** True subtype: `true` literal. */\n#define YYJSON_SUBTYPE_TRUE     ((uint8_t)(1 << 3)) /* ___01___ */\n/** Unsigned integer subtype: `uint64_t`. */\n#define YYJSON_SUBTYPE_UINT     ((uint8_t)(0 << 3)) /* ___00___ */\n/** Signed integer subtype: `int64_t`. */\n#define YYJSON_SUBTYPE_SINT     ((uint8_t)(1 << 3)) /* ___01___ */\n/** Real number subtype: `double`. */\n#define YYJSON_SUBTYPE_REAL     ((uint8_t)(2 << 3)) /* ___10___ */\n/** String that do not need to be escaped for writing (internal use). */\n#define YYJSON_SUBTYPE_NOESC    ((uint8_t)(1 << 3)) /* ___01___ */\n\n/** The mask used to extract the type of a JSON value. */\n#define YYJSON_TYPE_MASK        ((uint8_t)0x07)     /* _____111 */\n/** The number of bits used by the type. */\n#define YYJSON_TYPE_BIT         ((uint8_t)3)\n/** The mask used to extract the subtype of a JSON value. */\n#define YYJSON_SUBTYPE_MASK     ((uint8_t)0x18)     /* ___11___ */\n/** The number of bits used by the subtype. */\n#define YYJSON_SUBTYPE_BIT      ((uint8_t)2)\n/** The mask used to extract the reserved bits of a JSON value. */\n#define YYJSON_RESERVED_MASK    ((uint8_t)0xE0)     /* 111_____ */\n/** The number of reserved bits. */\n#define YYJSON_RESERVED_BIT     ((uint8_t)3)\n/** The mask used to extract the tag of a JSON value. */\n#define YYJSON_TAG_MASK         ((uint8_t)0xFF)     /* 11111111 */\n/** The number of bits used by the tag. */\n#define YYJSON_TAG_BIT          ((uint8_t)8)\n\n/** Padding size for JSON reader. */\n#define YYJSON_PADDING_SIZE     4\n\n\n\n/*==============================================================================\n * MARK: - Allocator\n *============================================================================*/\n\n/**\n A memory allocator.\n\n Typically you don't need to use it, unless you want to customize your own\n memory allocator.\n */\ntypedef struct yyjson_alc {\n    /** Same as libc's malloc(size), should not be NULL. */\n    void *(*malloc)(void *ctx, size_t size);\n    /** Same as libc's realloc(ptr, size), should not be NULL. */\n    void *(*realloc)(void *ctx, void *ptr, size_t old_size, size_t size);\n    /** Same as libc's free(ptr), should not be NULL. */\n    void (*free)(void *ctx, void *ptr);\n    /** A context for malloc/realloc/free, can be NULL. */\n    void *ctx;\n} yyjson_alc;\n\n/**\n A pool allocator uses fixed length pre-allocated memory.\n\n This allocator may be used to avoid malloc/realloc calls. The pre-allocated\n memory should be held by the caller. The maximum amount of memory required to\n read a JSON can be calculated using the `yyjson_read_max_memory_usage()`\n function, but the amount of memory required to write a JSON cannot be directly\n calculated.\n\n This is not a general-purpose allocator. It is designed to handle a single JSON\n data at a time. If it is used for overly complex memory tasks, such as parsing\n multiple JSON documents using the same allocator but releasing only a few of\n them, it may cause memory fragmentation, resulting in performance degradation\n and memory waste.\n\n @param alc The allocator to be initialized.\n    If this parameter is NULL, the function will fail and return false.\n    If `buf` or `size` is invalid, this will be set to an empty allocator.\n @param buf The buffer memory for this allocator.\n    If this parameter is NULL, the function will fail and return false.\n @param size The size of `buf`, in bytes.\n    If this parameter is less than 8 words (32/64 bytes on 32/64-bit OS), the\n    function will fail and return false.\n @return true if the `alc` has been successfully initialized.\n\n @b Example\n @code\n    // parse JSON with stack memory\n    char buf[1024];\n    yyjson_alc alc;\n    yyjson_alc_pool_init(&alc, buf, 1024);\n\n    const char *json = \"{\\\"name\\\":\\\"Helvetica\\\",\\\"size\\\":16}\"\n    yyjson_doc *doc = yyjson_read_opts(json, strlen(json), 0, &alc, NULL);\n    // the memory of `doc` is on the stack\n @endcode\n\n @warning This Allocator is not thread-safe.\n */\nyyjson_api bool yyjson_alc_pool_init(yyjson_alc *alc, void *buf, size_t size);\n\n/**\n A dynamic allocator.\n\n This allocator has a similar usage to the pool allocator above. However, when\n there is not enough memory, this allocator will dynamically request more memory\n using libc's `malloc` function, and frees it all at once when it is destroyed.\n\n @return A new dynamic allocator, or NULL if memory allocation failed.\n @note The returned value should be freed with `yyjson_alc_dyn_free()`.\n\n @warning This Allocator is not thread-safe.\n */\nyyjson_api yyjson_alc *yyjson_alc_dyn_new(void);\n\n/**\n Free a dynamic allocator which is created by `yyjson_alc_dyn_new()`.\n @param alc The dynamic allocator to be destroyed.\n */\nyyjson_api void yyjson_alc_dyn_free(yyjson_alc *alc);\n\n\n\n/*==============================================================================\n * MARK: - Text Locating\n *============================================================================*/\n\n/**\n Locate the line and column number for a byte position in a string.\n This can be used to get better description for error position.\n\n @param str The input string.\n @param len The byte length of the input string.\n @param pos The byte position within the input string.\n @param line A pointer to receive the line number, starting from 1.\n @param col  A pointer to receive the column number, starting from 1.\n @param chr  A pointer to receive the character index, starting from 0.\n @return true on success, false if `str` is NULL or `pos` is out of bounds.\n @note Line/column/character are calculated based on Unicode characters for\n    compatibility with text editors. For multi-byte UTF-8 characters,\n    the returned value may not directly correspond to the byte position.\n */\nyyjson_api bool yyjson_locate_pos(const char *str, size_t len, size_t pos,\n                                  size_t *line, size_t *col, size_t *chr);\n\n\n\n/*==============================================================================\n * MARK: - JSON Structure\n *============================================================================*/\n\n/**\n An immutable document for reading JSON.\n This document holds memory for all its JSON values and strings. When it is no\n longer used, the user should call `yyjson_doc_free()` to free its memory.\n */\ntypedef struct yyjson_doc yyjson_doc;\n\n/**\n An immutable value for reading JSON.\n A JSON Value has the same lifetime as its document. The memory is held by its\n document and and cannot be freed alone.\n */\ntypedef struct yyjson_val yyjson_val;\n\n/**\n A mutable document for building JSON.\n This document holds memory for all its JSON values and strings. When it is no\n longer used, the user should call `yyjson_mut_doc_free()` to free its memory.\n */\ntypedef struct yyjson_mut_doc yyjson_mut_doc;\n\n/**\n A mutable value for building JSON.\n A JSON Value has the same lifetime as its document. The memory is held by its\n document and and cannot be freed alone.\n */\ntypedef struct yyjson_mut_val yyjson_mut_val;\n\n\n\n/*==============================================================================\n * MARK: - JSON Reader API\n *============================================================================*/\n\n/** Run-time options for JSON reader. */\ntypedef uint32_t yyjson_read_flag;\n\n/** Default option (RFC 8259 compliant):\n    - Read positive integer as uint64_t.\n    - Read negative integer as int64_t.\n    - Read floating-point number as double with round-to-nearest mode.\n    - Read integer which cannot fit in uint64_t or int64_t as double.\n    - Report error if double number is infinity.\n    - Report error if string contains invalid UTF-8 character or BOM.\n    - Report error on trailing commas, comments, inf and nan literals. */\nstatic const yyjson_read_flag YYJSON_READ_NOFLAG                    = 0;\n\n/** Read the input data in-situ.\n    This option allows the reader to modify and use input data to store string\n    values, which can increase reading speed slightly.\n    The caller should hold the input data before free the document.\n    The input data must be padded by at least `YYJSON_PADDING_SIZE` bytes.\n    For example: `[1,2]` should be `[1,2]\\0\\0\\0\\0`, input length should be 5. */\nstatic const yyjson_read_flag YYJSON_READ_INSITU                    = 1 << 0;\n\n/** Stop when done instead of issuing an error if there's additional content\n    after a JSON document. This option may be used to parse small pieces of JSON\n    in larger data, such as `NDJSON`. */\nstatic const yyjson_read_flag YYJSON_READ_STOP_WHEN_DONE            = 1 << 1;\n\n/** Allow single trailing comma at the end of an object or array,\n    such as `[1,2,3,]`, `{\"a\":1,\"b\":2,}` (non-standard). */\nstatic const yyjson_read_flag YYJSON_READ_ALLOW_TRAILING_COMMAS     = 1 << 2;\n\n/** Allow C-style single-line and mult-line comments (non-standard). */\nstatic const yyjson_read_flag YYJSON_READ_ALLOW_COMMENTS            = 1 << 3;\n\n/** Allow inf/nan number and literal, case-insensitive,\n    such as 1e999, NaN, inf, -Infinity (non-standard). */\nstatic const yyjson_read_flag YYJSON_READ_ALLOW_INF_AND_NAN         = 1 << 4;\n\n/** Read all numbers as raw strings (value with `YYJSON_TYPE_RAW` type),\n    inf/nan literal is also read as raw with `ALLOW_INF_AND_NAN` flag. */\nstatic const yyjson_read_flag YYJSON_READ_NUMBER_AS_RAW             = 1 << 5;\n\n/** Allow reading invalid unicode when parsing string values (non-standard).\n    Invalid characters will be allowed to appear in the string values, but\n    invalid escape sequences will still be reported as errors.\n    This flag does not affect the performance of correctly encoded strings.\n\n    @warning Strings in JSON values may contain incorrect encoding when this\n    option is used, you need to handle these strings carefully to avoid security\n    risks. */\nstatic const yyjson_read_flag YYJSON_READ_ALLOW_INVALID_UNICODE     = 1 << 6;\n\n/** Read big numbers as raw strings. These big numbers include integers that\n    cannot be represented by `int64_t` and `uint64_t`, and floating-point\n    numbers that cannot be represented by finite `double`.\n    The flag will be overridden by `YYJSON_READ_NUMBER_AS_RAW` flag. */\nstatic const yyjson_read_flag YYJSON_READ_BIGNUM_AS_RAW             = 1 << 7;\n\n/** Allow UTF-8 BOM and skip it before parsing if any (non-standard). */\nstatic const yyjson_read_flag YYJSON_READ_ALLOW_BOM                 = 1 << 8;\n\n/** Allow extended number formats (non-standard):\n    - Hexadecimal numbers, such as `0x7B`.\n    - Numbers with leading or trailing decimal point, such as `.123`, `123.`.\n    - Numbers with a leading plus sign, such as `+123`. */\nstatic const yyjson_read_flag YYJSON_READ_ALLOW_EXT_NUMBER          = 1 << 9;\n\n/** Allow extended escape sequences in strings (non-standard):\n    - Additional escapes: `\\a`, `\\e`, `\\v`, ``\\'``, `\\?`, `\\0`.\n    - Hex escapes: `\\xNN`, such as `\\x7B`.\n    - Line continuation: backslash followed by line terminator sequences.\n    - Unknown escape: if backslash is followed by an unsupported character,\n        the backslash will be removed and the character will be kept as-is.\n        However, `\\1`-`\\9` will still trigger an error. */\nstatic const yyjson_read_flag YYJSON_READ_ALLOW_EXT_ESCAPE          = 1 << 10;\n\n/** Allow extended whitespace characters (non-standard):\n    - Vertical tab `\\v` and form feed `\\f`.\n    - Line separator `\\u2028` and paragraph separator `\\u2029`.\n    - Non-breaking space `\\xA0`.\n    - Byte order mark: `\\uFEFF`.\n    - Other Unicode characters in the Zs (Separator, space) category. */\nstatic const yyjson_read_flag YYJSON_READ_ALLOW_EXT_WHITESPACE      = 1 << 11;\n\n/** Allow strings enclosed in single quotes (non-standard), such as ``'ab'``. */\nstatic const yyjson_read_flag YYJSON_READ_ALLOW_SINGLE_QUOTED_STR   = 1 << 12;\n\n/** Allow object keys without quotes (non-standard), such as `{a:1,b:2}`.\n    This extends the ECMAScript IdentifierName rule by allowing any\n    non-whitespace character with code point above `U+007F`. */\nstatic const yyjson_read_flag YYJSON_READ_ALLOW_UNQUOTED_KEY        = 1 << 13;\n\n/** Allow JSON5 format, see: [https://json5.org].\n    This flag supports all JSON5 features with some additional extensions:\n    - Accepts more escape sequences than JSON5 (e.g. `\\a`, `\\e`).\n    - Unquoted keys are not limited to ECMAScript IdentifierName.\n    - Allow case-insensitive `NaN`, `Inf` and `Infinity` literals. */\nstatic const yyjson_read_flag YYJSON_READ_JSON5 =\n    (1 << 2)  | /* YYJSON_READ_ALLOW_TRAILING_COMMAS */\n    (1 << 3)  | /* YYJSON_READ_ALLOW_COMMENTS */\n    (1 << 4)  | /* YYJSON_READ_ALLOW_INF_AND_NAN */\n    (1 << 9)  | /* YYJSON_READ_ALLOW_EXT_NUMBER */\n    (1 << 10) | /* YYJSON_READ_ALLOW_EXT_ESCAPE */\n    (1 << 11) | /* YYJSON_READ_ALLOW_EXT_WHITESPACE */\n    (1 << 12) | /* YYJSON_READ_ALLOW_SINGLE_QUOTED_STR */\n    (1 << 13);  /* YYJSON_READ_ALLOW_UNQUOTED_KEY */\n\n\n\n/** Result code for JSON reader. */\ntypedef uint32_t yyjson_read_code;\n\n/** Success, no error. */\nstatic const yyjson_read_code YYJSON_READ_SUCCESS                       = 0;\n\n/** Invalid parameter, such as NULL input string or 0 input length. */\nstatic const yyjson_read_code YYJSON_READ_ERROR_INVALID_PARAMETER       = 1;\n\n/** Memory allocation failed. */\nstatic const yyjson_read_code YYJSON_READ_ERROR_MEMORY_ALLOCATION       = 2;\n\n/** Input JSON string is empty. */\nstatic const yyjson_read_code YYJSON_READ_ERROR_EMPTY_CONTENT           = 3;\n\n/** Unexpected content after document, such as `[123]abc`. */\nstatic const yyjson_read_code YYJSON_READ_ERROR_UNEXPECTED_CONTENT      = 4;\n\n/** Unexpected end of input, the parsed part is valid, such as `[123`. */\nstatic const yyjson_read_code YYJSON_READ_ERROR_UNEXPECTED_END          = 5;\n\n/** Unexpected character inside the document, such as `[abc]`. */\nstatic const yyjson_read_code YYJSON_READ_ERROR_UNEXPECTED_CHARACTER    = 6;\n\n/** Invalid JSON structure, such as `[1,]`. */\nstatic const yyjson_read_code YYJSON_READ_ERROR_JSON_STRUCTURE          = 7;\n\n/** Invalid comment, deprecated, use `UNEXPECTED_END` for unclosed comment. */\nstatic const yyjson_read_code YYJSON_READ_ERROR_INVALID_COMMENT         = 8;\n\n/** Invalid number, such as `123.e12`, `000`. */\nstatic const yyjson_read_code YYJSON_READ_ERROR_INVALID_NUMBER          = 9;\n\n/** Invalid string, such as invalid escaped character inside a string. */\nstatic const yyjson_read_code YYJSON_READ_ERROR_INVALID_STRING          = 10;\n\n/** Invalid JSON literal, such as `truu`. */\nstatic const yyjson_read_code YYJSON_READ_ERROR_LITERAL                 = 11;\n\n/** Failed to open a file. */\nstatic const yyjson_read_code YYJSON_READ_ERROR_FILE_OPEN               = 12;\n\n/** Failed to read a file. */\nstatic const yyjson_read_code YYJSON_READ_ERROR_FILE_READ               = 13;\n\n/** Incomplete input during incremental parsing; parsing state is preserved. */\nstatic const yyjson_read_code YYJSON_READ_ERROR_MORE                    = 14;\n\n/** Error information for JSON reader. */\ntypedef struct yyjson_read_err {\n    /** Error code, see `yyjson_read_code` for all possible values. */\n    yyjson_read_code code;\n    /** Error message, constant, no need to free (NULL if success). */\n    const char *msg;\n    /** Error byte position for input data (0 if success). */\n    size_t pos;\n} yyjson_read_err;\n\n\n\n#if !defined(YYJSON_DISABLE_READER) || !YYJSON_DISABLE_READER\n\n/**\n Read JSON with options.\n\n This function is thread-safe when:\n 1. The `dat` is not modified by other threads.\n 2. The `alc` is thread-safe or NULL.\n\n @param dat The JSON data (UTF-8 without BOM), null-terminator is not required.\n    If this parameter is NULL, the function will fail and return NULL.\n    The `dat` will not be modified without the flag `YYJSON_READ_INSITU`, so you\n    can pass a `const char *` string and case it to `char *` if you don't use\n    the `YYJSON_READ_INSITU` flag.\n @param len The length of JSON data in bytes.\n    If this parameter is 0, the function will fail and return NULL.\n @param flg The JSON read options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n @param alc The memory allocator used by JSON reader.\n    Pass NULL to use the libc's default allocator.\n @param err A pointer to receive error information.\n    Pass NULL if you don't need error information.\n @return A new JSON document, or NULL if an error occurs.\n    When it's no longer needed, it should be freed with `yyjson_doc_free()`.\n */\nyyjson_api yyjson_doc *yyjson_read_opts(char *dat,\n                                        size_t len,\n                                        yyjson_read_flag flg,\n                                        const yyjson_alc *alc,\n                                        yyjson_read_err *err);\n\n/**\n Read a JSON file.\n\n This function is thread-safe when:\n 1. The file is not modified by other threads.\n 2. The `alc` is thread-safe or NULL.\n\n @param path The JSON file's path.\n    This should be a null-terminated string using the system's native encoding.\n    If this path is NULL or invalid, the function will fail and return NULL.\n @param flg The JSON read options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n @param alc The memory allocator used by JSON reader.\n    Pass NULL to use the libc's default allocator.\n @param err A pointer to receive error information.\n    Pass NULL if you don't need error information.\n @return A new JSON document, or NULL if an error occurs.\n    When it's no longer needed, it should be freed with `yyjson_doc_free()`.\n\n @warning On 32-bit operating system, files larger than 2GB may fail to read.\n */\nyyjson_api yyjson_doc *yyjson_read_file(const char *path,\n                                        yyjson_read_flag flg,\n                                        const yyjson_alc *alc,\n                                        yyjson_read_err *err);\n\n/**\n Read JSON from a file pointer.\n\n @param fp The file pointer.\n    The data will be read from the current position of the FILE to the end.\n    If this fp is NULL or invalid, the function will fail and return NULL.\n @param flg The JSON read options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n @param alc The memory allocator used by JSON reader.\n    Pass NULL to use the libc's default allocator.\n @param err A pointer to receive error information.\n    Pass NULL if you don't need error information.\n @return A new JSON document, or NULL if an error occurs.\n    When it's no longer needed, it should be freed with `yyjson_doc_free()`.\n\n @warning On 32-bit operating system, files larger than 2GB may fail to read.\n */\nyyjson_api yyjson_doc *yyjson_read_fp(FILE *fp,\n                                      yyjson_read_flag flg,\n                                      const yyjson_alc *alc,\n                                      yyjson_read_err *err);\n\n/**\n Read a JSON string.\n\n This function is thread-safe.\n\n @param dat The JSON data (UTF-8 without BOM), null-terminator is not required.\n    If this parameter is NULL, the function will fail and return NULL.\n @param len The length of JSON data in bytes.\n    If this parameter is 0, the function will fail and return NULL.\n @param flg The JSON read options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n @return A new JSON document, or NULL if an error occurs.\n    When it's no longer needed, it should be freed with `yyjson_doc_free()`.\n */\nyyjson_api_inline yyjson_doc *yyjson_read(const char *dat,\n                                          size_t len,\n                                          yyjson_read_flag flg) {\n    flg &= ~YYJSON_READ_INSITU; /* const string cannot be modified */\n    return yyjson_read_opts((char *)(void *)(size_t)(const void *)dat,\n                            len, flg, NULL, NULL);\n}\n\n\n\n#if !defined(YYJSON_DISABLE_INCR_READER) || !YYJSON_DISABLE_INCR_READER\n\n/** Opaque state for incremental JSON reader. */\ntypedef struct yyjson_incr_state yyjson_incr_state;\n\n/**\n Initialize state for incremental read.\n\n To read a large JSON document incrementally:\n 1. Call `yyjson_incr_new()` to create the state for incremental reading.\n 2. Call `yyjson_incr_read()` repeatedly.\n 3. Call `yyjson_incr_free()` to free the state.\n\n Note: The incremental JSON reader only supports standard JSON.\n Flags for non-standard features (e.g. comments, trailing commas) are ignored.\n\n @param buf The JSON data, null-terminator is not required.\n    If this parameter is NULL, the function will fail and return NULL.\n @param buf_len The length of the JSON data in `buf`.\n    If use `YYJSON_READ_INSITU`, `buf_len` should not include the padding size.\n @param flg The JSON read options.\n    Multiple options can be combined with `|` operator.\n @param alc The memory allocator used by JSON reader.\n    Pass NULL to use the libc's default allocator.\n @return A state for incremental reading.\n    It should be freed with `yyjson_incr_free()`.\n    NULL is returned if memory allocation fails.\n*/\nyyjson_api yyjson_incr_state *yyjson_incr_new(char *buf, size_t buf_len,\n                                              yyjson_read_flag flg,\n                                              const yyjson_alc *alc);\n\n/**\n Performs incremental read of up to `len` bytes.\n\n If NULL is returned and `err->code` is set to `YYJSON_READ_ERROR_MORE`, it\n indicates that more data is required to continue parsing. Then, call this\n function again with incremented `len`. Continue until a document is returned or\n an error other than `YYJSON_READ_ERROR_MORE` is returned.\n\n Note: Parsing in very small increments is not efficient. An increment of\n several kilobytes or megabytes is recommended.\n\n @param state The state for incremental reading, created using\n    `yyjson_incr_new()`.\n @param len The number of bytes of JSON data available to parse.\n    If this parameter is 0, the function will fail and return NULL.\n @param err A pointer to receive error information.\n @return A new JSON document, or NULL if an error occurs.\n    When the document is no longer needed, it should be freed with\n    `yyjson_doc_free()`.\n*/\nyyjson_api yyjson_doc *yyjson_incr_read(yyjson_incr_state *state, size_t len,\n                                        yyjson_read_err *err);\n\n/** Release the incremental read state and free the memory. */\nyyjson_api void yyjson_incr_free(yyjson_incr_state *state);\n\n#endif /* YYJSON_DISABLE_INCR_READER */\n\n/**\n Returns the size of maximum memory usage to read a JSON data.\n\n You may use this value to avoid malloc() or calloc() call inside the reader\n to get better performance, or read multiple JSON with one piece of memory.\n\n @param len The length of JSON data in bytes.\n @param flg The JSON read options.\n @return The maximum memory size to read this JSON, or 0 if overflow.\n\n @b Example\n @code\n    // read multiple JSON with same pre-allocated memory\n\n    char *dat1, *dat2, *dat3; // JSON data\n    size_t len1, len2, len3; // JSON length\n    size_t max_len = MAX(len1, MAX(len2, len3));\n    yyjson_doc *doc;\n\n    // use one allocator for multiple JSON\n    size_t size = yyjson_read_max_memory_usage(max_len, 0);\n    void *buf = malloc(size);\n    yyjson_alc alc;\n    yyjson_alc_pool_init(&alc, buf, size);\n\n    // no more alloc() or realloc() call during reading\n    doc = yyjson_read_opts(dat1, len1, 0, &alc, NULL);\n    yyjson_doc_free(doc);\n    doc = yyjson_read_opts(dat2, len2, 0, &alc, NULL);\n    yyjson_doc_free(doc);\n    doc = yyjson_read_opts(dat3, len3, 0, &alc, NULL);\n    yyjson_doc_free(doc);\n\n    free(buf);\n @endcode\n @see yyjson_alc_pool_init()\n */\nyyjson_api_inline size_t yyjson_read_max_memory_usage(size_t len,\n                                                      yyjson_read_flag flg) {\n    /*\n     1. The max value count is (json_size / 2 + 1),\n        for example: \"[1,2,3,4]\" size is 9, value count is 5.\n     2. Some broken JSON may cost more memory during reading, but fail at end,\n        for example: \"[[[[[[[[\".\n     3. yyjson use 16 bytes per value, see struct yyjson_val.\n     4. yyjson use dynamic memory with a growth factor of 1.5.\n\n     The max memory size is (json_size / 2 * 16 * 1.5 + padding).\n     */\n    size_t mul = (size_t)12 + !(flg & YYJSON_READ_INSITU);\n    size_t pad = 256;\n    size_t max = (size_t)(~(size_t)0);\n    if (flg & YYJSON_READ_STOP_WHEN_DONE) len = len < 256 ? 256 : len;\n    if (len >= (max - pad - mul) / mul) return 0;\n    return len * mul + pad;\n}\n\n/**\n Read a JSON number.\n\n This function is thread-safe when data is not modified by other threads.\n\n @param dat The JSON data (UTF-8 without BOM), null-terminator is required.\n    If this parameter is NULL, the function will fail and return NULL.\n @param val The output value where result is stored.\n    If this parameter is NULL, the function will fail and return NULL.\n    The value will hold either UINT or SINT or REAL number;\n @param flg The JSON read options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n    Supports `YYJSON_READ_NUMBER_AS_RAW` and `YYJSON_READ_ALLOW_INF_AND_NAN`.\n @param alc The memory allocator used for long number.\n    It is only used when the built-in floating point reader is disabled.\n    Pass NULL to use the libc's default allocator.\n @param err A pointer to receive error information.\n    Pass NULL if you don't need error information.\n @return If successful, a pointer to the character after the last character\n    used in the conversion, NULL if an error occurs.\n */\nyyjson_api const char *yyjson_read_number(const char *dat,\n                                          yyjson_val *val,\n                                          yyjson_read_flag flg,\n                                          const yyjson_alc *alc,\n                                          yyjson_read_err *err);\n\n/** Same as `yyjson_read_number()`. */\nyyjson_api_inline const char *yyjson_mut_read_number(const char *dat,\n                                                     yyjson_mut_val *val,\n                                                     yyjson_read_flag flg,\n                                                     const yyjson_alc *alc,\n                                                     yyjson_read_err *err) {\n    return yyjson_read_number(dat, (yyjson_val *)val, flg, alc, err);\n}\n\n#endif /* YYJSON_DISABLE_READER) */\n\n\n\n/*==============================================================================\n * MARK: - JSON Writer API\n *============================================================================*/\n\n/** Run-time options for JSON writer. */\ntypedef uint32_t yyjson_write_flag;\n\n/** Default option:\n    - Write JSON minify.\n    - Report error on inf or nan number.\n    - Report error on invalid UTF-8 string.\n    - Do not escape unicode or slash. */\nstatic const yyjson_write_flag YYJSON_WRITE_NOFLAG                  = 0;\n\n/** Write JSON pretty with 4 space indent. */\nstatic const yyjson_write_flag YYJSON_WRITE_PRETTY                  = 1 << 0;\n\n/** Escape unicode as `uXXXX`, make the output ASCII only. */\nstatic const yyjson_write_flag YYJSON_WRITE_ESCAPE_UNICODE          = 1 << 1;\n\n/** Escape '/' as '\\/'. */\nstatic const yyjson_write_flag YYJSON_WRITE_ESCAPE_SLASHES          = 1 << 2;\n\n/** Write inf and nan number as 'Infinity' and 'NaN' literal (non-standard). */\nstatic const yyjson_write_flag YYJSON_WRITE_ALLOW_INF_AND_NAN       = 1 << 3;\n\n/** Write inf and nan number as null literal.\n    This flag will override `YYJSON_WRITE_ALLOW_INF_AND_NAN` flag. */\nstatic const yyjson_write_flag YYJSON_WRITE_INF_AND_NAN_AS_NULL     = 1 << 4;\n\n/** Allow invalid unicode when encoding string values (non-standard).\n    Invalid characters in string value will be copied byte by byte.\n    If `YYJSON_WRITE_ESCAPE_UNICODE` flag is also set, invalid character will be\n    escaped as `U+FFFD` (replacement character).\n    This flag does not affect the performance of correctly encoded strings. */\nstatic const yyjson_write_flag YYJSON_WRITE_ALLOW_INVALID_UNICODE   = 1 << 5;\n\n/** Write JSON pretty with 2 space indent.\n    This flag will override `YYJSON_WRITE_PRETTY` flag. */\nstatic const yyjson_write_flag YYJSON_WRITE_PRETTY_TWO_SPACES       = 1 << 6;\n\n/** Adds a newline character `\\n` at the end of the JSON.\n    This can be helpful for text editors or NDJSON. */\nstatic const yyjson_write_flag YYJSON_WRITE_NEWLINE_AT_END          = 1 << 7;\n\n\n\n/** The highest 8 bits of `yyjson_write_flag` and real number value's `tag`\n    are reserved for controlling the output format of floating-point numbers. */\n#define YYJSON_WRITE_FP_FLAG_BITS 8\n\n/** The highest 4 bits of flag are reserved for precision value. */\n#define YYJSON_WRITE_FP_PREC_BITS 4\n\n/** Write floating-point number using fixed-point notation.\n    - This is similar to ECMAScript `Number.prototype.toFixed(prec)`,\n      but with trailing zeros removed. The `prec` ranges from 1 to 15.\n    - This will produce shorter output but may lose some precision. */\n#define YYJSON_WRITE_FP_TO_FIXED(prec) ((yyjson_write_flag)( \\\n    (uint32_t)((uint32_t)(prec)) << (32 - 4) ))\n\n/** Write floating-point numbers using single-precision (float).\n    - This casts `double` to `float` before serialization.\n    - This will produce shorter output, but may lose some precision.\n    - This flag is ignored if `YYJSON_WRITE_FP_TO_FIXED(prec)` is also used. */\n#define YYJSON_WRITE_FP_TO_FLOAT ((yyjson_write_flag)(1 << (32 - 5)))\n\n\n\n/** Result code for JSON writer */\ntypedef uint32_t yyjson_write_code;\n\n/** Success, no error. */\nstatic const yyjson_write_code YYJSON_WRITE_SUCCESS                     = 0;\n\n/** Invalid parameter, such as NULL document. */\nstatic const yyjson_write_code YYJSON_WRITE_ERROR_INVALID_PARAMETER     = 1;\n\n/** Memory allocation failure occurs. */\nstatic const yyjson_write_code YYJSON_WRITE_ERROR_MEMORY_ALLOCATION     = 2;\n\n/** Invalid value type in JSON document. */\nstatic const yyjson_write_code YYJSON_WRITE_ERROR_INVALID_VALUE_TYPE    = 3;\n\n/** NaN or Infinity number occurs. */\nstatic const yyjson_write_code YYJSON_WRITE_ERROR_NAN_OR_INF            = 4;\n\n/** Failed to open a file. */\nstatic const yyjson_write_code YYJSON_WRITE_ERROR_FILE_OPEN             = 5;\n\n/** Failed to write a file. */\nstatic const yyjson_write_code YYJSON_WRITE_ERROR_FILE_WRITE            = 6;\n\n/** Invalid unicode in string. */\nstatic const yyjson_write_code YYJSON_WRITE_ERROR_INVALID_STRING        = 7;\n\n/** Error information for JSON writer. */\ntypedef struct yyjson_write_err {\n    /** Error code, see `yyjson_write_code` for all possible values. */\n    yyjson_write_code code;\n    /** Error message, constant, no need to free (NULL if success). */\n    const char *msg;\n} yyjson_write_err;\n\n\n\n#if !defined(YYJSON_DISABLE_WRITER) || !YYJSON_DISABLE_WRITER\n\n/*==============================================================================\n * MARK: - JSON Document Writer API\n *============================================================================*/\n\n/**\n Write a document to JSON string with options.\n\n This function is thread-safe when:\n The `alc` is thread-safe or NULL.\n\n @param doc The JSON document.\n    If this doc is NULL or has no root, the function will fail and return false.\n @param flg The JSON write options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n @param alc The memory allocator used by JSON writer.\n    Pass NULL to use the libc's default allocator.\n @param len A pointer to receive output length in bytes (not including the\n    null-terminator). Pass NULL if you don't need length information.\n @param err A pointer to receive error information.\n    Pass NULL if you don't need error information.\n @return A new JSON string, or NULL if an error occurs.\n    This string is encoded as UTF-8 with a null-terminator.\n    When it's no longer needed, it should be freed with free() or alc->free().\n */\nyyjson_api char *yyjson_write_opts(const yyjson_doc *doc,\n                                   yyjson_write_flag flg,\n                                   const yyjson_alc *alc,\n                                   size_t *len,\n                                   yyjson_write_err *err);\n\n/**\n Write a document to JSON file with options.\n\n This function is thread-safe when:\n 1. The file is not accessed by other threads.\n 2. The `alc` is thread-safe or NULL.\n\n @param path The JSON file's path.\n    This should be a null-terminated string using the system's native encoding.\n    If this path is NULL or invalid, the function will fail and return false.\n    If this file is not empty, the content will be discarded.\n @param doc The JSON document.\n    If this doc is NULL or has no root, the function will fail and return false.\n @param flg The JSON write options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n @param alc The memory allocator used by JSON writer.\n    Pass NULL to use the libc's default allocator.\n @param err A pointer to receive error information.\n    Pass NULL if you don't need error information.\n @return true if successful, false if an error occurs.\n\n @warning On 32-bit operating system, files larger than 2GB may fail to write.\n */\nyyjson_api bool yyjson_write_file(const char *path,\n                                  const yyjson_doc *doc,\n                                  yyjson_write_flag flg,\n                                  const yyjson_alc *alc,\n                                  yyjson_write_err *err);\n\n/**\n Write a document to file pointer with options.\n\n @param fp The file pointer.\n    The data will be written to the current position of the file.\n    If this fp is NULL or invalid, the function will fail and return false.\n @param doc The JSON document.\n    If this doc is NULL or has no root, the function will fail and return false.\n @param flg The JSON write options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n @param alc The memory allocator used by JSON writer.\n    Pass NULL to use the libc's default allocator.\n @param err A pointer to receive error information.\n    Pass NULL if you don't need error information.\n @return true if successful, false if an error occurs.\n\n @warning On 32-bit operating system, files larger than 2GB may fail to write.\n */\nyyjson_api bool yyjson_write_fp(FILE *fp,\n                                const yyjson_doc *doc,\n                                yyjson_write_flag flg,\n                                const yyjson_alc *alc,\n                                yyjson_write_err *err);\n\n/**\n Write a document to JSON string.\n\n This function is thread-safe.\n\n @param doc The JSON document.\n    If this doc is NULL or has no root, the function will fail and return false.\n @param flg The JSON write options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n @param len A pointer to receive output length in bytes (not including the\n    null-terminator). Pass NULL if you don't need length information.\n @return A new JSON string, or NULL if an error occurs.\n    This string is encoded as UTF-8 with a null-terminator.\n    When it's no longer needed, it should be freed with free().\n */\nyyjson_api_inline char *yyjson_write(const yyjson_doc *doc,\n                                     yyjson_write_flag flg,\n                                     size_t *len) {\n    return yyjson_write_opts(doc, flg, NULL, len, NULL);\n}\n\n\n\n/**\n Write a document to JSON string with options.\n\n This function is thread-safe when:\n 1. The `doc` is not modified by other threads.\n 2. The `alc` is thread-safe or NULL.\n\n @param doc The mutable JSON document.\n    If this doc is NULL or has no root, the function will fail and return false.\n @param flg The JSON write options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n @param alc The memory allocator used by JSON writer.\n    Pass NULL to use the libc's default allocator.\n @param len A pointer to receive output length in bytes (not including the\n    null-terminator). Pass NULL if you don't need length information.\n @param err A pointer to receive error information.\n    Pass NULL if you don't need error information.\n @return A new JSON string, or NULL if an error occurs.\n    This string is encoded as UTF-8 with a null-terminator.\n    When it's no longer needed, it should be freed with free() or alc->free().\n */\nyyjson_api char *yyjson_mut_write_opts(const yyjson_mut_doc *doc,\n                                       yyjson_write_flag flg,\n                                       const yyjson_alc *alc,\n                                       size_t *len,\n                                       yyjson_write_err *err);\n\n/**\n Write a document to JSON file with options.\n\n This function is thread-safe when:\n 1. The file is not accessed by other threads.\n 2. The `doc` is not modified by other threads.\n 3. The `alc` is thread-safe or NULL.\n\n @param path The JSON file's path.\n    This should be a null-terminated string using the system's native encoding.\n    If this path is NULL or invalid, the function will fail and return false.\n    If this file is not empty, the content will be discarded.\n @param doc The mutable JSON document.\n    If this doc is NULL or has no root, the function will fail and return false.\n @param flg The JSON write options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n @param alc The memory allocator used by JSON writer.\n    Pass NULL to use the libc's default allocator.\n @param err A pointer to receive error information.\n    Pass NULL if you don't need error information.\n @return true if successful, false if an error occurs.\n\n @warning On 32-bit operating system, files larger than 2GB may fail to write.\n */\nyyjson_api bool yyjson_mut_write_file(const char *path,\n                                      const yyjson_mut_doc *doc,\n                                      yyjson_write_flag flg,\n                                      const yyjson_alc *alc,\n                                      yyjson_write_err *err);\n\n/**\n Write a document to file pointer with options.\n\n @param fp The file pointer.\n    The data will be written to the current position of the file.\n    If this fp is NULL or invalid, the function will fail and return false.\n @param doc The mutable JSON document.\n    If this doc is NULL or has no root, the function will fail and return false.\n @param flg The JSON write options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n @param alc The memory allocator used by JSON writer.\n    Pass NULL to use the libc's default allocator.\n @param err A pointer to receive error information.\n    Pass NULL if you don't need error information.\n @return true if successful, false if an error occurs.\n\n @warning On 32-bit operating system, files larger than 2GB may fail to write.\n */\nyyjson_api bool yyjson_mut_write_fp(FILE *fp,\n                                    const yyjson_mut_doc *doc,\n                                    yyjson_write_flag flg,\n                                    const yyjson_alc *alc,\n                                    yyjson_write_err *err);\n\n/**\n Write a document to JSON string.\n\n This function is thread-safe when:\n The `doc` is not modified by other threads.\n\n @param doc The JSON document.\n    If this doc is NULL or has no root, the function will fail and return false.\n @param flg The JSON write options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n @param len A pointer to receive output length in bytes (not including the\n    null-terminator). Pass NULL if you don't need length information.\n @return A new JSON string, or NULL if an error occurs.\n    This string is encoded as UTF-8 with a null-terminator.\n    When it's no longer needed, it should be freed with free().\n */\nyyjson_api_inline char *yyjson_mut_write(const yyjson_mut_doc *doc,\n                                         yyjson_write_flag flg,\n                                         size_t *len) {\n    return yyjson_mut_write_opts(doc, flg, NULL, len, NULL);\n}\n\n\n\n/*==============================================================================\n * MARK: - JSON Value Writer API\n *============================================================================*/\n\n/**\n Write a value to JSON string with options.\n\n This function is thread-safe when:\n The `alc` is thread-safe or NULL.\n\n @param val The JSON root value.\n    If this parameter is NULL, the function will fail and return NULL.\n @param flg The JSON write options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n @param alc The memory allocator used by JSON writer.\n    Pass NULL to use the libc's default allocator.\n @param len A pointer to receive output length in bytes (not including the\n    null-terminator). Pass NULL if you don't need length information.\n @param err A pointer to receive error information.\n    Pass NULL if you don't need error information.\n @return A new JSON string, or NULL if an error occurs.\n    This string is encoded as UTF-8 with a null-terminator.\n    When it's no longer needed, it should be freed with free() or alc->free().\n */\nyyjson_api char *yyjson_val_write_opts(const yyjson_val *val,\n                                       yyjson_write_flag flg,\n                                       const yyjson_alc *alc,\n                                       size_t *len,\n                                       yyjson_write_err *err);\n\n/**\n Write a value to JSON file with options.\n\n This function is thread-safe when:\n 1. The file is not accessed by other threads.\n 2. The `alc` is thread-safe or NULL.\n\n @param path The JSON file's path.\n    This should be a null-terminated string using the system's native encoding.\n    If this path is NULL or invalid, the function will fail and return false.\n    If this file is not empty, the content will be discarded.\n @param val The JSON root value.\n    If this parameter is NULL, the function will fail and return NULL.\n @param flg The JSON write options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n @param alc The memory allocator used by JSON writer.\n    Pass NULL to use the libc's default allocator.\n @param err A pointer to receive error information.\n    Pass NULL if you don't need error information.\n @return true if successful, false if an error occurs.\n\n @warning On 32-bit operating system, files larger than 2GB may fail to write.\n */\nyyjson_api bool yyjson_val_write_file(const char *path,\n                                      const yyjson_val *val,\n                                      yyjson_write_flag flg,\n                                      const yyjson_alc *alc,\n                                      yyjson_write_err *err);\n\n/**\n Write a value to file pointer with options.\n\n @param fp The file pointer.\n    The data will be written to the current position of the file.\n    If this path is NULL or invalid, the function will fail and return false.\n @param val The JSON root value.\n    If this parameter is NULL, the function will fail and return NULL.\n @param flg The JSON write options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n @param alc The memory allocator used by JSON writer.\n    Pass NULL to use the libc's default allocator.\n @param err A pointer to receive error information.\n    Pass NULL if you don't need error information.\n @return true if successful, false if an error occurs.\n\n @warning On 32-bit operating system, files larger than 2GB may fail to write.\n */\nyyjson_api bool yyjson_val_write_fp(FILE *fp,\n                                    const yyjson_val *val,\n                                    yyjson_write_flag flg,\n                                    const yyjson_alc *alc,\n                                    yyjson_write_err *err);\n\n/**\n Write a value to JSON string.\n\n This function is thread-safe.\n\n @param val The JSON root value.\n    If this parameter is NULL, the function will fail and return NULL.\n @param flg The JSON write options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n @param len A pointer to receive output length in bytes (not including the\n    null-terminator). Pass NULL if you don't need length information.\n @return A new JSON string, or NULL if an error occurs.\n    This string is encoded as UTF-8 with a null-terminator.\n    When it's no longer needed, it should be freed with free().\n */\nyyjson_api_inline char *yyjson_val_write(const yyjson_val *val,\n                                         yyjson_write_flag flg,\n                                         size_t *len) {\n    return yyjson_val_write_opts(val, flg, NULL, len, NULL);\n}\n\n/**\n Write a value to JSON string with options.\n\n This function is thread-safe when:\n 1. The `val` is not modified by other threads.\n 2. The `alc` is thread-safe or NULL.\n\n @param val The mutable JSON root value.\n    If this parameter is NULL, the function will fail and return NULL.\n @param flg The JSON write options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n @param alc The memory allocator used by JSON writer.\n    Pass NULL to use the libc's default allocator.\n @param len A pointer to receive output length in bytes (not including the\n    null-terminator). Pass NULL if you don't need length information.\n @param err A pointer to receive error information.\n    Pass NULL if you don't need error information.\n @return  A new JSON string, or NULL if an error occurs.\n    This string is encoded as UTF-8 with a null-terminator.\n    When it's no longer needed, it should be freed with free() or alc->free().\n */\nyyjson_api char *yyjson_mut_val_write_opts(const yyjson_mut_val *val,\n                                           yyjson_write_flag flg,\n                                           const yyjson_alc *alc,\n                                           size_t *len,\n                                           yyjson_write_err *err);\n\n/**\n Write a value to JSON file with options.\n\n This function is thread-safe when:\n 1. The file is not accessed by other threads.\n 2. The `val` is not modified by other threads.\n 3. The `alc` is thread-safe or NULL.\n\n @param path The JSON file's path.\n    This should be a null-terminated string using the system's native encoding.\n    If this path is NULL or invalid, the function will fail and return false.\n    If this file is not empty, the content will be discarded.\n @param val The mutable JSON root value.\n    If this parameter is NULL, the function will fail and return NULL.\n @param flg The JSON write options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n @param alc The memory allocator used by JSON writer.\n    Pass NULL to use the libc's default allocator.\n @param err A pointer to receive error information.\n    Pass NULL if you don't need error information.\n @return true if successful, false if an error occurs.\n\n @warning On 32-bit operating system, files larger than 2GB may fail to write.\n */\nyyjson_api bool yyjson_mut_val_write_file(const char *path,\n                                          const yyjson_mut_val *val,\n                                          yyjson_write_flag flg,\n                                          const yyjson_alc *alc,\n                                          yyjson_write_err *err);\n\n/**\n Write a value to JSON file with options.\n\n @param fp The file pointer.\n    The data will be written to the current position of the file.\n    If this path is NULL or invalid, the function will fail and return false.\n @param val The mutable JSON root value.\n    If this parameter is NULL, the function will fail and return NULL.\n @param flg The JSON write options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n @param alc The memory allocator used by JSON writer.\n    Pass NULL to use the libc's default allocator.\n @param err A pointer to receive error information.\n    Pass NULL if you don't need error information.\n @return true if successful, false if an error occurs.\n\n @warning On 32-bit operating system, files larger than 2GB may fail to write.\n */\nyyjson_api bool yyjson_mut_val_write_fp(FILE *fp,\n                                        const yyjson_mut_val *val,\n                                        yyjson_write_flag flg,\n                                        const yyjson_alc *alc,\n                                        yyjson_write_err *err);\n\n/**\n Write a value to JSON string.\n\n This function is thread-safe when:\n The `val` is not modified by other threads.\n\n @param val The JSON root value.\n    If this parameter is NULL, the function will fail and return NULL.\n @param flg The JSON write options.\n    Multiple options can be combined with `|` operator. 0 means no options.\n @param len A pointer to receive output length in bytes (not including the\n    null-terminator). Pass NULL if you don't need length information.\n @return A new JSON string, or NULL if an error occurs.\n    This string is encoded as UTF-8 with a null-terminator.\n    When it's no longer needed, it should be freed with free().\n */\nyyjson_api_inline char *yyjson_mut_val_write(const yyjson_mut_val *val,\n                                             yyjson_write_flag flg,\n                                             size_t *len) {\n    return yyjson_mut_val_write_opts(val, flg, NULL, len, NULL);\n}\n\n/**\n Write a JSON number.\n\n @param val A JSON number value to be converted to a string.\n    If this parameter is invalid, the function will fail and return NULL.\n @param buf A buffer to store the resulting null-terminated string.\n    If this parameter is NULL, the function will fail and return NULL.\n    For integer values, the buffer must be at least 21 bytes.\n    For floating-point values, the buffer must be at least 40 bytes.\n @return On success, returns a pointer to the character after the last\n    written character. On failure, returns NULL.\n @note\n    - This function is thread-safe and does not allocate memory\n        (when `YYJSON_DISABLE_FAST_FP_CONV` is not defined).\n    - This function will fail and return NULL only in the following cases:\n        1) `val` or `buf` is NULL;\n        2) `val` is not a number type;\n        3) `val` is `inf` or `nan`, and non-standard JSON is explicitly disabled\n            via the `YYJSON_DISABLE_NON_STANDARD` flag.\n */\nyyjson_api char *yyjson_write_number(const yyjson_val *val, char *buf);\n\n/** Same as `yyjson_write_number()`. */\nyyjson_api_inline char *yyjson_mut_write_number(const yyjson_mut_val *val,\n                                                char *buf) {\n    return yyjson_write_number((const yyjson_val *)val, buf);\n}\n\n#endif /* YYJSON_DISABLE_WRITER */\n\n\n\n/*==============================================================================\n * MARK: - JSON Document API\n *============================================================================*/\n\n/** Returns the root value of this JSON document.\n    Returns NULL if `doc` is NULL. */\nyyjson_api_inline yyjson_val *yyjson_doc_get_root(yyjson_doc *doc);\n\n/** Returns read size of input JSON data.\n    Returns 0 if `doc` is NULL.\n    For example: the read size of `[1,2,3]` is 7 bytes.  */\nyyjson_api_inline size_t yyjson_doc_get_read_size(yyjson_doc *doc);\n\n/** Returns total value count in this JSON document.\n    Returns 0 if `doc` is NULL.\n    For example: the value count of `[1,2,3]` is 4. */\nyyjson_api_inline size_t yyjson_doc_get_val_count(yyjson_doc *doc);\n\n/** Release the JSON document and free the memory.\n    After calling this function, the `doc` and all values from the `doc` are no\n    longer available. This function will do nothing if the `doc` is NULL. */\nyyjson_api_inline void yyjson_doc_free(yyjson_doc *doc);\n\n\n\n/*==============================================================================\n * MARK: - JSON Value Type API\n *============================================================================*/\n\n/** Returns whether the JSON value is raw.\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_is_raw(yyjson_val *val);\n\n/** Returns whether the JSON value is `null`.\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_is_null(yyjson_val *val);\n\n/** Returns whether the JSON value is `true`.\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_is_true(yyjson_val *val);\n\n/** Returns whether the JSON value is `false`.\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_is_false(yyjson_val *val);\n\n/** Returns whether the JSON value is bool (true/false).\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_is_bool(yyjson_val *val);\n\n/** Returns whether the JSON value is unsigned integer (uint64_t).\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_is_uint(yyjson_val *val);\n\n/** Returns whether the JSON value is signed integer (int64_t).\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_is_sint(yyjson_val *val);\n\n/** Returns whether the JSON value is integer (uint64_t/int64_t).\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_is_int(yyjson_val *val);\n\n/** Returns whether the JSON value is real number (double).\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_is_real(yyjson_val *val);\n\n/** Returns whether the JSON value is number (uint64_t/int64_t/double).\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_is_num(yyjson_val *val);\n\n/** Returns whether the JSON value is string.\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_is_str(yyjson_val *val);\n\n/** Returns whether the JSON value is array.\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_is_arr(yyjson_val *val);\n\n/** Returns whether the JSON value is object.\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_is_obj(yyjson_val *val);\n\n/** Returns whether the JSON value is container (array/object).\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_is_ctn(yyjson_val *val);\n\n\n\n/*==============================================================================\n * MARK: - JSON Value Content API\n *============================================================================*/\n\n/** Returns the JSON value's type.\n    Returns YYJSON_TYPE_NONE if `val` is NULL. */\nyyjson_api_inline yyjson_type yyjson_get_type(yyjson_val *val);\n\n/** Returns the JSON value's subtype.\n    Returns YYJSON_SUBTYPE_NONE if `val` is NULL. */\nyyjson_api_inline yyjson_subtype yyjson_get_subtype(yyjson_val *val);\n\n/** Returns the JSON value's tag.\n    Returns 0 if `val` is NULL. */\nyyjson_api_inline uint8_t yyjson_get_tag(yyjson_val *val);\n\n/** Returns the JSON value's type description.\n    The return value should be one of these strings: \"raw\", \"null\", \"string\",\n    \"array\", \"object\", \"true\", \"false\", \"uint\", \"sint\", \"real\", \"unknown\". */\nyyjson_api_inline const char *yyjson_get_type_desc(yyjson_val *val);\n\n/** Returns the content if the value is raw.\n    Returns NULL if `val` is NULL or type is not raw. */\nyyjson_api_inline const char *yyjson_get_raw(yyjson_val *val);\n\n/** Returns the content if the value is bool.\n    Returns false if `val` is NULL or type is not bool. */\nyyjson_api_inline bool yyjson_get_bool(yyjson_val *val);\n\n/** Returns the content and cast to uint64_t.\n    Returns 0 if `val` is NULL or type is not integer(sint/uint). */\nyyjson_api_inline uint64_t yyjson_get_uint(yyjson_val *val);\n\n/** Returns the content and cast to int64_t.\n    Returns 0 if `val` is NULL or type is not integer(sint/uint). */\nyyjson_api_inline int64_t yyjson_get_sint(yyjson_val *val);\n\n/** Returns the content and cast to int.\n    Returns 0 if `val` is NULL or type is not integer(sint/uint). */\nyyjson_api_inline int yyjson_get_int(yyjson_val *val);\n\n/** Returns the content if the value is real number, or 0.0 on error.\n    Returns 0.0 if `val` is NULL or type is not real(double). */\nyyjson_api_inline double yyjson_get_real(yyjson_val *val);\n\n/** Returns the content and typecast to `double` if the value is number.\n    Returns 0.0 if `val` is NULL or type is not number(uint/sint/real). */\nyyjson_api_inline double yyjson_get_num(yyjson_val *val);\n\n/** Returns the content if the value is string.\n    Returns NULL if `val` is NULL or type is not string. */\nyyjson_api_inline const char *yyjson_get_str(yyjson_val *val);\n\n/** Returns the content length (string length, array size, object size.\n    Returns 0 if `val` is NULL or type is not string/array/object. */\nyyjson_api_inline size_t yyjson_get_len(yyjson_val *val);\n\n/** Returns whether the JSON value is equals to a string.\n    Returns false if input is NULL or type is not string. */\nyyjson_api_inline bool yyjson_equals_str(yyjson_val *val, const char *str);\n\n/** Returns whether the JSON value is equals to a string.\n    The `str` should be a UTF-8 string, null-terminator is not required.\n    Returns false if input is NULL or type is not string. */\nyyjson_api_inline bool yyjson_equals_strn(yyjson_val *val, const char *str,\n                                          size_t len);\n\n/** Returns whether two JSON values are equal (deep compare).\n    Returns false if input is NULL.\n    @note the result may be inaccurate if object has duplicate keys.\n    @warning This function is recursive and may cause a stack overflow\n        if the object level is too deep. */\nyyjson_api_inline bool yyjson_equals(yyjson_val *lhs, yyjson_val *rhs);\n\n/** Set the value to raw.\n    Returns false if input is NULL or `val` is object or array.\n    @warning This will modify the `immutable` value, use with caution. */\nyyjson_api_inline bool yyjson_set_raw(yyjson_val *val,\n                                      const char *raw, size_t len);\n\n/** Set the value to null.\n    Returns false if input is NULL or `val` is object or array.\n    @warning This will modify the `immutable` value, use with caution. */\nyyjson_api_inline bool yyjson_set_null(yyjson_val *val);\n\n/** Set the value to bool.\n    Returns false if input is NULL or `val` is object or array.\n    @warning This will modify the `immutable` value, use with caution. */\nyyjson_api_inline bool yyjson_set_bool(yyjson_val *val, bool num);\n\n/** Set the value to uint.\n    Returns false if input is NULL or `val` is object or array.\n    @warning This will modify the `immutable` value, use with caution. */\nyyjson_api_inline bool yyjson_set_uint(yyjson_val *val, uint64_t num);\n\n/** Set the value to sint.\n    Returns false if input is NULL or `val` is object or array.\n    @warning This will modify the `immutable` value, use with caution. */\nyyjson_api_inline bool yyjson_set_sint(yyjson_val *val, int64_t num);\n\n/** Set the value to int.\n    Returns false if input is NULL or `val` is object or array.\n    @warning This will modify the `immutable` value, use with caution. */\nyyjson_api_inline bool yyjson_set_int(yyjson_val *val, int num);\n\n/** Set the value to float.\n    Returns false if input is NULL or `val` is object or array.\n    @warning This will modify the `immutable` value, use with caution. */\nyyjson_api_inline bool yyjson_set_float(yyjson_val *val, float num);\n\n/** Set the value to double.\n    Returns false if input is NULL or `val` is object or array.\n    @warning This will modify the `immutable` value, use with caution. */\nyyjson_api_inline bool yyjson_set_double(yyjson_val *val, double num);\n\n/** Set the value to real.\n    Returns false if input is NULL or `val` is object or array.\n    @warning This will modify the `immutable` value, use with caution. */\nyyjson_api_inline bool yyjson_set_real(yyjson_val *val, double num);\n\n/** Set the floating-point number's output format to fixed-point notation.\n    Returns false if input is NULL or `val` is not real type.\n    @see YYJSON_WRITE_FP_TO_FIXED flag.\n    @warning This will modify the `immutable` value, use with caution. */\nyyjson_api_inline bool yyjson_set_fp_to_fixed(yyjson_val *val, int prec);\n\n/** Set the floating-point number's output format to single-precision.\n    Returns false if input is NULL or `val` is not real type.\n    @see YYJSON_WRITE_FP_TO_FLOAT flag.\n    @warning This will modify the `immutable` value, use with caution. */\nyyjson_api_inline bool yyjson_set_fp_to_float(yyjson_val *val, bool flt);\n\n/** Set the value to string (null-terminated).\n    Returns false if input is NULL or `val` is object or array.\n    @warning This will modify the `immutable` value, use with caution. */\nyyjson_api_inline bool yyjson_set_str(yyjson_val *val, const char *str);\n\n/** Set the value to string (with length).\n    Returns false if input is NULL or `val` is object or array.\n    @warning This will modify the `immutable` value, use with caution. */\nyyjson_api_inline bool yyjson_set_strn(yyjson_val *val,\n                                       const char *str, size_t len);\n\n/** Marks this string as not needing to be escaped during JSON writing.\n    This can be used to avoid the overhead of escaping if the string contains\n    only characters that do not require escaping.\n    Returns false if input is NULL or `val` is not string.\n    @see YYJSON_SUBTYPE_NOESC subtype.\n    @warning This will modify the `immutable` value, use with caution. */\nyyjson_api_inline bool yyjson_set_str_noesc(yyjson_val *val, bool noesc);\n\n\n\n/*==============================================================================\n * MARK: - JSON Array API\n *============================================================================*/\n\n/** Returns the number of elements in this array.\n    Returns 0 if `arr` is NULL or type is not array. */\nyyjson_api_inline size_t yyjson_arr_size(yyjson_val *arr);\n\n/** Returns the element at the specified position in this array.\n    Returns NULL if array is NULL/empty or the index is out of bounds.\n    @warning This function takes a linear search time if array is not flat.\n        For example: `[1,{},3]` is flat, `[1,[2],3]` is not flat. */\nyyjson_api_inline yyjson_val *yyjson_arr_get(yyjson_val *arr, size_t idx);\n\n/** Returns the first element of this array.\n    Returns NULL if `arr` is NULL/empty or type is not array. */\nyyjson_api_inline yyjson_val *yyjson_arr_get_first(yyjson_val *arr);\n\n/** Returns the last element of this array.\n    Returns NULL if `arr` is NULL/empty or type is not array.\n    @warning This function takes a linear search time if array is not flat.\n        For example: `[1,{},3]` is flat, `[1,[2],3]` is not flat.*/\nyyjson_api_inline yyjson_val *yyjson_arr_get_last(yyjson_val *arr);\n\n\n\n/*==============================================================================\n * MARK: - JSON Array Iterator API\n *============================================================================*/\n\n/**\n A JSON array iterator.\n\n @b Example\n @code\n    yyjson_val *val;\n    yyjson_arr_iter iter = yyjson_arr_iter_with(arr);\n    while ((val = yyjson_arr_iter_next(&iter))) {\n        your_func(val);\n    }\n @endcode\n */\ntypedef struct yyjson_arr_iter {\n    size_t idx; /**< next value's index */\n    size_t max; /**< maximum index (arr.size) */\n    yyjson_val *cur; /**< next value */\n} yyjson_arr_iter;\n\n/**\n Initialize an iterator for this array.\n\n @param arr The array to be iterated over.\n    If this parameter is NULL or not an array, `iter` will be set to empty.\n @param iter The iterator to be initialized.\n    If this parameter is NULL, the function will fail and return false.\n @return true if the `iter` has been successfully initialized.\n\n @note The iterator does not need to be destroyed.\n */\nyyjson_api_inline bool yyjson_arr_iter_init(yyjson_val *arr,\n                                            yyjson_arr_iter *iter);\n\n/**\n Create an iterator with an array , same as `yyjson_arr_iter_init()`.\n\n @param arr The array to be iterated over.\n    If this parameter is NULL or not an array, an empty iterator will returned.\n @return A new iterator for the array.\n\n @note The iterator does not need to be destroyed.\n */\nyyjson_api_inline yyjson_arr_iter yyjson_arr_iter_with(yyjson_val *arr);\n\n/**\n Returns whether the iteration has more elements.\n If `iter` is NULL, this function will return false.\n */\nyyjson_api_inline bool yyjson_arr_iter_has_next(yyjson_arr_iter *iter);\n\n/**\n Returns the next element in the iteration, or NULL on end.\n If `iter` is NULL, this function will return NULL.\n */\nyyjson_api_inline yyjson_val *yyjson_arr_iter_next(yyjson_arr_iter *iter);\n\n/**\n Macro for iterating over an array.\n It works like iterator, but with a more intuitive API.\n\n @b Example\n @code\n    size_t idx, max;\n    yyjson_val *val;\n    yyjson_arr_foreach(arr, idx, max, val) {\n        your_func(idx, val);\n    }\n @endcode\n */\n#define yyjson_arr_foreach(arr, idx, max, val) \\\n    for ((idx) = 0, \\\n        (max) = yyjson_arr_size(arr), \\\n        (val) = yyjson_arr_get_first(arr); \\\n        (idx) < (max); \\\n        (idx)++, \\\n        (val) = unsafe_yyjson_get_next(val))\n\n\n\n/*==============================================================================\n * MARK: - JSON Object API\n *============================================================================*/\n\n/** Returns the number of key-value pairs in this object.\n    Returns 0 if `obj` is NULL or type is not object. */\nyyjson_api_inline size_t yyjson_obj_size(yyjson_val *obj);\n\n/** Returns the value to which the specified key is mapped.\n    Returns NULL if this object contains no mapping for the key.\n    Returns NULL if `obj/key` is NULL, or type is not object.\n\n    The `key` should be a null-terminated UTF-8 string.\n\n    @warning This function takes a linear search time. */\nyyjson_api_inline yyjson_val *yyjson_obj_get(yyjson_val *obj, const char *key);\n\n/** Returns the value to which the specified key is mapped.\n    Returns NULL if this object contains no mapping for the key.\n    Returns NULL if `obj/key` is NULL, or type is not object.\n\n    The `key` should be a UTF-8 string, null-terminator is not required.\n    The `key_len` should be the length of the key, in bytes.\n\n    @warning This function takes a linear search time. */\nyyjson_api_inline yyjson_val *yyjson_obj_getn(yyjson_val *obj, const char *key,\n                                              size_t key_len);\n\n\n\n/*==============================================================================\n * MARK: - JSON Object Iterator API\n *============================================================================*/\n\n/**\n A JSON object iterator.\n\n @b Example\n @code\n    yyjson_val *key, *val;\n    yyjson_obj_iter iter = yyjson_obj_iter_with(obj);\n    while ((key = yyjson_obj_iter_next(&iter))) {\n        val = yyjson_obj_iter_get_val(key);\n        your_func(key, val);\n    }\n @endcode\n\n If the ordering of the keys is known at compile-time, you can use this method\n to speed up value lookups:\n @code\n    // {\"k1\":1, \"k2\": 3, \"k3\": 3}\n    yyjson_val *key, *val;\n    yyjson_obj_iter iter = yyjson_obj_iter_with(obj);\n    yyjson_val *v1 = yyjson_obj_iter_get(&iter, \"k1\");\n    yyjson_val *v3 = yyjson_obj_iter_get(&iter, \"k3\");\n @endcode\n @see yyjson_obj_iter_get() and yyjson_obj_iter_getn()\n */\ntypedef struct yyjson_obj_iter {\n    size_t idx; /**< next key's index */\n    size_t max; /**< maximum key index (obj.size) */\n    yyjson_val *cur; /**< next key */\n    yyjson_val *obj; /**< the object being iterated */\n} yyjson_obj_iter;\n\n/**\n Initialize an iterator for this object.\n\n @param obj The object to be iterated over.\n    If this parameter is NULL or not an object, `iter` will be set to empty.\n @param iter The iterator to be initialized.\n    If this parameter is NULL, the function will fail and return false.\n @return true if the `iter` has been successfully initialized.\n\n @note The iterator does not need to be destroyed.\n */\nyyjson_api_inline bool yyjson_obj_iter_init(yyjson_val *obj,\n                                            yyjson_obj_iter *iter);\n\n/**\n Create an iterator with an object, same as `yyjson_obj_iter_init()`.\n\n @param obj The object to be iterated over.\n    If this parameter is NULL or not an object, an empty iterator will returned.\n @return A new iterator for the object.\n\n @note The iterator does not need to be destroyed.\n */\nyyjson_api_inline yyjson_obj_iter yyjson_obj_iter_with(yyjson_val *obj);\n\n/**\n Returns whether the iteration has more elements.\n If `iter` is NULL, this function will return false.\n */\nyyjson_api_inline bool yyjson_obj_iter_has_next(yyjson_obj_iter *iter);\n\n/**\n Returns the next key in the iteration, or NULL on end.\n If `iter` is NULL, this function will return NULL.\n */\nyyjson_api_inline yyjson_val *yyjson_obj_iter_next(yyjson_obj_iter *iter);\n\n/**\n Returns the value for key inside the iteration.\n If `iter` is NULL, this function will return NULL.\n */\nyyjson_api_inline yyjson_val *yyjson_obj_iter_get_val(yyjson_val *key);\n\n/**\n Iterates to a specified key and returns the value.\n\n This function does the same thing as `yyjson_obj_get()`, but is much faster\n if the ordering of the keys is known at compile-time and you are using the same\n order to look up the values. If the key exists in this object, then the\n iterator will stop at the next key, otherwise the iterator will not change and\n NULL is returned.\n\n @param iter The object iterator, should not be NULL.\n @param key The key, should be a UTF-8 string with null-terminator.\n @return The value to which the specified key is mapped.\n    NULL if this object contains no mapping for the key or input is invalid.\n\n @warning This function takes a linear search time if the key is not nearby.\n */\nyyjson_api_inline yyjson_val *yyjson_obj_iter_get(yyjson_obj_iter *iter,\n                                                  const char *key);\n\n/**\n Iterates to a specified key and returns the value.\n\n This function does the same thing as `yyjson_obj_getn()`, but is much faster\n if the ordering of the keys is known at compile-time and you are using the same\n order to look up the values. If the key exists in this object, then the\n iterator will stop at the next key, otherwise the iterator will not change and\n NULL is returned.\n\n @param iter The object iterator, should not be NULL.\n @param key The key, should be a UTF-8 string, null-terminator is not required.\n @param key_len The the length of `key`, in bytes.\n @return The value to which the specified key is mapped.\n    NULL if this object contains no mapping for the key or input is invalid.\n\n @warning This function takes a linear search time if the key is not nearby.\n */\nyyjson_api_inline yyjson_val *yyjson_obj_iter_getn(yyjson_obj_iter *iter,\n                                                   const char *key,\n                                                   size_t key_len);\n\n/**\n Macro for iterating over an object.\n It works like iterator, but with a more intuitive API.\n\n @b Example\n @code\n    size_t idx, max;\n    yyjson_val *key, *val;\n    yyjson_obj_foreach(obj, idx, max, key, val) {\n        your_func(key, val);\n    }\n @endcode\n */\n#define yyjson_obj_foreach(obj, idx, max, key, val) \\\n    for ((idx) = 0, \\\n        (max) = yyjson_obj_size(obj), \\\n        (key) = (obj) ? unsafe_yyjson_get_first(obj) : NULL, \\\n        (val) = (key) + 1; \\\n        (idx) < (max); \\\n        (idx)++, \\\n        (key) = unsafe_yyjson_get_next(val), \\\n        (val) = (key) + 1)\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Document API\n *============================================================================*/\n\n/** Returns the root value of this JSON document.\n    Returns NULL if `doc` is NULL. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_get_root(yyjson_mut_doc *doc);\n\n/** Sets the root value of this JSON document.\n    Pass NULL to clear root value of the document. */\nyyjson_api_inline void yyjson_mut_doc_set_root(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *root);\n\n/**\n Set the string pool size for a mutable document.\n This function does not allocate memory immediately, but uses the size when\n the next memory allocation is needed.\n\n If the caller knows the approximate bytes of strings that the document needs to\n store (e.g. copy string with `yyjson_mut_strcpy` function), setting a larger\n size can avoid multiple memory allocations and improve performance.\n\n @param doc The mutable document.\n @param len The desired string pool size in bytes (total string length).\n @return true if successful, false if size is 0 or overflow.\n */\nyyjson_api bool yyjson_mut_doc_set_str_pool_size(yyjson_mut_doc *doc,\n                                                 size_t len);\n\n/**\n Set the value pool size for a mutable document.\n This function does not allocate memory immediately, but uses the size when\n the next memory allocation is needed.\n\n If the caller knows the approximate number of values that the document needs to\n store (e.g. create new value with `yyjson_mut_xxx` functions), setting a larger\n size can avoid multiple memory allocations and improve performance.\n\n @param doc The mutable document.\n @param count The desired value pool size (number of `yyjson_mut_val`).\n @return true if successful, false if size is 0 or overflow.\n */\nyyjson_api bool yyjson_mut_doc_set_val_pool_size(yyjson_mut_doc *doc,\n                                                 size_t count);\n\n/** Release the JSON document and free the memory.\n    After calling this function, the `doc` and all values from the `doc` are no\n    longer available. This function will do nothing if the `doc` is NULL.  */\nyyjson_api void yyjson_mut_doc_free(yyjson_mut_doc *doc);\n\n/** Creates and returns a new mutable JSON document, returns NULL on error.\n    If allocator is NULL, the default allocator will be used. */\nyyjson_api yyjson_mut_doc *yyjson_mut_doc_new(const yyjson_alc *alc);\n\n/** Copies and returns a new mutable document from input, returns NULL on error.\n    This makes a `deep-copy` on the immutable document.\n    If allocator is NULL, the default allocator will be used.\n    @note `imut_doc` -> `mut_doc`. */\nyyjson_api yyjson_mut_doc *yyjson_doc_mut_copy(yyjson_doc *doc,\n                                               const yyjson_alc *alc);\n\n/** Copies and returns a new mutable document from input, returns NULL on error.\n    This makes a `deep-copy` on the mutable document.\n    If allocator is NULL, the default allocator will be used.\n    @note `mut_doc` -> `mut_doc`. */\nyyjson_api yyjson_mut_doc *yyjson_mut_doc_mut_copy(yyjson_mut_doc *doc,\n                                                   const yyjson_alc *alc);\n\n/** Copies and returns a new mutable value from input, returns NULL on error.\n    This makes a `deep-copy` on the immutable value.\n    The memory was managed by mutable document.\n    @note `imut_val` -> `mut_val`. */\nyyjson_api yyjson_mut_val *yyjson_val_mut_copy(yyjson_mut_doc *doc,\n                                               yyjson_val *val);\n\n/** Copies and returns a new mutable value from input, returns NULL on error.\n    This makes a `deep-copy` on the mutable value.\n    The memory was managed by mutable document.\n    @note `mut_val` -> `mut_val`.\n    @warning This function is recursive and may cause a stack overflow\n        if the object level is too deep. */\nyyjson_api yyjson_mut_val *yyjson_mut_val_mut_copy(yyjson_mut_doc *doc,\n                                                   yyjson_mut_val *val);\n\n/** Copies and returns a new immutable document from input,\n    returns NULL on error. This makes a `deep-copy` on the mutable document.\n    The returned document should be freed with `yyjson_doc_free()`.\n    @note `mut_doc` -> `imut_doc`.\n    @warning This function is recursive and may cause a stack overflow\n        if the object level is too deep. */\nyyjson_api yyjson_doc *yyjson_mut_doc_imut_copy(yyjson_mut_doc *doc,\n                                                const yyjson_alc *alc);\n\n/** Copies and returns a new immutable document from input,\n    returns NULL on error. This makes a `deep-copy` on the mutable value.\n    The returned document should be freed with `yyjson_doc_free()`.\n    @note `mut_val` -> `imut_doc`.\n    @warning This function is recursive and may cause a stack overflow\n        if the object level is too deep. */\nyyjson_api yyjson_doc *yyjson_mut_val_imut_copy(yyjson_mut_val *val,\n                                                const yyjson_alc *alc);\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Value Type API\n *============================================================================*/\n\n/** Returns whether the JSON value is raw.\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_mut_is_raw(yyjson_mut_val *val);\n\n/** Returns whether the JSON value is `null`.\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_mut_is_null(yyjson_mut_val *val);\n\n/** Returns whether the JSON value is `true`.\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_mut_is_true(yyjson_mut_val *val);\n\n/** Returns whether the JSON value is `false`.\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_mut_is_false(yyjson_mut_val *val);\n\n/** Returns whether the JSON value is bool (true/false).\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_mut_is_bool(yyjson_mut_val *val);\n\n/** Returns whether the JSON value is unsigned integer (uint64_t).\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_mut_is_uint(yyjson_mut_val *val);\n\n/** Returns whether the JSON value is signed integer (int64_t).\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_mut_is_sint(yyjson_mut_val *val);\n\n/** Returns whether the JSON value is integer (uint64_t/int64_t).\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_mut_is_int(yyjson_mut_val *val);\n\n/** Returns whether the JSON value is real number (double).\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_mut_is_real(yyjson_mut_val *val);\n\n/** Returns whether the JSON value is number (uint/sint/real).\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_mut_is_num(yyjson_mut_val *val);\n\n/** Returns whether the JSON value is string.\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_mut_is_str(yyjson_mut_val *val);\n\n/** Returns whether the JSON value is array.\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_mut_is_arr(yyjson_mut_val *val);\n\n/** Returns whether the JSON value is object.\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_mut_is_obj(yyjson_mut_val *val);\n\n/** Returns whether the JSON value is container (array/object).\n    Returns false if `val` is NULL. */\nyyjson_api_inline bool yyjson_mut_is_ctn(yyjson_mut_val *val);\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Value Content API\n *============================================================================*/\n\n/** Returns the JSON value's type.\n    Returns `YYJSON_TYPE_NONE` if `val` is NULL. */\nyyjson_api_inline yyjson_type yyjson_mut_get_type(yyjson_mut_val *val);\n\n/** Returns the JSON value's subtype.\n    Returns `YYJSON_SUBTYPE_NONE` if `val` is NULL. */\nyyjson_api_inline yyjson_subtype yyjson_mut_get_subtype(yyjson_mut_val *val);\n\n/** Returns the JSON value's tag.\n    Returns 0 if `val` is NULL. */\nyyjson_api_inline uint8_t yyjson_mut_get_tag(yyjson_mut_val *val);\n\n/** Returns the JSON value's type description.\n    The return value should be one of these strings: \"raw\", \"null\", \"string\",\n    \"array\", \"object\", \"true\", \"false\", \"uint\", \"sint\", \"real\", \"unknown\". */\nyyjson_api_inline const char *yyjson_mut_get_type_desc(yyjson_mut_val *val);\n\n/** Returns the content if the value is raw.\n    Returns NULL if `val` is NULL or type is not raw. */\nyyjson_api_inline const char *yyjson_mut_get_raw(yyjson_mut_val *val);\n\n/** Returns the content if the value is bool.\n    Returns NULL if `val` is NULL or type is not bool. */\nyyjson_api_inline bool yyjson_mut_get_bool(yyjson_mut_val *val);\n\n/** Returns the content and cast to uint64_t.\n    Returns 0 if `val` is NULL or type is not integer(sint/uint). */\nyyjson_api_inline uint64_t yyjson_mut_get_uint(yyjson_mut_val *val);\n\n/** Returns the content and cast to int64_t.\n    Returns 0 if `val` is NULL or type is not integer(sint/uint). */\nyyjson_api_inline int64_t yyjson_mut_get_sint(yyjson_mut_val *val);\n\n/** Returns the content and cast to int.\n    Returns 0 if `val` is NULL or type is not integer(sint/uint). */\nyyjson_api_inline int yyjson_mut_get_int(yyjson_mut_val *val);\n\n/** Returns the content if the value is real number.\n    Returns 0.0 if `val` is NULL or type is not real(double). */\nyyjson_api_inline double yyjson_mut_get_real(yyjson_mut_val *val);\n\n/** Returns the content and typecast to `double` if the value is number.\n    Returns 0.0 if `val` is NULL or type is not number(uint/sint/real). */\nyyjson_api_inline double yyjson_mut_get_num(yyjson_mut_val *val);\n\n/** Returns the content if the value is string.\n    Returns NULL if `val` is NULL or type is not string. */\nyyjson_api_inline const char *yyjson_mut_get_str(yyjson_mut_val *val);\n\n/** Returns the content length (string length, array size, object size.\n    Returns 0 if `val` is NULL or type is not string/array/object. */\nyyjson_api_inline size_t yyjson_mut_get_len(yyjson_mut_val *val);\n\n/** Returns whether the JSON value is equals to a string.\n    The `str` should be a null-terminated UTF-8 string.\n    Returns false if input is NULL or type is not string. */\nyyjson_api_inline bool yyjson_mut_equals_str(yyjson_mut_val *val,\n                                             const char *str);\n\n/** Returns whether the JSON value is equals to a string.\n    The `str` should be a UTF-8 string, null-terminator is not required.\n    Returns false if input is NULL or type is not string. */\nyyjson_api_inline bool yyjson_mut_equals_strn(yyjson_mut_val *val,\n                                              const char *str, size_t len);\n\n/** Returns whether two JSON values are equal (deep compare).\n    Returns false if input is NULL.\n    @note the result may be inaccurate if object has duplicate keys.\n    @warning This function is recursive and may cause a stack overflow\n        if the object level is too deep. */\nyyjson_api_inline bool yyjson_mut_equals(yyjson_mut_val *lhs,\n                                         yyjson_mut_val *rhs);\n\n/** Set the value to raw.\n    Returns false if input is NULL.\n    @warning This function should not be used on an existing object or array. */\nyyjson_api_inline bool yyjson_mut_set_raw(yyjson_mut_val *val,\n                                          const char *raw, size_t len);\n\n/** Set the value to null.\n    Returns false if input is NULL.\n    @warning This function should not be used on an existing object or array. */\nyyjson_api_inline bool yyjson_mut_set_null(yyjson_mut_val *val);\n\n/** Set the value to bool.\n    Returns false if input is NULL.\n    @warning This function should not be used on an existing object or array. */\nyyjson_api_inline bool yyjson_mut_set_bool(yyjson_mut_val *val, bool num);\n\n/** Set the value to uint.\n    Returns false if input is NULL.\n    @warning This function should not be used on an existing object or array. */\nyyjson_api_inline bool yyjson_mut_set_uint(yyjson_mut_val *val, uint64_t num);\n\n/** Set the value to sint.\n    Returns false if input is NULL.\n    @warning This function should not be used on an existing object or array. */\nyyjson_api_inline bool yyjson_mut_set_sint(yyjson_mut_val *val, int64_t num);\n\n/** Set the value to int.\n    Returns false if input is NULL.\n    @warning This function should not be used on an existing object or array. */\nyyjson_api_inline bool yyjson_mut_set_int(yyjson_mut_val *val, int num);\n\n/** Set the value to float.\n    Returns false if input is NULL.\n    @warning This function should not be used on an existing object or array. */\nyyjson_api_inline bool yyjson_mut_set_float(yyjson_mut_val *val, float num);\n\n/** Set the value to double.\n    Returns false if input is NULL.\n    @warning This function should not be used on an existing object or array. */\nyyjson_api_inline bool yyjson_mut_set_double(yyjson_mut_val *val, double num);\n\n/** Set the value to real.\n    Returns false if input is NULL.\n    @warning This function should not be used on an existing object or array. */\nyyjson_api_inline bool yyjson_mut_set_real(yyjson_mut_val *val, double num);\n\n/** Set the floating-point number's output format to fixed-point notation.\n    Returns false if input is NULL or `val` is not real type.\n    @see YYJSON_WRITE_FP_TO_FIXED flag.\n    @warning This will modify the `immutable` value, use with caution. */\nyyjson_api_inline bool yyjson_mut_set_fp_to_fixed(yyjson_mut_val *val,\n                                                  int prec);\n\n/** Set the floating-point number's output format to single-precision.\n    Returns false if input is NULL or `val` is not real type.\n    @see YYJSON_WRITE_FP_TO_FLOAT flag.\n    @warning This will modify the `immutable` value, use with caution. */\nyyjson_api_inline bool yyjson_mut_set_fp_to_float(yyjson_mut_val *val,\n                                                  bool flt);\n\n/** Set the value to string (null-terminated).\n    Returns false if input is NULL.\n    @warning This function should not be used on an existing object or array. */\nyyjson_api_inline bool yyjson_mut_set_str(yyjson_mut_val *val, const char *str);\n\n/** Set the value to string (with length).\n    Returns false if input is NULL.\n    @warning This function should not be used on an existing object or array. */\nyyjson_api_inline bool yyjson_mut_set_strn(yyjson_mut_val *val,\n                                           const char *str, size_t len);\n\n/** Marks this string as not needing to be escaped during JSON writing.\n    This can be used to avoid the overhead of escaping if the string contains\n    only characters that do not require escaping.\n    Returns false if input is NULL or `val` is not string.\n    @see YYJSON_SUBTYPE_NOESC subtype.\n    @warning This will modify the `immutable` value, use with caution. */\nyyjson_api_inline bool yyjson_mut_set_str_noesc(yyjson_mut_val *val,\n                                                bool noesc);\n\n/** Set the value to array.\n    Returns false if input is NULL.\n    @warning This function should not be used on an existing object or array. */\nyyjson_api_inline bool yyjson_mut_set_arr(yyjson_mut_val *val);\n\n/** Set the value to array.\n    Returns false if input is NULL.\n    @warning This function should not be used on an existing object or array. */\nyyjson_api_inline bool yyjson_mut_set_obj(yyjson_mut_val *val);\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Value Creation API\n *============================================================================*/\n\n/** Creates and returns a raw value, returns NULL on error.\n    The `str` should be a null-terminated UTF-8 string.\n\n    @warning The input string is not copied, you should keep this string\n        unmodified for the lifetime of this JSON document. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_raw(yyjson_mut_doc *doc,\n                                                 const char *str);\n\n/** Creates and returns a raw value, returns NULL on error.\n    The `str` should be a UTF-8 string, null-terminator is not required.\n\n    @warning The input string is not copied, you should keep this string\n        unmodified for the lifetime of this JSON document. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_rawn(yyjson_mut_doc *doc,\n                                                  const char *str,\n                                                  size_t len);\n\n/** Creates and returns a raw value, returns NULL on error.\n    The `str` should be a null-terminated UTF-8 string.\n    The input string is copied and held by the document. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_rawcpy(yyjson_mut_doc *doc,\n                                                    const char *str);\n\n/** Creates and returns a raw value, returns NULL on error.\n    The `str` should be a UTF-8 string, null-terminator is not required.\n    The input string is copied and held by the document. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_rawncpy(yyjson_mut_doc *doc,\n                                                     const char *str,\n                                                     size_t len);\n\n/** Creates and returns a null value, returns NULL on error. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_null(yyjson_mut_doc *doc);\n\n/** Creates and returns a true value, returns NULL on error. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_true(yyjson_mut_doc *doc);\n\n/** Creates and returns a false value, returns NULL on error. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_false(yyjson_mut_doc *doc);\n\n/** Creates and returns a bool value, returns NULL on error. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_bool(yyjson_mut_doc *doc,\n                                                  bool val);\n\n/** Creates and returns an unsigned integer value, returns NULL on error. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_uint(yyjson_mut_doc *doc,\n                                                  uint64_t num);\n\n/** Creates and returns a signed integer value, returns NULL on error. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_sint(yyjson_mut_doc *doc,\n                                                  int64_t num);\n\n/** Creates and returns a signed integer value, returns NULL on error. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_int(yyjson_mut_doc *doc,\n                                                 int64_t num);\n\n/** Creates and returns a float number value, returns NULL on error. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_float(yyjson_mut_doc *doc,\n                                                   float num);\n\n/** Creates and returns a double number value, returns NULL on error. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_double(yyjson_mut_doc *doc,\n                                                    double num);\n\n/** Creates and returns a real number value, returns NULL on error. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_real(yyjson_mut_doc *doc,\n                                                  double num);\n\n/** Creates and returns a string value, returns NULL on error.\n    The `str` should be a null-terminated UTF-8 string.\n    @warning The input string is not copied, you should keep this string\n        unmodified for the lifetime of this JSON document. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_str(yyjson_mut_doc *doc,\n                                                 const char *str);\n\n/** Creates and returns a string value, returns NULL on error.\n    The `str` should be a UTF-8 string, null-terminator is not required.\n    @warning The input string is not copied, you should keep this string\n        unmodified for the lifetime of this JSON document. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_strn(yyjson_mut_doc *doc,\n                                                  const char *str,\n                                                  size_t len);\n\n/** Creates and returns a string value, returns NULL on error.\n    The `str` should be a null-terminated UTF-8 string.\n    The input string is copied and held by the document. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_strcpy(yyjson_mut_doc *doc,\n                                                    const char *str);\n\n/** Creates and returns a string value, returns NULL on error.\n    The `str` should be a UTF-8 string, null-terminator is not required.\n    The input string is copied and held by the document. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_strncpy(yyjson_mut_doc *doc,\n                                                     const char *str,\n                                                     size_t len);\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Array API\n *============================================================================*/\n\n/** Returns the number of elements in this array.\n    Returns 0 if `arr` is NULL or type is not array. */\nyyjson_api_inline size_t yyjson_mut_arr_size(yyjson_mut_val *arr);\n\n/** Returns the element at the specified position in this array.\n    Returns NULL if array is NULL/empty or the index is out of bounds.\n    @warning This function takes a linear search time. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get(yyjson_mut_val *arr,\n                                                     size_t idx);\n\n/** Returns the first element of this array.\n    Returns NULL if `arr` is NULL/empty or type is not array. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get_first(yyjson_mut_val *arr);\n\n/** Returns the last element of this array.\n    Returns NULL if `arr` is NULL/empty or type is not array. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get_last(yyjson_mut_val *arr);\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Array Iterator API\n *============================================================================*/\n\n/**\n A mutable JSON array iterator.\n\n @warning You should not modify the array while iterating over it, but you can\n    use `yyjson_mut_arr_iter_remove()` to remove current value.\n\n @b Example\n @code\n    yyjson_mut_val *val;\n    yyjson_mut_arr_iter iter = yyjson_mut_arr_iter_with(arr);\n    while ((val = yyjson_mut_arr_iter_next(&iter))) {\n        your_func(val);\n        if (your_val_is_unused(val)) {\n            yyjson_mut_arr_iter_remove(&iter);\n        }\n    }\n @endcode\n */\ntypedef struct yyjson_mut_arr_iter {\n    size_t idx; /**< next value's index */\n    size_t max; /**< maximum index (arr.size) */\n    yyjson_mut_val *cur; /**< current value */\n    yyjson_mut_val *pre; /**< previous value */\n    yyjson_mut_val *arr; /**< the array being iterated */\n} yyjson_mut_arr_iter;\n\n/**\n Initialize an iterator for this array.\n\n @param arr The array to be iterated over.\n    If this parameter is NULL or not an array, `iter` will be set to empty.\n @param iter The iterator to be initialized.\n    If this parameter is NULL, the function will fail and return false.\n @return true if the `iter` has been successfully initialized.\n\n @note The iterator does not need to be destroyed.\n */\nyyjson_api_inline bool yyjson_mut_arr_iter_init(yyjson_mut_val *arr,\n    yyjson_mut_arr_iter *iter);\n\n/**\n Create an iterator with an array , same as `yyjson_mut_arr_iter_init()`.\n\n @param arr The array to be iterated over.\n    If this parameter is NULL or not an array, an empty iterator will returned.\n @return A new iterator for the array.\n\n @note The iterator does not need to be destroyed.\n */\nyyjson_api_inline yyjson_mut_arr_iter yyjson_mut_arr_iter_with(\n    yyjson_mut_val *arr);\n\n/**\n Returns whether the iteration has more elements.\n If `iter` is NULL, this function will return false.\n */\nyyjson_api_inline bool yyjson_mut_arr_iter_has_next(\n    yyjson_mut_arr_iter *iter);\n\n/**\n Returns the next element in the iteration, or NULL on end.\n If `iter` is NULL, this function will return NULL.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_iter_next(\n    yyjson_mut_arr_iter *iter);\n\n/**\n Removes and returns current element in the iteration.\n If `iter` is NULL, this function will return NULL.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_iter_remove(\n    yyjson_mut_arr_iter *iter);\n\n/**\n Macro for iterating over an array.\n It works like iterator, but with a more intuitive API.\n\n @warning You should not modify the array while iterating over it.\n\n @b Example\n @code\n    size_t idx, max;\n    yyjson_mut_val *val;\n    yyjson_mut_arr_foreach(arr, idx, max, val) {\n        your_func(idx, val);\n    }\n @endcode\n */\n#define yyjson_mut_arr_foreach(arr, idx, max, val) \\\n    for ((idx) = 0, \\\n        (max) = yyjson_mut_arr_size(arr), \\\n        (val) = yyjson_mut_arr_get_first(arr); \\\n        (idx) < (max); \\\n        (idx)++, \\\n        (val) = (val)->next)\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Array Creation API\n *============================================================================*/\n\n/**\n Creates and returns an empty mutable array.\n @param doc A mutable document, used for memory allocation only.\n @return The new array. NULL if input is NULL or memory allocation failed.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr(yyjson_mut_doc *doc);\n\n/**\n Creates and returns a new mutable array with the given boolean values.\n\n @param doc A mutable document, used for memory allocation only.\n    If this parameter is NULL, the function will fail and return NULL.\n @param vals A C array of boolean values.\n @param count The value count. If this value is 0, an empty array will return.\n @return The new array. NULL if input is invalid or memory allocation failed.\n\n @b Example\n @code\n    const bool vals[3] = { true, false, true };\n    yyjson_mut_val *arr = yyjson_mut_arr_with_bool(doc, vals, 3);\n @endcode\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_bool(\n    yyjson_mut_doc *doc, const bool *vals, size_t count);\n\n/**\n Creates and returns a new mutable array with the given sint numbers.\n\n @param doc A mutable document, used for memory allocation only.\n    If this parameter is NULL, the function will fail and return NULL.\n @param vals A C array of sint numbers.\n @param count The number count. If this value is 0, an empty array will return.\n @return The new array. NULL if input is invalid or memory allocation failed.\n\n @b Example\n @code\n    const int64_t vals[3] = { -1, 0, 1 };\n    yyjson_mut_val *arr = yyjson_mut_arr_with_sint64(doc, vals, 3);\n @endcode\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint(\n    yyjson_mut_doc *doc, const int64_t *vals, size_t count);\n\n/**\n Creates and returns a new mutable array with the given uint numbers.\n\n @param doc A mutable document, used for memory allocation only.\n    If this parameter is NULL, the function will fail and return NULL.\n @param vals A C array of uint numbers.\n @param count The number count. If this value is 0, an empty array will return.\n @return The new array. NULL if input is invalid or memory allocation failed.\n\n @b Example\n @code\n    const uint64_t vals[3] = { 0, 1, 0 };\n    yyjson_mut_val *arr = yyjson_mut_arr_with_uint(doc, vals, 3);\n @endcode\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint(\n    yyjson_mut_doc *doc, const uint64_t *vals, size_t count);\n\n/**\n Creates and returns a new mutable array with the given real numbers.\n\n @param doc A mutable document, used for memory allocation only.\n    If this parameter is NULL, the function will fail and return NULL.\n @param vals A C array of real numbers.\n @param count The number count. If this value is 0, an empty array will return.\n @return The new array. NULL if input is invalid or memory allocation failed.\n\n @b Example\n @code\n    const double vals[3] = { 0.1, 0.2, 0.3 };\n    yyjson_mut_val *arr = yyjson_mut_arr_with_real(doc, vals, 3);\n @endcode\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_real(\n    yyjson_mut_doc *doc, const double *vals, size_t count);\n\n/**\n Creates and returns a new mutable array with the given int8 numbers.\n\n @param doc A mutable document, used for memory allocation only.\n    If this parameter is NULL, the function will fail and return NULL.\n @param vals A C array of int8 numbers.\n @param count The number count. If this value is 0, an empty array will return.\n @return The new array. NULL if input is invalid or memory allocation failed.\n\n @b Example\n @code\n    const int8_t vals[3] = { -1, 0, 1 };\n    yyjson_mut_val *arr = yyjson_mut_arr_with_sint8(doc, vals, 3);\n @endcode\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint8(\n    yyjson_mut_doc *doc, const int8_t *vals, size_t count);\n\n/**\n Creates and returns a new mutable array with the given int16 numbers.\n\n @param doc A mutable document, used for memory allocation only.\n    If this parameter is NULL, the function will fail and return NULL.\n @param vals A C array of int16 numbers.\n @param count The number count. If this value is 0, an empty array will return.\n @return The new array. NULL if input is invalid or memory allocation failed.\n\n @b Example\n @code\n    const int16_t vals[3] = { -1, 0, 1 };\n    yyjson_mut_val *arr = yyjson_mut_arr_with_sint16(doc, vals, 3);\n @endcode\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint16(\n    yyjson_mut_doc *doc, const int16_t *vals, size_t count);\n\n/**\n Creates and returns a new mutable array with the given int32 numbers.\n\n @param doc A mutable document, used for memory allocation only.\n    If this parameter is NULL, the function will fail and return NULL.\n @param vals A C array of int32 numbers.\n @param count The number count. If this value is 0, an empty array will return.\n @return The new array. NULL if input is invalid or memory allocation failed.\n\n @b Example\n @code\n    const int32_t vals[3] = { -1, 0, 1 };\n    yyjson_mut_val *arr = yyjson_mut_arr_with_sint32(doc, vals, 3);\n @endcode\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint32(\n    yyjson_mut_doc *doc, const int32_t *vals, size_t count);\n\n/**\n Creates and returns a new mutable array with the given int64 numbers.\n\n @param doc A mutable document, used for memory allocation only.\n    If this parameter is NULL, the function will fail and return NULL.\n @param vals A C array of int64 numbers.\n @param count The number count. If this value is 0, an empty array will return.\n @return The new array. NULL if input is invalid or memory allocation failed.\n\n @b Example\n @code\n    const int64_t vals[3] = { -1, 0, 1 };\n    yyjson_mut_val *arr = yyjson_mut_arr_with_sint64(doc, vals, 3);\n @endcode\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint64(\n    yyjson_mut_doc *doc, const int64_t *vals, size_t count);\n\n/**\n Creates and returns a new mutable array with the given uint8 numbers.\n\n @param doc A mutable document, used for memory allocation only.\n    If this parameter is NULL, the function will fail and return NULL.\n @param vals A C array of uint8 numbers.\n @param count The number count. If this value is 0, an empty array will return.\n @return The new array. NULL if input is invalid or memory allocation failed.\n\n @b Example\n @code\n    const uint8_t vals[3] = { 0, 1, 0 };\n    yyjson_mut_val *arr = yyjson_mut_arr_with_uint8(doc, vals, 3);\n @endcode\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint8(\n    yyjson_mut_doc *doc, const uint8_t *vals, size_t count);\n\n/**\n Creates and returns a new mutable array with the given uint16 numbers.\n\n @param doc A mutable document, used for memory allocation only.\n    If this parameter is NULL, the function will fail and return NULL.\n @param vals A C array of uint16 numbers.\n @param count The number count. If this value is 0, an empty array will return.\n @return The new array. NULL if input is invalid or memory allocation failed.\n\n @b Example\n @code\n    const uint16_t vals[3] = { 0, 1, 0 };\n    yyjson_mut_val *arr = yyjson_mut_arr_with_uint16(doc, vals, 3);\n @endcode\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint16(\n    yyjson_mut_doc *doc, const uint16_t *vals, size_t count);\n\n/**\n Creates and returns a new mutable array with the given uint32 numbers.\n\n @param doc A mutable document, used for memory allocation only.\n    If this parameter is NULL, the function will fail and return NULL.\n @param vals A C array of uint32 numbers.\n @param count The number count. If this value is 0, an empty array will return.\n @return The new array. NULL if input is invalid or memory allocation failed.\n\n @b Example\n @code\n    const uint32_t vals[3] = { 0, 1, 0 };\n    yyjson_mut_val *arr = yyjson_mut_arr_with_uint32(doc, vals, 3);\n @endcode\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint32(\n    yyjson_mut_doc *doc, const uint32_t *vals, size_t count);\n\n/**\n Creates and returns a new mutable array with the given uint64 numbers.\n\n @param doc A mutable document, used for memory allocation only.\n    If this parameter is NULL, the function will fail and return NULL.\n @param vals A C array of uint64 numbers.\n @param count The number count. If this value is 0, an empty array will return.\n @return The new array. NULL if input is invalid or memory allocation failed.\n\n @b Example\n @code\n     const uint64_t vals[3] = { 0, 1, 0 };\n     yyjson_mut_val *arr = yyjson_mut_arr_with_uint64(doc, vals, 3);\n @endcode\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint64(\n    yyjson_mut_doc *doc, const uint64_t *vals, size_t count);\n\n/**\n Creates and returns a new mutable array with the given float numbers.\n\n @param doc A mutable document, used for memory allocation only.\n    If this parameter is NULL, the function will fail and return NULL.\n @param vals A C array of float numbers.\n @param count The number count. If this value is 0, an empty array will return.\n @return The new array. NULL if input is invalid or memory allocation failed.\n\n @b Example\n @code\n    const float vals[3] = { -1.0f, 0.0f, 1.0f };\n    yyjson_mut_val *arr = yyjson_mut_arr_with_float(doc, vals, 3);\n @endcode\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_float(\n    yyjson_mut_doc *doc, const float *vals, size_t count);\n\n/**\n Creates and returns a new mutable array with the given double numbers.\n\n @param doc A mutable document, used for memory allocation only.\n    If this parameter is NULL, the function will fail and return NULL.\n @param vals A C array of double numbers.\n @param count The number count. If this value is 0, an empty array will return.\n @return The new array. NULL if input is invalid or memory allocation failed.\n\n @b Example\n @code\n    const double vals[3] = { -1.0, 0.0, 1.0 };\n    yyjson_mut_val *arr = yyjson_mut_arr_with_double(doc, vals, 3);\n @endcode\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_double(\n    yyjson_mut_doc *doc, const double *vals, size_t count);\n\n/**\n Creates and returns a new mutable array with the given strings, these strings\n will not be copied.\n\n @param doc A mutable document, used for memory allocation only.\n    If this parameter is NULL, the function will fail and return NULL.\n @param vals A C array of UTF-8 null-terminator strings.\n    If this array contains NULL, the function will fail and return NULL.\n @param count The number of values in `vals`.\n    If this value is 0, an empty array will return.\n @return The new array. NULL if input is invalid or memory allocation failed.\n\n @warning The input strings are not copied, you should keep these strings\n    unmodified for the lifetime of this JSON document. If these strings will be\n    modified, you should use `yyjson_mut_arr_with_strcpy()` instead.\n\n @b Example\n @code\n    const char *vals[3] = { \"a\", \"b\", \"c\" };\n    yyjson_mut_val *arr = yyjson_mut_arr_with_str(doc, vals, 3);\n @endcode\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_str(\n    yyjson_mut_doc *doc, const char **vals, size_t count);\n\n/**\n Creates and returns a new mutable array with the given strings and string\n lengths, these strings will not be copied.\n\n @param doc A mutable document, used for memory allocation only.\n    If this parameter is NULL, the function will fail and return NULL.\n @param vals A C array of UTF-8 strings, null-terminator is not required.\n    If this array contains NULL, the function will fail and return NULL.\n @param lens A C array of string lengths, in bytes.\n @param count The number of strings in `vals`.\n    If this value is 0, an empty array will return.\n @return The new array. NULL if input is invalid or memory allocation failed.\n\n @warning The input strings are not copied, you should keep these strings\n    unmodified for the lifetime of this JSON document. If these strings will be\n    modified, you should use `yyjson_mut_arr_with_strncpy()` instead.\n\n @b Example\n @code\n    const char *vals[3] = { \"a\", \"bb\", \"c\" };\n    const size_t lens[3] = { 1, 2, 1 };\n    yyjson_mut_val *arr = yyjson_mut_arr_with_strn(doc, vals, lens, 3);\n @endcode\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strn(\n    yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count);\n\n/**\n Creates and returns a new mutable array with the given strings, these strings\n will be copied.\n\n @param doc A mutable document, used for memory allocation only.\n    If this parameter is NULL, the function will fail and return NULL.\n @param vals A C array of UTF-8 null-terminator strings.\n    If this array contains NULL, the function will fail and return NULL.\n @param count The number of values in `vals`.\n    If this value is 0, an empty array will return.\n @return The new array. NULL if input is invalid or memory allocation failed.\n\n @b Example\n @code\n    const char *vals[3] = { \"a\", \"b\", \"c\" };\n    yyjson_mut_val *arr = yyjson_mut_arr_with_strcpy(doc, vals, 3);\n @endcode\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strcpy(\n    yyjson_mut_doc *doc, const char **vals, size_t count);\n\n/**\n Creates and returns a new mutable array with the given strings and string\n lengths, these strings will be copied.\n\n @param doc A mutable document, used for memory allocation only.\n    If this parameter is NULL, the function will fail and return NULL.\n @param vals A C array of UTF-8 strings, null-terminator is not required.\n    If this array contains NULL, the function will fail and return NULL.\n @param lens A C array of string lengths, in bytes.\n @param count The number of strings in `vals`.\n    If this value is 0, an empty array will return.\n @return The new array. NULL if input is invalid or memory allocation failed.\n\n @b Example\n @code\n    const char *vals[3] = { \"a\", \"bb\", \"c\" };\n    const size_t lens[3] = { 1, 2, 1 };\n    yyjson_mut_val *arr = yyjson_mut_arr_with_strn(doc, vals, lens, 3);\n @endcode\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strncpy(\n    yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count);\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Array Modification API\n *============================================================================*/\n\n/**\n Inserts a value into an array at a given index.\n @param arr The array to which the value is to be inserted.\n    Returns false if it is NULL or not an array.\n @param val The value to be inserted. Returns false if it is NULL.\n @param idx The index to which to insert the new value.\n    Returns false if the index is out of range.\n @return Whether successful.\n @warning This function takes a linear search time.\n */\nyyjson_api_inline bool yyjson_mut_arr_insert(yyjson_mut_val *arr,\n                                             yyjson_mut_val *val, size_t idx);\n\n/**\n Inserts a value at the end of the array.\n @param arr The array to which the value is to be inserted.\n    Returns false if it is NULL or not an array.\n @param val The value to be inserted. Returns false if it is NULL.\n @return Whether successful.\n */\nyyjson_api_inline bool yyjson_mut_arr_append(yyjson_mut_val *arr,\n                                             yyjson_mut_val *val);\n\n/**\n Inserts a value at the head of the array.\n @param arr The array to which the value is to be inserted.\n    Returns false if it is NULL or not an array.\n @param val The value to be inserted. Returns false if it is NULL.\n @return    Whether successful.\n */\nyyjson_api_inline bool yyjson_mut_arr_prepend(yyjson_mut_val *arr,\n                                              yyjson_mut_val *val);\n\n/**\n Replaces a value at index and returns old value.\n @param arr The array to which the value is to be replaced.\n    Returns false if it is NULL or not an array.\n @param idx The index to which to replace the value.\n    Returns false if the index is out of range.\n @param val The new value to replace. Returns false if it is NULL.\n @return Old value, or NULL on error.\n @warning This function takes a linear search time.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_replace(yyjson_mut_val *arr,\n                                                         size_t idx,\n                                                         yyjson_mut_val *val);\n\n/**\n Removes and returns a value at index.\n @param arr The array from which the value is to be removed.\n    Returns false if it is NULL or not an array.\n @param idx The index from which to remove the value.\n    Returns false if the index is out of range.\n @return Old value, or NULL on error.\n @warning This function takes a linear search time.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove(yyjson_mut_val *arr,\n                                                        size_t idx);\n\n/**\n Removes and returns the first value in this array.\n @param arr The array from which the value is to be removed.\n    Returns false if it is NULL or not an array.\n @return The first value, or NULL on error.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove_first(\n    yyjson_mut_val *arr);\n\n/**\n Removes and returns the last value in this array.\n @param arr The array from which the value is to be removed.\n    Returns false if it is NULL or not an array.\n @return The last value, or NULL on error.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove_last(\n    yyjson_mut_val *arr);\n\n/**\n Removes all values within a specified range in the array.\n @param arr The array from which the value is to be removed.\n    Returns false if it is NULL or not an array.\n @param idx The start index of the range (0 is the first).\n @param len The number of items in the range (can be 0).\n @return Whether successful.\n @warning This function takes a linear search time.\n */\nyyjson_api_inline bool yyjson_mut_arr_remove_range(yyjson_mut_val *arr,\n                                                   size_t idx, size_t len);\n\n/**\n Removes all values in this array.\n @param arr The array from which all of the values are to be removed.\n    Returns false if it is NULL or not an array.\n @return Whether successful.\n */\nyyjson_api_inline bool yyjson_mut_arr_clear(yyjson_mut_val *arr);\n\n/**\n Rotates values in this array for the given number of times.\n For example: `[1,2,3,4,5]` rotate 2 is `[3,4,5,1,2]`.\n @param arr The array to be rotated.\n @param idx Index (or times) to rotate.\n @warning This function takes a linear search time.\n */\nyyjson_api_inline bool yyjson_mut_arr_rotate(yyjson_mut_val *arr,\n                                             size_t idx);\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Array Modification Convenience API\n *============================================================================*/\n\n/**\n Adds a value at the end of the array.\n @param arr The array to which the value is to be inserted.\n    Returns false if it is NULL or not an array.\n @param val The value to be inserted. Returns false if it is NULL.\n @return Whether successful.\n */\nyyjson_api_inline bool yyjson_mut_arr_add_val(yyjson_mut_val *arr,\n                                              yyjson_mut_val *val);\n\n/**\n Adds a `null` value at the end of the array.\n @param doc The `doc` is only used for memory allocation.\n @param arr The array to which the value is to be inserted.\n    Returns false if it is NULL or not an array.\n @return Whether successful.\n */\nyyjson_api_inline bool yyjson_mut_arr_add_null(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *arr);\n\n/**\n Adds a `true` value at the end of the array.\n @param doc The `doc` is only used for memory allocation.\n @param arr The array to which the value is to be inserted.\n    Returns false if it is NULL or not an array.\n @return Whether successful.\n */\nyyjson_api_inline bool yyjson_mut_arr_add_true(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *arr);\n\n/**\n Adds a `false` value at the end of the array.\n @param doc The `doc` is only used for memory allocation.\n @param arr The array to which the value is to be inserted.\n    Returns false if it is NULL or not an array.\n @return Whether successful.\n */\nyyjson_api_inline bool yyjson_mut_arr_add_false(yyjson_mut_doc *doc,\n                                                yyjson_mut_val *arr);\n\n/**\n Adds a bool value at the end of the array.\n @param doc The `doc` is only used for memory allocation.\n @param arr The array to which the value is to be inserted.\n    Returns false if it is NULL or not an array.\n @param val The bool value to be added.\n @return Whether successful.\n */\nyyjson_api_inline bool yyjson_mut_arr_add_bool(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *arr,\n                                               bool val);\n\n/**\n Adds an unsigned integer value at the end of the array.\n @param doc The `doc` is only used for memory allocation.\n @param arr The array to which the value is to be inserted.\n    Returns false if it is NULL or not an array.\n @param num The number to be added.\n @return Whether successful.\n */\nyyjson_api_inline bool yyjson_mut_arr_add_uint(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *arr,\n                                               uint64_t num);\n\n/**\n Adds a signed integer value at the end of the array.\n @param doc The `doc` is only used for memory allocation.\n @param arr The array to which the value is to be inserted.\n    Returns false if it is NULL or not an array.\n @param num The number to be added.\n @return Whether successful.\n */\nyyjson_api_inline bool yyjson_mut_arr_add_sint(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *arr,\n                                               int64_t num);\n\n/**\n Adds an integer value at the end of the array.\n @param doc The `doc` is only used for memory allocation.\n @param arr The array to which the value is to be inserted.\n    Returns false if it is NULL or not an array.\n @param num The number to be added.\n @return Whether successful.\n */\nyyjson_api_inline bool yyjson_mut_arr_add_int(yyjson_mut_doc *doc,\n                                              yyjson_mut_val *arr,\n                                              int64_t num);\n\n/**\n Adds a float value at the end of the array.\n @param doc The `doc` is only used for memory allocation.\n @param arr The array to which the value is to be inserted.\n    Returns false if it is NULL or not an array.\n @param num The number to be added.\n @return Whether successful.\n */\nyyjson_api_inline bool yyjson_mut_arr_add_float(yyjson_mut_doc *doc,\n                                                yyjson_mut_val *arr,\n                                                float num);\n\n/**\n Adds a double value at the end of the array.\n @param doc The `doc` is only used for memory allocation.\n @param arr The array to which the value is to be inserted.\n    Returns false if it is NULL or not an array.\n @param num The number to be added.\n @return Whether successful.\n */\nyyjson_api_inline bool yyjson_mut_arr_add_double(yyjson_mut_doc *doc,\n                                                 yyjson_mut_val *arr,\n                                                 double num);\n\n/**\n Adds a double value at the end of the array.\n @param doc The `doc` is only used for memory allocation.\n @param arr The array to which the value is to be inserted.\n    Returns false if it is NULL or not an array.\n @param num The number to be added.\n @return Whether successful.\n */\nyyjson_api_inline bool yyjson_mut_arr_add_real(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *arr,\n                                               double num);\n\n/**\n Adds a string value at the end of the array (no copy).\n @param doc The `doc` is only used for memory allocation.\n @param arr The array to which the value is to be inserted.\n    Returns false if it is NULL or not an array.\n @param str A null-terminated UTF-8 string.\n @return Whether successful.\n @warning The input string is not copied, you should keep this string unmodified\n    for the lifetime of this JSON document.\n */\nyyjson_api_inline bool yyjson_mut_arr_add_str(yyjson_mut_doc *doc,\n                                              yyjson_mut_val *arr,\n                                              const char *str);\n\n/**\n Adds a string value at the end of the array (no copy).\n @param doc The `doc` is only used for memory allocation.\n @param arr The array to which the value is to be inserted.\n    Returns false if it is NULL or not an array.\n @param str A UTF-8 string, null-terminator is not required.\n @param len The length of the string, in bytes.\n @return Whether successful.\n @warning The input string is not copied, you should keep this string unmodified\n    for the lifetime of this JSON document.\n */\nyyjson_api_inline bool yyjson_mut_arr_add_strn(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *arr,\n                                               const char *str,\n                                               size_t len);\n\n/**\n Adds a string value at the end of the array (copied).\n @param doc The `doc` is only used for memory allocation.\n @param arr The array to which the value is to be inserted.\n    Returns false if it is NULL or not an array.\n @param str A null-terminated UTF-8 string.\n @return Whether successful.\n */\nyyjson_api_inline bool yyjson_mut_arr_add_strcpy(yyjson_mut_doc *doc,\n                                                 yyjson_mut_val *arr,\n                                                 const char *str);\n\n/**\n Adds a string value at the end of the array (copied).\n @param doc The `doc` is only used for memory allocation.\n @param arr The array to which the value is to be inserted.\n    Returns false if it is NULL or not an array.\n @param str A UTF-8 string, null-terminator is not required.\n @param len The length of the string, in bytes.\n @return Whether successful.\n */\nyyjson_api_inline bool yyjson_mut_arr_add_strncpy(yyjson_mut_doc *doc,\n                                                  yyjson_mut_val *arr,\n                                                  const char *str,\n                                                  size_t len);\n\n/**\n Creates and adds a new array at the end of the array.\n @param doc The `doc` is only used for memory allocation.\n @param arr The array to which the value is to be inserted.\n    Returns false if it is NULL or not an array.\n @return The new array, or NULL on error.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_add_arr(yyjson_mut_doc *doc,\n                                                         yyjson_mut_val *arr);\n\n/**\n Creates and adds a new object at the end of the array.\n @param doc The `doc` is only used for memory allocation.\n @param arr The array to which the value is to be inserted.\n    Returns false if it is NULL or not an array.\n @return The new object, or NULL on error.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_add_obj(yyjson_mut_doc *doc,\n                                                         yyjson_mut_val *arr);\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Object API\n *============================================================================*/\n\n/** Returns the number of key-value pairs in this object.\n    Returns 0 if `obj` is NULL or type is not object. */\nyyjson_api_inline size_t yyjson_mut_obj_size(yyjson_mut_val *obj);\n\n/** Returns the value to which the specified key is mapped.\n    Returns NULL if this object contains no mapping for the key.\n    Returns NULL if `obj/key` is NULL, or type is not object.\n\n    The `key` should be a null-terminated UTF-8 string.\n\n    @warning This function takes a linear search time. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_get(yyjson_mut_val *obj,\n                                                     const char *key);\n\n/** Returns the value to which the specified key is mapped.\n    Returns NULL if this object contains no mapping for the key.\n    Returns NULL if `obj/key` is NULL, or type is not object.\n\n    The `key` should be a UTF-8 string, null-terminator is not required.\n    The `key_len` should be the length of the key, in bytes.\n\n    @warning This function takes a linear search time. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_getn(yyjson_mut_val *obj,\n                                                      const char *key,\n                                                      size_t key_len);\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Object Iterator API\n *============================================================================*/\n\n/**\n A mutable JSON object iterator.\n\n @warning You should not modify the object while iterating over it, but you can\n    use `yyjson_mut_obj_iter_remove()` to remove current value.\n\n @b Example\n @code\n    yyjson_mut_val *key, *val;\n    yyjson_mut_obj_iter iter = yyjson_mut_obj_iter_with(obj);\n    while ((key = yyjson_mut_obj_iter_next(&iter))) {\n        val = yyjson_mut_obj_iter_get_val(key);\n        your_func(key, val);\n        if (your_val_is_unused(key, val)) {\n            yyjson_mut_obj_iter_remove(&iter);\n        }\n    }\n @endcode\n\n If the ordering of the keys is known at compile-time, you can use this method\n to speed up value lookups:\n @code\n    // {\"k1\":1, \"k2\": 3, \"k3\": 3}\n    yyjson_mut_val *key, *val;\n    yyjson_mut_obj_iter iter = yyjson_mut_obj_iter_with(obj);\n    yyjson_mut_val *v1 = yyjson_mut_obj_iter_get(&iter, \"k1\");\n    yyjson_mut_val *v3 = yyjson_mut_obj_iter_get(&iter, \"k3\");\n @endcode\n @see `yyjson_mut_obj_iter_get()` and `yyjson_mut_obj_iter_getn()`\n */\ntypedef struct yyjson_mut_obj_iter {\n    size_t idx; /**< next key's index */\n    size_t max; /**< maximum key index (obj.size) */\n    yyjson_mut_val *cur; /**< current key */\n    yyjson_mut_val *pre; /**< previous key */\n    yyjson_mut_val *obj; /**< the object being iterated */\n} yyjson_mut_obj_iter;\n\n/**\n Initialize an iterator for this object.\n\n @param obj The object to be iterated over.\n    If this parameter is NULL or not an array, `iter` will be set to empty.\n @param iter The iterator to be initialized.\n    If this parameter is NULL, the function will fail and return false.\n @return true if the `iter` has been successfully initialized.\n\n @note The iterator does not need to be destroyed.\n */\nyyjson_api_inline bool yyjson_mut_obj_iter_init(yyjson_mut_val *obj,\n    yyjson_mut_obj_iter *iter);\n\n/**\n Create an iterator with an object, same as `yyjson_obj_iter_init()`.\n\n @param obj The object to be iterated over.\n    If this parameter is NULL or not an object, an empty iterator will returned.\n @return A new iterator for the object.\n\n @note The iterator does not need to be destroyed.\n */\nyyjson_api_inline yyjson_mut_obj_iter yyjson_mut_obj_iter_with(\n    yyjson_mut_val *obj);\n\n/**\n Returns whether the iteration has more elements.\n If `iter` is NULL, this function will return false.\n */\nyyjson_api_inline bool yyjson_mut_obj_iter_has_next(\n    yyjson_mut_obj_iter *iter);\n\n/**\n Returns the next key in the iteration, or NULL on end.\n If `iter` is NULL, this function will return NULL.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_next(\n    yyjson_mut_obj_iter *iter);\n\n/**\n Returns the value for key inside the iteration.\n If `iter` is NULL, this function will return NULL.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_get_val(\n    yyjson_mut_val *key);\n\n/**\n Removes current key-value pair in the iteration, returns the removed value.\n If `iter` is NULL, this function will return NULL.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_remove(\n    yyjson_mut_obj_iter *iter);\n\n/**\n Iterates to a specified key and returns the value.\n\n This function does the same thing as `yyjson_mut_obj_get()`, but is much faster\n if the ordering of the keys is known at compile-time and you are using the same\n order to look up the values. If the key exists in this object, then the\n iterator will stop at the next key, otherwise the iterator will not change and\n NULL is returned.\n\n @param iter The object iterator, should not be NULL.\n @param key The key, should be a UTF-8 string with null-terminator.\n @return The value to which the specified key is mapped.\n    NULL if this object contains no mapping for the key or input is invalid.\n\n @warning This function takes a linear search time if the key is not nearby.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_get(\n    yyjson_mut_obj_iter *iter, const char *key);\n\n/**\n Iterates to a specified key and returns the value.\n\n This function does the same thing as `yyjson_mut_obj_getn()` but is much faster\n if the ordering of the keys is known at compile-time and you are using the same\n order to look up the values. If the key exists in this object, then the\n iterator will stop at the next key, otherwise the iterator will not change and\n NULL is returned.\n\n @param iter The object iterator, should not be NULL.\n @param key The key, should be a UTF-8 string, null-terminator is not required.\n @param key_len The the length of `key`, in bytes.\n @return The value to which the specified key is mapped.\n    NULL if this object contains no mapping for the key or input is invalid.\n\n @warning This function takes a linear search time if the key is not nearby.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_getn(\n    yyjson_mut_obj_iter *iter, const char *key, size_t key_len);\n\n/**\n Macro for iterating over an object.\n It works like iterator, but with a more intuitive API.\n\n @warning You should not modify the object while iterating over it.\n\n @b Example\n @code\n    size_t idx, max;\n    yyjson_mut_val *key, *val;\n    yyjson_mut_obj_foreach(obj, idx, max, key, val) {\n        your_func(key, val);\n    }\n @endcode\n */\n#define yyjson_mut_obj_foreach(obj, idx, max, key, val) \\\n    for ((idx) = 0, \\\n        (max) = yyjson_mut_obj_size(obj), \\\n        (key) = (max) ? ((yyjson_mut_val *)(obj)->uni.ptr)->next->next : NULL, \\\n        (val) = (key) ? (key)->next : NULL; \\\n        (idx) < (max); \\\n        (idx)++, \\\n        (key) = (val)->next, \\\n        (val) = (key)->next)\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Object Creation API\n *============================================================================*/\n\n/** Creates and returns a mutable object, returns NULL on error. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj(yyjson_mut_doc *doc);\n\n/**\n Creates and returns a mutable object with keys and values, returns NULL on\n error. The keys and values are not copied. The strings should be a\n null-terminated UTF-8 string.\n\n @warning The input string is not copied, you should keep this string\n    unmodified for the lifetime of this JSON document.\n\n @b Example\n @code\n    const char *keys[2] = { \"id\", \"name\" };\n    const char *vals[2] = { \"01\", \"Harry\" };\n    yyjson_mut_val *obj = yyjson_mut_obj_with_str(doc, keys, vals, 2);\n @endcode\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_with_str(yyjson_mut_doc *doc,\n                                                          const char **keys,\n                                                          const char **vals,\n                                                          size_t count);\n\n/**\n Creates and returns a mutable object with key-value pairs and pair count,\n returns NULL on error. The keys and values are not copied. The strings should\n be a null-terminated UTF-8 string.\n\n @warning The input string is not copied, you should keep this string\n    unmodified for the lifetime of this JSON document.\n\n @b Example\n @code\n    const char *kv_pairs[4] = { \"id\", \"01\", \"name\", \"Harry\" };\n    yyjson_mut_val *obj = yyjson_mut_obj_with_kv(doc, kv_pairs, 2);\n @endcode\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_with_kv(yyjson_mut_doc *doc,\n                                                         const char **kv_pairs,\n                                                         size_t pair_count);\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Object Modification API\n *============================================================================*/\n\n/**\n Adds a key-value pair at the end of the object.\n This function allows duplicated key in one object.\n @param obj The object to which the new key-value pair is to be added.\n @param key The key, should be a string which is created by `yyjson_mut_str()`,\n    `yyjson_mut_strn()`, `yyjson_mut_strcpy()` or `yyjson_mut_strncpy()`.\n @param val The value to add to the object.\n @return Whether successful.\n */\nyyjson_api_inline bool yyjson_mut_obj_add(yyjson_mut_val *obj,\n                                          yyjson_mut_val *key,\n                                          yyjson_mut_val *val);\n/**\n Sets a key-value pair at the end of the object.\n This function may remove all key-value pairs for the given key before add.\n @param obj The object to which the new key-value pair is to be added.\n @param key The key, should be a string which is created by `yyjson_mut_str()`,\n    `yyjson_mut_strn()`, `yyjson_mut_strcpy()` or `yyjson_mut_strncpy()`.\n @param val The value to add to the object. If this value is null, the behavior\n    is same as `yyjson_mut_obj_remove()`.\n @return Whether successful.\n */\nyyjson_api_inline bool yyjson_mut_obj_put(yyjson_mut_val *obj,\n                                          yyjson_mut_val *key,\n                                          yyjson_mut_val *val);\n\n/**\n Inserts a key-value pair to the object at the given position.\n This function allows duplicated key in one object.\n @param obj The object to which the new key-value pair is to be added.\n @param key The key, should be a string which is created by `yyjson_mut_str()`,\n    `yyjson_mut_strn()`, `yyjson_mut_strcpy()` or `yyjson_mut_strncpy()`.\n @param val The value to add to the object.\n @param idx The index to which to insert the new pair.\n @return Whether successful.\n */\nyyjson_api_inline bool yyjson_mut_obj_insert(yyjson_mut_val *obj,\n                                             yyjson_mut_val *key,\n                                             yyjson_mut_val *val,\n                                             size_t idx);\n\n/**\n Removes all key-value pair from the object with given key.\n @param obj The object from which the key-value pair is to be removed.\n @param key The key, should be a string value.\n @return The first matched value, or NULL if no matched value.\n @warning This function takes a linear search time.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove(yyjson_mut_val *obj,\n                                                        yyjson_mut_val *key);\n\n/**\n Removes all key-value pair from the object with given key.\n @param obj The object from which the key-value pair is to be removed.\n @param key The key, should be a UTF-8 string with null-terminator.\n @return The first matched value, or NULL if no matched value.\n @warning This function takes a linear search time.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_key(\n    yyjson_mut_val *obj, const char *key);\n\n/**\n Removes all key-value pair from the object with given key.\n @param obj The object from which the key-value pair is to be removed.\n @param key The key, should be a UTF-8 string, null-terminator is not required.\n @param key_len The length of the key.\n @return The first matched value, or NULL if no matched value.\n @warning This function takes a linear search time.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_keyn(\n    yyjson_mut_val *obj, const char *key, size_t key_len);\n\n/**\n Removes all key-value pairs in this object.\n @param obj The object from which all of the values are to be removed.\n @return Whether successful.\n */\nyyjson_api_inline bool yyjson_mut_obj_clear(yyjson_mut_val *obj);\n\n/**\n Replaces value from the object with given key.\n If the key is not exist, or the value is NULL, it will fail.\n @param obj The object to which the value is to be replaced.\n @param key The key, should be a string value.\n @param val The value to replace into the object.\n @return Whether successful.\n @warning This function takes a linear search time.\n */\nyyjson_api_inline bool yyjson_mut_obj_replace(yyjson_mut_val *obj,\n                                              yyjson_mut_val *key,\n                                              yyjson_mut_val *val);\n\n/**\n Rotates key-value pairs in the object for the given number of times.\n For example: `{\"a\":1,\"b\":2,\"c\":3,\"d\":4}` rotate 1 is\n `{\"b\":2,\"c\":3,\"d\":4,\"a\":1}`.\n @param obj The object to be rotated.\n @param idx Index (or times) to rotate.\n @return Whether successful.\n @warning This function takes a linear search time.\n */\nyyjson_api_inline bool yyjson_mut_obj_rotate(yyjson_mut_val *obj,\n                                             size_t idx);\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Object Modification Convenience API\n *============================================================================*/\n\n/** Adds a `null` value at the end of the object.\n    The `key` should be a null-terminated UTF-8 string.\n    This function allows duplicated key in one object.\n\n    @warning The key string is not copied, you should keep the string\n        unmodified for the lifetime of this JSON document. */\nyyjson_api_inline bool yyjson_mut_obj_add_null(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *obj,\n                                               const char *key);\n\n/** Adds a `true` value at the end of the object.\n    The `key` should be a null-terminated UTF-8 string.\n    This function allows duplicated key in one object.\n\n    @warning The key string is not copied, you should keep the string\n        unmodified for the lifetime of this JSON document. */\nyyjson_api_inline bool yyjson_mut_obj_add_true(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *obj,\n                                               const char *key);\n\n/** Adds a `false` value at the end of the object.\n    The `key` should be a null-terminated UTF-8 string.\n    This function allows duplicated key in one object.\n\n    @warning The key string is not copied, you should keep the string\n        unmodified for the lifetime of this JSON document. */\nyyjson_api_inline bool yyjson_mut_obj_add_false(yyjson_mut_doc *doc,\n                                                yyjson_mut_val *obj,\n                                                const char *key);\n\n/** Adds a bool value at the end of the object.\n    The `key` should be a null-terminated UTF-8 string.\n    This function allows duplicated key in one object.\n\n    @warning The key string is not copied, you should keep the string\n        unmodified for the lifetime of this JSON document. */\nyyjson_api_inline bool yyjson_mut_obj_add_bool(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *obj,\n                                               const char *key, bool val);\n\n/** Adds an unsigned integer value at the end of the object.\n    The `key` should be a null-terminated UTF-8 string.\n    This function allows duplicated key in one object.\n\n    @warning The key string is not copied, you should keep the string\n        unmodified for the lifetime of this JSON document. */\nyyjson_api_inline bool yyjson_mut_obj_add_uint(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *obj,\n                                               const char *key, uint64_t val);\n\n/** Adds a signed integer value at the end of the object.\n    The `key` should be a null-terminated UTF-8 string.\n    This function allows duplicated key in one object.\n\n    @warning The key string is not copied, you should keep the string\n        unmodified for the lifetime of this JSON document. */\nyyjson_api_inline bool yyjson_mut_obj_add_sint(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *obj,\n                                               const char *key, int64_t val);\n\n/** Adds an int value at the end of the object.\n    The `key` should be a null-terminated UTF-8 string.\n    This function allows duplicated key in one object.\n\n    @warning The key string is not copied, you should keep the string\n        unmodified for the lifetime of this JSON document. */\nyyjson_api_inline bool yyjson_mut_obj_add_int(yyjson_mut_doc *doc,\n                                              yyjson_mut_val *obj,\n                                              const char *key, int64_t val);\n\n/** Adds a float value at the end of the object.\n    The `key` should be a null-terminated UTF-8 string.\n    This function allows duplicated key in one object.\n\n    @warning The key string is not copied, you should keep the string\n        unmodified for the lifetime of this JSON document. */\nyyjson_api_inline bool yyjson_mut_obj_add_float(yyjson_mut_doc *doc,\n                                                yyjson_mut_val *obj,\n                                                const char *key, float val);\n\n/** Adds a double value at the end of the object.\n    The `key` should be a null-terminated UTF-8 string.\n    This function allows duplicated key in one object.\n\n    @warning The key string is not copied, you should keep the string\n        unmodified for the lifetime of this JSON document. */\nyyjson_api_inline bool yyjson_mut_obj_add_double(yyjson_mut_doc *doc,\n                                                 yyjson_mut_val *obj,\n                                                 const char *key, double val);\n\n/** Adds a real value at the end of the object.\n    The `key` should be a null-terminated UTF-8 string.\n    This function allows duplicated key in one object.\n\n    @warning The key string is not copied, you should keep the string\n        unmodified for the lifetime of this JSON document. */\nyyjson_api_inline bool yyjson_mut_obj_add_real(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *obj,\n                                               const char *key, double val);\n\n/** Adds a string value at the end of the object.\n    The `key` and `val` should be null-terminated UTF-8 strings.\n    This function allows duplicated key in one object.\n\n    @warning The key/value strings are not copied, you should keep these strings\n        unmodified for the lifetime of this JSON document. */\nyyjson_api_inline bool yyjson_mut_obj_add_str(yyjson_mut_doc *doc,\n                                              yyjson_mut_val *obj,\n                                              const char *key, const char *val);\n\n/** Adds a string value at the end of the object.\n    The `key` should be a null-terminated UTF-8 string.\n    The `val` should be a UTF-8 string, null-terminator is not required.\n    The `len` should be the length of the `val`, in bytes.\n    This function allows duplicated key in one object.\n\n    @warning The key/value strings are not copied, you should keep these strings\n        unmodified for the lifetime of this JSON document. */\nyyjson_api_inline bool yyjson_mut_obj_add_strn(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *obj,\n                                               const char *key,\n                                               const char *val, size_t len);\n\n/** Adds a string value at the end of the object.\n    The `key` and `val` should be null-terminated UTF-8 strings.\n    The value string is copied.\n    This function allows duplicated key in one object.\n\n    @warning The key string is not copied, you should keep the string\n        unmodified for the lifetime of this JSON document. */\nyyjson_api_inline bool yyjson_mut_obj_add_strcpy(yyjson_mut_doc *doc,\n                                                 yyjson_mut_val *obj,\n                                                 const char *key,\n                                                 const char *val);\n\n/** Adds a string value at the end of the object.\n    The `key` should be a null-terminated UTF-8 string.\n    The `val` should be a UTF-8 string, null-terminator is not required.\n    The `len` should be the length of the `val`, in bytes.\n    This function allows duplicated key in one object.\n\n    @warning The key strings are not copied, you should keep these strings\n        unmodified for the lifetime of this JSON document. */\nyyjson_api_inline bool yyjson_mut_obj_add_strncpy(yyjson_mut_doc *doc,\n                                                  yyjson_mut_val *obj,\n                                                  const char *key,\n                                                  const char *val, size_t len);\n\n/**\n Creates and adds a new array to the target object.\n The `key` should be a null-terminated UTF-8 string.\n This function allows duplicated key in one object.\n\n @warning The key string is not copied, you should keep these strings\n          unmodified for the lifetime of this JSON document.\n @return The new array, or NULL on error.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_add_arr(yyjson_mut_doc *doc,\n                                                         yyjson_mut_val *obj,\n                                                         const char *key);\n\n/**\n Creates and adds a new object to the target object.\n The `key` should be a null-terminated UTF-8 string.\n This function allows duplicated key in one object.\n\n @warning The key string is not copied, you should keep these strings\n          unmodified for the lifetime of this JSON document.\n @return The new object, or NULL on error.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_add_obj(yyjson_mut_doc *doc,\n                                                         yyjson_mut_val *obj,\n                                                         const char *key);\n\n/** Adds a JSON value at the end of the object.\n    The `key` should be a null-terminated UTF-8 string.\n    This function allows duplicated key in one object.\n\n    @warning The key string is not copied, you should keep the string\n        unmodified for the lifetime of this JSON document. */\nyyjson_api_inline bool yyjson_mut_obj_add_val(yyjson_mut_doc *doc,\n                                              yyjson_mut_val *obj,\n                                              const char *key,\n                                              yyjson_mut_val *val);\n\n/** Removes all key-value pairs for the given key.\n    Returns the first value to which the specified key is mapped or NULL if this\n    object contains no mapping for the key.\n    The `key` should be a null-terminated UTF-8 string.\n\n    @warning This function takes a linear search time. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_str(\n    yyjson_mut_val *obj, const char *key);\n\n/** Removes all key-value pairs for the given key.\n    Returns the first value to which the specified key is mapped or NULL if this\n    object contains no mapping for the key.\n    The `key` should be a UTF-8 string, null-terminator is not required.\n    The `len` should be the length of the key, in bytes.\n\n    @warning This function takes a linear search time. */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_strn(\n    yyjson_mut_val *obj, const char *key, size_t len);\n\n/** Replaces all matching keys with the new key.\n    Returns true if at least one key was renamed.\n    The `key` and `new_key` should be a null-terminated UTF-8 string.\n    The `new_key` is copied and held by doc.\n\n    @warning This function takes a linear search time.\n    If `new_key` already exists, it will cause duplicate keys.\n */\nyyjson_api_inline bool yyjson_mut_obj_rename_key(yyjson_mut_doc *doc,\n                                                 yyjson_mut_val *obj,\n                                                 const char *key,\n                                                 const char *new_key);\n\n/** Replaces all matching keys with the new key.\n    Returns true if at least one key was renamed.\n    The `key` and `new_key` should be a UTF-8 string,\n    null-terminator is not required. The `new_key` is copied and held by doc.\n\n    @warning This function takes a linear search time.\n    If `new_key` already exists, it will cause duplicate keys.\n */\nyyjson_api_inline bool yyjson_mut_obj_rename_keyn(yyjson_mut_doc *doc,\n                                                  yyjson_mut_val *obj,\n                                                  const char *key,\n                                                  size_t len,\n                                                  const char *new_key,\n                                                  size_t new_len);\n\n\n\n#if !defined(YYJSON_DISABLE_UTILS) || !YYJSON_DISABLE_UTILS\n\n/*==============================================================================\n * MARK: - JSON Pointer API (RFC 6901)\n * https://tools.ietf.org/html/rfc6901\n *============================================================================*/\n\n/** JSON Pointer error code. */\ntypedef uint32_t yyjson_ptr_code;\n\n/** No JSON pointer error. */\nstatic const yyjson_ptr_code YYJSON_PTR_ERR_NONE = 0;\n\n/** Invalid input parameter, such as NULL input. */\nstatic const yyjson_ptr_code YYJSON_PTR_ERR_PARAMETER = 1;\n\n/** JSON pointer syntax error, such as invalid escape, token no prefix. */\nstatic const yyjson_ptr_code YYJSON_PTR_ERR_SYNTAX = 2;\n\n/** JSON pointer resolve failed, such as index out of range, key not found. */\nstatic const yyjson_ptr_code YYJSON_PTR_ERR_RESOLVE = 3;\n\n/** Document's root is NULL, but it is required for the function call. */\nstatic const yyjson_ptr_code YYJSON_PTR_ERR_NULL_ROOT = 4;\n\n/** Cannot set root as the target is not a document. */\nstatic const yyjson_ptr_code YYJSON_PTR_ERR_SET_ROOT = 5;\n\n/** The memory allocation failed and a new value could not be created. */\nstatic const yyjson_ptr_code YYJSON_PTR_ERR_MEMORY_ALLOCATION = 6;\n\n/** Error information for JSON pointer. */\ntypedef struct yyjson_ptr_err {\n    /** Error code, see `yyjson_ptr_code` for all possible values. */\n    yyjson_ptr_code code;\n    /** Error message, constant, no need to free (NULL if no error). */\n    const char *msg;\n    /** Error byte position for input JSON pointer (0 if no error). */\n    size_t pos;\n} yyjson_ptr_err;\n\n/**\n A context for JSON pointer operation.\n\n This struct stores the context of JSON Pointer operation result. The struct\n can be used with three helper functions: `ctx_append()`, `ctx_replace()`, and\n `ctx_remove()`, which perform the corresponding operations on the container\n without re-parsing the JSON Pointer.\n\n For example:\n @code\n    // doc before: {\"a\":[0,1,null]}\n    // ptr: \"/a/2\"\n    val = yyjson_mut_doc_ptr_getx(doc, ptr, strlen(ptr), &ctx, &err);\n    if (yyjson_is_null(val)) {\n        yyjson_ptr_ctx_remove(&ctx);\n    }\n    // doc after: {\"a\":[0,1]}\n @endcode\n */\ntypedef struct yyjson_ptr_ctx {\n    /**\n     The container (parent) of the target value. It can be either an array or\n     an object. If the target location has no value, but all its parent\n     containers exist, and the target location can be used to insert a new\n     value, then `ctn` is the parent container of the target location.\n     Otherwise, `ctn` is NULL.\n     */\n    yyjson_mut_val *ctn;\n    /**\n     The previous sibling of the target value. It can be either a value in an\n     array or a key in an object. As the container is a `circular linked list`\n     of elements, `pre` is the previous node of the target value. If the\n     operation is `add` or `set`, then `pre` is the previous node of the new\n     value, not the original target value. If the target value does not exist,\n     `pre` is NULL.\n     */\n    yyjson_mut_val *pre;\n    /**\n     The removed value if the operation is `set`, `replace` or `remove`. It can\n     be used to restore the original state of the document if needed.\n     */\n    yyjson_mut_val *old;\n} yyjson_ptr_ctx;\n\n/**\n Get value by a JSON Pointer.\n @param doc The JSON document to be queried.\n @param ptr The JSON pointer string (UTF-8 with null-terminator).\n @return The value referenced by the JSON pointer.\n    NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved.\n */\nyyjson_api_inline yyjson_val *yyjson_doc_ptr_get(yyjson_doc *doc,\n                                                 const char *ptr);\n\n/**\n Get value by a JSON Pointer.\n @param doc The JSON document to be queried.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @return The value referenced by the JSON pointer.\n    NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved.\n */\nyyjson_api_inline yyjson_val *yyjson_doc_ptr_getn(yyjson_doc *doc,\n                                                  const char *ptr, size_t len);\n\n/**\n Get value by a JSON Pointer.\n @param doc The JSON document to be queried.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @param err A pointer to store the error information, or NULL if not needed.\n @return The value referenced by the JSON pointer.\n    NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved.\n */\nyyjson_api_inline yyjson_val *yyjson_doc_ptr_getx(yyjson_doc *doc,\n                                                  const char *ptr, size_t len,\n                                                  yyjson_ptr_err *err);\n\n/**\n Get value by a JSON Pointer.\n @param val The JSON value to be queried.\n @param ptr The JSON pointer string (UTF-8 with null-terminator).\n @return The value referenced by the JSON pointer.\n    NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved.\n */\nyyjson_api_inline yyjson_val *yyjson_ptr_get(yyjson_val *val,\n                                             const char *ptr);\n\n/**\n Get value by a JSON Pointer.\n @param val The JSON value to be queried.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @return The value referenced by the JSON pointer.\n    NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved.\n */\nyyjson_api_inline yyjson_val *yyjson_ptr_getn(yyjson_val *val,\n                                              const char *ptr, size_t len);\n\n/**\n Get value by a JSON Pointer.\n @param val The JSON value to be queried.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @param err A pointer to store the error information, or NULL if not needed.\n @return The value referenced by the JSON pointer.\n    NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved.\n */\nyyjson_api_inline yyjson_val *yyjson_ptr_getx(yyjson_val *val,\n                                              const char *ptr, size_t len,\n                                              yyjson_ptr_err *err);\n\n/**\n Get value by a JSON Pointer.\n @param doc The JSON document to be queried.\n @param ptr The JSON pointer string (UTF-8 with null-terminator).\n @return The value referenced by the JSON pointer.\n    NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_get(yyjson_mut_doc *doc,\n                                                         const char *ptr);\n\n/**\n Get value by a JSON Pointer.\n @param doc The JSON document to be queried.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @return The value referenced by the JSON pointer.\n    NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_getn(yyjson_mut_doc *doc,\n                                                          const char *ptr,\n                                                          size_t len);\n\n/**\n Get value by a JSON Pointer.\n @param doc The JSON document to be queried.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @param ctx A pointer to store the result context, or NULL if not needed.\n @param err A pointer to store the error information, or NULL if not needed.\n @return The value referenced by the JSON pointer.\n    NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_getx(yyjson_mut_doc *doc,\n                                                          const char *ptr,\n                                                          size_t len,\n                                                          yyjson_ptr_ctx *ctx,\n                                                          yyjson_ptr_err *err);\n\n/**\n Get value by a JSON Pointer.\n @param val The JSON value to be queried.\n @param ptr The JSON pointer string (UTF-8 with null-terminator).\n @return The value referenced by the JSON pointer.\n    NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_get(yyjson_mut_val *val,\n                                                     const char *ptr);\n\n/**\n Get value by a JSON Pointer.\n @param val The JSON value to be queried.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @return The value referenced by the JSON pointer.\n    NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_getn(yyjson_mut_val *val,\n                                                      const char *ptr,\n                                                      size_t len);\n\n/**\n Get value by a JSON Pointer.\n @param val The JSON value to be queried.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @param ctx A pointer to store the result context, or NULL if not needed.\n @param err A pointer to store the error information, or NULL if not needed.\n @return The value referenced by the JSON pointer.\n    NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_getx(yyjson_mut_val *val,\n                                                      const char *ptr,\n                                                      size_t len,\n                                                      yyjson_ptr_ctx *ctx,\n                                                      yyjson_ptr_err *err);\n\n/**\n Add (insert) value by a JSON pointer.\n @param doc The target JSON document.\n @param ptr The JSON pointer string (UTF-8 with null-terminator).\n @param new_val The value to be added.\n @return true if JSON pointer is valid and new value is added, false otherwise.\n @note The parent nodes will be created if they do not exist.\n */\nyyjson_api_inline bool yyjson_mut_doc_ptr_add(yyjson_mut_doc *doc,\n                                              const char *ptr,\n                                              yyjson_mut_val *new_val);\n\n/**\n Add (insert) value by a JSON pointer.\n @param doc The target JSON document.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @param new_val The value to be added.\n @return true if JSON pointer is valid and new value is added, false otherwise.\n @note The parent nodes will be created if they do not exist.\n */\nyyjson_api_inline bool yyjson_mut_doc_ptr_addn(yyjson_mut_doc *doc,\n                                               const char *ptr, size_t len,\n                                               yyjson_mut_val *new_val);\n\n/**\n Add (insert) value by a JSON pointer.\n @param doc The target JSON document.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @param new_val The value to be added.\n @param create_parent Whether to create parent nodes if not exist.\n @param ctx A pointer to store the result context, or NULL if not needed.\n @param err A pointer to store the error information, or NULL if not needed.\n @return true if JSON pointer is valid and new value is added, false otherwise.\n */\nyyjson_api_inline bool yyjson_mut_doc_ptr_addx(yyjson_mut_doc *doc,\n                                               const char *ptr, size_t len,\n                                               yyjson_mut_val *new_val,\n                                               bool create_parent,\n                                               yyjson_ptr_ctx *ctx,\n                                               yyjson_ptr_err *err);\n\n/**\n Add (insert) value by a JSON pointer.\n @param val The target JSON value.\n @param ptr The JSON pointer string (UTF-8 with null-terminator).\n @param doc Only used to create new values when needed.\n @param new_val The value to be added.\n @return true if JSON pointer is valid and new value is added, false otherwise.\n @note The parent nodes will be created if they do not exist.\n */\nyyjson_api_inline bool yyjson_mut_ptr_add(yyjson_mut_val *val,\n                                          const char *ptr,\n                                          yyjson_mut_val *new_val,\n                                          yyjson_mut_doc *doc);\n\n/**\n Add (insert) value by a JSON pointer.\n @param val The target JSON value.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @param doc Only used to create new values when needed.\n @param new_val The value to be added.\n @return true if JSON pointer is valid and new value is added, false otherwise.\n @note The parent nodes will be created if they do not exist.\n */\nyyjson_api_inline bool yyjson_mut_ptr_addn(yyjson_mut_val *val,\n                                           const char *ptr, size_t len,\n                                           yyjson_mut_val *new_val,\n                                           yyjson_mut_doc *doc);\n\n/**\n Add (insert) value by a JSON pointer.\n @param val The target JSON value.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @param doc Only used to create new values when needed.\n @param new_val The value to be added.\n @param create_parent Whether to create parent nodes if not exist.\n @param ctx A pointer to store the result context, or NULL if not needed.\n @param err A pointer to store the error information, or NULL if not needed.\n @return true if JSON pointer is valid and new value is added, false otherwise.\n */\nyyjson_api_inline bool yyjson_mut_ptr_addx(yyjson_mut_val *val,\n                                           const char *ptr, size_t len,\n                                           yyjson_mut_val *new_val,\n                                           yyjson_mut_doc *doc,\n                                           bool create_parent,\n                                           yyjson_ptr_ctx *ctx,\n                                           yyjson_ptr_err *err);\n\n/**\n Set value by a JSON pointer.\n @param doc The target JSON document.\n @param ptr The JSON pointer string (UTF-8 with null-terminator).\n @param new_val The value to be set, pass NULL to remove.\n @return true if JSON pointer is valid and new value is set, false otherwise.\n @note The parent nodes will be created if they do not exist.\n    If the target value already exists, it will be replaced by the new value.\n */\nyyjson_api_inline bool yyjson_mut_doc_ptr_set(yyjson_mut_doc *doc,\n                                              const char *ptr,\n                                              yyjson_mut_val *new_val);\n\n/**\n Set value by a JSON pointer.\n @param doc The target JSON document.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @param new_val The value to be set, pass NULL to remove.\n @return true if JSON pointer is valid and new value is set, false otherwise.\n @note The parent nodes will be created if they do not exist.\n    If the target value already exists, it will be replaced by the new value.\n */\nyyjson_api_inline bool yyjson_mut_doc_ptr_setn(yyjson_mut_doc *doc,\n                                               const char *ptr, size_t len,\n                                               yyjson_mut_val *new_val);\n\n/**\n Set value by a JSON pointer.\n @param doc The target JSON document.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @param new_val The value to be set, pass NULL to remove.\n @param create_parent Whether to create parent nodes if not exist.\n @param ctx A pointer to store the result context, or NULL if not needed.\n @param err A pointer to store the error information, or NULL if not needed.\n @return true if JSON pointer is valid and new value is set, false otherwise.\n @note If the target value already exists, it will be replaced by the new value.\n */\nyyjson_api_inline bool yyjson_mut_doc_ptr_setx(yyjson_mut_doc *doc,\n                                               const char *ptr, size_t len,\n                                               yyjson_mut_val *new_val,\n                                               bool create_parent,\n                                               yyjson_ptr_ctx *ctx,\n                                               yyjson_ptr_err *err);\n\n/**\n Set value by a JSON pointer.\n @param val The target JSON value.\n @param ptr The JSON pointer string (UTF-8 with null-terminator).\n @param new_val The value to be set, pass NULL to remove.\n @param doc Only used to create new values when needed.\n @return true if JSON pointer is valid and new value is set, false otherwise.\n @note The parent nodes will be created if they do not exist.\n    If the target value already exists, it will be replaced by the new value.\n */\nyyjson_api_inline bool yyjson_mut_ptr_set(yyjson_mut_val *val,\n                                          const char *ptr,\n                                          yyjson_mut_val *new_val,\n                                          yyjson_mut_doc *doc);\n\n/**\n Set value by a JSON pointer.\n @param val The target JSON value.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @param new_val The value to be set, pass NULL to remove.\n @param doc Only used to create new values when needed.\n @return true if JSON pointer is valid and new value is set, false otherwise.\n @note The parent nodes will be created if they do not exist.\n    If the target value already exists, it will be replaced by the new value.\n */\nyyjson_api_inline bool yyjson_mut_ptr_setn(yyjson_mut_val *val,\n                                           const char *ptr, size_t len,\n                                           yyjson_mut_val *new_val,\n                                           yyjson_mut_doc *doc);\n\n/**\n Set value by a JSON pointer.\n @param val The target JSON value.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @param new_val The value to be set, pass NULL to remove.\n @param doc Only used to create new values when needed.\n @param create_parent Whether to create parent nodes if not exist.\n @param ctx A pointer to store the result context, or NULL if not needed.\n @param err A pointer to store the error information, or NULL if not needed.\n @return true if JSON pointer is valid and new value is set, false otherwise.\n @note If the target value already exists, it will be replaced by the new value.\n */\nyyjson_api_inline bool yyjson_mut_ptr_setx(yyjson_mut_val *val,\n                                           const char *ptr, size_t len,\n                                           yyjson_mut_val *new_val,\n                                           yyjson_mut_doc *doc,\n                                           bool create_parent,\n                                           yyjson_ptr_ctx *ctx,\n                                           yyjson_ptr_err *err);\n\n/**\n Replace value by a JSON pointer.\n @param doc The target JSON document.\n @param ptr The JSON pointer string (UTF-8 with null-terminator).\n @param new_val The new value to replace the old one.\n @return The old value that was replaced, or NULL if not found.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replace(\n    yyjson_mut_doc *doc, const char *ptr, yyjson_mut_val *new_val);\n\n/**\n Replace value by a JSON pointer.\n @param doc The target JSON document.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @param new_val The new value to replace the old one.\n @return The old value that was replaced, or NULL if not found.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replacen(\n    yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val);\n\n/**\n Replace value by a JSON pointer.\n @param doc The target JSON document.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @param new_val The new value to replace the old one.\n @param ctx A pointer to store the result context, or NULL if not needed.\n @param err A pointer to store the error information, or NULL if not needed.\n @return The old value that was replaced, or NULL if not found.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replacex(\n    yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val,\n    yyjson_ptr_ctx *ctx, yyjson_ptr_err *err);\n\n/**\n Replace value by a JSON pointer.\n @param val The target JSON value.\n @param ptr The JSON pointer string (UTF-8 with null-terminator).\n @param new_val The new value to replace the old one.\n @return The old value that was replaced, or NULL if not found.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replace(\n    yyjson_mut_val *val, const char *ptr, yyjson_mut_val *new_val);\n\n/**\n Replace value by a JSON pointer.\n @param val The target JSON value.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @param new_val The new value to replace the old one.\n @return The old value that was replaced, or NULL if not found.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replacen(\n    yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val);\n\n/**\n Replace value by a JSON pointer.\n @param val The target JSON value.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @param new_val The new value to replace the old one.\n @param ctx A pointer to store the result context, or NULL if not needed.\n @param err A pointer to store the error information, or NULL if not needed.\n @return The old value that was replaced, or NULL if not found.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replacex(\n    yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val,\n    yyjson_ptr_ctx *ctx, yyjson_ptr_err *err);\n\n/**\n Remove value by a JSON pointer.\n @param doc The target JSON document.\n @param ptr The JSON pointer string (UTF-8 with null-terminator).\n @return The removed value, or NULL on error.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_remove(\n    yyjson_mut_doc *doc, const char *ptr);\n\n/**\n Remove value by a JSON pointer.\n @param doc The target JSON document.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @return The removed value, or NULL on error.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_removen(\n    yyjson_mut_doc *doc, const char *ptr, size_t len);\n\n/**\n Remove value by a JSON pointer.\n @param doc The target JSON document.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @param ctx A pointer to store the result context, or NULL if not needed.\n @param err A pointer to store the error information, or NULL if not needed.\n @return The removed value, or NULL on error.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_removex(\n    yyjson_mut_doc *doc, const char *ptr, size_t len,\n    yyjson_ptr_ctx *ctx, yyjson_ptr_err *err);\n\n/**\n Remove value by a JSON pointer.\n @param val The target JSON value.\n @param ptr The JSON pointer string (UTF-8 with null-terminator).\n @return The removed value, or NULL on error.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_remove(yyjson_mut_val *val,\n                                                        const char *ptr);\n\n/**\n Remove value by a JSON pointer.\n @param val The target JSON value.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @return The removed value, or NULL on error.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_removen(yyjson_mut_val *val,\n                                                         const char *ptr,\n                                                         size_t len);\n\n/**\n Remove value by a JSON pointer.\n @param val The target JSON value.\n @param ptr The JSON pointer string (UTF-8, null-terminator is not required).\n @param len The length of `ptr` in bytes.\n @param ctx A pointer to store the result context, or NULL if not needed.\n @param err A pointer to store the error information, or NULL if not needed.\n @return The removed value, or NULL on error.\n */\nyyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_removex(yyjson_mut_val *val,\n                                                         const char *ptr,\n                                                         size_t len,\n                                                         yyjson_ptr_ctx *ctx,\n                                                         yyjson_ptr_err *err);\n\n/**\n Append value by JSON pointer context.\n @param ctx The context from the `yyjson_mut_ptr_xxx()` calls.\n @param key New key if `ctx->ctn` is object, or NULL if `ctx->ctn` is array.\n @param val New value to be added.\n @return true on success or false on fail.\n */\nyyjson_api_inline bool yyjson_ptr_ctx_append(yyjson_ptr_ctx *ctx,\n                                             yyjson_mut_val *key,\n                                             yyjson_mut_val *val);\n\n/**\n Replace value by JSON pointer context.\n @param ctx The context from the `yyjson_mut_ptr_xxx()` calls.\n @param val New value to be replaced.\n @return true on success or false on fail.\n @note If success, the old value will be returned via `ctx->old`.\n */\nyyjson_api_inline bool yyjson_ptr_ctx_replace(yyjson_ptr_ctx *ctx,\n                                              yyjson_mut_val *val);\n\n/**\n Remove value by JSON pointer context.\n @param ctx The context from the `yyjson_mut_ptr_xxx()` calls.\n @return true on success or false on fail.\n @note If success, the old value will be returned via `ctx->old`.\n */\nyyjson_api_inline bool yyjson_ptr_ctx_remove(yyjson_ptr_ctx *ctx);\n\n\n\n/*==============================================================================\n * MARK: - JSON Patch API (RFC 6902)\n * https://tools.ietf.org/html/rfc6902\n *============================================================================*/\n\n/** Result code for JSON patch. */\ntypedef uint32_t yyjson_patch_code;\n\n/** Success, no error. */\nstatic const yyjson_patch_code YYJSON_PATCH_SUCCESS = 0;\n\n/** Invalid parameter, such as NULL input or non-array patch. */\nstatic const yyjson_patch_code YYJSON_PATCH_ERROR_INVALID_PARAMETER = 1;\n\n/** Memory allocation failure occurs. */\nstatic const yyjson_patch_code YYJSON_PATCH_ERROR_MEMORY_ALLOCATION = 2;\n\n/** JSON patch operation is not object type. */\nstatic const yyjson_patch_code YYJSON_PATCH_ERROR_INVALID_OPERATION = 3;\n\n/** JSON patch operation is missing a required key. */\nstatic const yyjson_patch_code YYJSON_PATCH_ERROR_MISSING_KEY = 4;\n\n/** JSON patch operation member is invalid. */\nstatic const yyjson_patch_code YYJSON_PATCH_ERROR_INVALID_MEMBER = 5;\n\n/** JSON patch operation `test` not equal. */\nstatic const yyjson_patch_code YYJSON_PATCH_ERROR_EQUAL = 6;\n\n/** JSON patch operation failed on JSON pointer. */\nstatic const yyjson_patch_code YYJSON_PATCH_ERROR_POINTER = 7;\n\n/** Error information for JSON patch. */\ntypedef struct yyjson_patch_err {\n    /** Error code, see `yyjson_patch_code` for all possible values. */\n    yyjson_patch_code code;\n    /** Index of the error operation (0 if no error). */\n    size_t idx;\n    /** Error message, constant, no need to free (NULL if no error). */\n    const char *msg;\n    /** JSON pointer error if `code == YYJSON_PATCH_ERROR_POINTER`. */\n    yyjson_ptr_err ptr;\n} yyjson_patch_err;\n\n/**\n Creates and returns a patched JSON value (RFC 6902).\n The memory of the returned value is allocated by the `doc`.\n The `err` is used to receive error information, pass NULL if not needed.\n Returns NULL if the patch could not be applied.\n */\nyyjson_api yyjson_mut_val *yyjson_patch(yyjson_mut_doc *doc,\n                                        yyjson_val *orig,\n                                        yyjson_val *patch,\n                                        yyjson_patch_err *err);\n\n/**\n Creates and returns a patched JSON value (RFC 6902).\n The memory of the returned value is allocated by the `doc`.\n The `err` is used to receive error information, pass NULL if not needed.\n Returns NULL if the patch could not be applied.\n */\nyyjson_api yyjson_mut_val *yyjson_mut_patch(yyjson_mut_doc *doc,\n                                            yyjson_mut_val *orig,\n                                            yyjson_mut_val *patch,\n                                            yyjson_patch_err *err);\n\n\n\n/*==============================================================================\n * MARK: - JSON Merge-Patch API (RFC 7386)\n * https://tools.ietf.org/html/rfc7386\n *============================================================================*/\n\n/**\n Creates and returns a merge-patched JSON value (RFC 7386).\n The memory of the returned value is allocated by the `doc`.\n Returns NULL if the patch could not be applied.\n\n @warning This function is recursive and may cause a stack overflow if the\n    object level is too deep.\n */\nyyjson_api yyjson_mut_val *yyjson_merge_patch(yyjson_mut_doc *doc,\n                                              yyjson_val *orig,\n                                              yyjson_val *patch);\n\n/**\n Creates and returns a merge-patched JSON value (RFC 7386).\n The memory of the returned value is allocated by the `doc`.\n Returns NULL if the patch could not be applied.\n\n @warning This function is recursive and may cause a stack overflow if the\n    object level is too deep.\n */\nyyjson_api yyjson_mut_val *yyjson_mut_merge_patch(yyjson_mut_doc *doc,\n                                                  yyjson_mut_val *orig,\n                                                  yyjson_mut_val *patch);\n\n#endif /* YYJSON_DISABLE_UTILS */\n\n\n\n/*==============================================================================\n * MARK: - JSON Structure (Implementation)\n *============================================================================*/\n\n/** Payload of a JSON value (8 bytes). */\ntypedef union yyjson_val_uni {\n    uint64_t    u64;\n    int64_t     i64;\n    double      f64;\n    const char *str;\n    void       *ptr;\n    size_t      ofs;\n} yyjson_val_uni;\n\n/**\n Immutable JSON value, 16 bytes.\n */\nstruct yyjson_val {\n    uint64_t tag; /**< type, subtype and length */\n    yyjson_val_uni uni; /**< payload */\n};\n\nstruct yyjson_doc {\n    /** Root value of the document (nonnull). */\n    yyjson_val *root;\n    /** Allocator used by document (nonnull). */\n    yyjson_alc alc;\n    /** The total number of bytes read when parsing JSON (nonzero). */\n    size_t dat_read;\n    /** The total number of value read when parsing JSON (nonzero). */\n    size_t val_read;\n    /** The string pool used by JSON values (nullable). */\n    char *str_pool;\n};\n\n\n\n/*==============================================================================\n * MARK: - Unsafe JSON Value API (Implementation)\n *============================================================================*/\n\n/*\n Whether the string does not need to be escaped for serialization.\n This function is used to optimize the writing speed of small constant strings.\n This function works only if the compiler can evaluate it at compile time.\n\n Clang supports it since v8.0,\n    earlier versions do not support constant_p(strlen) and return false.\n GCC supports it since at least v4.4,\n    earlier versions may compile it as run-time instructions.\n ICC supports it since at least v16,\n    earlier versions are uncertain.\n\n @param str The C string.\n @param len The returnd value from strlen(str).\n */\nyyjson_api_inline bool unsafe_yyjson_is_str_noesc(const char *str, size_t len) {\n#if YYJSON_HAS_CONSTANT_P && \\\n    (!YYJSON_IS_REAL_GCC || yyjson_gcc_available(4, 4, 0))\n    if (yyjson_constant_p(len) && len <= 32) {\n        /*\n         Same as the following loop:\n\n         for (size_t i = 0; i < len; i++) {\n             char c = str[i];\n             if (c < ' ' || c > '~' || c == '\"' || c == '\\\\') return false;\n         }\n\n         GCC evaluates it at compile time only if the string length is within 17\n         and -O3 (which turns on the -fpeel-loops flag) is used.\n         So the loop is unrolled for GCC.\n         */\n#       define yyjson_repeat32_incr(x) \\\n            x(0)  x(1)  x(2)  x(3)  x(4)  x(5)  x(6)  x(7)  \\\n            x(8)  x(9)  x(10) x(11) x(12) x(13) x(14) x(15) \\\n            x(16) x(17) x(18) x(19) x(20) x(21) x(22) x(23) \\\n            x(24) x(25) x(26) x(27) x(28) x(29) x(30) x(31)\n#       define yyjson_check_char_noesc(i) \\\n            if (i < len) { \\\n                char c = str[i]; \\\n                if (c < ' ' || c > '~' || c == '\"' || c == '\\\\') return false; }\n        yyjson_repeat32_incr(yyjson_check_char_noesc)\n#       undef yyjson_repeat32_incr\n#       undef yyjson_check_char_noesc\n        return true;\n    }\n#else\n    (void)str;\n    (void)len;\n#endif\n    return false;\n}\n\nyyjson_api_inline double unsafe_yyjson_u64_to_f64(uint64_t num) {\n#if YYJSON_U64_TO_F64_NO_IMPL\n        uint64_t msb = ((uint64_t)1) << 63;\n        if ((num & msb) == 0) {\n            return (double)(int64_t)num;\n        } else {\n            return ((double)(int64_t)((num >> 1) | (num & 1))) * (double)2.0;\n        }\n#else\n        return (double)num;\n#endif\n}\n\nyyjson_api_inline yyjson_type unsafe_yyjson_get_type(void *val) {\n    uint8_t tag = (uint8_t)((yyjson_val *)val)->tag;\n    return (yyjson_type)(tag & YYJSON_TYPE_MASK);\n}\n\nyyjson_api_inline yyjson_subtype unsafe_yyjson_get_subtype(void *val) {\n    uint8_t tag = (uint8_t)((yyjson_val *)val)->tag;\n    return (yyjson_subtype)(tag & YYJSON_SUBTYPE_MASK);\n}\n\nyyjson_api_inline uint8_t unsafe_yyjson_get_tag(void *val) {\n    uint8_t tag = (uint8_t)((yyjson_val *)val)->tag;\n    return (uint8_t)(tag & YYJSON_TAG_MASK);\n}\n\nyyjson_api_inline bool unsafe_yyjson_is_raw(void *val) {\n    return unsafe_yyjson_get_type(val) == YYJSON_TYPE_RAW;\n}\n\nyyjson_api_inline bool unsafe_yyjson_is_null(void *val) {\n    return unsafe_yyjson_get_type(val) == YYJSON_TYPE_NULL;\n}\n\nyyjson_api_inline bool unsafe_yyjson_is_bool(void *val) {\n    return unsafe_yyjson_get_type(val) == YYJSON_TYPE_BOOL;\n}\n\nyyjson_api_inline bool unsafe_yyjson_is_num(void *val) {\n    return unsafe_yyjson_get_type(val) == YYJSON_TYPE_NUM;\n}\n\nyyjson_api_inline bool unsafe_yyjson_is_str(void *val) {\n    return unsafe_yyjson_get_type(val) == YYJSON_TYPE_STR;\n}\n\nyyjson_api_inline bool unsafe_yyjson_is_arr(void *val) {\n    return unsafe_yyjson_get_type(val) == YYJSON_TYPE_ARR;\n}\n\nyyjson_api_inline bool unsafe_yyjson_is_obj(void *val) {\n    return unsafe_yyjson_get_type(val) == YYJSON_TYPE_OBJ;\n}\n\nyyjson_api_inline bool unsafe_yyjson_is_ctn(void *val) {\n    uint8_t mask = YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ;\n    return (unsafe_yyjson_get_tag(val) & mask) == mask;\n}\n\nyyjson_api_inline bool unsafe_yyjson_is_uint(void *val) {\n    const uint8_t patt = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT;\n    return unsafe_yyjson_get_tag(val) == patt;\n}\n\nyyjson_api_inline bool unsafe_yyjson_is_sint(void *val) {\n    const uint8_t patt = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT;\n    return unsafe_yyjson_get_tag(val) == patt;\n}\n\nyyjson_api_inline bool unsafe_yyjson_is_int(void *val) {\n    const uint8_t mask = YYJSON_TAG_MASK & (~YYJSON_SUBTYPE_SINT);\n    const uint8_t patt = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT;\n    return (unsafe_yyjson_get_tag(val) & mask) == patt;\n}\n\nyyjson_api_inline bool unsafe_yyjson_is_real(void *val) {\n    const uint8_t patt = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL;\n    return unsafe_yyjson_get_tag(val) == patt;\n}\n\nyyjson_api_inline bool unsafe_yyjson_is_true(void *val) {\n    const uint8_t patt = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE;\n    return unsafe_yyjson_get_tag(val) == patt;\n}\n\nyyjson_api_inline bool unsafe_yyjson_is_false(void *val) {\n    const uint8_t patt = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE;\n    return unsafe_yyjson_get_tag(val) == patt;\n}\n\nyyjson_api_inline bool unsafe_yyjson_arr_is_flat(yyjson_val *val) {\n    size_t ofs = val->uni.ofs;\n    size_t len = (size_t)(val->tag >> YYJSON_TAG_BIT);\n    return len * sizeof(yyjson_val) + sizeof(yyjson_val) == ofs;\n}\n\nyyjson_api_inline const char *unsafe_yyjson_get_raw(void *val) {\n    return ((yyjson_val *)val)->uni.str;\n}\n\nyyjson_api_inline bool unsafe_yyjson_get_bool(void *val) {\n    uint8_t tag = unsafe_yyjson_get_tag(val);\n    return (bool)((tag & YYJSON_SUBTYPE_MASK) >> YYJSON_TYPE_BIT);\n}\n\nyyjson_api_inline uint64_t unsafe_yyjson_get_uint(void *val) {\n    return ((yyjson_val *)val)->uni.u64;\n}\n\nyyjson_api_inline int64_t unsafe_yyjson_get_sint(void *val) {\n    return ((yyjson_val *)val)->uni.i64;\n}\n\nyyjson_api_inline int unsafe_yyjson_get_int(void *val) {\n    return (int)((yyjson_val *)val)->uni.i64;\n}\n\nyyjson_api_inline double unsafe_yyjson_get_real(void *val) {\n    return ((yyjson_val *)val)->uni.f64;\n}\n\nyyjson_api_inline double unsafe_yyjson_get_num(void *val) {\n    uint8_t tag = unsafe_yyjson_get_tag(val);\n    if (tag == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL)) {\n        return ((yyjson_val *)val)->uni.f64;\n    } else if (tag == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT)) {\n        return (double)((yyjson_val *)val)->uni.i64;\n    } else if (tag == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT)) {\n        return unsafe_yyjson_u64_to_f64(((yyjson_val *)val)->uni.u64);\n    }\n    return 0.0;\n}\n\nyyjson_api_inline const char *unsafe_yyjson_get_str(void *val) {\n    return ((yyjson_val *)val)->uni.str;\n}\n\nyyjson_api_inline size_t unsafe_yyjson_get_len(void *val) {\n    return (size_t)(((yyjson_val *)val)->tag >> YYJSON_TAG_BIT);\n}\n\nyyjson_api_inline yyjson_val *unsafe_yyjson_get_first(yyjson_val *ctn) {\n    return ctn + 1;\n}\n\nyyjson_api_inline yyjson_val *unsafe_yyjson_get_next(yyjson_val *val) {\n    bool is_ctn = unsafe_yyjson_is_ctn(val);\n    size_t ctn_ofs = val->uni.ofs;\n    size_t ofs = (is_ctn ? ctn_ofs : sizeof(yyjson_val));\n    return (yyjson_val *)(void *)((uint8_t *)val + ofs);\n}\n\nyyjson_api_inline bool unsafe_yyjson_equals_strn(void *val, const char *str,\n                                                 size_t len) {\n    return unsafe_yyjson_get_len(val) == len &&\n           memcmp(((yyjson_val *)val)->uni.str, str, len) == 0;\n}\n\nyyjson_api_inline bool unsafe_yyjson_equals_str(void *val, const char *str) {\n    return unsafe_yyjson_equals_strn(val, str, strlen(str));\n}\n\nyyjson_api_inline void unsafe_yyjson_set_type(void *val, yyjson_type type,\n                                              yyjson_subtype subtype) {\n    uint8_t tag = (type | subtype);\n    uint64_t new_tag = ((yyjson_val *)val)->tag;\n    new_tag = (new_tag & (~(uint64_t)YYJSON_TAG_MASK)) | (uint64_t)tag;\n    ((yyjson_val *)val)->tag = new_tag;\n}\n\nyyjson_api_inline void unsafe_yyjson_set_len(void *val, size_t len) {\n    uint64_t tag = ((yyjson_val *)val)->tag & YYJSON_TAG_MASK;\n    tag |= (uint64_t)len << YYJSON_TAG_BIT;\n    ((yyjson_val *)val)->tag = tag;\n}\n\nyyjson_api_inline void unsafe_yyjson_set_tag(void *val, yyjson_type type,\n                                             yyjson_subtype subtype,\n                                             size_t len) {\n    uint64_t tag = (uint64_t)len << YYJSON_TAG_BIT;\n    tag |= (type | subtype);\n    ((yyjson_val *)val)->tag = tag;\n}\n\nyyjson_api_inline void unsafe_yyjson_inc_len(void *val) {\n    uint64_t tag = ((yyjson_val *)val)->tag;\n    tag += (uint64_t)(1 << YYJSON_TAG_BIT);\n    ((yyjson_val *)val)->tag = tag;\n}\n\nyyjson_api_inline void unsafe_yyjson_set_raw(void *val, const char *raw,\n                                             size_t len) {\n    unsafe_yyjson_set_tag(val, YYJSON_TYPE_RAW, YYJSON_SUBTYPE_NONE, len);\n    ((yyjson_val *)val)->uni.str = raw;\n}\n\nyyjson_api_inline void unsafe_yyjson_set_null(void *val) {\n    unsafe_yyjson_set_tag(val, YYJSON_TYPE_NULL, YYJSON_SUBTYPE_NONE, 0);\n}\n\nyyjson_api_inline void unsafe_yyjson_set_bool(void *val, bool num) {\n    yyjson_subtype subtype = num ? YYJSON_SUBTYPE_TRUE : YYJSON_SUBTYPE_FALSE;\n    unsafe_yyjson_set_tag(val, YYJSON_TYPE_BOOL, subtype, 0);\n}\n\nyyjson_api_inline void unsafe_yyjson_set_uint(void *val, uint64_t num) {\n    unsafe_yyjson_set_tag(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_UINT, 0);\n    ((yyjson_val *)val)->uni.u64 = num;\n}\n\nyyjson_api_inline void unsafe_yyjson_set_sint(void *val, int64_t num) {\n    unsafe_yyjson_set_tag(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_SINT, 0);\n    ((yyjson_val *)val)->uni.i64 = num;\n}\n\nyyjson_api_inline void unsafe_yyjson_set_fp_to_fixed(void *val, int prec) {\n    ((yyjson_val *)val)->tag &= ~((uint64_t)YYJSON_WRITE_FP_TO_FIXED(15) << 32);\n    ((yyjson_val *)val)->tag |= (uint64_t)YYJSON_WRITE_FP_TO_FIXED(prec) << 32;\n}\n\nyyjson_api_inline void unsafe_yyjson_set_fp_to_float(void *val, bool flt) {\n    uint64_t flag = (uint64_t)YYJSON_WRITE_FP_TO_FLOAT << 32;\n    if (flt) ((yyjson_val *)val)->tag |= flag;\n    else ((yyjson_val *)val)->tag &= ~flag;\n}\n\nyyjson_api_inline void unsafe_yyjson_set_float(void *val, float num) {\n    unsafe_yyjson_set_tag(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_REAL, 0);\n    ((yyjson_val *)val)->tag |= (uint64_t)YYJSON_WRITE_FP_TO_FLOAT << 32;\n    ((yyjson_val *)val)->uni.f64 = (double)num;\n}\n\nyyjson_api_inline void unsafe_yyjson_set_double(void *val, double num) {\n    unsafe_yyjson_set_tag(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_REAL, 0);\n    ((yyjson_val *)val)->uni.f64 = num;\n}\n\nyyjson_api_inline void unsafe_yyjson_set_real(void *val, double num) {\n    unsafe_yyjson_set_tag(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_REAL, 0);\n    ((yyjson_val *)val)->uni.f64 = num;\n}\n\nyyjson_api_inline void unsafe_yyjson_set_str_noesc(void *val, bool noesc) {\n    ((yyjson_val *)val)->tag &= ~(uint64_t)YYJSON_SUBTYPE_MASK;\n    if (noesc) ((yyjson_val *)val)->tag |= (uint64_t)YYJSON_SUBTYPE_NOESC;\n}\n\nyyjson_api_inline void unsafe_yyjson_set_strn(void *val, const char *str,\n                                              size_t len) {\n    unsafe_yyjson_set_tag(val, YYJSON_TYPE_STR, YYJSON_SUBTYPE_NONE, len);\n    ((yyjson_val *)val)->uni.str = str;\n}\n\nyyjson_api_inline void unsafe_yyjson_set_str(void *val, const char *str) {\n    size_t len = strlen(str);\n    bool noesc = unsafe_yyjson_is_str_noesc(str, len);\n    yyjson_subtype subtype = noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE;\n    unsafe_yyjson_set_tag(val, YYJSON_TYPE_STR, subtype, len);\n    ((yyjson_val *)val)->uni.str = str;\n}\n\nyyjson_api_inline void unsafe_yyjson_set_arr(void *val, size_t size) {\n    unsafe_yyjson_set_tag(val, YYJSON_TYPE_ARR, YYJSON_SUBTYPE_NONE, size);\n}\n\nyyjson_api_inline void unsafe_yyjson_set_obj(void *val, size_t size) {\n    unsafe_yyjson_set_tag(val, YYJSON_TYPE_OBJ, YYJSON_SUBTYPE_NONE, size);\n}\n\n\n\n/*==============================================================================\n * MARK: - JSON Document API (Implementation)\n *============================================================================*/\n\nyyjson_api_inline yyjson_val *yyjson_doc_get_root(yyjson_doc *doc) {\n    return doc ? doc->root : NULL;\n}\n\nyyjson_api_inline size_t yyjson_doc_get_read_size(yyjson_doc *doc) {\n    return doc ? doc->dat_read : 0;\n}\n\nyyjson_api_inline size_t yyjson_doc_get_val_count(yyjson_doc *doc) {\n    return doc ? doc->val_read : 0;\n}\n\nyyjson_api_inline void yyjson_doc_free(yyjson_doc *doc) {\n    if (doc) {\n        yyjson_alc alc = doc->alc;\n        memset(&doc->alc, 0, sizeof(alc));\n        if (doc->str_pool) alc.free(alc.ctx, doc->str_pool);\n        alc.free(alc.ctx, doc);\n    }\n}\n\n\n\n/*==============================================================================\n * MARK: - JSON Value Type API (Implementation)\n *============================================================================*/\n\nyyjson_api_inline bool yyjson_is_raw(yyjson_val *val) {\n    return val ? unsafe_yyjson_is_raw(val) : false;\n}\n\nyyjson_api_inline bool yyjson_is_null(yyjson_val *val) {\n    return val ? unsafe_yyjson_is_null(val) : false;\n}\n\nyyjson_api_inline bool yyjson_is_true(yyjson_val *val) {\n    return val ? unsafe_yyjson_is_true(val) : false;\n}\n\nyyjson_api_inline bool yyjson_is_false(yyjson_val *val) {\n    return val ? unsafe_yyjson_is_false(val) : false;\n}\n\nyyjson_api_inline bool yyjson_is_bool(yyjson_val *val) {\n    return val ? unsafe_yyjson_is_bool(val) : false;\n}\n\nyyjson_api_inline bool yyjson_is_uint(yyjson_val *val) {\n    return val ? unsafe_yyjson_is_uint(val) : false;\n}\n\nyyjson_api_inline bool yyjson_is_sint(yyjson_val *val) {\n    return val ? unsafe_yyjson_is_sint(val) : false;\n}\n\nyyjson_api_inline bool yyjson_is_int(yyjson_val *val) {\n    return val ? unsafe_yyjson_is_int(val) : false;\n}\n\nyyjson_api_inline bool yyjson_is_real(yyjson_val *val) {\n    return val ? unsafe_yyjson_is_real(val) : false;\n}\n\nyyjson_api_inline bool yyjson_is_num(yyjson_val *val) {\n    return val ? unsafe_yyjson_is_num(val) : false;\n}\n\nyyjson_api_inline bool yyjson_is_str(yyjson_val *val) {\n    return val ? unsafe_yyjson_is_str(val) : false;\n}\n\nyyjson_api_inline bool yyjson_is_arr(yyjson_val *val) {\n    return val ? unsafe_yyjson_is_arr(val) : false;\n}\n\nyyjson_api_inline bool yyjson_is_obj(yyjson_val *val) {\n    return val ? unsafe_yyjson_is_obj(val) : false;\n}\n\nyyjson_api_inline bool yyjson_is_ctn(yyjson_val *val) {\n    return val ? unsafe_yyjson_is_ctn(val) : false;\n}\n\n\n\n/*==============================================================================\n * MARK: - JSON Value Content API (Implementation)\n *============================================================================*/\n\nyyjson_api_inline yyjson_type yyjson_get_type(yyjson_val *val) {\n    return val ? unsafe_yyjson_get_type(val) : YYJSON_TYPE_NONE;\n}\n\nyyjson_api_inline yyjson_subtype yyjson_get_subtype(yyjson_val *val) {\n    return val ? unsafe_yyjson_get_subtype(val) : YYJSON_SUBTYPE_NONE;\n}\n\nyyjson_api_inline uint8_t yyjson_get_tag(yyjson_val *val) {\n    return val ? unsafe_yyjson_get_tag(val) : 0;\n}\n\nyyjson_api_inline const char *yyjson_get_type_desc(yyjson_val *val) {\n    switch (yyjson_get_tag(val)) {\n        case YYJSON_TYPE_RAW  | YYJSON_SUBTYPE_NONE:  return \"raw\";\n        case YYJSON_TYPE_NULL | YYJSON_SUBTYPE_NONE:  return \"null\";\n        case YYJSON_TYPE_STR  | YYJSON_SUBTYPE_NONE:  return \"string\";\n        case YYJSON_TYPE_STR  | YYJSON_SUBTYPE_NOESC: return \"string\";\n        case YYJSON_TYPE_ARR  | YYJSON_SUBTYPE_NONE:  return \"array\";\n        case YYJSON_TYPE_OBJ  | YYJSON_SUBTYPE_NONE:  return \"object\";\n        case YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE:  return \"true\";\n        case YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE: return \"false\";\n        case YYJSON_TYPE_NUM  | YYJSON_SUBTYPE_UINT:  return \"uint\";\n        case YYJSON_TYPE_NUM  | YYJSON_SUBTYPE_SINT:  return \"sint\";\n        case YYJSON_TYPE_NUM  | YYJSON_SUBTYPE_REAL:  return \"real\";\n        default:                                      return \"unknown\";\n    }\n}\n\nyyjson_api_inline const char *yyjson_get_raw(yyjson_val *val) {\n    return yyjson_is_raw(val) ? unsafe_yyjson_get_raw(val) : NULL;\n}\n\nyyjson_api_inline bool yyjson_get_bool(yyjson_val *val) {\n    return yyjson_is_bool(val) ? unsafe_yyjson_get_bool(val) : false;\n}\n\nyyjson_api_inline uint64_t yyjson_get_uint(yyjson_val *val) {\n    return yyjson_is_int(val) ? unsafe_yyjson_get_uint(val) : 0;\n}\n\nyyjson_api_inline int64_t yyjson_get_sint(yyjson_val *val) {\n    return yyjson_is_int(val) ? unsafe_yyjson_get_sint(val) : 0;\n}\n\nyyjson_api_inline int yyjson_get_int(yyjson_val *val) {\n    return yyjson_is_int(val) ? unsafe_yyjson_get_int(val) : 0;\n}\n\nyyjson_api_inline double yyjson_get_real(yyjson_val *val) {\n    return yyjson_is_real(val) ? unsafe_yyjson_get_real(val) : 0.0;\n}\n\nyyjson_api_inline double yyjson_get_num(yyjson_val *val) {\n    return val ? unsafe_yyjson_get_num(val) : 0.0;\n}\n\nyyjson_api_inline const char *yyjson_get_str(yyjson_val *val) {\n    return yyjson_is_str(val) ? unsafe_yyjson_get_str(val) : NULL;\n}\n\nyyjson_api_inline size_t yyjson_get_len(yyjson_val *val) {\n    return val ? unsafe_yyjson_get_len(val) : 0;\n}\n\nyyjson_api_inline bool yyjson_equals_str(yyjson_val *val, const char *str) {\n    if (yyjson_likely(val && str)) {\n        return unsafe_yyjson_is_str(val) &&\n               unsafe_yyjson_equals_str(val, str);\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_equals_strn(yyjson_val *val, const char *str,\n                                          size_t len) {\n    if (yyjson_likely(val && str)) {\n        return unsafe_yyjson_is_str(val) &&\n               unsafe_yyjson_equals_strn(val, str, len);\n    }\n    return false;\n}\n\nyyjson_api bool unsafe_yyjson_equals(yyjson_val *lhs, yyjson_val *rhs);\n\nyyjson_api_inline bool yyjson_equals(yyjson_val *lhs, yyjson_val *rhs) {\n    if (yyjson_unlikely(!lhs || !rhs)) return false;\n    return unsafe_yyjson_equals(lhs, rhs);\n}\n\nyyjson_api_inline bool yyjson_set_raw(yyjson_val *val,\n                                      const char *raw, size_t len) {\n    if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false;\n    unsafe_yyjson_set_raw(val, raw, len);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_set_null(yyjson_val *val) {\n    if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false;\n    unsafe_yyjson_set_null(val);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_set_bool(yyjson_val *val, bool num) {\n    if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false;\n    unsafe_yyjson_set_bool(val, num);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_set_uint(yyjson_val *val, uint64_t num) {\n    if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false;\n    unsafe_yyjson_set_uint(val, num);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_set_sint(yyjson_val *val, int64_t num) {\n    if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false;\n    unsafe_yyjson_set_sint(val, num);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_set_int(yyjson_val *val, int num) {\n    if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false;\n    unsafe_yyjson_set_sint(val, (int64_t)num);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_set_float(yyjson_val *val, float num) {\n    if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false;\n    unsafe_yyjson_set_float(val, num);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_set_double(yyjson_val *val, double num) {\n    if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false;\n    unsafe_yyjson_set_double(val, num);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_set_real(yyjson_val *val, double num) {\n    if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false;\n    unsafe_yyjson_set_real(val, num);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_set_fp_to_fixed(yyjson_val *val, int prec) {\n    if (yyjson_unlikely(!yyjson_is_real(val))) return false;\n    unsafe_yyjson_set_fp_to_fixed(val, prec);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_set_fp_to_float(yyjson_val *val, bool flt) {\n    if (yyjson_unlikely(!yyjson_is_real(val))) return false;\n    unsafe_yyjson_set_fp_to_float(val, flt);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_set_str(yyjson_val *val, const char *str) {\n    if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false;\n    if (yyjson_unlikely(!str)) return false;\n    unsafe_yyjson_set_str(val, str);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_set_strn(yyjson_val *val,\n                                       const char *str, size_t len) {\n    if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false;\n    if (yyjson_unlikely(!str)) return false;\n    unsafe_yyjson_set_strn(val, str, len);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_set_str_noesc(yyjson_val *val, bool noesc) {\n    if (yyjson_unlikely(!yyjson_is_str(val))) return false;\n    unsafe_yyjson_set_str_noesc(val, noesc);\n    return true;\n}\n\n\n\n/*==============================================================================\n * MARK: - JSON Array API (Implementation)\n *============================================================================*/\n\nyyjson_api_inline size_t yyjson_arr_size(yyjson_val *arr) {\n    return yyjson_is_arr(arr) ? unsafe_yyjson_get_len(arr) : 0;\n}\n\nyyjson_api_inline yyjson_val *yyjson_arr_get(yyjson_val *arr, size_t idx) {\n    if (yyjson_likely(yyjson_is_arr(arr))) {\n        if (yyjson_likely(unsafe_yyjson_get_len(arr) > idx)) {\n            yyjson_val *val = unsafe_yyjson_get_first(arr);\n            if (unsafe_yyjson_arr_is_flat(arr)) {\n                return val + idx;\n            } else {\n                while (idx-- > 0) val = unsafe_yyjson_get_next(val);\n                return val;\n            }\n        }\n    }\n    return NULL;\n}\n\nyyjson_api_inline yyjson_val *yyjson_arr_get_first(yyjson_val *arr) {\n    if (yyjson_likely(yyjson_is_arr(arr))) {\n        if (yyjson_likely(unsafe_yyjson_get_len(arr) > 0)) {\n            return unsafe_yyjson_get_first(arr);\n        }\n    }\n    return NULL;\n}\n\nyyjson_api_inline yyjson_val *yyjson_arr_get_last(yyjson_val *arr) {\n    if (yyjson_likely(yyjson_is_arr(arr))) {\n        size_t len = unsafe_yyjson_get_len(arr);\n        if (yyjson_likely(len > 0)) {\n            yyjson_val *val = unsafe_yyjson_get_first(arr);\n            if (unsafe_yyjson_arr_is_flat(arr)) {\n                return val + (len - 1);\n            } else {\n                while (len-- > 1) val = unsafe_yyjson_get_next(val);\n                return val;\n            }\n        }\n    }\n    return NULL;\n}\n\n\n\n/*==============================================================================\n * MARK: - JSON Array Iterator API (Implementation)\n *============================================================================*/\n\nyyjson_api_inline bool yyjson_arr_iter_init(yyjson_val *arr,\n                                            yyjson_arr_iter *iter) {\n    if (yyjson_likely(yyjson_is_arr(arr) && iter)) {\n        iter->idx = 0;\n        iter->max = unsafe_yyjson_get_len(arr);\n        iter->cur = unsafe_yyjson_get_first(arr);\n        return true;\n    }\n    if (iter) memset(iter, 0, sizeof(yyjson_arr_iter));\n    return false;\n}\n\nyyjson_api_inline yyjson_arr_iter yyjson_arr_iter_with(yyjson_val *arr) {\n    yyjson_arr_iter iter;\n    yyjson_arr_iter_init(arr, &iter);\n    return iter;\n}\n\nyyjson_api_inline bool yyjson_arr_iter_has_next(yyjson_arr_iter *iter) {\n    return iter ? iter->idx < iter->max : false;\n}\n\nyyjson_api_inline yyjson_val *yyjson_arr_iter_next(yyjson_arr_iter *iter) {\n    yyjson_val *val;\n    if (iter && iter->idx < iter->max) {\n        val = iter->cur;\n        iter->cur = unsafe_yyjson_get_next(val);\n        iter->idx++;\n        return val;\n    }\n    return NULL;\n}\n\n\n\n/*==============================================================================\n * MARK: - JSON Object API (Implementation)\n *============================================================================*/\n\nyyjson_api_inline size_t yyjson_obj_size(yyjson_val *obj) {\n    return yyjson_is_obj(obj) ? unsafe_yyjson_get_len(obj) : 0;\n}\n\nyyjson_api_inline yyjson_val *yyjson_obj_get(yyjson_val *obj,\n                                             const char *key) {\n    return yyjson_obj_getn(obj, key, key ? strlen(key) : 0);\n}\n\nyyjson_api_inline yyjson_val *yyjson_obj_getn(yyjson_val *obj,\n                                              const char *_key,\n                                              size_t key_len) {\n    if (yyjson_likely(yyjson_is_obj(obj) && _key)) {\n        size_t len = unsafe_yyjson_get_len(obj);\n        yyjson_val *key = unsafe_yyjson_get_first(obj);\n        while (len-- > 0) {\n            if (unsafe_yyjson_equals_strn(key, _key, key_len)) return key + 1;\n            key = unsafe_yyjson_get_next(key + 1);\n        }\n    }\n    return NULL;\n}\n\n\n\n/*==============================================================================\n * MARK: - JSON Object Iterator API (Implementation)\n *============================================================================*/\n\nyyjson_api_inline bool yyjson_obj_iter_init(yyjson_val *obj,\n                                            yyjson_obj_iter *iter) {\n    if (yyjson_likely(yyjson_is_obj(obj) && iter)) {\n        iter->idx = 0;\n        iter->max = unsafe_yyjson_get_len(obj);\n        iter->cur = unsafe_yyjson_get_first(obj);\n        iter->obj = obj;\n        return true;\n    }\n    if (iter) memset(iter, 0, sizeof(yyjson_obj_iter));\n    return false;\n}\n\nyyjson_api_inline yyjson_obj_iter yyjson_obj_iter_with(yyjson_val *obj) {\n    yyjson_obj_iter iter;\n    yyjson_obj_iter_init(obj, &iter);\n    return iter;\n}\n\nyyjson_api_inline bool yyjson_obj_iter_has_next(yyjson_obj_iter *iter) {\n    return iter ? iter->idx < iter->max : false;\n}\n\nyyjson_api_inline yyjson_val *yyjson_obj_iter_next(yyjson_obj_iter *iter) {\n    if (iter && iter->idx < iter->max) {\n        yyjson_val *key = iter->cur;\n        iter->idx++;\n        iter->cur = unsafe_yyjson_get_next(key + 1);\n        return key;\n    }\n    return NULL;\n}\n\nyyjson_api_inline yyjson_val *yyjson_obj_iter_get_val(yyjson_val *key) {\n    return key ? key + 1 : NULL;\n}\n\nyyjson_api_inline yyjson_val *yyjson_obj_iter_get(yyjson_obj_iter *iter,\n                                                  const char *key) {\n    return yyjson_obj_iter_getn(iter, key, key ? strlen(key) : 0);\n}\n\nyyjson_api_inline yyjson_val *yyjson_obj_iter_getn(yyjson_obj_iter *iter,\n                                                   const char *key,\n                                                   size_t key_len) {\n    if (iter && key) {\n        size_t idx = iter->idx;\n        size_t max = iter->max;\n        yyjson_val *cur = iter->cur;\n        if (yyjson_unlikely(idx == max)) {\n            idx = 0;\n            cur = unsafe_yyjson_get_first(iter->obj);\n        }\n        while (idx++ < max) {\n            yyjson_val *next = unsafe_yyjson_get_next(cur + 1);\n            if (unsafe_yyjson_equals_strn(cur, key, key_len)) {\n                iter->idx = idx;\n                iter->cur = next;\n                return cur + 1;\n            }\n            cur = next;\n            if (idx == iter->max && iter->idx < iter->max) {\n                idx = 0;\n                max = iter->idx;\n                cur = unsafe_yyjson_get_first(iter->obj);\n            }\n        }\n    }\n    return NULL;\n}\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Structure (Implementation)\n *============================================================================*/\n\n/**\n Mutable JSON value, 24 bytes.\n The 'tag' and 'uni' field is same as immutable value.\n The 'next' field links all elements inside the container to be a cycle.\n */\nstruct yyjson_mut_val {\n    uint64_t tag; /**< type, subtype and length */\n    yyjson_val_uni uni; /**< payload */\n    yyjson_mut_val *next; /**< the next value in circular linked list */\n};\n\n/**\n A memory chunk in string memory pool.\n */\ntypedef struct yyjson_str_chunk {\n    struct yyjson_str_chunk *next; /* next chunk linked list */\n    size_t chunk_size; /* chunk size in bytes */\n    /* char str[]; flexible array member */\n} yyjson_str_chunk;\n\n/**\n A memory pool to hold all strings in a mutable document.\n */\ntypedef struct yyjson_str_pool {\n    char *cur; /* cursor inside current chunk */\n    char *end; /* the end of current chunk */\n    size_t chunk_size; /* chunk size in bytes while creating new chunk */\n    size_t chunk_size_max; /* maximum chunk size in bytes */\n    yyjson_str_chunk *chunks; /* a linked list of chunks, nullable */\n} yyjson_str_pool;\n\n/**\n A memory chunk in value memory pool.\n `sizeof(yyjson_val_chunk)` should not larger than `sizeof(yyjson_mut_val)`.\n */\ntypedef struct yyjson_val_chunk {\n    struct yyjson_val_chunk *next; /* next chunk linked list */\n    size_t chunk_size; /* chunk size in bytes */\n    /* char pad[sizeof(yyjson_mut_val) - sizeof(yyjson_val_chunk)]; padding */\n    /* yyjson_mut_val vals[]; flexible array member */\n} yyjson_val_chunk;\n\n/**\n A memory pool to hold all values in a mutable document.\n */\ntypedef struct yyjson_val_pool {\n    yyjson_mut_val *cur; /* cursor inside current chunk */\n    yyjson_mut_val *end; /* the end of current chunk */\n    size_t chunk_size; /* chunk size in bytes while creating new chunk */\n    size_t chunk_size_max; /* maximum chunk size in bytes */\n    yyjson_val_chunk *chunks; /* a linked list of chunks, nullable */\n} yyjson_val_pool;\n\nstruct yyjson_mut_doc {\n    yyjson_mut_val *root; /**< root value of the JSON document, nullable */\n    yyjson_alc alc; /**< a valid allocator, nonnull */\n    yyjson_str_pool str_pool; /**< string memory pool */\n    yyjson_val_pool val_pool; /**< value memory pool */\n};\n\n/* Ensures the capacity to at least equal to the specified byte length. */\nyyjson_api bool unsafe_yyjson_str_pool_grow(yyjson_str_pool *pool,\n                                            const yyjson_alc *alc,\n                                            size_t len);\n\n/* Ensures the capacity to at least equal to the specified value count. */\nyyjson_api bool unsafe_yyjson_val_pool_grow(yyjson_val_pool *pool,\n                                            const yyjson_alc *alc,\n                                            size_t count);\n\n/* Allocate memory for string. */\nyyjson_api_inline char *unsafe_yyjson_mut_str_alc(yyjson_mut_doc *doc,\n                                                  size_t len) {\n    char *mem;\n    const yyjson_alc *alc = &doc->alc;\n    yyjson_str_pool *pool = &doc->str_pool;\n    if (yyjson_unlikely((size_t)(pool->end - pool->cur) <= len)) {\n        if (yyjson_unlikely(!unsafe_yyjson_str_pool_grow(pool, alc, len + 1))) {\n            return NULL;\n        }\n    }\n    mem = pool->cur;\n    pool->cur = mem + len + 1;\n    return mem;\n}\n\nyyjson_api_inline char *unsafe_yyjson_mut_strncpy(yyjson_mut_doc *doc,\n                                                  const char *str, size_t len) {\n    char *mem = unsafe_yyjson_mut_str_alc(doc, len);\n    if (yyjson_unlikely(!mem)) return NULL;\n    memcpy((void *)mem, (const void *)str, len);\n    mem[len] = '\\0';\n    return mem;\n}\n\nyyjson_api_inline yyjson_mut_val *unsafe_yyjson_mut_val(yyjson_mut_doc *doc,\n                                                        size_t count) {\n    yyjson_mut_val *val;\n    yyjson_alc *alc = &doc->alc;\n    yyjson_val_pool *pool = &doc->val_pool;\n    if (yyjson_unlikely((size_t)(pool->end - pool->cur) < count)) {\n        if (yyjson_unlikely(!unsafe_yyjson_val_pool_grow(pool, alc, count))) {\n            return NULL;\n        }\n    }\n    val = pool->cur;\n    pool->cur += count;\n    return val;\n}\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Document API (Implementation)\n *============================================================================*/\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_get_root(yyjson_mut_doc *doc) {\n    return doc ? doc->root : NULL;\n}\n\nyyjson_api_inline void yyjson_mut_doc_set_root(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *root) {\n    if (doc) doc->root = root;\n}\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Value Type API (Implementation)\n *============================================================================*/\n\nyyjson_api_inline bool yyjson_mut_is_raw(yyjson_mut_val *val) {\n    return val ? unsafe_yyjson_is_raw(val) : false;\n}\n\nyyjson_api_inline bool yyjson_mut_is_null(yyjson_mut_val *val) {\n    return val ? unsafe_yyjson_is_null(val) : false;\n}\n\nyyjson_api_inline bool yyjson_mut_is_true(yyjson_mut_val *val) {\n    return val ? unsafe_yyjson_is_true(val) : false;\n}\n\nyyjson_api_inline bool yyjson_mut_is_false(yyjson_mut_val *val) {\n    return val ? unsafe_yyjson_is_false(val) : false;\n}\n\nyyjson_api_inline bool yyjson_mut_is_bool(yyjson_mut_val *val) {\n    return val ? unsafe_yyjson_is_bool(val) : false;\n}\n\nyyjson_api_inline bool yyjson_mut_is_uint(yyjson_mut_val *val) {\n    return val ? unsafe_yyjson_is_uint(val) : false;\n}\n\nyyjson_api_inline bool yyjson_mut_is_sint(yyjson_mut_val *val) {\n    return val ? unsafe_yyjson_is_sint(val) : false;\n}\n\nyyjson_api_inline bool yyjson_mut_is_int(yyjson_mut_val *val) {\n    return val ? unsafe_yyjson_is_int(val) : false;\n}\n\nyyjson_api_inline bool yyjson_mut_is_real(yyjson_mut_val *val) {\n    return val ? unsafe_yyjson_is_real(val) : false;\n}\n\nyyjson_api_inline bool yyjson_mut_is_num(yyjson_mut_val *val) {\n    return val ? unsafe_yyjson_is_num(val) : false;\n}\n\nyyjson_api_inline bool yyjson_mut_is_str(yyjson_mut_val *val) {\n    return val ? unsafe_yyjson_is_str(val) : false;\n}\n\nyyjson_api_inline bool yyjson_mut_is_arr(yyjson_mut_val *val) {\n    return val ? unsafe_yyjson_is_arr(val) : false;\n}\n\nyyjson_api_inline bool yyjson_mut_is_obj(yyjson_mut_val *val) {\n    return val ? unsafe_yyjson_is_obj(val) : false;\n}\n\nyyjson_api_inline bool yyjson_mut_is_ctn(yyjson_mut_val *val) {\n    return val ? unsafe_yyjson_is_ctn(val) : false;\n}\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Value Content API (Implementation)\n *============================================================================*/\n\nyyjson_api_inline yyjson_type yyjson_mut_get_type(yyjson_mut_val *val) {\n    return yyjson_get_type((yyjson_val *)val);\n}\n\nyyjson_api_inline yyjson_subtype yyjson_mut_get_subtype(yyjson_mut_val *val) {\n    return yyjson_get_subtype((yyjson_val *)val);\n}\n\nyyjson_api_inline uint8_t yyjson_mut_get_tag(yyjson_mut_val *val) {\n    return yyjson_get_tag((yyjson_val *)val);\n}\n\nyyjson_api_inline const char *yyjson_mut_get_type_desc(yyjson_mut_val *val) {\n    return yyjson_get_type_desc((yyjson_val *)val);\n}\n\nyyjson_api_inline const char *yyjson_mut_get_raw(yyjson_mut_val *val) {\n    return yyjson_get_raw((yyjson_val *)val);\n}\n\nyyjson_api_inline bool yyjson_mut_get_bool(yyjson_mut_val *val) {\n    return yyjson_get_bool((yyjson_val *)val);\n}\n\nyyjson_api_inline uint64_t yyjson_mut_get_uint(yyjson_mut_val *val) {\n    return yyjson_get_uint((yyjson_val *)val);\n}\n\nyyjson_api_inline int64_t yyjson_mut_get_sint(yyjson_mut_val *val) {\n    return yyjson_get_sint((yyjson_val *)val);\n}\n\nyyjson_api_inline int yyjson_mut_get_int(yyjson_mut_val *val) {\n    return yyjson_get_int((yyjson_val *)val);\n}\n\nyyjson_api_inline double yyjson_mut_get_real(yyjson_mut_val *val) {\n    return yyjson_get_real((yyjson_val *)val);\n}\n\nyyjson_api_inline double yyjson_mut_get_num(yyjson_mut_val *val) {\n    return yyjson_get_num((yyjson_val *)val);\n}\n\nyyjson_api_inline const char *yyjson_mut_get_str(yyjson_mut_val *val) {\n    return yyjson_get_str((yyjson_val *)val);\n}\n\nyyjson_api_inline size_t yyjson_mut_get_len(yyjson_mut_val *val) {\n    return yyjson_get_len((yyjson_val *)val);\n}\n\nyyjson_api_inline bool yyjson_mut_equals_str(yyjson_mut_val *val,\n                                             const char *str) {\n    return yyjson_equals_str((yyjson_val *)val, str);\n}\n\nyyjson_api_inline bool yyjson_mut_equals_strn(yyjson_mut_val *val,\n                                              const char *str, size_t len) {\n    return yyjson_equals_strn((yyjson_val *)val, str, len);\n}\n\nyyjson_api bool unsafe_yyjson_mut_equals(yyjson_mut_val *lhs,\n                                         yyjson_mut_val *rhs);\n\nyyjson_api_inline bool yyjson_mut_equals(yyjson_mut_val *lhs,\n                                         yyjson_mut_val *rhs) {\n    if (yyjson_unlikely(!lhs || !rhs)) return false;\n    return unsafe_yyjson_mut_equals(lhs, rhs);\n}\n\nyyjson_api_inline bool yyjson_mut_set_raw(yyjson_mut_val *val,\n                                          const char *raw, size_t len) {\n    if (yyjson_unlikely(!val || !raw)) return false;\n    unsafe_yyjson_set_raw(val, raw, len);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_mut_set_null(yyjson_mut_val *val) {\n    if (yyjson_unlikely(!val)) return false;\n    unsafe_yyjson_set_null(val);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_mut_set_bool(yyjson_mut_val *val, bool num) {\n    if (yyjson_unlikely(!val)) return false;\n    unsafe_yyjson_set_bool(val, num);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_mut_set_uint(yyjson_mut_val *val, uint64_t num) {\n    if (yyjson_unlikely(!val)) return false;\n    unsafe_yyjson_set_uint(val, num);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_mut_set_sint(yyjson_mut_val *val, int64_t num) {\n    if (yyjson_unlikely(!val)) return false;\n    unsafe_yyjson_set_sint(val, num);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_mut_set_int(yyjson_mut_val *val, int num) {\n    if (yyjson_unlikely(!val)) return false;\n    unsafe_yyjson_set_sint(val, (int64_t)num);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_mut_set_float(yyjson_mut_val *val, float num) {\n    if (yyjson_unlikely(!val)) return false;\n    unsafe_yyjson_set_float(val, num);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_mut_set_double(yyjson_mut_val *val, double num) {\n    if (yyjson_unlikely(!val)) return false;\n    unsafe_yyjson_set_double(val, num);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_mut_set_real(yyjson_mut_val *val, double num) {\n    if (yyjson_unlikely(!val)) return false;\n    unsafe_yyjson_set_real(val, num);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_mut_set_fp_to_fixed(yyjson_mut_val *val,\n                                                  int prec) {\n    if (yyjson_unlikely(!yyjson_mut_is_real(val))) return false;\n    unsafe_yyjson_set_fp_to_fixed(val, prec);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_mut_set_fp_to_float(yyjson_mut_val *val,\n                                                  bool flt) {\n    if (yyjson_unlikely(!yyjson_mut_is_real(val))) return false;\n    unsafe_yyjson_set_fp_to_float(val, flt);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_mut_set_str(yyjson_mut_val *val,\n                                          const char *str) {\n    if (yyjson_unlikely(!val || !str)) return false;\n    unsafe_yyjson_set_str(val, str);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_mut_set_strn(yyjson_mut_val *val,\n                                           const char *str, size_t len) {\n    if (yyjson_unlikely(!val || !str)) return false;\n    unsafe_yyjson_set_strn(val, str, len);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_mut_set_str_noesc(yyjson_mut_val *val,\n                                                bool noesc) {\n    if (yyjson_unlikely(!yyjson_mut_is_str(val))) return false;\n    unsafe_yyjson_set_str_noesc(val, noesc);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_mut_set_arr(yyjson_mut_val *val) {\n    if (yyjson_unlikely(!val)) return false;\n    unsafe_yyjson_set_arr(val, 0);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_mut_set_obj(yyjson_mut_val *val) {\n    if (yyjson_unlikely(!val)) return false;\n    unsafe_yyjson_set_obj(val, 0);\n    return true;\n}\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Value Creation API (Implementation)\n *============================================================================*/\n\n#define yyjson_mut_val_one(func) \\\n    if (yyjson_likely(doc)) { \\\n        yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); \\\n        if (yyjson_likely(val)) { \\\n            func \\\n            return val; \\\n        } \\\n    } \\\n    return NULL\n\n#define yyjson_mut_val_one_str(func) \\\n    if (yyjson_likely(doc && str)) { \\\n        yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); \\\n        if (yyjson_likely(val)) { \\\n            func \\\n            return val; \\\n        } \\\n    } \\\n    return NULL\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_raw(yyjson_mut_doc *doc,\n                                                 const char *str) {\n    yyjson_mut_val_one_str({ unsafe_yyjson_set_raw(val, str, strlen(str)); });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_rawn(yyjson_mut_doc *doc,\n                                                  const char *str,\n                                                  size_t len) {\n    yyjson_mut_val_one_str({ unsafe_yyjson_set_raw(val, str, len); });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_rawcpy(yyjson_mut_doc *doc,\n                                                    const char *str) {\n    yyjson_mut_val_one_str({\n        size_t len = strlen(str);\n        char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len);\n        if (yyjson_unlikely(!new_str)) return NULL;\n        unsafe_yyjson_set_raw(val, new_str, len);\n    });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_rawncpy(yyjson_mut_doc *doc,\n                                                     const char *str,\n                                                     size_t len) {\n    yyjson_mut_val_one_str({\n        char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len);\n        if (yyjson_unlikely(!new_str)) return NULL;\n        unsafe_yyjson_set_raw(val, new_str, len);\n    });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_null(yyjson_mut_doc *doc) {\n    yyjson_mut_val_one({ unsafe_yyjson_set_null(val); });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_true(yyjson_mut_doc *doc) {\n    yyjson_mut_val_one({ unsafe_yyjson_set_bool(val, true); });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_false(yyjson_mut_doc *doc) {\n    yyjson_mut_val_one({ unsafe_yyjson_set_bool(val, false); });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_bool(yyjson_mut_doc *doc,\n                                                  bool _val) {\n    yyjson_mut_val_one({ unsafe_yyjson_set_bool(val, _val); });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_uint(yyjson_mut_doc *doc,\n                                                  uint64_t num) {\n    yyjson_mut_val_one({ unsafe_yyjson_set_uint(val, num); });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_sint(yyjson_mut_doc *doc,\n                                                  int64_t num) {\n    yyjson_mut_val_one({ unsafe_yyjson_set_sint(val, num); });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_int(yyjson_mut_doc *doc,\n                                                 int64_t num) {\n    yyjson_mut_val_one({ unsafe_yyjson_set_sint(val, num); });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_float(yyjson_mut_doc *doc,\n                                                   float num) {\n    yyjson_mut_val_one({ unsafe_yyjson_set_float(val, num); });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_double(yyjson_mut_doc *doc,\n                                                    double num) {\n    yyjson_mut_val_one({ unsafe_yyjson_set_double(val, num); });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_real(yyjson_mut_doc *doc,\n                                                  double num) {\n    yyjson_mut_val_one({ unsafe_yyjson_set_real(val, num); });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_str(yyjson_mut_doc *doc,\n                                                 const char *str) {\n    yyjson_mut_val_one_str({ unsafe_yyjson_set_str(val, str); });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_strn(yyjson_mut_doc *doc,\n                                                  const char *str,\n                                                  size_t len) {\n    yyjson_mut_val_one_str({ unsafe_yyjson_set_strn(val, str, len); });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_strcpy(yyjson_mut_doc *doc,\n                                                    const char *str) {\n    yyjson_mut_val_one_str({\n        size_t len = strlen(str);\n        bool noesc = unsafe_yyjson_is_str_noesc(str, len);\n        yyjson_subtype sub = noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE;\n        char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len);\n        if (yyjson_unlikely(!new_str)) return NULL;\n        unsafe_yyjson_set_tag(val, YYJSON_TYPE_STR, sub, len);\n        val->uni.str = new_str;\n    });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_strncpy(yyjson_mut_doc *doc,\n                                                     const char *str,\n                                                     size_t len) {\n    yyjson_mut_val_one_str({\n        char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len);\n        if (yyjson_unlikely(!new_str)) return NULL;\n        unsafe_yyjson_set_strn(val, new_str, len);\n    });\n}\n\n#undef yyjson_mut_val_one\n#undef yyjson_mut_val_one_str\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Array API (Implementation)\n *============================================================================*/\n\nyyjson_api_inline size_t yyjson_mut_arr_size(yyjson_mut_val *arr) {\n    return yyjson_mut_is_arr(arr) ? unsafe_yyjson_get_len(arr) : 0;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get(yyjson_mut_val *arr,\n                                                     size_t idx) {\n    if (yyjson_likely(idx < yyjson_mut_arr_size(arr))) {\n        yyjson_mut_val *val = (yyjson_mut_val *)arr->uni.ptr;\n        while (idx-- > 0) val = val->next;\n        return val->next;\n    }\n    return NULL;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get_first(\n    yyjson_mut_val *arr) {\n    if (yyjson_likely(yyjson_mut_arr_size(arr) > 0)) {\n        return ((yyjson_mut_val *)arr->uni.ptr)->next;\n    }\n    return NULL;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get_last(\n    yyjson_mut_val *arr) {\n    if (yyjson_likely(yyjson_mut_arr_size(arr) > 0)) {\n        return ((yyjson_mut_val *)arr->uni.ptr);\n    }\n    return NULL;\n}\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Array Iterator API (Implementation)\n *============================================================================*/\n\nyyjson_api_inline bool yyjson_mut_arr_iter_init(yyjson_mut_val *arr,\n                                                yyjson_mut_arr_iter *iter) {\n    if (yyjson_likely(yyjson_mut_is_arr(arr) && iter)) {\n        iter->idx = 0;\n        iter->max = unsafe_yyjson_get_len(arr);\n        iter->cur = iter->max ? (yyjson_mut_val *)arr->uni.ptr : NULL;\n        iter->pre = NULL;\n        iter->arr = arr;\n        return true;\n    }\n    if (iter) memset(iter, 0, sizeof(yyjson_mut_arr_iter));\n    return false;\n}\n\nyyjson_api_inline yyjson_mut_arr_iter yyjson_mut_arr_iter_with(\n    yyjson_mut_val *arr) {\n    yyjson_mut_arr_iter iter;\n    yyjson_mut_arr_iter_init(arr, &iter);\n    return iter;\n}\n\nyyjson_api_inline bool yyjson_mut_arr_iter_has_next(yyjson_mut_arr_iter *iter) {\n    return iter ? iter->idx < iter->max : false;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_iter_next(\n    yyjson_mut_arr_iter *iter) {\n    if (iter && iter->idx < iter->max) {\n        yyjson_mut_val *val = iter->cur;\n        iter->pre = val;\n        iter->cur = val->next;\n        iter->idx++;\n        return iter->cur;\n    }\n    return NULL;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_iter_remove(\n    yyjson_mut_arr_iter *iter) {\n    if (yyjson_likely(iter && 0 < iter->idx && iter->idx <= iter->max)) {\n        yyjson_mut_val *prev = iter->pre;\n        yyjson_mut_val *cur = iter->cur;\n        yyjson_mut_val *next = cur->next;\n        if (yyjson_unlikely(iter->idx == iter->max)) iter->arr->uni.ptr = prev;\n        iter->idx--;\n        iter->max--;\n        unsafe_yyjson_set_len(iter->arr, iter->max);\n        prev->next = next;\n        iter->cur = prev;\n        return cur;\n    }\n    return NULL;\n}\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Array Creation API (Implementation)\n *============================================================================*/\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr(yyjson_mut_doc *doc) {\n    if (yyjson_likely(doc)) {\n        yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1);\n        if (yyjson_likely(val)) {\n            val->tag = YYJSON_TYPE_ARR | YYJSON_SUBTYPE_NONE;\n            return val;\n        }\n    }\n    return NULL;\n}\n\n#define yyjson_mut_arr_with_func(func) \\\n    if (yyjson_likely(doc && ((0 < count && count < \\\n        (~(size_t)0) / sizeof(yyjson_mut_val) && vals) || count == 0))) { \\\n        yyjson_mut_val *arr = unsafe_yyjson_mut_val(doc, 1 + count); \\\n        if (yyjson_likely(arr)) { \\\n            arr->tag = ((uint64_t)count << YYJSON_TAG_BIT) | YYJSON_TYPE_ARR; \\\n            if (count > 0) { \\\n                size_t i; \\\n                for (i = 0; i < count; i++) { \\\n                    yyjson_mut_val *val = arr + i + 1; \\\n                    func \\\n                    val->next = val + 1; \\\n                } \\\n                arr[count].next = arr + 1; \\\n                arr->uni.ptr = arr + count; \\\n            } \\\n            return arr; \\\n        } \\\n    } \\\n    return NULL\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_bool(\n    yyjson_mut_doc *doc, const bool *vals, size_t count) {\n    yyjson_mut_arr_with_func({\n        unsafe_yyjson_set_bool(val, vals[i]);\n    });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint(\n    yyjson_mut_doc *doc, const int64_t *vals, size_t count) {\n    return yyjson_mut_arr_with_sint64(doc, vals, count);\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint(\n    yyjson_mut_doc *doc, const uint64_t *vals, size_t count) {\n    return yyjson_mut_arr_with_uint64(doc, vals, count);\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_real(\n    yyjson_mut_doc *doc, const double *vals, size_t count) {\n    yyjson_mut_arr_with_func({\n        unsafe_yyjson_set_real(val, vals[i]);\n    });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint8(\n    yyjson_mut_doc *doc, const int8_t *vals, size_t count) {\n    yyjson_mut_arr_with_func({\n        unsafe_yyjson_set_sint(val, vals[i]);\n    });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint16(\n    yyjson_mut_doc *doc, const int16_t *vals, size_t count) {\n    yyjson_mut_arr_with_func({\n        unsafe_yyjson_set_sint(val, vals[i]);\n    });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint32(\n    yyjson_mut_doc *doc, const int32_t *vals, size_t count) {\n    yyjson_mut_arr_with_func({\n        unsafe_yyjson_set_sint(val, vals[i]);\n    });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint64(\n    yyjson_mut_doc *doc, const int64_t *vals, size_t count) {\n    yyjson_mut_arr_with_func({\n        unsafe_yyjson_set_sint(val, vals[i]);\n    });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint8(\n    yyjson_mut_doc *doc, const uint8_t *vals, size_t count) {\n    yyjson_mut_arr_with_func({\n        unsafe_yyjson_set_uint(val, vals[i]);\n    });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint16(\n    yyjson_mut_doc *doc, const uint16_t *vals, size_t count) {\n    yyjson_mut_arr_with_func({\n        unsafe_yyjson_set_uint(val, vals[i]);\n    });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint32(\n    yyjson_mut_doc *doc, const uint32_t *vals, size_t count) {\n    yyjson_mut_arr_with_func({\n        unsafe_yyjson_set_uint(val, vals[i]);\n    });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint64(\n    yyjson_mut_doc *doc, const uint64_t *vals, size_t count) {\n    yyjson_mut_arr_with_func({\n        unsafe_yyjson_set_uint(val, vals[i]);\n    });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_float(\n    yyjson_mut_doc *doc, const float *vals, size_t count) {\n    yyjson_mut_arr_with_func({\n        unsafe_yyjson_set_float(val, vals[i]);\n    });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_double(\n    yyjson_mut_doc *doc, const double *vals, size_t count) {\n    yyjson_mut_arr_with_func({\n        unsafe_yyjson_set_double(val, vals[i]);\n    });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_str(\n    yyjson_mut_doc *doc, const char **vals, size_t count) {\n    yyjson_mut_arr_with_func({\n        if (yyjson_unlikely(!vals[i])) return NULL;\n        unsafe_yyjson_set_str(val, vals[i]);\n    });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strn(\n    yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count) {\n    if (yyjson_unlikely(count > 0 && !lens)) return NULL;\n    yyjson_mut_arr_with_func({\n        if (yyjson_unlikely(!vals[i])) return NULL;\n        unsafe_yyjson_set_strn(val, vals[i], lens[i]);\n    });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strcpy(\n    yyjson_mut_doc *doc, const char **vals, size_t count) {\n    size_t len;\n    const char *str, *new_str;\n    yyjson_mut_arr_with_func({\n        str = vals[i];\n        if (yyjson_unlikely(!str)) return NULL;\n        len = strlen(str);\n        new_str = unsafe_yyjson_mut_strncpy(doc, str, len);\n        if (yyjson_unlikely(!new_str)) return NULL;\n        unsafe_yyjson_set_strn(val, new_str, len);\n    });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strncpy(\n    yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count) {\n    size_t len;\n    const char *str, *new_str;\n    if (yyjson_unlikely(count > 0 && !lens)) return NULL;\n    yyjson_mut_arr_with_func({\n        str = vals[i];\n        if (yyjson_unlikely(!str)) return NULL;\n        len = lens[i];\n        new_str = unsafe_yyjson_mut_strncpy(doc, str, len);\n        if (yyjson_unlikely(!new_str)) return NULL;\n        unsafe_yyjson_set_strn(val, new_str, len);\n    });\n}\n\n#undef yyjson_mut_arr_with_func\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Array Modification API (Implementation)\n *============================================================================*/\n\nyyjson_api_inline bool yyjson_mut_arr_insert(yyjson_mut_val *arr,\n                                             yyjson_mut_val *val, size_t idx) {\n    if (yyjson_likely(yyjson_mut_is_arr(arr) && val)) {\n        size_t len = unsafe_yyjson_get_len(arr);\n        if (yyjson_likely(idx <= len)) {\n            unsafe_yyjson_set_len(arr, len + 1);\n            if (len == 0) {\n                val->next = val;\n                arr->uni.ptr = val;\n            } else {\n                yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr);\n                yyjson_mut_val *next = prev->next;\n                if (idx == len) {\n                    prev->next = val;\n                    val->next = next;\n                    arr->uni.ptr = val;\n                } else {\n                    while (idx-- > 0) {\n                        prev = next;\n                        next = next->next;\n                    }\n                    prev->next = val;\n                    val->next = next;\n                }\n            }\n            return true;\n        }\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_mut_arr_append(yyjson_mut_val *arr,\n                                             yyjson_mut_val *val) {\n    if (yyjson_likely(yyjson_mut_is_arr(arr) && val)) {\n        size_t len = unsafe_yyjson_get_len(arr);\n        unsafe_yyjson_set_len(arr, len + 1);\n        if (len == 0) {\n            val->next = val;\n        } else {\n            yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr);\n            yyjson_mut_val *next = prev->next;\n            prev->next = val;\n            val->next = next;\n        }\n        arr->uni.ptr = val;\n        return true;\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_mut_arr_prepend(yyjson_mut_val *arr,\n                                              yyjson_mut_val *val) {\n    if (yyjson_likely(yyjson_mut_is_arr(arr) && val)) {\n        size_t len = unsafe_yyjson_get_len(arr);\n        unsafe_yyjson_set_len(arr, len + 1);\n        if (len == 0) {\n            val->next = val;\n            arr->uni.ptr = val;\n        } else {\n            yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr);\n            yyjson_mut_val *next = prev->next;\n            prev->next = val;\n            val->next = next;\n        }\n        return true;\n    }\n    return false;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_replace(yyjson_mut_val *arr,\n                                                         size_t idx,\n                                                         yyjson_mut_val *val) {\n    if (yyjson_likely(yyjson_mut_is_arr(arr) && val)) {\n        size_t len = unsafe_yyjson_get_len(arr);\n        if (yyjson_likely(idx < len)) {\n            if (yyjson_likely(len > 1)) {\n                yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr);\n                yyjson_mut_val *next = prev->next;\n                while (idx-- > 0) {\n                    prev = next;\n                    next = next->next;\n                }\n                prev->next = val;\n                val->next = next->next;\n                if ((void *)next == arr->uni.ptr) arr->uni.ptr = val;\n                return next;\n            } else {\n                yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr);\n                val->next = val;\n                arr->uni.ptr = val;\n                return prev;\n            }\n        }\n    }\n    return NULL;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove(yyjson_mut_val *arr,\n                                                        size_t idx) {\n    if (yyjson_likely(yyjson_mut_is_arr(arr))) {\n        size_t len = unsafe_yyjson_get_len(arr);\n        if (yyjson_likely(idx < len)) {\n            unsafe_yyjson_set_len(arr, len - 1);\n            if (yyjson_likely(len > 1)) {\n                yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr);\n                yyjson_mut_val *next = prev->next;\n                while (idx-- > 0) {\n                    prev = next;\n                    next = next->next;\n                }\n                prev->next = next->next;\n                if ((void *)next == arr->uni.ptr) arr->uni.ptr = prev;\n                return next;\n            } else {\n                return ((yyjson_mut_val *)arr->uni.ptr);\n            }\n        }\n    }\n    return NULL;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove_first(\n    yyjson_mut_val *arr) {\n    if (yyjson_likely(yyjson_mut_is_arr(arr))) {\n        size_t len = unsafe_yyjson_get_len(arr);\n        if (len > 1) {\n            yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr);\n            yyjson_mut_val *next = prev->next;\n            prev->next = next->next;\n            unsafe_yyjson_set_len(arr, len - 1);\n            return next;\n        } else if (len == 1) {\n            yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr);\n            unsafe_yyjson_set_len(arr, 0);\n            return prev;\n        }\n    }\n    return NULL;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove_last(\n    yyjson_mut_val *arr) {\n    if (yyjson_likely(yyjson_mut_is_arr(arr))) {\n        size_t len = unsafe_yyjson_get_len(arr);\n        if (yyjson_likely(len > 1)) {\n            yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr);\n            yyjson_mut_val *next = prev->next;\n            unsafe_yyjson_set_len(arr, len - 1);\n            while (--len > 0) prev = prev->next;\n            prev->next = next;\n            next = (yyjson_mut_val *)arr->uni.ptr;\n            arr->uni.ptr = prev;\n            return next;\n        } else if (len == 1) {\n            yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr);\n            unsafe_yyjson_set_len(arr, 0);\n            return prev;\n        }\n    }\n    return NULL;\n}\n\nyyjson_api_inline bool yyjson_mut_arr_remove_range(yyjson_mut_val *arr,\n                                                   size_t _idx, size_t _len) {\n    if (yyjson_likely(yyjson_mut_is_arr(arr))) {\n        yyjson_mut_val *prev, *next;\n        bool tail_removed;\n        size_t len = unsafe_yyjson_get_len(arr);\n        if (yyjson_unlikely(_idx + _len > len)) return false;\n        if (yyjson_unlikely(_len == 0)) return true;\n        unsafe_yyjson_set_len(arr, len - _len);\n        if (yyjson_unlikely(len == _len)) return true;\n        tail_removed = (_idx + _len == len);\n        prev = ((yyjson_mut_val *)arr->uni.ptr);\n        while (_idx-- > 0) prev = prev->next;\n        next = prev->next;\n        while (_len-- > 0) next = next->next;\n        prev->next = next;\n        if (yyjson_unlikely(tail_removed)) arr->uni.ptr = prev;\n        return true;\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_mut_arr_clear(yyjson_mut_val *arr) {\n    if (yyjson_likely(yyjson_mut_is_arr(arr))) {\n        unsafe_yyjson_set_len(arr, 0);\n        return true;\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_mut_arr_rotate(yyjson_mut_val *arr,\n                                             size_t idx) {\n    if (yyjson_likely(yyjson_mut_is_arr(arr) &&\n                      unsafe_yyjson_get_len(arr) > idx)) {\n        yyjson_mut_val *val = (yyjson_mut_val *)arr->uni.ptr;\n        while (idx-- > 0) val = val->next;\n        arr->uni.ptr = (void *)val;\n        return true;\n    }\n    return false;\n}\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Array Modification Convenience API (Implementation)\n *============================================================================*/\n\nyyjson_api_inline bool yyjson_mut_arr_add_val(yyjson_mut_val *arr,\n                                              yyjson_mut_val *val) {\n    return yyjson_mut_arr_append(arr, val);\n}\n\nyyjson_api_inline bool yyjson_mut_arr_add_null(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *arr) {\n    if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) {\n        yyjson_mut_val *val = yyjson_mut_null(doc);\n        return yyjson_mut_arr_append(arr, val);\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_mut_arr_add_true(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *arr) {\n    if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) {\n        yyjson_mut_val *val = yyjson_mut_true(doc);\n        return yyjson_mut_arr_append(arr, val);\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_mut_arr_add_false(yyjson_mut_doc *doc,\n                                                yyjson_mut_val *arr) {\n    if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) {\n        yyjson_mut_val *val = yyjson_mut_false(doc);\n        return yyjson_mut_arr_append(arr, val);\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_mut_arr_add_bool(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *arr,\n                                               bool _val) {\n    if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) {\n        yyjson_mut_val *val = yyjson_mut_bool(doc, _val);\n        return yyjson_mut_arr_append(arr, val);\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_mut_arr_add_uint(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *arr,\n                                               uint64_t num) {\n    if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) {\n        yyjson_mut_val *val = yyjson_mut_uint(doc, num);\n        return yyjson_mut_arr_append(arr, val);\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_mut_arr_add_sint(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *arr,\n                                               int64_t num) {\n    if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) {\n        yyjson_mut_val *val = yyjson_mut_sint(doc, num);\n        return yyjson_mut_arr_append(arr, val);\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_mut_arr_add_int(yyjson_mut_doc *doc,\n                                              yyjson_mut_val *arr,\n                                              int64_t num) {\n    if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) {\n        yyjson_mut_val *val = yyjson_mut_sint(doc, num);\n        return yyjson_mut_arr_append(arr, val);\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_mut_arr_add_float(yyjson_mut_doc *doc,\n                                                yyjson_mut_val *arr,\n                                                float num) {\n    if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) {\n        yyjson_mut_val *val = yyjson_mut_float(doc, num);\n        return yyjson_mut_arr_append(arr, val);\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_mut_arr_add_double(yyjson_mut_doc *doc,\n                                                 yyjson_mut_val *arr,\n                                                 double num) {\n    if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) {\n        yyjson_mut_val *val = yyjson_mut_double(doc, num);\n        return yyjson_mut_arr_append(arr, val);\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_mut_arr_add_real(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *arr,\n                                               double num) {\n    if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) {\n        yyjson_mut_val *val = yyjson_mut_real(doc, num);\n        return yyjson_mut_arr_append(arr, val);\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_mut_arr_add_str(yyjson_mut_doc *doc,\n                                              yyjson_mut_val *arr,\n                                              const char *str) {\n    if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) {\n        yyjson_mut_val *val = yyjson_mut_str(doc, str);\n        return yyjson_mut_arr_append(arr, val);\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_mut_arr_add_strn(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *arr,\n                                               const char *str, size_t len) {\n    if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) {\n        yyjson_mut_val *val = yyjson_mut_strn(doc, str, len);\n        return yyjson_mut_arr_append(arr, val);\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_mut_arr_add_strcpy(yyjson_mut_doc *doc,\n                                                 yyjson_mut_val *arr,\n                                                 const char *str) {\n    if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) {\n        yyjson_mut_val *val = yyjson_mut_strcpy(doc, str);\n        return yyjson_mut_arr_append(arr, val);\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_mut_arr_add_strncpy(yyjson_mut_doc *doc,\n                                                  yyjson_mut_val *arr,\n                                                  const char *str, size_t len) {\n    if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) {\n        yyjson_mut_val *val = yyjson_mut_strncpy(doc, str, len);\n        return yyjson_mut_arr_append(arr, val);\n    }\n    return false;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_add_arr(yyjson_mut_doc *doc,\n                                                         yyjson_mut_val *arr) {\n    if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) {\n        yyjson_mut_val *val = yyjson_mut_arr(doc);\n        return yyjson_mut_arr_append(arr, val) ? val : NULL;\n    }\n    return NULL;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_arr_add_obj(yyjson_mut_doc *doc,\n                                                         yyjson_mut_val *arr) {\n    if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) {\n        yyjson_mut_val *val = yyjson_mut_obj(doc);\n        return yyjson_mut_arr_append(arr, val) ? val : NULL;\n    }\n    return NULL;\n}\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Object API (Implementation)\n *============================================================================*/\n\nyyjson_api_inline size_t yyjson_mut_obj_size(yyjson_mut_val *obj) {\n    return yyjson_mut_is_obj(obj) ? unsafe_yyjson_get_len(obj) : 0;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_get(yyjson_mut_val *obj,\n                                                     const char *key) {\n    return yyjson_mut_obj_getn(obj, key, key ? strlen(key) : 0);\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_getn(yyjson_mut_val *obj,\n                                                      const char *_key,\n                                                      size_t key_len) {\n    size_t len = yyjson_mut_obj_size(obj);\n    if (yyjson_likely(len && _key)) {\n        yyjson_mut_val *key = ((yyjson_mut_val *)obj->uni.ptr)->next->next;\n        while (len-- > 0) {\n            if (unsafe_yyjson_equals_strn(key, _key, key_len)) return key->next;\n            key = key->next->next;\n        }\n    }\n    return NULL;\n}\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Object Iterator API (Implementation)\n *============================================================================*/\n\nyyjson_api_inline bool yyjson_mut_obj_iter_init(yyjson_mut_val *obj,\n                                                yyjson_mut_obj_iter *iter) {\n    if (yyjson_likely(yyjson_mut_is_obj(obj) && iter)) {\n        iter->idx = 0;\n        iter->max = unsafe_yyjson_get_len(obj);\n        iter->cur = iter->max ? (yyjson_mut_val *)obj->uni.ptr : NULL;\n        iter->pre = NULL;\n        iter->obj = obj;\n        return true;\n    }\n    if (iter) memset(iter, 0, sizeof(yyjson_mut_obj_iter));\n    return false;\n}\n\nyyjson_api_inline yyjson_mut_obj_iter yyjson_mut_obj_iter_with(\n    yyjson_mut_val *obj) {\n    yyjson_mut_obj_iter iter;\n    yyjson_mut_obj_iter_init(obj, &iter);\n    return iter;\n}\n\nyyjson_api_inline bool yyjson_mut_obj_iter_has_next(yyjson_mut_obj_iter *iter) {\n    return iter ? iter->idx < iter->max : false;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_next(\n    yyjson_mut_obj_iter *iter) {\n    if (iter && iter->idx < iter->max) {\n        yyjson_mut_val *key = iter->cur;\n        iter->pre = key;\n        iter->cur = key->next->next;\n        iter->idx++;\n        return iter->cur;\n    }\n    return NULL;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_get_val(\n    yyjson_mut_val *key) {\n    return key ? key->next : NULL;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_remove(\n    yyjson_mut_obj_iter *iter) {\n    if (yyjson_likely(iter && 0 < iter->idx && iter->idx <= iter->max)) {\n        yyjson_mut_val *prev = iter->pre;\n        yyjson_mut_val *cur = iter->cur;\n        yyjson_mut_val *next = cur->next->next;\n        if (yyjson_unlikely(iter->idx == iter->max)) iter->obj->uni.ptr = prev;\n        iter->idx--;\n        iter->max--;\n        unsafe_yyjson_set_len(iter->obj, iter->max);\n        prev->next->next = next;\n        iter->cur = prev;\n        return cur->next;\n    }\n    return NULL;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_get(\n    yyjson_mut_obj_iter *iter, const char *key) {\n    return yyjson_mut_obj_iter_getn(iter, key, key ? strlen(key) : 0);\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_getn(\n    yyjson_mut_obj_iter *iter, const char *key, size_t key_len) {\n    if (iter && key) {\n        size_t idx = 0;\n        size_t max = iter->max;\n        yyjson_mut_val *pre, *cur = iter->cur;\n        while (idx++ < max) {\n            pre = cur;\n            cur = cur->next->next;\n            if (unsafe_yyjson_equals_strn(cur, key, key_len)) {\n                iter->idx += idx;\n                if (iter->idx > max) iter->idx -= max + 1;\n                iter->pre = pre;\n                iter->cur = cur;\n                return cur->next;\n            }\n        }\n    }\n    return NULL;\n}\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Object Creation API (Implementation)\n *============================================================================*/\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj(yyjson_mut_doc *doc) {\n    if (yyjson_likely(doc)) {\n        yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1);\n        if (yyjson_likely(val)) {\n            val->tag = YYJSON_TYPE_OBJ | YYJSON_SUBTYPE_NONE;\n            return val;\n        }\n    }\n    return NULL;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_with_str(yyjson_mut_doc *doc,\n                                                          const char **keys,\n                                                          const char **vals,\n                                                          size_t count) {\n    if (yyjson_likely(doc && ((count > 0 && keys && vals) || (count == 0)))) {\n        yyjson_mut_val *obj = unsafe_yyjson_mut_val(doc, 1 + count * 2);\n        if (yyjson_likely(obj)) {\n            obj->tag = ((uint64_t)count << YYJSON_TAG_BIT) | YYJSON_TYPE_OBJ;\n            if (count > 0) {\n                size_t i;\n                for (i = 0; i < count; i++) {\n                    yyjson_mut_val *key = obj + (i * 2 + 1);\n                    yyjson_mut_val *val = obj + (i * 2 + 2);\n                    uint64_t key_len = (uint64_t)strlen(keys[i]);\n                    uint64_t val_len = (uint64_t)strlen(vals[i]);\n                    key->tag = (key_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR;\n                    val->tag = (val_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR;\n                    key->uni.str = keys[i];\n                    val->uni.str = vals[i];\n                    key->next = val;\n                    val->next = val + 1;\n                }\n                obj[count * 2].next = obj + 1;\n                obj->uni.ptr = obj + (count * 2 - 1);\n            }\n            return obj;\n        }\n    }\n    return NULL;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_with_kv(yyjson_mut_doc *doc,\n                                                         const char **pairs,\n                                                         size_t count) {\n    if (yyjson_likely(doc && ((count > 0 && pairs) || (count == 0)))) {\n        yyjson_mut_val *obj = unsafe_yyjson_mut_val(doc, 1 + count * 2);\n        if (yyjson_likely(obj)) {\n            obj->tag = ((uint64_t)count << YYJSON_TAG_BIT) | YYJSON_TYPE_OBJ;\n            if (count > 0) {\n                size_t i;\n                for (i = 0; i < count; i++) {\n                    yyjson_mut_val *key = obj + (i * 2 + 1);\n                    yyjson_mut_val *val = obj + (i * 2 + 2);\n                    const char *key_str = pairs[i * 2 + 0];\n                    const char *val_str = pairs[i * 2 + 1];\n                    uint64_t key_len = (uint64_t)strlen(key_str);\n                    uint64_t val_len = (uint64_t)strlen(val_str);\n                    key->tag = (key_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR;\n                    val->tag = (val_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR;\n                    key->uni.str = key_str;\n                    val->uni.str = val_str;\n                    key->next = val;\n                    val->next = val + 1;\n                }\n                obj[count * 2].next = obj + 1;\n                obj->uni.ptr = obj + (count * 2 - 1);\n            }\n            return obj;\n        }\n    }\n    return NULL;\n}\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Object Modification API (Implementation)\n *============================================================================*/\n\nyyjson_api_inline void unsafe_yyjson_mut_obj_add(yyjson_mut_val *obj,\n                                                 yyjson_mut_val *key,\n                                                 yyjson_mut_val *val,\n                                                 size_t len) {\n    if (yyjson_likely(len)) {\n        yyjson_mut_val *prev_val = ((yyjson_mut_val *)obj->uni.ptr)->next;\n        yyjson_mut_val *next_key = prev_val->next;\n        prev_val->next = key;\n        val->next = next_key;\n    } else {\n        val->next = key;\n    }\n    key->next = val;\n    obj->uni.ptr = (void *)key;\n    unsafe_yyjson_set_len(obj, len + 1);\n}\n\nyyjson_api_inline yyjson_mut_val *unsafe_yyjson_mut_obj_remove(\n    yyjson_mut_val *obj, const char *key, size_t key_len) {\n    size_t obj_len = unsafe_yyjson_get_len(obj);\n    if (obj_len) {\n        yyjson_mut_val *pre_key = (yyjson_mut_val *)obj->uni.ptr;\n        yyjson_mut_val *cur_key = pre_key->next->next;\n        yyjson_mut_val *removed_item = NULL;\n        size_t i;\n        for (i = 0; i < obj_len; i++) {\n            if (unsafe_yyjson_equals_strn(cur_key, key, key_len)) {\n                if (!removed_item) removed_item = cur_key->next;\n                cur_key = cur_key->next->next;\n                pre_key->next->next = cur_key;\n                if (i + 1 == obj_len) obj->uni.ptr = pre_key;\n                i--;\n                obj_len--;\n            } else {\n                pre_key = cur_key;\n                cur_key = cur_key->next->next;\n            }\n        }\n        unsafe_yyjson_set_len(obj, obj_len);\n        return removed_item;\n    } else {\n        return NULL;\n    }\n}\n\nyyjson_api_inline bool unsafe_yyjson_mut_obj_replace(yyjson_mut_val *obj,\n                                                     yyjson_mut_val *key,\n                                                     yyjson_mut_val *val) {\n    size_t key_len = unsafe_yyjson_get_len(key);\n    size_t obj_len = unsafe_yyjson_get_len(obj);\n    if (obj_len) {\n        yyjson_mut_val *pre_key = (yyjson_mut_val *)obj->uni.ptr;\n        yyjson_mut_val *cur_key = pre_key->next->next;\n        size_t i;\n        for (i = 0; i < obj_len; i++) {\n            if (unsafe_yyjson_equals_strn(cur_key, key->uni.str, key_len)) {\n                cur_key->next->tag = val->tag;\n                cur_key->next->uni.u64 = val->uni.u64;\n                return true;\n            } else {\n                cur_key = cur_key->next->next;\n            }\n        }\n    }\n    return false;\n}\n\nyyjson_api_inline void unsafe_yyjson_mut_obj_rotate(yyjson_mut_val *obj,\n                                                    size_t idx) {\n    yyjson_mut_val *key = (yyjson_mut_val *)obj->uni.ptr;\n    while (idx-- > 0) key = key->next->next;\n    obj->uni.ptr = (void *)key;\n}\n\nyyjson_api_inline bool yyjson_mut_obj_add(yyjson_mut_val *obj,\n                                          yyjson_mut_val *key,\n                                          yyjson_mut_val *val) {\n    if (yyjson_likely(yyjson_mut_is_obj(obj) &&\n                      yyjson_mut_is_str(key) && val)) {\n        unsafe_yyjson_mut_obj_add(obj, key, val, unsafe_yyjson_get_len(obj));\n        return true;\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_mut_obj_put(yyjson_mut_val *obj,\n                                          yyjson_mut_val *key,\n                                          yyjson_mut_val *val) {\n    bool replaced = false;\n    size_t key_len;\n    yyjson_mut_obj_iter iter;\n    yyjson_mut_val *cur_key;\n    if (yyjson_unlikely(!yyjson_mut_is_obj(obj) ||\n                        !yyjson_mut_is_str(key))) return false;\n    key_len = unsafe_yyjson_get_len(key);\n    yyjson_mut_obj_iter_init(obj, &iter);\n    while ((cur_key = yyjson_mut_obj_iter_next(&iter)) != 0) {\n        if (unsafe_yyjson_equals_strn(cur_key, key->uni.str, key_len)) {\n            if (!replaced && val) {\n                replaced = true;\n                val->next = cur_key->next->next;\n                cur_key->next = val;\n            } else {\n                yyjson_mut_obj_iter_remove(&iter);\n            }\n        }\n    }\n    if (!replaced && val) unsafe_yyjson_mut_obj_add(obj, key, val, iter.max);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_mut_obj_insert(yyjson_mut_val *obj,\n                                             yyjson_mut_val *key,\n                                             yyjson_mut_val *val,\n                                             size_t idx) {\n    if (yyjson_likely(yyjson_mut_is_obj(obj) &&\n                      yyjson_mut_is_str(key) && val)) {\n        size_t len = unsafe_yyjson_get_len(obj);\n        if (yyjson_likely(len >= idx)) {\n            if (len > idx) {\n                void *ptr = obj->uni.ptr;\n                unsafe_yyjson_mut_obj_rotate(obj, idx);\n                unsafe_yyjson_mut_obj_add(obj, key, val, len);\n                obj->uni.ptr = ptr;\n            } else {\n                unsafe_yyjson_mut_obj_add(obj, key, val, len);\n            }\n            return true;\n        }\n    }\n    return false;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove(yyjson_mut_val *obj,\n    yyjson_mut_val *key) {\n    if (yyjson_likely(yyjson_mut_is_obj(obj) && yyjson_mut_is_str(key))) {\n        return unsafe_yyjson_mut_obj_remove(obj, key->uni.str,\n                                            unsafe_yyjson_get_len(key));\n    }\n    return NULL;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_key(\n    yyjson_mut_val *obj, const char *key) {\n    if (yyjson_likely(yyjson_mut_is_obj(obj) && key)) {\n        size_t key_len = strlen(key);\n        return unsafe_yyjson_mut_obj_remove(obj, key, key_len);\n    }\n    return NULL;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_keyn(\n    yyjson_mut_val *obj, const char *key, size_t key_len) {\n    if (yyjson_likely(yyjson_mut_is_obj(obj) && key)) {\n        return unsafe_yyjson_mut_obj_remove(obj, key, key_len);\n    }\n    return NULL;\n}\n\nyyjson_api_inline bool yyjson_mut_obj_clear(yyjson_mut_val *obj) {\n    if (yyjson_likely(yyjson_mut_is_obj(obj))) {\n        unsafe_yyjson_set_len(obj, 0);\n        return true;\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_mut_obj_replace(yyjson_mut_val *obj,\n                                              yyjson_mut_val *key,\n                                              yyjson_mut_val *val) {\n    if (yyjson_likely(yyjson_mut_is_obj(obj) &&\n                      yyjson_mut_is_str(key) && val)) {\n        return unsafe_yyjson_mut_obj_replace(obj, key, val);\n    }\n    return false;\n}\n\nyyjson_api_inline bool yyjson_mut_obj_rotate(yyjson_mut_val *obj,\n                                             size_t idx) {\n    if (yyjson_likely(yyjson_mut_is_obj(obj) &&\n                      unsafe_yyjson_get_len(obj) > idx)) {\n        unsafe_yyjson_mut_obj_rotate(obj, idx);\n        return true;\n    }\n    return false;\n}\n\n\n\n/*==============================================================================\n * MARK: - Mutable JSON Object Modification Convenience API (Implementation)\n *============================================================================*/\n\n#define yyjson_mut_obj_add_func(func) \\\n    if (yyjson_likely(doc && yyjson_mut_is_obj(obj) && _key)) { \\\n        yyjson_mut_val *key = unsafe_yyjson_mut_val(doc, 2); \\\n        if (yyjson_likely(key)) { \\\n            size_t len = unsafe_yyjson_get_len(obj); \\\n            yyjson_mut_val *val = key + 1; \\\n            size_t key_len = strlen(_key); \\\n            bool noesc = unsafe_yyjson_is_str_noesc(_key, key_len); \\\n            key->tag = YYJSON_TYPE_STR; \\\n            key->tag |= noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE; \\\n            key->tag |= (uint64_t)strlen(_key) << YYJSON_TAG_BIT; \\\n            key->uni.str = _key; \\\n            func \\\n            unsafe_yyjson_mut_obj_add(obj, key, val, len); \\\n            return true; \\\n        } \\\n    } \\\n    return false\n\nyyjson_api_inline bool yyjson_mut_obj_add_null(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *obj,\n                                               const char *_key) {\n    yyjson_mut_obj_add_func({ unsafe_yyjson_set_null(val); });\n}\n\nyyjson_api_inline bool yyjson_mut_obj_add_true(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *obj,\n                                               const char *_key) {\n    yyjson_mut_obj_add_func({ unsafe_yyjson_set_bool(val, true); });\n}\n\nyyjson_api_inline bool yyjson_mut_obj_add_false(yyjson_mut_doc *doc,\n                                                yyjson_mut_val *obj,\n                                                const char *_key) {\n    yyjson_mut_obj_add_func({ unsafe_yyjson_set_bool(val, false); });\n}\n\nyyjson_api_inline bool yyjson_mut_obj_add_bool(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *obj,\n                                               const char *_key,\n                                               bool _val) {\n    yyjson_mut_obj_add_func({ unsafe_yyjson_set_bool(val, _val); });\n}\n\nyyjson_api_inline bool yyjson_mut_obj_add_uint(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *obj,\n                                               const char *_key,\n                                               uint64_t _val) {\n    yyjson_mut_obj_add_func({ unsafe_yyjson_set_uint(val, _val); });\n}\n\nyyjson_api_inline bool yyjson_mut_obj_add_sint(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *obj,\n                                               const char *_key,\n                                               int64_t _val) {\n    yyjson_mut_obj_add_func({ unsafe_yyjson_set_sint(val, _val); });\n}\n\nyyjson_api_inline bool yyjson_mut_obj_add_int(yyjson_mut_doc *doc,\n                                              yyjson_mut_val *obj,\n                                              const char *_key,\n                                              int64_t _val) {\n    yyjson_mut_obj_add_func({ unsafe_yyjson_set_sint(val, _val); });\n}\n\nyyjson_api_inline bool yyjson_mut_obj_add_float(yyjson_mut_doc *doc,\n                                                yyjson_mut_val *obj,\n                                                const char *_key,\n                                                float _val) {\n    yyjson_mut_obj_add_func({ unsafe_yyjson_set_float(val, _val); });\n}\n\nyyjson_api_inline bool yyjson_mut_obj_add_double(yyjson_mut_doc *doc,\n                                                 yyjson_mut_val *obj,\n                                                 const char *_key,\n                                                 double _val) {\n    yyjson_mut_obj_add_func({ unsafe_yyjson_set_double(val, _val); });\n}\n\nyyjson_api_inline bool yyjson_mut_obj_add_real(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *obj,\n                                               const char *_key,\n                                               double _val) {\n    yyjson_mut_obj_add_func({ unsafe_yyjson_set_real(val, _val); });\n}\n\nyyjson_api_inline bool yyjson_mut_obj_add_str(yyjson_mut_doc *doc,\n                                              yyjson_mut_val *obj,\n                                              const char *_key,\n                                              const char *_val) {\n    if (yyjson_unlikely(!_val)) return false;\n    yyjson_mut_obj_add_func({\n        size_t val_len = strlen(_val);\n        bool val_noesc = unsafe_yyjson_is_str_noesc(_val, val_len);\n        val->tag = ((uint64_t)strlen(_val) << YYJSON_TAG_BIT) | YYJSON_TYPE_STR;\n        val->tag |= val_noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE;\n        val->uni.str = _val;\n    });\n}\n\nyyjson_api_inline bool yyjson_mut_obj_add_strn(yyjson_mut_doc *doc,\n                                               yyjson_mut_val *obj,\n                                               const char *_key,\n                                               const char *_val,\n                                               size_t _len) {\n    if (yyjson_unlikely(!_val)) return false;\n    yyjson_mut_obj_add_func({\n        val->tag = ((uint64_t)_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR;\n        val->uni.str = _val;\n    });\n}\n\nyyjson_api_inline bool yyjson_mut_obj_add_strcpy(yyjson_mut_doc *doc,\n                                                 yyjson_mut_val *obj,\n                                                 const char *_key,\n                                                 const char *_val) {\n    if (yyjson_unlikely(!_val)) return false;\n    yyjson_mut_obj_add_func({\n        size_t _len = strlen(_val);\n        val->uni.str = unsafe_yyjson_mut_strncpy(doc, _val, _len);\n        if (yyjson_unlikely(!val->uni.str)) return false;\n        val->tag = ((uint64_t)_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR;\n    });\n}\n\nyyjson_api_inline bool yyjson_mut_obj_add_strncpy(yyjson_mut_doc *doc,\n                                                  yyjson_mut_val *obj,\n                                                  const char *_key,\n                                                  const char *_val,\n                                                  size_t _len) {\n    if (yyjson_unlikely(!_val)) return false;\n    yyjson_mut_obj_add_func({\n        val->uni.str = unsafe_yyjson_mut_strncpy(doc, _val, _len);\n        if (yyjson_unlikely(!val->uni.str)) return false;\n        val->tag = ((uint64_t)_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR;\n    });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_add_arr(yyjson_mut_doc *doc,\n                                                         yyjson_mut_val *obj,\n                                                         const char *_key) {\n    yyjson_mut_val *key = yyjson_mut_str(doc, _key);\n    yyjson_mut_val *val = yyjson_mut_arr(doc);\n    return yyjson_mut_obj_add(obj, key, val) ? val : NULL;\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_add_obj(yyjson_mut_doc *doc,\n                                                         yyjson_mut_val *obj,\n                                                         const char *_key) {\n    yyjson_mut_val *key = yyjson_mut_str(doc, _key);\n    yyjson_mut_val *val = yyjson_mut_obj(doc);\n    return yyjson_mut_obj_add(obj, key, val) ? val : NULL;\n}\n\nyyjson_api_inline bool yyjson_mut_obj_add_val(yyjson_mut_doc *doc,\n                                              yyjson_mut_val *obj,\n                                              const char *_key,\n                                              yyjson_mut_val *_val) {\n    if (yyjson_unlikely(!_val)) return false;\n    yyjson_mut_obj_add_func({\n        val = _val;\n    });\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_str(yyjson_mut_val *obj,\n                                                            const char *key) {\n    return yyjson_mut_obj_remove_strn(obj, key, key ? strlen(key) : 0);\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_strn(\n    yyjson_mut_val *obj, const char *_key, size_t _len) {\n    if (yyjson_likely(yyjson_mut_is_obj(obj) && _key)) {\n        yyjson_mut_val *key;\n        yyjson_mut_obj_iter iter;\n        yyjson_mut_val *val_removed = NULL;\n        yyjson_mut_obj_iter_init(obj, &iter);\n        while ((key = yyjson_mut_obj_iter_next(&iter)) != NULL) {\n            if (unsafe_yyjson_equals_strn(key, _key, _len)) {\n                if (!val_removed) val_removed = key->next;\n                yyjson_mut_obj_iter_remove(&iter);\n            }\n        }\n        return val_removed;\n    }\n    return NULL;\n}\n\nyyjson_api_inline bool yyjson_mut_obj_rename_key(yyjson_mut_doc *doc,\n                                                 yyjson_mut_val *obj,\n                                                 const char *key,\n                                                 const char *new_key) {\n    if (!key || !new_key) return false;\n    return yyjson_mut_obj_rename_keyn(doc, obj, key, strlen(key),\n                                      new_key, strlen(new_key));\n}\n\nyyjson_api_inline bool yyjson_mut_obj_rename_keyn(yyjson_mut_doc *doc,\n                                                  yyjson_mut_val *obj,\n                                                  const char *key,\n                                                  size_t len,\n                                                  const char *new_key,\n                                                  size_t new_len) {\n    char *cpy_key = NULL;\n    yyjson_mut_val *old_key;\n    yyjson_mut_obj_iter iter;\n    if (!doc || !obj || !key || !new_key) return false;\n    yyjson_mut_obj_iter_init(obj, &iter);\n    while ((old_key = yyjson_mut_obj_iter_next(&iter))) {\n        if (unsafe_yyjson_equals_strn((void *)old_key, key, len)) {\n            if (!cpy_key) {\n                cpy_key = unsafe_yyjson_mut_strncpy(doc, new_key, new_len);\n                if (!cpy_key) return false;\n            }\n            yyjson_mut_set_strn(old_key, cpy_key, new_len);\n        }\n    }\n    return cpy_key != NULL;\n}\n\n\n\n#if !defined(YYJSON_DISABLE_UTILS) || !YYJSON_DISABLE_UTILS\n\n/*==============================================================================\n * MARK: - JSON Pointer API (Implementation)\n *============================================================================*/\n\n#define yyjson_ptr_set_err(_code, _msg) do { \\\n    if (err) { \\\n        err->code = YYJSON_PTR_ERR_##_code; \\\n        err->msg = _msg; \\\n        err->pos = 0; \\\n    } \\\n} while(false)\n\n/* require: val != NULL, *ptr == '/', len > 0 */\nyyjson_api yyjson_val *unsafe_yyjson_ptr_getx(yyjson_val *val,\n                                              const char *ptr, size_t len,\n                                              yyjson_ptr_err *err);\n\n/* require: val != NULL, *ptr == '/', len > 0 */\nyyjson_api yyjson_mut_val *unsafe_yyjson_mut_ptr_getx(yyjson_mut_val *val,\n                                                      const char *ptr,\n                                                      size_t len,\n                                                      yyjson_ptr_ctx *ctx,\n                                                      yyjson_ptr_err *err);\n\n/* require: val/new_val/doc != NULL, *ptr == '/', len > 0 */\nyyjson_api bool unsafe_yyjson_mut_ptr_putx(yyjson_mut_val *val,\n                                           const char *ptr, size_t len,\n                                           yyjson_mut_val *new_val,\n                                           yyjson_mut_doc *doc,\n                                           bool create_parent, bool insert_new,\n                                           yyjson_ptr_ctx *ctx,\n                                           yyjson_ptr_err *err);\n\n/* require: val/err != NULL, *ptr == '/', len > 0 */\nyyjson_api yyjson_mut_val *unsafe_yyjson_mut_ptr_replacex(\n    yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val,\n    yyjson_ptr_ctx *ctx, yyjson_ptr_err *err);\n\n/* require: val/err != NULL, *ptr == '/', len > 0 */\nyyjson_api yyjson_mut_val *unsafe_yyjson_mut_ptr_removex(yyjson_mut_val *val,\n                                                         const char *ptr,\n                                                         size_t len,\n                                                         yyjson_ptr_ctx *ctx,\n                                                         yyjson_ptr_err *err);\n\nyyjson_api_inline yyjson_val *yyjson_doc_ptr_get(yyjson_doc *doc,\n                                                 const char *ptr) {\n    if (yyjson_unlikely(!ptr)) return NULL;\n    return yyjson_doc_ptr_getn(doc, ptr, strlen(ptr));\n}\n\nyyjson_api_inline yyjson_val *yyjson_doc_ptr_getn(yyjson_doc *doc,\n                                                  const char *ptr, size_t len) {\n    return yyjson_doc_ptr_getx(doc, ptr, len, NULL);\n}\n\nyyjson_api_inline yyjson_val *yyjson_doc_ptr_getx(yyjson_doc *doc,\n                                                  const char *ptr, size_t len,\n                                                  yyjson_ptr_err *err) {\n    yyjson_ptr_set_err(NONE, NULL);\n    if (yyjson_unlikely(!doc || !ptr)) {\n        yyjson_ptr_set_err(PARAMETER, \"input parameter is NULL\");\n        return NULL;\n    }\n    if (yyjson_unlikely(!doc->root)) {\n        yyjson_ptr_set_err(NULL_ROOT, \"document's root is NULL\");\n        return NULL;\n    }\n    if (yyjson_unlikely(len == 0)) {\n        return doc->root;\n    }\n    if (yyjson_unlikely(*ptr != '/')) {\n        yyjson_ptr_set_err(SYNTAX, \"no prefix '/'\");\n        return NULL;\n    }\n    return unsafe_yyjson_ptr_getx(doc->root, ptr, len, err);\n}\n\nyyjson_api_inline yyjson_val *yyjson_ptr_get(yyjson_val *val,\n                                             const char *ptr) {\n    if (yyjson_unlikely(!ptr)) return NULL;\n    return yyjson_ptr_getn(val, ptr, strlen(ptr));\n}\n\nyyjson_api_inline yyjson_val *yyjson_ptr_getn(yyjson_val *val,\n                                              const char *ptr, size_t len) {\n    return yyjson_ptr_getx(val, ptr, len, NULL);\n}\n\nyyjson_api_inline yyjson_val *yyjson_ptr_getx(yyjson_val *val,\n                                              const char *ptr, size_t len,\n                                              yyjson_ptr_err *err) {\n    yyjson_ptr_set_err(NONE, NULL);\n    if (yyjson_unlikely(!val || !ptr)) {\n        yyjson_ptr_set_err(PARAMETER, \"input parameter is NULL\");\n        return NULL;\n    }\n    if (yyjson_unlikely(len == 0)) {\n        return val;\n    }\n    if (yyjson_unlikely(*ptr != '/')) {\n        yyjson_ptr_set_err(SYNTAX, \"no prefix '/'\");\n        return NULL;\n    }\n    return unsafe_yyjson_ptr_getx(val, ptr, len, err);\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_get(yyjson_mut_doc *doc,\n                                                         const char *ptr) {\n    if (!ptr) return NULL;\n    return yyjson_mut_doc_ptr_getn(doc, ptr, strlen(ptr));\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_getn(yyjson_mut_doc *doc,\n                                                          const char *ptr,\n                                                          size_t len) {\n    return yyjson_mut_doc_ptr_getx(doc, ptr, len, NULL, NULL);\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_getx(yyjson_mut_doc *doc,\n                                                          const char *ptr,\n                                                          size_t len,\n                                                          yyjson_ptr_ctx *ctx,\n                                                          yyjson_ptr_err *err) {\n    yyjson_ptr_set_err(NONE, NULL);\n    if (ctx) memset(ctx, 0, sizeof(*ctx));\n\n    if (yyjson_unlikely(!doc || !ptr)) {\n        yyjson_ptr_set_err(PARAMETER, \"input parameter is NULL\");\n        return NULL;\n    }\n    if (yyjson_unlikely(!doc->root)) {\n        yyjson_ptr_set_err(NULL_ROOT, \"document's root is NULL\");\n        return NULL;\n    }\n    if (yyjson_unlikely(len == 0)) {\n        return doc->root;\n    }\n    if (yyjson_unlikely(*ptr != '/')) {\n        yyjson_ptr_set_err(SYNTAX, \"no prefix '/'\");\n        return NULL;\n    }\n    return unsafe_yyjson_mut_ptr_getx(doc->root, ptr, len, ctx, err);\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_get(yyjson_mut_val *val,\n                                                     const char *ptr) {\n    if (!ptr) return NULL;\n    return yyjson_mut_ptr_getn(val, ptr, strlen(ptr));\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_getn(yyjson_mut_val *val,\n                                                      const char *ptr,\n                                                      size_t len) {\n    return yyjson_mut_ptr_getx(val, ptr, len, NULL, NULL);\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_getx(yyjson_mut_val *val,\n                                                      const char *ptr,\n                                                      size_t len,\n                                                      yyjson_ptr_ctx *ctx,\n                                                      yyjson_ptr_err *err) {\n    yyjson_ptr_set_err(NONE, NULL);\n    if (ctx) memset(ctx, 0, sizeof(*ctx));\n\n    if (yyjson_unlikely(!val || !ptr)) {\n        yyjson_ptr_set_err(PARAMETER, \"input parameter is NULL\");\n        return NULL;\n    }\n    if (yyjson_unlikely(len == 0)) {\n        return val;\n    }\n    if (yyjson_unlikely(*ptr != '/')) {\n        yyjson_ptr_set_err(SYNTAX, \"no prefix '/'\");\n        return NULL;\n    }\n    return unsafe_yyjson_mut_ptr_getx(val, ptr, len, ctx, err);\n}\n\nyyjson_api_inline bool yyjson_mut_doc_ptr_add(yyjson_mut_doc *doc,\n                                              const char *ptr,\n                                              yyjson_mut_val *new_val) {\n    if (yyjson_unlikely(!ptr)) return false;\n    return yyjson_mut_doc_ptr_addn(doc, ptr, strlen(ptr), new_val);\n}\n\nyyjson_api_inline bool yyjson_mut_doc_ptr_addn(yyjson_mut_doc *doc,\n                                               const char *ptr,\n                                               size_t len,\n                                               yyjson_mut_val *new_val) {\n    return yyjson_mut_doc_ptr_addx(doc, ptr, len, new_val, true, NULL, NULL);\n}\n\nyyjson_api_inline bool yyjson_mut_doc_ptr_addx(yyjson_mut_doc *doc,\n                                               const char *ptr, size_t len,\n                                               yyjson_mut_val *new_val,\n                                               bool create_parent,\n                                               yyjson_ptr_ctx *ctx,\n                                               yyjson_ptr_err *err) {\n    yyjson_ptr_set_err(NONE, NULL);\n    if (ctx) memset(ctx, 0, sizeof(*ctx));\n\n    if (yyjson_unlikely(!doc || !ptr || !new_val)) {\n        yyjson_ptr_set_err(PARAMETER, \"input parameter is NULL\");\n        return false;\n    }\n    if (yyjson_unlikely(len == 0)) {\n        if (doc->root) {\n            yyjson_ptr_set_err(SET_ROOT, \"cannot set document's root\");\n            return false;\n        } else {\n            doc->root = new_val;\n            return true;\n        }\n    }\n    if (yyjson_unlikely(*ptr != '/')) {\n        yyjson_ptr_set_err(SYNTAX, \"no prefix '/'\");\n        return false;\n    }\n    if (yyjson_unlikely(!doc->root && !create_parent)) {\n        yyjson_ptr_set_err(NULL_ROOT, \"document's root is NULL\");\n        return false;\n    }\n    if (yyjson_unlikely(!doc->root)) {\n        yyjson_mut_val *root = yyjson_mut_obj(doc);\n        if (yyjson_unlikely(!root)) {\n            yyjson_ptr_set_err(MEMORY_ALLOCATION, \"failed to create value\");\n            return false;\n        }\n        if (unsafe_yyjson_mut_ptr_putx(root, ptr, len, new_val, doc,\n                                       create_parent, true, ctx, err)) {\n            doc->root = root;\n            return true;\n        }\n        return false;\n    }\n    return unsafe_yyjson_mut_ptr_putx(doc->root, ptr, len, new_val, doc,\n                                      create_parent, true, ctx, err);\n}\n\nyyjson_api_inline bool yyjson_mut_ptr_add(yyjson_mut_val *val,\n                                          const char *ptr,\n                                          yyjson_mut_val *new_val,\n                                          yyjson_mut_doc *doc) {\n    if (yyjson_unlikely(!ptr)) return false;\n    return yyjson_mut_ptr_addn(val, ptr, strlen(ptr), new_val, doc);\n}\n\nyyjson_api_inline bool yyjson_mut_ptr_addn(yyjson_mut_val *val,\n                                           const char *ptr, size_t len,\n                                           yyjson_mut_val *new_val,\n                                           yyjson_mut_doc *doc) {\n    return yyjson_mut_ptr_addx(val, ptr, len, new_val, doc, true, NULL, NULL);\n}\n\nyyjson_api_inline bool yyjson_mut_ptr_addx(yyjson_mut_val *val,\n                                           const char *ptr, size_t len,\n                                           yyjson_mut_val *new_val,\n                                           yyjson_mut_doc *doc,\n                                           bool create_parent,\n                                           yyjson_ptr_ctx *ctx,\n                                           yyjson_ptr_err *err) {\n    yyjson_ptr_set_err(NONE, NULL);\n    if (ctx) memset(ctx, 0, sizeof(*ctx));\n\n    if (yyjson_unlikely(!val || !ptr || !new_val || !doc)) {\n        yyjson_ptr_set_err(PARAMETER, \"input parameter is NULL\");\n        return false;\n    }\n    if (yyjson_unlikely(len == 0)) {\n        yyjson_ptr_set_err(SET_ROOT, \"cannot set root\");\n        return false;\n    }\n    if (yyjson_unlikely(*ptr != '/')) {\n        yyjson_ptr_set_err(SYNTAX, \"no prefix '/'\");\n        return false;\n    }\n    return unsafe_yyjson_mut_ptr_putx(val, ptr, len, new_val,\n                                       doc, create_parent, true, ctx, err);\n}\n\nyyjson_api_inline bool yyjson_mut_doc_ptr_set(yyjson_mut_doc *doc,\n                                              const char *ptr,\n                                              yyjson_mut_val *new_val) {\n    if (yyjson_unlikely(!ptr)) return false;\n    return yyjson_mut_doc_ptr_setn(doc, ptr, strlen(ptr), new_val);\n}\n\nyyjson_api_inline bool yyjson_mut_doc_ptr_setn(yyjson_mut_doc *doc,\n                                               const char *ptr, size_t len,\n                                               yyjson_mut_val *new_val) {\n    return yyjson_mut_doc_ptr_setx(doc, ptr, len, new_val, true, NULL, NULL);\n}\n\nyyjson_api_inline bool yyjson_mut_doc_ptr_setx(yyjson_mut_doc *doc,\n                                               const char *ptr, size_t len,\n                                               yyjson_mut_val *new_val,\n                                               bool create_parent,\n                                               yyjson_ptr_ctx *ctx,\n                                               yyjson_ptr_err *err) {\n    yyjson_ptr_set_err(NONE, NULL);\n    if (ctx) memset(ctx, 0, sizeof(*ctx));\n\n    if (yyjson_unlikely(!doc || !ptr)) {\n        yyjson_ptr_set_err(PARAMETER, \"input parameter is NULL\");\n        return false;\n    }\n    if (yyjson_unlikely(len == 0)) {\n        if (ctx) ctx->old = doc->root;\n        doc->root = new_val;\n        return true;\n    }\n    if (yyjson_unlikely(*ptr != '/')) {\n        yyjson_ptr_set_err(SYNTAX, \"no prefix '/'\");\n        return false;\n    }\n    if (!new_val) {\n        if (!doc->root) {\n            yyjson_ptr_set_err(RESOLVE, \"JSON pointer cannot be resolved\");\n            return false;\n        }\n        return !!unsafe_yyjson_mut_ptr_removex(doc->root, ptr, len, ctx, err);\n    }\n    if (yyjson_unlikely(!doc->root && !create_parent)) {\n        yyjson_ptr_set_err(NULL_ROOT, \"document's root is NULL\");\n        return false;\n    }\n    if (yyjson_unlikely(!doc->root)) {\n        yyjson_mut_val *root = yyjson_mut_obj(doc);\n        if (yyjson_unlikely(!root)) {\n            yyjson_ptr_set_err(MEMORY_ALLOCATION, \"failed to create value\");\n            return false;\n        }\n        if (unsafe_yyjson_mut_ptr_putx(root, ptr, len, new_val, doc,\n                                       create_parent, false, ctx, err)) {\n            doc->root = root;\n            return true;\n        }\n        return false;\n    }\n    return unsafe_yyjson_mut_ptr_putx(doc->root, ptr, len, new_val, doc,\n                                      create_parent, false, ctx, err);\n}\n\nyyjson_api_inline bool yyjson_mut_ptr_set(yyjson_mut_val *val,\n                                          const char *ptr,\n                                          yyjson_mut_val *new_val,\n                                          yyjson_mut_doc *doc) {\n    if (yyjson_unlikely(!ptr)) return false;\n    return yyjson_mut_ptr_setn(val, ptr, strlen(ptr), new_val, doc);\n}\n\nyyjson_api_inline bool yyjson_mut_ptr_setn(yyjson_mut_val *val,\n                                           const char *ptr, size_t len,\n                                           yyjson_mut_val *new_val,\n                                           yyjson_mut_doc *doc) {\n    return yyjson_mut_ptr_setx(val, ptr, len, new_val, doc, true, NULL, NULL);\n}\n\nyyjson_api_inline bool yyjson_mut_ptr_setx(yyjson_mut_val *val,\n                                           const char *ptr, size_t len,\n                                           yyjson_mut_val *new_val,\n                                           yyjson_mut_doc *doc,\n                                           bool create_parent,\n                                           yyjson_ptr_ctx *ctx,\n                                           yyjson_ptr_err *err) {\n    yyjson_ptr_set_err(NONE, NULL);\n    if (ctx) memset(ctx, 0, sizeof(*ctx));\n\n    if (yyjson_unlikely(!val || !ptr || !doc)) {\n        yyjson_ptr_set_err(PARAMETER, \"input parameter is NULL\");\n        return false;\n    }\n    if (yyjson_unlikely(len == 0)) {\n        yyjson_ptr_set_err(SET_ROOT, \"cannot set root\");\n        return false;\n    }\n    if (yyjson_unlikely(*ptr != '/')) {\n        yyjson_ptr_set_err(SYNTAX, \"no prefix '/'\");\n        return false;\n    }\n    if (!new_val) {\n        return !!unsafe_yyjson_mut_ptr_removex(val, ptr, len, ctx, err);\n    }\n    return unsafe_yyjson_mut_ptr_putx(val, ptr, len, new_val, doc,\n                                      create_parent, false, ctx, err);\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replace(\n    yyjson_mut_doc *doc, const char *ptr, yyjson_mut_val *new_val) {\n    if (!ptr) return NULL;\n    return yyjson_mut_doc_ptr_replacen(doc, ptr, strlen(ptr), new_val);\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replacen(\n    yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val) {\n    return yyjson_mut_doc_ptr_replacex(doc, ptr, len, new_val, NULL, NULL);\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replacex(\n    yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val,\n    yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) {\n\n    yyjson_ptr_set_err(NONE, NULL);\n    if (ctx) memset(ctx, 0, sizeof(*ctx));\n\n    if (yyjson_unlikely(!doc || !ptr || !new_val)) {\n        yyjson_ptr_set_err(PARAMETER, \"input parameter is NULL\");\n        return NULL;\n    }\n    if (yyjson_unlikely(len == 0)) {\n        yyjson_mut_val *root = doc->root;\n        if (yyjson_unlikely(!root)) {\n            yyjson_ptr_set_err(RESOLVE, \"JSON pointer cannot be resolved\");\n            return NULL;\n        }\n        if (ctx) ctx->old = root;\n        doc->root = new_val;\n        return root;\n    }\n    if (yyjson_unlikely(!doc->root)) {\n        yyjson_ptr_set_err(NULL_ROOT, \"document's root is NULL\");\n        return NULL;\n    }\n    if (yyjson_unlikely(*ptr != '/')) {\n        yyjson_ptr_set_err(SYNTAX, \"no prefix '/'\");\n        return NULL;\n    }\n    return unsafe_yyjson_mut_ptr_replacex(doc->root, ptr, len, new_val,\n                                          ctx, err);\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replace(\n    yyjson_mut_val *val, const char *ptr, yyjson_mut_val *new_val) {\n    if (!ptr) return NULL;\n    return yyjson_mut_ptr_replacen(val, ptr, strlen(ptr), new_val);\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replacen(\n    yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val) {\n    return yyjson_mut_ptr_replacex(val, ptr, len, new_val, NULL, NULL);\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replacex(\n    yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val,\n    yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) {\n\n    yyjson_ptr_set_err(NONE, NULL);\n    if (ctx) memset(ctx, 0, sizeof(*ctx));\n\n    if (yyjson_unlikely(!val || !ptr || !new_val)) {\n        yyjson_ptr_set_err(PARAMETER, \"input parameter is NULL\");\n        return NULL;\n    }\n    if (yyjson_unlikely(len == 0)) {\n        yyjson_ptr_set_err(SET_ROOT, \"cannot set root\");\n        return NULL;\n    }\n    if (yyjson_unlikely(*ptr != '/')) {\n        yyjson_ptr_set_err(SYNTAX, \"no prefix '/'\");\n        return NULL;\n    }\n    return unsafe_yyjson_mut_ptr_replacex(val, ptr, len, new_val, ctx, err);\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_remove(\n    yyjson_mut_doc *doc, const char *ptr) {\n    if (!ptr) return NULL;\n    return yyjson_mut_doc_ptr_removen(doc, ptr, strlen(ptr));\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_removen(\n    yyjson_mut_doc *doc, const char *ptr, size_t len) {\n    return yyjson_mut_doc_ptr_removex(doc, ptr, len, NULL, NULL);\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_removex(\n    yyjson_mut_doc *doc, const char *ptr, size_t len,\n    yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) {\n\n    yyjson_ptr_set_err(NONE, NULL);\n    if (ctx) memset(ctx, 0, sizeof(*ctx));\n\n    if (yyjson_unlikely(!doc || !ptr)) {\n        yyjson_ptr_set_err(PARAMETER, \"input parameter is NULL\");\n        return NULL;\n    }\n    if (yyjson_unlikely(!doc->root)) {\n        yyjson_ptr_set_err(NULL_ROOT, \"document's root is NULL\");\n        return NULL;\n    }\n    if (yyjson_unlikely(len == 0)) {\n        yyjson_mut_val *root = doc->root;\n        if (ctx) ctx->old = root;\n        doc->root = NULL;\n        return root;\n    }\n    if (yyjson_unlikely(*ptr != '/')) {\n        yyjson_ptr_set_err(SYNTAX, \"no prefix '/'\");\n        return NULL;\n    }\n    return unsafe_yyjson_mut_ptr_removex(doc->root, ptr, len, ctx, err);\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_remove(yyjson_mut_val *val,\n                                                        const char *ptr) {\n    if (!ptr) return NULL;\n    return yyjson_mut_ptr_removen(val, ptr, strlen(ptr));\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_removen(yyjson_mut_val *val,\n                                                         const char *ptr,\n                                                         size_t len) {\n    return yyjson_mut_ptr_removex(val, ptr, len, NULL, NULL);\n}\n\nyyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_removex(yyjson_mut_val *val,\n                                                         const char *ptr,\n                                                         size_t len,\n                                                         yyjson_ptr_ctx *ctx,\n                                                         yyjson_ptr_err *err) {\n    yyjson_ptr_set_err(NONE, NULL);\n    if (ctx) memset(ctx, 0, sizeof(*ctx));\n\n    if (yyjson_unlikely(!val || !ptr)) {\n        yyjson_ptr_set_err(PARAMETER, \"input parameter is NULL\");\n        return NULL;\n    }\n    if (yyjson_unlikely(len == 0)) {\n        yyjson_ptr_set_err(SET_ROOT, \"cannot set root\");\n        return NULL;\n    }\n    if (yyjson_unlikely(*ptr != '/')) {\n        yyjson_ptr_set_err(SYNTAX, \"no prefix '/'\");\n        return NULL;\n    }\n    return unsafe_yyjson_mut_ptr_removex(val, ptr, len, ctx, err);\n}\n\nyyjson_api_inline bool yyjson_ptr_ctx_append(yyjson_ptr_ctx *ctx,\n                                             yyjson_mut_val *key,\n                                             yyjson_mut_val *val) {\n    yyjson_mut_val *ctn, *pre_key, *pre_val, *cur_key, *cur_val;\n    if (!ctx || !ctx->ctn || !val) return false;\n    ctn = ctx->ctn;\n\n    if (yyjson_mut_is_obj(ctn)) {\n        if (!key) return false;\n        key->next = val;\n        pre_key = ctx->pre;\n        if (unsafe_yyjson_get_len(ctn) == 0) {\n            val->next = key;\n            ctn->uni.ptr = key;\n            ctx->pre = key;\n        } else if (!pre_key) {\n            pre_key = (yyjson_mut_val *)ctn->uni.ptr;\n            pre_val = pre_key->next;\n            val->next = pre_val->next;\n            pre_val->next = key;\n            ctn->uni.ptr = key;\n            ctx->pre = pre_key;\n        } else {\n            cur_key = pre_key->next->next;\n            cur_val = cur_key->next;\n            val->next = cur_val->next;\n            cur_val->next = key;\n            if (ctn->uni.ptr == cur_key) ctn->uni.ptr = key;\n            ctx->pre = cur_key;\n        }\n    } else {\n        pre_val = ctx->pre;\n        if (unsafe_yyjson_get_len(ctn) == 0) {\n            val->next = val;\n            ctn->uni.ptr = val;\n            ctx->pre = val;\n        } else if (!pre_val) {\n            pre_val = (yyjson_mut_val *)ctn->uni.ptr;\n            val->next = pre_val->next;\n            pre_val->next = val;\n            ctn->uni.ptr = val;\n            ctx->pre = pre_val;\n        } else {\n            cur_val = pre_val->next;\n            val->next = cur_val->next;\n            cur_val->next = val;\n            if (ctn->uni.ptr == cur_val) ctn->uni.ptr = val;\n            ctx->pre = cur_val;\n        }\n    }\n    unsafe_yyjson_inc_len(ctn);\n    return true;\n}\n\nyyjson_api_inline bool yyjson_ptr_ctx_replace(yyjson_ptr_ctx *ctx,\n                                              yyjson_mut_val *val) {\n    yyjson_mut_val *ctn, *pre_key, *cur_key, *pre_val, *cur_val;\n    if (!ctx || !ctx->ctn || !ctx->pre || !val) return false;\n    ctn = ctx->ctn;\n    if (yyjson_mut_is_obj(ctn)) {\n        pre_key = ctx->pre;\n        pre_val = pre_key->next;\n        cur_key = pre_val->next;\n        cur_val = cur_key->next;\n        /* replace current value */\n        cur_key->next = val;\n        val->next = cur_val->next;\n        ctx->old = cur_val;\n    } else {\n        pre_val = ctx->pre;\n        cur_val = pre_val->next;\n        /* replace current value */\n        if (pre_val != cur_val) {\n            val->next = cur_val->next;\n            pre_val->next = val;\n            if (ctn->uni.ptr == cur_val) ctn->uni.ptr = val;\n        } else {\n            val->next = val;\n            ctn->uni.ptr = val;\n            ctx->pre = val;\n        }\n        ctx->old = cur_val;\n    }\n    return true;\n}\n\nyyjson_api_inline bool yyjson_ptr_ctx_remove(yyjson_ptr_ctx *ctx) {\n    yyjson_mut_val *ctn, *pre_key, *pre_val, *cur_key, *cur_val;\n    size_t len;\n    if (!ctx || !ctx->ctn || !ctx->pre) return false;\n    ctn = ctx->ctn;\n    if (yyjson_mut_is_obj(ctn)) {\n        pre_key = ctx->pre;\n        pre_val = pre_key->next;\n        cur_key = pre_val->next;\n        cur_val = cur_key->next;\n        /* remove current key-value */\n        pre_val->next = cur_val->next;\n        if (ctn->uni.ptr == cur_key) ctn->uni.ptr = pre_key;\n        ctx->pre = NULL;\n        ctx->old = cur_val;\n    } else {\n        pre_val = ctx->pre;\n        cur_val = pre_val->next;\n        /* remove current key-value */\n        pre_val->next = cur_val->next;\n        if (ctn->uni.ptr == cur_val) ctn->uni.ptr = pre_val;\n        ctx->pre = NULL;\n        ctx->old = cur_val;\n    }\n    len = unsafe_yyjson_get_len(ctn) - 1;\n    if (len == 0) ctn->uni.ptr = NULL;\n    unsafe_yyjson_set_len(ctn, len);\n    return true;\n}\n\n#undef yyjson_ptr_set_err\n\n\n\n/*==============================================================================\n * MARK: - JSON Value at Pointer API (Implementation)\n *============================================================================*/\n\n/**\n Set provided `value` if the JSON Pointer (RFC 6901) exists and is type bool.\n Returns true if value at `ptr` exists and is the correct type, otherwise false.\n */\nyyjson_api_inline bool yyjson_ptr_get_bool(\n    yyjson_val *root, const char *ptr, bool *value) {\n    yyjson_val *val = yyjson_ptr_get(root, ptr);\n    if (value && yyjson_is_bool(val)) {\n        *value = unsafe_yyjson_get_bool(val);\n        return true;\n    } else {\n        return false;\n    }\n}\n\n/**\n Set provided `value` if the JSON Pointer (RFC 6901) exists and is an integer\n that fits in `uint64_t`. Returns true if successful, otherwise false.\n */\nyyjson_api_inline bool yyjson_ptr_get_uint(\n    yyjson_val *root, const char *ptr, uint64_t *value) {\n    yyjson_val *val = yyjson_ptr_get(root, ptr);\n    if (value && val) {\n        uint64_t ret = val->uni.u64;\n        if (unsafe_yyjson_is_uint(val) ||\n            (unsafe_yyjson_is_sint(val) && !(ret >> 63))) {\n            *value = ret;\n            return true;\n        }\n    }\n    return false;\n}\n\n/**\n Set provided `value` if the JSON Pointer (RFC 6901) exists and is an integer\n that fits in `int64_t`. Returns true if successful, otherwise false.\n */\nyyjson_api_inline bool yyjson_ptr_get_sint(\n    yyjson_val *root, const char *ptr, int64_t *value) {\n    yyjson_val *val = yyjson_ptr_get(root, ptr);\n    if (value && val) {\n        int64_t ret = val->uni.i64;\n        if (unsafe_yyjson_is_sint(val) ||\n            (unsafe_yyjson_is_uint(val) && ret >= 0)) {\n            *value = ret;\n            return true;\n        }\n    }\n    return false;\n}\n\n/**\n Set provided `value` if the JSON Pointer (RFC 6901) exists and is type real.\n Returns true if value at `ptr` exists and is the correct type, otherwise false.\n */\nyyjson_api_inline bool yyjson_ptr_get_real(\n    yyjson_val *root, const char *ptr, double *value) {\n    yyjson_val *val = yyjson_ptr_get(root, ptr);\n    if (value && yyjson_is_real(val)) {\n        *value = unsafe_yyjson_get_real(val);\n        return true;\n    } else {\n        return false;\n    }\n}\n\n/**\n Set provided `value` if the JSON Pointer (RFC 6901) exists and is type sint,\n uint or real.\n Returns true if value at `ptr` exists and is the correct type, otherwise false.\n */\nyyjson_api_inline bool yyjson_ptr_get_num(\n    yyjson_val *root, const char *ptr, double *value) {\n    yyjson_val *val = yyjson_ptr_get(root, ptr);\n    if (value && yyjson_is_num(val)) {\n        *value = unsafe_yyjson_get_num(val);\n        return true;\n    } else {\n        return false;\n    }\n}\n\n/**\n Set provided `value` if the JSON Pointer (RFC 6901) exists and is type string.\n Returns true if value at `ptr` exists and is the correct type, otherwise false.\n */\nyyjson_api_inline bool yyjson_ptr_get_str(\n    yyjson_val *root, const char *ptr, const char **value) {\n    yyjson_val *val = yyjson_ptr_get(root, ptr);\n    if (value && yyjson_is_str(val)) {\n        *value = unsafe_yyjson_get_str(val);\n        return true;\n    } else {\n        return false;\n    }\n}\n\n\n\n/*==============================================================================\n * MARK: - Deprecated\n *============================================================================*/\n\n/** @deprecated renamed to `yyjson_doc_ptr_get` */\nyyjson_deprecated(\"renamed to yyjson_doc_ptr_get\")\nyyjson_api_inline yyjson_val *yyjson_doc_get_pointer(yyjson_doc *doc,\n                                                     const char *ptr) {\n    return yyjson_doc_ptr_get(doc, ptr);\n}\n\n/** @deprecated renamed to `yyjson_doc_ptr_getn` */\nyyjson_deprecated(\"renamed to yyjson_doc_ptr_getn\")\nyyjson_api_inline yyjson_val *yyjson_doc_get_pointern(yyjson_doc *doc,\n                                                      const char *ptr,\n                                                      size_t len) {\n    return yyjson_doc_ptr_getn(doc, ptr, len);\n}\n\n/** @deprecated renamed to `yyjson_mut_doc_ptr_get` */\nyyjson_deprecated(\"renamed to yyjson_mut_doc_ptr_get\")\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_get_pointer(\n    yyjson_mut_doc *doc, const char *ptr) {\n    return yyjson_mut_doc_ptr_get(doc, ptr);\n}\n\n/** @deprecated renamed to `yyjson_mut_doc_ptr_getn` */\nyyjson_deprecated(\"renamed to yyjson_mut_doc_ptr_getn\")\nyyjson_api_inline yyjson_mut_val *yyjson_mut_doc_get_pointern(\n    yyjson_mut_doc *doc, const char *ptr, size_t len) {\n    return yyjson_mut_doc_ptr_getn(doc, ptr, len);\n}\n\n/** @deprecated renamed to `yyjson_ptr_get` */\nyyjson_deprecated(\"renamed to yyjson_ptr_get\")\nyyjson_api_inline yyjson_val *yyjson_get_pointer(yyjson_val *val,\n                                                 const char *ptr) {\n    return yyjson_ptr_get(val, ptr);\n}\n\n/** @deprecated renamed to `yyjson_ptr_getn` */\nyyjson_deprecated(\"renamed to yyjson_ptr_getn\")\nyyjson_api_inline yyjson_val *yyjson_get_pointern(yyjson_val *val,\n                                                  const char *ptr,\n                                                  size_t len) {\n    return yyjson_ptr_getn(val, ptr, len);\n}\n\n/** @deprecated renamed to `yyjson_mut_ptr_get` */\nyyjson_deprecated(\"renamed to yyjson_mut_ptr_get\")\nyyjson_api_inline yyjson_mut_val *yyjson_mut_get_pointer(yyjson_mut_val *val,\n                                                         const char *ptr) {\n    return yyjson_mut_ptr_get(val, ptr);\n}\n\n/** @deprecated renamed to `yyjson_mut_ptr_getn` */\nyyjson_deprecated(\"renamed to yyjson_mut_ptr_getn\")\nyyjson_api_inline yyjson_mut_val *yyjson_mut_get_pointern(yyjson_mut_val *val,\n                                                          const char *ptr,\n                                                          size_t len) {\n    return yyjson_mut_ptr_getn(val, ptr, len);\n}\n\n/** @deprecated renamed to `yyjson_mut_ptr_getn` */\nyyjson_deprecated(\"renamed to unsafe_yyjson_ptr_getn\")\nyyjson_api_inline yyjson_val *unsafe_yyjson_get_pointer(yyjson_val *val,\n                                                        const char *ptr,\n                                                        size_t len) {\n    yyjson_ptr_err err;\n    return unsafe_yyjson_ptr_getx(val, ptr, len, &err);\n}\n\n/** @deprecated renamed to `unsafe_yyjson_mut_ptr_getx` */\nyyjson_deprecated(\"renamed to unsafe_yyjson_mut_ptr_getx\")\nyyjson_api_inline yyjson_mut_val *unsafe_yyjson_mut_get_pointer(\n    yyjson_mut_val *val, const char *ptr, size_t len) {\n    yyjson_ptr_err err;\n    return unsafe_yyjson_mut_ptr_getx(val, ptr, len, NULL, &err);\n}\n\n#endif /* YYJSON_DISABLE_UTILS */\n\n\n\n/*==============================================================================\n * MARK: - Compiler Hint End\n *============================================================================*/\n\n#if defined(__clang__)\n#   pragma clang diagnostic pop\n#elif defined(__GNUC__)\n#   if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)\n#       pragma GCC diagnostic pop\n#   endif\n#elif defined(_MSC_VER)\n#   pragma warning(pop)\n#endif /* warning suppress end */\n\n#ifdef __cplusplus\n}\n#endif /* extern \"C\" end */\n\n#endif /* YYJSON_H */\n"
  },
  {
    "path": "src/common/FFPlatform.h",
    "content": "#pragma once\n\n#include \"common/FFstrbuf.h\"\n#include \"common/FFlist.h\"\n\ntypedef struct FFPlatformSysinfo\n{\n    FFstrbuf name;\n    FFstrbuf release;\n    FFstrbuf version;\n    FFstrbuf architecture;\n    uint32_t pageSize;\n} FFPlatformSysinfo;\n\ntypedef struct FFPlatform\n{\n    FFstrbuf homeDir;  // Trailing slash included\n    FFstrbuf cacheDir; // Trailing slash included\n    FFlist configDirs; // List of FFstrbuf, trailing slash included\n    FFlist dataDirs;   // List of FFstrbuf, trailing slash included\n    FFstrbuf exePath;  // The real path of current exe (empty if unavailable)\n    FFstrbuf cwd;      // Trailing slash included\n\n    uint32_t pid;\n    #ifndef _WIN32\n    uint32_t uid;\n    #else\n    FFstrbuf sid;\n    #endif\n    FFstrbuf userName;\n    FFstrbuf fullUserName;\n    FFstrbuf hostName;\n    FFstrbuf userShell;\n\n    FFPlatformSysinfo sysinfo;\n} FFPlatform;\n\nvoid ffPlatformInit(FFPlatform* platform);\nvoid ffPlatformDestroy(FFPlatform* platform);\n"
  },
  {
    "path": "src/common/FFcheckmacros.h",
    "content": "#pragma once\n\n#ifdef _MSC_VER\n    #include <sal.h>\n#endif\n\n#if defined(__has_attribute) && __has_attribute(__warn_unused_result__)\n    #define FF_C_NODISCARD __attribute__((__warn_unused_result__))\n#elif defined(_MSC_VER)\n    #define FF_C_NODISCARD _Check_return_\n#else\n    #define FF_C_NODISCARD\n#endif\n\n#if defined(__has_attribute) && __has_attribute(__format__)\n    #define FF_C_PRINTF(formatStrIndex, argsStartIndex) __attribute__((__format__ (printf, formatStrIndex, argsStartIndex)))\n#else\n    #define FF_C_PRINTF(formatStrIndex, argsStartIndex)\n#endif\n\n#if defined(__has_attribute) && __has_attribute(__format__)\n    #define FF_C_SCANF(formatStrIndex, argsStartIndex) __attribute__((__format__ (scanf, formatStrIndex, argsStartIndex)))\n#else\n    #define FF_C_SCANF(formatStrIndex, argsStartIndex)\n#endif\n\n#if defined(__has_attribute) && __has_attribute(__nonnull__)\n    #define FF_C_NONNULL(argIndex, ...) __attribute__((__nonnull__(argIndex, ##__VA_ARGS__)))\n#else\n    #define FF_C_NONNULL(argIndex, ...)\n#endif\n\n#if defined(__has_attribute) && __has_attribute(__returns_nonnull__)\n    #define FF_C_RETURNS_NONNULL __attribute__((__returns_nonnull__))\n#else\n    #define FF_C_RETURNS_NONNULL\n#endif\n"
  },
  {
    "path": "src/common/FFlist.h",
    "content": "#pragma once\n\n#include \"FFcheckmacros.h\"\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <assert.h>\n#include <stdlib.h>\n\n#define FF_LIST_DEFAULT_ALLOC 16\n\ntypedef struct FFlist\n{\n    uint8_t* data;\n    uint32_t elementSize;\n    uint32_t length;\n    uint32_t capacity;\n} FFlist;\n\nvoid* ffListAdd(FFlist* list);\n\n// Removes the first element, and copy its value to `*result`\nbool ffListShift(FFlist* list, void* result);\n// Removes the last element, and copy its value to `*result`\nbool ffListPop(FFlist* list, void* result);\n\nstatic inline void ffListInit(FFlist* list, uint32_t elementSize)\n{\n    assert(elementSize > 0);\n    list->elementSize = elementSize;\n    list->capacity = 0;\n    list->length = 0;\n    list->data = NULL;\n}\n\nstatic inline void ffListInitA(FFlist* list, uint32_t elementSize, uint32_t capacity)\n{\n    ffListInit(list, elementSize);\n    list->capacity = capacity;\n    list->data = __builtin_expect(capacity == 0, 0) ? NULL : (uint8_t*) malloc((size_t)list->capacity * list->elementSize);\n}\n\nstatic inline FFlist ffListCreate(uint32_t elementSize)\n{\n    FFlist result;\n    ffListInit(&result, elementSize);\n    return result;\n}\n\nstatic inline void* ffListGet(const FFlist* list, uint32_t index)\n{\n    assert(list->capacity > index);\n    return list->data + (index * list->elementSize);\n}\n\nFF_C_NODISCARD static inline uint32_t ffListFirstIndexComp(const FFlist* list, void* compElement, bool(*compFunc)(const void*, const void*))\n{\n    for(uint32_t i = 0; i < list->length; i++)\n    {\n        if(compFunc(ffListGet(list, i), compElement))\n            return i;\n    }\n\n    return list->length;\n}\n\nstatic inline bool ffListContains(const FFlist* list, void* compElement, bool(*compFunc)(const void*, const void*))\n{\n    return ffListFirstIndexComp(list, compElement, compFunc) != list->length;\n}\n\nstatic inline void ffListSort(FFlist* list, int(*compar)(const void*, const void*))\n{\n    qsort(list->data, list->length, list->elementSize, compar);\n}\n\n// Move the contents of `src` into `list`, and left `src` empty\nstatic inline void ffListInitMove(FFlist* list, FFlist* src)\n{\n    if (src)\n    {\n        list->elementSize = src->elementSize;\n        list->capacity = src->capacity;\n        list->length = src->length;\n        list->data = src->data;\n        ffListInit(src, list->elementSize);\n    }\n    else\n    {\n        ffListInit(list, 0);\n    }\n}\n\nstatic inline void ffListDestroy(FFlist* list)\n{\n    if (!list->data) return;\n\n    //Avoid free-after-use. These 3 assignments are cheap so don't remove them\n    list->capacity = list->length = 0;\n    free(list->data);\n    list->data = NULL;\n}\n\nstatic inline void ffListClear(FFlist* list)\n{\n    list->length = 0;\n}\n\nstatic inline void ffListReserve(FFlist* list, uint32_t newCapacity)\n{\n    if (__builtin_expect(newCapacity <= list->capacity, false))\n        return;\n\n    list->data = (uint8_t*) realloc(list->data, (size_t) newCapacity * list->elementSize);\n    list->capacity = newCapacity;\n}\n\n#define FF_LIST_FOR_EACH(itemType, itemVarName, listVar) \\\n    assert(sizeof(itemType) == (listVar).elementSize); \\\n    for(itemType* itemVarName = (itemType*)(listVar).data; \\\n        itemVarName - (itemType*)(listVar).data < (intptr_t)(listVar).length; \\\n        ++itemVarName)\n\n#define FF_LIST_AUTO_DESTROY FFlist __attribute__((__cleanup__(ffListDestroy)))\n\n#define FF_LIST_GET(itemType, listVar, index) \\\n    ({ \\\n        assert(sizeof(itemType) == (listVar).elementSize); \\\n        assert((listVar).capacity > (index)); \\\n        (itemType*)(listVar).data + (index); \\\n    })\n\n#define FF_LIST_ADD(itemType, listVar) \\\n    ({ \\\n        assert(sizeof(itemType) == (listVar).elementSize); \\\n        (itemType*) ffListAdd(&(listVar)); \\\n    })\n\n#define FF_LIST_FIRST(itemType, listVar) FF_LIST_GET(itemType, listVar, 0)\n#define FF_LIST_LAST(itemType, listVar) \\\n    ({ \\\n        assert((listVar).length > 0); \\\n        FF_LIST_GET(itemType, listVar, ((listVar).length - 1)); \\\n    })\n"
  },
  {
    "path": "src/common/FFstrbuf.h",
    "content": "#pragma once\n\n#include \"FFcheckmacros.h\"\n\n#include <stdint.h>\n#include <stdarg.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <assert.h>\n\n#ifdef FF_USE_SYSTEM_YYJSON\n    #include <yyjson.h>\n#else\n    #include \"3rdparty/yyjson/yyjson.h\"\n#endif\n\n#ifdef _WIN32\n    // #include <shlwapi.h>\n    __stdcall char* StrStrIA(const char* lpFirst, const char* lpSrch);\n    #define strcasestr StrStrIA\n#endif\n\n#define FASTFETCH_STRBUF_DEFAULT_ALLOC 32\n\n// static string (allocated == 0), chars points to a string literal\n// dynamic string (allocated > 0), chars points to a heap allocated buffer\ntypedef struct FFstrbuf\n{\n    uint32_t allocated;\n    uint32_t length;\n    char* chars;\n} FFstrbuf;\n\nstatic inline void ffStrbufInit(FFstrbuf* strbuf);\nvoid ffStrbufInitA(FFstrbuf* strbuf, uint32_t allocate);\nvoid ffStrbufInitVF(FFstrbuf* strbuf, const char* format, va_list arguments);\nvoid ffStrbufInitMoveNS(FFstrbuf* strbuf, uint32_t length, char* heapStr);\n\nvoid ffStrbufEnsureFree(FFstrbuf* strbuf, uint32_t free);\nvoid ffStrbufEnsureFixedLengthFree(FFstrbuf* strbuf, uint32_t free);\n\nvoid ffStrbufClear(FFstrbuf* strbuf);\n\nstatic inline void ffStrbufAppend(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict value);\nvoid ffStrbufAppendC(FFstrbuf* strbuf, char c);\nvoid ffStrbufAppendNC(FFstrbuf* strbuf, uint32_t num, char c);\nvoid ffStrbufAppendNS(FFstrbuf* strbuf, uint32_t length, const char* value);\nvoid ffStrbufAppendTransformS(FFstrbuf* strbuf, const char* value, int(*transformFunc)(int));\nFF_C_PRINTF(2, 3) void ffStrbufAppendF(FFstrbuf* strbuf, const char* format, ...);\nvoid ffStrbufAppendVF(FFstrbuf* strbuf, const char* format, va_list arguments);\nconst char* ffStrbufAppendSUntilC(FFstrbuf* strbuf, const char* value, char until);\n\nvoid ffStrbufPrependNS(FFstrbuf* strbuf, uint32_t length, const char* value);\nvoid ffStrbufPrependC(FFstrbuf* strbuf, char c);\n\nvoid ffStrbufInsertNC(FFstrbuf* strbuf, uint32_t index, uint32_t num, char c);\n\n// Clear the content of strbuf and set new value\n// NOTE: Unlike ffStrbufAppend*, ffStrbufSet* functions may NOT reserve extra space\nvoid ffStrbufSet(FFstrbuf* strbuf, const FFstrbuf* value);\nvoid ffStrbufSetNS(FFstrbuf* strbuf, uint32_t length, const char* value);\nFF_C_PRINTF(2, 3) void ffStrbufSetF(FFstrbuf* strbuf, const char* format, ...);\n\nvoid ffStrbufTrimLeft(FFstrbuf* strbuf, char c);\nvoid ffStrbufTrimRight(FFstrbuf* strbuf, char c);\nvoid ffStrbufTrimLeftSpace(FFstrbuf* strbuf);\nvoid ffStrbufTrimRightSpace(FFstrbuf* strbuf);\n\nbool ffStrbufRemoveSubstr(FFstrbuf* strbuf, uint32_t startIndex, uint32_t endIndex);\nvoid ffStrbufRemoveS(FFstrbuf* strbuf, const char* str);\nvoid ffStrbufRemoveStrings(FFstrbuf* strbuf, uint32_t numStrings, const char* strings[]);\n\nFF_C_NODISCARD uint32_t ffStrbufNextIndexC(const FFstrbuf* strbuf, uint32_t start, char c);\nFF_C_NODISCARD uint32_t ffStrbufNextIndexS(const FFstrbuf* strbuf, uint32_t start, const char* str);\n\nFF_C_NODISCARD uint32_t ffStrbufPreviousIndexC(const FFstrbuf* strbuf, uint32_t start, char c);\n\nvoid ffStrbufReplaceAllC(FFstrbuf* strbuf, char find, char replace);\n\n// Returns true if the strbuf is modified\nbool ffStrbufSubstrBefore(FFstrbuf* strbuf, uint32_t index);\nbool ffStrbufSubstrAfter(FFstrbuf* strbuf, uint32_t index); // Not including the index\nbool ffStrbufSubstrAfterFirstC(FFstrbuf* strbuf, char c);\nbool ffStrbufSubstrAfterFirstS(FFstrbuf* strbuf, const char* str);\nbool ffStrbufSubstrAfterLastC(FFstrbuf* strbuf, char c);\nbool ffStrbufSubstr(FFstrbuf* strbuf, uint32_t start, uint32_t end);\n\nFF_C_NODISCARD uint32_t ffStrbufCountC(const FFstrbuf* strbuf, char c);\n\nbool ffStrbufRemoveIgnCaseEndS(FFstrbuf* strbuf, const char* end);\n\nbool ffStrbufEnsureEndsWithC(FFstrbuf* strbuf, char c);\n\nvoid ffStrbufWriteTo(const FFstrbuf* strbuf, FILE* file);\nvoid ffStrbufPutTo(const FFstrbuf* strbuf, FILE* file);\n\nFF_C_NODISCARD double ffStrbufToDouble(const FFstrbuf* strbuf, double defaultValue);\nFF_C_NODISCARD int64_t ffStrbufToSInt(const FFstrbuf* strbuf, int64_t defaultValue);\nFF_C_NODISCARD uint64_t ffStrbufToUInt(const FFstrbuf* strbuf, uint64_t defaultValue);\n\nvoid ffStrbufUpperCase(FFstrbuf* strbuf);\nvoid ffStrbufLowerCase(FFstrbuf* strbuf);\n\n// Function alters the buffer to extract lines or delimited segments (replaces the delimiter with '\\0')\n// so that buffer MUST be heap allocated (NOT a static string)\n// `lineptr` must be `NULL` and `n` MUST be `0` for the first call\n// Caller MUST NOT free `*lineptr`\nbool ffStrbufGetdelim(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer);\nvoid ffStrbufGetdelimRestore(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer);\n\n/**\n * @brief Read a line from a FFstrbuf.\n *\n * @details Behaves like getline(3) but reads from a FFstrbuf.\n *\n * @param[in,out] lineptr The pointer to a pointer that will be set to the start of the line\n                          (points to buffer's internal memory address to avoid memory allocation and copy).\n                          MUST NOT be freed by the caller, unlike `getline(3)`.\n *                        MUST be NULL for the first call.\n * @param[in,out] n The pointer to the size of the buffer of lineptr.\n                    MUST be 0 for the first call.\n * @param[in] buffer The buffer to read from.\n                     MUST be heap allocated (NOT a static string).\n *\n * @return true if a line has been read, false if the end of the buffer has been reached.\n */\nstatic inline bool ffStrbufGetline(char** lineptr, size_t* n, FFstrbuf* buffer)\n{\n    return ffStrbufGetdelim(lineptr, n, '\\n', buffer);\n}\n/**\n * @brief Restore the end of a line that was modified by ffStrbufGetline.\n * @warning This function should be called before breaking an ffStrbufGetline loop if `buffer` will be used later.\n */\nstatic inline void ffStrbufGetlineRestore(char** lineptr, size_t* n, FFstrbuf* buffer)\n{\n    ffStrbufGetdelimRestore(lineptr, n, '\\n', buffer);\n}\nbool ffStrbufRemoveDupWhitespaces(FFstrbuf* strbuf);\nbool ffStrbufMatchSeparatedNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator);\nbool ffStrbufMatchSeparatedIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator);\nbool ffStrbufSeparatedContainNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator);\nbool ffStrbufSeparatedContainIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator);\n\nint ffStrbufAppendUtf32CodePoint(FFstrbuf* strbuf, uint32_t codepoint);\n\nvoid ffStrbufAppendSInt(FFstrbuf* strbuf, int64_t value);\nvoid ffStrbufAppendUInt(FFstrbuf* strbuf, uint64_t value);\n// Appends a double value to the string buffer with the specified precision (0~15).\n// if `precision < 0`, let yyjson decide the precision\nvoid ffStrbufAppendDouble(FFstrbuf* strbuf, double value, int8_t precision, bool trailingZeros);\n\nFF_C_NODISCARD static inline FFstrbuf ffStrbufCreateA(uint32_t allocate)\n{\n    FFstrbuf strbuf;\n    ffStrbufInitA(&strbuf, allocate);\n    return strbuf;\n}\n\nstatic inline void ffStrbufInitCopy(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict src)\n{\n    if (src->allocated == 0) // static string\n        *strbuf = *src;\n    else\n    {\n        ffStrbufInitA(strbuf, src->allocated);\n        ffStrbufAppend(strbuf, src);\n    }\n}\n\nFF_C_NODISCARD static inline FFstrbuf ffStrbufCreateCopy(const FFstrbuf* src)\n{\n    FFstrbuf strbuf;\n    ffStrbufInitCopy(&strbuf, src);\n    return strbuf;\n}\n\n// Move the content of `src` into `strbuf`, and left `src` empty\nstatic inline void ffStrbufInitMove(FFstrbuf* strbuf, FFstrbuf* src)\n{\n    if (src)\n    {\n        *strbuf = *src;\n        ffStrbufInit(src);\n    }\n    else\n        ffStrbufInit(strbuf);\n}\n\nFF_C_NODISCARD static inline FFstrbuf ffStrbufCreateMove(FFstrbuf* src)\n{\n    FFstrbuf strbuf;\n    ffStrbufInitMove(&strbuf, src);\n    return strbuf;\n}\n\nFF_C_NODISCARD static inline FFstrbuf ffStrbufCreateVF(const char* format, va_list arguments)\n{\n    FFstrbuf strbuf;\n    ffStrbufInitVF(&strbuf, format, arguments);\n    return strbuf;\n}\n\nFF_C_PRINTF(2, 3)\nstatic inline void ffStrbufInitF(FFstrbuf* strbuf, const char* format, ...)\n{\n    va_list arguments;\n    va_start(arguments, format);\n    ffStrbufInitVF(strbuf, format, arguments);\n    va_end(arguments);\n}\n\nFF_C_PRINTF(1, 2)\nFF_C_NODISCARD static inline FFstrbuf ffStrbufCreateF(const char* format, ...)\n{\n    FFstrbuf strbuf;\n\n    va_list arguments;\n    va_start(arguments, format);\n    ffStrbufInitVF(&strbuf, format, arguments);\n    va_end(arguments);\n\n    return strbuf;\n}\n\nstatic inline void ffStrbufInitMoveS(FFstrbuf* strbuf, char* heapStr)\n{\n    ffStrbufInitMoveNS(strbuf, (uint32_t) strlen(heapStr), heapStr);\n}\n\nstatic inline void ffStrbufDestroy(FFstrbuf* strbuf)\n{\n    if(strbuf->allocated > 0)\n        free(strbuf->chars);\n\n    ffStrbufInit(strbuf);\n}\n\nFF_C_NODISCARD static inline uint32_t ffStrbufGetFree(const FFstrbuf* strbuf)\n{\n    assert(strbuf != NULL);\n    if(strbuf->allocated == 0)\n        return 0;\n\n    return strbuf->allocated - strbuf->length - 1; // - 1 for the null byte\n}\n\nstatic inline void ffStrbufRecalculateLength(FFstrbuf* strbuf)\n{\n    strbuf->length = (uint32_t) strlen(strbuf->chars);\n}\n\nstatic inline void ffStrbufSetS(FFstrbuf* strbuf, const char* value)\n{\n    assert(strbuf != NULL);\n\n    if (value == NULL)\n        ffStrbufClear(strbuf);\n    else\n        ffStrbufSetNS(strbuf, (uint32_t) strlen(value), value);\n}\n\nstatic inline bool ffStrbufSetJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal)\n{\n    assert(strbuf != NULL);\n\n    if (yyjson_is_str(jsonVal))\n    {\n        ffStrbufSetNS(strbuf, (uint32_t) unsafe_yyjson_get_len(jsonVal), unsafe_yyjson_get_str(jsonVal));\n        return true;\n    }\n\n    ffStrbufClear(strbuf);\n    return false;\n}\n\nstatic inline void ffStrbufAppendS(FFstrbuf* strbuf, const char* value)\n{\n    if(value == NULL)\n        return;\n    ffStrbufAppendNS(strbuf, (uint32_t) strlen(value), value);\n}\n\nstatic inline bool ffStrbufAppendJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal)\n{\n    if (yyjson_is_str(jsonVal))\n    {\n        ffStrbufAppendNS(strbuf, (uint32_t) unsafe_yyjson_get_len(jsonVal), unsafe_yyjson_get_str(jsonVal));\n        return true;\n    }\n    return false;\n}\n\nstatic inline void ffStrbufInit(FFstrbuf* strbuf)\n{\n    extern char* CHAR_NULL_PTR;\n    strbuf->allocated = strbuf->length = 0;\n    strbuf->chars = CHAR_NULL_PTR;\n}\n\nFF_C_NODISCARD static inline FFstrbuf ffStrbufCreate(void)\n{\n    FFstrbuf strbuf;\n    ffStrbufInit(&strbuf);\n    return strbuf;\n}\n\nstatic inline void ffStrbufInitStatic(FFstrbuf* strbuf, const char* str)\n{\n    ffStrbufInit(strbuf);\n    if (!str) return;\n\n    strbuf->allocated = 0;\n    strbuf->length = (uint32_t) strlen(str);\n    strbuf->chars = (char*) str;\n}\n\nFF_C_NODISCARD static inline FFstrbuf ffStrbufCreateStatic(const char* str)\n{\n    FFstrbuf strbuf;\n    ffStrbufInitStatic(&strbuf, str);\n    return strbuf;\n}\n\nstatic inline void ffStrbufSetStatic(FFstrbuf* strbuf, const char* value)\n{\n    if(strbuf->allocated > 0)\n        free(strbuf->chars);\n\n    if(value != NULL)\n        ffStrbufInitStatic(strbuf, value);\n    else\n        ffStrbufInit(strbuf);\n}\n\nstatic inline void ffStrbufInitNS(FFstrbuf* strbuf, uint32_t length, const char* str)\n{\n    ffStrbufInit(strbuf);\n    ffStrbufAppendNS(strbuf, length, str);\n}\n\nFF_C_NODISCARD static inline FFstrbuf ffStrbufCreateNS(uint32_t length, const char* str)\n{\n    FFstrbuf strbuf;\n    ffStrbufInitNS(&strbuf, length, str);\n    return strbuf;\n}\n\nstatic inline bool ffStrbufInitJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal)\n{\n    ffStrbufInit(strbuf);\n    return ffStrbufAppendJsonVal(strbuf, jsonVal);\n}\n\nstatic inline void ffStrbufInitS(FFstrbuf* strbuf, const char* str)\n{\n    ffStrbufInit(strbuf);\n    ffStrbufAppendS(strbuf, str);\n}\n\nFF_C_NODISCARD static inline FFstrbuf ffStrbufCreateS(const char* str)\n{\n    FFstrbuf strbuf;\n    ffStrbufInitS(&strbuf, str);\n    return strbuf;\n}\n\nstatic inline void ffStrbufAppend(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict value)\n{\n    assert(value != strbuf);\n    if(value == NULL)\n        return;\n    ffStrbufAppendNS(strbuf, value->length, value->chars);\n}\n\nstatic inline void ffStrbufPrepend(FFstrbuf* strbuf, FFstrbuf* value)\n{\n    if(value == NULL)\n        return;\n    ffStrbufPrependNS(strbuf, value->length, value->chars);\n}\n\nstatic inline void ffStrbufPrependS(FFstrbuf* strbuf, const char* value)\n{\n    if(value == NULL)\n        return;\n    ffStrbufPrependNS(strbuf, (uint32_t) strlen(value), value);\n}\n\nstatic inline FF_C_NODISCARD int ffStrbufComp(const FFstrbuf* strbuf, const FFstrbuf* comp)\n{\n    uint32_t length = strbuf->length > comp->length ? comp->length : strbuf->length;\n    return memcmp(strbuf->chars, comp->chars, length + 1);\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufEqual(const FFstrbuf* strbuf, const FFstrbuf* comp)\n{\n    return ffStrbufComp(strbuf, comp) == 0;\n}\n\nstatic inline FF_C_NODISCARD int ffStrbufCompS(const FFstrbuf* strbuf, const char* comp)\n{\n    return strcmp(strbuf->chars, comp);\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufEqualS(const FFstrbuf* strbuf, const char* comp)\n{\n    return ffStrbufCompS(strbuf, comp) == 0;\n}\n\nstatic inline FF_C_NODISCARD int ffStrbufIgnCaseCompS(const FFstrbuf* strbuf, const char* comp)\n{\n    return strcasecmp(strbuf->chars, comp);\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufIgnCaseEqualS(const FFstrbuf* strbuf, const char* comp)\n{\n    return ffStrbufIgnCaseCompS(strbuf, comp) == 0;\n}\n\nstatic inline FF_C_NODISCARD int ffStrbufIgnCaseComp(const FFstrbuf* strbuf, const FFstrbuf* comp)\n{\n    return ffStrbufIgnCaseCompS(strbuf, comp->chars);\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufIgnCaseEqual(const FFstrbuf* strbuf, const FFstrbuf* comp)\n{\n    return ffStrbufIgnCaseComp(strbuf, comp) == 0;\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufContainC(const FFstrbuf* strbuf, char c)\n{\n    return memchr(strbuf->chars, c, strbuf->length) != NULL;\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufContainS(const FFstrbuf* strbuf, const char* str)\n{\n    return strstr(strbuf->chars, str) != NULL;\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufContain(const FFstrbuf* strbuf, const FFstrbuf* str)\n{\n    return ffStrbufContainS(strbuf, str->chars);\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufContainIgnCaseS(const FFstrbuf* strbuf, const char* str)\n{\n    return strcasestr(strbuf->chars, str) != NULL;\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufContainIgnCase(const FFstrbuf* strbuf, const FFstrbuf* str)\n{\n    return ffStrbufContainIgnCaseS(strbuf, str->chars);\n}\n\nstatic inline FF_C_NODISCARD uint32_t ffStrbufFirstIndexC(const FFstrbuf* strbuf, char c)\n{\n    return ffStrbufNextIndexC(strbuf, 0, c);\n}\n\nstatic inline FF_C_NODISCARD uint32_t ffStrbufFirstIndex(const FFstrbuf* strbuf, const FFstrbuf* searched)\n{\n    return ffStrbufNextIndexS(strbuf, 0, searched->chars);\n}\n\nstatic inline FF_C_NODISCARD uint32_t ffStrbufFirstIndexS(const FFstrbuf* strbuf, const char* str)\n{\n    return ffStrbufNextIndexS(strbuf, 0, str);\n}\n\nstatic inline FF_C_NODISCARD uint32_t ffStrbufLastIndexC(const FFstrbuf* strbuf, char c)\n{\n    if(strbuf->length == 0)\n        return 0;\n\n    return ffStrbufPreviousIndexC(strbuf, strbuf->length - 1, c);\n}\n\nstatic inline bool ffStrbufSubstrBeforeFirstC(FFstrbuf* strbuf, char c)\n{\n    return ffStrbufSubstrBefore(strbuf, ffStrbufFirstIndexC(strbuf, c));\n}\n\nstatic inline bool ffStrbufSubstrBeforeLastC(FFstrbuf* strbuf, char c)\n{\n    return ffStrbufSubstrBefore(strbuf, ffStrbufLastIndexC(strbuf, c));\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufStartsWithC(const FFstrbuf* strbuf, char c)\n{\n    return strbuf->chars[0] == c;\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufStartsWithSN(const FFstrbuf* strbuf, const char* start, uint32_t length)\n{\n    if (length > strbuf->length)\n        return false;\n\n    return memcmp(strbuf->chars, start, length) == 0;\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufStartsWithS(const FFstrbuf* strbuf, const char* start)\n{\n    return ffStrbufStartsWithSN(strbuf, start, (uint32_t) strlen(start));\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufStartsWith(const FFstrbuf* strbuf, const FFstrbuf* start)\n{\n    return ffStrbufStartsWithSN(strbuf, start->chars, start->length);\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufStartsWithIgnCaseNS(const FFstrbuf* strbuf, uint32_t length, const char* start)\n{\n    if(length > strbuf->length)\n        return false;\n    return strncasecmp(strbuf->chars, start, length) == 0;\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufStartsWithIgnCaseS(const FFstrbuf* strbuf, const char* start)\n{\n    return ffStrbufStartsWithIgnCaseNS(strbuf, (uint32_t) strlen(start), start);\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufStartsWithIgnCase(const FFstrbuf* strbuf, const FFstrbuf* start)\n{\n    return ffStrbufStartsWithIgnCaseNS(strbuf, start->length, start->chars);\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufEndsWithC(const FFstrbuf* strbuf, char c)\n{\n    return strbuf->length == 0 ? false :\n        strbuf->chars[strbuf->length - 1] == c;\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufEndsWithNS(const FFstrbuf* strbuf, uint32_t endLength, const char* end)\n{\n    if(endLength > strbuf->length)\n        return false;\n\n    return memcmp(strbuf->chars + strbuf->length - endLength, end, endLength) == 0;\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufEndsWithS(const FFstrbuf* strbuf, const char* end)\n{\n    return ffStrbufEndsWithNS(strbuf, (uint32_t) strlen(end), end);\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufEndsWithFn(const FFstrbuf* strbuf, int (*const fn)(int))\n{\n    return strbuf->length == 0 ? false :\n        fn(strbuf->chars[strbuf->length - 1]);\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufEndsWith(const FFstrbuf* strbuf, const FFstrbuf* end)\n{\n    return ffStrbufEndsWithNS(strbuf, end->length, end->chars);\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufEndsWithIgnCaseNS(const FFstrbuf* strbuf, uint32_t endLength, const char* end)\n{\n    if(endLength > strbuf->length)\n        return false;\n    return strcasecmp(strbuf->chars + strbuf->length - endLength, end) == 0;\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufEndsWithIgnCaseS(const FFstrbuf* strbuf, const char* end)\n{\n    return ffStrbufEndsWithIgnCaseNS(strbuf, (uint32_t) strlen(end), end);\n}\n\nstatic inline FF_C_NODISCARD bool ffStrbufEndsWithIgnCase(const FFstrbuf* strbuf, const FFstrbuf* end)\n{\n    return ffStrbufEndsWithIgnCaseNS(strbuf, end->length, end->chars);\n}\n\nstatic inline void ffStrbufTrim(FFstrbuf* strbuf, char c)\n{\n    ffStrbufTrimRight(strbuf, c);\n    ffStrbufTrimLeft(strbuf, c);\n}\n\nstatic inline void ffStrbufTrimSpace(FFstrbuf* strbuf)\n{\n    ffStrbufTrimRightSpace(strbuf);\n    ffStrbufTrimLeftSpace(strbuf);\n}\n\nstatic inline bool ffStrbufMatchSeparatedS(const FFstrbuf* strbuf, const char* comp, char separator)\n{\n    return ffStrbufMatchSeparatedNS(strbuf, (uint32_t) strlen(comp), comp, separator);\n}\n\nstatic inline bool ffStrbufMatchSeparated(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator)\n{\n    return ffStrbufMatchSeparatedNS(strbuf, comp->length, comp->chars, separator);\n}\n\nstatic inline bool ffStrbufMatchSeparatedIgnCaseS(const FFstrbuf* strbuf, const char* comp, char separator)\n{\n    return ffStrbufMatchSeparatedIgnCaseNS(strbuf, (uint32_t) strlen(comp), comp, separator);\n}\n\nstatic inline bool ffStrbufMatchSeparatedIgnCase(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator)\n{\n    return ffStrbufMatchSeparatedIgnCaseNS(strbuf, comp->length, comp->chars, separator);\n}\n\nstatic inline bool ffStrbufSeparatedContainS(const FFstrbuf* strbuf, const char* comp, char separator)\n{\n    return ffStrbufSeparatedContainNS(strbuf, (uint32_t) strlen(comp), comp, separator);\n}\n\nstatic inline bool ffStrbufSeparatedContain(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator)\n{\n    return ffStrbufSeparatedContainNS(strbuf, comp->length, comp->chars, separator);\n}\n\nstatic inline bool ffStrbufSeparatedContainIgnCaseS(const FFstrbuf* strbuf, const char* comp, char separator)\n{\n    return ffStrbufSeparatedContainIgnCaseNS(strbuf, (uint32_t) strlen(comp), comp, separator);\n}\n\nstatic inline bool ffStrbufSeparatedContainIgnCase(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator)\n{\n    return ffStrbufSeparatedContainIgnCaseNS(strbuf, comp->length, comp->chars, separator);\n}\n\n#define FF_STRBUF_AUTO_DESTROY FFstrbuf __attribute__((__cleanup__(ffStrbufDestroy)))\n"
  },
  {
    "path": "src/common/apple/Info.plist.in",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n  <dict>\n    <key>CFBundleIdentifier</key>\n    <string>fastfetch</string>\n    <key>CFBundleName</key>\n    <string>@PROJECT_NAME@</string>\n    <key>CFBundleShortVersionString</key>\n    <string>@PROJECT_VERSION@</string>\n    <key>CFBundleDevelopmentRegion</key>\n    <string>English</string>\n    <key>NSBluetoothAlwaysUsageDescription</key>\n    <string>For detecting Bluetooth devices</string>\n  </dict>\n</plist>\n"
  },
  {
    "path": "src/common/apple/cf_helpers.c",
    "content": "#include \"cf_helpers.h\"\n\nconst char* ffCfNumGetInt64(CFTypeRef cf, int64_t* result)\n{\n    if(CFGetTypeID(cf) == CFNumberGetTypeID())\n    {\n        if(!CFNumberGetValue((CFNumberRef)cf, kCFNumberSInt64Type, result))\n            return \"Number type is not SInt64\";\n        return NULL;\n    }\n    else if(CFGetTypeID(cf) == CFDataGetTypeID())\n    {\n        if(CFDataGetLength((CFDataRef)cf) != sizeof(int64_t))\n            return \"Data length is not sizeof(int64_t)\";\n        CFDataGetBytes((CFDataRef)cf, CFRangeMake(0, sizeof(int64_t)), (uint8_t*)result);\n        return NULL;\n    }\n\n    return \"TypeID is neither 'CFNumber' nor 'CFData'\";\n}\n\nconst char* ffCfNumGetInt(CFTypeRef cf, int32_t* result)\n{\n    if(CFGetTypeID(cf) == CFNumberGetTypeID())\n    {\n        if(!CFNumberGetValue((CFNumberRef)cf, kCFNumberSInt32Type, result))\n            return \"Number type is not SInt32\";\n        return NULL;\n    }\n    else if(CFGetTypeID(cf) == CFDataGetTypeID())\n    {\n        if(CFDataGetLength((CFDataRef)cf) != sizeof(int))\n            return \"Data length is not sizeof(int)\";\n        CFDataGetBytes((CFDataRef)cf, CFRangeMake(0, sizeof(int)), (uint8_t*)result);\n        return NULL;\n    }\n\n    return \"TypeID is neither 'CFNumber' nor 'CFData'\";\n}\n\nconst char* ffCfStrGetString(CFTypeRef cf, FFstrbuf* result)\n{\n    ffStrbufClear(result);\n    if (!cf)\n        return NULL;\n\n    if (CFGetTypeID(cf) == CFStringGetTypeID())\n    {\n        CFStringRef cfStr = (CFStringRef)cf;\n\n        const char* cstr = CFStringGetCStringPtr(cfStr, kCFStringEncodingUTF8);\n        if (cstr)\n            ffStrbufSetS(result, cstr);\n        else\n        {\n            uint32_t length = (uint32_t) CFStringGetLength(cfStr);\n            if (length == 0)\n                return NULL;\n            ffStrbufEnsureFixedLengthFree(result, (uint32_t) CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8));\n            if(!CFStringGetCString(cfStr, result->chars, result->allocated, kCFStringEncodingUTF8))\n                return \"CFStringGetCString() failed\";\n            // CFStringGetCString ensures the buffer is NUL terminated\n            // https://developer.apple.com/documentation/corefoundation/1542721-cfstringgetcstring\n            result->length = (uint32_t) strnlen(result->chars, (uint32_t)result->allocated);\n        }\n    }\n    else if (CFGetTypeID(cf) == CFDataGetTypeID())\n    {\n        CFDataRef cfData = (CFDataRef)cf;\n        uint32_t length = (uint32_t)CFDataGetLength(cfData);\n        if (length == 0)\n            return NULL;\n        ffStrbufEnsureFixedLengthFree(result, length + 1);\n        CFDataGetBytes(cfData, CFRangeMake(0, length), (uint8_t*)result->chars);\n        result->length = (uint32_t)strnlen(result->chars, length);\n        result->chars[result->length] = '\\0';\n    }\n    else\n        return \"TypeID is neither 'CFString' nor 'CFData'\";\n\n    return NULL;\n}\n\nconst char* ffCfDataGetDataAsString(CFTypeRef cf, FFstrbuf* result)\n{\n    ffStrbufClear(result);\n    if (!cf)\n        return NULL;\n\n    if (CFGetTypeID(cf) == CFDataGetTypeID())\n    {\n        CFDataRef cfData = (CFDataRef)cf;\n        uint32_t length = (uint32_t)CFDataGetLength(cfData);\n        if (length == 0)\n            return NULL;\n        ffStrbufEnsureFixedLengthFree(result, length + 1);\n        CFDataGetBytes(cfData, CFRangeMake(0, length), (uint8_t*)result->chars);\n        result->length = length;\n        result->chars[result->length] = '\\0';\n    }\n    else\n        return \"TypeID is not 'CFData'\";\n\n    return NULL;\n}\n\nconst char* ffCfDictGetString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result)\n{\n    CFTypeRef cf = (CFTypeRef)CFDictionaryGetValue(dict, key);\n    if(cf == NULL)\n        return \"CFDictionaryGetValue() failed\";\n\n    return ffCfStrGetString(cf, result);\n}\n\nconst char* ffCfDictGetDataAsString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result)\n{\n    CFTypeRef cf = (CFTypeRef)CFDictionaryGetValue(dict, key);\n    if(cf == NULL)\n        return \"CFDictionaryGetValue() failed\";\n\n    return ffCfDataGetDataAsString(cf, result);\n}\n\nconst char* ffCfDictGetBool(CFDictionaryRef dict, CFStringRef key, bool* result)\n{\n    CFBooleanRef cf = (CFBooleanRef)CFDictionaryGetValue(dict, key);\n    if(cf == NULL)\n        return \"CFDictionaryGetValue() failed\";\n\n    if(CFGetTypeID(cf) != CFBooleanGetTypeID())\n        return \"TypeID is not 'CFBoolean'\";\n\n    *result = CFBooleanGetValue(cf);\n    return NULL;\n}\n\nconst char* ffCfDictGetInt(CFDictionaryRef dict, CFStringRef key, int* result)\n{\n    CFTypeRef cf = (CFTypeRef)CFDictionaryGetValue(dict, key);\n    if(cf == NULL)\n        return \"CFDictionaryGetValue() failed\";\n\n    return ffCfNumGetInt(cf, result);\n}\n\nconst char* ffCfDictGetInt64(CFDictionaryRef dict, CFStringRef key, int64_t* result)\n{\n    CFTypeRef cf = (CFTypeRef)CFDictionaryGetValue(dict, key);\n    if(cf == NULL)\n        return \"CFDictionaryGetValue() failed\";\n\n    return ffCfNumGetInt64(cf, result);\n}\n\nconst char* ffCfDictGetData(CFDictionaryRef dict, CFStringRef key, uint32_t offset, uint32_t size, uint8_t* result, uint32_t* length)\n{\n    CFTypeRef cf = (CFTypeRef)CFDictionaryGetValue(dict, key);\n    if(cf == NULL)\n        return \"CFDictionaryGetValue() failed\";\n\n    if(CFGetTypeID(cf) != CFDataGetTypeID())\n        return \"TypeID is not 'CFData'\";\n\n    CFIndex trueLength = CFDataGetLength((CFDataRef)cf);\n\n    if(trueLength < offset + size)\n        return \"Data length is less than offset + size\";\n\n    if(length)\n        *length = (uint32_t) trueLength;\n\n    CFDataGetBytes((CFDataRef)cf, CFRangeMake(offset, size), result);\n    return NULL;\n}\n\nconst char* ffCfDictGetDict(CFDictionaryRef dict, CFStringRef key, CFDictionaryRef* result)\n{\n    CFDictionaryRef cf = (CFDictionaryRef)CFDictionaryGetValue(dict, key);\n    if (cf == NULL || CFGetTypeID(cf) != CFDictionaryGetTypeID())\n        return \"TypeID is not 'CFDictionary'\";\n\n    *result = cf;\n    return NULL;\n}\n"
  },
  {
    "path": "src/common/apple/cf_helpers.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include <CoreFoundation/CoreFoundation.h>\n#include <IOKit/IOKitLib.h>\n\n//Return error info if failed, NULL otherwise\nconst char* ffCfStrGetString(CFTypeRef cf, FFstrbuf* result);\nconst char* ffCfNumGetInt(CFTypeRef cf, int32_t* result);\nconst char* ffCfNumGetInt64(CFTypeRef cf, int64_t* result);\nconst char* ffCfDataGetDataAsString(CFTypeRef cf, FFstrbuf* result);\nconst char* ffCfDictGetString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result);\nconst char* ffCfDictGetBool(CFDictionaryRef dict, CFStringRef key, bool* result);\nconst char* ffCfDictGetInt(CFDictionaryRef dict, CFStringRef key, int* result);\nconst char* ffCfDictGetInt64(CFDictionaryRef dict, CFStringRef key, int64_t* result);\nconst char* ffCfDictGetData(CFDictionaryRef dict, CFStringRef key, uint32_t offset, uint32_t size, uint8_t* result, uint32_t* length);\nconst char* ffCfDictGetDataAsString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result);\nconst char* ffCfDictGetDict(CFDictionaryRef dict, CFStringRef key, CFDictionaryRef* result);\n\nstatic inline CFNumberRef ffCfCreateInt(int value)\n{\n    return CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &value);\n}\n\nstatic inline void cfReleaseWrapper(void* type)\n{\n    assert(type);\n    if (*(CFTypeRef*) type)\n        CFRelease(*(CFTypeRef*) type);\n}\n\n#define FF_CFTYPE_AUTO_RELEASE __attribute__((__cleanup__(cfReleaseWrapper)))\n\nstatic inline void wrapIoObjectRelease(io_object_t* service)\n{\n    assert(service);\n    if (*service)\n        IOObjectRelease(*service);\n}\n#define FF_IOOBJECT_AUTO_RELEASE __attribute__((__cleanup__(wrapIoObjectRelease)))\n"
  },
  {
    "path": "src/common/apple/osascript.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\nbool ffOsascript(const char* input, FFstrbuf* result);\n"
  },
  {
    "path": "src/common/apple/osascript.m",
    "content": "#include \"osascript.h\"\n\n#import <Foundation/Foundation.h>\n#import <AppKit/AppKit.h>\n#import <CoreData/CoreData.h>\n\nbool ffOsascript(const char* input, FFstrbuf* result)\n{\n    NSAppleScript* script = [NSAppleScript.alloc initWithSource:@(input)];\n    NSDictionary* errInfo = nil;\n    NSAppleEventDescriptor* descriptor = [script executeAndReturnError:&errInfo];\n    if (errInfo)\n        return false;\n\n    ffStrbufSetS(result, descriptor.stringValue.UTF8String);\n    return true;\n}\n"
  },
  {
    "path": "src/common/apple/smc_temps.c",
    "content": "#include \"smc_temps.h\"\n#include \"common/apple/cf_helpers.h\"\n#include \"common/stringUtils.h\"\n\n#include <stdint.h>\n#include <math.h>\n#include <IOKit/IOKitLib.h>\n\nstatic const char kSmcCmdReadBytes = 5;\nstatic const char kSmcCmdReadKeyInfo = 9;\nstatic const uint32_t kKernelIndexSmc = 2;\n\ntypedef struct\n{\n    char major;\n    char minor;\n    char build;\n    char reserved[1];\n    uint16_t release;\n} SmcKeyData_vers_t;\n\ntypedef struct\n{\n    uint16_t version;\n    uint16_t length;\n    uint32_t cpuPLimit;\n    uint32_t gpuPLimit;\n    uint32_t memPLimit;\n} SmcKeyData_pLimitData_t;\n\ntypedef struct\n{\n    uint32_t dataSize;\n    uint32_t dataType;\n    char dataAttributes;\n} SmcKeyData_keyInfo_t;\n\ntypedef unsigned char SmcBytes_t[32];\n\ntypedef struct\n{\n    uint32_t key;\n    SmcKeyData_vers_t vers;\n    SmcKeyData_pLimitData_t pLimitData;\n    SmcKeyData_keyInfo_t keyInfo;\n    char result;\n    char status;\n    char data8;\n    uint32_t data32;\n    SmcBytes_t bytes;\n} SmcKeyData_t;\n\ntypedef char UInt32Char_t[5];\n\ntypedef struct\n{\n    UInt32Char_t key;\n    uint32_t dataSize;\n    UInt32Char_t dataType;\n    SmcBytes_t bytes;\n} SmcVal_t;\n\nstatic uint32_t smcStrtoul(const char *str, int size, int base)\n{\n    uint32_t total = 0;\n\n    for (int i = 0; i < size; i++)\n    {\n        if (base == 16)\n            total += (uint32_t)(str[i] << (size - 1 - i) * 8);\n        else\n            total += (uint32_t)((unsigned char)(str[i]) << (size - 1 - i) * 8);\n    }\n    return total;\n}\n\nstatic void smcUltostr(char *str, uint32_t val)\n{\n    str[0] = (char)(val >> 24);\n    str[1] = (char)(val >> 16);\n    str[2] = (char)(val >> 8);\n    str[3] = (char)val;\n    str[4] = '\\0';\n}\n\nstatic const char *smcCall(io_connect_t conn, uint32_t selector, SmcKeyData_t *inputStructure, SmcKeyData_t *outputStructure)\n{\n    size_t size = sizeof(SmcKeyData_t);\n\n    if (IOConnectCallStructMethod(conn, selector, inputStructure, size, outputStructure, &size) != kIOReturnSuccess)\n        return \"IOConnectCallStructMethod(conn) failed\";\n    return NULL;\n}\n\n// Provides key info, using a cache to dramatically improve the energy impact of smcFanControl\nstatic const char *smcGetKeyInfo(io_connect_t conn, const uint32_t key, SmcKeyData_keyInfo_t *key_info)\n{\n    SmcKeyData_t inputStructure = {0};\n    SmcKeyData_t outputStructure = {0};\n\n    inputStructure.key = key;\n    inputStructure.data8 = kSmcCmdReadKeyInfo;\n\n    const char *error = smcCall(conn, kKernelIndexSmc, &inputStructure, &outputStructure);\n    if (error)\n        return error;\n\n    *key_info = outputStructure.keyInfo;\n    return NULL;\n}\n\nstatic const char *smcReadSmcVal(io_connect_t conn, const UInt32Char_t key, SmcVal_t *val)\n{\n    SmcKeyData_t inputStructure = {0};\n    SmcKeyData_t outputStructure = {0};\n\n    inputStructure.key = smcStrtoul(key, 4, 16);\n    strcpy(val->key, key);\n\n    const char *error = smcGetKeyInfo(conn, inputStructure.key, &outputStructure.keyInfo);\n    if (error)\n        return error;\n\n    val->dataSize = outputStructure.keyInfo.dataSize;\n    smcUltostr(val->dataType, outputStructure.keyInfo.dataType);\n    inputStructure.keyInfo.dataSize = val->dataSize;\n    inputStructure.data8 = kSmcCmdReadBytes;\n\n    error = smcCall(conn, kKernelIndexSmc, &inputStructure, &outputStructure);\n    if (error)\n        return error;\n\n    memcpy(val->bytes, outputStructure.bytes, sizeof(outputStructure.bytes));\n\n    return NULL;\n}\n\nstatic const char *smcOpen(io_connect_t *conn)\n{\n    FF_IOOBJECT_AUTO_RELEASE io_object_t device = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceMatching(\"AppleSMC\"));\n    if (!device)\n        return \"No SMC device found\";\n\n    if (IOServiceOpen(device, mach_task_self(), 0, conn) != kIOReturnSuccess)\n        return \"IOServiceOpen() failed\";\n\n    return NULL;\n}\n\nstatic const char *smcReadValue(io_connect_t conn, const UInt32Char_t key, double *value)\n{\n    SmcVal_t val = {0};\n    const char* error = smcReadSmcVal(conn, key, &val);\n    if (error != NULL)\n        return error;\n    if (val.dataSize == 0)\n        return \"Empty SMC result\";\n\n    switch (val.dataType[0])\n    {\n        case 'u': // unsigned integer types\n            if (val.dataType[1] == 'i')\n            {\n                switch (val.dataSize)\n                {\n                    case 1: *value = *(uint8_t *)(val.bytes); break;\n                    case 2: *value = ntohs(*(uint16_t *)(val.bytes)); break;\n                    case 4: *value = ntohl(*(uint32_t *)(val.bytes)); break;\n                    case 8: *value = (double) ntohll(*(uint64_t *)(val.bytes)); break;\n                    default:\n                        return \"Unsupported SMC unsigned integer data size\";\n                }\n            }\n            else\n                return \"Unsupported SMC unsigned data type\";\n            break;\n\n        case 'f': // floating point types\n            if (ffStrEquals(val.dataType, \"flt \") && val.dataSize == 4)\n                *value = *(float *)(val.bytes);\n            else if (val.dataType[1] == 'p' && val.dataSize == 2) // fixed point types\n            {\n                if (ffStrEquals(val.dataType, \"fp1f\"))\n                    *value = ntohs(*(uint16_t *)(val.bytes)) / 32768.0;\n                else if (ffStrEquals(val.dataType, \"fp4c\"))\n                    *value = ntohs(*(uint16_t *)(val.bytes)) / 4096.0;\n                else if (ffStrEquals(val.dataType, \"fp5b\"))\n                    *value = ntohs(*(uint16_t *)(val.bytes)) / 2048.0;\n                else if (ffStrEquals(val.dataType, \"fp6a\"))\n                    *value = ntohs(*(uint16_t *)(val.bytes)) / 1024.0;\n                else if (ffStrEquals(val.dataType, \"fp79\"))\n                    *value = ntohs(*(uint16_t *)(val.bytes)) / 512.0;\n                else if (ffStrEquals(val.dataType, \"fp88\"))\n                    *value = ntohs(*(uint16_t *)(val.bytes)) / 256.0;\n                else if (ffStrEquals(val.dataType, \"fpa6\"))\n                    *value = ntohs(*(uint16_t *)(val.bytes)) / 64.0;\n                else if (ffStrEquals(val.dataType, \"fpc4\"))\n                    *value = ntohs(*(uint16_t *)(val.bytes)) / 16.0;\n                else if (ffStrEquals(val.dataType, \"fpe2\"))\n                    *value = ntohs(*(uint16_t *)(val.bytes)) / 4.0;\n                else\n                    return \"Unsupported SMC floating point data type\";\n            }\n            else\n                return \"Unsupported SMC floating point data type\";\n            break;\n\n        case 's': // signed integer types\n            if (val.dataType[1] == 'i')\n            {\n                switch (val.dataSize)\n                {\n                    case 1: *value = *(int8_t *)(val.bytes); break;\n                    case 2: *value = ntohs(*(int16_t *)(val.bytes)); break;\n                    case 4: *value = ntohl(*(int32_t *)(val.bytes)); break;\n                    case 8: *value = (double)ntohll(*(int64_t *)(val.bytes)); break;\n                    default: return \"Unsupported SMC signed integer data size\";\n                }\n            }\n            else if (val.dataType[1] == 'p' && val.dataSize == 2) // signed fixed point types\n            {\n                if (ffStrEquals(val.dataType, \"sp1e\"))\n                    *value = (int16_t)ntohs(*(int16_t *)(val.bytes)) / 16384.0;\n                else if (ffStrEquals(val.dataType, \"sp3c\"))\n                    *value = (int16_t)ntohs(*(int16_t *)(val.bytes)) / 4096.0;\n                else if (ffStrEquals(val.dataType, \"sp4b\"))\n                    *value = (int16_t)ntohs(*(int16_t *)(val.bytes)) / 2048.0;\n                else if (ffStrEquals(val.dataType, \"sp5a\"))\n                    *value = (int16_t)ntohs(*(int16_t *)(val.bytes)) / 1024.0;\n                else if (ffStrEquals(val.dataType, \"sp69\"))\n                    *value = (int16_t)ntohs(*(int16_t *)(val.bytes)) / 512.0;\n                else if (ffStrEquals(val.dataType, \"sp78\"))\n                    *value = (int16_t)ntohs(*(int16_t *)(val.bytes)) / 256.0;\n                else if (ffStrEquals(val.dataType, \"sp87\"))\n                    *value = (int16_t)ntohs(*(int16_t *)(val.bytes)) / 128.0;\n                else if (ffStrEquals(val.dataType, \"sp96\"))\n                    *value = (int16_t)ntohs(*(int16_t *)(val.bytes)) / 64.0;\n                else if (ffStrEquals(val.dataType, \"spb4\"))\n                    *value = (int16_t)ntohs(*(int16_t *)(val.bytes)) / 16.0;\n                else if (ffStrEquals(val.dataType, \"spf0\"))\n                    *value = (int16_t)ntohs(*(int16_t *)(val.bytes)) / 1.0;\n                else\n                    return \"Unsupported SMC signed integer data type\";\n            }\n            else\n                return \"Unsupported SMC signed data type\";\n            break;\n\n        case '{': // special types like pwm\n            if (ffStrEquals(val.dataType, \"{pwm\") && val.dataSize == 2)\n            {\n                *value = (double)ntohs(*(uint16_t *)(val.bytes)) * 100 / 65536.0;\n            }\n            else\n                return \"Unsupported SMC special data type\";\n            break;\n\n        default:\n            return \"Unsupported SMC data type\";\n    }\n    return NULL;\n}\n\nstatic bool detectTemp(io_connect_t conn, const char* sensor, double* sum)\n{\n    double temp = 0;\n    const char* error = smcReadValue(conn, sensor, &temp);\n    if (error) return false;\n    // https://github.com/exelban/stats/blob/14e29c4d60229c363cca9c9d25c30c87b7870830/Modules/Sensors/readers.swift#L124\n    if (temp < 10 || temp > 120) return false;\n    *sum += temp;\n    return true;\n}\n\nstatic io_connect_t conn;\n\nconst char* ffDetectSmcSpecificTemp(const char* sensor, double* result)\n{\n    if (!conn)\n    {\n        if (smcOpen(&conn) != NULL)\n            conn = (io_connect_t) -1;\n    }\n    if (conn == (io_connect_t) -1)\n        return \"Could not open SMC connection\";\n\n    if (!detectTemp(conn, sensor, result))\n        return \"Could not read SMC temperature\";\n\n    return NULL;\n}\n\nconst char* ffDetectSmcTemps(enum FFTempType type, double* result)\n{\n    if (!conn)\n    {\n        if (smcOpen(&conn) != NULL)\n            conn = (io_connect_t) -1;\n    }\n    if (conn == (io_connect_t) -1)\n        return \"Could not open SMC connection\";\n\n    uint32_t count = 0;\n    *result = 0;\n\n    // https://github.com/exelban/stats/blob/master/Modules/Sensors/values.swift\n    switch (type)\n    {\n    case FF_TEMP_CPU_X64:\n        count += detectTemp(conn, \"TC0D\", result); // CPU diode\n        count += detectTemp(conn, \"TC0E\", result); // CPU diode virtual\n        count += detectTemp(conn, \"TC0F\", result); // CPU diode filtered\n        count += detectTemp(conn, \"TC0P\", result); // CPU proximity\n        break;\n\n    case FF_TEMP_CPU_M1X:\n        count += detectTemp(conn, \"Tp09\", result); // CPU efficient core 1\n        count += detectTemp(conn, \"Tp0T\", result); // CPU efficient core 2\n\n        count += detectTemp(conn, \"Tp01\", result); // CPU performance core 1\n        count += detectTemp(conn, \"Tp05\", result); // CPU performance core 2\n        count += detectTemp(conn, \"Tp0D\", result); // CPU performance core 3\n        count += detectTemp(conn, \"Tp0H\", result); // CPU performance core 4\n        count += detectTemp(conn, \"Tp0L\", result); // CPU performance core 5\n        count += detectTemp(conn, \"Tp0P\", result); // CPU performance core 6\n        count += detectTemp(conn, \"Tp0X\", result); // CPU performance core 7\n        count += detectTemp(conn, \"Tp0b\", result); // CPU performance core 8\n        break;\n\n    case FF_TEMP_CPU_M2X:\n        count += detectTemp(conn, \"Tp1h\", result); // CPU efficiency core 1\n        count += detectTemp(conn, \"Tp1t\", result); // CPU efficiency core 2\n        count += detectTemp(conn, \"Tp1p\", result); // CPU efficiency core 3\n        count += detectTemp(conn, \"Tp1l\", result); // CPU efficiency core 4\n\n        count += detectTemp(conn, \"Tp01\", result); // CPU performance core 1\n        count += detectTemp(conn, \"Tp05\", result); // CPU performance core 2\n        count += detectTemp(conn, \"Tp09\", result); // CPU performance core 3\n        count += detectTemp(conn, \"Tp0D\", result); // CPU performance core 4\n        count += detectTemp(conn, \"Tp0X\", result); // CPU performance core 5\n        count += detectTemp(conn, \"Tp0b\", result); // CPU performance core 6\n        count += detectTemp(conn, \"Tp0f\", result); // CPU performance core 7\n        count += detectTemp(conn, \"Tp0j\", result); // CPU performance core 8\n        break;\n\n    case FF_TEMP_CPU_M3X:\n        count += detectTemp(conn, \"Te05\", result); // CPU efficiency core 1\n        count += detectTemp(conn, \"Te0L\", result); // CPU efficiency core 2\n        count += detectTemp(conn, \"Te0P\", result); // CPU efficiency core 3\n        count += detectTemp(conn, \"Te0S\", result); // CPU efficiency core 4\n        count += detectTemp(conn, \"Tf04\", result); // CPU performance core 1\n        count += detectTemp(conn, \"Tf09\", result); // CPU performance core 2\n        count += detectTemp(conn, \"Tf0A\", result); // CPU performance core 3\n        count += detectTemp(conn, \"Tf0B\", result); // CPU performance core 4\n        count += detectTemp(conn, \"Tf0D\", result); // CPU performance core 5\n        count += detectTemp(conn, \"Tf0E\", result); // CPU performance core 6\n        count += detectTemp(conn, \"Tf44\", result); // CPU performance core 7\n        count += detectTemp(conn, \"Tf49\", result); // CPU performance core 8\n        count += detectTemp(conn, \"Tf4A\", result); // CPU performance core 9\n        count += detectTemp(conn, \"Tf4B\", result); // CPU performance core 10\n        count += detectTemp(conn, \"Tf4D\", result); // CPU performance core 11\n        count += detectTemp(conn, \"Tf4E\", result); // CPU performance core 12\n        break;\n\n    case FF_TEMP_CPU_M4X:\n        count += detectTemp(conn, \"Te05\", result); // CPU efficiency core 1\n        count += detectTemp(conn, \"Te0S\", result); // CPU efficiency core 2\n        count += detectTemp(conn, \"Te09\", result); // CPU efficiency core 3\n        count += detectTemp(conn, \"Te0H\", result); // CPU efficiency core 4\n        count += detectTemp(conn, \"Tp01\", result); // CPU performance core 1\n        count += detectTemp(conn, \"Tp05\", result); // CPU performance core 2\n        count += detectTemp(conn, \"Tp09\", result); // CPU performance core 3\n        count += detectTemp(conn, \"Tp0D\", result); // CPU performance core 4\n        count += detectTemp(conn, \"Tp0V\", result); // CPU performance core 5\n        count += detectTemp(conn, \"Tp0Y\", result); // CPU performance core 6\n        count += detectTemp(conn, \"Tp0b\", result); // CPU performance core 7\n        count += detectTemp(conn, \"Tp0e\", result); // CPU performance core 8\n        break;\n\n    case FF_TEMP_GPU_INTEL:\n        count += detectTemp(conn, \"TCGC\", result); // GPU Intel Graphics\n        goto gpu_unknown;\n\n    case FF_TEMP_GPU_AMD:\n        count += detectTemp(conn, \"TGDD\", result); // GPU AMD Radeon\n        goto gpu_unknown;\n\n    case FF_TEMP_GPU_UNKNOWN: // Nvidia?\n    gpu_unknown:\n        count += detectTemp(conn, \"TG0D\", result); // GPU diode\n        count += detectTemp(conn, \"TG0P\", result); // GPU proximity\n        break;\n\n    case FF_TEMP_GPU_M1X:\n        count += detectTemp(conn, \"Tg05\", result); // GPU 1\n        count += detectTemp(conn, \"Tg0D\", result); // GPU 2\n        count += detectTemp(conn, \"Tg0L\", result); // GPU 3\n        count += detectTemp(conn, \"Tg0T\", result); // GPU 4\n        break;\n\n    case FF_TEMP_GPU_M2X:\n        count += detectTemp(conn, \"Tg0f\", result); // GPU 1\n        count += detectTemp(conn, \"Tg0j\", result); // GPU 2\n        break;\n\n    case FF_TEMP_GPU_M3X:\n        count += detectTemp(conn, \"Tf14\", result); // GPU 1\n        count += detectTemp(conn, \"Tf18\", result); // GPU 2\n        count += detectTemp(conn, \"Tf19\", result); // GPU 3\n        count += detectTemp(conn, \"Tf1A\", result); // GPU 4\n        count += detectTemp(conn, \"Tf24\", result); // GPU 5\n        count += detectTemp(conn, \"Tf28\", result); // GPU 6\n        count += detectTemp(conn, \"Tf29\", result); // GPU 7\n        count += detectTemp(conn, \"Tf2A\", result); // GPU 8\n        break;\n\n    case FF_TEMP_GPU_M4X:\n        count += detectTemp(conn, \"Tg0G\", result); // GPU 1 (Basic)\n        count += detectTemp(conn, \"Tg0H\", result); // GPU 2 (Basic)\n        count += detectTemp(conn, \"Tg1U\", result); // GPU 1 (Pro / Max)\n        count += detectTemp(conn, \"Tg1k\", result); // GPU 2 (Pro / Max)\n        count += detectTemp(conn, \"Tg0K\", result); // GPU 3\n        count += detectTemp(conn, \"Tg0L\", result); // GPU 4\n        count += detectTemp(conn, \"Tg0d\", result); // GPU 5\n        count += detectTemp(conn, \"Tg0e\", result); // GPU 6\n        count += detectTemp(conn, \"Tg0j\", result); // GPU 7\n        count += detectTemp(conn, \"Tg0k\", result); // GPU 8\n        break;\n\n    case FF_TEMP_BATTERY:\n        count += detectTemp(conn, \"TB1T\", result); // Battery\n        count += detectTemp(conn, \"TB2T\", result); // Battery\n        break;\n\n    case FF_TEMP_MEMORY:\n        count += detectTemp(conn, \"Tm02\", result); // Memory 1\n        count += detectTemp(conn, \"Tm06\", result); // Memory 2\n        count += detectTemp(conn, \"Tm08\", result); // Memory 3\n        count += detectTemp(conn, \"Tm09\", result); // Memory 4\n        break;\n    }\n\n    if (count == 0)\n        return \"No temperatures detected\";\n\n    *result /= count;\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/common/apple/smc_temps.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\ntypedef struct FFTempValue\n{\n    FFstrbuf name;\n    FFstrbuf deviceClass;\n    double value;\n} FFTempValue;\n\nenum FFTempType\n{\n    FF_TEMP_CPU_X64,\n    FF_TEMP_CPU_M1X,\n    FF_TEMP_CPU_M2X,\n    FF_TEMP_CPU_M3X,\n    FF_TEMP_CPU_M4X,\n\n    FF_TEMP_GPU_INTEL,\n    FF_TEMP_GPU_AMD,\n    FF_TEMP_GPU_UNKNOWN,\n    FF_TEMP_GPU_M1X,\n    FF_TEMP_GPU_M2X,\n    FF_TEMP_GPU_M3X,\n    FF_TEMP_GPU_M4X,\n\n    FF_TEMP_BATTERY,\n\n    FF_TEMP_MEMORY,\n};\n\nconst char* ffDetectSmcSpecificTemp(const char* sensor, double* result);\nconst char* ffDetectSmcTemps(enum FFTempType type, double* result);\n"
  },
  {
    "path": "src/common/argType.h",
    "content": "#pragma once\n\n#include \"common/FFstrbuf.h\"\n\ntypedef enum __attribute__((__packed__)) FFArgType\n{\n    FF_ARG_TYPE_NULL = 0,\n    FF_ARG_TYPE_UINT,\n    FF_ARG_TYPE_UINT64,\n    FF_ARG_TYPE_UINT16,\n    FF_ARG_TYPE_UINT8,\n    FF_ARG_TYPE_INT,\n    FF_ARG_TYPE_STRING,\n    FF_ARG_TYPE_STRBUF,\n    FF_ARG_TYPE_FLOAT,\n    FF_ARG_TYPE_DOUBLE,\n    FF_ARG_TYPE_LIST,\n    FF_ARG_TYPE_BOOL\n} FFArgType;\n\n#define FF_ARG(variable, var_name) { _Generic((variable), \\\n        uint32_t: FF_ARG_TYPE_UINT, \\\n        uint64_t: FF_ARG_TYPE_UINT64, \\\n        uint16_t: FF_ARG_TYPE_UINT16, \\\n        uint8_t: FF_ARG_TYPE_UINT8, \\\n        int32_t: FF_ARG_TYPE_INT, \\\n        char*: FF_ARG_TYPE_STRING, \\\n        const char*: FF_ARG_TYPE_STRING, \\\n        FFstrbuf: FF_ARG_TYPE_STRBUF, \\\n        float: FF_ARG_TYPE_FLOAT, \\\n        double: FF_ARG_TYPE_DOUBLE, \\\n        FFlist: FF_ARG_TYPE_LIST, \\\n        bool: FF_ARG_TYPE_BOOL \\\n    ), _Generic((variable), char*: (variable), const char*: (variable), default: &(variable) ), (var_name) }\n"
  },
  {
    "path": "src/common/arrayUtils.h",
    "content": "#pragma once\n\n#include <assert.h>\n\n#ifdef __has_builtin\n    #if !__cplusplus && FF_SUPPORTS_COUNT_OF\n        #define ARRAY_SIZE(x) _Countof(x)\n    #elif __has_builtin(__is_array)\n        #define ARRAY_SIZE(x) ({ static_assert(__is_array(__typeof__(x)), \"Must be an array\"); (uint32_t) (sizeof(x) / sizeof(*(x))); })\n    #elif __has_builtin(__builtin_types_compatible_p)\n        #define ARRAY_SIZE(x) ({ static_assert(!__builtin_types_compatible_p(__typeof__(x), __typeof__(&*(x))), \"Must not be a pointer\"); (uint32_t) (sizeof(x) / sizeof(*(x))); })\n    #endif\n#endif\n#ifndef ARRAY_SIZE\n    #define ARRAY_SIZE(x) ((uint32_t) (sizeof(x) / sizeof(*(x))))\n#endif\n"
  },
  {
    "path": "src/common/base64.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\nvoid ffBase64EncodeRaw(uint32_t size, const char *str, uint32_t *out_size, char *output);\nstatic inline FFstrbuf ffBase64EncodeStrbuf(const FFstrbuf* in)\n{\n    FFstrbuf out = ffStrbufCreateA(10 + in->length * 4 / 3);\n    ffBase64EncodeRaw(in->length, in->chars, &out.length, out.chars);\n    assert(out.length < out.allocated);\n\n    return out;\n}\n\nbool ffBase64DecodeRaw(uint32_t size, const char *str, uint32_t *out_size, char *output);\nstatic inline FFstrbuf ffBase64DecodeStrbuf(const FFstrbuf* in)\n{\n    FFstrbuf out = ffStrbufCreateA(10 + in->length * 3 / 4);\n    ffBase64DecodeRaw(in->length, in->chars, &out.length, out.chars);\n    assert(out.length < out.allocated);\n\n    return out;\n}\n"
  },
  {
    "path": "src/common/binary.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\n/**\n * Extracts string literals from a binary file\n *\n * @param file Path to the binary file to extract strings from\n * @param cb Callback function that will be called for each string found\n *           Return false from callback to stop extraction\n * @param userdata User-provided data passed to the callback function\n * @param minLength Minimum length of strings to extract\n *\n * @return NULL on success, error message on failure.\n * @note This function won't return an error if no strings are found.\n *       Always check if strings are correctly extracted after this function all.\n */\nconst char* ffBinaryExtractStrings(const char* file, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength);\n"
  },
  {
    "path": "src/common/color.h",
    "content": "#pragma once\n\n#define FF_COLOR_MODE_RESET \"0;\"\n#define FF_COLOR_MODE_BOLD \"1;\"\n#define FF_COLOR_MODE_DIM \"2;\"\n#define FF_COLOR_MODE_ITALIC \"3;\"\n#define FF_COLOR_MODE_UNDERLINE \"4;\"\n#define FF_COLOR_MODE_BLINK \"5;\"\n#define FF_COLOR_MODE_INVERSE \"7;\"\n#define FF_COLOR_MODE_HIDDEN \"8;\"\n#define FF_COLOR_MODE_STRIKETHROUGH \"9;\"\n\n#define FF_COLOR_FG_BLACK \"30\"\n#define FF_COLOR_FG_RED \"31\"\n#define FF_COLOR_FG_GREEN \"32\"\n#define FF_COLOR_FG_YELLOW \"33\"\n#define FF_COLOR_FG_BLUE \"34\"\n#define FF_COLOR_FG_MAGENTA \"35\"\n#define FF_COLOR_FG_CYAN \"36\"\n#define FF_COLOR_FG_WHITE \"37\"\n#define FF_COLOR_FG_DEFAULT \"39\"\n\n#define FF_COLOR_FG_LIGHT_BLACK \"90\"\n#define FF_COLOR_FG_LIGHT_RED \"91\"\n#define FF_COLOR_FG_LIGHT_GREEN \"92\"\n#define FF_COLOR_FG_LIGHT_YELLOW \"93\"\n#define FF_COLOR_FG_LIGHT_BLUE \"94\"\n#define FF_COLOR_FG_LIGHT_MAGENTA \"95\"\n#define FF_COLOR_FG_LIGHT_CYAN \"96\"\n#define FF_COLOR_FG_LIGHT_WHITE \"97\"\n\n#define FF_COLOR_BG_BLACK \"40\"\n#define FF_COLOR_BG_RED \"41\"\n#define FF_COLOR_BG_GREEN \"42\"\n#define FF_COLOR_BG_YELLOW \"43\"\n#define FF_COLOR_BG_BLUE \"44\"\n#define FF_COLOR_BG_MAGENTA \"45\"\n#define FF_COLOR_BG_CYAN \"46\"\n#define FF_COLOR_BG_WHITE \"47\"\n#define FF_COLOR_BG_DEFAULT \"49\"\n\n#define FF_COLOR_BG_LIGHT_BLACK \"100\"\n#define FF_COLOR_BG_LIGHT_RED \"101\"\n#define FF_COLOR_BG_LIGHT_GREEN \"102\"\n#define FF_COLOR_BG_LIGHT_YELLOW \"103\"\n#define FF_COLOR_BG_LIGHT_BLUE \"104\"\n#define FF_COLOR_BG_LIGHT_MAGENTA \"105\"\n#define FF_COLOR_BG_LIGHT_CYAN \"106\"\n#define FF_COLOR_BG_LIGHT_WHITE \"107\"\n\n#define FF_COLOR_FG_256 \"38;5;\"\n#define FF_COLOR_BG_256 \"48;5;\"\n\n#define FF_COLOR_FG_RGB \"38;2;\"\n#define FF_COLOR_BG_RGB \"48;2;\"\n"
  },
  {
    "path": "src/common/commandoption.h",
    "content": "#pragma once\n\n#include \"common/ffdata.h\"\n\nvoid ffPrepareCommandOption(FFdata* data);\nvoid ffPrintCommandOption(FFdata* data);\nvoid ffMigrateCommandOptionToJsonc(FFdata* data);\nbool ffParseModuleOptions(const char* key, const char* value);\n"
  },
  {
    "path": "src/common/dbus.h",
    "content": "#pragma once\n\n#ifdef FF_HAVE_DBUS\n#include <dbus/dbus.h>\n\n#include \"common/FFstrbuf.h\"\n#include \"common/library.h\"\n\ntypedef struct FFDBusLibrary\n{\n    FF_LIBRARY_SYMBOL(dbus_bus_get)\n    FF_LIBRARY_SYMBOL(dbus_message_new_method_call)\n    FF_LIBRARY_SYMBOL(dbus_message_append_args)\n    FF_LIBRARY_SYMBOL(dbus_message_iter_init)\n    FF_LIBRARY_SYMBOL(dbus_message_iter_get_arg_type)\n    FF_LIBRARY_SYMBOL(dbus_message_iter_get_basic)\n    FF_LIBRARY_SYMBOL(dbus_message_iter_recurse)\n    FF_LIBRARY_SYMBOL(dbus_message_iter_has_next)\n    FF_LIBRARY_SYMBOL(dbus_message_iter_next)\n    FF_LIBRARY_SYMBOL(dbus_message_unref)\n    FF_LIBRARY_SYMBOL(dbus_connection_send_with_reply_and_block)\n    FF_LIBRARY_SYMBOL(dbus_connection_unref)\n} FFDBusLibrary;\n\ntypedef struct FFDBusData\n{\n    const FFDBusLibrary* lib;\n    DBusConnection* connection;\n} FFDBusData;\n\nconst char* ffDBusLoadData(DBusBusType busType, FFDBusData* data); //Returns an error message or NULL on success\nbool ffDBusGetString(FFDBusData* dbus, DBusMessageIter* iter, FFstrbuf* result);\nbool ffDBusGetBool(FFDBusData* dbus, DBusMessageIter* iter, bool* result);\nbool ffDBusGetUint(FFDBusData* dbus, DBusMessageIter* iter, uint32_t* result);\nDBusMessage* ffDBusGetMethodReply(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* method, const char* arg1, const char* arg2);\nDBusMessage* ffDBusGetProperty(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property);\nbool ffDBusGetPropertyString(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property, FFstrbuf* result);\nbool ffDBusGetInt(FFDBusData* dbus, DBusMessageIter* iter, int32_t* result);\nbool ffDBusGetPropertyUint(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property, uint32_t* result);\nvoid ffDBusDestroyData(FFDBusData* data);\n\nstatic inline DBusMessage* ffDBusGetAllProperties(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface)\n{\n    return ffDBusGetMethodReply(dbus, busName, objectPath, \"org.freedesktop.DBus.Properties\", \"GetAll\", interface, NULL);\n}\n\n#define FF_DBUS_AUTO_DESTROY_DATA __attribute__((__cleanup__(ffDBusDestroyData)))\n\n#endif // FF_HAVE_DBUS\n"
  },
  {
    "path": "src/common/debug.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"common/time.h\"\n\nstatic inline const char* ffFindFileName(const char* file)\n{\n    const char* lastSlash = __builtin_strrchr(file, '/');\n    #ifdef _WIN32\n    if (lastSlash == NULL)\n        lastSlash = __builtin_strrchr(file, '\\\\');\n    #endif\n    if (lastSlash != NULL)\n        return lastSlash + 1;\n    return file;\n}\n\n#ifndef NDEBUG\n    #define FF_DEBUG_PRINT(file_, line_, format_, ...) \\\n        do { \\\n            if (instance.config.display.debugMode) \\\n                fprintf(stderr, \"[%s%4d, %s] \" format_ \"\\n\", ffFindFileName(file_), line_, ffTimeToTimeStr(ffTimeGetNow()), ##__VA_ARGS__); \\\n        } while (0)\n#else\n    #define FF_DEBUG_PRINT(file_, line_, format_, ...) \\\n        do { } while (0)\n#endif\n\n#define FF_DEBUG(format, ...) FF_DEBUG_PRINT(__FILE__, __LINE__, format, ##__VA_ARGS__)\n\n#if _WIN32\nconst char* ffDebugWin32Error(unsigned long errorCode);\nconst char* ffDebugNtStatus(NTSTATUS status);\n#endif\n"
  },
  {
    "path": "src/common/duration.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\nvoid ffDurationAppendNum(uint64_t totalSeconds, FFstrbuf* result);\n"
  },
  {
    "path": "src/common/edidHelper.h",
    "content": "#pragma once\n\n#include <stdint.h>\n#include \"common/FFstrbuf.h\"\n\nvoid ffEdidGetVendorAndModel(const uint8_t edid[128], FFstrbuf* result);\nbool ffEdidGetName(const uint8_t edid[128], FFstrbuf* name);\nvoid ffEdidGetPreferredResolutionAndRefreshRate(const uint8_t edid[128], uint32_t* width, uint32_t* height, double* refreshRate);\nvoid ffEdidGetPhysicalResolution(const uint8_t edid[128], uint32_t* width, uint32_t* height);\nvoid ffEdidGetPhysicalSize(const uint8_t edid[128], uint32_t* width, uint32_t* height); // in mm\nvoid ffEdidGetSerialAndManufactureDate(const uint8_t edid[128], uint32_t* serial, uint16_t* year, uint16_t* week);\nbool ffEdidGetHdrCompatible(const uint8_t* edid, uint32_t length);\nbool ffEdidIsValid(const uint8_t edid[128], uint32_t length);\n"
  },
  {
    "path": "src/common/ffdata.h",
    "content": "#pragma once\n\n#include \"common/FFstrbuf.h\"\n\ntypedef enum __attribute__((__packed__)) FFDataResultDocType\n{\n    FF_RESULT_DOC_TYPE_DEFAULT = 0,\n    FF_RESULT_DOC_TYPE_JSON,\n    FF_RESULT_DOC_TYPE_CONFIG,\n    FF_RESULT_DOC_TYPE_CONFIG_FULL,\n} FFDataResultDocType;\n\n// FFdata aggregates configuration, generation parameters, and output state used by fastfetch.\n// It holds the parsed configuration document, a mutable JSON document for results, and related metadata.\ntypedef struct FFdata\n{\n    yyjson_doc* configDoc; // Parsed JSON configuration document\n    yyjson_mut_doc* resultDoc; // Mutable JSON document for storing results\n    FFstrbuf structure; // Custom output structure from command line\n    FFstrbuf structureDisabled; // Disabled modules in the output structure from command line\n    FFstrbuf genConfigPath; // Path to generate configuration file\n    FFDataResultDocType docType; // Type of result document\n    bool configLoaded;\n} FFdata;\n"
  },
  {
    "path": "src/common/font.h",
    "content": "#pragma once\n\n#include \"common/FFstrbuf.h\"\n#include \"common/FFlist.h\"\n\ntypedef struct FFfont\n{\n    FFstrbuf pretty;\n    FFstrbuf name;\n    FFstrbuf size;\n    FFlist styles;\n} FFfont;\n\nvoid ffFontInit(FFfont* font);\nvoid ffFontInitQt(FFfont* font, const char* data);\nvoid ffFontInitPango(FFfont* font, const char* data);\nvoid ffFontInitValues(FFfont* font, const char* name, const char* size);\nvoid ffFontInitXlfd(FFfont* font, const char* xlfd);\nvoid ffFontInitXft(FFfont* font, const char* xft);\nvoid ffFontInitMoveValues(FFfont* font, FFstrbuf* name, FFstrbuf* size, FFstrbuf* style);\nvoid ffFontInitWithSpace(FFfont* font, const char* rawName);\nvoid ffFontDestroy(FFfont* font);\n\nstatic inline void ffFontInitCopy(FFfont* font, const char* name)\n{\n    ffFontInitValues(font, name, NULL);\n}\n"
  },
  {
    "path": "src/common/format.h",
    "content": "#pragma once\n\n#include \"common/argType.h\"\n\ntypedef struct FFformatarg\n{\n    FFArgType type;\n    const void* value;\n    const char* name; // argument name, must start with an alphabet\n} FFformatarg;\n\nvoid ffFormatAppendFormatArg(FFstrbuf* buffer, const FFformatarg* formatarg);\nvoid ffParseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint32_t numArgs, const FFformatarg* arguments);\n#define FF_PARSE_FORMAT_STRING_CHECKED(buffer, formatstr, arguments) \\\n    ffParseFormatString((buffer), (formatstr), sizeof(arguments) / sizeof(*arguments), (arguments));\n"
  },
  {
    "path": "src/common/frequency.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\nbool ffFreqAppendNum(uint32_t mhz, FFstrbuf* result);\n"
  },
  {
    "path": "src/common/haiku/version.cpp",
    "content": "extern \"C\" {\n#include \"version.h\"\n}\n\n#include <File.h>\n#include <AppFileInfo.h>\n\nbool ffGetFileVersion(const char* filePath, FFstrbuf* version)\n{\n    BFile f(filePath, B_READ_ONLY);\n    if (f.InitCheck() != B_OK)\n        return false;\n\n    BAppFileInfo fileInfo(&f);\n    if (f.InitCheck() != B_OK)\n        return false;\n\n    version_info info;\n    if (fileInfo.GetVersionInfo(&info, B_SYSTEM_VERSION_KIND) != B_OK)\n        return false;\n\n    ffStrbufSetF(version, \"%d.%d.%d\", (int)info.major, (int)info.middle, (int)info.minor);\n    return true;\n}\n"
  },
  {
    "path": "src/common/haiku/version.h",
    "content": "#include \"common/FFstrbuf.h\"\n\nbool ffGetFileVersion(const char* filePath, FFstrbuf* version);\n"
  },
  {
    "path": "src/common/impl/FFPlatform.c",
    "content": "#include \"FFPlatform_private.h\"\n#include \"common/stringUtils.h\"\n#include \"common/io.h\"\n#include \"detection/version/version.h\"\n\nvoid ffPlatformInit(FFPlatform* platform)\n{\n    ffStrbufInit(&platform->homeDir);\n    ffStrbufInit(&platform->cacheDir);\n    ffListInit(&platform->configDirs, sizeof(FFstrbuf));\n    ffListInit(&platform->dataDirs, sizeof(FFstrbuf));\n    ffStrbufInit(&platform->exePath);\n    ffStrbufInit(&platform->cwd);\n\n    ffStrbufInit(&platform->userName);\n    ffStrbufInit(&platform->fullUserName);\n    ffStrbufInit(&platform->hostName);\n    ffStrbufInit(&platform->userShell);\n\n    #ifdef _WIN32\n    ffStrbufInit(&platform->sid);\n    #endif\n\n    FFPlatformSysinfo* info = &platform->sysinfo;\n\n    ffStrbufInit(&info->name);\n    ffStrbufInit(&info->release);\n    ffStrbufInit(&info->version);\n    ffStrbufInit(&info->architecture);\n\n    ffPlatformInitImpl(platform);\n\n    if(info->name.length == 0)\n        ffStrbufSetStatic(&info->name, ffVersionResult.sysName);\n\n    if(info->architecture.length == 0)\n        ffStrbufSetStatic(&info->architecture, ffVersionResult.architecture);\n}\n\nvoid ffPlatformDestroy(FFPlatform* platform)\n{\n    ffStrbufDestroy(&platform->homeDir);\n    ffStrbufDestroy(&platform->cacheDir);\n\n    FF_LIST_FOR_EACH(FFstrbuf, dir, platform->configDirs)\n        ffStrbufDestroy(dir);\n    ffListDestroy(&platform->configDirs);\n\n    FF_LIST_FOR_EACH(FFstrbuf, dir, platform->dataDirs)\n        ffStrbufDestroy(dir);\n    ffListDestroy(&platform->dataDirs);\n    ffStrbufDestroy(&platform->exePath);\n    ffStrbufDestroy(&platform->cwd);\n\n    ffStrbufDestroy(&platform->userName);\n    ffStrbufDestroy(&platform->hostName);\n    ffStrbufDestroy(&platform->userShell);\n    ffStrbufDestroy(&platform->fullUserName);\n\n    #ifdef _WIN32\n    ffStrbufDestroy(&platform->sid);\n    #endif\n\n    FFPlatformSysinfo* info = &platform->sysinfo;\n    ffStrbufDestroy(&info->architecture);\n    ffStrbufDestroy(&info->name);\n    ffStrbufDestroy(&info->release);\n    ffStrbufDestroy(&info->version);\n}\n\nvoid ffPlatformPathAddAbsolute(FFlist* dirs, const char* path)\n{\n    if (!ffPathExists(path, FF_PATHTYPE_DIRECTORY))\n        return;\n\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreateS(path);\n    ffStrbufEnsureEndsWithC(&buffer, '/');\n    if (!ffListContains(dirs, &buffer, (void*) ffStrbufEqual))\n        ffStrbufInitMove((FFstrbuf*) ffListAdd(dirs), &buffer);\n}\n\nvoid ffPlatformPathAddHome(FFlist* dirs, const FFPlatform* platform, const char* suffix)\n{\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreateA(64);\n    ffStrbufAppend(&buffer, &platform->homeDir);\n    ffStrbufAppendS(&buffer, suffix);\n    ffStrbufEnsureEndsWithC(&buffer, '/');\n    if (ffPathExists(buffer.chars, FF_PATHTYPE_DIRECTORY) && !ffListContains(dirs, &buffer, (void*) ffStrbufEqual))\n        ffStrbufInitMove((FFstrbuf*) ffListAdd(dirs), &buffer);\n}\n"
  },
  {
    "path": "src/common/impl/FFPlatform_private.h",
    "content": "#pragma once\n\n#include \"common/FFPlatform.h\"\n\nvoid ffPlatformInitImpl(FFPlatform* platform);\n\nvoid ffPlatformPathAddAbsolute(FFlist* dirs, const char* path);\nvoid ffPlatformPathAddHome(FFlist* dirs, const FFPlatform* platform, const char* suffix);\n"
  },
  {
    "path": "src/common/impl/FFPlatform_unix.c",
    "content": "#include \"FFPlatform_private.h\"\n#include \"common/FFstrbuf.h\"\n#include \"common/arrayUtils.h\"\n#include \"common/stringUtils.h\"\n#include \"common/io.h\"\n#include \"fastfetch_config.h\"\n\n#include <unistd.h>\n#include <pwd.h>\n#include <limits.h>\n#include <sys/utsname.h>\n#include <paths.h>\n\n#ifdef __APPLE__\n    #include <mach-o/dyld.h>\n    #include <sys/sysctl.h>\n#elif defined(__FreeBSD__) || defined(__NetBSD__)\n    #include <sys/sysctl.h>\n#elif defined(__OpenBSD__)\n    #include <sys/sysctl.h>\n    #include <kvm.h>\n    #include \"common/path.h\"\n#elif defined(__HAIKU__)\n    #include <image.h>\n    #include <OS.h>\n#endif\n\nstatic void getExePath(FFPlatform* platform)\n{\n    char exePath[PATH_MAX];\n    #if defined(__linux__) || defined (__GNU__)\n        ssize_t exePathLen = readlink(\"/proc/self/exe\", exePath, sizeof(exePath) - 1);\n        if (exePathLen >= 0)\n            exePath[exePathLen] = '\\0';\n    #elif defined(__APPLE__)\n        uint32_t exePathLen = sizeof(exePath);\n        if (_NSGetExecutablePath(exePath, &exePathLen) == 0)\n            exePathLen = (uint32_t) strlen(exePath);\n        else\n            exePathLen = 0;\n    #elif defined(__FreeBSD__) || defined(__NetBSD__)\n        size_t exePathLen = sizeof(exePath);\n        if(sysctl(\n            (int[]){CTL_KERN,\n            #ifdef __FreeBSD__\n                KERN_PROC, KERN_PROC_PATHNAME, (pid_t) platform->pid\n            #else\n                KERN_PROC_ARGS, platform->pid, KERN_PROC_PATHNAME\n            #endif\n            }, 4,\n            exePath, &exePathLen,\n            NULL, 0\n        ) < 0)\n            exePathLen = 0;\n        else\n            exePathLen--; // remove terminating NUL\n    #elif defined(__OpenBSD__)\n        // OpenBSD doesn't have a reliable way to get the executable path.\n        // Current implementation uses argv[0], which can be easily spoofed.\n        // See #2195\n        size_t exePathLen = 0;\n        kvm_t* kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, NULL);\n        if (kd)\n        {\n            int kpCount;\n            struct kinfo_proc* kp = kvm_getprocs(kd, KERN_PROC_PID, (pid_t) platform->pid, sizeof(*kp), &kpCount);\n            if (kp && kpCount == 1)\n            {\n                char** argv = kvm_getargv(kd, kp, 0);\n                if (argv && argv[0])\n                {\n                    char* arg0 = argv[0];\n                    if (arg0[0])\n                    {\n                        if (strchr(arg0, '/') != NULL) // likely a path (absolute or relative)\n                        {\n                            exePathLen = strlen(arg0);\n                            if (exePathLen < ARRAY_SIZE(exePath))\n                            {\n                                memcpy(exePath, arg0, exePathLen);\n                                exePath[exePathLen] = '\\0';\n                            }\n                            else\n                                exePathLen = 0;\n                        }\n                        else\n                        {\n                            FF_STRBUF_AUTO_DESTROY tmpPath = ffStrbufCreate();\n                            if (ffFindExecutableInPath(arg0, &tmpPath) == NULL && tmpPath.length < ARRAY_SIZE(exePath))\n                            {\n                                memcpy(exePath, tmpPath.chars, tmpPath.length + 1);\n                                exePathLen = tmpPath.length;\n                            }\n                        }\n\n                        if (exePathLen > 0)\n                        {\n                            struct stat st;\n                            if (stat(exePath, &st) == 0 && S_ISREG(st.st_mode))\n                            {\n                                int cntp;\n                                struct kinfo_file* kf = kvm_getfiles(kd, KERN_FILE_BYPID, platform->pid, sizeof(*kf), &cntp);\n                                if (kf)\n                                {\n                                    int i;\n                                    for (i = 0; i < cntp; i++)\n                                    {\n                                        if (kf[i].fd_fd == KERN_FILE_TEXT)\n                                        {\n                                            // KERN_FILE_TEXT is the executable file, not a shared library, and should be unique in the list.\n                                            if (st.st_dev != (dev_t)kf[i].va_fsid || st.st_ino != (ino_t)kf[i].va_fileid)\n                                                i = -1;\n                                            break;\n                                        }\n                                    }\n                                    if (i < 0)\n                                        exePathLen = 0;\n                                }\n                                else\n                                {\n                                    // If we can't get the list of open files, we can't verify that the file is actually the executable\n                                    // Assume it is\n                                }\n                            }\n                            else\n                                exePathLen = 0;\n                        }\n                    }\n                }\n            }\n            kvm_close(kd);\n        }\n    #elif defined(__sun)\n        ssize_t exePathLen = readlink(\"/proc/self/path/a.out\", exePath, sizeof(exePath) - 1);\n        if (exePathLen >= 0)\n            exePath[exePathLen] = '\\0';\n    #elif defined(__HAIKU__)\n        size_t exePathLen = 0;\n        image_info info;\n        int32 cookie = 0;\n\n        while (get_next_image_info(B_CURRENT_TEAM, &cookie, &info) == B_OK) {\n            if (info.type == B_APP_IMAGE) {\n                exePathLen = strlcpy(exePath, info.name, sizeof(exePath));\n                break;\n            }\n        }\n    #endif\n    if (exePathLen > 0)\n    {\n        ffStrbufEnsureFree(&platform->exePath, PATH_MAX);\n        if (realpath(exePath, platform->exePath.chars))\n            ffStrbufRecalculateLength(&platform->exePath);\n        else\n            ffStrbufSetNS(&platform->exePath, (uint32_t) exePathLen, exePath);\n    }\n}\n\nstatic void platformPathAddEnv(FFlist* dirs, const char* env)\n{\n    const char* envValue = getenv(env);\n    if(!ffStrSet(envValue))\n        return;\n\n    FF_STRBUF_AUTO_DESTROY value = ffStrbufCreateA(64);\n    ffStrbufAppendS(&value, envValue);\n\n    uint32_t startIndex = 0;\n    while (startIndex < value.length)\n    {\n        uint32_t colonIndex = ffStrbufNextIndexC(&value, startIndex, ':');\n        value.chars[colonIndex] = '\\0';\n\n        if(!ffStrSet(value.chars + startIndex))\n        {\n            startIndex = colonIndex + 1;\n            continue;\n        }\n\n        ffPlatformPathAddAbsolute(dirs, value.chars + startIndex);\n\n        startIndex = colonIndex + 1;\n    }\n}\n\nstatic void getHomeDir(FFPlatform* platform, const struct passwd* pwd)\n{\n    const char* home = pwd ? pwd->pw_dir : getenv(\"HOME\");\n    ffStrbufAppendS(&platform->homeDir, home);\n    ffStrbufEnsureEndsWithC(&platform->homeDir, '/');\n}\n\nstatic void getCacheDir(FFPlatform* platform)\n{\n    const char* cache = getenv(\"XDG_CACHE_HOME\");\n    if(ffStrSet(cache))\n    {\n        ffStrbufAppendS(&platform->cacheDir, cache);\n        ffStrbufEnsureEndsWithC(&platform->cacheDir, '/');\n    }\n    else\n    {\n        ffStrbufAppend(&platform->cacheDir, &platform->homeDir);\n        ffStrbufAppendS(&platform->cacheDir, \".cache/\");\n    }\n}\n\nstatic void getConfigDirs(FFPlatform* platform)\n{\n    // Always make sure `${XDG_CONFIG_HOME:-$HOME/.config}` is the first entry\n    platformPathAddEnv(&platform->configDirs, \"XDG_CONFIG_HOME\");\n    ffPlatformPathAddHome(&platform->configDirs, platform, \".config/\");\n\n    #if defined(__APPLE__)\n        ffPlatformPathAddHome(&platform->configDirs, platform, \"Library/Preferences/\");\n        ffPlatformPathAddHome(&platform->configDirs, platform, \"Library/Application Support/\");\n    #endif\n    #if defined(__HAIKU__)\n        ffPlatformPathAddHome(&platform->configDirs, platform, \"config/settings/\");\n    #endif\n\n    ffPlatformPathAddHome(&platform->configDirs, platform, \"\");\n    platformPathAddEnv(&platform->configDirs, \"XDG_CONFIG_DIRS\");\n\n    #if !defined(__APPLE__)\n        ffPlatformPathAddAbsolute(&platform->configDirs, FASTFETCH_TARGET_DIR_ETC \"/xdg/\");\n    #endif\n\n    ffPlatformPathAddAbsolute(&platform->configDirs, FASTFETCH_TARGET_DIR_ETC \"/\");\n    ffPlatformPathAddAbsolute(&platform->configDirs, FASTFETCH_TARGET_DIR_INSTALL_SYSCONF \"/\");\n}\n\nstatic void getDataDirs(FFPlatform* platform)\n{\n    platformPathAddEnv(&platform->dataDirs, \"XDG_DATA_HOME\");\n    ffPlatformPathAddHome(&platform->dataDirs, platform, \".local/share/\");\n\n    // Add ${currentExePath}/../share\n    if (platform->exePath.length > 0)\n    {\n        FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateCopy(&platform->exePath);\n        ffStrbufSubstrBeforeLastC(&path, '/');\n        ffStrbufSubstrBeforeLastC(&path, '/');\n        ffStrbufAppendS(&path, \"/share\");\n        ffPlatformPathAddAbsolute(&platform->dataDirs, path.chars);\n    }\n\n    #ifdef __APPLE__\n        ffPlatformPathAddHome(&platform->dataDirs, platform, \"Library/Application Support/\");\n    #endif\n\n    ffPlatformPathAddHome(&platform->dataDirs, platform, \"\");\n    platformPathAddEnv(&platform->dataDirs, \"XDG_DATA_DIRS\");\n#ifdef _PATH_LOCALBASE\n    ffPlatformPathAddAbsolute(&platform->dataDirs, _PATH_LOCALBASE \"/share/\");\n#endif\n    ffPlatformPathAddAbsolute(&platform->dataDirs, FASTFETCH_TARGET_DIR_USR \"/local/share/\");\n    ffPlatformPathAddAbsolute(&platform->dataDirs, FASTFETCH_TARGET_DIR_USR \"/share/\");\n}\n\nstatic void getUserName(FFPlatform* platform, const struct passwd* pwd)\n{\n    if (pwd)\n    {\n        ffStrbufSetS(&platform->userName, pwd->pw_name);\n        ffStrbufSetS(&platform->fullUserName, pwd->pw_gecos);\n        ffStrbufTrimSpace(&platform->fullUserName);\n    }\n    else\n    {\n        ffStrbufSetS(&platform->userName, getenv(\"USER\"));\n    }\n}\n\nstatic void getHostName(FFPlatform* platform, const struct utsname* uts)\n{\n    ffStrbufAppendS(&platform->hostName, uts->nodename);\n}\n\nstatic void getUserShell(FFPlatform* platform, const struct passwd* pwd)\n{\n    const char* shell = getenv(\"SHELL\");\n    if(!ffStrSet(shell) && pwd)\n        shell = pwd->pw_shell;\n\n    ffStrbufAppendS(&platform->userShell, shell);\n}\n\nstatic void getSysinfo(FFPlatformSysinfo* info, const struct utsname* uts)\n{\n    ffStrbufAppendS(&info->name, uts->sysname);\n    ffStrbufAppendS(&info->release, uts->release);\n    ffStrbufAppendS(&info->version, uts->version);\n    #ifdef __HAIKU__\n    /* historical reason */\n    if (ffStrEquals(uts->machine, \"BePC\"))\n        ffStrbufSetStatic(&info->architecture, \"i386\");\n    else\n    #endif\n    ffStrbufAppendS(&info->architecture, uts->machine);\n\n    #if defined(__FreeBSD__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__)\n    size_t length = sizeof(info->pageSize);\n    sysctl((int[]){ CTL_HW, HW_PAGESIZE }, 2, &info->pageSize, &length, NULL, 0);\n    #else\n    info->pageSize = (uint32_t) sysconf(_SC_PAGESIZE);\n    #endif\n}\n\nstatic void getCwd(FFPlatform* platform)\n{\n    char cwd[PATH_MAX];\n    if (getcwd(cwd, sizeof(cwd)) != NULL)\n    {\n        ffStrbufSetS(&platform->cwd, cwd);\n        ffStrbufEnsureEndsWithC(&platform->cwd, '/');\n    }\n}\n\nvoid ffPlatformInitImpl(FFPlatform* platform)\n{\n    platform->pid = (uint32_t) getpid();\n    platform->uid = getuid();\n    struct passwd* pwd = getpwuid(platform->uid);\n\n    struct utsname uts;\n    if(uname(&uts) < 0)\n        memset(&uts, 0, sizeof(uts));\n\n    getExePath(platform);\n    getCwd(platform);\n    getHomeDir(platform, pwd);\n    getCacheDir(platform);\n    getConfigDirs(platform);\n    getDataDirs(platform);\n\n    getUserName(platform, pwd);\n    getHostName(platform, &uts);\n    getUserShell(platform, pwd);\n\n    getSysinfo(&platform->sysinfo, &uts);\n}\n"
  },
  {
    "path": "src/common/impl/FFPlatform_windows.c",
    "content": "#include \"FFPlatform_private.h\"\n#include \"common/io.h\"\n#include \"common/library.h\"\n#include \"common/stringUtils.h\"\n#include \"common/windows/unicode.h\"\n#include \"common/windows/registry.h\"\n#include \"common/windows/nt.h\"\n\n#include <stdalign.h>\n#include <windows.h>\n#include <shlobj.h>\n#include <sddl.h>\n\n#define SECURITY_WIN32 1 // For secext.h\n#include <secext.h>\n\nstatic void getExePath(FFPlatform* platform)\n{\n    wchar_t exePathW[MAX_PATH];\n\n    FF_AUTO_CLOSE_FD HANDLE hPath = CreateFileW(\n        ffGetPeb()->ProcessParameters->ImagePathName.Buffer,\n        GENERIC_READ,\n        FILE_SHARE_READ,\n        NULL,\n        OPEN_EXISTING,\n        FILE_FLAG_BACKUP_SEMANTICS,\n        NULL);\n    if (hPath != INVALID_HANDLE_VALUE)\n    {\n        DWORD len = GetFinalPathNameByHandleW(hPath, exePathW, MAX_PATH, FILE_NAME_NORMALIZED);\n        if (len > 0 && len < MAX_PATH)\n        {\n            ffStrbufSetNWS(&platform->exePath, len, exePathW);\n            if (ffStrbufStartsWithS(&platform->exePath, \"\\\\\\\\?\\\\\"))\n                ffStrbufSubstrAfter(&platform->exePath, 3);\n        }\n    }\n\n    if (platform->exePath.length == 0)\n    {\n        PCUNICODE_STRING imagePathName = &ffGetPeb()->ProcessParameters->ImagePathName;\n        ffStrbufSetNWS(&platform->exePath, imagePathName->Length / sizeof(wchar_t), imagePathName->Buffer);\n    }\n\n    ffStrbufReplaceAllC(&platform->exePath, '\\\\', '/');\n}\n\nstatic void getHomeDir(FFPlatform* platform)\n{\n    PWSTR pPath = NULL;\n    if(SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_Profile, KF_FLAG_DEFAULT, NULL, &pPath)))\n    {\n        ffStrbufSetWS(&platform->homeDir, pPath);\n        ffStrbufReplaceAllC(&platform->homeDir, '\\\\', '/');\n        ffStrbufEnsureEndsWithC(&platform->homeDir, '/');\n    }\n    else\n    {\n        ffStrbufSetS(&platform->homeDir, getenv(\"USERPROFILE\"));\n        ffStrbufReplaceAllC(&platform->homeDir, '\\\\', '/');\n        ffStrbufEnsureEndsWithC(&platform->homeDir, '/');\n    }\n    CoTaskMemFree(pPath);\n}\n\nstatic void getCacheDir(FFPlatform* platform)\n{\n    PWSTR pPath = NULL;\n    if(SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_LocalAppData, KF_FLAG_DEFAULT, NULL, &pPath)))\n    {\n        ffStrbufSetWS(&platform->cacheDir, pPath);\n        ffStrbufReplaceAllC(&platform->cacheDir, '\\\\', '/');\n        ffStrbufEnsureEndsWithC(&platform->cacheDir, '/');\n    }\n    else\n    {\n        ffStrbufAppend(&platform->cacheDir, &platform->homeDir);\n        ffStrbufAppendS(&platform->cacheDir, \"AppData/Local/\");\n    }\n    CoTaskMemFree(pPath);\n}\n\nstatic void platformPathAddKnownFolder(FFlist* dirs, REFKNOWNFOLDERID folderId)\n{\n    PWSTR pPath = NULL;\n    if (SUCCEEDED(SHGetKnownFolderPath(folderId, KF_FLAG_DEFAULT, NULL, &pPath)))\n    {\n        FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreateWS(pPath);\n        CoTaskMemFree(pPath);\n        ffStrbufReplaceAllC(&buffer, '\\\\', '/');\n        ffStrbufEnsureEndsWithC(&buffer, '/');\n        if (!ffListContains(dirs, &buffer, (void*) ffStrbufEqual))\n            ffStrbufInitMove((FFstrbuf*) ffListAdd(dirs), &buffer);\n    }\n}\n\nstatic void platformPathAddEnvSuffix(FFlist* dirs, const char* env, const char* suffix)\n{\n    const char* value = getenv(env);\n    if(!ffStrSet(value))\n        return;\n\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreateA(64);\n    ffStrbufAppendS(&buffer, value);\n    ffStrbufReplaceAllC(&buffer, '\\\\', '/');\n    ffStrbufEnsureEndsWithC(&buffer, '/');\n    if (suffix)\n    {\n        ffStrbufAppendS(&buffer, suffix);\n        ffStrbufEnsureEndsWithC(&buffer, '/');\n    }\n\n    if (ffPathExists(buffer.chars, FF_PATHTYPE_DIRECTORY) && !ffListContains(dirs, &buffer, (void*) ffStrbufEqual))\n        ffStrbufInitMove((FFstrbuf*) ffListAdd(dirs), &buffer);\n}\n\nstatic void getConfigDirs(FFPlatform* platform)\n{\n    if(getenv(\"MSYSTEM\"))\n    {\n        // We are in MSYS2 / Git Bash\n        platformPathAddEnvSuffix(&platform->configDirs, \"HOME\", \".config/\");\n        platformPathAddEnvSuffix(&platform->configDirs, \"HOME\", NULL);\n        platformPathAddEnvSuffix(&platform->configDirs, \"MINGW_PREFIX\", \"etc\");\n    }\n\n    ffPlatformPathAddHome(&platform->configDirs, platform, \".config/\");\n    platformPathAddKnownFolder(&platform->configDirs, &FOLDERID_ProgramData);\n    platformPathAddKnownFolder(&platform->configDirs, &FOLDERID_RoamingAppData);\n    platformPathAddKnownFolder(&platform->configDirs, &FOLDERID_LocalAppData);\n    ffPlatformPathAddHome(&platform->configDirs, platform, \"\");\n}\n\nstatic void getDataDirs(FFPlatform* platform)\n{\n    if(getenv(\"MSYSTEM\") && getenv(\"HOME\"))\n    {\n        // We are in MSYS2 / Git Bash\n        platformPathAddEnvSuffix(&platform->dataDirs, \"HOME\", \".local/share/\");\n        platformPathAddEnvSuffix(&platform->dataDirs, \"HOME\", NULL);\n        platformPathAddEnvSuffix(&platform->dataDirs, \"MINGW_PREFIX\", \"share\");\n    }\n    ffPlatformPathAddHome(&platform->dataDirs, platform, \".local/share/\");\n    platformPathAddKnownFolder(&platform->dataDirs, &FOLDERID_ProgramData);\n    platformPathAddKnownFolder(&platform->dataDirs, &FOLDERID_RoamingAppData);\n    platformPathAddKnownFolder(&platform->dataDirs, &FOLDERID_LocalAppData);\n    ffPlatformPathAddHome(&platform->dataDirs, platform, \"\");\n}\n\nstatic void getUserName(FFPlatform* platform)\n{\n    wchar_t buffer[256];\n    DWORD size = ARRAY_SIZE(buffer);\n    if (GetUserNameExW(NameDisplay, buffer, &size))\n        ffStrbufSetWS(&platform->fullUserName, buffer);\n\n    size = ARRAY_SIZE(buffer);\n    if (GetUserNameW(buffer, &size)) // GetUserNameExW(10002)?\n        ffStrbufSetWS(&platform->userName, buffer);\n    else\n        ffStrbufSetS(&platform->userName, getenv(\"USERNAME\"));\n\n    alignas(TOKEN_USER) char buf[SECURITY_MAX_SID_SIZE + sizeof(TOKEN_USER)];\n    if (NT_SUCCESS(NtQueryInformationToken(NtCurrentProcessToken(), TokenUser, buf, sizeof(buf), &size)))\n    {\n        TOKEN_USER* tokenUser = (TOKEN_USER*) buf;\n        UNICODE_STRING sidString = { .Buffer = buffer, .Length = 0, .MaximumLength = sizeof(buffer) };\n        if (NT_SUCCESS(RtlConvertSidToUnicodeString(&sidString, tokenUser->User.Sid, FALSE)))\n            ffStrbufSetNWS(&platform->sid, sidString.Length / sizeof(wchar_t), sidString.Buffer);\n    }\n}\n\nstatic void getHostName(FFPlatform* platform)\n{\n    wchar_t buffer[256];\n    DWORD len = ARRAY_SIZE(buffer);\n    if (GetComputerNameExW(ComputerNameDnsHostname, buffer, &len) && len > 0)\n        ffStrbufSetNWS(&platform->hostName, len, buffer);\n    else\n    {\n        len = ARRAY_SIZE(buffer);\n        if (GetComputerNameExW(ComputerNameNetBIOS, buffer, &len) && len > 0)\n            ffStrbufSetNWS(&platform->hostName, len, buffer);\n    }\n}\n\nstatic void getUserShell(FFPlatform* platform)\n{\n    // Works in MSYS2\n    const char* userShell = getenv(\"SHELL\");\n    if (userShell)\n    {\n        ffStrbufAppendS(&platform->userShell, userShell);\n        ffStrbufReplaceAllC(&platform->userShell, '\\\\', '/');\n    }\n}\n\nstatic const char* detectWine(void)\n{\n    const char * __cdecl wine_get_version(void);\n    void* hntdll = ffLibraryGetModule(L\"ntdll.dll\");\n    if (!hntdll) return NULL;\n    FF_LIBRARY_LOAD_SYMBOL_LAZY(hntdll, wine_get_version);\n    if (!ffwine_get_version) return NULL;\n    return ffwine_get_version();\n}\n\nstatic void getSystemReleaseAndVersion(FFPlatformSysinfo* info)\n{\n    FF_AUTO_CLOSE_FD HANDLE hKey = NULL;\n    if(!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, L\"SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\", &hKey, NULL))\n        return;\n\n    uint32_t ubr = 0;\n    ffRegReadValues(hKey, 2, (FFRegValueArg[]) {\n        FF_ARG(ubr, L\"UBR\"),\n        FF_ARG(info->version, L\"BuildLabEx\"),\n    }, NULL);\n\n    ffStrbufSetF(&info->release,\n        \"%u.%u.%u.%u\",\n        (unsigned) SharedUserData->NtMajorVersion,\n        (unsigned) SharedUserData->NtMinorVersion,\n        (unsigned) SharedUserData->NtBuildNumber,\n        (unsigned) ubr);\n\n    const char* wineVersion = detectWine();\n    if (wineVersion)\n        ffStrbufSetF(&info->name, \"Wine_%s\", wineVersion);\n    else\n        ffStrbufSetStatic(&info->name, \"WIN32_NT\");\n}\n\nstatic void getSystemPageSize(FFPlatformSysinfo* info)\n{\n    SYSTEM_BASIC_INFORMATION sbi;\n    if (NT_SUCCESS(NtQuerySystemInformation(SystemBasicInformation, &sbi, sizeof(sbi), NULL)))\n        info->pageSize = sbi.PhysicalPageSize;\n    else\n        info->pageSize = 4096;\n}\n\nstatic void getSystemArchitecture(FFPlatformSysinfo* info)\n{\n    SYSTEM_PROCESSOR_INFORMATION spi;\n    if (NT_SUCCESS(NtQuerySystemInformation(SystemProcessorInformation, &spi, sizeof(spi), NULL)))\n    {\n        switch (spi.ProcessorArchitecture)\n        {\n        case PROCESSOR_ARCHITECTURE_AMD64:\n            ffStrbufSetStatic(&info->architecture, \"x86_64\");\n            break;\n        case PROCESSOR_ARCHITECTURE_IA64:\n            ffStrbufSetStatic(&info->architecture, \"ia64\");\n            break;\n        case PROCESSOR_ARCHITECTURE_INTEL:\n            switch (spi.ProcessorLevel)\n            {\n                case 4:\n                    ffStrbufSetStatic(&info->architecture, \"i486\");\n                    break;\n                case 5:\n                    ffStrbufSetStatic(&info->architecture, \"i586\");\n                    break;\n                case 6:\n                    ffStrbufSetStatic(&info->architecture, \"i686\");\n                    break;\n                default:\n                    ffStrbufSetStatic(&info->architecture, \"i386\");\n                    break;\n            }\n            break;\n        case PROCESSOR_ARCHITECTURE_ARM64:\n            ffStrbufSetStatic(&info->architecture, \"aarch64\");\n            break;\n        case PROCESSOR_ARCHITECTURE_ARM:\n            ffStrbufSetStatic(&info->architecture, \"arm\");\n            break;\n        case PROCESSOR_ARCHITECTURE_PPC:\n            ffStrbufSetStatic(&info->architecture, \"ppc\");\n            break;\n        case PROCESSOR_ARCHITECTURE_MIPS:\n            ffStrbufSetStatic(&info->architecture, \"mips\");\n            break;\n        case PROCESSOR_ARCHITECTURE_ALPHA:\n            ffStrbufSetStatic(&info->architecture, \"alpha\");\n            break;\n        case PROCESSOR_ARCHITECTURE_ALPHA64:\n            ffStrbufSetStatic(&info->architecture, \"alpha64\");\n            break;\n        case PROCESSOR_ARCHITECTURE_UNKNOWN:\n        default:\n            ffStrbufSetStatic(&info->architecture, \"unknown\");\n            break;\n        }\n    }\n}\n\nstatic void getCwd(FFPlatform* platform)\n{\n    PCURDIR cwd = &ffGetPeb()->ProcessParameters->CurrentDirectory;\n    ffStrbufSetNWS(&platform->cwd, cwd->DosPath.Length / sizeof(WCHAR), cwd->DosPath.Buffer);\n    ffStrbufReplaceAllC(&platform->cwd, '\\\\', '/');\n    ffStrbufEnsureEndsWithC(&platform->cwd, '/');\n}\n\nvoid ffPlatformInitImpl(FFPlatform* platform)\n{\n    platform->pid = (uint32_t) (uintptr_t) ffGetTeb()->ClientId.UniqueProcess;\n    getExePath(platform);\n    getCwd(platform);\n    getHomeDir(platform);\n    getCacheDir(platform);\n    getConfigDirs(platform);\n    getDataDirs(platform);\n\n    getUserName(platform);\n    getHostName(platform);\n    getUserShell(platform);\n\n    getSystemReleaseAndVersion(&platform->sysinfo);\n    getSystemArchitecture(&platform->sysinfo);\n    getSystemPageSize(&platform->sysinfo);\n}\n"
  },
  {
    "path": "src/common/impl/FFlist.c",
    "content": "#include \"common/FFlist.h\"\n\n#include <stdlib.h>\n#include <string.h>\n\nvoid* ffListAdd(FFlist* list)\n{\n    if(list->length == list->capacity)\n        ffListReserve(list, list->capacity == 0 ? FF_LIST_DEFAULT_ALLOC : list->capacity * 2);\n\n    ++list->length;\n    return ffListGet(list, list->length - 1);\n}\n\nbool ffListShift(FFlist* list, void* result)\n{\n    if(list->length == 0)\n        return false;\n\n    memcpy(result, list->data, list->elementSize);\n    memmove(list->data, list->data + list->elementSize, (size_t) list->elementSize * (list->length - 1));\n    --list->length;\n    return true;\n}\n\nbool ffListPop(FFlist* list, void* result)\n{\n    if(list->length == 0)\n        return false;\n\n    memcpy(result, ffListGet(list, list->length - 1), list->elementSize);\n    --list->length;\n    return true;\n}\n"
  },
  {
    "path": "src/common/impl/FFstrbuf.c",
    "content": "#include \"common/FFstrbuf.h\"\n#include \"common/mallocHelper.h\"\n\n#include <ctype.h>\n#include <inttypes.h>\n#include <math.h>\n\nchar* CHAR_NULL_PTR = \"\";\n\nvoid ffStrbufInitA(FFstrbuf* strbuf, uint32_t allocate)\n{\n    strbuf->allocated = allocate;\n\n    if(strbuf->allocated > 0)\n        strbuf->chars = (char*) malloc(sizeof(char) * strbuf->allocated);\n\n    //This will set the length to zero and the null byte.\n    ffStrbufClear(strbuf);\n}\n\nvoid ffStrbufInitVF(FFstrbuf* strbuf, const char* format, va_list arguments)\n{\n    assert(format != NULL);\n\n    char* buffer = NULL;\n    int len = vasprintf(&buffer, format, arguments);\n    assert(len >= 0);\n\n    ffStrbufInitMoveNS(strbuf, (uint32_t)len, buffer);\n}\n\n// Takes ownership of `heapStr`. The caller must not free `heapStr` after calling this\n// function; the memory will be managed and freed via the associated FFstrbuf.\nvoid ffStrbufInitMoveNS(FFstrbuf* strbuf, uint32_t length, char* heapStr)\n{\n    assert(heapStr != NULL);\n\n    strbuf->length = length;\n    size_t allocSize = ffMallocUsableSize(heapStr);\n    if (allocSize == 0)\n        allocSize = length + 1;\n    else if (allocSize > UINT32_MAX)\n        allocSize = UINT32_MAX;\n    strbuf->allocated = (uint32_t) allocSize;\n    strbuf->chars = heapStr;\n}\n\nvoid ffStrbufEnsureFree(FFstrbuf* strbuf, uint32_t free)\n{\n    if(ffStrbufGetFree(strbuf) >= free && !(strbuf->allocated == 0 && strbuf->length > 0))\n        return;\n\n    uint32_t allocate = strbuf->allocated;\n    if(allocate < FASTFETCH_STRBUF_DEFAULT_ALLOC)\n        allocate = FASTFETCH_STRBUF_DEFAULT_ALLOC;\n\n    while((strbuf->length + free + 1) > allocate) // + 1 for the null byte\n        allocate *= 2;\n\n    if(strbuf->allocated == 0)\n    {\n        char* newbuf = malloc(sizeof(*strbuf->chars) * allocate);\n        if(strbuf->length == 0)\n            *newbuf = '\\0';\n        else\n            memcpy(newbuf, strbuf->chars, strbuf->length + 1);\n        strbuf->chars = newbuf;\n    }\n    else\n        strbuf->chars = realloc(strbuf->chars, sizeof(*strbuf->chars) * allocate);\n\n    strbuf->allocated = allocate;\n}\n\n// Ensure that at least `free` bytes are available in the buffer besides the current length\n// for an empty buffer, free + 1 length memory will be allocated(+1 for the NUL)\nvoid ffStrbufEnsureFixedLengthFree(FFstrbuf* strbuf, uint32_t free)\n{\n    uint32_t oldFree = ffStrbufGetFree(strbuf);\n    if (oldFree >= free && !(strbuf->allocated == 0 && strbuf->length > 0))\n        return;\n\n    uint32_t newCap = strbuf->allocated + (free - oldFree);\n\n    if(strbuf->allocated == 0)\n    {\n        newCap += strbuf->length + 1;\n        char* newbuf = malloc(sizeof(*strbuf->chars) * newCap);\n        if(strbuf->length == 0)\n            *newbuf = '\\0';\n        else\n            memcpy(newbuf, strbuf->chars, strbuf->length + 1);\n        strbuf->chars = newbuf;\n    }\n    else\n        strbuf->chars = realloc(strbuf->chars, sizeof(*strbuf->chars) * newCap);\n\n    strbuf->allocated = newCap;\n}\n\nvoid ffStrbufClear(FFstrbuf* strbuf)\n{\n    assert(strbuf != NULL);\n\n    if(strbuf->allocated == 0)\n        strbuf->chars = CHAR_NULL_PTR;\n    else\n        strbuf->chars[0] = '\\0';\n\n    strbuf->length = 0;\n}\n\nvoid ffStrbufAppendC(FFstrbuf* strbuf, char c)\n{\n    ffStrbufEnsureFree(strbuf, 1);\n    strbuf->chars[strbuf->length++] = c;\n    strbuf->chars[strbuf->length] = '\\0';\n}\n\nvoid ffStrbufAppendNC(FFstrbuf* strbuf, uint32_t num, char c)\n{\n    if (num == 0) return;\n\n    ffStrbufEnsureFree(strbuf, num);\n    memset(&strbuf->chars[strbuf->length], c, num);\n    strbuf->length += num;\n    strbuf->chars[strbuf->length] = '\\0';\n}\n\nvoid ffStrbufAppendNS(FFstrbuf* strbuf, uint32_t length, const char* value)\n{\n    if(value == NULL || length == 0)\n        return;\n\n    ffStrbufEnsureFree(strbuf, length);\n    memcpy(&strbuf->chars[strbuf->length], value, length);\n    strbuf->length += length;\n    strbuf->chars[strbuf->length] = '\\0';\n}\n\nvoid ffStrbufAppendTransformS(FFstrbuf* strbuf, const char* value, int(*transformFunc)(int))\n{\n    if(value == NULL)\n        return;\n\n    //Ensure capacity > 0 or the modification below will fail\n    uint32_t length = (uint32_t) strlen(value);\n    if(length == 0)\n        return;\n\n    ffStrbufEnsureFree(strbuf, length);\n    for(uint32_t i = 0; value[i] != '\\0'; i++)\n    {\n        strbuf->chars[strbuf->length++] = (char) transformFunc(value[i]);\n    }\n    strbuf->chars[strbuf->length] = '\\0';\n}\n\nvoid ffStrbufAppendVF(FFstrbuf* strbuf, const char* format, va_list arguments)\n{\n    assert(format != NULL);\n\n    va_list copy;\n    va_copy(copy, arguments);\n\n    uint32_t free = ffStrbufGetFree(strbuf);\n    int written = vsnprintf(strbuf->chars + strbuf->length, strbuf->allocated > 0 ? free + 1 : 0, format, arguments);\n\n    if(written > 0 && (uint32_t) written > free)\n    {\n        ffStrbufEnsureFree(strbuf, (uint32_t) written);\n        written = vsnprintf(strbuf->chars + strbuf->length, (uint32_t) written + 1, format, copy);\n    }\n\n    va_end(copy);\n\n    if(written > 0)\n        strbuf->length += (uint32_t) written;\n}\n\nconst char* ffStrbufAppendSUntilC(FFstrbuf* strbuf, const char* value, char until)\n{\n    if(value == NULL)\n        return NULL;\n\n    const char* end = strchr(value, until);\n    if(end == NULL)\n        ffStrbufAppendS(strbuf, value);\n    else\n        ffStrbufAppendNS(strbuf, (uint32_t) (end - value), value);\n    return end;\n}\n\nvoid ffStrbufSetF(FFstrbuf* strbuf, const char* format, ...)\n{\n    assert(format != NULL);\n\n    va_list arguments;\n    va_start(arguments, format);\n\n    if(strbuf->allocated == 0) {\n        ffStrbufInitVF(strbuf, format, arguments);\n        va_end(arguments);\n        return;\n    }\n\n    ffStrbufClear(strbuf);\n    ffStrbufAppendVF(strbuf, format, arguments);\n    va_end(arguments);\n}\n\nvoid ffStrbufAppendF(FFstrbuf* strbuf, const char* format, ...)\n{\n    assert(format != NULL);\n\n    va_list arguments;\n    va_start(arguments, format);\n    ffStrbufAppendVF(strbuf, format, arguments);\n    va_end(arguments);\n}\n\nvoid ffStrbufPrependNS(FFstrbuf* strbuf, uint32_t length, const char* value)\n{\n    if(value == NULL || length == 0)\n        return;\n\n    ffStrbufEnsureFree(strbuf, length);\n    memmove(strbuf->chars + length, strbuf->chars, strbuf->length + 1); // + 1 for the null byte\n    memcpy(strbuf->chars, value, length);\n    strbuf->length += length;\n}\n\nvoid ffStrbufPrependC(FFstrbuf* strbuf, char c)\n{\n    ffStrbufEnsureFree(strbuf, 1);\n    memmove(strbuf->chars + 1, strbuf->chars, strbuf->length + 1); // + 1 for the null byte\n    strbuf->chars[0] = c;\n    strbuf->length += 1;\n}\n\nvoid ffStrbufSetNS(FFstrbuf* strbuf, uint32_t length, const char* value)\n{\n    assert(strbuf != NULL);\n\n    if (length == 0)\n    {\n        ffStrbufClear(strbuf);\n        return;\n    }\n\n    assert(value != NULL);\n\n    if (strbuf->allocated < length + 1)\n    {\n        if (strbuf->allocated > 0)\n            free(strbuf->chars);\n        strbuf->allocated = length + 1;\n        strbuf->chars = malloc(sizeof(char) * strbuf->allocated);\n    }\n\n    memcpy(strbuf->chars, value, length);\n    strbuf->length = length;\n    strbuf->chars[length] = '\\0';\n}\n\nvoid ffStrbufSet(FFstrbuf* strbuf, const FFstrbuf* value)\n{\n    assert(value && value != strbuf);\n\n    if (value->length == 0)\n    {\n        ffStrbufClear(strbuf);\n        return;\n    }\n\n    if (value->allocated == 0) // static string\n    {\n        if (strbuf->allocated != 0)\n        {\n            free(strbuf->chars);\n            strbuf->allocated = 0;\n        }\n        strbuf->chars = value->chars;\n        strbuf->length = value->length;\n        return;\n    }\n    ffStrbufSetNS(strbuf, value->length, value->chars);\n}\n\nvoid ffStrbufTrimLeft(FFstrbuf* strbuf, char c)\n{\n    if(strbuf->length == 0)\n        return;\n\n    uint32_t index = 0;\n    while(index < strbuf->length && strbuf->chars[index] == c)\n        ++index;\n\n    if(index == 0)\n        return;\n\n    if(strbuf->allocated == 0)\n    {\n        //static string\n        strbuf->length -= index;\n        strbuf->chars += index;\n        return;\n    }\n\n    memmove(strbuf->chars, strbuf->chars + index, strbuf->length - index);\n    strbuf->length -= index;\n    strbuf->chars[strbuf->length] = '\\0';\n}\n\nvoid ffStrbufTrimRight(FFstrbuf* strbuf, char c)\n{\n    if (strbuf->length == 0)\n        return;\n\n    if (!ffStrbufEndsWithC(strbuf, c))\n        return;\n\n    do\n        --strbuf->length;\n    while (ffStrbufEndsWithC(strbuf, c));\n\n    if (strbuf->allocated == 0)\n    {\n        //static string\n        ffStrbufInitNS(strbuf, strbuf->length, strbuf->chars);\n        return;\n    }\n\n    strbuf->chars[strbuf->length] = '\\0';\n}\n\nvoid ffStrbufTrimLeftSpace(FFstrbuf* strbuf)\n{\n    if(strbuf->length == 0)\n        return;\n\n    uint32_t index = 0;\n    while(index < strbuf->length && isspace(strbuf->chars[index]))\n        ++index;\n\n    if(index == 0)\n        return;\n\n    if(strbuf->allocated == 0)\n    {\n        //static string\n        strbuf->length -= index;\n        strbuf->chars += index;\n        return;\n    }\n\n    memmove(strbuf->chars, strbuf->chars + index, strbuf->length - index);\n    strbuf->length -= index;\n    strbuf->chars[strbuf->length] = '\\0';\n}\n\nvoid ffStrbufTrimRightSpace(FFstrbuf* strbuf)\n{\n    if (strbuf->length == 0)\n        return;\n\n    if (!ffStrbufEndsWithFn(strbuf, isspace))\n        return;\n\n    do\n        --strbuf->length;\n    while (ffStrbufEndsWithFn(strbuf, isspace));\n\n    if (strbuf->allocated == 0)\n    {\n        //static string\n        ffStrbufInitNS(strbuf, strbuf->length, strbuf->chars);\n        return;\n    }\n\n    strbuf->chars[strbuf->length] = '\\0';\n}\n\nbool ffStrbufRemoveSubstr(FFstrbuf* strbuf, uint32_t startIndex, uint32_t endIndex)\n{\n    if(startIndex > strbuf->length || startIndex >= endIndex)\n        return false;\n\n    if(endIndex > strbuf->length)\n    {\n        ffStrbufSubstrBefore(strbuf, startIndex);\n        return true;\n    }\n\n    ffStrbufEnsureFree(strbuf, 0);\n    memmove(strbuf->chars + startIndex, strbuf->chars + endIndex, strbuf->length - endIndex);\n    strbuf->length -= (endIndex - startIndex);\n    strbuf->chars[strbuf->length] = '\\0';\n    return true;\n}\n\nvoid ffStrbufRemoveS(FFstrbuf* strbuf, const char* str)\n{\n    uint32_t stringLength = (uint32_t) strlen(str);\n\n    for(uint32_t i = ffStrbufNextIndexS(strbuf, 0, str); i < strbuf->length; i = ffStrbufNextIndexS(strbuf, i, str))\n        ffStrbufRemoveSubstr(strbuf, i, i + stringLength);\n}\n\nvoid ffStrbufRemoveStrings(FFstrbuf* strbuf, uint32_t numStrings, const char* strings[])\n{\n    for(uint32_t i = 0; i < numStrings; i++)\n        ffStrbufRemoveS(strbuf, strings[i]);\n}\n\nuint32_t ffStrbufNextIndexC(const FFstrbuf* strbuf, uint32_t start, char c)\n{\n    assert(start <= strbuf->length);\n\n    const char* ptr = (const char*)memchr(strbuf->chars + start, c, strbuf->length - start);\n    return ptr ? (uint32_t)(ptr - strbuf->chars) : strbuf->length;\n}\n\nuint32_t ffStrbufNextIndexS(const FFstrbuf* strbuf, uint32_t start, const char* str)\n{\n    assert(start <= strbuf->length);\n\n    const char* ptr = strstr(strbuf->chars + start, str);\n    return ptr ? (uint32_t)(ptr - strbuf->chars) : strbuf->length;\n}\n\nuint32_t ffStrbufPreviousIndexC(const FFstrbuf* strbuf, uint32_t start, char c)\n{\n    assert(start <= strbuf->length);\n\n    //We need to loop one higher than the actual index, because uint32_t is guaranteed to be >= 0, so this statement would always be true\n    for(uint32_t i = start + 1; i > 0; i--)\n    {\n        if(strbuf->chars[i - 1] == c)\n            return i - 1;\n    }\n    return strbuf->length;\n}\n\nvoid ffStrbufReplaceAllC(FFstrbuf* strbuf, char find, char replace)\n{\n    if (strbuf->length == 0)\n        return;\n\n    ffStrbufEnsureFree(strbuf, 0);\n    for (\n        char *current_pos = memchr(strbuf->chars, find, strbuf->length);\n        current_pos;\n        current_pos = memchr(\n            current_pos + 1,\n            find,\n            strbuf->length - (uint32_t)(current_pos + 1 - strbuf->chars)\n        )\n    )\n        *current_pos = replace;\n}\n\nbool ffStrbufSubstrBefore(FFstrbuf* strbuf, uint32_t index)\n{\n    if(strbuf->length <= index)\n        return false;\n\n    if(strbuf->allocated == 0)\n    {\n        //static string\n        if (index < strbuf->length)\n            ffStrbufInitNS(strbuf, index, strbuf->chars);\n        return true;\n    }\n\n    strbuf->length = index;\n    strbuf->chars[strbuf->length] = '\\0';\n    return true;\n}\n\nbool ffStrbufSubstrAfter(FFstrbuf* strbuf, uint32_t index)\n{\n    if(index >= strbuf->length)\n    {\n        ffStrbufClear(strbuf);\n        return true;\n    }\n\n    if(strbuf->allocated == 0)\n    {\n        //static string\n        strbuf->length -= index + 1;\n        strbuf->chars += index + 1;\n        return true;\n    }\n\n    memmove(strbuf->chars, strbuf->chars + index + 1, strbuf->length - index - 1);\n    strbuf->length -= (index + 1);\n    strbuf->chars[strbuf->length] = '\\0';\n    return true;\n}\n\nbool ffStrbufSubstrAfterFirstC(FFstrbuf* strbuf, char c)\n{\n    uint32_t index = ffStrbufFirstIndexC(strbuf, c);\n    if(index >= strbuf->length)\n        return false;\n    ffStrbufSubstrAfter(strbuf, index);\n    return true;\n}\n\nbool ffStrbufSubstrAfterFirstS(FFstrbuf* strbuf, const char* str)\n{\n    if(*str == '\\0')\n        return false;\n\n    uint32_t index = ffStrbufFirstIndexS(strbuf, str) + (uint32_t) strlen(str) - 1; // -1, because firstIndexS is already pointing to str[0], we want to add only the remaining length\n    if(index >= strbuf->length)\n        return false;\n\n    ffStrbufSubstrAfter(strbuf, index);\n    return true;\n}\n\nbool ffStrbufSubstrAfterLastC(FFstrbuf* strbuf, char c)\n{\n    uint32_t index = ffStrbufLastIndexC(strbuf, c);\n    if(index >= strbuf->length)\n        return false;\n\n    ffStrbufSubstrAfter(strbuf, index);\n    return true;\n}\n\nbool ffStrbufSubstr(FFstrbuf* strbuf, uint32_t start, uint32_t end)\n{\n    if (__builtin_expect(start >= end, false))\n    {\n        ffStrbufClear(strbuf);\n        return false;\n    }\n\n    if (__builtin_expect(start == 0, false)) return ffStrbufSubstrBefore(strbuf, end);\n    if (__builtin_expect(end >= strbuf->length, false)) return ffStrbufSubstrAfter(strbuf, start - 1);\n\n    uint32_t len = end - start;\n    ffStrbufEnsureFixedLengthFree(strbuf, len); // In case of static string\n    memmove(strbuf->chars, strbuf->chars + start, len);\n\n    strbuf->length = len;\n    strbuf->chars[len] = '\\0';\n    return true;\n}\n\nuint32_t ffStrbufCountC(const FFstrbuf* strbuf, char c)\n{\n    uint32_t result = 0;\n    for(uint32_t i = 0; i < strbuf->length; i++)\n    {\n        if(strbuf->chars[i] == c)\n            result++;\n    }\n\n    return result;\n}\n\n\nbool ffStrbufRemoveIgnCaseEndS(FFstrbuf* strbuf, const char* end)\n{\n    uint32_t endLength = (uint32_t) strlen(end);\n    if(ffStrbufEndsWithIgnCaseNS(strbuf, endLength, end))\n    {\n        ffStrbufSubstrBefore(strbuf, strbuf->length - endLength);\n        return true;\n    }\n\n    return false;\n}\n\nbool ffStrbufEnsureEndsWithC(FFstrbuf* strbuf, char c)\n{\n    if(ffStrbufEndsWithC(strbuf, c))\n        return false;\n\n    ffStrbufAppendC(strbuf, c);\n    return true;\n}\n\nvoid ffStrbufWriteTo(const FFstrbuf* strbuf, FILE* file)\n{\n    fwrite(strbuf->chars, sizeof(*strbuf->chars), strbuf->length, file);\n}\n\nvoid ffStrbufPutTo(const FFstrbuf* strbuf, FILE* file)\n{\n    ffStrbufWriteTo(strbuf, file);\n    fputc('\\n', file);\n}\n\ndouble ffStrbufToDouble(const FFstrbuf* strbuf, double defaultValue)\n{\n    char* str_end;\n    double result = strtod(strbuf->chars, &str_end);\n    return str_end == strbuf->chars ? defaultValue : result;\n}\n\nuint64_t ffStrbufToUInt(const FFstrbuf* strbuf, uint64_t defaultValue)\n{\n    char* str_end;\n    unsigned long long result = strtoull(strbuf->chars, &str_end, 10);\n    return str_end == strbuf->chars ? defaultValue : (uint64_t)result;\n}\n\nint64_t ffStrbufToSInt(const FFstrbuf* strbuf, int64_t defaultValue)\n{\n    char* str_end;\n    long long result = strtoll(strbuf->chars, &str_end, 10);\n    return str_end == strbuf->chars ? defaultValue : (int64_t)result;\n}\n\nvoid ffStrbufAppendSInt(FFstrbuf* strbuf, int64_t value)\n{\n    ffStrbufEnsureFree(strbuf, 21); // Required by yyjson_write_number\n    char* start = strbuf->chars + strbuf->length;\n\n    yyjson_val val = {};\n    unsafe_yyjson_set_sint(&val, value);\n    char* end = yyjson_write_number(&val, start);\n\n    assert(end != NULL);\n\n    strbuf->length += (uint32_t)(end - start);\n}\n\nvoid ffStrbufAppendUInt(FFstrbuf* strbuf, uint64_t value)\n{\n    ffStrbufEnsureFree(strbuf, 21); // Required by yyjson_write_number\n    char* start = strbuf->chars + strbuf->length;\n\n    yyjson_val val = {};\n    unsafe_yyjson_set_uint(&val, value);\n    char* end = yyjson_write_number(&val, start);\n\n    assert(end != NULL);\n\n    strbuf->length += (uint32_t)(end - start);\n}\n\nvoid ffStrbufAppendDouble(FFstrbuf* strbuf, double value, int8_t precision, bool trailingZeros)\n{\n    assert(precision <= 15); // yyjson_write_number supports up to 15 digits after the decimal point\n\n    ffStrbufEnsureFree(strbuf, 40); // Required by yyjson_write_number\n    char* start = strbuf->chars + strbuf->length;\n\n    if (precision == 0)\n        value = round(value);\n    yyjson_val val = {};\n    unsafe_yyjson_set_double(&val, value);\n    if (precision > 0)\n        unsafe_yyjson_set_fp_to_fixed(&val, precision);\n\n    // Write at most <precision> digits after the decimal point; doesn't append trailing zeros\n    char* end = yyjson_write_number(&val, start);\n\n    assert(end > start);\n\n    strbuf->length += (uint32_t)(end - start);\n\n    if (__builtin_expect(value > 1e21 || value < -1e21, false))\n    {\n        // If the value is too large, yyjson_write_number will write it in scientific notation\n        return;\n    }\n\n    if (trailingZeros)\n    {\n        if (precision > 1)\n        {\n            for (char* p = end - 1; *p != '.' && p > start; --p)\n                --precision;\n            if (precision > 0)\n                ffStrbufAppendNC(strbuf, (uint32_t) precision, '0');\n        }\n        else if (precision == 0 || (precision < 0 && end[-1] == '0'))\n        {\n            goto removeDecimalPoint;\n        }\n    }\n    else\n    {\n        if (end[-1] == '0')\n        {\n        removeDecimalPoint:\n            // yyjson always appends \".0\" to make it a float point number. We need to remove it\n            strbuf->length -= 2;\n            strbuf->chars[strbuf->length] = '\\0';\n        }\n    }\n}\n\nvoid ffStrbufUpperCase(FFstrbuf* strbuf)\n{\n    for (uint32_t i = 0; i < strbuf->length; ++i)\n        strbuf->chars[i] = (char) toupper(strbuf->chars[i]);\n}\n\nvoid ffStrbufLowerCase(FFstrbuf* strbuf)\n{\n    for (uint32_t i = 0; i < strbuf->length; ++i)\n        strbuf->chars[i] = (char) tolower(strbuf->chars[i]);\n}\n\nvoid ffStrbufInsertNC(FFstrbuf* strbuf, uint32_t index, uint32_t num, char c)\n{\n    if(num == 0) return;\n    if (index >= strbuf->length)\n        index = strbuf->length;\n\n    ffStrbufEnsureFree(strbuf, num);\n    memmove(strbuf->chars + index + num, strbuf->chars + index, strbuf->length - index + 1);\n    memset(&strbuf->chars[index], c, num);\n    strbuf->length += num;\n}\n\nbool ffStrbufGetdelim(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer)\n{\n    assert(lineptr && n && buffer);\n    assert(buffer->allocated > 0 || (buffer->allocated == 0 && buffer->length == 0));\n    assert(!*lineptr || (*lineptr >= buffer->chars && *lineptr <= buffer->chars + buffer->length));\n\n    const char* pBufferEnd = buffer->chars + buffer->length;\n    if (!*lineptr)\n        *lineptr = buffer->chars;\n    else\n    {\n        *lineptr += *n;\n        if (*lineptr >= pBufferEnd) // non-empty last line\n            return false;\n        **lineptr = delimiter;\n        ++*lineptr;\n    }\n    if (*lineptr >= pBufferEnd) // empty last line\n        return false;\n\n    size_t remaining = (size_t) (pBufferEnd - *lineptr);\n    char* ending = memchr(*lineptr, delimiter, remaining);\n    if (ending)\n    {\n        *n = (size_t) (ending - *lineptr);\n        *ending = '\\0';\n    }\n    else\n        *n = remaining;\n    return true;\n}\n\nvoid ffStrbufGetdelimRestore(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer)\n{\n    assert(buffer && lineptr && n);\n    assert(buffer->allocated > 0 || (buffer->allocated == 0 && buffer->length == 0));\n    assert(!*lineptr || (*lineptr >= buffer->chars && *lineptr <= buffer->chars + buffer->length));\n\n    if (!*lineptr)\n        return;\n\n    *lineptr += *n;\n    if (*lineptr < buffer->chars + buffer->length)\n        **lineptr = delimiter;\n}\n\nbool ffStrbufRemoveDupWhitespaces(FFstrbuf* strbuf)\n{\n    if (strbuf->allocated == 0) return false; // Doesn't work with static strings\n\n    bool changed = false;\n    for (uint32_t i = 0; i < strbuf->length; i++)\n    {\n        if (strbuf->chars[i] != ' ') continue;\n\n        i++;\n        uint32_t j = i;\n        for (; j < strbuf->length && strbuf->chars[j] == ' '; j++);\n\n        if (j == i) continue;\n        memmove(&strbuf->chars[i], &strbuf->chars[j], strbuf->length - j + 1);\n        strbuf->length -= j - i;\n        changed = true;\n    }\n\n    return changed;\n}\n\n/// @brief Check if a separated string (comp) contains a substring (strbuf).\n/// @param strbuf The substring to check.\n/// @param compLength The length of the separated string to check.\n/// @param comp The separated string to check.\n/// @param separator The separator character.\nbool ffStrbufMatchSeparatedNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator)\n{\n    if (strbuf->length == 0)\n        return true;\n\n    if (compLength == 0)\n        return false;\n\n    for (const char* p = comp; p < comp + compLength;)\n    {\n        const char* colon = memchr(p, separator, compLength);\n        if (colon == NULL)\n            return strcmp(strbuf->chars, p) == 0;\n\n        uint32_t substrLength = (uint32_t) (colon - p);\n        if (strbuf->length == substrLength && memcmp(strbuf->chars, p, substrLength) == 0)\n            return true;\n\n        p = colon + 1;\n    }\n\n    return false;\n}\n\n/// @brief Case insensitive version of ffStrbufMatchSeparatedNS.\nbool ffStrbufMatchSeparatedIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator)\n{\n    if (strbuf->length == 0)\n        return true;\n\n    if (compLength == 0)\n        return false;\n\n    for (const char* p = comp; p < comp + compLength;)\n    {\n        const char* colon = memchr(p, separator, compLength);\n        if (colon == NULL)\n            return strcasecmp(strbuf->chars, p) == 0;\n\n        uint32_t substrLength = (uint32_t) (colon - p);\n        if (strbuf->length == substrLength && strncasecmp(strbuf->chars, p, substrLength) == 0)\n            return true;\n\n        p = colon + 1;\n    }\n\n    return false;\n}\n\nint ffStrbufAppendUtf32CodePoint(FFstrbuf* strbuf, uint32_t codepoint)\n{\n    if (codepoint <= 0x7F) {\n        ffStrbufAppendC(strbuf, (char)codepoint);\n        return 1;\n    } else if (codepoint <= 0x7FF) {\n        ffStrbufAppendNS(strbuf, 2, (char[]){\n            (char) (0xC0 | (codepoint >> 6)),\n            (char) (0x80 | (codepoint & 0x3F))\n        });\n        return 2;\n    } else if (codepoint <= 0xFFFF) {\n        ffStrbufAppendNS(strbuf, 3, (char[]){\n            (char) (0xE0 | (codepoint >> 12)),\n            (char) (0x80 | ((codepoint >> 6) & 0x3F)),\n            (char) (0x80 | (codepoint & 0x3F))\n        });\n        return 3;\n    } else if (codepoint <= 0x10FFFF) {\n        ffStrbufAppendNS(strbuf, 4, (char[]){\n            (char) (0xF0 | (codepoint >> 18)),\n            (char) (0x80 | ((codepoint >> 12) & 0x3F)),\n            (char) (0x80 | ((codepoint >> 6) & 0x3F)),\n            (char) (0x80 | (codepoint & 0x3F))\n        });\n        return 4;\n    }\n\n    ffStrbufAppendS(strbuf, \"�\"); // U+FFFD REPLACEMENT CHARACTER\n    return 1;\n}\n\n/// @brief Check if a separated string (strbuf) contains a substring (comp).\n/// @param strbuf The separated to check.\n/// @param compLength The length of the separated string to check.\n/// @param comp The substring to check.\n/// @param separator The separator character.\nbool ffStrbufSeparatedContainNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator)\n{\n    uint32_t startIndex = 0;\n    while(startIndex < strbuf->length)\n    {\n        uint32_t colonIndex = ffStrbufNextIndexC(strbuf, startIndex, separator);\n\n        uint32_t folderLength = colonIndex - startIndex;\n        if (folderLength == compLength && memcmp(strbuf->chars + startIndex, comp, compLength) == 0)\n            return true;\n\n        startIndex = colonIndex + 1;\n    }\n\n    return false;\n}\n\nbool ffStrbufSeparatedContainIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator)\n{\n    uint32_t startIndex = 0;\n    while(startIndex < strbuf->length)\n    {\n        uint32_t colonIndex = ffStrbufNextIndexC(strbuf, startIndex, separator);\n\n        uint32_t folderLength = colonIndex - startIndex;\n        if (folderLength == compLength && strncasecmp(strbuf->chars + startIndex, comp, compLength) == 0)\n            return true;\n\n        startIndex = colonIndex + 1;\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "src/common/impl/base64.c",
    "content": "#include \"common/base64.h\"\n\n// https://github.com/kostya/benchmarks/blob/master/base64/test-nolib.c#L145\nvoid ffBase64EncodeRaw(uint32_t size, const char *str, uint32_t *out_size, char *output)\n{\n    static const char chars[] = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n    char *out = output;\n    const char *ends = str + (size - size % 3);\n    while (str != ends)\n    {\n        uint32_t n = __builtin_bswap32(*(uint32_t *)str);\n        *out++ = chars[(n >> 26) & 63];\n        *out++ = chars[(n >> 20) & 63];\n        *out++ = chars[(n >> 14) & 63];\n        *out++ = chars[(n >> 8) & 63];\n        str += 3;\n    }\n\n    if (size % 3 == 1)\n    {\n        uint64_t n = (uint64_t)*str << 16;\n        *out++ = chars[(n >> 18) & 63];\n        *out++ = chars[(n >> 12) & 63];\n        *out++ = '=';\n        *out++ = '=';\n    }\n    else if (size % 3 == 2)\n    {\n        uint64_t n = (uint64_t)*str++ << 16;\n        n |= (uint64_t)*str << 8;\n        *out++ = chars[(n >> 18) & 63];\n        *out++ = chars[(n >> 12) & 63];\n        *out++ = chars[(n >> 6) & 63];\n        *out++ = '=';\n    }\n    *out = '\\0';\n    *out_size = (uint32_t)(out - output);\n}\n\nstatic uint8_t decode_table[256];\n\nvoid init_decode_table()\n{\n    uint8_t ch = 0;\n    do\n    {\n        int32_t code = -1;\n        if (ch >= 'A' && ch <= 'Z')\n            code = ch - 0x41;\n        if (ch >= 'a' && ch <= 'z')\n            code = ch - 0x47;\n        if (ch >= '0' && ch <= '9')\n            code = ch + 0x04;\n        if (ch == '+' || ch == '-')\n            code = 0x3E;\n        if (ch == '/' || ch == '_')\n            code = 0x3F;\n        decode_table[ch] = (uint8_t) code;\n    } while (ch++ < 0xFF);\n}\n\n#define next_char(x) uint8_t x = decode_table[(uint8_t) *str++];\n\nbool ffBase64DecodeRaw(uint32_t size, const char *str, uint32_t *out_size, char *output)\n{\n    if (*(uint64_t*) decode_table == 0)\n        init_decode_table();\n\n    char *out = output;\n    while (size > 0 && (str[size - 1] == '\\n' || str[size - 1] == '\\r' || str[size - 1] == '='))\n        size--;\n\n    const char *ends = str + size - 4;\n    while (true)\n    {\n        if (str > ends)\n            break;\n        while (*str == '\\n' || *str == '\\r')\n            str++;\n\n        if (str > ends)\n            break;\n        next_char(a);\n        next_char(b);\n        next_char(c);\n        next_char(d);\n\n        *out++ = (char)(a << 2 | b >> 4);\n        *out++ = (char)(b << 4 | c >> 2);\n        *out++ = (char)(c << 6 | d >> 0);\n    }\n\n    uint8_t mod = (uint8_t) (ends - str + 4) % 4;\n    if (mod == 2)\n    {\n        next_char(a);\n        next_char(b);\n        *out++ = (char)(a << 2 | b >> 4);\n    }\n    else if (mod == 3)\n    {\n        next_char(a);\n        next_char(b);\n        next_char(c);\n        *out++ = (char)(a << 2 | b >> 4);\n        *out++ = (char)(b << 4 | c >> 2);\n    }\n\n    *out = '\\0';\n    *out_size = (uint32_t) (out - output);\n    return true;\n}\n"
  },
  {
    "path": "src/common/impl/binary_apple.c",
    "content": "#include \"common/binary.h\"\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n#include \"common/mallocHelper.h\"\n\n#include <stdlib.h>\n#include <string.h>\n#include <fcntl.h>\n#include <mach-o/loader.h>\n#include <mach-o/swap.h>\n#include <mach-o/fat.h>\n\n#pragma GCC diagnostic ignored \"-Wdeprecated-declarations\" // swap_fat_arch\n\n// Ref: https://github.com/AlexDenisov/segment_dumper/blob/master/main.c\n\n/**\n * Helper function to read data from a file at a specific offset\n */\nstatic inline bool readData(FILE *objFile, void *buf, size_t size, off_t offset)\n{\n    fseek(objFile, offset, SEEK_SET);\n    return fread(buf, 1, size, objFile) == size;\n}\n\n/**\n * Handles a Mach-O section by extracting strings from the __cstring section\n *\n * @param objFile File handle to the Mach-O object file\n * @param name Section name to check\n * @param offset Offset of the section in the file\n * @param size Size of the section\n * @param cb Callback function to process strings\n * @param userdata User data for the callback\n * @param minLength Minimum string length to extract\n *\n * @return true to continue processing, false to stop\n */\nstatic bool handleMachSection(FILE *objFile, const char *name, off_t offset, size_t size, bool (*cb)(const char *str, uint32_t len, void *userdata), void *userdata, uint32_t minLength)\n{\n    if (!ffStrEquals(name, \"__cstring\")) return true;\n\n    FF_AUTO_FREE char* data = (char*) malloc(size);\n    if (!readData(objFile, data, size, offset))\n        return true;\n\n    for (size_t off = 0; off < size; ++off)\n    {\n        const char* p = (const char*) data + off;\n        if (*p == '\\0') continue;\n        uint32_t len = (uint32_t) strlen(p);\n        if (len < minLength) continue;\n        if (*p >= ' ' && *p <= '~') // Ignore control characters\n        {\n            if (!cb(p, len, userdata)) return false;\n        }\n        off += len;\n    }\n    return true;\n}\n\n/**\n * Processes a Mach-O header (32-bit or 64-bit)\n *\n * This function parses the load commands in a Mach-O header, looking for\n * LC_SEGMENT or LC_SEGMENT_64 commands that contain the __TEXT segment.\n * It then processes the sections within that segment to extract strings.\n *\n * @param objFile File handle to the Mach-O object file\n * @param offset Offset of the Mach header in the file\n * @param is_64 Whether this is a 64-bit Mach-O header\n * @param cb Callback function to process strings\n * @param userdata User data for the callback\n * @param minLength Minimum string length to extract\n *\n * @return NULL on success, error message on failure\n */\nstatic const char* dumpMachHeader(FILE *objFile, off_t offset, bool is_64, bool (*cb)(const char *str, uint32_t len, void *userdata), void *userdata, uint32_t minLength)\n{\n    uint32_t ncmds;\n    off_t loadCommandsOffset = offset;\n\n    if (is_64)\n    {\n        struct mach_header_64 header;\n        if (!readData(objFile, &header, sizeof(header), offset))\n            return \"read mach header failed\";\n\n        ncmds = header.ncmds;\n        loadCommandsOffset += sizeof(header);\n    }\n    else\n    {\n        struct mach_header header;\n        if (!readData(objFile, &header, sizeof(header), offset))\n            return \"read mach header failed\";\n\n        ncmds = header.ncmds;\n        loadCommandsOffset += sizeof(header);\n    }\n\n    off_t commandOffset = loadCommandsOffset;\n    struct load_command cmd = {};\n    for (uint32_t i = 0U; i < ncmds; i++, commandOffset += cmd.cmdsize)\n    {\n        if (!readData(objFile, &cmd, sizeof(cmd), commandOffset))\n            continue;\n\n        if (cmd.cmd == LC_SEGMENT_64)\n        {\n            struct segment_command_64 segment;\n            if (!readData(objFile, &segment, sizeof(segment), commandOffset))\n                continue;\n\n            if (!ffStrEquals(segment.segname, \"__TEXT\")) continue;\n\n            for (uint32_t j = 0U; j < segment.nsects; j++)\n            {\n                struct section_64 section;\n                if (!readData(objFile, &section, sizeof(section), (off_t) ((size_t) commandOffset + sizeof(segment) + j * sizeof(section))))\n                    continue;\n\n                if (!handleMachSection(objFile, section.sectname, section.offset, section.size, cb, userdata, minLength))\n                    return NULL;\n            }\n        }\n        else if (cmd.cmd == LC_SEGMENT)\n        {\n            struct segment_command segment;\n            if (!readData(objFile, &segment, sizeof(segment), commandOffset))\n                continue;\n\n            if (!ffStrEquals(segment.segname, \"__TEXT\")) continue;\n\n            for (uint32_t j = 0; j < segment.nsects; j++)\n            {\n                struct section section;\n                if (!readData(objFile, &section, sizeof(section), (off_t) ((size_t) commandOffset + sizeof(segment) + j * sizeof(section))))\n                    continue;\n\n                if (!handleMachSection(objFile, section.sectname, section.offset, section.size, cb, userdata, minLength))\n                    return NULL;\n            }\n        }\n\n        return NULL;\n    }\n\n    return NULL;\n}\n\n/**\n * Processes a Fat binary header (Universal binary)\n *\n * This function handles the fat header of a universal binary, which can contain\n * multiple Mach-O binaries for different architectures. It extracts and processes\n * each embedded Mach-O file.\n *\n * @param objFile File handle to the universal binary\n * @param cb Callback function to process strings\n * @param userdata User data for the callback\n * @param minLength Minimum string length to extract\n *\n * @return NULL on success, error message on failure\n */\nstatic const char* dumpFatHeader(FILE *objFile, bool (*cb)(const char *str, uint32_t len, void *userdata), void *userdata, uint32_t minLength)\n{\n    struct fat_header header;\n    if (!readData(objFile, &header, sizeof(header), 0))\n        return \"read fat header failed\";\n\n    bool needSwap = header.magic == FAT_CIGAM || header.magic == FAT_CIGAM_64;\n\n    if (needSwap) swap_fat_header(&header, NX_UnknownByteOrder);\n\n    for (uint32_t i = 0U; i < header.nfat_arch; i++)\n    {\n        off_t machHeaderOffset = 0;\n        if (header.magic == FAT_MAGIC)\n        {\n            struct fat_arch arch;\n            if (!readData(objFile, &arch, sizeof(arch), (off_t) (sizeof(header) + i * sizeof(arch))))\n                continue;\n\n            if (needSwap)\n                swap_fat_arch(&arch, 1, NX_UnknownByteOrder);\n            machHeaderOffset = (off_t)arch.offset;\n        }\n        else\n        {\n            struct fat_arch_64 arch;\n            if (!readData(objFile, &arch, sizeof(arch), (off_t) (sizeof(header) + i * sizeof(arch))))\n                continue;\n\n            if (needSwap)\n                swap_fat_arch_64(&arch, 1, NX_UnknownByteOrder);\n\n            machHeaderOffset = (off_t)arch.offset;\n        }\n\n        uint32_t magic;\n        if (!readData(objFile, &magic, sizeof(magic), machHeaderOffset))\n            continue;\n\n        if (magic == MH_MAGIC_64 || magic == MH_MAGIC)\n        {\n            dumpMachHeader(objFile, machHeaderOffset, magic == MH_MAGIC_64, cb, userdata, minLength);\n            return NULL;\n        }\n    }\n    return \"Unsupported fat header\";\n}\n\n/**\n * Extracts string literals from a Mach-O (Apple) binary file\n *\n * This function supports both single-architecture Mach-O files and\n * universal binaries (fat binaries) containing multiple architectures.\n * It locates the __cstring section in the __TEXT segment which contains\n * the string literals used in the program.\n */\nconst char *ffBinaryExtractStrings(const char *machoFile, bool (*cb)(const char *str, uint32_t len, void *userdata), void *userdata, uint32_t minLength)\n{\n    FF_AUTO_CLOSE_FILE FILE *objFile = fopen(machoFile, \"rb\");\n    if (objFile == NULL)\n        return \"File could not be opened\";\n\n    // Read the magic number to determine the type of binary\n    uint32_t magic;\n    if (!readData(objFile, &magic, sizeof(magic), 0))\n        return \"read magic number failed\";\n\n    // Check for supported formats\n    // MH_CIGAM and MH_CIGAM_64 seem to be no longer used, as `swap_mach_header` is marked as deprecated.\n    // However FAT_CIGAM and FAT_CIGAM_64 are still used (/usr/bin/vim).\n    if (magic != MH_MAGIC && magic != MH_MAGIC_64 && magic != FAT_CIGAM && magic != FAT_CIGAM_64 && magic != FAT_MAGIC && magic != FAT_MAGIC_64)\n        return \"Unsupported format or big endian mach-o file\";\n\n    // Process either a fat binary or a regular Mach-O binary\n    if (magic == FAT_MAGIC || magic == FAT_MAGIC_64 || magic == FAT_CIGAM || magic == FAT_CIGAM_64)\n        return dumpFatHeader(objFile, cb, userdata, minLength);\n    else\n        return dumpMachHeader(objFile, 0, magic == MH_MAGIC_64, cb, userdata, minLength);\n}\n"
  },
  {
    "path": "src/common/impl/binary_linux.c",
    "content": "#include \"common/binary.h\"\n\n#if defined(FF_HAVE_ELF) || defined(__sun) || (defined(__FreeBSD__) && !defined(__DragonFly__)) || defined(__OpenBSD__) || defined(__NetBSD__)\n\n#include \"common/io.h\"\n#include \"common/library.h\"\n#include \"common/stringUtils.h\"\n\n#include <libelf.h> // #1254\n#include <fcntl.h>\n\n/**\n * Structure to hold dynamically loaded libelf function pointers\n */\nstruct FFElfData {\n    FF_LIBRARY_SYMBOL(elf_version)\n    FF_LIBRARY_SYMBOL(elf_begin)\n    FF_LIBRARY_SYMBOL(elf_getshdrstrndx)\n    FF_LIBRARY_SYMBOL(elf_nextscn)\n    FF_LIBRARY_SYMBOL(elf64_getshdr)\n    FF_LIBRARY_SYMBOL(elf32_getshdr)\n    FF_LIBRARY_SYMBOL(elf_getdata)\n    FF_LIBRARY_SYMBOL(elf_strptr)\n    FF_LIBRARY_SYMBOL(elf_end)\n\n    bool inited;\n} elfData;\n\n/**\n * Extracts string literals from an ELF (Linux/Unix) binary file\n *\n * This function loads the libelf library dynamically, opens the ELF file,\n * locates the .rodata section (which contains string literals), and\n * scans it for valid strings. Each string found is passed to the\n * callback function for processing.\n *\n * The function supports both 32-bit and 64-bit ELF formats.\n */\nconst char* ffBinaryExtractStrings(const char* elfFile, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength)\n{\n    // Initialize libelf if not already done\n    if (!elfData.inited)\n    {\n        elfData.inited = true;\n        FF_LIBRARY_LOAD_MESSAGE(libelf, \"libelf\" FF_LIBRARY_EXTENSION, 1);\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_version)\n        if (elfData.ffelf_version(EV_CURRENT) == EV_NONE) return \"elf_version() failed\";\n\n        // Load all required libelf functions\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_begin)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_getshdrstrndx)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_nextscn)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf64_getshdr)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf32_getshdr)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_getdata)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_strptr)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_end)\n\n        libelf = NULL;\n    }\n\n    if (elfData.ffelf_end == NULL)\n        return \"load libelf failed\";\n\n    // Open the ELF file\n    FF_AUTO_CLOSE_FD int fd = open(elfFile, O_RDONLY | O_CLOEXEC);\n    if (fd < 0) return \"open() failed\";\n\n    Elf* elf = elfData.ffelf_begin(fd, ELF_C_READ, NULL);\n    if (elf == NULL) return \"elf_begin() failed\";\n\n    // Get the section header string table index\n    size_t shstrndx = 0;\n    if (elfData.ffelf_getshdrstrndx(elf, &shstrndx) < 0)\n    {\n        elfData.ffelf_end(elf);\n        return \"elf_getshdrstrndx() failed\";\n    }\n\n    // Iterate through all sections, looking for .rodata which contains string literals\n    Elf_Scn* scn = NULL;\n    while ((scn = elfData.ffelf_nextscn(elf, scn)) != NULL)\n    {\n        // Try 64-bit section header first, then 32-bit if that fails\n        Elf64_Shdr* shdr64 = elfData.ffelf64_getshdr(scn);\n        Elf32_Shdr* shdr32 = NULL;\n        if (shdr64 == NULL)\n        {\n            shdr32 = elfData.ffelf32_getshdr(scn);\n            if (shdr32 == NULL) continue;\n        }\n\n        // Get the section name and check if it's .rodata\n        const char* name = elfData.ffelf_strptr(elf, shstrndx, shdr64 ? shdr64->sh_name : shdr32->sh_name);\n        if (name == NULL || !ffStrEquals(name, \".rodata\")) continue;\n\n        // Get the section data\n        Elf_Data* data = elfData.ffelf_getdata(scn, NULL);\n        if (data == NULL) continue;\n\n        // Scan the section for string literals\n        for (size_t off = 0; off < data->d_size; ++off)\n        {\n            const char* p = (const char*) data->d_buf + off;\n            if (*p == '\\0') continue;\n            uint32_t len = (uint32_t) strlen(p);\n            if (len < minLength) continue;\n            // Only process printable ASCII characters\n            if (*p >= ' ' && *p <= '~') // Ignore control characters\n            {\n                if (!cb(p, len, userdata)) break;\n            }\n            off += len;\n        }\n\n        break;\n    }\n\n    elfData.ffelf_end(elf);\n    return NULL;\n}\n\n#else\n\n/**\n * Fallback implementation when libelf is not available\n */\nconst char* ffBinaryExtractStrings(const char* file, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength)\n{\n    FF_UNUSED(file, cb, userdata, minLength);\n    return \"Fastfetch was built without libelf support\";\n}\n\n#endif\n"
  },
  {
    "path": "src/common/impl/binary_windows.c",
    "content": "#include \"common/binary.h\"\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n#include \"common/windows/nt.h\"\n\n#include <windows.h>\n#include <stdlib.h>\n#include <string.h>\n\n/**\n * Extracts string literals from a PE (Windows) executable\n *\n * This function maps the PE file into memory, locates the .rdata section\n * (which typically contains string literals), and scans it for valid strings.\n * Each string found is passed to the callback function for processing.\n */\nconst char* ffBinaryExtractStrings(const char *peFile, bool (*cb)(const char *str, uint32_t len, void *userdata), void *userdata, uint32_t minLength)\n{\n    FF_AUTO_CLOSE_FD HANDLE hFile = CreateFileA(peFile, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n        NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);\n    if (hFile == INVALID_HANDLE_VALUE)\n        return \"CreateFileA() failed\";\n\n    FF_AUTO_CLOSE_FD HANDLE hMap = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL);\n    if (!hMap)\n        return \"CreateFileMappingW() failed\";\n\n    void* base = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);\n    if (!base)\n        return \"MapViewOfFile() failed\";\n\n    PIMAGE_NT_HEADERS ntHeaders = RtlImageNtHeader(base);\n    if (!ntHeaders)\n    {\n        UnmapViewOfFile(base);\n        return \"RtlImageNtHeader() failed\";\n    }\n\n    PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(ntHeaders);\n    for (WORD i = 0; i < ntHeaders->FileHeader.NumberOfSections; ++i, ++section)\n    {\n        // Look for initialized data sections with the name \".rdata\" which typically contains string literals\n        if ((section->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) && ffStrEquals((const char*) section->Name, \".rdata\"))\n        {\n            uint8_t *data = (uint8_t *) base + section->PointerToRawData;\n\n            // Scan the section for string literals\n            for (size_t off = 0; off < section->SizeOfRawData; ++off)\n            {\n                const char* p = (const char*) data + off;\n                if (*p == '\\0') continue;\n                uint32_t len = (uint32_t) strlen(p);\n                if (len < minLength) continue;\n                // Only process printable ASCII characters\n                if (*p >= ' ' && *p <= '~') // Ignore control characters\n                {\n                    if (!cb(p, len, userdata)) break;\n                }\n                off += len;\n            }\n        }\n    }\n\n    UnmapViewOfFile(base);\n    return NULL;\n}\n"
  },
  {
    "path": "src/common/impl/commandoption.c",
    "content": "#include \"common/commandoption.h\"\n#include \"common/color.h\"\n#include \"common/printing.h\"\n#include \"common/time.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"fastfetch_datatext.h\"\n#include \"modules/modules.h\"\n\n#include <ctype.h>\n#include <inttypes.h>\n\nbool ffParseModuleOptions(const char* key, const char* value)\n{\n    if (!ffStrStartsWith(key, \"--\") || !ffCharIsEnglishAlphabet(key[2])) return false;\n    if (value && !*value) value = NULL;\n    for (FFModuleBaseInfo** modules = ffModuleInfos[toupper(key[2]) - 'A']; *modules; ++modules)\n    {\n        FFModuleBaseInfo* baseInfo = *modules;\n        const char* subKey = ffOptionTestPrefix(key, baseInfo->name);\n        if (subKey != NULL)\n        {\n            if (subKey[0] == '\\0' || subKey[0] == '-') // Key is exactly the module name or has a leading '-'\n            {\n                fprintf(stderr, \"Error: unknown module key %s\\n\", key);\n                exit(477);\n            }\n\n            FF_STRBUF_AUTO_DESTROY moduleName = ffStrbufCreateS(baseInfo->name);\n            ffStrbufLowerCase(&moduleName);\n\n            FF_STRBUF_AUTO_DESTROY jsonKey = ffStrbufCreate();\n            bool flag = false;\n            for (const char* p = subKey; *p; ++p)\n            {\n                if (*p == '-')\n                {\n                    if (flag)\n                    {\n                        fprintf(stderr, \"Error: invalid double `-` in module key %s\\n\", key);\n                        exit(477);\n                    }\n                    flag = true;\n                }\n                else\n                {\n                    if (!isalpha((unsigned char)*p) && !isdigit((unsigned char)*p))\n                    {\n                        fprintf(stderr, \"Error: invalid character `%c` in module key %s\\n\", *p, key);\n                        exit(477);\n                    }\n\n                    if (flag)\n                    {\n                        flag = false;\n                        ffStrbufAppendC(&jsonKey, (char) toupper((unsigned char) *p));\n                    }\n                    else\n                        ffStrbufAppendC(&jsonKey, *p);\n                }\n            }\n            fprintf(stderr, \"Error: Unsupported module option: %s\\n\", key);\n            fputs(\"       Support of module options has been removed. Please add the flag to the JSON config instead.\\n\", stderr);\n            fprintf(stderr, \"       Example (demonstration only): `{ \\\"modules\\\": [ { \\\"type\\\": \\\"%s\\\", \\\"%s\\\": %s%s%s } ] }`\\n\", moduleName.chars, jsonKey.chars, value ? \"\\\"\" : \"\", value ?: \"true\", value ? \"\\\"\" : \"\");\n            fputs(\"       See <https://github.com/fastfetch-cli/fastfetch/wiki/Configuration> for more information.\\n\", stderr);\n            exit(477);\n        }\n    }\n    return false;\n}\n\nvoid ffPrepareCommandOption(FFdata* data)\n{\n    char* moduleType = NULL;\n    size_t moduleLen = 0;\n    while (ffStrbufGetdelim(&moduleType, &moduleLen, ':', &data->structure))\n    {\n        #define FF_IF_MODULE_MATCH(moduleNameConstant) if (moduleLen == strlen(moduleNameConstant) \\\n            && ffStrEqualsIgnCase(moduleType, moduleNameConstant) \\\n            && !ffStrbufSeparatedContainIgnCaseS(&data->structureDisabled, moduleNameConstant, ':'))\n\n        switch (moduleType[0])\n        {\n            case 'C': case 'c':\n                FF_IF_MODULE_MATCH(FF_CPUUSAGE_MODULE_NAME)\n                    ffPrepareCPUUsage();\n                break;\n\n            case 'D': case 'd':\n                FF_IF_MODULE_MATCH(FF_DISKIO_MODULE_NAME)\n                {\n                    __attribute__((__cleanup__(ffDestroyDiskIOOptions))) FFDiskIOOptions options;\n                    ffInitDiskIOOptions(&options);\n                    ffPrepareDiskIO(&options);\n                }\n                break;\n\n            case 'N': case 'n':\n                FF_IF_MODULE_MATCH(FF_NETIO_MODULE_NAME)\n                {\n                    __attribute__((__cleanup__(ffDestroyNetIOOptions))) FFNetIOOptions options;\n                    ffInitNetIOOptions(&options);\n                    ffPrepareNetIO(&options);\n                }\n                break;\n\n            case 'P': case 'p':\n                FF_IF_MODULE_MATCH(FF_PUBLICIP_MODULE_NAME)\n                {\n                    __attribute__((__cleanup__(ffDestroyPublicIpOptions))) FFPublicIPOptions options;\n                    ffInitPublicIpOptions(&options);\n                    ffPreparePublicIp(&options);\n                }\n                break;\n\n            case 'W': case 'w':\n                FF_IF_MODULE_MATCH(FF_WEATHER_MODULE_NAME)\n                {\n                    __attribute__((__cleanup__(ffDestroyWeatherOptions))) FFWeatherOptions options;\n                    ffInitWeatherOptions(&options);\n                    ffPrepareWeather(&options);\n                }\n                break;\n        }\n\n        #undef FF_IF_MODULE_MATCH\n    }\n}\n\nstatic void genJsonConfig(FFdata* data, FFModuleBaseInfo* baseInfo, void* options)\n{\n    yyjson_mut_doc* doc = data->resultDoc;\n\n    yyjson_mut_val* modules = yyjson_mut_obj_get(doc->root, \"modules\");\n    if (!modules)\n        modules = yyjson_mut_obj_add_arr(doc, doc->root, \"modules\");\n\n    FF_STRBUF_AUTO_DESTROY type = ffStrbufCreateS(baseInfo->name);\n    ffStrbufLowerCase(&type);\n\n    if (data->docType == FF_RESULT_DOC_TYPE_CONFIG_FULL)\n    {\n        yyjson_mut_val* module = yyjson_mut_obj(doc);\n        yyjson_mut_obj_add_strbuf(doc, module, \"type\", &type);\n\n        if (baseInfo->generateJsonConfig)\n            baseInfo->generateJsonConfig(options, doc, module);\n\n        if (yyjson_mut_obj_size(module) > 1)\n            yyjson_mut_arr_add_val(modules, module);\n        else\n            yyjson_mut_arr_add_strbuf(doc, modules, &type);\n    }\n    else\n    {\n        yyjson_mut_arr_add_strbuf(doc, modules, &type);\n    }\n}\n\nstatic void genJsonResult(FFdata* data, FFModuleBaseInfo* baseInfo, void* options)\n{\n    yyjson_mut_doc* doc = data->resultDoc;\n    yyjson_mut_val* module = yyjson_mut_arr_add_obj(doc, doc->root);\n    yyjson_mut_obj_add_str(doc, module, \"type\", baseInfo->name);\n    if (baseInfo->generateJsonResult)\n        baseInfo->generateJsonResult(options, doc, module);\n    else\n        yyjson_mut_obj_add_str(doc, module, \"error\", \"Unsupported for JSON format\");\n}\n\nstatic void parseStructureCommand(\n    FFdata* data,\n    const char* line,\n    void (*fn)(FFdata*, FFModuleBaseInfo* baseInfo, void* options)\n)\n{\n    if(ffCharIsEnglishAlphabet(line[0]))\n    {\n        for (FFModuleBaseInfo** modules = ffModuleInfos[toupper(line[0]) - 'A']; *modules; ++modules)\n        {\n            FFModuleBaseInfo* baseInfo = *modules;\n            if (ffStrEqualsIgnCase(line, baseInfo->name))\n            {\n                uint8_t optionBuf[FF_OPTION_MAX_SIZE];\n                baseInfo->initOptions(optionBuf);\n                if (__builtin_expect(data->resultDoc != NULL, false))\n                    fn(data, baseInfo, optionBuf);\n                else\n                    baseInfo->printModule(optionBuf);\n                baseInfo->destroyOptions(optionBuf);\n                return;\n            }\n        }\n    }\n\n    ffPrintError(line, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, \"<no implementation provided>\");\n}\n\nvoid ffPrintCommandOption(FFdata* data)\n{\n    //Parse the structure and call the modules\n    int32_t thres = instance.config.display.stat;\n\n    char* moduleType = NULL;\n    size_t moduleLen = 0;\n    while (ffStrbufGetdelim(&moduleType, &moduleLen, ':', &data->structure))\n    {\n        if (ffStrbufSeparatedContainIgnCaseS(&data->structureDisabled, moduleType, ':'))\n            continue;\n\n        double ms = 0;\n        if(thres >= 0)\n            ms = ffTimeGetTick();\n\n        parseStructureCommand(data, moduleType, genJsonResult);\n\n        if(thres >= 0)\n        {\n            ms = ffTimeGetTick() - ms;\n\n            if (data->resultDoc)\n            {\n                yyjson_mut_val* moduleJson = yyjson_mut_arr_get_last(data->resultDoc->root);\n                yyjson_mut_obj_add_real(data->resultDoc, moduleJson, \"stat\", ms);\n            }\n            else\n            {\n                char str[64];\n                int len = snprintf(str, sizeof str, \"%.3fms\", ms);\n                if (thres > 0)\n                    snprintf(str, sizeof str, \"\\e[%sm%.3fms\\e[m\", (ms <= thres ? FF_COLOR_FG_GREEN : ms <= 2 * thres ? FF_COLOR_FG_YELLOW : FF_COLOR_FG_RED), ms);\n                printf(\"\\e7\\e[1A\\e[9999999C\\e[%dD%s\\e8\", len, str); // Save; Up 1; Right 9999999; Left <len>; Print <str>; Load\n            }\n        }\n\n        #if defined(_WIN32)\n            if (!data->resultDoc && !instance.config.display.noBuffer) fflush(stdout);\n        #endif\n    }\n}\n\nvoid ffMigrateCommandOptionToJsonc(FFdata* data)\n{\n    //If we don't have a custom structure, use the default one\n    if(data->structure.length == 0)\n        ffStrbufAppendS(&data->structure, FASTFETCH_DATATEXT_STRUCTURE); // Cannot use `ffStrbufSetStatic` here because we will modify the string\n\n    char* moduleType = NULL;\n    size_t moduleLen = 0;\n    while (ffStrbufGetdelim(&moduleType, &moduleLen, ':', &data->structure))\n    {\n        if (ffStrbufSeparatedContainIgnCaseS(&data->structureDisabled, moduleType, ':'))\n            continue;\n\n        parseStructureCommand(data, moduleType, genJsonConfig);\n    }\n}\n"
  },
  {
    "path": "src/common/impl/dbus.c",
    "content": "#include \"common/dbus.h\"\n\n#ifdef FF_HAVE_DBUS\n\n#include \"common/thread.h\"\n#include \"common/stringUtils.h\"\n\nstatic bool loadLibSymbols(FFDBusLibrary* lib)\n{\n    FF_LIBRARY_LOAD(dbus, false, \"libdbus-1\" FF_LIBRARY_EXTENSION, 4);\n    FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_bus_get, false)\n    FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_new_method_call, false)\n    FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_append_args, false)\n    FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_iter_init, false)\n    FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_iter_get_arg_type, false)\n    FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_iter_get_basic, false)\n    FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_iter_recurse, false)\n    FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_iter_has_next, false)\n    FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_iter_next, false)\n    FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_unref, false)\n    FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_connection_send_with_reply_and_block, false)\n    FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_connection_unref, false)\n    dbus = NULL; // don't auto dlclose\n    return true;\n}\n\nstatic const FFDBusLibrary* loadLib(void)\n{\n    static FFDBusLibrary lib;\n    static bool loaded = false;\n    static bool loadSuccess = false;\n\n    if(!loaded)\n    {\n        loaded = true;\n        loadSuccess = loadLibSymbols(&lib);\n    }\n\n    return loadSuccess ? &lib : NULL;\n}\n\nconst char* ffDBusLoadData(DBusBusType busType, FFDBusData* data)\n{\n    data->lib = loadLib();\n    if(data->lib == NULL)\n        return \"Failed to load DBus library\";\n\n    data->connection = data->lib->ffdbus_bus_get(busType, NULL);\n    if(data->connection == NULL)\n        return \"Failed to connect to DBus\";\n\n    return NULL;\n}\n\nvoid ffDBusDestroyData(FFDBusData* data)\n{\n    if (data->connection != NULL)\n    {\n        data->lib->ffdbus_connection_unref(data->connection);\n        data->connection = NULL;\n    }\n}\n\nbool ffDBusGetString(FFDBusData* dbus, DBusMessageIter* iter, FFstrbuf* result)\n{\n    int argType = dbus->lib->ffdbus_message_iter_get_arg_type(iter);\n\n    if(argType == DBUS_TYPE_STRING || argType == DBUS_TYPE_OBJECT_PATH)\n    {\n        const char* value = NULL;\n        dbus->lib->ffdbus_message_iter_get_basic(iter, &value);\n\n        if(!ffStrSet(value))\n            return false;\n\n        ffStrbufAppendS(result, value);\n        return true;\n    }\n\n    if (argType == DBUS_TYPE_BYTE)\n    {\n        uint8_t value;\n        dbus->lib->ffdbus_message_iter_get_basic(iter, &value);\n        ffStrbufAppendC(result, (char) value);\n        return false; // Don't append a comma\n    }\n\n    if(argType != DBUS_TYPE_VARIANT && argType != DBUS_TYPE_ARRAY)\n        return false;\n\n    DBusMessageIter subIter;\n    dbus->lib->ffdbus_message_iter_recurse(iter, &subIter);\n\n    if(argType == DBUS_TYPE_VARIANT)\n        return ffDBusGetString(dbus, &subIter, result);\n\n    //At this point we have an array\n\n    bool foundAValue = false;\n\n    while(true)\n    {\n        if(ffDBusGetString(dbus, &subIter, result))\n        {\n            foundAValue = true;\n            ffStrbufAppendS(result, \", \");\n        }\n\n        if(!dbus->lib->ffdbus_message_iter_next(&subIter))\n            break;\n        else\n            continue;\n    }\n\n    if(foundAValue)\n        ffStrbufSubstrBefore(result, result->length - 2);\n\n    return foundAValue;\n}\n\nbool ffDBusGetBool(FFDBusData* dbus, DBusMessageIter* iter, bool* result)\n{\n    int argType = dbus->lib->ffdbus_message_iter_get_arg_type(iter);\n\n    if(argType == DBUS_TYPE_BOOLEAN)\n    {\n        dbus_bool_t value = 0;\n        dbus->lib->ffdbus_message_iter_get_basic(iter, &value);\n        *result = value != 0;\n        return true;\n    }\n\n    if(argType != DBUS_TYPE_VARIANT)\n        return false;\n\n    DBusMessageIter subIter;\n    dbus->lib->ffdbus_message_iter_recurse(iter, &subIter);\n    return ffDBusGetBool(dbus, &subIter, result);\n}\n\nbool ffDBusGetUint(FFDBusData* dbus, DBusMessageIter* iter, uint32_t* result)\n{\n    int argType = dbus->lib->ffdbus_message_iter_get_arg_type(iter);\n\n    if(argType == DBUS_TYPE_BYTE)\n    {\n        uint8_t value = 0;\n        dbus->lib->ffdbus_message_iter_get_basic(iter, &value);\n        *result = value;\n        return true;\n    }\n\n    if(argType == DBUS_TYPE_UINT16)\n    {\n        uint16_t value = 0;\n        dbus->lib->ffdbus_message_iter_get_basic(iter, &value);\n        *result = value;\n        return true;\n    }\n\n    if(argType == DBUS_TYPE_UINT32)\n    {\n        dbus->lib->ffdbus_message_iter_get_basic(iter, result);\n        return true;\n    }\n\n    if(argType != DBUS_TYPE_VARIANT)\n        return false;\n\n    DBusMessageIter subIter;\n    dbus->lib->ffdbus_message_iter_recurse(iter, &subIter);\n    return ffDBusGetUint(dbus, &subIter, result);\n}\n\nbool ffDBusGetInt(FFDBusData* dbus, DBusMessageIter* iter, int32_t* result)\n{\n    int argType = dbus->lib->ffdbus_message_iter_get_arg_type(iter);\n\n    if(argType == DBUS_TYPE_INT16)\n    {\n        int16_t value = 0;\n        dbus->lib->ffdbus_message_iter_get_basic(iter, &value);\n        *result = value;\n        return true;\n    }\n\n    if(argType == DBUS_TYPE_INT32)\n    {\n        dbus->lib->ffdbus_message_iter_get_basic(iter, result);\n        return true;\n    }\n\n    if(argType == DBUS_TYPE_BYTE)\n    {\n        uint8_t value = 0;\n        dbus->lib->ffdbus_message_iter_get_basic(iter, &value);\n        *result = value;\n        return true;\n    }\n\n    if(argType == DBUS_TYPE_UINT16)\n    {\n        uint16_t value = 0;\n        dbus->lib->ffdbus_message_iter_get_basic(iter, &value);\n        *result = (int16_t) value;\n        return true;\n    }\n\n    if(argType == DBUS_TYPE_UINT32)\n    {\n        dbus->lib->ffdbus_message_iter_get_basic(iter, result);\n        return true;\n    }\n\n    if(argType != DBUS_TYPE_VARIANT)\n        return false;\n\n    DBusMessageIter subIter;\n    dbus->lib->ffdbus_message_iter_recurse(iter, &subIter);\n    return ffDBusGetInt(dbus, &subIter, result);\n}\n\nDBusMessage* ffDBusGetMethodReply(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* method, const char* arg1, const char* arg2)\n{\n    DBusMessage* message = dbus->lib->ffdbus_message_new_method_call(busName, objectPath, interface, method);\n    if(message == NULL)\n        return NULL;\n\n    if (arg1)\n    {\n        if (arg2)\n            dbus->lib->ffdbus_message_append_args(message, DBUS_TYPE_STRING, &arg1, DBUS_TYPE_STRING, &arg2, DBUS_TYPE_INVALID);\n        else\n            dbus->lib->ffdbus_message_append_args(message, DBUS_TYPE_STRING, &arg1, DBUS_TYPE_INVALID);\n    }\n\n    DBusMessage* reply = dbus->lib->ffdbus_connection_send_with_reply_and_block(dbus->connection, message, instance.config.general.processingTimeout, NULL);\n\n    dbus->lib->ffdbus_message_unref(message);\n\n    return reply;\n}\n\nDBusMessage* ffDBusGetProperty(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property)\n{\n    DBusMessage* message = dbus->lib->ffdbus_message_new_method_call(busName, objectPath, \"org.freedesktop.DBus.Properties\", \"Get\");\n    if(message == NULL)\n        return NULL;\n\n    dbus->lib->ffdbus_message_append_args(message,\n        DBUS_TYPE_STRING, &interface,\n        DBUS_TYPE_STRING, &property,\n        DBUS_TYPE_INVALID);\n\n    DBusMessage* reply = dbus->lib->ffdbus_connection_send_with_reply_and_block(dbus->connection, message, instance.config.general.processingTimeout, NULL);\n\n    dbus->lib->ffdbus_message_unref(message);\n\n    return reply;\n}\n\nbool ffDBusGetPropertyString(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property, FFstrbuf* result)\n{\n    DBusMessage* reply = ffDBusGetProperty(dbus, busName, objectPath, interface, property);\n    if(reply == NULL)\n        return false;\n\n    DBusMessageIter rootIterator;\n    if(!dbus->lib->ffdbus_message_iter_init(reply, &rootIterator))\n    {\n        dbus->lib->ffdbus_message_unref(reply);\n        return false;\n    }\n\n    bool ret = ffDBusGetString(dbus, &rootIterator, result);\n\n    dbus->lib->ffdbus_message_unref(reply);\n\n    return ret;\n}\n\nbool ffDBusGetPropertyUint(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property, uint32_t* result)\n{\n    DBusMessage* reply = ffDBusGetProperty(dbus, busName, objectPath, interface, property);\n    if(reply == NULL)\n        return false;\n\n    DBusMessageIter rootIterator;\n    if(!dbus->lib->ffdbus_message_iter_init(reply, &rootIterator))\n    {\n        dbus->lib->ffdbus_message_unref(reply);\n        return false;\n    }\n\n    bool ret = ffDBusGetUint(dbus, &rootIterator, result);\n\n    dbus->lib->ffdbus_message_unref(reply);\n\n    return ret;\n}\n\n#endif //FF_HAVE_DBUS\n"
  },
  {
    "path": "src/common/impl/debug_windows.c",
    "content": "#include \"common/debug.h\"\n\n#include <windows.h>\n\nconst char* ffDebugWin32Error(DWORD errorCode)\n{\n    static char buffer[256];\n\n    DWORD len = FormatMessageA(\n        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,\n        NULL,\n        (DWORD) errorCode,\n        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),\n        buffer,\n        sizeof(buffer),\n        NULL);\n\n    if (len == 0) {\n        snprintf(buffer, sizeof(buffer), \"Unknown error code (%lu)\", errorCode);\n    } else {\n        // Remove trailing newline\n        if (buffer[len - 1] == '\\n') buffer[len - 1] = '\\0';\n        if (buffer[len - 2] == '\\r') buffer[len - 2] = '\\0';\n        snprintf(buffer + len - 2, sizeof(buffer) - len + 2, \" (%lu)\", errorCode);\n    }\n\n    return buffer;\n}\n\nconst char* ffDebugNtStatus(NTSTATUS status)\n{\n    return ffDebugWin32Error(RtlNtStatusToDosError(status));\n}\n"
  },
  {
    "path": "src/common/impl/duration.c",
    "content": "#include \"common/duration.h\"\n\nvoid ffDurationAppendNum(uint64_t totalSeconds, FFstrbuf* result)\n{\n    const FFOptionsDisplay* options = &instance.config.display;\n\n    bool spaceBeforeUnit = options->durationSpaceBeforeUnit != FF_SPACE_BEFORE_UNIT_NEVER;\n\n    if (totalSeconds < 60)\n    {\n        ffStrbufAppendUInt(result, totalSeconds);\n        if (spaceBeforeUnit) ffStrbufAppendC(result, ' ');\n        ffStrbufAppendS(result, options->durationAbbreviation ? \"sec\" : \"second\");\n        if (totalSeconds != 1)\n            ffStrbufAppendC(result, 's');\n        return;\n    }\n\n    uint32_t seconds = (uint32_t) (totalSeconds % 60);\n    totalSeconds /= 60;\n    if (seconds >= 30)\n        totalSeconds++;\n\n    uint32_t minutes = (uint32_t) (totalSeconds % 60);\n    totalSeconds /= 60;\n    uint32_t hours = (uint32_t) (totalSeconds % 24);\n    totalSeconds /= 24;\n    uint32_t days = (uint32_t) totalSeconds;\n\n    if (days > 0)\n    {\n        ffStrbufAppendUInt(result, days);\n        if (spaceBeforeUnit) ffStrbufAppendC(result, ' ');\n        if (options->durationAbbreviation)\n        {\n            ffStrbufAppendC(result, 'd');\n\n            if (hours > 0 || minutes > 0)\n                ffStrbufAppendC(result, ' ');\n        }\n        else\n        {\n            ffStrbufAppendS(result, days == 1 ? \"day\" : \"days\");\n\n            if (days >= 100)\n                ffStrbufAppendS(result, \"(!)\");\n\n            if (hours > 0 || minutes > 0)\n                ffStrbufAppendS(result, \", \");\n        }\n    }\n\n    if (hours > 0)\n    {\n        ffStrbufAppendUInt(result, hours);\n        if (spaceBeforeUnit) ffStrbufAppendC(result, ' ');\n        if (options->durationAbbreviation)\n        {\n            ffStrbufAppendC(result, 'h');\n\n            if (minutes > 0)\n                ffStrbufAppendC(result, ' ');\n        }\n        else\n        {\n            ffStrbufAppendS(result, hours == 1 ? \"hour\" : \"hours\");\n\n            if (minutes > 0)\n                ffStrbufAppendS(result, \", \");\n        }\n    }\n\n    if (minutes > 0)\n    {\n        ffStrbufAppendUInt(result, minutes);\n        if (spaceBeforeUnit) ffStrbufAppendC(result, ' ');\n        if (options->durationAbbreviation)\n            ffStrbufAppendC(result, 'm');\n        else\n            ffStrbufAppendS(result, minutes == 1 ? \"min\" : \"mins\");\n    }\n}\n"
  },
  {
    "path": "src/common/impl/edidHelper.c",
    "content": "#include \"common/edidHelper.h\"\n\nvoid ffEdidGetPhysicalResolution(const uint8_t edid[128], uint32_t* width, uint32_t* height)\n{\n    const int dtd = 54;\n    *width = (((uint32_t) edid[dtd + 4] >> 4) << 8) | edid[dtd + 2];\n    *height = (((uint32_t) edid[dtd + 7] >> 4) << 8) | edid[dtd + 5];\n}\n\nvoid ffEdidGetPreferredResolutionAndRefreshRate(const uint8_t edid[128], uint32_t* width, uint32_t* height, double* refreshRate)\n{\n    for (uint32_t i = 0x36; i < 0x7E; i += 0x12)\n    { // read through descriptor blocks...\n        if (edid[i] != 0x00 && edid[i + 1] != 0x00)\n        { // a dtd\n            uint32_t hactive = edid[i+2] + (uint32_t) ((edid[i + 4] & 0xf0) << 4);\n            uint32_t hblank = edid[i+3] + (uint32_t) ((edid[i + 4] & 0x0f) << 8);\n            uint32_t vactive = edid[i+5] + (uint32_t) ((edid[i + 7] & 0xf0) << 4);\n            uint32_t vblank = edid[i+6] + (uint32_t) ((edid[i + 7] & 0x0f) << 8);\n            uint32_t pixclk = ((uint32_t) edid[i+1] << 8) | (edid[i]);\n            *width = hactive;\n            *height = vactive;\n            *refreshRate = (double)pixclk * 10000 / (double)(hactive+hblank) / (double)(vactive+vblank);\n            return;\n        }\n    }\n}\n\nvoid ffEdidGetVendorAndModel(const uint8_t edid[128], FFstrbuf* result)\n{\n    // https://github.com/jinksong/read_edid/blob/master/parse-edid/parse-edid.c\n    ffStrbufAppendF(result, \"%c%c%c%04X\",\n        (char) (((uint32_t)edid[8] >> 2 & 0x1f) + 'A' - 1),\n        (char) (((((uint32_t)edid[8] & 0x3) << 3) | (((uint32_t)edid[9] & 0xe0) >> 5)) + 'A' - 1),\n        (char) (((uint32_t)edid[9] & 0x1f) + 'A' - 1),\n        (uint32_t) (edid[10] + (uint32_t) (edid[11] << 8))\n    );\n}\n\nbool ffEdidGetName(const uint8_t edid[128], FFstrbuf* name)\n{\n    // https://github.com/jinksong/read_edid/blob/master/parse-edid/parse-edid.c\n    for (uint32_t i = 0x36; i < 0x7E; i += 0x12)\n    { // read through descriptor blocks...\n        if (edid[i] == 0x00)\n        { // not a timing descriptor\n            if (edid[i + 3] == 0xfc)\n            { // Model Name tag\n                for (uint32_t j = 0; j < 13; j++)\n                {\n                    if (edid[i + 5 + j] == 0x0a)\n                    {\n                        ffStrbufAppendNS(name, j, (const char*) &edid[i + 5]);\n                        return true;\n                    }\n                }\n            }\n        }\n    }\n\n    // use manufacturer + model number as monitor name\n    ffEdidGetVendorAndModel(edid, name);\n    return false;\n}\n\nvoid ffEdidGetPhysicalSize(const uint8_t edid[128], uint32_t* width, uint32_t* height)\n{\n    // Detailed Timing Descriptors\n    uint32_t dw = (((uint32_t) edid[68] & 0xF0) << 4) + edid[66];\n    uint32_t dh = (((uint32_t) edid[68] & 0x0F) << 8) + edid[67];\n\n    // Basic Display Parameters\n    uint32_t bw = edid[21] * 10;\n    uint32_t bh = edid[22] * 10;\n\n    // Some monitors report invalid data in DTD. See #1406\n    if (abs((int)dw - (int)bw) < 10 && abs((int)dh - (int)bh) < 10)\n    {\n        *width = dw;\n        *height = dh;\n    }\n    else\n    {\n        *width = bw;\n        *height = bh;\n    }\n}\n\nvoid ffEdidGetSerialAndManufactureDate(const uint8_t edid[128], uint32_t* serial, uint16_t* year, uint16_t* week)\n{\n    if (edid[17] > 0 && edid[17] < 0xFF)\n    {\n        *year = (uint16_t) (edid[17] + 1990);\n        *week = (uint16_t) edid[16];\n        if (*week == 0xFF) *week = 0;\n    }\n    else\n        *year = *week = 0;\n\n    *serial = *(uint32_t*) &edid[12];\n}\n\nbool ffEdidGetHdrCompatible(const uint8_t* edid, uint32_t length)\n{\n    if (length <= 128) return false;\n    for (const uint8_t* cta = &edid[128]; cta < &edid[length]; cta += 128)\n    {\n        // https://en.wikipedia.org/wiki/Extended_Display_Identification_Data#CTA_EDID_Timing_Extension_Block\n        if (cta[0] != 0x02 /* CTA EDID */) continue;\n        if (cta[1] < 0x03 /* Version 3 */) continue;\n        const uint8_t offset = cta[2];\n        if (offset <= 4) continue;\n        for (uint8_t i = 4; i < offset;)\n        {\n            uint8_t blkLen = cta[i] & 0x1f;\n            if (blkLen > 0)\n            {\n                uint8_t blkTag = (cta[i] & 0xe0) >> 5;\n                if (blkTag == 0x07 /* Extended Block Type Tag */)\n                {\n                    uint8_t extendedTag = cta[i + 1];\n                    if (extendedTag == 6 /* HDR SMDB */ || extendedTag == 7 /* HDR DMDB */)\n                        return true;\n                }\n            }\n            i += (uint8_t) (blkLen + 1);\n        }\n    }\n    return false;\n}\n\nbool ffEdidIsValid(const uint8_t edid[128], uint32_t length)\n{\n    if (length < 128 || length % 128 != 0) return false;\n\n    static const uint8_t edidHeader[] = { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 };\n    if (memcmp(edid, edidHeader, sizeof(edidHeader)) != 0) return false;\n\n    uint8_t sum = 0;\n    for (uint32_t i = 0; i < 128; i++) sum += edid[i];\n\n    return sum == 0;\n}\n"
  },
  {
    "path": "src/common/impl/font.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/FFlist.h\"\n#include \"common/FFstrbuf.h\"\n#include \"common/stringUtils.h\"\n#include \"common/font.h\"\n\n#include <string.h>\n#include <ctype.h>\n\nvoid ffFontInit(FFfont* font)\n{\n    // Ensure no memory allocates\n    ffStrbufInit(&font->pretty);\n    ffStrbufInit(&font->name);\n    ffStrbufInit(&font->size);\n    ffListInit(&font->styles, sizeof(FFstrbuf));\n}\n\nstatic void strbufAppendNSExcludingC(FFstrbuf* strbuf, uint32_t length, const char* value, char exclude)\n{\n    if(value == NULL || length == 0)\n        return;\n\n    ffStrbufEnsureFree(strbuf, length);\n\n    for(uint32_t i = 0; i < length; i++)\n    {\n        if(value[i] != exclude)\n        strbuf->chars[strbuf->length++] = value[i];\n    }\n\n    strbuf->chars[strbuf->length] = '\\0';\n}\n\nstatic void fontInitPretty(FFfont* font)\n{\n    ffStrbufAppend(&font->pretty, &font->name);\n\n    if(font->size.length == 0 && font->styles.length == 0)\n        return;\n    else if(font->pretty.length == 0)\n        ffStrbufAppendS(&font->pretty, \"default\");\n\n    ffStrbufAppendS(&font->pretty, \" (\");\n\n    if(font->size.length > 0)\n    {\n        ffStrbufAppend(&font->pretty, &font->size);\n        if (!ffStrbufEndsWithS(&font->size, \"pt\") && !ffStrbufEndsWithS(&font->size, \"px\"))\n            ffStrbufAppendS(&font->pretty, \"pt\");\n\n        if(font->styles.length > 0)\n            ffStrbufAppendS(&font->pretty, \", \");\n    }\n\n    for(uint32_t i = 0; i < font->styles.length; i++)\n    {\n        ffStrbufAppend(&font->pretty, FF_LIST_GET(FFstrbuf, font->styles, i));\n\n        if(i < font->styles.length - 1)\n            ffStrbufAppendS(&font->pretty, \", \");\n    }\n\n    ffStrbufAppendC(&font->pretty, ')');\n}\n\nvoid ffFontInitQt(FFfont* font, const char* data)\n{\n    ffFontInit(font);\n\n    //See https://doc.qt.io/qt-5/qfont.html#toString\n\n    //Family\n    data = ffStrbufAppendSUntilC(&font->name, data, ',');\n    ffStrbufTrim(&font->name, ' ');\n    if (!data) goto exit;\n    data++;\n\n    //Size\n    data = ffStrbufAppendSUntilC(&font->size, data, ',');\n    ffStrbufTrim(&font->size, ' ');\n    if (!data) goto exit;\n    data++;\n\n    //Style\n    data = strrchr(data, ',');\n    if (!data) goto exit;\n    data++;\n    if (isalpha(*data))\n    {\n        do\n        {\n            FFstrbuf* style = (FFstrbuf*) ffListAdd(&font->styles);\n            ffStrbufInit(style);\n            data = ffStrbufAppendSUntilC(style, data, ' ');\n            if (data) data++;\n        } while (data);\n    }\n\nexit:\n    fontInitPretty(font);\n}\n\nstatic void fontPangoParseWord(const char** data, FFfont* font, FFstrbuf* alternativeBuffer)\n{\n    while(**data == ' ' || **data == '\\t' || **data == ',')\n        ++(*data);\n\n    const char* wordStart = *data;\n\n    while(**data != ' ' && **data != '\\t' && **data != ',' && **data != '\\0' && **data != '`' && **data != '\\\\')\n        ++(*data);\n\n    uint32_t wordLength = (uint32_t) (*data - wordStart);\n    if(wordLength == 0)\n        return;\n\n    if(**data == '\\0' || **data == '`' || **data == '\\\\')\n    {\n        ffStrbufAppendNS(&font->size, wordLength, wordStart);\n        if(ffStrbufEndsWithS(&font->size, \"px\"))\n            ffStrbufSubstrBefore(&font->size, font->size.length - 2);\n\n        double dummy;\n        if(sscanf(font->size.chars, \"%lf\", &dummy) == 1)\n            return;\n\n        ffStrbufClear(&font->size);\n    }\n\n    if(\n        ffStrStartsWithIgnCase(wordStart, \"Ultra\") ||\n        ffStrStartsWithIgnCase(wordStart, \"Extra\") ||\n        ffStrStartsWithIgnCase(wordStart, \"Semi\") ||\n        ffStrStartsWithIgnCase(wordStart, \"Demi\") ||\n        ffStrStartsWithIgnCase(wordStart, \"Normal\") ||\n        ffStrStartsWithIgnCase(wordStart, \"Roman\") ||\n        ffStrStartsWithIgnCase(wordStart, \"Oblique\") ||\n        ffStrStartsWithIgnCase(wordStart, \"Italic\") ||\n        ffStrStartsWithIgnCase(wordStart, \"Thin\") ||\n        ffStrStartsWithIgnCase(wordStart, \"Light\") ||\n        ffStrStartsWithIgnCase(wordStart, \"Bold\") ||\n        ffStrStartsWithIgnCase(wordStart, \"Black\") ||\n        ffStrStartsWithIgnCase(wordStart, \"Condensed\") ||\n        ffStrStartsWithIgnCase(wordStart, \"Expanded\")\n    ) {\n        if(alternativeBuffer == NULL)\n        {\n            alternativeBuffer = (FFstrbuf*) ffListAdd(&font->styles);\n            ffStrbufInit(alternativeBuffer);\n        }\n\n        strbufAppendNSExcludingC(alternativeBuffer, wordLength, wordStart, '-');\n\n        if(\n            ffStrStartsWithIgnCase(wordStart, \"Ultra \") ||\n            ffStrStartsWithIgnCase(wordStart, \"Extra \") ||\n            ffStrStartsWithIgnCase(wordStart, \"Semi \") ||\n            ffStrStartsWithIgnCase(wordStart, \"Demi \")\n        ) {\n            fontPangoParseWord(data, font, alternativeBuffer);\n        }\n\n        return;\n    }\n\n    if(alternativeBuffer != NULL)\n    {\n        strbufAppendNSExcludingC(alternativeBuffer, wordLength, wordStart, '-');\n        return;\n    }\n\n    if(font->name.length > 0)\n        ffStrbufAppendC(&font->name, ' ');\n    ffStrbufAppendNS(&font->name, wordLength, wordStart);\n}\n\nvoid ffFontInitPango(FFfont* font, const char* data)\n{\n    ffFontInit(font);\n\n    while(*data != '\\0' && *data != '`' && *data != '\\\\')\n        fontPangoParseWord(&data, font, NULL);\n\n    fontInitPretty(font);\n}\n\nvoid ffFontInitValues(FFfont* font, const char* name, const char* size)\n{\n    ffFontInit(font);\n\n    ffStrbufAppendS(&font->name, name);\n    ffStrbufTrim(&font->name, '\"');\n    ffStrbufAppendS(&font->size, size);\n\n    fontInitPretty(font);\n}\n\nvoid ffFontInitXlfd(FFfont* font, const char* xlfd)\n{\n    assert(xlfd && *xlfd);\n\n    // https://en.wikipedia.org/wiki/X_logical_font_description\n    ffFontInit(font);\n\n    // XLFD: -foundry-family-weight-slant-setwidth-addstyle-pixelsize-pointsize-xres-yres-spacing-averagewidth-charsetregistry-charsetencoding\n    // It often starts with '-', which would create an empty first field. Skip it to align indexes.\n    if(*xlfd == '-')\n        xlfd++;\n\n    const char* pstart = xlfd;\n\n    for(int field = 0; field < 14; field++)\n    {\n        const char* pend = strchr(pstart, '-');\n        uint32_t length = pend ? (uint32_t)(pend - pstart) : (uint32_t) strlen(pstart);\n\n        if(length > 0)\n        {\n            if(field == 1) // family\n            {\n                ffStrbufAppendNS(&font->name, length, pstart);\n            }\n            else if(field == 7) // pointsize (decipoints, preferred)\n            {\n                // parse positive integer from substring\n                long deciPt = 0;\n                bool ok = true;\n                for(uint32_t i = 0; i < length; i++)\n                {\n                    char c = pstart[i];\n                    if(c < '0' || c > '9') { ok = false; break; }\n                    deciPt = deciPt * 10 + (c - '0');\n                }\n\n                if(ok && deciPt > 0)\n                {\n                    ffStrbufClear(&font->size);\n\n                    char tmp[32];\n                    if(deciPt % 10 == 0)\n                        snprintf(tmp, sizeof(tmp), \"%ldpt\", deciPt / 10);\n                    else\n                        snprintf(tmp, sizeof(tmp), \"%ld.%ldpt\", deciPt / 10, deciPt % 10);\n\n                    ffStrbufAppendS(&font->size, tmp);\n                }\n            }\n            else if(field == 6) // pixelsize (fallback if pointsize missing/invalid)\n            {\n                if(font->size.length == 0)\n                {\n                    long px = 0;\n                    bool ok = true;\n                    for(uint32_t i = 0; i < length; i++)\n                    {\n                        char c = pstart[i];\n                        if(c < '0' || c > '9') { ok = false; break; }\n                        px = px * 10 + (c - '0');\n                    }\n\n                    if(ok && px > 0)\n                    {\n                        ffStrbufAppendNS(&font->size, length, pstart);\n                        ffStrbufAppendS(&font->size, \"px\");\n                    }\n                }\n            }\n            else if(field >= 2 && field <= 5) // weight/slant/setwidth/addstyle\n            {\n                // ignore \"normal\" (case-insensitive)\n                if(!(length == 6 && ffStrStartsWithIgnCase(pstart, \"normal\")))\n                {\n                    FFstrbuf* style = (FFstrbuf*) ffListAdd(&font->styles);\n                    ffStrbufInitNS(style, length, pstart);\n                }\n            }\n        }\n\n        if(!pend)\n            break;\n\n        pstart = pend + 1;\n    }\n\n    fontInitPretty(font);\n}\n\nvoid ffFontInitXft(FFfont* font, const char* xft)\n{\n    assert(xft);\n\n    // https://en.wikipedia.org/wiki/Xft\n    // Xft/Fontconfig pattern examples:\n    //   \"DejaVu Sans Mono-10\"\n    //   \"monospace:size=10:weight=bold:slant=italic\"\n    //   \"Fira Code-12:style=Regular\"\n    // Goal: extract family(name), size, and some common styles.\n\n    ffFontInit(font);\n\n    // 1) Parse \"head\" part before first ':' => usually \"family[-size]\" (may include commas)\n    const char* p = xft;\n\n    while(*p == ' ' || *p == '\\t')\n        ++p;\n\n    const char* headStart = p;\n    while(*p != '\\0' && *p != ':')\n        ++p;\n    const char* headEnd = p;\n\n    // trim tail spaces\n    while(headEnd > headStart && (headEnd[-1] == ' ' || headEnd[-1] == '\\t'))\n        --headEnd;\n\n    // If multiple families are listed, take the first one (up to comma)\n    for(const char* q = headStart; q < headEnd; ++q)\n    {\n        if(*q == ',')\n        {\n            headEnd = q;\n            while(headEnd > headStart && (headEnd[-1] == ' ' || headEnd[-1] == '\\t'))\n                --headEnd;\n            break;\n        }\n    }\n\n    // Try parse trailing \"-<number>\" as size, otherwise entire head is name\n    const char* dashPos = NULL;\n    const char* sizeStart = NULL;\n\n    for(const char* q = headEnd; q > headStart; )\n    {\n        --q;\n        if(*q == '-' && (q + 1) < headEnd && ffCharIsDigit(q[1]))\n        {\n            dashPos = q;\n            sizeStart = q + 1;\n            break;\n        }\n    }\n\n    if(dashPos)\n    {\n        bool ok = true;\n        bool seenDigit = false;\n        for(const char* q = sizeStart; q < headEnd; ++q)\n        {\n            if(ffCharIsDigit(*q))\n                seenDigit = true;\n            else if(*q == '.')\n                continue;\n            else\n            {\n                ok = false;\n                break;\n            }\n        }\n\n        if(ok && seenDigit)\n        {\n            const char* nameEnd = dashPos;\n            while(nameEnd > headStart && (nameEnd[-1] == ' ' || nameEnd[-1] == '\\t'))\n                --nameEnd;\n\n            if(nameEnd > headStart)\n                ffStrbufAppendNS(&font->name, (uint32_t) (nameEnd - headStart), headStart);\n\n            if(headEnd > sizeStart)\n                ffStrbufAppendNS(&font->size, (uint32_t) (headEnd - sizeStart), sizeStart);\n        }\n        else\n        {\n            if(headEnd > headStart)\n                ffStrbufAppendNS(&font->name, (uint32_t) (headEnd - headStart), headStart);\n        }\n    }\n    else\n    {\n        if(headEnd > headStart)\n            ffStrbufAppendNS(&font->name, (uint32_t) (headEnd - headStart), headStart);\n    }\n\n    ffStrbufTrim(&font->name, ' ');\n    ffStrbufTrim(&font->name, '\"');\n\n    // 2) Parse key=value fields after ':' (Fontconfig-like). Fields separated by ':'.\n    // Common keys: size, pixelsize, pointsize, style, weight, slant, width\n    while(*p == ':')\n    {\n        ++p;\n\n        // key\n        const char* keyStart = p;\n        while(*p != '\\0' && *p != '=' && *p != ':')\n            ++p;\n        const char* keyEnd = p;\n\n        if(*p != '=')\n            continue; // skip tokens without '='\n\n        ++p; // skip '='\n\n        // value (until next ':', allow backslash-escaping)\n        FF_STRBUF_AUTO_DESTROY value = ffStrbufCreate();\n\n        while(*p != '\\0' && *p != ':')\n        {\n            if(*p == '\\\\' && p[1] != '\\0')\n            {\n                ++p;\n                ffStrbufAppendC(&value, *p);\n                ++p;\n                continue;\n            }\n\n            ffStrbufAppendC(&value, *p);\n            ++p;\n        }\n\n        ffStrbufTrim(&value, ' ');\n        ffStrbufTrim(&value, '\"');\n\n        uint32_t keyLen = (uint32_t) (keyEnd - keyStart);\n\n        // helper: set numeric size if not set yet\n        const bool sizeEmpty = (font->size.length == 0);\n        if(value.length > 0)\n        {\n            if(\n                (keyLen == 4 && ffStrStartsWithIgnCase(keyStart, \"size\")) ||\n                (keyLen == 9 && ffStrStartsWithIgnCase(keyStart, \"pixelsize\"))\n            )\n            {\n                if(sizeEmpty && ffCharIsDigit(value.chars[0]))\n                {\n                    ffStrbufAppend(&font->size, &value);\n                    ffStrbufAppendS(&font->size, keyLen == 4 ? \"pt\" : \"px\");\n                }\n            }\n            else if(keyLen == 5 && ffStrStartsWithIgnCase(keyStart, \"style\"))\n            {\n                // style may contain multiple words: \"Bold Italic\"\n                const char* s = value.chars;\n                while(*s != '\\0')\n                {\n                    while(*s == ' ' || *s == '\\t' || *s == ',')\n                        ++s;\n\n                    const char* w = s;\n                    while(*s != '\\0' && *s != ' ' && *s != '\\t' && *s != ',')\n                        ++s;\n\n                    if(s > w)\n                    {\n                        FFstrbuf* style = FF_LIST_ADD(FFstrbuf, font->styles);\n                        ffStrbufInitNS(style, (uint32_t) (s - w), w);\n                    }\n                }\n            }\n            else if(\n                (keyLen == 6 && ffStrStartsWithIgnCase(keyStart, \"weight\")) ||\n                (keyLen == 5 && ffStrStartsWithIgnCase(keyStart, \"slant\")) ||\n                (keyLen == 5 && ffStrStartsWithIgnCase(keyStart, \"width\"))\n            ) {\n                // normalize: remove '-' to align with other parsers (\"Semi-Bold\" -> \"SemiBold\")\n                FFstrbuf* style = FF_LIST_ADD(FFstrbuf, font->styles);\n                ffStrbufInit(style);\n                strbufAppendNSExcludingC(style, value.length, value.chars, '-');\n                ffStrbufTrim(style, ' ');\n            }\n        }\n    }\n\n    fontInitPretty(font);\n}\n\nvoid ffFontInitMoveValues(FFfont* font, FFstrbuf* name, FFstrbuf* size, FFstrbuf* style)\n{\n    ffFontInit(font);\n\n    if (name) ffStrbufInitMove(&font->name, name);\n    if (size) ffStrbufInitMove(&font->size, size);\n    if (style)\n    {\n        FFstrbuf* styleBuf = FF_LIST_ADD(FFstrbuf, font->styles);\n        ffStrbufInitMove(styleBuf, style);\n    }\n\n    fontInitPretty(font);\n}\n\nvoid ffFontInitWithSpace(FFfont* font, const char* rawName)\n{\n    const char* pspace = strrchr(rawName, ' ');\n    if(pspace == NULL)\n    {\n        ffFontInitCopy(font, rawName);\n        return;\n    }\n\n    ffFontInit(font);\n\n    ffStrbufAppendNS(&font->name, (uint32_t)(pspace - rawName), rawName);\n    ffStrbufAppendS(&font->size, pspace + 1);\n\n    fontInitPretty(font);\n}\n\nvoid ffFontDestroy(FFfont* font)\n{\n    ffStrbufDestroy(&font->pretty);\n    ffStrbufDestroy(&font->name);\n    ffStrbufDestroy(&font->size);\n\n    FF_LIST_FOR_EACH(FFstrbuf, str, font->styles)\n        ffStrbufDestroy(str);\n    ffListDestroy(&font->styles);\n}\n"
  },
  {
    "path": "src/common/impl/format.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/format.h\"\n#include \"common/parsing.h\"\n#include \"common/textModifier.h\"\n#include \"common/stringUtils.h\"\n\n#include <inttypes.h>\n\nvoid ffFormatAppendFormatArg(FFstrbuf* buffer, const FFformatarg* formatarg)\n{\n    switch(formatarg->type)\n    {\n        case FF_ARG_TYPE_INT:\n            ffStrbufAppendSInt(buffer, *(int32_t*)formatarg->value);\n            break;\n        case FF_ARG_TYPE_UINT:\n            ffStrbufAppendUInt(buffer, *(uint32_t*)formatarg->value);\n            break;\n        case FF_ARG_TYPE_UINT64:\n            ffStrbufAppendUInt(buffer, *(uint64_t*)formatarg->value);\n            break;\n        case FF_ARG_TYPE_UINT16:\n            ffStrbufAppendUInt(buffer, *(uint16_t*)formatarg->value);\n            break;\n        case FF_ARG_TYPE_UINT8:\n            ffStrbufAppendUInt(buffer, *(uint8_t*)formatarg->value);\n            break;\n        case FF_ARG_TYPE_STRING:\n            ffStrbufAppendS(buffer, (const char*)formatarg->value);\n            break;\n        case FF_ARG_TYPE_STRBUF:\n            ffStrbufAppend(buffer, (const FFstrbuf*)formatarg->value);\n            break;\n        case FF_ARG_TYPE_FLOAT:\n            ffStrbufAppendDouble(buffer, *(float*)formatarg->value, instance.config.display.fractionNdigits, instance.config.display.fractionTrailingZeros != FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER);\n            break;\n        case FF_ARG_TYPE_DOUBLE:\n            ffStrbufAppendDouble(buffer, *(double*)formatarg->value, instance.config.display.fractionNdigits, instance.config.display.fractionTrailingZeros != FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER);\n            break;\n        case FF_ARG_TYPE_BOOL:\n            ffStrbufAppendS(buffer, *(bool*)formatarg->value ? \"true\" : \"false\");\n            break;\n        case FF_ARG_TYPE_LIST:\n        {\n            const FFlist* list = (const FFlist*) formatarg->value;\n            for(uint32_t i = 0; i < list->length; i++)\n            {\n                ffStrbufAppend(buffer, FF_LIST_GET(FFstrbuf, *list, i));\n                if(i < list->length - 1)\n                    ffStrbufAppendS(buffer, \", \");\n            }\n            break;\n        }\n        default:\n            if(formatarg->type != FF_ARG_TYPE_NULL)\n                fprintf(stderr, \"Error: format string \\\"%s\\\": argument is not implemented: %i\\n\", buffer->chars, formatarg->type);\n            break;\n    }\n}\n\n/**\n * @brief parses a string to a uint32_t\n *\n * If the string can't be parsed, or is < 1, uint32_t max is returned.\n *\n * @param placeholderValue the string to parse\n * @return uint32_t the parsed value\n */\nstatic uint32_t getArgumentIndex(const char* placeholderValue, uint32_t numArgs, const FFformatarg* arguments)\n{\n    char firstChar = placeholderValue[0];\n    if (firstChar == '\\0')\n        return 0; // use arg counter\n\n    if (firstChar >= '0' && firstChar <= '9')\n    {\n        char* pEnd = NULL;\n        uint32_t result = (uint32_t) strtoul(placeholderValue, &pEnd, 10);\n        if (result > numArgs)\n            return UINT32_MAX;\n        if (*pEnd != '\\0')\n            return UINT32_MAX;\n        return result;\n    }\n    else if (ffCharIsEnglishAlphabet(firstChar))\n    {\n        for (uint32_t i = 0; i < numArgs; ++i)\n        {\n            const FFformatarg* arg = &arguments[i];\n            if (arg->name && ffStrEqualsIgnCase(placeholderValue, arg->name))\n                return i + 1;\n        }\n    }\n\n    return UINT32_MAX;\n}\n\nstatic inline void appendInvalidPlaceholder(FFstrbuf* buffer, const char* start, const FFstrbuf* placeholderValue, uint32_t index, uint32_t formatStringLength)\n{\n    ffStrbufAppendS(buffer, start);\n    ffStrbufAppend(buffer, placeholderValue);\n\n    if(index < formatStringLength)\n        ffStrbufAppendC(buffer, '}');\n}\n\nstatic inline bool formatArgSet(const FFformatarg* arg)\n{\n    return arg->value != NULL && (\n        (arg->type == FF_ARG_TYPE_DOUBLE && *(double*)arg->value > 0.0) ||\n        (arg->type == FF_ARG_TYPE_FLOAT && *(float*)arg->value > 0.0) ||\n        (arg->type == FF_ARG_TYPE_INT && *(int*)arg->value > 0) ||\n        (arg->type == FF_ARG_TYPE_STRBUF && ((FFstrbuf*)arg->value)->length > 0) ||\n        (arg->type == FF_ARG_TYPE_STRING && ffStrSet((char*)arg->value)) ||\n        (arg->type == FF_ARG_TYPE_UINT8 && *(uint8_t*)arg->value > 0) ||\n        (arg->type == FF_ARG_TYPE_UINT16 && *(uint16_t*)arg->value > 0) ||\n        (arg->type == FF_ARG_TYPE_UINT && *(uint32_t*)arg->value > 0) ||\n        (arg->type == FF_ARG_TYPE_BOOL && *(bool*)arg->value) ||\n        (arg->type == FF_ARG_TYPE_LIST && ((FFlist*)arg->value)->length > 0)\n    );\n}\n\nvoid ffParseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint32_t numArgs, const FFformatarg* arguments)\n{\n    uint32_t argCounter = 0;\n\n    uint32_t numOpenIfs = 0;\n    uint32_t numOpenNotIfs = 0;\n\n    FF_STRBUF_AUTO_DESTROY placeholderValue = ffStrbufCreate();\n\n    for(uint32_t i = 0; i < formatstr->length; ++i)\n    {\n        // if we don't have a placeholder start just copy the chars over to output buffer\n        if(formatstr->chars[i] != '{')\n        {\n            ffStrbufAppendC(buffer, formatstr->chars[i]);\n            continue;\n        }\n\n        // jump to next char, the start of the placeholder value\n        ++i;\n\n        // double {{ elvaluates to a single { and doesn't count as start\n        if(formatstr->chars[i] == '{')\n        {\n            ffStrbufAppendC(buffer, '{');\n            continue;\n        }\n\n        ffStrbufClear(&placeholderValue);\n\n        {\n            uint32_t iEnd = ffStrbufNextIndexC(formatstr, i, '}');\n            ffStrbufAppendNS(&placeholderValue, iEnd - i, &formatstr->chars[i]);\n            i = iEnd;\n        }\n\n        char firstChar = placeholderValue.chars[0];\n\n        if (placeholderValue.length == 1)\n        {\n            // test if for stop, if so break the loop\n            if (firstChar == '-')\n                break;\n\n            // test for end of an if, if so do nothing\n            if (firstChar == '?')\n            {\n                if(numOpenIfs == 0)\n                    appendInvalidPlaceholder(buffer, \"{\", &placeholderValue, i, formatstr->length);\n                else\n                    --numOpenIfs;\n\n                continue;\n            }\n\n            // test for end of a not if, if so do nothing\n            if (firstChar == '/')\n            {\n                if(numOpenNotIfs == 0)\n                    appendInvalidPlaceholder(buffer, \"{\", &placeholderValue, i, formatstr->length);\n                else\n                    --numOpenNotIfs;\n\n                continue;\n            }\n\n            // test for end of a color, if so do nothing\n            if (firstChar == '#')\n            {\n                if (!instance.config.display.pipe)\n                    ffStrbufAppendS(buffer, FASTFETCH_TEXT_MODIFIER_RESET);\n\n                continue;\n            }\n        }\n\n        // test for if, if so evaluate it\n        if (firstChar == '?')\n        {\n            ffStrbufSubstrAfter(&placeholderValue, 0);\n\n            uint32_t index = getArgumentIndex(placeholderValue.chars, numArgs, arguments);\n\n            // testing for an invalid index\n            if (index > numArgs || index < 1)\n            {\n                appendInvalidPlaceholder(buffer, \"{?\", &placeholderValue, i, formatstr->length);\n                continue;\n            }\n\n            // continue normally if an format arg is set and the value is > 0\n            if (formatArgSet(&arguments[index - 1]))\n            {\n                ++numOpenIfs;\n                continue;\n            }\n\n            // fastforward to the end of the if without printing the in between\n            i = ffStrbufNextIndexS(formatstr, i, \"{?}\") + 2; // 2 is the length of \"{?}\" - 1 because the loop will increment it again directly after continue\n            continue;\n        }\n\n        // test for not if, if so evaluate it\n        if (firstChar == '/')\n        {\n            ffStrbufSubstrAfter(&placeholderValue, 0);\n\n            uint32_t index = getArgumentIndex(placeholderValue.chars, numArgs, arguments);\n\n            // testing for an invalid index\n            if (index > numArgs || index < 1)\n            {\n                appendInvalidPlaceholder(buffer, \"{/\", &placeholderValue, i, formatstr->length);\n                continue;\n            }\n\n            //continue normally if an format arg is not set or the value is 0\n            if (!formatArgSet(&arguments[index - 1]))\n            {\n                ++numOpenNotIfs;\n                continue;\n            }\n\n            // fastforward to the end of the if without printing the in between\n            i = ffStrbufNextIndexS(formatstr, i, \"{/}\") + 2; // 2 is the length of \"{/}\" - 1 because the loop will increment it again directly after continue\n            continue;\n        }\n\n        //test for color, if so evaluate it\n        if (firstChar == '#')\n        {\n            if (!instance.config.display.pipe)\n            {\n                ffStrbufAppendS(buffer, \"\\e[\");\n                ffOptionParseColorNoClear(placeholderValue.chars + 1, buffer);\n                ffStrbufAppendC(buffer, 'm');\n            }\n            continue;\n        }\n\n        //test for constant or env var, if so evaluate it\n        if (firstChar == '$')\n        {\n            char* pend = NULL;\n            int32_t indexSigned = (int32_t) strtol(placeholderValue.chars + 1, &pend, 10);\n            if (pend == placeholderValue.chars + 1)\n            {\n                // treat placeholder as an environment variable\n                char* envValue = getenv(placeholderValue.chars + 1);\n                if (envValue)\n                    ffStrbufAppendS(buffer, envValue);\n                else\n                    appendInvalidPlaceholder(buffer, \"{\", &placeholderValue, i, formatstr->length);\n            }\n            else\n            {\n                // treat placeholder as a constant\n                uint32_t index = (uint32_t) (indexSigned < 0 ? (int32_t) instance.config.display.constants.length + indexSigned : indexSigned - 1);\n\n                if (*pend != '\\0' || instance.config.display.constants.length <= index)\n                    appendInvalidPlaceholder(buffer, \"{\", &placeholderValue, i, formatstr->length);\n                else\n                {\n                    FFstrbuf* item = FF_LIST_GET(FFstrbuf, instance.config.display.constants, index);\n                    ffStrbufAppend(buffer, item);\n                }\n            }\n            continue;\n        }\n\n        char* pSep = placeholderValue.chars;\n        char cSep = '\\0';\n        while (*pSep && *pSep != ':' && *pSep != '<' && *pSep != '>' && *pSep != '~')\n            ++pSep;\n        if (*pSep)\n        {\n            cSep = *pSep;\n            *pSep = '\\0';\n        }\n        else\n        {\n            pSep = NULL;\n        }\n\n        uint32_t index = getArgumentIndex(placeholderValue.chars, numArgs, arguments);\n\n        // test for invalid index\n        if (index == 0)\n            index = ++argCounter;\n\n        if (index > numArgs)\n        {\n            if (pSep) *pSep = cSep;\n            appendInvalidPlaceholder(buffer, \"{\", &placeholderValue, i, formatstr->length);\n            continue;\n        }\n\n        if (!cSep)\n            ffFormatAppendFormatArg(buffer, &arguments[index - 1]);\n        else if (cSep == '~')\n        {\n            FF_STRBUF_AUTO_DESTROY tempString = ffStrbufCreate();\n            ffFormatAppendFormatArg(&tempString, &arguments[index - 1]);\n\n            char* pEnd = NULL;\n            int32_t start = (int32_t) strtol(pSep + 1, &pEnd, 10);\n            if (start < 0)\n                start = (int32_t) tempString.length + start;\n            if (start >= 0 && (uint32_t) start < tempString.length)\n            {\n                if (*pEnd == '\\0')\n                    ffStrbufAppendNS(buffer, tempString.length - (uint32_t) start, &tempString.chars[start]);\n                else if (*pEnd == ',')\n                {\n                    int32_t end = (int32_t) strtol(pEnd + 1, &pEnd, 10);\n                    if (!*pEnd)\n                    {\n                        if (end < 0)\n                            end = (int32_t) tempString.length + end;\n                        if ((uint32_t) end > tempString.length)\n                            end = (int32_t) tempString.length;\n                        if (end > start)\n                            ffStrbufAppendNS(buffer, (uint32_t) (end - start), &tempString.chars[start]);\n                    }\n                }\n            }\n\n            if (*pEnd)\n            {\n                *pSep = cSep;\n                appendInvalidPlaceholder(buffer, \"{\", &placeholderValue, i, formatstr->length);\n                continue;\n            }\n        }\n        else\n        {\n            char* pEnd = NULL;\n            int32_t truncLength = (int32_t) strtol(pSep + 1, &pEnd, 10);\n            if (*pEnd != '\\0')\n            {\n                *pSep = cSep;\n                appendInvalidPlaceholder(buffer, \"{\", &placeholderValue, i, formatstr->length);\n                continue;\n            }\n\n            bool ellipsis = false;\n            if (truncLength < 0)\n            {\n                ellipsis = true;\n                truncLength = -truncLength;\n            }\n\n            FF_STRBUF_AUTO_DESTROY tempString = ffStrbufCreate();\n            ffFormatAppendFormatArg(&tempString, &arguments[index - 1]);\n            if (tempString.length == (uint32_t) truncLength)\n                ffStrbufAppend(buffer, &tempString);\n            else if (tempString.length > (uint32_t) truncLength)\n            {\n                if (cSep == ':')\n                {\n                    ffStrbufSubstrBefore(&tempString, (uint32_t) truncLength);\n                    ffStrbufTrimRightSpace(&tempString);\n                }\n                else\n                    ffStrbufSubstrBefore(&tempString, (uint32_t) (!ellipsis? truncLength : truncLength - 1));\n                ffStrbufAppend(buffer, &tempString);\n\n                if (ellipsis)\n                    ffStrbufAppendS(buffer, \"…\");\n            }\n            else if (cSep == ':')\n                ffStrbufAppend(buffer, &tempString);\n            else\n            {\n                if (cSep == '<')\n                {\n                    ffStrbufAppend(buffer, &tempString);\n                    ffStrbufAppendNC(buffer, (uint32_t) truncLength - tempString.length, ' ');\n                }\n                else\n                {\n                    ffStrbufAppendNC(buffer, (uint32_t) truncLength - tempString.length, ' ');\n                    ffStrbufAppend(buffer, &tempString);\n                }\n            }\n        }\n    }\n\n    if (!instance.config.display.pipe)\n        ffStrbufAppendS(buffer, FASTFETCH_TEXT_MODIFIER_RESET);\n}\n"
  },
  {
    "path": "src/common/impl/frequency.c",
    "content": "#include \"common/frequency.h\"\n\nbool ffFreqAppendNum(uint32_t mhz, FFstrbuf* result)\n{\n    if (mhz == 0)\n        return false;\n\n    const FFOptionsDisplay* options = &instance.config.display;\n    bool spaceBeforeUnit = options->freqSpaceBeforeUnit != FF_SPACE_BEFORE_UNIT_NEVER;\n    int8_t ndigits = options->freqNdigits;\n\n    if (ndigits >= 0)\n    {\n        ffStrbufAppendDouble(result, mhz / 1000., ndigits, true);\n        if (spaceBeforeUnit) ffStrbufAppendC(result, ' ');\n        ffStrbufAppendS(result, \"GHz\");\n    }\n    else\n    {\n        ffStrbufAppendUInt(result, mhz);\n        if (spaceBeforeUnit) ffStrbufAppendC(result, ' ');\n        ffStrbufAppendS(result, \"MHz\");\n    }\n    return true;\n}\n"
  },
  {
    "path": "src/common/impl/init.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/init.h\"\n#include \"common/parsing.h\"\n#include \"common/thread.h\"\n#include \"common/textModifier.h\"\n#include \"detection/displayserver/displayserver.h\"\n#include \"detection/terminaltheme/terminaltheme.h\"\n#include \"logo/logo.h\"\n\n#include <stdlib.h>\n#include <unistd.h>\n#include <locale.h>\n#ifdef _WIN32\n    #include <windows.h>\n    #include \"common/windows/unicode.h\"\n#else\n    #include <signal.h>\n#endif\n\nFFinstance instance; // Global singleton\n\nstatic void initState(FFstate* state)\n{\n    state->logoWidth = 0;\n    state->logoHeight = 0;\n    state->keysHeight = 0;\n    state->terminalLightTheme = false;\n    state->titleFqdn = false;\n\n    ffPlatformInit(&state->platform);\n    state->dynamicInterval = 0;\n\n    {\n        // don't enable bright color if the terminal is in light mode\n        FFTerminalThemeResult result;\n        if (ffDetectTerminalTheme(&result, true /* forceEnv for performance */) && !result.bg.dark)\n            state->terminalLightTheme = true;\n    }\n}\n\nstatic void defaultConfig(void)\n{\n    ffOptionsInitLogo(&instance.config.logo);\n    ffOptionsInitGeneral(&instance.config.general);\n    ffOptionsInitDisplay(&instance.config.display);\n}\n\nvoid ffInitInstance(void)\n{\n    #ifdef _WIN32\n        // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/setlocale-wsetlocale?source=recommendat>\n        setlocale(LC_ALL, \".UTF8\");\n    #else\n        // Never use `setlocale(LC_ALL, \"\")`\n        setlocale(LC_TIME, \"\");\n    #endif\n\n    defaultConfig();\n    initState(&instance.state);\n}\n\nstatic volatile bool ffDisableLinewrap = true;\nstatic volatile bool ffHideCursor = true;\n\nstatic void resetConsole(void)\n{\n    if(ffDisableLinewrap)\n        fputs(\"\\033[?7h\", stdout);\n\n    if(ffHideCursor)\n        fputs(\"\\033[?25h\", stdout);\n\n    #if defined(_WIN32)\n        fflush(stdout);\n    #endif\n\n    if(instance.state.dynamicInterval > 0)\n        fputs(\"\\033[?1049l\", stdout); // Disable alternate buffer\n}\n\n#ifdef _WIN32\nBOOL WINAPI consoleHandler(FF_MAYBE_UNUSED DWORD signal)\n{\n    resetConsole();\n    exit(0);\n}\n#else\nstatic void exitSignalHandler(FF_MAYBE_UNUSED int signal)\n{\n    resetConsole();\n    exit(0);\n}\n#endif\n\nvoid ffStart(void)\n{\n    ffDisableLinewrap = instance.config.display.disableLinewrap && !instance.config.display.pipe;\n    ffHideCursor = instance.config.display.hideCursor && !instance.config.display.pipe;\n\n    #ifdef _WIN32\n    SetErrorMode(SEM_FAILCRITICALERRORS);\n    if (instance.config.display.noBuffer)\n        setvbuf(stdout, NULL, _IONBF, 0);\n    else\n        setvbuf(stdout, NULL, _IOFBF, 4096);\n    SetConsoleCtrlHandler(consoleHandler, TRUE);\n    HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);\n    DWORD mode = 0;\n    GetConsoleMode(hStdout, &mode);\n    SetConsoleMode(hStdout, mode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);\n    SetConsoleOutputCP(CP_UTF8);\n    #else\n    if (instance.config.display.noBuffer) setvbuf(stdout, NULL, _IONBF, 0);\n    struct sigaction action = { .sa_handler = exitSignalHandler };\n    sigaction(SIGINT, &action, NULL);\n    sigaction(SIGTERM, &action, NULL);\n    sigaction(SIGQUIT, &action, NULL);\n    sigset_t newmask;\n    sigemptyset(&newmask);\n    sigaddset(&newmask, SIGCHLD);\n    sigprocmask(SIG_BLOCK, &newmask, NULL);\n    #endif\n\n    //reset everything to default before we start printing\n    if(!instance.config.display.pipe)\n        fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout);\n\n    if(ffHideCursor)\n        fputs(\"\\033[?25l\", stdout);\n\n    if(ffDisableLinewrap)\n        fputs(\"\\033[?7l\", stdout);\n\n    if(instance.state.dynamicInterval > 0)\n    {\n        fputs(\"\\033[?1049h\\033[H\", stdout); // Enable alternate buffer\n        fflush(stdout);\n    }\n}\n\nvoid ffFinish(void)\n{\n    resetConsole();\n}\n\nstatic void destroyConfig(void)\n{\n    ffOptionsDestroyLogo(&instance.config.logo);\n    ffOptionsDestroyGeneral(&instance.config.general);\n    ffOptionsDestroyDisplay(&instance.config.display);\n}\n\nstatic void destroyState(void)\n{\n    ffPlatformDestroy(&instance.state.platform);\n}\n\nvoid ffDestroyInstance(void)\n{\n    destroyConfig();\n    destroyState();\n}\n\n//Must be in a file compiled with the libfastfetch target, because the FF_HAVE* macros are not defined for the executable targets\nvoid ffListFeatures(void)\n{\n    fputs(\n        #if FF_HAVE_THREADS\n            \"threads\\n\"\n        #endif\n        #if FF_HAVE_VULKAN\n            \"vulkan\\n\"\n        #endif\n        #if FF_HAVE_WAYLAND\n            \"wayland\\n\"\n        #endif\n        #if FF_HAVE_XCB_RANDR\n            \"xcb-randr\\n\"\n        #endif\n        #if FF_HAVE_XRANDR\n            \"xrandr\\n\"\n        #endif\n        #if FF_HAVE_DRM\n            \"drm\\n\"\n        #endif\n        #if FF_HAVE_DRM_AMDGPU\n            \"drm_amdgpu\\n\"\n        #endif\n        #if FF_HAVE_GIO\n            \"gio\\n\"\n        #endif\n        #if FF_HAVE_DCONF\n            \"dconf\\n\"\n        #endif\n        #if FF_HAVE_DBUS\n            \"dbus\\n\"\n        #endif\n        #if FF_HAVE_IMAGEMAGICK7\n            \"imagemagick7\\n\"\n        #endif\n        #if FF_HAVE_IMAGEMAGICK6\n            \"imagemagick6\\n\"\n        #endif\n        #if FF_HAVE_CHAFA\n            \"chafa\\n\"\n        #endif\n        #if FF_HAVE_ZLIB\n            \"zlib\\n\"\n        #endif\n        #if FF_HAVE_SQLITE3\n            \"sqlite3\\n\"\n        #endif\n        #if FF_HAVE_RPM\n            \"rpm\\n\"\n        #endif\n        #if FF_HAVE_EGL\n            \"egl\\n\"\n        #endif\n        #if FF_HAVE_GLX\n            \"glx\\n\"\n        #endif\n        #if FF_HAVE_OPENCL\n            \"opencl\\n\"\n        #endif\n        #if FF_HAVE_FREETYPE\n            \"freetype\\n\"\n        #endif\n        #if FF_HAVE_PULSE\n            \"libpulse\\n\"\n        #endif\n        #if FF_HAVE_DDCUTIL\n            \"libddcutil\\n\"\n        #endif\n        #if FF_HAVE_ELF || __sun || (__FreeBSD__ && !__DragonFly__) || __OpenBSD__ || __NetBSD__\n            \"libelf\\n\"\n        #endif\n        #if FF_HAVE_LIBZFS\n            \"libzfs\\n\"\n        #endif\n        #if FF_HAVE_DIRECTX_HEADERS\n            \"Directx Headers\\n\"\n        #endif\n        #if FF_USE_SYSTEM_YYJSON\n            \"System yyjson\\n\"\n        #endif\n        #if FF_HAVE_LINUX_VIDEODEV2\n            \"linux/videodev2\\n\"\n        #endif\n        #if FF_HAVE_LINUX_WIRELESS\n            \"linux/wireless\\n\"\n        #endif\n        #if FF_HAVE_EMBEDDED_PCIIDS\n            \"Embedded pciids\\n\"\n        #endif\n        #if FF_WIN81_COMPAT\n            \"Windows 8.1 Compatibility\\n\"\n        #endif\n        #if FF_APPLE_MEMSIZE_USABLE\n            \"Apple memsize_usable\\n\"\n        #endif\n        \"\"\n    , stdout);\n}\n"
  },
  {
    "path": "src/common/impl/io_unix.c",
    "content": "#include \"common/io.h\"\n#include \"fastfetch.h\"\n#include \"common/stringUtils.h\"\n#include \"common/time.h\"\n\n#include <fcntl.h>\n#include <termios.h>\n#include <dirent.h>\n#include <errno.h>\n#ifndef __APPLE__\n#include <poll.h>\n#else\n#include <sys/select.h>\n#endif\n\n#if FF_HAVE_WORDEXP\n    #include <wordexp.h>\n#else\n    #include <glob.h>\n#endif\n\nstatic void createSubfolders(const char* fileName)\n{\n    FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();\n\n    const char *token = NULL;\n    while((token = strchr(fileName, '/')) != NULL)\n    {\n        ffStrbufAppendNS(&path, (uint32_t)(token - fileName + 1), fileName);\n        mkdir(path.chars, S_IRWXU | S_IRGRP | S_IROTH);\n        fileName = token + 1;\n    }\n}\n\nbool ffWriteFileData(const char* fileName, size_t dataSize, const void* data)\n{\n    int openFlagsModes = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC;\n    mode_t openFlagsRights = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;\n\n    int FF_AUTO_CLOSE_FD fd = open(fileName, openFlagsModes, openFlagsRights);\n    if(fd == -1)\n    {\n        if (errno == ENOENT)\n        {\n            createSubfolders(fileName);\n            fd = open(fileName, openFlagsModes, openFlagsRights);\n            if(fd == -1)\n                return false;\n        }\n        else\n            return false;\n    }\n\n    return write(fd, data, dataSize) > 0;\n}\n\nstatic inline void readWithLength(int fd, FFstrbuf* buffer, uint32_t length)\n{\n    ffStrbufEnsureFixedLengthFree(buffer, length);\n    ssize_t bytesRead = 0;\n    while(\n        length > 0 && (bytesRead = read(fd, buffer->chars + buffer->length, length)) > 0\n    ) {\n        buffer->length += (uint32_t) bytesRead;\n        length -= (uint32_t) bytesRead;\n    }\n}\n\nstatic inline void readUntilEOF(int fd, FFstrbuf* buffer)\n{\n    ffStrbufEnsureFree(buffer, 31);\n    uint32_t available = ffStrbufGetFree(buffer);\n    ssize_t bytesRead = 0;\n    while(\n        (bytesRead = read(fd, buffer->chars + buffer->length, available)) > 0\n    ) {\n        buffer->length += (uint32_t) bytesRead;\n        if((uint32_t) bytesRead == available)\n            ffStrbufEnsureFree(buffer, buffer->allocated - 1); // Doubles capacity every round. -1 for the null byte.\n        available = ffStrbufGetFree(buffer);\n    }\n}\n\nbool ffAppendFDBuffer(int fd, FFstrbuf* buffer)\n{\n    struct stat fileInfo;\n    if(fstat(fd, &fileInfo) != 0)\n        return false;\n\n    if (fileInfo.st_size > 0)\n        readWithLength(fd, buffer, (uint32_t)fileInfo.st_size);\n    else\n        readUntilEOF(fd, buffer);\n\n    buffer->chars[buffer->length] = '\\0';\n\n    return buffer->length > 0;\n}\n\nssize_t ffReadFileData(const char* fileName, size_t dataSize, void* data)\n{\n    int FF_AUTO_CLOSE_FD fd = open(fileName, O_RDONLY | O_CLOEXEC);\n    if(fd == -1)\n        return -1;\n\n    return ffReadFDData(fd, dataSize, data);\n}\n\nssize_t ffReadFileDataRelative(int dfd, const char* fileName, size_t dataSize, void* data)\n{\n    int FF_AUTO_CLOSE_FD fd = openat(dfd, fileName, O_RDONLY | O_CLOEXEC);\n    if(fd == -1)\n        return -1;\n\n    return ffReadFDData(fd, dataSize, data);\n}\n\nbool ffAppendFileBuffer(const char* fileName, FFstrbuf* buffer)\n{\n    int FF_AUTO_CLOSE_FD fd = open(fileName, O_RDONLY | O_CLOEXEC);\n    if(fd == -1)\n        return false;\n\n    return ffAppendFDBuffer(fd, buffer);\n}\n\nbool ffAppendFileBufferRelative(int dfd, const char* fileName, FFstrbuf* buffer)\n{\n    int FF_AUTO_CLOSE_FD fd = openat(dfd, fileName, O_RDONLY | O_CLOEXEC);\n    if(fd == -1)\n        return false;\n\n    return ffAppendFDBuffer(fd, buffer);\n}\n\nbool ffPathExpandEnv(const char* in, FFstrbuf* out)\n{\n    bool result = false;\n\n    #if FF_HAVE_WORDEXP\n\n    wordexp_t exp;\n    if (wordexp(in, &exp, 0) != 0) // WARN: 0 = no safety flags; command substitution allowed\n        return false;\n\n    if (exp.we_wordc >= 1)\n    {\n        result = true;\n        ffStrbufSetS(out, exp.we_wordv[exp.we_wordc > 1 ? ffTimeGetNow() % exp.we_wordc : 0]);\n    }\n\n    wordfree(&exp);\n\n    #else\n\n    glob_t gb;\n    if (glob(in, GLOB_NOSORT\n            #ifdef GLOB_TILDE\n            | GLOB_TILDE\n            #endif\n            #ifdef GLOB_BRACE\n            | GLOB_BRACE\n            #endif\n        , NULL, &gb) != 0)\n        return false;\n\n    if (gb.gl_pathc >= 1)\n    {\n        result = true;\n        ffStrbufSetS(out, gb.gl_pathv[gb.gl_pathc > 1 ? ffTimeGetNow() % (unsigned) gb.gl_pathc : 0]);\n    }\n\n    globfree(&gb);\n\n    #endif\n\n    return result;\n}\n\nstatic int ftty = -1;\nstatic struct termios oldTerm;\nvoid restoreTerm(void)\n{\n    tcsetattr(ftty, TCSAFLUSH, &oldTerm);\n}\n\nconst char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...)\n{\n    if (ftty < 0)\n    {\n        ftty = open(\"/dev/tty\", O_RDWR | O_NOCTTY | O_CLOEXEC);\n        if (ftty < 0)\n            return \"open(\\\"/dev/tty\\\", O_RDWR | O_NOCTTY | O_CLOEXEC) failed\";\n\n        if(tcgetattr(ftty, &oldTerm) == -1)\n            return \"tcgetattr(STDIN_FILENO, &oldTerm) failed\";\n\n        struct termios newTerm = oldTerm;\n        newTerm.c_lflag &= (tcflag_t) ~(ICANON | ECHO);\n        if(tcsetattr(ftty, TCSAFLUSH, &newTerm) == -1)\n            return \"tcsetattr(STDIN_FILENO, TCSAFLUSH, &newTerm)\";\n        atexit(restoreTerm);\n    }\n\n    ffWriteFDData(ftty, strlen(request), request);\n\n    //Give the terminal some time to respond\n    #ifndef __APPLE__\n    if(poll(&(struct pollfd) { .fd = ftty, .events = POLLIN }, 1, FF_IO_TERM_RESP_WAIT_MS) <= 0)\n        return \"poll(/dev/tty) timeout or failed\";\n    #else\n    {\n        // On macOS, poll(/dev/tty) always returns immediately\n        // See also https://nathancraddock.com/blog/macos-dev-tty-polling/\n        fd_set rd;\n        FD_ZERO(&rd);\n        FD_SET(ftty, &rd);\n        if(select(ftty + 1, &rd, NULL, NULL, &(struct timeval) { .tv_sec = FF_IO_TERM_RESP_WAIT_MS / 1000, .tv_usec = (FF_IO_TERM_RESP_WAIT_MS % 1000) * 1000 }) <= 0)\n            return \"select(/dev/tty) timeout or failed\";\n    }\n    #endif\n\n    char buffer[1024];\n    size_t bytesRead = 0;\n\n    va_list args;\n    va_start(args, format);\n\n    while (true)\n    {\n        ssize_t nRead = read(ftty, buffer + bytesRead, sizeof(buffer) - bytesRead - 1);\n\n        if (nRead <= 0)\n        {\n            va_end(args);\n            return \"read(STDIN_FILENO, buffer, sizeof(buffer) - 1) failed\";\n        }\n\n        bytesRead += (size_t) nRead;\n        buffer[bytesRead] = '\\0';\n\n        va_list cargs;\n        va_copy(cargs, args);\n        int ret = vsscanf(buffer, format, cargs);\n        va_end(cargs);\n\n        if (ret <= 0)\n        {\n            va_end(args);\n            return \"vsscanf(buffer, format, args) failed\";\n        }\n        if (ret >= nParams)\n            break;\n    }\n\n    va_end(args);\n\n    return NULL;\n}\n\nbool ffSuppressIO(bool suppress)\n{\n    #ifndef NDEBUG\n    if (instance.config.display.debugMode)\n        return false;\n    #endif\n\n    static bool init = false;\n    static int origOut = -1;\n    static int origErr = -1;\n    static int nullFile = -1;\n\n    if(!init)\n    {\n        if(!suppress)\n            return true;\n\n        origOut = dup(STDOUT_FILENO);\n        origErr = dup(STDERR_FILENO);\n        nullFile = open(\"/dev/null\", O_WRONLY | O_CLOEXEC);\n        init = true;\n    }\n\n    if(nullFile == -1)\n        return false;\n\n    fflush(stdout);\n    fflush(stderr);\n\n    dup2(suppress ? nullFile : origOut, STDOUT_FILENO);\n    dup2(suppress ? nullFile : origErr, STDERR_FILENO);\n    return true;\n}\n\nvoid listFilesRecursively(uint32_t baseLength, FFstrbuf* folder, uint8_t indentation, const char* folderName, bool pretty)\n{\n    FF_AUTO_CLOSE_FD int dfd = open(folder->chars, O_RDONLY | O_CLOEXEC);\n    if (dfd < 0)\n        return;\n\n    DIR* dir = fdopendir(dfd);\n    if(dir == NULL)\n        return;\n\n    uint32_t folderLength = folder->length;\n\n    if(pretty && folderName != NULL)\n    {\n        for(uint8_t i = 0; i < indentation - 1; i++)\n            fputs(\"  | \", stdout);\n        printf(\"%s/\\n\", folderName);\n    }\n\n    struct dirent* entry;\n\n    while((entry = readdir(dir)) != NULL)\n    {\n        if(entry->d_name[0] == '.') // skip hidden files\n            continue;\n\n        bool isDir = false;\n#if !defined(__sun) && !defined(__HAIKU__)\n        if(entry->d_type != DT_UNKNOWN && entry->d_type != DT_LNK)\n            isDir = entry->d_type == DT_DIR;\n        else\n#endif\n        {\n            struct stat stbuf;\n            if (fstatat(dfd, entry->d_name, &stbuf, 0) < 0)\n                isDir = false;\n            else\n                isDir = S_ISDIR(stbuf.st_mode);\n        }\n        if (isDir)\n        {\n            ffStrbufAppendS(folder, entry->d_name);\n            ffStrbufAppendC(folder, '/');\n            listFilesRecursively(baseLength, folder, (uint8_t) (indentation + 1), entry->d_name, pretty);\n            ffStrbufSubstrBefore(folder, folderLength);\n            continue;\n        }\n\n        if (pretty)\n        {\n            for(uint8_t i = 0; i < indentation; i++)\n                fputs(\"  | \", stdout);\n        }\n        else\n        {\n            fputs(folder->chars + baseLength, stdout);\n        }\n\n        puts(entry->d_name);\n    }\n\n    closedir(dir);\n}\n\nvoid ffListFilesRecursively(const char* path, bool pretty)\n{\n    FF_STRBUF_AUTO_DESTROY folder = ffStrbufCreateS(path);\n    ffStrbufEnsureEndsWithC(&folder, '/');\n    listFilesRecursively(folder.length, &folder, 0, NULL, pretty);\n}\n\nFFNativeFD ffGetNullFD(void)\n{\n    static FFNativeFD hNullFile = -1;\n    if (hNullFile != -1)\n        return hNullFile;\n    hNullFile = open(\"/dev/null\", O_WRONLY | O_CLOEXEC);\n    return hNullFile;\n}\n\nbool ffRemoveFile(const char* fileName)\n{\n    return unlink(fileName) == 0;\n}\n"
  },
  {
    "path": "src/common/impl/io_windows.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n#include \"common/windows/nt.h\"\n#include \"common/windows/unicode.h\"\n\n#include <windows.h>\n\nstatic bool createSubfolders(wchar_t* fileName)\n{\n    HANDLE hRoot = ffGetPeb()->ProcessParameters->CurrentDirectory.Handle;\n    bool closeRoot = false;\n    wchar_t* ptr = fileName;\n\n    // Absolute drive path: C:\\...\n    if (ffCharIsEnglishAlphabet((char)ptr[0]) && ptr[1] == L':' && ptr[2] == L'\\\\')\n    {\n        wchar_t saved = ptr[3];\n        ptr[3] = L'\\0';\n\n        hRoot = CreateFileW(\n            fileName,\n            FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE,\n            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n            NULL,\n            OPEN_EXISTING,\n            FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_FLAG_BACKUP_SEMANTICS,\n            NULL\n        );\n\n        ptr[3] = saved;\n        if (hRoot == INVALID_HANDLE_VALUE)\n            return false;\n\n        closeRoot = true;\n        ptr += 3; // skip \"C:\\\"\n    }\n    // UNC path: \\\\server\\share\\...\n    else if (ptr[0] == L'\\\\' && ptr[1] == L'\\\\')\n    {\n        wchar_t* serverEnd = wcschr(ptr + 2, L'\\\\');\n        if (serverEnd == NULL)\n            return false;\n\n        wchar_t* shareEnd = wcschr(serverEnd + 1, L'\\\\');\n        if (shareEnd == NULL)\n            return true; // no parent subfolder exists before file name\n\n        wchar_t saved = *shareEnd;\n        *shareEnd = L'\\0';\n\n        hRoot = CreateFileW(\n            fileName,\n            FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE,\n            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n            NULL,\n            OPEN_EXISTING,\n            FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_FLAG_BACKUP_SEMANTICS,\n            NULL\n        );\n\n        *shareEnd = saved;\n        if (hRoot == INVALID_HANDLE_VALUE)\n            return false;\n\n        closeRoot = true;\n        ptr = shareEnd + 1; // first component under share\n    }\n    // Rooted path on current drive: \\foo\\bar\n    else if (ptr[0] == L'\\\\')\n    {\n        UNICODE_STRING* dosPath = &ffGetPeb()->ProcessParameters->CurrentDirectory.DosPath;\n        wchar_t driveRoot[] = { dosPath->Buffer[0], L':', L'\\\\', L'\\0' };\n        hRoot = CreateFileW(\n            driveRoot,\n            FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE,\n            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n            NULL,\n            OPEN_EXISTING,\n            FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_FLAG_BACKUP_SEMANTICS,\n            NULL\n        );\n        if (hRoot == INVALID_HANDLE_VALUE)\n            return false;\n        closeRoot = true;\n        ptr++; // skip leading '\\'\n    }\n\n    while (true)\n    {\n        wchar_t* token = wcschr(ptr, L'\\\\');\n        if (token == NULL)\n            break;\n\n        // Skip empty path segments caused by duplicated '\\'\n        if (token == ptr)\n        {\n            ptr = token + 1;\n            continue;\n        }\n\n        HANDLE hNew = INVALID_HANDLE_VALUE;\n        IO_STATUS_BLOCK iosb = {};\n\n        NTSTATUS status = NtCreateFile(\n            &hNew,\n            FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE,\n            &(OBJECT_ATTRIBUTES) {\n                .Length = sizeof(OBJECT_ATTRIBUTES),\n                .RootDirectory = hRoot,\n                .ObjectName = &(UNICODE_STRING) {\n                    .Buffer = ptr,\n                    .Length = (USHORT)((USHORT)(token - ptr) * sizeof(wchar_t)),\n                    .MaximumLength = (USHORT)((USHORT)(token - ptr) * sizeof(wchar_t)),\n                },\n                .Attributes = OBJ_CASE_INSENSITIVE,\n            },\n            &iosb,\n            NULL,\n            FILE_ATTRIBUTE_NORMAL,\n            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n            FILE_OPEN_IF,\n            FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,\n            NULL,\n            0\n        );\n\n        if (!NT_SUCCESS(status))\n        {\n            if (closeRoot && hRoot != INVALID_HANDLE_VALUE)\n                NtClose(hRoot);\n            return false;\n        }\n\n        if (closeRoot && hRoot != INVALID_HANDLE_VALUE)\n            NtClose(hRoot);\n        hRoot = hNew;\n        closeRoot = true;\n\n        ptr = token + 1;\n    }\n\n    if (closeRoot && hRoot != INVALID_HANDLE_VALUE)\n        NtClose(hRoot);\n\n    return true;\n}\n\nbool ffWriteFileData(const char* fileName, size_t dataSize, const void* data)\n{\n    wchar_t fileNameW[MAX_PATH];\n    ULONG len = 0;\n    if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), &len, fileName, (ULONG)strlen(fileName) + 1)))\n        return false;\n\n    for (ULONG i = 0; i < len / sizeof(wchar_t); ++i)\n    {\n        if (fileNameW[i] == L'/')\n            fileNameW[i] = L'\\\\';\n    }\n\n    HANDLE FF_AUTO_CLOSE_FD handle = CreateFileW(fileNameW, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);\n    if (handle == INVALID_HANDLE_VALUE)\n    {\n        if (GetLastError() == ERROR_PATH_NOT_FOUND)\n        {\n            if (!createSubfolders(fileNameW))\n                return false;\n            handle = CreateFileW(fileNameW, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);\n            if (handle == INVALID_HANDLE_VALUE)\n                return false;\n        }\n        else\n            return false;\n    }\n\n    DWORD written;\n    return !!WriteFile(handle, data, (DWORD)dataSize, &written, NULL);\n}\n\nstatic inline void readWithLength(HANDLE handle, FFstrbuf* buffer, uint32_t length)\n{\n    ffStrbufEnsureFree(buffer, length);\n    DWORD bytesRead = 0;\n    while(\n        length > 0 &&\n        ReadFile(handle, buffer->chars + buffer->length, length, &bytesRead, NULL) != FALSE &&\n        bytesRead > 0\n    ) {\n        buffer->length += (uint32_t) bytesRead;\n        length -= (uint32_t) bytesRead;\n    }\n}\n\nstatic inline void readUntilEOF(HANDLE handle, FFstrbuf* buffer)\n{\n    ffStrbufEnsureFree(buffer, 31);\n    uint32_t available = ffStrbufGetFree(buffer);\n    DWORD bytesRead = 0;\n    while(\n        ReadFile(handle, buffer->chars + buffer->length, available, &bytesRead, NULL) != FALSE &&\n        bytesRead > 0\n    ) {\n        buffer->length += (uint32_t) bytesRead;\n        if((uint32_t) bytesRead == available)\n            ffStrbufEnsureFree(buffer, buffer->allocated - 1); // Doubles capacity every round. -1 for the null byte.\n        available = ffStrbufGetFree(buffer);\n    }\n}\n\nbool ffAppendFDBuffer(HANDLE handle, FFstrbuf* buffer)\n{\n    FILE_STANDARD_INFORMATION fileInfo;\n    IO_STATUS_BLOCK iosb;\n    if(!NT_SUCCESS(NtQueryInformationFile(handle, &iosb, &fileInfo, sizeof(fileInfo), FileStandardInformation)))\n        fileInfo.EndOfFile.QuadPart = 0;\n\n    if (fileInfo.EndOfFile.QuadPart > 0)\n        readWithLength(handle, buffer, (uint32_t)fileInfo.EndOfFile.QuadPart);\n    else\n        readUntilEOF(handle, buffer);\n\n    buffer->chars[buffer->length] = '\\0';\n\n    return buffer->length > 0;\n}\n\nssize_t ffReadFileData(const char* fileName, size_t dataSize, void* data)\n{\n    HANDLE FF_AUTO_CLOSE_FD handle = CreateFileA(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);\n    if(handle == INVALID_HANDLE_VALUE)\n        return -1;\n\n    return ffReadFDData(handle, dataSize, data);\n}\n\nbool ffAppendFileBuffer(const char* fileName, FFstrbuf* buffer)\n{\n    HANDLE FF_AUTO_CLOSE_FD handle = CreateFileA(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);\n    if(handle == INVALID_HANDLE_VALUE)\n        return false;\n\n    return ffAppendFDBuffer(handle, buffer);\n}\n\nHANDLE openatW(HANDLE dfd, const wchar_t* fileName, uint16_t fileNameLen, bool directory)\n{\n    assert(fileNameLen <= 0x7FFF);\n\n    HANDLE hFile;\n    IO_STATUS_BLOCK iosb = {};\n    if(!NT_SUCCESS(NtOpenFile(&hFile,\n        (directory ? FILE_LIST_DIRECTORY | FILE_TRAVERSE : FILE_READ_DATA | FILE_READ_EA) | FILE_READ_ATTRIBUTES | SYNCHRONIZE, &(OBJECT_ATTRIBUTES) {\n            .Length = sizeof(OBJECT_ATTRIBUTES),\n            .RootDirectory = dfd,\n            .ObjectName = &(UNICODE_STRING) {\n                .Buffer = (PWSTR) fileName,\n                .Length = fileNameLen * (USHORT) sizeof(wchar_t),\n                .MaximumLength = (fileNameLen + 1) * (USHORT) sizeof(wchar_t),\n            },\n            .Attributes = OBJ_CASE_INSENSITIVE,\n        },\n        &iosb,\n        FILE_SHARE_READ | (directory ? FILE_SHARE_WRITE | FILE_SHARE_DELETE : 0),\n        FILE_SYNCHRONOUS_IO_NONALERT | (directory ? FILE_DIRECTORY_FILE : FILE_NON_DIRECTORY_FILE)\n    )))\n        return INVALID_HANDLE_VALUE;\n\n    return hFile;\n}\n\nHANDLE openat(HANDLE dfd, const char* fileName, bool directory)\n{\n    wchar_t fileNameW[MAX_PATH];\n    ULONG len;\n    if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), &len, fileName, (ULONG)strlen(fileName) + 1)))\n        return INVALID_HANDLE_VALUE;\n    // Implies `fileNameW[len] = L'\\0';` and `len` includes the null terminator\n    len /= sizeof(wchar_t); // convert from bytes to characters\n\n    for (uint32_t i = 0; i < len - 1; ++i)\n    {\n        if (fileNameW[i] == L'/')\n            fileNameW[i] = L'\\\\';\n    }\n\n    return openatW(dfd, fileNameW, (uint16_t)(len - 1), directory);\n}\n\nbool ffAppendFileBufferRelative(HANDLE dfd, const char* fileName, FFstrbuf* buffer)\n{\n    HANDLE FF_AUTO_CLOSE_FD fd = openat(dfd, fileName, false);\n    if(fd == INVALID_HANDLE_VALUE)\n        return false;\n\n    return ffAppendFDBuffer(fd, buffer);\n}\n\nssize_t ffReadFileDataRelative(HANDLE dfd, const char* fileName, size_t dataSize, void* data)\n{\n    HANDLE FF_AUTO_CLOSE_FD fd = openat(dfd, fileName, false);\n    if(fd == INVALID_HANDLE_VALUE)\n        return -1;\n\n    return ffReadFDData(fd, dataSize, data);\n}\n\nbool ffPathExpandEnv(const char* in, FFstrbuf* out)\n{\n    if (in[0] == '~') {\n        if ((in[1] == '/' || in[1] == '\\\\' || in[1] == '\\0') && !ffStrContainsC(in, '%')) {\n            ffStrbufSet(out, &instance.state.platform.homeDir);\n            ffStrbufAppendS(out, in + 1);\n            return true;\n        }\n    }\n\n    wchar_t pathInW[MAX_PATH], pathOutW[MAX_PATH];\n    ULONG len = (ULONG) strlen(in);\n    if (!NT_SUCCESS(RtlUTF8ToUnicodeN(pathInW, (ULONG) sizeof(pathInW), &len, in, len)))\n        return false;\n    len /= sizeof(wchar_t); // convert from bytes to characters\n\n    size_t outLen; // in characters, including null terminator\n    if (!NT_SUCCESS(RtlExpandEnvironmentStrings(NULL, pathInW, len, pathOutW, ARRAY_SIZE(pathOutW), &outLen)))\n        return false;\n\n    ffStrbufSetNWS(out, (uint32_t) outLen - 1, pathOutW);\n    return true;\n}\n\nbool ffSuppressIO(bool suppress)\n{\n    #ifndef NDEBUG\n    if (instance.config.display.debugMode)\n        return false;\n    #endif\n\n    static bool init = false;\n    static HANDLE hOrigOut = INVALID_HANDLE_VALUE;\n    static HANDLE hOrigErr = INVALID_HANDLE_VALUE;\n    HANDLE hNullFile = ffGetNullFD();\n    static int fOrigOut = -1;\n    static int fOrigErr = -1;\n    static int fNullFile = -1;\n\n    if (!init)\n    {\n        if(!suppress)\n            return true;\n\n        hOrigOut = GetStdHandle(STD_OUTPUT_HANDLE);\n        hOrigErr = GetStdHandle(STD_ERROR_HANDLE);\n        fOrigOut = _dup(STDOUT_FILENO);\n        fOrigErr = _dup(STDERR_FILENO);\n        fNullFile = _open_osfhandle((intptr_t) hNullFile, 0);\n\n        init = true;\n    }\n    if (hNullFile == INVALID_HANDLE_VALUE || fNullFile == -1)\n        return false;\n\n    fflush(stdout);\n    fflush(stderr);\n\n    SetStdHandle(STD_OUTPUT_HANDLE, suppress ? hNullFile : hOrigOut);\n    SetStdHandle(STD_ERROR_HANDLE, suppress ? hNullFile : hOrigErr);\n    _dup2(suppress ? fNullFile : fOrigOut, STDOUT_FILENO);\n    _dup2(suppress ? fNullFile : fOrigErr, STDERR_FILENO);\n\n    return true;\n}\n\nvoid listFilesRecursively(uint32_t baseLength, FFstrbuf* folder, uint8_t indentation, const char* folderName, bool pretty)\n{\n    uint32_t folderLength = folder->length;\n\n    if(pretty && folderName != NULL)\n    {\n        for(uint8_t i = 0; i < indentation - 1; i++)\n            fputs(\"  | \", stdout);\n        printf(\"%s/\\n\", folderName);\n    }\n\n    ffStrbufAppendC(folder, '*');\n    WIN32_FIND_DATAA entry;\n    HANDLE hFind = FindFirstFileA(folder->chars, &entry);\n    ffStrbufTrimRight(folder, '*');\n    if(hFind == INVALID_HANDLE_VALUE)\n        return;\n\n    do\n    {\n        if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)\n        {\n            if(ffStrEquals(entry.cFileName, \".\") || ffStrEquals(entry.cFileName, \"..\"))\n                continue;\n\n            ffStrbufSubstrBefore(folder, folderLength);\n            ffStrbufAppendS(folder, entry.cFileName);\n            ffStrbufAppendC(folder, '/');\n            listFilesRecursively(baseLength, folder, (uint8_t) (indentation + 1), entry.cFileName, pretty);\n            ffStrbufSubstrBefore(folder, folderLength);\n            continue;\n        }\n\n        if (pretty)\n        {\n            for(uint8_t i = 0; i < indentation; i++)\n                fputs(\"  | \", stdout);\n        }\n        else\n        {\n            fputs(folder->chars + baseLength, stdout);\n        }\n\n        puts(entry.cFileName);\n    } while (FindNextFileA(hFind, &entry));\n    FindClose(hFind);\n}\n\nvoid ffListFilesRecursively(const char* path, bool pretty)\n{\n    FF_STRBUF_AUTO_DESTROY folder = ffStrbufCreateS(path);\n    ffStrbufEnsureEndsWithC(&folder, '/');\n    listFilesRecursively(folder.length, &folder, 0, NULL, pretty);\n}\n\nconst char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...)\n{\n    HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);\n    FF_AUTO_CLOSE_FD HANDLE hConin = INVALID_HANDLE_VALUE;\n    DWORD inputMode;\n    if (!GetConsoleMode(hInput, &inputMode))\n    {\n        hConin = CreateFileW(L\"CONIN$\", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, NULL);\n        hInput = hConin;\n    }\n    SetConsoleMode(hInput, 0);\n\n    FlushConsoleInputBuffer(hInput);\n\n    {\n        DWORD bytes = 0;\n        HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);\n        FF_AUTO_CLOSE_FD HANDLE hConout = INVALID_HANDLE_VALUE;\n        DWORD outputMode;\n        if (!GetConsoleMode(hOutput, &outputMode))\n        {\n            hConout = CreateFileW(L\"CONOUT$\", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, NULL);\n            hOutput = hConout;\n        }\n        WriteFile(hOutput, request, (DWORD) strlen(request), &bytes, NULL);\n    }\n\n    while (true)\n    {\n        if (NtWaitForSingleObject(hInput, TRUE, &(LARGE_INTEGER) { .QuadPart = (int64_t) FF_IO_TERM_RESP_WAIT_MS * -10000 }) != STATUS_WAIT_0)\n        {\n            SetConsoleMode(hInput, inputMode);\n            return \"NtWaitForSingleObject() failed or timeout\";\n        }\n\n        // Ignore all unexpected input events\n        INPUT_RECORD record;\n        DWORD len = 0;\n        if (!PeekConsoleInputW(hInput, &record, 1, &len))\n            break;\n\n        if (\n            record.EventType == KEY_EVENT &&\n            record.Event.KeyEvent.uChar.UnicodeChar != L'\\r' &&\n            record.Event.KeyEvent.uChar.UnicodeChar != L'\\n'\n        )\n            break;\n        else\n            ReadConsoleInputW(hInput, &record, 1, &len);\n    }\n\n    va_list args;\n    va_start(args, format);\n\n    char buffer[1024];\n    uint32_t bytesRead = 0;\n\n    while (true)\n    {\n        DWORD bytes = 0;\n        if (!ReadFile(hInput, buffer, sizeof(buffer) - 1, &bytes, NULL) || bytes == 0)\n        {\n            va_end(args);\n            return \"ReadFile() failed\";\n        }\n\n        bytesRead += bytes;\n        buffer[bytesRead] = '\\0';\n\n        va_list cargs;\n        va_copy(cargs, args);\n        int ret = vsscanf(buffer, format, cargs);\n        va_end(cargs);\n\n        if (ret <= 0)\n        {\n            va_end(args);\n            return \"vsscanf(buffer, format, args) failed\";\n        }\n        if (ret >= nParams)\n            break;\n    }\n\n    SetConsoleMode(hInput, inputMode);\n\n    va_end(args);\n\n    return NULL;\n}\n\nFFNativeFD ffGetNullFD(void)\n{\n    static FFNativeFD hNullFile = INVALID_HANDLE_VALUE;\n    if (hNullFile != INVALID_HANDLE_VALUE)\n        return hNullFile;\n    hNullFile = CreateFileW(\n        L\"NUL\",\n        GENERIC_READ | GENERIC_WRITE,\n        FILE_SHARE_WRITE,\n        0,\n        OPEN_EXISTING,\n        0,\n        &(SECURITY_ATTRIBUTES){\n            .nLength = sizeof(SECURITY_ATTRIBUTES),\n            .lpSecurityDescriptor = NULL,\n            .bInheritHandle = TRUE,\n        });\n    return hNullFile;\n}\n\nbool ffRemoveFile(const char* fileName)\n{\n    return DeleteFileA(fileName) != FALSE;\n}\n"
  },
  {
    "path": "src/common/impl/jsonconfig.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/color.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/printing.h\"\n#include \"common/io.h\"\n#include \"common/time.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/version/version.h\"\n#include \"modules/modules.h\"\n\n#include <assert.h>\n#include <ctype.h>\n#include <inttypes.h>\n\nbool ffJsonConfigParseModuleArgs(yyjson_val* key, yyjson_val* val, FFModuleArgs* moduleArgs)\n{\n    if (unsafe_yyjson_equals_str(key, \"type\") || unsafe_yyjson_equals_str(key, \"condition\"))\n        return true;\n\n    if (unsafe_yyjson_equals_str(key, \"key\"))\n    {\n        ffStrbufSetJsonVal(&moduleArgs->key, val);\n        return true;\n    }\n    else if (unsafe_yyjson_equals_str(key, \"format\"))\n    {\n        ffStrbufSetJsonVal(&moduleArgs->outputFormat, val);\n        return true;\n    }\n    else if (unsafe_yyjson_equals_str(key, \"outputColor\"))\n    {\n        ffOptionParseColor(yyjson_get_str(val), &moduleArgs->outputColor);\n        return true;\n    }\n    else if (unsafe_yyjson_equals_str(key, \"keyColor\"))\n    {\n        ffOptionParseColor(yyjson_get_str(val), &moduleArgs->keyColor);\n        return true;\n    }\n    else if (unsafe_yyjson_equals_str(key, \"keyWidth\"))\n    {\n        moduleArgs->keyWidth = (uint32_t) yyjson_get_uint(val);\n        return true;\n    }\n    else if (unsafe_yyjson_equals_str(key, \"keyIcon\"))\n    {\n        ffStrbufSetJsonVal(&moduleArgs->keyIcon, val);\n        return true;\n    }\n    return false;\n}\n\nvoid ffJsonConfigGenerateModuleArgsConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, FFModuleArgs* moduleArgs)\n{\n    if (moduleArgs->key.length > 0)\n        yyjson_mut_obj_add_strbuf(doc, module, \"key\", &moduleArgs->key);\n    if (moduleArgs->outputFormat.length > 0)\n        yyjson_mut_obj_add_strbuf(doc, module, \"format\", &moduleArgs->outputFormat);\n    if (moduleArgs->outputColor.length > 0)\n        yyjson_mut_obj_add_strbuf(doc, module, \"outputColor\", &moduleArgs->outputColor);\n    if (moduleArgs->keyColor.length > 0)\n        yyjson_mut_obj_add_strbuf(doc, module, \"keyColor\", &moduleArgs->keyColor);\n    if (moduleArgs->keyWidth > 0)\n        yyjson_mut_obj_add_uint(doc, module, \"keyWidth\", moduleArgs->keyWidth);\n    if (moduleArgs->keyIcon.length > 0)\n        yyjson_mut_obj_add_strbuf(doc, module, \"keyIcon\", &moduleArgs->keyIcon);\n}\n\nconst char* ffJsonConfigParseEnum(yyjson_val* val, int* result, FFKeyValuePair pairs[])\n{\n    if (yyjson_is_int(val))\n    {\n        int intVal = yyjson_get_int(val);\n\n        for (const FFKeyValuePair* pPair = pairs; pPair->key; ++pPair)\n        {\n            if (intVal == pPair->value)\n            {\n                *result = pPair->value;\n                return NULL;\n            }\n        }\n\n        return \"Invalid enum integer\";\n    }\n    else if (yyjson_is_str(val))\n    {\n        const char* strVal = yyjson_get_str(val);\n        for (const FFKeyValuePair* pPair = pairs; pPair->key; ++pPair)\n        {\n            if (ffStrEqualsIgnCase(strVal, pPair->key))\n            {\n                *result = pPair->value;\n                return NULL;\n            }\n        }\n\n        return \"Invalid enum string\";\n    }\n    else\n        return \"Invalid enum value type; must be a string or integer\";\n}\n\nstatic bool parseModuleJsonObject(const char* type, yyjson_val* jsonVal, yyjson_mut_doc* jsonDoc)\n{\n    if(!ffCharIsEnglishAlphabet(type[0])) return false;\n\n    for (FFModuleBaseInfo** modules = ffModuleInfos[toupper(type[0]) - 'A']; *modules; ++modules)\n    {\n        FFModuleBaseInfo* baseInfo = *modules;\n        if (ffStrEqualsIgnCase(type, baseInfo->name))\n        {\n            uint8_t optionBuf[FF_OPTION_MAX_SIZE];\n            baseInfo->initOptions(optionBuf);\n            if (jsonVal) baseInfo->parseJsonObject(optionBuf, jsonVal);\n            bool succeeded;\n            if (jsonDoc)\n            {\n                yyjson_mut_val* module = yyjson_mut_arr_add_obj(jsonDoc, jsonDoc->root);\n                yyjson_mut_obj_add_str(jsonDoc, module, \"type\", baseInfo->name);\n                if (baseInfo->generateJsonResult)\n                    succeeded = baseInfo->generateJsonResult(optionBuf, jsonDoc, module);\n                else\n                {\n                    yyjson_mut_obj_add_str(jsonDoc, module, \"error\", \"Unsupported for JSON format\");\n                    succeeded = false;\n                }\n            }\n            else\n                succeeded = baseInfo->printModule(optionBuf);\n            baseInfo->destroyOptions(optionBuf);\n            return succeeded;\n        }\n    }\n\n    if (jsonDoc)\n    {\n        yyjson_mut_val* module = yyjson_mut_arr_add_obj(jsonDoc, jsonDoc->root);\n        yyjson_mut_obj_add_strcpy(jsonDoc, module, \"type\", type);\n        yyjson_mut_obj_add_str(jsonDoc, module, \"error\", \"Unknown module type\");\n    }\n    else\n    {\n        FFModuleArgs moduleArgs;\n        ffOptionInitModuleArg(&moduleArgs, \"\");\n        ffPrintError(type, 0, &moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown module type\");\n        ffOptionDestroyModuleArg(&moduleArgs);\n    }\n    return false;\n}\n\nstatic void prepareModuleJsonObject(const char* type, yyjson_val* module)\n{\n    switch (type[0])\n    {\n        case 'b': case 'B': {\n            if (ffStrEqualsIgnCase(type, FF_CPUUSAGE_MODULE_NAME))\n                ffPrepareCPUUsage();\n            break;\n        }\n        case 'c': case 'C': {\n            if (ffStrEqualsIgnCase(type, FF_COMMAND_MODULE_NAME))\n            {\n                __attribute__((__cleanup__(ffDestroyCommandOptions))) FFCommandOptions options;\n                ffInitCommandOptions(&options);\n                if (module) ffCommandModuleInfo.parseJsonObject(&options, module);\n                ffPrepareCommand(&options);\n            }\n            break;\n        }\n        case 'd': case 'D': {\n            if (ffStrEqualsIgnCase(type, FF_DISKIO_MODULE_NAME))\n            {\n                __attribute__((__cleanup__(ffDestroyDiskIOOptions))) FFDiskIOOptions options;\n                ffInitDiskIOOptions(&options);\n                if (module) ffDiskIOModuleInfo.parseJsonObject(&options, module);\n                ffPrepareDiskIO(&options);\n            }\n            break;\n        }\n        case 'n': case 'N': {\n            if (ffStrEqualsIgnCase(type, FF_NETIO_MODULE_NAME))\n            {\n                __attribute__((__cleanup__(ffDestroyNetIOOptions))) FFNetIOOptions options;\n                ffInitNetIOOptions(&options);\n                if (module) ffNetIOModuleInfo.parseJsonObject(&options, module);\n                ffPrepareNetIO(&options);\n            }\n            break;\n        }\n        case 'p': case 'P': {\n            if (ffStrEqualsIgnCase(type, FF_PUBLICIP_MODULE_NAME))\n            {\n                __attribute__((__cleanup__(ffDestroyPublicIpOptions))) FFPublicIPOptions options;\n                ffInitPublicIpOptions(&options);\n                if (module) ffPublicIPModuleInfo.parseJsonObject(&options, module);\n                ffPreparePublicIp(&options);\n            }\n            break;\n        }\n        case 'w': case 'W': {\n            if (ffStrEqualsIgnCase(type, FF_WEATHER_MODULE_NAME))\n            {\n                __attribute__((__cleanup__(ffDestroyWeatherOptions))) FFWeatherOptions options;\n                ffInitWeatherOptions(&options);\n                if (module) ffWeatherModuleInfo.parseJsonObject(&options, module);\n                ffPrepareWeather(&options);\n            }\n            break;\n        }\n    }\n}\n\nstatic bool matchesJsonArray(const char* str, yyjson_val* val)\n{\n    assert(val);\n\n    if (unsafe_yyjson_is_str(val))\n        return ffStrEqualsIgnCase(str, unsafe_yyjson_get_str(val));\n\n    if (!unsafe_yyjson_is_arr(val)) return false;\n\n    size_t idx, max;\n    yyjson_val* item;\n    yyjson_arr_foreach(val, idx, max, item)\n    {\n        if (yyjson_is_str(item) && ffStrEqualsIgnCase(str, unsafe_yyjson_get_str(item)))\n            return true;\n    }\n    return false;\n}\n\nstatic const char* printJsonConfig(FFdata* data, bool prepare)\n{\n    yyjson_mut_doc* jsonDoc = data->resultDoc;\n    yyjson_val* const root = yyjson_doc_get_root(data->configDoc);\n    assert(root);\n\n    if (!yyjson_is_obj(root))\n        return \"Invalid JSON config format. Root value must be an object\";\n\n    yyjson_val* modules = yyjson_obj_get(root, \"modules\");\n    if (!modules) return NULL;\n    if (!yyjson_is_arr(modules)) return \"Property 'modules' must be an array of strings or objects\";\n\n    bool succeeded = true;\n    int32_t thres = instance.config.display.stat;\n    yyjson_val* item;\n    size_t idx, max;\n    yyjson_arr_foreach(modules, idx, max, item)\n    {\n        double ms = 0;\n        if(!prepare && thres >= 0)\n            ms = ffTimeGetTick();\n\n        yyjson_val* module = item;\n        const char* type = yyjson_get_str(module);\n        if (type)\n            module = NULL;\n        else if (yyjson_is_obj(module))\n        {\n            yyjson_val* conditions = yyjson_obj_get(module, \"condition\");\n            if (conditions)\n            {\n                if (!yyjson_is_obj(conditions))\n                    return \"Property 'conditions' must be an object\";\n\n                yyjson_val* system = yyjson_obj_get(conditions, \"system\");\n                if (system && !matchesJsonArray(ffVersionResult.sysName, system))\n                    continue;\n\n                system = yyjson_obj_get(conditions, \"!system\");\n                if (system && matchesJsonArray(ffVersionResult.sysName, system))\n                    continue;\n\n                yyjson_val* arch = yyjson_obj_get(conditions, \"arch\");\n                if (arch && !matchesJsonArray(ffVersionResult.architecture, arch))\n                    continue;\n\n                arch = yyjson_obj_get(conditions, \"!arch\");\n                if (arch && matchesJsonArray(ffVersionResult.architecture, arch))\n                    continue;\n\n                yyjson_val* previousSucceeded = yyjson_obj_get(conditions, \"succeeded\");\n                if (previousSucceeded && !unsafe_yyjson_is_null(previousSucceeded))\n                {\n                    if (!unsafe_yyjson_is_bool(previousSucceeded))\n                        return \"Property 'succeeded' in 'condition' must be a boolean\";\n                    if (succeeded != unsafe_yyjson_get_bool(previousSucceeded))\n                        continue;\n                }\n            }\n\n            type = yyjson_get_str(yyjson_obj_get(module, \"type\"));\n            if (!type) return \"module object must contain a \\\"type\\\" key ( case sensitive )\";\n            if (yyjson_obj_size(module) == 1) // contains only Property type\n                module = NULL;\n        }\n        else\n            return \"modules must be an array of strings or objects\";\n\n        if (ffStrbufSeparatedContainIgnCaseS(&data->structureDisabled, type, ':'))\n            continue;\n\n        if(prepare)\n            prepareModuleJsonObject(type, module);\n        else\n            succeeded = parseModuleJsonObject(type, module, jsonDoc);\n\n        if(!prepare && thres >= 0)\n        {\n            ms = ffTimeGetTick() - ms;\n            if (jsonDoc)\n            {\n                yyjson_mut_val* moduleJson = yyjson_mut_arr_get_last(jsonDoc->root);\n                yyjson_mut_obj_add_real(jsonDoc, moduleJson, \"stat\", ms);\n            }\n            else\n            {\n                char str[64];\n                int len = snprintf(str, sizeof str, \"%.3fms\", ms);\n                if (thres > 0)\n                    snprintf(str, sizeof str, \"\\e[%sm%.3fms\\e[m\", (ms <= thres ? FF_COLOR_FG_GREEN : ms <= 2 * thres ? FF_COLOR_FG_YELLOW : FF_COLOR_FG_RED), ms);\n                printf(\"\\e7\\e[1A\\e[9999999C\\e[%dD%s\\e8\", len, str); // Save; Up 1; Right 9999999; Left <len>; Print <str>; Load\n            }\n        }\n\n        #if defined(_WIN32)\n        if (!instance.config.display.noBuffer && !jsonDoc) fflush(stdout);\n        #endif\n    }\n\n    return NULL;\n}\n\nvoid ffPrintJsonConfig(FFdata* data, bool prepare)\n{\n    yyjson_mut_doc* jsonDoc = data->resultDoc;\n    const char* error = printJsonConfig(data, prepare);\n    if (error)\n    {\n        if (jsonDoc)\n        {\n            yyjson_mut_val* obj = yyjson_mut_obj(jsonDoc);\n            yyjson_mut_obj_add_str(jsonDoc, obj, \"error\", error);\n            yyjson_mut_doc_set_root(jsonDoc, obj);\n        }\n        else\n            ffPrintError(\"JsonConfig\", 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, \"%s\", error);\n    }\n}\n"
  },
  {
    "path": "src/common/impl/kmod_apple.c",
    "content": "#include \"common/kmod.h\"\n#include \"common/apple/cf_helpers.h\"\n#include <IOKit/kext/KextManager.h>\n#include <CoreFoundation/CoreFoundation.h>\n\nbool ffKmodLoaded(const char* modName)\n{\n    FF_CFTYPE_AUTO_RELEASE CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, modName, kCFStringEncodingUTF8);\n    FF_CFTYPE_AUTO_RELEASE CFArrayRef identifiers = CFArrayCreate(kCFAllocatorDefault, (const void**) &name, 1, &kCFTypeArrayCallBacks);\n    FF_CFTYPE_AUTO_RELEASE CFArrayRef keys = CFArrayCreate(kCFAllocatorDefault, NULL, 0, NULL);\n    FF_CFTYPE_AUTO_RELEASE CFDictionaryRef kextInfo = KextManagerCopyLoadedKextInfo(identifiers, keys);\n    return CFDictionaryContainsKey(kextInfo, name);\n}\n"
  },
  {
    "path": "src/common/impl/kmod_bsd.c",
    "content": "#include \"common/kmod.h\"\n#include <sys/param.h>\n#include <sys/module.h>\n\nbool ffKmodLoaded(const char* modName)\n{\n    return modfind(modName) >= 0;\n}\n"
  },
  {
    "path": "src/common/impl/kmod_linux.c",
    "content": "#include \"common/kmod.h\"\n#include \"common/io.h\"\n\nbool ffKmodLoaded(const char* modName)\n{\n    static FFstrbuf modules;\n    if (modules.chars == NULL)\n    {\n        ffStrbufInitS(&modules, \"\\n\");\n        ffAppendFileBuffer(\"/proc/modules\", &modules);\n    }\n\n    if (modules.length == 0) return false;\n\n    uint32_t len = (uint32_t) strlen(modName);\n    if (len > 250) return false;\n\n    char temp[256];\n    temp[0] = '\\n';\n    memcpy(temp + 1, modName, len);\n    temp[1 + len] = ' ';\n    return memmem(modules.chars, modules.length, temp, len + 2) != NULL;\n}\n"
  },
  {
    "path": "src/common/impl/kmod_nbsd.c",
    "content": "#include \"common/kmod.h\"\n#include \"common/stringUtils.h\"\n\n#include <sys/module.h>\n#include <sys/param.h>\n\ntypedef struct __attribute__((__packed__)) FFNbsdModList\n{\n    int len;\n    modstat_t mods[];\n} FFNbsdModList;\n\nbool ffKmodLoaded(const char* modName)\n{\n    static FFNbsdModList* list = NULL;\n\n    if (list == NULL)\n    {\n        struct iovec iov = {};\n\n        for (size_t len = 8192;; len = iov.iov_len)\n        {\n            iov.iov_len = len;\n            iov.iov_base = realloc(iov.iov_base, len);\n            if (modctl(MODCTL_STAT, &iov) < 0)\n            {\n                free(iov.iov_base);\n                return true; // ignore errors\n            }\n\n            if (len >= iov.iov_len) break;\n        }\n        list = (FFNbsdModList*) iov.iov_base;\n    }\n\n    for (int i = 0; i < list->len; i++)\n    {\n        if (ffStrEquals(list->mods[i].ms_name, modName))\n            return true;\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "src/common/impl/kmod_nosupport.c",
    "content": "#include \"common/kmod.h\"\n\nbool ffKmodLoaded(FF_MAYBE_UNUSED const char* modName)\n{\n    return true; // Don't generate kernel module related errors\n}\n"
  },
  {
    "path": "src/common/impl/kmod_sunos.c",
    "content": "#include \"common/kmod.h\"\n#include \"common/stringUtils.h\"\n\n#include <sys/modctl.h>\n#include <errno.h>\n\nbool ffKmodLoaded(const char* modName)\n{\n    struct modinfo modinfo = {\n        .mi_id = -1,\n        .mi_nextid = -1,\n        .mi_info = MI_INFO_ALL,\n    };\n\n    for (int id = -1; modctl(MODINFO, id, &modinfo) == 0; id = modinfo.mi_id)\n    {\n        modinfo.mi_name[MODMAXNAMELEN - 1] = '\\0';\n\n        if (ffStrEquals(modinfo.mi_name, modName))\n            return true;\n    }\n\n    return !(errno == EINVAL || errno == ENOENT);\n}\n"
  },
  {
    "path": "src/common/impl/kmod_windows.c",
    "content": "#include \"common/kmod.h\"\n#include \"common/windows/nt.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/stringUtils.h\"\n\nbool ffKmodLoaded(const char* modName)\n{\n    ULONG bufferSize = 0;\n    NtQuerySystemInformation(SystemModuleInformation, NULL, 0, &bufferSize);\n    if (bufferSize == 0)\n        return true; // ignore errors\n\n    FF_AUTO_FREE RTL_PROCESS_MODULES* buffer = malloc(bufferSize);\n\n    if (!NT_SUCCESS(NtQuerySystemInformation(SystemModuleInformation, buffer, bufferSize, &bufferSize)))\n        return true; // ignore errors\n\n    for (ULONG i = 0; i < buffer->NumberOfModules; i++)\n    {\n        const char* name = (const char*) buffer->Modules[i].FullPathName + buffer->Modules[i].OffsetToFileName;\n\n        if (ffStrEqualsIgnCase(name, modName))\n            return true;\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "src/common/impl/library.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/library.h\"\n\n#if _WIN32\n#include \"common/debug.h\"\n#include \"common/windows/nt.h\"\n#include <errno.h>\n#include <ntstatus.h>\n#endif\n\n#ifndef FF_DISABLE_DLOPEN\n\n#include <stdarg.h>\n\n//Clang doesn't define __SANITIZE_ADDRESS__ but defines __has_feature(address_sanitizer)\n#if !defined(__SANITIZE_ADDRESS__) && defined(__has_feature)\n    #if __has_feature(address_sanitizer)\n        #define __SANITIZE_ADDRESS__\n    #endif\n#endif\n\n#ifndef FF_DLOPEN_FLAGS\n    #ifdef __SANITIZE_ADDRESS__\n        #define FF_DLOPEN_FLAGS RTLD_LAZY | RTLD_NODELETE\n    #else\n        #define FF_DLOPEN_FLAGS RTLD_LAZY\n    #endif\n#endif\n\nstatic void* libraryLoad(const char* path, int maxVersion)\n{\n    void* result = dlopen(path, FF_DLOPEN_FLAGS);\n\n    #ifdef _WIN32\n\n    // libX.dll.1 never exists on Windows, while libX-1.dll may exist\n    FF_UNUSED(maxVersion)\n\n    if(result != NULL)\n        return result;\n\n    uint32_t pathLen = ffStrbufLastIndexC(&instance.state.platform.exePath, '/');\n    if (pathLen == instance.state.platform.exePath.length)\n        return result;\n\n    char absPath[MAX_PATH * 2];\n    strcpy(mempcpy(absPath, instance.state.platform.exePath.chars, pathLen + 1), path);\n    return dlopen(absPath, FF_DLOPEN_FLAGS);\n\n    #else\n\n    if(result != NULL || maxVersion < 0)\n        return result;\n\n    FF_STRBUF_AUTO_DESTROY pathbuf = ffStrbufCreateA(64);\n    ffStrbufAppendS(&pathbuf, path);\n    ffStrbufAppendC(&pathbuf, '.');\n\n    for(int i = maxVersion; i >= 0; --i)\n    {\n        uint32_t originalLength = pathbuf.length;\n        ffStrbufAppendSInt(&pathbuf, i);\n\n        result = dlopen(pathbuf.chars, FF_DLOPEN_FLAGS);\n        if(result != NULL)\n            break;\n\n        ffStrbufSubstrBefore(&pathbuf, originalLength);\n    }\n\n    #endif\n\n    return result;\n}\n\nvoid* ffLibraryLoad(const char* path, int maxVersion, ...)\n{\n    void* result = libraryLoad(path, maxVersion);\n\n    if (!result)\n    {\n        va_list defaultNames;\n        va_start(defaultNames, maxVersion);\n\n        do\n        {\n            const char* pathRest = va_arg(defaultNames, const char*);\n            if(pathRest == NULL)\n                break;\n\n            int maxVersionRest = va_arg(defaultNames, int);\n            result = libraryLoad(pathRest, maxVersionRest);\n        } while (!result);\n\n        va_end(defaultNames);\n    }\n\n    return result;\n}\n\n#endif\n\n#if _WIN32\n\nvoid* dlopen(const char* path, FF_MAYBE_UNUSED int mode)\n{\n    wchar_t pathW[MAX_PATH + 1];\n    ULONG pathWBytes = 0;\n\n    NTSTATUS status = RtlUTF8ToUnicodeN(pathW, sizeof(pathW), &pathWBytes, path, (uint32_t)strlen(path) + 1);\n    if (!NT_SUCCESS(status))\n    {\n        FF_DEBUG(\"RtlUTF8ToUnicodeN failed for path %s with status 0x%08lX: %s\", path, status, ffDebugNtStatus(status));\n        return NULL;\n    }\n\n    PVOID module = NULL;\n    status = LdrLoadDll(NULL, NULL, &(UNICODE_STRING) {\n        .Length = (USHORT) pathWBytes - sizeof(wchar_t), // Exclude null terminator\n        .MaximumLength = (USHORT) pathWBytes,\n        .Buffer = pathW,\n    }, &module);\n\n    if (!NT_SUCCESS(status))\n    {\n        FF_DEBUG(\"LdrLoadDll failed for path %s with status 0x%08lX: %s\", path, status, ffDebugNtStatus(status));\n        return NULL;\n    }\n\n    return module;\n}\n\nint dlclose(void* handle)\n{\n    NTSTATUS status = LdrUnloadDll(handle);\n    if (!NT_SUCCESS(status))\n    {\n        FF_DEBUG(\"LdrUnloadDll failed for handle %p with status 0x%08lX: %s\", handle, status, ffDebugNtStatus(status));\n        return -1;\n    }\n    return 0;\n}\n\nvoid* dlsym(void* handle, const char* symbol)\n{\n    void* address;\n    USHORT symbolBytes = (USHORT) strlen(symbol) + 1;\n    NTSTATUS status = LdrGetProcedureAddress(handle, &(ANSI_STRING) {\n        .Length = symbolBytes - sizeof(char),\n        .MaximumLength = symbolBytes,\n        .Buffer = (char*) symbol,\n    }, 0, &address);\n    if (!NT_SUCCESS(status))\n    {\n        FF_DEBUG(\"LdrGetProcedureAddress failed for symbol %s with status 0x%08lX: %s\", symbol, status, ffDebugNtStatus(status));\n        return NULL;\n    }\n    return address;\n}\n\nvoid* ffLibraryGetModule(const wchar_t* libraryFileName)\n{\n    assert(libraryFileName != NULL && \"Use \\\"ffGetPeb()->ImageBaseAddress\\\" instead\");\n\n    void* module = NULL;\n    USHORT libraryFileNameBytes = (USHORT) (wcslen(libraryFileName) * sizeof(wchar_t)) + sizeof(wchar_t);\n    NTSTATUS status = LdrGetDllHandle(NULL, NULL, &(UNICODE_STRING) {\n        .Length = libraryFileNameBytes - sizeof(wchar_t),\n        .MaximumLength = libraryFileNameBytes,\n        .Buffer = (wchar_t*) libraryFileName,\n    }, &module);\n    if (!NT_SUCCESS(status))\n    {\n        FF_DEBUG(\"LdrGetDllHandle failed for library %ls with status 0x%08lX: %s\", libraryFileName, status, ffDebugNtStatus(status));\n        return NULL;\n    }\n    return module;\n}\n#endif\n"
  },
  {
    "path": "src/common/impl/memrchr.c",
    "content": "#include \"common/memrchr.h\"\n#include <stddef.h>\n#include <stdint.h>\n\nvoid* memrchr(const void* s, int c, size_t n)\n{\n    if (n == 0) return NULL;\n\n    const uint8_t uc = (uint8_t) c;\n\n    const uint8_t* p = (const uint8_t*) s + n;\n\n    while (n--)\n    {\n        if (*--p == uc) return (void*) p;\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/common/impl/netif.c",
    "content": "#include \"common/netif.h\"\n\n#ifndef _WIN32\n    #include <net/if.h>\n    #include <netinet/in.h>\n#endif\n\nconst FFNetifDefaultRouteResult* ffNetifGetDefaultRouteV4(void)\n{\n    static FFNetifDefaultRouteResult result;\n    if (result.status == FF_NETIF_UNINITIALIZED) {\n        result.status = ffNetifGetDefaultRouteImplV4(&result) ? FF_NETIF_OK : FF_NETIF_INVALID;\n    }\n    return &result;\n}\n\nconst FFNetifDefaultRouteResult* ffNetifGetDefaultRouteV6(void)\n{\n    static FFNetifDefaultRouteResult result;\n    if (result.status == FF_NETIF_UNINITIALIZED) {\n        result.status = ffNetifGetDefaultRouteImplV6(&result) ? FF_NETIF_OK : FF_NETIF_INVALID;\n    }\n    return &result;\n}\n"
  },
  {
    "path": "src/common/impl/netif_apple.c",
    "content": "#include \"common/netif.h\"\n#include \"common/io.h\"\n#include \"common/mallocHelper.h\"\n\n#include <net/if.h>\n#include <net/if_dl.h>\n#include <net/route.h>\n#include <netinet/in.h>\n#include <sys/socket.h>\n\n#define ROUNDUP2(a, n)       ((a) > 0 ? (1 + (((a) - 1U) | ((n) - 1))) : (n))\n\n#if __APPLE__\n    // https://github.com/apple-oss-distributions/network_cmds/blob/8f38231438e6a4d16ef8015e97e12c2c05105644/rtsol.tproj/if.c#L243\n    #define ROUNDUP(a)           ROUNDUP2((a), sizeof(uint32_t))\n#elif __sun\n    // https://github.com/illumos/illumos-gate/blob/95b8c88950fa7b19af46bc63230137cf96b0bff7/usr/src/cmd/cmd-inet/usr.sbin/route.c#L339\n    #define ROUNDUP(a)           ROUNDUP2((a), sizeof(long))\n#else\n    #error unknown platform\n#endif\n\nstatic struct sockaddr *\nget_rt_address(struct rt_msghdr *rtm, int desired)\n{\n    struct sockaddr *sa = (struct sockaddr *)(rtm + 1);\n\n    for (int i = 0; i < RTAX_MAX; i++)\n    {\n        if (rtm->rtm_addrs & (1 << i))\n        {\n            if ((1 << i) == desired)\n                return sa;\n\n            #ifndef __sun\n            uint32_t salen = sa->sa_len;\n            #else\n            uint32_t salen;\n            // https://github.com/illumos/illumos-gate/blob/95b8c88950fa7b19af46bc63230137cf96b0bff7/usr/src/cmd/cmd-inet/usr.sbin/route.c#L2941\n            switch (sa->sa_family) {\n            case AF_INET:\n                salen = sizeof (struct sockaddr_in);\n                break;\n            case AF_LINK:\n                salen = sizeof (struct sockaddr_dl);\n                break;\n            case AF_INET6:\n                salen = sizeof (struct sockaddr_in6);\n                break;\n            default:\n                salen = sizeof (struct sockaddr);\n                break;\n            }\n            #endif\n            sa = (struct sockaddr *)(ROUNDUP(salen) + (char *)sa);\n        }\n    }\n    return NULL;\n}\n\nbool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result)\n{\n    //https://github.com/hashPirate/copenheimer-masscan-fork/blob/36f1ed9f7b751a7dccd5ed27874e2e703db7d481/src/rawsock-getif.c#L104\n\n    FF_AUTO_CLOSE_FD int pfRoute = socket(PF_ROUTE, SOCK_RAW, AF_INET);\n    if (pfRoute < 0)\n        return false;\n\n    {\n        struct timeval timeout = {1, 0};\n        setsockopt(pfRoute, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));\n        setsockopt(pfRoute, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout));\n    }\n\n    uint32_t pid = instance.state.platform.pid;\n\n    struct {\n        struct rt_msghdr hdr;\n        struct sockaddr_in dst;\n        uint8_t data[512];\n    } rtmsg = {\n        .hdr = {\n            .rtm_type = RTM_GET,\n            .rtm_flags = RTF_UP | RTF_GATEWAY,\n            .rtm_version = RTM_VERSION,\n            .rtm_addrs = RTA_DST | RTA_IFP | RTA_IFA,\n            .rtm_msglen = sizeof(rtmsg.hdr) + sizeof(rtmsg.dst),\n            .rtm_pid = (pid_t) pid,\n            .rtm_seq = 1,\n        },\n        .dst = {\n            .sin_family = AF_INET,\n            #ifndef __sun\n            .sin_len = sizeof(rtmsg.dst),\n            #endif\n        },\n    };\n\n    if (send(pfRoute, &rtmsg, rtmsg.hdr.rtm_msglen, 0) != rtmsg.hdr.rtm_msglen)\n        return false;\n\n    while (recv(pfRoute, &rtmsg, sizeof(rtmsg), 0) > 0 && !(rtmsg.hdr.rtm_seq == 1 && rtmsg.hdr.rtm_pid == (pid_t) pid))\n        ;\n\n    #ifndef __sun // On Solaris, the RTF_GATEWAY flag is not set for default routes for some reason\n    if ((rtmsg.hdr.rtm_flags & (RTF_UP | RTF_GATEWAY)) == (RTF_UP | RTF_GATEWAY))\n    #endif\n    {\n        struct sockaddr_dl* sdl = (struct sockaddr_dl *)get_rt_address(&rtmsg.hdr, RTA_IFP);\n        if (sdl\n            #ifndef __sun\n            && sdl->sdl_len\n            #endif\n        )\n        {\n            assert(sdl->sdl_nlen <= IF_NAMESIZE);\n            memcpy(result->ifName, sdl->sdl_data, sdl->sdl_nlen);\n            result->ifName[sdl->sdl_nlen] = '\\0';\n            result->ifIndex = sdl->sdl_index;\n\n            // Get the preferred source address\n            struct sockaddr_in* src = (struct sockaddr_in*)get_rt_address(&rtmsg.hdr, RTA_IFA);\n            if (src && src->sin_family == AF_INET)\n                result->preferredSourceAddrV4 = src->sin_addr.s_addr;\n\n            return true;\n        }\n        return false;\n    }\n\n    return false;\n}\n\nbool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result)\n{\n    //https://github.com/hashPirate/copenheimer-masscan-fork/blob/36f1ed9f7b751a7dccd5ed27874e2e703db7d481/src/rawsock-getif.c#L104\n\n    FF_AUTO_CLOSE_FD int pfRoute = socket(PF_ROUTE, SOCK_RAW, AF_INET6);\n    if (pfRoute < 0)\n        return false;\n\n    {\n        struct timeval timeout = {1, 0};\n        setsockopt(pfRoute, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));\n        setsockopt(pfRoute, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout));\n    }\n\n    uint32_t pid = instance.state.platform.pid;\n\n    struct {\n        struct rt_msghdr hdr;\n        struct sockaddr_in6 dst;\n        uint8_t data[512];\n    } rtmsg = {\n        .hdr = {\n            .rtm_type = RTM_GET,\n            .rtm_flags = RTF_UP | RTF_GATEWAY,\n            .rtm_version = RTM_VERSION,\n            .rtm_addrs = RTA_DST | RTA_IFP,\n            .rtm_msglen = sizeof(rtmsg.hdr) + sizeof(rtmsg.dst),\n            .rtm_pid = (pid_t) pid,\n            .rtm_seq = 2,\n        },\n        .dst = {\n            .sin6_family = AF_INET6,\n            #ifndef __sun\n            .sin6_len = sizeof(rtmsg.dst),\n            #endif\n        },\n    };\n\n    if (send(pfRoute, &rtmsg, rtmsg.hdr.rtm_msglen, 0) != rtmsg.hdr.rtm_msglen)\n        return false;\n\n    while (recv(pfRoute, &rtmsg, sizeof(rtmsg), 0) > 0 && !(rtmsg.hdr.rtm_seq == 2 && rtmsg.hdr.rtm_pid == (pid_t) pid))\n        ;\n\n    #ifndef __sun // On Solaris, the RTF_GATEWAY flag is not set for default routes for some reason\n    if ((rtmsg.hdr.rtm_flags & (RTF_UP | RTF_GATEWAY)) == (RTF_UP | RTF_GATEWAY))\n    #endif\n    {\n        struct sockaddr_dl* sdl = (struct sockaddr_dl *)get_rt_address(&rtmsg.hdr, RTA_IFP);\n        if (sdl\n            #ifndef __sun\n            && sdl->sdl_len\n            #endif\n        )\n        {\n            assert(sdl->sdl_nlen <= IF_NAMESIZE);\n            memcpy(result->ifName, sdl->sdl_data, sdl->sdl_nlen);\n            result->ifName[sdl->sdl_nlen] = '\\0';\n            result->ifIndex = sdl->sdl_index;\n\n            return true;\n        }\n        return false;\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "src/common/impl/netif_bsd.c",
    "content": "#include \"common/netif.h\"\n#include \"common/io.h\"\n#include \"common/mallocHelper.h\"\n\n#include <net/if.h>\n#include <net/if_dl.h>\n#include <net/route.h>\n#include <netinet/in.h>\n#include <sys/socket.h>\n#include <sys/sysctl.h>\n\n#define ROUNDUP2(a, n)       ((a) > 0 ? (1 + (((a) - 1U) | ((n) - 1))) : (n))\n\n#if __DragonFly__\n    // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/cf0aa2f1e47a3f0a6055fe427563cb3f3e627064/sys/net/route.h#L315C9-L315C19\n    #define ROUNDUP(a)           ROUNDUP2((a), sizeof(long))\n#elif __FreeBSD__\n    // https://github.com/freebsd/freebsd-src/blob/e4c0ecba44b20ebb2e4d80978c2cb6d16b730cb9/sys/net/route.h#L368C9-L368C16\n    #define ROUNDUP(a)           ROUNDUP2((a), sizeof(long))\n#elif __NetBSD__\n    // https://github.com/NetBSD/src/blob/29beb637d057520c0ed37ac2cde966f7cc0cadf4/sys/net/route.h#L330\n    #define ROUNDUP(a)           ROUNDUP2((a), sizeof(uint64_t))\n#elif __OpenBSD__\n    // https://github.com/openbsd/src/blob/ca647cfa4ec3ccb8360714bc0ebc32a394f7fb6a/regress/sys/netinet/bindconnect/bindconnect.c#L250\n    #define ROUNDUP(a)           ROUNDUP2((a), sizeof(long))\n#else\n    #error unknown platform\n#endif\n\nstatic struct sockaddr *\nget_rt_address(struct rt_msghdr *rtm, int desired)\n{\n    struct sockaddr *sa = (struct sockaddr *)(rtm + 1);\n\n    for (int i = 0; i < RTAX_MAX; i++)\n    {\n        if (rtm->rtm_addrs & (1 << i))\n        {\n            if ((1 << i) == desired)\n                return sa;\n            sa = (struct sockaddr *)(ROUNDUP(sa->sa_len) + (char *)sa);\n        }\n    }\n    return NULL;\n}\n\nbool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result)\n{\n    int mib[6] = {CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_FLAGS, RTF_GATEWAY};\n    size_t needed;\n\n    if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0 || needed == 0)\n        return false;\n\n    FF_AUTO_FREE char* buf = malloc(needed);\n\n    if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0)\n        return false;\n\n    char* lim = buf + needed;\n    struct rt_msghdr* rtm;\n    for (char* next = buf; next < lim; next += rtm->rtm_msglen)\n    {\n        rtm = (struct rt_msghdr *)next;\n        struct sockaddr* sa = (struct sockaddr *)(rtm + 1);\n\n        if ((rtm->rtm_flags & RTF_GATEWAY) && !(rtm->rtm_flags & RTF_REJECT) && (sa->sa_family == AF_INET))\n        {\n            struct sockaddr_dl* sdl = (struct sockaddr_dl *)get_rt_address(rtm, RTA_IFP);\n            if (sdl && sdl->sdl_family == AF_LINK)\n            {\n                assert(sdl->sdl_nlen <= IF_NAMESIZE);\n                memcpy(result->ifName, sdl->sdl_data, sdl->sdl_nlen);\n                result->ifName[sdl->sdl_nlen] = '\\0';\n                result->ifIndex = sdl->sdl_index;\n\n                // Get the preferred source address\n                struct sockaddr_in* src = (struct sockaddr_in*)get_rt_address(rtm, RTA_IFA);\n                if (src && src->sin_family == AF_INET)\n                    result->preferredSourceAddrV4 = src->sin_addr.s_addr;\n\n                return true;\n            }\n        }\n    }\n    return false;\n}\n\nbool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result)\n{\n    int mib[6] = {CTL_NET, PF_ROUTE, 0, AF_INET6, NET_RT_FLAGS, RTF_GATEWAY};\n    size_t needed;\n\n    if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0 || needed == 0)\n        return false;\n\n    FF_AUTO_FREE char* buf = malloc(needed);\n\n    if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0)\n        return false;\n\n    char* lim = buf + needed;\n    struct rt_msghdr* rtm;\n    for (char* next = buf; next < lim; next += rtm->rtm_msglen)\n    {\n        rtm = (struct rt_msghdr *)next;\n        struct sockaddr* sa = (struct sockaddr *)(rtm + 1);\n\n        if ((rtm->rtm_flags & RTF_GATEWAY) && !(rtm->rtm_flags & RTF_REJECT) && (sa->sa_family == AF_INET6))\n        {\n            struct sockaddr_dl* sdl = (struct sockaddr_dl *)get_rt_address(rtm, RTA_IFP);\n            if (sdl && sdl->sdl_family == AF_LINK)\n            {\n                assert(sdl->sdl_nlen <= IF_NAMESIZE);\n                memcpy(result->ifName, sdl->sdl_data, sdl->sdl_nlen);\n                result->ifName[sdl->sdl_nlen] = '\\0';\n                result->ifIndex = sdl->sdl_index;\n\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "src/common/impl/netif_gnu.c",
    "content": "#include \"common/netif.h\"\n#include \"common/io.h\"\n\n#include <net/if.h>\n#include <stdio.h>\n\n#define FF_STR_INDIR(x) #x\n#define FF_STR(x) FF_STR_INDIR(x)\n\nbool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result)\n{\n    FILE* FF_AUTO_CLOSE_FILE netRoute = fopen(\"/proc/route\", \"r\");\n\n    if (!netRoute) return false;\n\n    // skip first line\n    FF_UNUSED(fscanf(netRoute, \"%*[^\\n]\\n\"));\n    unsigned long long destination; //, gateway, flags, refCount, use, metric, mask, mtu, ...\n    while (fscanf(netRoute, \"%\" FF_STR(IF_NAMESIZE) \"s%llx%*[^\\n]\", result->ifName, &destination) == 2)\n    {\n        if (destination != 0) continue;\n        result->ifIndex = if_nametoindex(result->ifName);\n        // TODO: Get the preferred source address\n        return true;\n    }\n    result->ifName[0] = '0';\n    return false;\n}\n\nbool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result)\n{\n    // TODO: AF_INET6\n    FF_UNUSED(result);\n    return false;\n}\n"
  },
  {
    "path": "src/common/impl/netif_haiku.c",
    "content": "#include \"common/netif.h\"\n#include \"common/io.h\"\n#include \"common/mallocHelper.h\"\n\n#include <arpa/inet.h>\n#include <net/if.h>\n#include <net/route.h>\n#include <sys/socket.h>\n#include <sys/sockio.h>\n#include <stdio.h>\n\n// loosely based on Haiku's src/bin/network/route/route.cpp\n\nbool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result)\n{\n    FF_AUTO_CLOSE_FD int pfRoute = socket(AF_INET, SOCK_RAW, AF_INET);\n    if (pfRoute < 0)\n        return false;\n\n    struct ifconf config;\n    config.ifc_len = sizeof(config.ifc_value);\n    if (ioctl(pfRoute, SIOCGRTSIZE, &config, sizeof(struct ifconf)) < 0)\n        return false;\n\n    int size = config.ifc_value;\n    if (size <= 0)\n        return false;\n\n    FF_AUTO_FREE void *buffer = malloc((size_t) size);\n    if (buffer == NULL) {\n        return false;\n    }\n\n    config.ifc_len = size;\n    config.ifc_buf = buffer;\n    if (ioctl(pfRoute, SIOCGRTTABLE, &config, sizeof(struct ifconf)) < 0)\n        return false;\n\n    struct ifreq *interface = (struct ifreq*)buffer;\n    struct ifreq *end = (struct ifreq*)((uint8_t*)buffer + size);\n\n    while (interface < end) {\n        if (interface->ifr_route.flags & RTF_DEFAULT) {\n            // interface->ifr_metric?\n            strlcpy(result->ifName, interface->ifr_name, IF_NAMESIZE);\n            result->ifIndex = if_nametoindex(interface->ifr_name);\n            if (interface->ifr_route.source)\n                result->preferredSourceAddrV4 = ((struct sockaddr_in*)interface->ifr_route.source)->sin_addr.s_addr;\n            return true;\n        }\n\n        size_t addressSize = 0;\n        if (interface->ifr_route.destination != NULL)\n            addressSize += interface->ifr_route.destination->sa_len;\n        if (interface->ifr_route.mask != NULL)\n            addressSize += interface->ifr_route.mask->sa_len;\n        if (interface->ifr_route.gateway != NULL)\n            addressSize += interface->ifr_route.gateway->sa_len;\n\n        interface = (struct ifreq*)((addr_t)interface + IF_NAMESIZE + sizeof(struct route_entry) + addressSize);\n    }\n\n    return false;\n}\n\nbool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result)\n{\n    FF_AUTO_CLOSE_FD int pfRoute = socket(AF_INET, SOCK_RAW, AF_INET6);\n    if (pfRoute < 0)\n        return false;\n\n    struct ifconf config;\n    config.ifc_len = sizeof(config.ifc_value);\n    if (ioctl(pfRoute, SIOCGRTSIZE, &config, sizeof(struct ifconf)) < 0)\n        return false;\n\n    int size = config.ifc_value;\n    if (size <= 0)\n        return false;\n\n    FF_AUTO_FREE void *buffer = malloc((size_t) size);\n    if (buffer == NULL) {\n        return false;\n    }\n\n    config.ifc_len = size;\n    config.ifc_buf = buffer;\n    if (ioctl(pfRoute, SIOCGRTTABLE, &config, sizeof(struct ifconf)) < 0)\n        return false;\n\n    struct ifreq *interface = (struct ifreq*)buffer;\n    struct ifreq *end = (struct ifreq*)((uint8_t*)buffer + size);\n\n    while (interface < end) {\n        if (interface->ifr_route.flags & RTF_DEFAULT) {\n            strlcpy(result->ifName, interface->ifr_name, IF_NAMESIZE);\n            result->ifIndex = if_nametoindex(interface->ifr_name);\n            return true;\n        }\n\n        size_t addressSize = 0;\n        if (interface->ifr_route.destination != NULL)\n            addressSize += interface->ifr_route.destination->sa_len;\n        if (interface->ifr_route.mask != NULL)\n            addressSize += interface->ifr_route.mask->sa_len;\n        if (interface->ifr_route.gateway != NULL)\n            addressSize += interface->ifr_route.gateway->sa_len;\n\n        interface = (struct ifreq*)((addr_t)interface + IF_NAMESIZE + sizeof(struct route_entry) + addressSize);\n    }\n    return false;\n}\n"
  },
  {
    "path": "src/common/impl/netif_linux.c",
    "content": "#include \"common/netif.h\"\n#include \"common/io.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/debug.h\"\n\n#include <arpa/inet.h>\n#include <linux/rtnetlink.h>\n#include <net/if.h>\n\nbool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result)\n{\n    FF_DEBUG(\"Starting IPv4 default route detection\");\n\n    FF_AUTO_CLOSE_FD int sock_fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);\n    if (sock_fd < 0)\n    {\n        FF_DEBUG(\"Failed to create netlink socket: %s\", strerror(errno));\n        return false;\n    }\n    FF_DEBUG(\"Created netlink socket: fd=%d\", sock_fd);\n\n    uint32_t pid = instance.state.platform.pid;\n    FF_DEBUG(\"Process PID: %u\", pid);\n\n    // Bind socket\n    struct sockaddr_nl addr = {\n        .nl_family = AF_NETLINK,\n        .nl_pid = 0,         // Let kernel choose PID\n        .nl_groups = 0,      // No multicast groups\n    };\n\n    if (bind(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {\n        FF_DEBUG(\"Failed to bind socket: %s\", strerror(errno));\n        return false;\n    }\n    FF_DEBUG(\"Successfully bound socket\");\n\n    struct __attribute__((__packed__)) {\n        struct nlmsghdr nlh;\n        struct rtmsg rtm;\n        struct rtattr rta;\n        uint32_t table;\n    } req = {\n        // Netlink message header\n        .nlh = {\n            .nlmsg_len = sizeof(req),\n            .nlmsg_type = RTM_GETROUTE,\n            .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,\n            .nlmsg_seq = 1,\n            .nlmsg_pid = pid,\n        },\n        // Route message\n        .rtm = {\n            .rtm_family = AF_INET,\n            .rtm_dst_len = 0,  // Match all destinations\n            .rtm_src_len = 0,  // Match all sources\n            .rtm_tos = 0,\n            .rtm_table = RT_TABLE_UNSPEC,\n            .rtm_protocol = RTPROT_UNSPEC,\n            .rtm_scope = RT_SCOPE_UNIVERSE,\n            .rtm_type = RTN_UNSPEC,\n            .rtm_flags = 0,\n        },\n        // Route attribute for main table\n        .rta = {\n            .rta_len = RTA_LENGTH(sizeof(uint32_t)),\n            .rta_type = RTA_TABLE,\n        },\n        .table = RT_TABLE_MAIN,\n    };\n\n    struct sockaddr_nl dest_addr = {\n        .nl_family = AF_NETLINK,\n        .nl_pid = 0,         // Kernel\n        .nl_groups = 0,      // No multicast groups\n    };\n\n    ssize_t sent = sendto(sock_fd, &req, sizeof(req), 0,\n        (struct sockaddr*)&dest_addr, sizeof(dest_addr));\n\n    if (sent != sizeof(req)) {\n        FF_DEBUG(\"Failed to send netlink request: sent=%zd, expected=%zu\", sent, sizeof(req));\n        return false;\n    }\n    FF_DEBUG(\"Sent netlink request: %zd bytes\", sent);\n\n    struct sockaddr_nl src_addr = {};\n    socklen_t src_addr_len = sizeof(src_addr);\n\n    uint8_t buffer[1024 * 16]; // 16 KB buffer should be sufficient\n    uint32_t minMetric = UINT32_MAX;\n    FF_MAYBE_UNUSED int routeCount = 0;\n\n    while (true)\n    {\n        ssize_t received = recvfrom(sock_fd, buffer, sizeof(buffer), 0,\n            (struct sockaddr*)&src_addr, &src_addr_len);\n\n        if (received < 0) {\n            FF_DEBUG(\"Failed to receive netlink response: %s\", strerror(errno));\n            return false;\n        }\n\n        if (received >= (ssize_t)sizeof(buffer)) {\n            FF_DEBUG(\"Received truncated message: received %zd, bufsize %zu\", received, sizeof(buffer));\n            return false;\n        }\n        FF_DEBUG(\"Received netlink response: %zd bytes\", received);\n        if (received == 0) {\n            FF_DEBUG(\"Received zero-length netlink response, ending processing\");\n            break;\n        }\n\n        struct {\n            uint32_t metric;\n            uint32_t ifindex;\n            uint32_t prefsrc;\n        } entry;\n\n        for (const struct nlmsghdr* nlh = (struct nlmsghdr*)buffer;\n            NLMSG_OK(nlh, received);\n            nlh = NLMSG_NEXT(nlh, received))\n        {\n            if (nlh->nlmsg_seq != 1 || nlh->nlmsg_pid != pid)\n                continue;\n            if (nlh->nlmsg_type == NLMSG_DONE)\n            {\n                FF_DEBUG(\"Received NLMSG_DONE, processed %d routes\", routeCount);\n                goto exit;\n            }\n\n            if (nlh->nlmsg_type == NLMSG_ERROR) {\n                FF_DEBUG(\"Netlink reports error: %s\", strerror(-((struct nlmsgerr*)NLMSG_DATA(nlh))->error));\n                continue;\n            }\n\n            if (nlh->nlmsg_type != RTM_NEWROUTE) {\n                FF_DEBUG(\"Skipping non-route message: type=%d\", nlh->nlmsg_type);\n                continue;\n            }\n\n            routeCount++;\n            struct rtmsg* rtm = (struct rtmsg*)NLMSG_DATA(nlh);\n            if (rtm->rtm_family != AF_INET) {\n                FF_DEBUG(\"Skipping non-IPv4 route #%d (family=%d)\", routeCount, rtm->rtm_family);\n                continue;\n            }\n\n            if (rtm->rtm_dst_len != 0) {\n                FF_DEBUG(\"Skipping non-default route #%d (dst_len=%d)\", routeCount, rtm->rtm_dst_len);\n                continue;\n            }\n\n            // Skip local/loopback routes\n            if (rtm->rtm_scope == RT_SCOPE_HOST || rtm->rtm_type == RTN_LOCAL) {\n                FF_DEBUG(\"Skipping local route #%d (scope=%d, type=%d)\", routeCount, rtm->rtm_scope, rtm->rtm_type);\n                continue;\n            }\n\n            FF_DEBUG(\"Processing IPv4 default route candidate #%d\", routeCount);\n            entry = (__typeof__(entry)) { }; // Default to zero metric (no RTA_PRIORITY found)\n\n            // Parse route attributes\n            size_t rtm_len = RTM_PAYLOAD(nlh);\n            for (struct rtattr* rta = RTM_RTA(rtm);\n                RTA_OK(rta, rtm_len);\n                rta = RTA_NEXT(rta, rtm_len))\n            {\n                if (RTA_PAYLOAD(rta) < sizeof(uint32_t))\n                    continue; // Skip invalid attributes\n\n                uint32_t rta_data = *(uint32_t*) RTA_DATA(rta);\n                switch (rta->rta_type) {\n                    case RTA_DST:\n                        FF_DEBUG(\"Unexpected RTA_DST: %s (len=%u)\", inet_ntoa((struct in_addr) { .s_addr = rta_data }), rtm->rtm_dst_len);\n                        goto next;\n                    case RTA_OIF:\n                        entry.ifindex = rta_data;\n                        FF_DEBUG(\"Found interface index: %u\", entry.ifindex);\n                        break;\n                    case RTA_GATEWAY:\n                        FF_DEBUG(\"Found gateway: %s\", inet_ntoa(*(struct in_addr*)&rta_data));\n                        if (rta_data == 0) goto next;\n                        break;\n                    case RTA_PRIORITY:\n                        FF_DEBUG(\"Found metric: %u\", rta_data);\n                        if (rta_data >= minMetric) goto next;\n                        entry.metric = rta_data;\n                        break;\n                    case RTA_PREFSRC:\n                        entry.prefsrc = rta_data;\n                        FF_DEBUG(\"Found preferred source: %s\", inet_ntoa(*(struct in_addr*)&rta_data));\n                        break;\n                }\n            }\n\n            if (entry.ifindex == 0 || entry.metric >= minMetric)\n            {\n            next:\n                FF_DEBUG(\"Skipping route: ifindex=%u, metric=%u\", entry.ifindex, entry.metric);\n                continue;\n            }\n            minMetric = entry.metric;\n            result->ifIndex = entry.ifindex;\n            FF_DEBUG(\"Updated best route: ifindex=%u, metric=%u, prefsrc=%x\", entry.ifindex, entry.metric, entry.prefsrc);\n            result->preferredSourceAddrV4 = entry.prefsrc;\n            if (minMetric == 0)\n            {\n                FF_DEBUG(\"Found zero metric route, stopping further processing\");\n                break; // Stop processing if we found a zero metric route\n            }\n        }\n    }\n\nexit:\n    if (minMetric < UINT32_MAX)\n    {\n        if_indextoname(result->ifIndex, result->ifName);\n        FF_DEBUG(\"Found default IPv4 route: interface=%s, index=%u, metric=%u\", result->ifName, result->ifIndex, minMetric);\n        return true;\n    }\n    FF_DEBUG(\"No IPv4 default route found\");\n    return false;\n}\n\nbool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result)\n{\n    FF_DEBUG(\"Starting IPv6 default route detection\");\n\n    FF_AUTO_CLOSE_FD int sock_fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);\n    if (sock_fd < 0)\n    {\n        FF_DEBUG(\"Failed to create netlink socket: %s\", strerror(errno));\n        return false;\n    }\n    FF_DEBUG(\"Created netlink socket: fd=%d\", sock_fd);\n\n    uint32_t pid = instance.state.platform.pid;\n    FF_DEBUG(\"Process PID: %u\", pid);\n\n    // Bind socket\n    struct sockaddr_nl addr = {\n        .nl_family = AF_NETLINK,\n        .nl_pid = 0,         // Let kernel choose PID\n        .nl_groups = 0,      // No multicast groups\n    };\n\n    if (bind(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {\n        FF_DEBUG(\"Failed to bind socket: %s\", strerror(errno));\n        return false;\n    }\n    FF_DEBUG(\"Successfully bound socket\");\n\n    struct __attribute__((__packed__)) {\n        struct nlmsghdr nlh;\n        struct rtmsg rtm;\n        struct rtattr rta;\n        uint32_t table;\n    } req = {\n        // Netlink message header\n        .nlh = {\n            .nlmsg_len = sizeof(req),\n            .nlmsg_type = RTM_GETROUTE,\n            .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,\n            .nlmsg_seq = 1,\n            .nlmsg_pid = pid,\n        },\n        // Route message\n        .rtm = {\n            .rtm_family = AF_INET6,    // IPv6 instead of IPv4\n            .rtm_dst_len = 0,  // Match all destinations\n            .rtm_src_len = 0,  // Match all sources\n            .rtm_tos = 0,\n            .rtm_table = RT_TABLE_UNSPEC,\n            .rtm_protocol = RTPROT_UNSPEC,\n            .rtm_scope = RT_SCOPE_UNIVERSE,\n            .rtm_type = RTN_UNSPEC,\n            .rtm_flags = 0,\n        },\n        // Route attribute for main table\n        .rta = {\n            .rta_len = RTA_LENGTH(sizeof(uint32_t)),\n            .rta_type = RTA_TABLE,\n        },\n        .table = RT_TABLE_MAIN,\n    };\n\n    struct sockaddr_nl dest_addr = {\n        .nl_family = AF_NETLINK,\n        .nl_pid = 0,         // Kernel\n        .nl_groups = 0,      // No multicast groups\n    };\n\n    ssize_t sent = sendto(sock_fd, &req, sizeof(req), 0,\n        (struct sockaddr*)&dest_addr, sizeof(dest_addr));\n\n    if (sent != sizeof(req)) {\n        FF_DEBUG(\"Failed to send netlink request: sent=%zd, expected=%zu\", sent, sizeof(req));\n        return false;\n    }\n    FF_DEBUG(\"Sent netlink request: %zd bytes\", sent);\n\n    struct sockaddr_nl src_addr = {};\n    socklen_t src_addr_len = sizeof(src_addr);\n\n    uint8_t buffer[1024 * 16]; // 16 KB buffer should be sufficient\n    uint32_t minMetric = UINT32_MAX;\n    FF_MAYBE_UNUSED int routeCount = 0;\n\n    while (true)\n    {\n        ssize_t received = recvfrom(sock_fd, buffer, sizeof(buffer), 0,\n            (struct sockaddr*)&src_addr, &src_addr_len);\n\n        if (received < 0) {\n            FF_DEBUG(\"Failed to receive netlink response: %s\", strerror(errno));\n            return false;\n        }\n\n        if (received >= (ssize_t)sizeof(buffer)) {\n            FF_DEBUG(\"Received truncated message: received %zd, bufsize %zu\", received, sizeof(buffer));\n            return false;\n        }\n        FF_DEBUG(\"Received netlink response: %zd bytes\", received);\n        if (received == 0) {\n            FF_DEBUG(\"Received zero-length netlink response, ending processing\");\n            break;\n        }\n\n        struct {\n            uint32_t metric;\n            uint32_t ifindex;\n        } entry;\n\n        for (const struct nlmsghdr* nlh = (struct nlmsghdr*)buffer;\n            NLMSG_OK(nlh, received);\n            nlh = NLMSG_NEXT(nlh, received))\n        {\n            if (nlh->nlmsg_seq != 1 || nlh->nlmsg_pid != pid)\n                continue;\n            if (nlh->nlmsg_type == NLMSG_DONE)\n            {\n                FF_DEBUG(\"Received NLMSG_DONE, processed %d routes\", routeCount);\n                goto exit;\n            }\n\n            if (nlh->nlmsg_type == NLMSG_ERROR) {\n                FF_DEBUG(\"Netlink reports error: %s\", strerror(-((struct nlmsgerr*)NLMSG_DATA(nlh))->error));\n                continue;\n            }\n\n            if (nlh->nlmsg_type != RTM_NEWROUTE) {\n                FF_DEBUG(\"Skipping non-route message: type=%d\", nlh->nlmsg_type);\n                continue;\n            }\n\n            routeCount++;\n            struct rtmsg* rtm = (struct rtmsg*)NLMSG_DATA(nlh);\n            if (rtm->rtm_family != AF_INET6) {\n                FF_DEBUG(\"Skipping non-IPv6 route #%d (family=%d)\", routeCount, rtm->rtm_family);\n                continue;\n            }\n\n            if (rtm->rtm_dst_len != 0) {\n                FF_DEBUG(\"Skipping non-default route #%d (dst_len=%d)\", routeCount, rtm->rtm_dst_len);\n                continue;\n            }\n\n            // Skip local/loopback routes\n            if (rtm->rtm_scope == RT_SCOPE_HOST || rtm->rtm_type == RTN_LOCAL) {\n                FF_DEBUG(\"Skipping local route #%d (scope=%d, type=%d)\", routeCount, rtm->rtm_scope, rtm->rtm_type);\n                continue;\n            }\n\n            FF_DEBUG(\"Processing IPv6 default route candidate #%d\", routeCount);\n            entry = (__typeof__(entry)) { }; // Default to zero metric (no RTA_PRIORITY found)\n\n            // Parse route attributes\n            size_t rtm_len = RTM_PAYLOAD(nlh);\n            for (struct rtattr* rta = RTM_RTA(rtm);\n                RTA_OK(rta, rtm_len);\n                rta = RTA_NEXT(rta, rtm_len))\n            {\n                switch (rta->rta_type) {\n                    case RTA_DST:\n                        if (RTA_PAYLOAD(rta) >= sizeof(struct in6_addr)) {\n                            FF_MAYBE_UNUSED char str[INET6_ADDRSTRLEN];\n                            FF_DEBUG(\"Unexpected RTA_DST: %s\", inet_ntop(AF_INET6, RTA_DATA(rta), str, sizeof(str)));\n                            goto next;\n                        }\n                        break;\n                    case RTA_OIF:\n                        if (RTA_PAYLOAD(rta) >= sizeof(uint32_t)) {\n                            entry.ifindex = *(uint32_t*) RTA_DATA(rta);\n                            FF_DEBUG(\"Found interface index: %u\", entry.ifindex);\n                        }\n                        break;\n                    case RTA_GATEWAY:\n                        if (RTA_PAYLOAD(rta) >= sizeof(struct in6_addr)) {\n                            struct in6_addr* gw = (struct in6_addr*) RTA_DATA(rta);\n                            if (IN6_IS_ADDR_UNSPECIFIED(gw)) goto next;\n                            FF_MAYBE_UNUSED char str[INET6_ADDRSTRLEN];\n                            FF_DEBUG(\"Found gateway: %s\", inet_ntop(AF_INET6, gw, str, sizeof(str)));\n                        }\n                        break;\n                    case RTA_PRIORITY:\n                        if (RTA_PAYLOAD(rta) >= sizeof(uint32_t)) {\n                            uint32_t metric = *(uint32_t*) RTA_DATA(rta);\n                            FF_DEBUG(\"Found metric: %u\", metric);\n                            if (metric >= minMetric) goto next;\n                            entry.metric = metric;\n                        }\n                        break;\n                }\n            }\n\n            if (entry.ifindex == 0 || entry.metric >= minMetric)\n            {\n            next:\n                FF_DEBUG(\"Skipping route: ifindex=%u, metric=%u\", entry.ifindex, entry.metric);\n                continue;\n            }\n            minMetric = entry.metric;\n            result->ifIndex = entry.ifindex;\n            FF_DEBUG(\"Updated best route: ifindex=%u, metric=%u\", entry.ifindex, entry.metric);\n\n            if (minMetric == 0)\n            {\n                FF_DEBUG(\"Found zero metric route, stopping further processing\");\n                break; // Stop processing if we found a zero metric route\n            }\n        }\n    }\n\nexit:\n    if (minMetric < UINT32_MAX)\n    {\n        if_indextoname(result->ifIndex, result->ifName);\n        FF_DEBUG(\"Found default IPv6 route: interface=%s, index=%u, metric=%u\", result->ifName, result->ifIndex, minMetric);\n        return true;\n    }\n    FF_DEBUG(\"No IPv6 default route found\");\n    return false;\n}\n"
  },
  {
    "path": "src/common/impl/netif_windows.c",
    "content": "#include \"common/netif.h\"\n#include \"common/mallocHelper.h\"\n\n#include <ws2tcpip.h> // AF_INET6, IN6_IS_ADDR_UNSPECIFIED\n#include <iphlpapi.h>\n\nbool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result)\n{\n    PMIB_IPFORWARD_TABLE2 pIpForwardTable = NULL;\n\n    if (!NETIO_SUCCESS(GetIpForwardTable2(AF_UNSPEC, &pIpForwardTable)))\n        return false;\n\n    bool foundDefault = false;\n    uint32_t smallestMetric = UINT32_MAX;\n\n    for (ULONG i = 0; i < pIpForwardTable->NumEntries; ++i)\n    {\n        MIB_IPFORWARD_ROW2* row = &pIpForwardTable->Table[i];\n\n        if (row->DestinationPrefix.PrefixLength == 0 &&\n            row->DestinationPrefix.Prefix.Ipv4.sin_family == AF_INET &&\n            row->DestinationPrefix.Prefix.Ipv4.sin_addr.S_un.S_addr == 0)\n        {\n            MIB_IF_ROW2 ifRow = {\n                .InterfaceIndex = row->InterfaceIndex,\n            };\n            if (NETIO_SUCCESS(GetIfEntry2(&ifRow)) && ifRow.OperStatus == IfOperStatusUp)\n            {\n                MIB_IPINTERFACE_ROW ipInterfaceRow = {\n                    .Family = AF_INET,\n                    .InterfaceIndex = row->InterfaceIndex,\n                };\n\n                uint32_t realMetric = row->Metric /* Metric offset */;\n\n                if (NETIO_SUCCESS(GetIpInterfaceEntry(&ipInterfaceRow)))\n                    realMetric += ipInterfaceRow.Metric /* Interface metric */;\n\n                if (realMetric < smallestMetric)\n                {\n                    smallestMetric = realMetric;\n                    result->ifIndex = row->InterfaceIndex;\n                    foundDefault = true;\n                    break;\n                }\n            }\n        }\n    }\n\n    FreeMibTable(pIpForwardTable);\n\n    return foundDefault;\n}\n\n\nbool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result)\n{\n    PMIB_IPFORWARD_TABLE2 pIpForwardTable = NULL;\n\n    if (!NETIO_SUCCESS(GetIpForwardTable2(AF_UNSPEC, &pIpForwardTable)))\n        return false;\n\n    bool foundDefault = false;\n    uint32_t smallestMetric = UINT32_MAX;\n\n    for (ULONG i = 0; i < pIpForwardTable->NumEntries; ++i)\n    {\n        MIB_IPFORWARD_ROW2* row = &pIpForwardTable->Table[i];\n\n        if (row->DestinationPrefix.PrefixLength == 0 &&\n            row->DestinationPrefix.Prefix.Ipv6.sin6_family == AF_INET6 &&\n            IN6_IS_ADDR_UNSPECIFIED(&row->DestinationPrefix.Prefix.Ipv6.sin6_addr))\n        {\n            MIB_IF_ROW2 ifRow = {\n                .InterfaceIndex = row->InterfaceIndex,\n            };\n            if (NETIO_SUCCESS(GetIfEntry2(&ifRow)) && ifRow.OperStatus == IfOperStatusUp)\n            {\n                MIB_IPINTERFACE_ROW ipInterfaceRow = {\n                    .Family = AF_INET6,\n                    .InterfaceIndex = row->InterfaceIndex,\n                };\n\n                uint32_t realMetric = row->Metric /* Metric offset */;\n\n                if (NETIO_SUCCESS(GetIpInterfaceEntry(&ipInterfaceRow)))\n                    realMetric += ipInterfaceRow.Metric /* Interface metric */;\n\n                if (realMetric < smallestMetric)\n                {\n                    smallestMetric = realMetric;\n                    result->ifIndex = row->InterfaceIndex;\n                    foundDefault = true;\n                }\n            }\n        }\n    }\n\n    FreeMibTable(pIpForwardTable);\n\n    return foundDefault;\n}\n"
  },
  {
    "path": "src/common/impl/networking_common.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/library.h\"\n#include \"common/networking.h\"\n#include \"common/stringUtils.h\"\n#include \"common/debug.h\"\n\n#ifdef FF_HAVE_ZLIB\n#include <zlib.h>\n\nstruct FFZlibLibrary\n{\n    FF_LIBRARY_SYMBOL(inflateInit2_)\n    FF_LIBRARY_SYMBOL(inflate)\n    FF_LIBRARY_SYMBOL(inflateEnd)\n\n    bool inited;\n} zlibData;\n\nconst char* ffNetworkingLoadZlibLibrary(void)\n{\n    if (!zlibData.inited)\n    {\n        zlibData.inited = true;\n        FF_LIBRARY_LOAD_MESSAGE(zlib,\n            #ifdef _WIN32\n                \"zlib1\"\n            #else\n                \"libz\"\n            #endif\n            FF_LIBRARY_EXTENSION, 2)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflateInit2_)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflate)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflateEnd)\n        zlib = NULL; // don't auto dlclose\n    }\n    return zlibData.ffinflateEnd == NULL ? \"Failed to load libz\" : NULL;\n}\n\n// Try to pre-read gzip header to determine uncompressed size\nstatic uint32_t guessGzipOutputSize(const void* data, uint32_t dataSize)\n{\n    // gzip file format: http://www.zlib.org/rfc-gzip.html\n    if (dataSize < 10 || ((const uint8_t*)data)[0] != 0x1f || ((const uint8_t*)data)[1] != 0x8b)\n        return 0;\n\n    // Uncompressed size in gzip format is stored in the last 4 bytes, but only valid if data is less than 4GB\n    if (dataSize > 18) {\n        // Get ISIZE value from the end of file (little endian)\n        const uint8_t* tail = (const uint8_t*)data + dataSize - 4;\n        uint32_t uncompressedSize = (uint32_t) tail[0] | ((uint32_t) tail[1] << 8u) | ((uint32_t) tail[2] << 16u) | ((uint32_t) tail[3] << 24u);\n\n        // For valid gzip files, this value is the length of the uncompressed data modulo 2^32\n        if (uncompressedSize > 0) {\n            FF_DEBUG(\"Read uncompressed size from GZIP trailer: %u bytes\", uncompressedSize);\n            // Add some margin to the estimated size for safety\n            return uncompressedSize + 64;\n        }\n    }\n\n    // If unable to get size from trailer or size is 0, use estimated value\n    // Typically, text data compression ratio is between 3-5x, we use the larger value\n    uint32_t estimatedSize = dataSize * 5;\n    FF_DEBUG(\"Unable to read exact uncompressed size, estimated as 5x of compressed data: %u bytes\", estimatedSize);\n    return estimatedSize;\n}\n\n// Decompress gzip content\nbool ffNetworkingDecompressGzip(FFstrbuf* buffer, char* headerEnd)\n{\n    assert(headerEnd != NULL && *headerEnd == '\\r');\n\n    // Calculate header size\n    uint32_t headerSize = (uint32_t) (headerEnd - buffer->chars);\n\n    *headerEnd = '\\0'; // Replace delimiter with null character for easier processing\n    // Ensure Content-Encoding is in response headers, not in response body\n    bool hasGzip = strcasestr(buffer->chars, \"\\nContent-Encoding: gzip\") != NULL;\n    *headerEnd = '\\r'; // Restore delimiter\n\n    if (!hasGzip) {\n        FF_DEBUG(\"No gzip compressed content detected, skipping decompression\");\n        return true;\n    }\n\n    FF_DEBUG(\"Gzip compressed content detected, preparing for decompression\");\n\n    const char* bodyStart = headerEnd + 4; // Skip delimiter\n\n    if (buffer->length <= headerSize + 4) {\n        // No content to decompress\n        FF_DEBUG(\"Compressed content size is 0, skipping decompression\");\n        return true;\n    }\n\n    // Calculate compressed content size\n    uint32_t compressedSize = buffer->length - headerSize - 4;\n\n    // Check if content is actually in gzip format (gzip header magic is 0x1f 0x8b)\n    if (compressedSize < 2 || (uint8_t)bodyStart[0] != 0x1f || (uint8_t)bodyStart[1] != 0x8b) {\n        FF_DEBUG(\"Content is not valid gzip format, skipping decompression\");\n        return false;\n    }\n\n    // Predict uncompressed size\n    uint32_t estimatedSize = guessGzipOutputSize(bodyStart, compressedSize);\n\n    // Create decompression buffer with estimated size\n    FF_STRBUF_AUTO_DESTROY decompressedBuffer = ffStrbufCreateA(estimatedSize > 0 ? estimatedSize : compressedSize * 5);\n    FF_DEBUG(\"Created decompression buffer: %u bytes\", decompressedBuffer.allocated);\n\n    // Initialize decompression\n    z_stream zs = {\n        .zalloc = Z_NULL,\n        .zfree = Z_NULL,\n        .opaque = Z_NULL,\n        .avail_in = (uInt)compressedSize,\n        .next_in = (Bytef*)bodyStart,\n        .avail_out = (uInt)ffStrbufGetFree(&decompressedBuffer),\n        .next_out = (Bytef*)decompressedBuffer.chars,\n    };\n\n    // Initialize decompression engine\n    if (zlibData.ffinflateInit2_(&zs, 16 + MAX_WBITS, ZLIB_VERSION, (int)sizeof(z_stream)) != Z_OK) {\n        FF_DEBUG(\"Failed to initialize decompression engine\");\n        return false;\n    }\n    uInt availableOut = zs.avail_out;\n\n    // Perform decompression\n    int result = zlibData.ffinflate(&zs, Z_FINISH);\n\n    // If output buffer is insufficient, try to extend buffer\n    while (result == Z_BUF_ERROR || (result != Z_STREAM_END && zs.avail_out == 0))\n    {\n        FF_DEBUG(\"Output buffer insufficient, trying to extend\");\n\n        // Save already decompressed data amount\n        uint32_t alreadyDecompressed = (uint32_t)(availableOut - zs.avail_out);\n        decompressedBuffer.length += alreadyDecompressed;\n        decompressedBuffer.chars[decompressedBuffer.length] = '\\0';\n\n        ffStrbufEnsureFree(&decompressedBuffer, decompressedBuffer.length / 2);\n\n        // Set output parameters to point to new buffer\n        zs.avail_out = (uInt)ffStrbufGetFree(&decompressedBuffer);\n        zs.next_out = (Bytef*)(decompressedBuffer.chars + decompressedBuffer.length);\n        availableOut = zs.avail_out;\n\n        // Decompress again\n        result = zlibData.ffinflate(&zs, Z_FINISH);\n    }\n\n    zlibData.ffinflateEnd(&zs);\n\n    // Calculate decompressed size\n    uint32_t decompressedSize = (uint32_t)(availableOut - zs.avail_out);\n    decompressedBuffer.length += decompressedSize;\n    decompressedBuffer.chars[decompressedBuffer.length] = '\\0';\n    FF_DEBUG(\"Successfully decompressed %u bytes compressed data to %u bytes\", compressedSize, decompressedBuffer.length);\n\n    // Modify Content-Length header and remove Content-Encoding header\n    FF_STRBUF_AUTO_DESTROY newBuffer = ffStrbufCreateA(headerSize + decompressedSize + 64);\n\n    char* line = NULL;\n    size_t len = 0;\n    while (ffStrbufGetline(&line, &len, buffer))\n    {\n        if (ffStrStartsWithIgnCase(line, \"Content-Encoding:\"))\n        {\n            continue;\n        }\n        else if (ffStrStartsWithIgnCase(line, \"Content-Length:\"))\n        {\n            ffStrbufAppendF(&newBuffer, \"Content-Length: %u\\r\\n\", decompressedBuffer.length);\n            continue;\n        }\n        else if (line[0] == '\\r')\n        {\n            ffStrbufAppendS(&newBuffer, \"\\r\\n\");\n            ffStrbufGetlineRestore(&line, &len, buffer);\n            break;\n        }\n\n        ffStrbufAppendS(&newBuffer, line); // Including the trailing \\r\n        ffStrbufAppendC(&newBuffer, '\\n');\n    }\n\n    ffStrbufAppend(&newBuffer, &decompressedBuffer);\n    ffStrbufDestroy(buffer);\n    ffStrbufInitMove(buffer, &newBuffer);\n\n    return true;\n}\n#endif // FF_HAVE_ZLIB\n"
  },
  {
    "path": "src/common/impl/networking_linux.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/networking.h\"\n#include \"common/time.h\"\n#include \"common/library.h\"\n#include \"common/stringUtils.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/debug.h\"\n\n#include <unistd.h>\n#include <poll.h>\n#include <sys/time.h>\n#include <sys/socket.h>\n#include <netdb.h>\n#include <netinet/in.h> // For FreeBSD\n#include <netinet/tcp.h>\n#include <errno.h>\n#include <fcntl.h>\n\nstatic const char* tryNonThreadingFastPath(FFNetworkingState* state)\n{\n    #if defined(TCP_FASTOPEN) || __APPLE__\n\n        if (!state->tfo)\n        {\n            #if __linux__ || __GNU__\n            // Linux doesn't support sendto() on unconnected sockets\n            FF_DEBUG(\"TCP Fast Open disabled, skipping\");\n            return \"TCP Fast Open disabled\";\n            #endif\n        }\n        else\n        {\n            FF_DEBUG(\"Attempting to use TCP Fast Open to connect\");\n\n            #ifndef __APPLE__ // On macOS, TCP_FASTOPEN doesn't seem to be needed\n            // Set TCP Fast Open\n            int flag = 1;\n            if (setsockopt(state->sockfd, IPPROTO_TCP,\n                #ifdef __APPLE__\n                // https://github.com/rust-lang/libc/pull/3135\n                0x218 // TCP_FASTOPEN_FORCE_ENABLE\n                #else\n                TCP_FASTOPEN\n                #endif\n                , &flag, sizeof(flag)) != 0) {\n                FF_DEBUG(\"Failed to set TCP_FASTOPEN option: %s\", strerror(errno));\n                return \"setsockopt(TCP_FASTOPEN) failed\";\n            } else {\n                #if __linux__ || __GNU__\n                FF_DEBUG(\"Successfully set TCP_FASTOPEN option, queue length: %d\", flag);\n                #elif defined(__APPLE__)\n                FF_DEBUG(\"Successfully set TCP_FASTOPEN_FORCE_ENABLE option\");\n                #else\n                FF_DEBUG(\"Successfully set TCP_FASTOPEN option\");\n                #endif\n            }\n            #endif\n        }\n\n        #ifndef __APPLE__\n        FF_DEBUG(\"Using sendto() + MSG_DONTWAIT to send %u bytes of data\", state->command.length);\n        ssize_t sent = sendto(state->sockfd,\n                                state->command.chars,\n                                state->command.length,\n            #ifdef MSG_FASTOPEN\n                                MSG_FASTOPEN |\n            #endif\n            #ifdef MSG_NOSIGNAL\n                                MSG_NOSIGNAL |\n            #endif\n                                MSG_DONTWAIT,\n                                state->addr->ai_addr,\n                                state->addr->ai_addrlen);\n        #else\n        if (fcntl(state->sockfd, F_SETFL, O_NONBLOCK) == -1) {\n            FF_DEBUG(\"fcntl(F_SETFL) failed: %s\", strerror(errno));\n            return \"fcntl(F_SETFL) failed\";\n        }\n        FF_DEBUG(\"Using connectx() to send %u bytes of data\", state->command.length);\n        // Use connectx to establish connection and send data in one call\n        size_t sent;\n        if (connectx(state->sockfd,\n            &(sa_endpoints_t) {\n                .sae_dstaddr = state->addr->ai_addr,\n                .sae_dstaddrlen = state->addr->ai_addrlen,\n            },\n            SAE_ASSOCID_ANY, state->tfo ? CONNECT_DATA_IDEMPOTENT : 0,\n            &(struct iovec) {\n                .iov_base = state->command.chars,\n                .iov_len = state->command.length,\n            }, 1, &sent, NULL) != 0) sent = 0;\n        if (fcntl(state->sockfd, F_SETFL, 0) == -1) {\n            FF_DEBUG(\"fcntl(F_SETFL) failed: %s\", strerror(errno));\n            return \"fcntl(F_SETFL) failed\";\n        }\n        #endif\n        if (sent > 0 || (errno == EAGAIN || errno == EWOULDBLOCK\n            #ifdef __APPLE__\n            // On macOS EINPROGRESS means the connection cannot be completed immediately\n            // On Linux, it means the TFO cookie is not available locally\n            || errno == EINPROGRESS\n            #endif\n        ))\n        {\n            FF_DEBUG(\n                #ifdef __APPLE__\n                \"connectx()\"\n                #else\n                \"sendto()\"\n                #endif\n                \" %s (sent=%zd, errno=%d: %s)\", errno == 0 ? \"succeeded\" : \"was in progress\",\n                sent, errno, strerror(errno));\n            freeaddrinfo(state->addr);\n            state->addr = NULL;\n            ffStrbufDestroy(&state->command);\n            return NULL;\n        }\n\n        FF_DEBUG(\n            #ifdef __APPLE__\n            \"connectx()\"\n            #else\n            \"sendto()\"\n            #endif\n            \" failed: %s (errno=%d)\", strerror(errno), errno);\n        #ifdef __APPLE__\n        return \"connectx() failed\";\n        #else\n        return \"sendto() failed\";\n        #endif\n    #else\n        FF_UNUSED(state);\n        return \"TFO support is not available\";\n    #endif\n}\n\n// Traditional connect and send function\nstatic const char* connectAndSend(FFNetworkingState* state)\n{\n    const char* ret = NULL;\n    FF_DEBUG(\"Using traditional connection method to connect\");\n\n    FF_DEBUG(\"Attempting connect() to server...\");\n    if(connect(state->sockfd, state->addr->ai_addr, state->addr->ai_addrlen) == -1)\n    {\n        FF_DEBUG(\"connect() failed: %s (errno=%d)\", strerror(errno), errno);\n        ret = \"connect() failed\";\n        goto error;\n    }\n    FF_DEBUG(\"connect() succeeded\");\n\n    FF_DEBUG(\"Attempting to send %u bytes of data...\", state->command.length);\n    if(send(state->sockfd, state->command.chars, state->command.length, 0) < 0)\n    {\n        FF_DEBUG(\"send() failed: %s (errno=%d)\", strerror(errno), errno);\n        ret = \"send() failed\";\n        goto error;\n    }\n    FF_DEBUG(\"Data sent successfully\");\n\n    goto exit;\n\nerror:\n    FF_DEBUG(\"Error occurred, closing socket\");\n    close(state->sockfd);\n    state->sockfd = -1;\n\nexit:\n    FF_DEBUG(\"Releasing address info and other resources\");\n    freeaddrinfo(state->addr);\n    state->addr = NULL;\n    ffStrbufDestroy(&state->command);\n\n    return ret;\n}\n\nFF_THREAD_ENTRY_DECL_WRAPPER(connectAndSend, FFNetworkingState*);\n\n// Parallel DNS resolution and socket creation\nstatic const char* initNetworkingState(FFNetworkingState* state, const char* host, const char* path, const char* headers)\n{\n    FF_DEBUG(\"Initializing network connection state: host=%s, path=%s\", host, path);\n\n    // Initialize command and host information\n    ffStrbufInitA(&state->command, 128);\n    ffStrbufAppendS(&state->command, \"GET \");\n    ffStrbufAppendS(&state->command, path);\n    ffStrbufAppendS(&state->command, \" HTTP/1.0\\r\\nHost: \");\n    ffStrbufAppendS(&state->command, host);\n    ffStrbufAppendS(&state->command, \"\\r\\nConnection: close\\r\\n\"); // Explicitly tell the server we don't need to keep the connection\n\n    // If compression needs to be enabled\n    if (state->compression) {\n        FF_DEBUG(\"Enabling HTTP content compression\");\n        ffStrbufAppendS(&state->command, \"Accept-Encoding: gzip\\r\\n\");\n    }\n\n    ffStrbufAppendS(&state->command, headers);\n    ffStrbufAppendS(&state->command, \"\\r\\n\");\n\n    #ifdef FF_HAVE_THREADS\n    state->thread = 0;\n    FF_DEBUG(\"Thread ID initialized to 0\");\n    #endif\n\n    const char* ret = NULL;\n\n    struct addrinfo hints = {\n        .ai_family = state->ipv6 ? AF_INET6 : AF_INET,\n        .ai_socktype = SOCK_STREAM,\n        .ai_flags = AI_NUMERICSERV\n    };\n\n    FF_DEBUG(\"Resolving address: %s (%s)\", host, state->ipv6 ? \"IPv6\" : \"IPv4\");\n    // Use AI_NUMERICSERV flag to indicate the service is a numeric port, reducing parsing time\n\n    int gaiRes = getaddrinfo(host, \"80\", &hints, &state->addr);\n    if(gaiRes != 0)\n    {\n        FF_DEBUG(\"getaddrinfo() failed: %s (res=%d)\", gai_strerror(gaiRes), gaiRes);\n        ret = \"getaddrinfo() failed\";\n        goto error;\n    }\n    FF_DEBUG(\"Address resolution successful\");\n\n    FF_DEBUG(\"Creating socket\");\n    state->sockfd = socket(state->addr->ai_family, state->addr->ai_socktype, state->addr->ai_protocol);\n    if(state->sockfd == -1)\n    {\n        FF_DEBUG(\"socket() failed: %s (errno=%d)\", strerror(errno), errno);\n        ret = \"socket() failed\";\n        goto error;\n    }\n    FF_DEBUG(\"Socket creation successful: fd=%d\", state->sockfd);\n\n    int flag = 1;\n    #ifdef TCP_NODELAY\n    // Disable Nagle's algorithm to reduce small packet transmission delay\n    if (setsockopt(state->sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag)) != 0) {\n        FF_DEBUG(\"Failed to set TCP_NODELAY: %s\", strerror(errno));\n    } else {\n        FF_DEBUG(\"Successfully disabled Nagle's algorithm\");\n    }\n    #endif\n\n    #ifdef TCP_QUICKACK\n    // Set TCP_QUICKACK option to avoid delayed acknowledgments\n    if (setsockopt(state->sockfd, IPPROTO_TCP, TCP_QUICKACK, &flag, sizeof(flag)) != 0) {\n        FF_DEBUG(\"Failed to set TCP_QUICKACK: %s\", strerror(errno));\n    } else {\n        FF_DEBUG(\"Successfully enabled TCP quick acknowledgment\");\n    }\n    #endif\n\n    if (state->timeout > 0)\n    {\n        FF_DEBUG(\"Setting connection timeout: %u ms\", state->timeout);\n        FF_MAYBE_UNUSED uint32_t sec = state->timeout / 1000;\n        if (sec == 0) sec = 1;\n\n        #ifdef TCP_CONNECTIONTIMEOUT\n        FF_DEBUG(\"Using TCP_CONNECTIONTIMEOUT: %u seconds\", sec);\n        setsockopt(state->sockfd, IPPROTO_TCP, TCP_CONNECTIONTIMEOUT, &sec, sizeof(sec));\n        #elif defined(TCP_KEEPINIT)\n        FF_DEBUG(\"Using TCP_KEEPINIT: %u seconds\", sec);\n        setsockopt(state->sockfd, IPPROTO_TCP, TCP_KEEPINIT, &sec, sizeof(sec));\n        #elif defined(TCP_USER_TIMEOUT)\n        FF_DEBUG(\"Using TCP_USER_TIMEOUT: %u milliseconds\", state->timeout);\n        setsockopt(state->sockfd, IPPROTO_TCP, TCP_USER_TIMEOUT, &state->timeout, sizeof(state->timeout));\n        #else\n        FF_DEBUG(\"Current platform does not support TCP connection timeout\");\n        #endif\n    }\n\n    return NULL;\n\nerror:\n    FF_DEBUG(\"Error occurred during initialization\");\n    if (state->addr != NULL)\n    {\n        FF_DEBUG(\"Releasing address information\");\n        freeaddrinfo(state->addr);\n        state->addr = NULL;\n    }\n\n    if (state->sockfd > 0)\n    {\n        FF_DEBUG(\"Closing socket: fd=%d\", state->sockfd);\n        close(state->sockfd);\n        state->sockfd = -1;\n    }\n    return ret;\n}\n\nconst char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, const char* path, const char* headers)\n{\n    FF_DEBUG(\"Preparing to send HTTP request: host=%s, path=%s\", host, path);\n\n    if (state->compression)\n    {\n        FF_DEBUG(\"Compression enabled, checking if zlib is available\");\n\n        #ifdef FF_HAVE_ZLIB\n        const char* zlibError = ffNetworkingLoadZlibLibrary();\n        // Only enable compression if zlib library is successfully loaded\n        if (zlibError == NULL)\n        {\n            FF_DEBUG(\"Successfully loaded zlib library, compression enabled\");\n        } else {\n            FF_DEBUG(\"Failed to load zlib library, compression disabled: %s\", zlibError);\n            state->compression = false;\n        }\n        #else\n        FF_DEBUG(\"zlib not supported at build time, compression disabled\");\n        state->compression = false;\n        #endif\n    }\n    else\n    {\n        FF_DEBUG(\"Compression disabled\");\n    }\n\n    const char* initResult = initNetworkingState(state, host, path, headers);\n    if (initResult != NULL) {\n        FF_DEBUG(\"Initialization failed: %s\", initResult);\n        return initResult;\n    }\n    FF_DEBUG(\"Network state initialization successful\");\n\n    const char* tfoResult = tryNonThreadingFastPath(state);\n    if (tfoResult == NULL) {\n        FF_DEBUG(\"TryNonThreadingFastPath() succeeded or in progress\");\n        return NULL;\n    }\n    FF_DEBUG(\"TryNonThreadingFastPath() failed: %s, trying traditional connection\", tfoResult);\n\n    #ifdef FF_HAVE_THREADS\n    if (instance.config.general.multithreading)\n    {\n        FF_DEBUG(\"Multithreading mode enabled, creating connection thread\");\n        state->thread = ffThreadCreate(connectAndSendThreadMain, state);\n        if (state->thread) {\n            FF_DEBUG(\"Thread creation successful: thread=%p\", (void*)(uintptr_t)state->thread);\n            return NULL;\n        }\n        FF_DEBUG(\"Thread creation failed\");\n    } else {\n        FF_DEBUG(\"Multithreading mode disabled, connecting in main thread\");\n    }\n    #endif\n\n    return connectAndSend(state);\n}\n\nconst char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer)\n{\n    assert(buffer->allocated > 0);\n    FF_DEBUG(\"Preparing to receive HTTP response\");\n    uint32_t timeout = state->timeout;\n\n    #ifdef FF_HAVE_THREADS\n    if (state->thread)\n    {\n        FF_DEBUG(\"Connection thread is running, waiting for it to complete (timeout=%u ms)\", timeout);\n        if (!ffThreadJoin(state->thread, timeout)) {\n            FF_DEBUG(\"Thread join failed or timed out\");\n            return \"ffThreadJoin() failed or timeout\";\n        }\n        FF_DEBUG(\"Thread completed successfully\");\n        state->thread = 0;\n    }\n    #endif\n\n    if(state->sockfd == -1)\n    {\n        FF_DEBUG(\"Invalid socket, HTTP request might have failed\");\n        return \"ffNetworkingSendHttpRequest() failed\";\n    }\n\n    // Set larger initial receive buffer instead of small repeated receives\n    int rcvbuf = 65536; // 64KB\n    setsockopt(state->sockfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));\n\n    #ifdef __APPLE__\n    // poll for the socket to be readable.\n    // Because of the non-blocking connectx() call, the connection might not be established yet\n    FF_DEBUG(\"Using poll() to check if socket is readable\");\n    {\n        int pollRes = poll(&(struct pollfd) {\n            .fd = state->sockfd,\n            .events = POLLIN\n        }, 1, timeout > 0 ? (int) timeout : -1);\n        if (pollRes == 0)\n        {\n            FF_DEBUG(\"poll() timed out after %u ms\", timeout);\n            close(state->sockfd);\n            state->sockfd = -1;\n            return \"poll() timeout\";\n        }\n        else if (pollRes == -1)\n        {\n            FF_DEBUG(\"poll() failed: %s (errno=%d)\", strerror(errno), errno);\n            close(state->sockfd);\n            state->sockfd = -1;\n            return \"poll() failed\";\n        }\n    }\n    FF_DEBUG(\"Socket is readable, proceeding to receive data\");\n    #else\n    if(timeout > 0)\n    {\n        FF_DEBUG(\"Setting receive timeout: %u ms\", timeout);\n        struct timeval timev;\n        timev.tv_sec = timeout / 1000;\n        timev.tv_usec = (__typeof__(timev.tv_usec)) ((timeout % 1000) * 1000); //milliseconds to microseconds\n        setsockopt(state->sockfd, SOL_SOCKET, SO_RCVTIMEO, &timev, sizeof(timev));\n    }\n    #endif\n\n    FF_DEBUG(\"Starting data reception\");\n    FF_MAYBE_UNUSED int recvCount = 0;\n    uint32_t contentLength = 0;\n    uint32_t headerEnd = 0;\n\n    do {\n        FF_DEBUG(\"Data reception loop #%d, current buffer size: %u, available space: %u\",\n                 ++recvCount, buffer->length, ffStrbufGetFree(buffer));\n\n        // We set `Connection: close`, so the server will close the connection when done.\n        // Thus we can use MSG_WAITALL to wait until the buffer is full or the connection is closed.\n        ssize_t received = recv(state->sockfd, buffer->chars + buffer->length, ffStrbufGetFree(buffer), MSG_WAITALL);\n\n        if (received <= 0) {\n            if (received == 0) {\n                FF_DEBUG(\"Connection closed (received=0)\");\n            } else {\n                FF_DEBUG(\"Reception failed: %s (errno=%d)\", strerror(errno), errno);\n            }\n            break;\n        }\n\n        buffer->length += (uint32_t) received;\n        buffer->chars[buffer->length] = '\\0';\n\n        FF_DEBUG(\"Successfully received %zd bytes of data, total: %u bytes\", received, buffer->length);\n\n        // Check if HTTP header end marker is found\n        if (headerEnd == 0) {\n            char* pHeaderEnd = memmem(buffer->chars, buffer->length, \"\\r\\n\\r\\n\", 4);\n            if (pHeaderEnd) {\n                headerEnd = (uint32_t)(pHeaderEnd - buffer->chars);\n                FF_DEBUG(\"Found HTTP header end marker, position: %u\", headerEnd);\n\n                // Check for Content-Length header to pre-allocate enough memory\n                const char* clHeader = strcasestr(buffer->chars, \"Content-Length:\");\n                if (clHeader) {\n                    contentLength = (uint32_t) strtoul(clHeader + 16, NULL, 10);\n                    if (contentLength > 0) {\n                        FF_DEBUG(\"Detected Content-Length: %u, pre-allocating buffer\", contentLength);\n                        // Ensure buffer is large enough, adding header size and some margin\n                        ffStrbufEnsureFree(buffer, contentLength + 16);\n                        FF_DEBUG(\"Extended receive buffer to %u bytes\", buffer->allocated);\n                    }\n                }\n            }\n        }\n    } while (ffStrbufGetFree(buffer) > 0);\n\n    FF_DEBUG(\"Closing socket: fd=%d\", state->sockfd);\n    close(state->sockfd);\n    state->sockfd = -1;\n\n    if (buffer->length == 0) {\n        FF_DEBUG(\"Server response is empty\");\n        return \"Empty server response received\";\n    }\n\n    if (headerEnd == 0) {\n        FF_DEBUG(\"No HTTP header end marker found\");\n        return \"No HTTP header end found\";\n    }\n    if (contentLength > 0 && buffer->length != contentLength + headerEnd + 4) {\n        FF_DEBUG(\"Received content length mismatches: %u != %u\", buffer->length, contentLength + headerEnd + 4);\n        return \"Content length mismatch\";\n    }\n\n    if (ffStrbufStartsWithS(buffer, \"HTTP/1.0 200 OK\\r\\n\")) {\n        FF_DEBUG(\"Received valid HTTP 200 response, content %u bytes, total %u bytes\", contentLength, buffer->length);\n    } else {\n        FF_DEBUG(\"Invalid response: %.40s...\", buffer->chars);\n        return \"Invalid response\";\n    }\n\n    // If compression was used, try to decompress\n    #ifdef FF_HAVE_ZLIB\n    if (state->compression) {\n        FF_DEBUG(\"Content received, checking if compressed\");\n        if (!ffNetworkingDecompressGzip(buffer, buffer->chars + headerEnd)) {\n            FF_DEBUG(\"Decompression failed or invalid compression format\");\n            return \"Failed to decompress or invalid format\";\n        } else {\n            FF_DEBUG(\"Decompression successful or no decompression needed, total length after decompression: %u bytes\", buffer->length);\n        }\n    }\n    #endif\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/common/impl/networking_windows.c",
    "content": "#include <mswsock.h>\n#include <ws2tcpip.h>\n\n//Must be included after <mswsock.h>\n#include \"fastfetch.h\"\n#include \"common/networking.h\"\n#include \"common/stringUtils.h\"\n#include \"common/debug.h\"\n\nstatic LPFN_CONNECTEX ConnectEx;\n\nstatic const char* initWsaData(WSADATA* wsaData)\n{\n    FF_DEBUG(\"Initializing WinSock\");\n    if(WSAStartup(MAKEWORD(2, 2), wsaData) != 0) {\n        FF_DEBUG(\"WSAStartup() failed\");\n        return \"WSAStartup() failed\";\n    }\n\n    if(LOBYTE(wsaData->wVersion) != 2 || HIBYTE(wsaData->wVersion) != 2) {\n        FF_DEBUG(\"Invalid wsaData version found: %d.%d\", LOBYTE(wsaData->wVersion), HIBYTE(wsaData->wVersion));\n        WSACleanup();\n        return \"Invalid wsaData version found\";\n    }\n\n    //Dummy socket needed for WSAIoctl\n    SOCKET sockfd = WSASocketW(AF_INET, SOCK_STREAM, 0, NULL, 0, 0);\n    if(sockfd == INVALID_SOCKET) {\n        FF_DEBUG(\"WSASocketW(AF_INET, SOCK_STREAM) failed\");\n        WSACleanup();\n        return \"WSASocketW(AF_INET, SOCK_STREAM) failed\";\n    }\n\n    DWORD dwBytes;\n    GUID guid = WSAID_CONNECTEX;\n    if(WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER,\n                &guid, sizeof(guid),\n                &ConnectEx, sizeof(ConnectEx),\n                &dwBytes, NULL, NULL) != 0) {\n        FF_DEBUG(\"WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER) failed\");\n        closesocket(sockfd);\n        WSACleanup();\n        return \"WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER) failed\";\n    }\n\n    closesocket(sockfd);\n    FF_DEBUG(\"WinSock initialized successfully\");\n\n    return NULL;\n}\n\nconst char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, const char* path, const char* headers)\n{\n    FF_DEBUG(\"Preparing to send HTTP request: host=%s, path=%s\", host, path);\n\n    if (state->compression)\n    {\n        #ifdef FF_HAVE_ZLIB\n        const char* zlibError = ffNetworkingLoadZlibLibrary();\n        // Only enable compression if zlib library is successfully loaded\n        if (zlibError == NULL)\n        {\n            FF_DEBUG(\"Successfully loaded zlib library, compression enabled\");\n        } else {\n            FF_DEBUG(\"Failed to load zlib library, compression disabled: %s\", zlibError);\n            state->compression = false;\n        }\n        #else\n        FF_DEBUG(\"zlib not supported at build time, compression disabled\");\n        state->compression = false;\n        #endif\n    }\n    else\n    {\n        FF_DEBUG(\"Compression disabled\");\n    }\n\n    static WSADATA wsaData;\n    if (wsaData.wVersion == 0)\n    {\n        const char* error = initWsaData(&wsaData);\n        if (error != NULL)\n        {\n            wsaData.wVersion = (WORD) -1;\n            FF_DEBUG(\"WinSock initialization failed: %s\", error);\n            return error;\n        }\n    }\n    else if (wsaData.wVersion == (WORD) -1)\n    {\n        FF_DEBUG(\"WinSock initialization previously failed\");\n        return \"initWsaData() failed before\";\n    }\n\n    ADDRINFOW* addr;\n    ADDRINFOW hints = {\n        .ai_flags = AI_NUMERICSERV,\n        .ai_family = state->ipv6 ? AF_INET6 : AF_INET,\n        .ai_socktype = SOCK_STREAM,\n    };\n\n    wchar_t hostW[256];\n    if (!NT_SUCCESS(RtlUTF8ToUnicodeN(hostW, (ULONG) sizeof(hostW), NULL, host, (ULONG) strlen(host) + 1)))\n    {\n        FF_DEBUG(\"Failed to convert host to wide string: %s\", host);\n        return \"Failed to convert host to wide string\";\n    }\n\n    FF_DEBUG(\"Resolving address: %s (%s)\", host, state->ipv6 ? \"IPv6\" : \"IPv4\");\n    if(GetAddrInfoW(hostW, L\"80\", &hints, &addr) != 0)\n    {\n        FF_DEBUG(\"GetAddrInfoW() failed\");\n        return \"GetAddrInfoW() failed\";\n    }\n\n    state->sockfd = WSASocketW(addr->ai_family, addr->ai_socktype, addr->ai_protocol, NULL, 0, 0);\n    if(state->sockfd == INVALID_SOCKET)\n    {\n        FF_DEBUG(\"WSASocketW() failed\");\n        FreeAddrInfoW(addr);\n        return \"WSASocketW() failed\";\n    }\n\n    DWORD flag = 1;\n    #ifdef TCP_NODELAY\n    // Enable TCP_NODELAY to disable Nagle's algorithm\n    if (setsockopt(state->sockfd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(flag)) != 0) {\n        FF_DEBUG(\"Failed to set TCP_NODELAY: %s\", ffDebugWin32Error((DWORD) WSAGetLastError()));\n    } else {\n        FF_DEBUG(\"Successfully disabled Nagle's algorithm\");\n    }\n    #endif\n\n    // Set timeout if needed\n    if (state->timeout > 0) {\n        FF_DEBUG(\"Setting connection timeout: %u ms\", state->timeout);\n        setsockopt(state->sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&state->timeout, sizeof(state->timeout));\n    }\n\n    //ConnectEx requires the socket to be initially bound\n    if((state->ipv6\n        ? bind(state->sockfd, (SOCKADDR *) &(struct sockaddr_in6) {\n            .sin6_family = AF_INET6,\n            .sin6_addr = in6addr_any,\n        }, sizeof(struct sockaddr_in6))\n        : bind(state->sockfd, (SOCKADDR *) &(struct sockaddr_in) {\n            .sin_family = AF_INET,\n            .sin_addr.s_addr = INADDR_ANY,\n        }, sizeof(struct sockaddr_in))) != 0)\n    {\n        FF_DEBUG(\"bind() failed: %s\", ffDebugWin32Error((DWORD) WSAGetLastError()));\n        closesocket(state->sockfd);\n        FreeAddrInfoW(addr);\n        state->sockfd = INVALID_SOCKET;\n        return \"bind() failed\";\n    }\n\n    // Initialize overlapped structure with WSA event for asynchronous I/O\n    state->overlapped = (OVERLAPPED){\n        .hEvent = WSACreateEvent()\n    };\n\n    if (state->overlapped.hEvent == WSA_INVALID_EVENT) {\n        FF_DEBUG(\"WSACreateEvent() failed\");\n        closesocket(state->sockfd);\n        FreeAddrInfoW(addr);\n        state->sockfd = INVALID_SOCKET;\n        return \"WSACreateEvent() failed\";\n    }\n\n    // Build HTTP command\n    ffStrbufInitA(&state->command, 128);\n    ffStrbufAppendS(&state->command, \"GET \");\n    ffStrbufAppendS(&state->command, path);\n    ffStrbufAppendS(&state->command, \" HTTP/1.0\\r\\nHost: \");\n    ffStrbufAppendS(&state->command, host);\n    ffStrbufAppendS(&state->command, \"\\r\\nConnection: close\\r\\n\"); // Explicitly request connection closure\n\n    // Add compression support if enabled\n    if (state->compression) {\n        FF_DEBUG(\"Enabling HTTP content compression\");\n        ffStrbufAppendS(&state->command, \"Accept-Encoding: gzip\\r\\n\");\n    }\n\n    ffStrbufAppendS(&state->command, headers);\n    ffStrbufAppendS(&state->command, \"\\r\\n\");\n\n    #ifdef TCP_FASTOPEN\n    if (state->tfo)\n    {\n        // Set TCP Fast Open\n        flag = 1;\n        if (setsockopt(state->sockfd, IPPROTO_TCP, TCP_FASTOPEN, (char*)&flag, sizeof(flag)) != 0) {\n            FF_DEBUG(\"Failed to set TCP_FASTOPEN option: %s\", ffDebugWin32Error((DWORD) WSAGetLastError()));\n        } else {\n            FF_DEBUG(\"Successfully set TCP_FASTOPEN option\");\n        }\n    }\n    else\n    {\n        FF_DEBUG(\"TCP Fast Open disabled\");\n    }\n    #endif\n\n    FF_DEBUG(\"Using ConnectEx to send %u bytes of data\", state->command.length);\n    DWORD sent = 0;\n    BOOL result = ConnectEx(state->sockfd, addr->ai_addr, (int)addr->ai_addrlen,\n                          state->command.chars, state->command.length, &sent, &state->overlapped);\n\n    FreeAddrInfoW(addr);\n    addr = NULL;\n\n    if(!result)\n    {\n        if (WSAGetLastError() != WSA_IO_PENDING)\n        {\n            FF_DEBUG(\"ConnectEx() failed: %s\", ffDebugWin32Error((DWORD) WSAGetLastError()));\n            WSACloseEvent(state->overlapped.hEvent);\n            closesocket(state->sockfd);\n            state->sockfd = INVALID_SOCKET;\n            ffStrbufDestroy(&state->command);\n            return \"ConnectEx() failed\";\n        }\n        else\n        {\n            FF_DEBUG(\"ConnectEx() pending\");\n        }\n    }\n    else\n    {\n        FF_DEBUG(\"ConnectEx() succeeded, sent %u bytes of data\", (unsigned) sent);\n    }\n\n    // No need to cleanup state fields here since we need them in the receive function\n    return NULL;\n}\n\nconst char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer)\n{\n    assert(buffer->allocated > 0);\n    FF_DEBUG(\"Preparing to receive HTTP response\");\n\n    if (state->sockfd == INVALID_SOCKET)\n    {\n        FF_DEBUG(\"Invalid socket, HTTP request might have failed\");\n        return \"ffNetworkingSendHttpRequest() failed\";\n    }\n\n    uint32_t timeout = state->timeout;\n    if (timeout > 0)\n    {\n        FF_DEBUG(\"WSAWaitForMultipleEvents with timeout: %u ms\", timeout);\n        DWORD result = WSAWaitForMultipleEvents(1, &state->overlapped.hEvent, TRUE, timeout, FALSE);\n        if (result != WSA_WAIT_EVENT_0)\n        {\n            if (result == WSA_WAIT_TIMEOUT) {\n                FF_DEBUG(\"WSAWaitForMultipleEvents timed out\");\n            } else {\n                FF_DEBUG(\"WSAWaitForMultipleEvents failed: %s\", ffDebugWin32Error((DWORD) WSAGetLastError()));\n            }\n            CancelIo((HANDLE) state->sockfd);\n            WSACloseEvent(state->overlapped.hEvent);\n            closesocket(state->sockfd);\n            ffStrbufDestroy(&state->command);\n            return \"WSAWaitForMultipleEvents() failed or timeout\";\n        }\n    }\n\n    DWORD transfer, flags;\n    if (!WSAGetOverlappedResult(state->sockfd, &state->overlapped, &transfer, TRUE, &flags))\n    {\n        FF_DEBUG(\"WSAGetOverlappedResult failed: %s\", ffDebugWin32Error((DWORD) WSAGetLastError()));\n        closesocket(state->sockfd);\n        WSACloseEvent(state->overlapped.hEvent);\n        ffStrbufDestroy(&state->command);\n        return \"WSAGetOverlappedResult() failed\";\n    }\n    FF_DEBUG(\"WSAGetOverlappedResult succeeded, %u bytes sent\", (unsigned) transfer);\n    ffStrbufDestroy(&state->command);\n    WSACloseEvent(state->overlapped.hEvent);\n    state->overlapped.hEvent = NULL;\n\n    if (setsockopt(state->sockfd, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, NULL, 0) != 0)\n    {\n        FF_DEBUG(\"Failed to update connect context: %s\", ffDebugWin32Error((DWORD) WSAGetLastError()));\n        // Not a critical error, continue anyway\n    }\n\n    if(timeout > 0)\n    {\n        FF_DEBUG(\"Setting receive timeout: %u ms\", timeout);\n        setsockopt(state->sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));\n    }\n\n    // Set larger receive buffer for better performance\n    int rcvbuf = 65536; // 64KB\n    if (setsockopt(state->sockfd, SOL_SOCKET, SO_RCVBUF, (const char*)&rcvbuf, sizeof(rcvbuf)))\n    {\n        FF_DEBUG(\"Failed to set SO_RCVBUF: %s\", ffDebugWin32Error((DWORD) WSAGetLastError()));\n        // Not a critical error, continue anyway\n    }\n\n    FF_DEBUG(\"Starting data reception\");\n    FF_MAYBE_UNUSED int recvCount = 0;\n    uint32_t contentLength = 0;\n    uint32_t headerEnd = 0;\n\n    do {\n        FF_DEBUG(\"Data reception loop #%d, current buffer size: %u, available space: %u\",\n                 ++recvCount, buffer->length, ffStrbufGetFree(buffer));\n\n        DWORD received = 0, recvFlags = 0;\n        int recvResult = WSARecv(state->sockfd, &(WSABUF) {\n            .buf = buffer->chars + buffer->length,\n            .len = (ULONG) ffStrbufGetFree(buffer),\n        }, 1, &received, &recvFlags, NULL, NULL);\n\n        if (recvResult == SOCKET_ERROR || received == 0) {\n            if (recvResult == 0 && received == 0) {\n                FF_DEBUG(\"Connection closed (received=0)\");\n            } else {\n                FF_DEBUG(\"Reception failed: %s\", ffDebugWin32Error((DWORD) WSAGetLastError()));\n            }\n            break;\n        }\n\n        buffer->length += (uint32_t) received;\n        buffer->chars[buffer->length] = '\\0';\n\n        FF_DEBUG(\"Successfully received %u bytes of data, total: %u bytes\", (unsigned) received, buffer->length);\n\n        // Check if HTTP header end marker is found\n        if (headerEnd == 0) {\n            char* pHeaderEnd = strstr(buffer->chars, \"\\r\\n\\r\\n\");\n            if (pHeaderEnd) {\n                headerEnd = (uint32_t)(pHeaderEnd - buffer->chars);\n                FF_DEBUG(\"Found HTTP header end marker, position: %u\", headerEnd);\n\n                // Check for Content-Length header to pre-allocate enough memory\n                const char* clHeader = strcasestr(buffer->chars, \"Content-Length:\");\n                if (clHeader) {\n                    contentLength = (uint32_t) strtoul(clHeader + 16, NULL, 10);\n                    if (contentLength > 0) {\n                        FF_DEBUG(\"Detected Content-Length: %u, pre-allocating buffer\", contentLength);\n                        // Ensure buffer is large enough, adding header size and some margin\n                        ffStrbufEnsureFree(buffer, contentLength + 16);\n                        FF_DEBUG(\"Extended receive buffer to %u bytes\", buffer->allocated);\n                    }\n                }\n            }\n        }\n    } while (ffStrbufGetFree(buffer) > 0);\n\n    FF_DEBUG(\"Closing socket: fd=%u\", (unsigned)state->sockfd);\n    closesocket(state->sockfd);\n    state->sockfd = INVALID_SOCKET;\n\n    if (buffer->length == 0) {\n        FF_DEBUG(\"Server response is empty\");\n        return \"Empty server response received\";\n    }\n\n    if (headerEnd == 0) {\n        FF_DEBUG(\"No HTTP header end marker found\");\n        return \"No HTTP header end found\";\n    }\n    if (contentLength > 0 && buffer->length != contentLength + headerEnd + 4) {\n        FF_DEBUG(\"Received content length mismatches: %u != %u\", buffer->length, contentLength + headerEnd + 4);\n        return \"Content length mismatch\";\n    }\n\n    if (ffStrbufStartsWithS(buffer, \"HTTP/1.0 200 OK\\r\\n\")) {\n        FF_DEBUG(\"Received valid HTTP 200 response, content length: %u bytes, total length: %u bytes\",\n                contentLength, buffer->length);\n    } else {\n        FF_DEBUG(\"Invalid response: %.40s...\", buffer->chars);\n        return \"Invalid response\";\n    }\n\n    // If compression was used, try to decompress\n    #ifdef FF_HAVE_ZLIB\n    if (state->compression) {\n        FF_DEBUG(\"Content received, checking if compressed\");\n        if (!ffNetworkingDecompressGzip(buffer, buffer->chars + headerEnd)) {\n            FF_DEBUG(\"Decompression failed or invalid compression format\");\n            return \"Failed to decompress or invalid format\";\n        } else {\n            FF_DEBUG(\"Decompression successful or no decompression needed, total length after decompression: %u bytes\", buffer->length);\n        }\n    }\n    #endif\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/common/impl/option.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/option.h\"\n#include \"common/color.h\"\n#include \"common/stringUtils.h\"\n\n// Return start position of the inner key if the argument key belongs to the module specified, NULL otherwise\nconst char* ffOptionTestPrefix(const char* argumentKey, const char* moduleName)\n{\n    const char* subKey = argumentKey;\n    if(!(subKey[0] == '-' && subKey[1] == '-'))\n        return NULL;\n\n    subKey += 2;\n    uint32_t moduleNameLen = (uint32_t)strlen(moduleName);\n    if(strncasecmp(subKey, moduleName, moduleNameLen) != 0)\n        return NULL;\n\n    subKey += moduleNameLen;\n\n    if(subKey[0] == '\\0')\n        return subKey;\n\n    if(subKey[0] != '-')\n        return NULL;\n\n    subKey += 1;\n\n    return subKey;\n}\n\nvoid ffOptionParseString(const char* argumentKey, const char* value, FFstrbuf* buffer)\n{\n    if(value == NULL)\n    {\n        fprintf(stderr, \"Error: usage: %s <str>\\n\", argumentKey);\n        exit(477);\n    }\n\n    ffStrbufSetS(buffer, value);\n}\n\nuint32_t ffOptionParseUInt32(const char* argumentKey, const char* value)\n{\n    if(value == NULL)\n    {\n        fprintf(stderr, \"Error: usage: %s <num>\\n\", argumentKey);\n        exit(480);\n    }\n\n    char* end;\n    uint32_t num = (uint32_t) strtoul(value, &end, 10);\n    if(*end != '\\0')\n    {\n        fprintf(stderr, \"Error: usage: %s <num>\\n\", argumentKey);\n        exit(479);\n    }\n\n    return num;\n}\n\nint32_t ffOptionParseInt32(const char* argumentKey, const char* value)\n{\n    if(value == NULL)\n    {\n        fprintf(stderr, \"Error: usage: %s <num>\\n\", argumentKey);\n        exit(480);\n    }\n\n    char* end;\n    int32_t num = (int32_t) strtol(value, &end, 10);\n    if(*end != '\\0')\n    {\n        fprintf(stderr, \"Error: usage: %s <num>\\n\", argumentKey);\n        exit(479);\n    }\n\n    return num;\n}\n\nint ffOptionParseEnum(const char* argumentKey, const char* requestedKey, FFKeyValuePair pairs[])\n{\n    if(requestedKey == NULL)\n    {\n        fprintf(stderr, \"Error: usage: %s <value>\\n\", argumentKey);\n        exit(476);\n    }\n\n    for (const FFKeyValuePair* pPair = pairs; pPair->key; ++pPair)\n    {\n        if(ffStrEqualsIgnCase(requestedKey, pPair->key))\n            return pPair->value;\n    }\n\n    fprintf(stderr, \"Error: unknown %s value: %s\\n\", argumentKey, requestedKey);\n    exit(478);\n}\n\nbool ffOptionParseBoolean(const char* str)\n{\n    return (\n        !ffStrSet(str) ||\n        ffStrEqualsIgnCase(str, \"true\") ||\n        ffStrEqualsIgnCase(str, \"yes\")  ||\n        ffStrEqualsIgnCase(str, \"on\")   ||\n        ffStrEqualsIgnCase(str, \"1\")\n    );\n}\n\nvoid ffOptionParseColorNoClear(const char* value, FFstrbuf* buffer)\n{\n    if (!value || value[0] == '\\0') return;\n\n    // If value is already an ANSI escape code, use it\n    if (value[0] == '\\e' && value[1] == '[')\n    {\n        ffStrbufAppendS(buffer, value + 2);\n        ffStrbufTrimRight(buffer, 'm');\n        return;\n    }\n\n    ffStrbufEnsureFree(buffer, 63);\n\n    while(*value != '\\0')\n    {\n        #define FF_APPEND_COLOR_CODE_COND(prefix, code) \\\n            if(ffStrStartsWithIgnCase(value, #prefix)) { ffStrbufAppendS(buffer, code); value += strlen(#prefix); continue; }\n        #define FF_APPEND_COLOR_PROP_COND(prefix, prop) \\\n            if(ffStrStartsWithIgnCase(value, #prefix)) { if (instance.config.display.prop.length) ffStrbufAppend(buffer, &instance.config.display.prop); else ffStrbufAppendS(buffer, FF_COLOR_FG_DEFAULT); value += strlen(#prefix); continue; }\n\n        if (ffCharIsEnglishAlphabet(value[0]))\n        {\n            FF_APPEND_COLOR_CODE_COND(reset_, FF_COLOR_MODE_RESET)\n            else FF_APPEND_COLOR_CODE_COND(bold_, FF_COLOR_MODE_BOLD)\n            else FF_APPEND_COLOR_CODE_COND(bright_, FF_COLOR_MODE_BOLD)\n            else FF_APPEND_COLOR_CODE_COND(dim_, FF_COLOR_MODE_DIM)\n            else FF_APPEND_COLOR_CODE_COND(italic_, FF_COLOR_MODE_ITALIC)\n            else FF_APPEND_COLOR_CODE_COND(underline_, FF_COLOR_MODE_UNDERLINE)\n            else FF_APPEND_COLOR_CODE_COND(blink_, FF_COLOR_MODE_BLINK)\n            else FF_APPEND_COLOR_CODE_COND(inverse_, FF_COLOR_MODE_INVERSE)\n            else FF_APPEND_COLOR_CODE_COND(hidden_, FF_COLOR_MODE_HIDDEN)\n            else FF_APPEND_COLOR_CODE_COND(strike_, FF_COLOR_MODE_STRIKETHROUGH)\n            else FF_APPEND_COLOR_CODE_COND(black, FF_COLOR_FG_BLACK)\n            else FF_APPEND_COLOR_CODE_COND(red, FF_COLOR_FG_RED)\n            else FF_APPEND_COLOR_CODE_COND(green, FF_COLOR_FG_GREEN)\n            else FF_APPEND_COLOR_CODE_COND(yellow, FF_COLOR_FG_YELLOW)\n            else FF_APPEND_COLOR_CODE_COND(blue, FF_COLOR_FG_BLUE)\n            else FF_APPEND_COLOR_CODE_COND(magenta, FF_COLOR_FG_MAGENTA)\n            else FF_APPEND_COLOR_CODE_COND(cyan, FF_COLOR_FG_CYAN)\n            else FF_APPEND_COLOR_CODE_COND(white, FF_COLOR_FG_WHITE)\n            else FF_APPEND_COLOR_CODE_COND(default, FF_COLOR_FG_DEFAULT)\n            else FF_APPEND_COLOR_CODE_COND(light_black, FF_COLOR_FG_LIGHT_BLACK)\n            else FF_APPEND_COLOR_CODE_COND(light_red, FF_COLOR_FG_LIGHT_RED)\n            else FF_APPEND_COLOR_CODE_COND(light_green, FF_COLOR_FG_LIGHT_GREEN)\n            else FF_APPEND_COLOR_CODE_COND(light_yellow, FF_COLOR_FG_LIGHT_YELLOW)\n            else FF_APPEND_COLOR_CODE_COND(light_blue, FF_COLOR_FG_LIGHT_BLUE)\n            else FF_APPEND_COLOR_CODE_COND(light_magenta, FF_COLOR_FG_LIGHT_MAGENTA)\n            else FF_APPEND_COLOR_CODE_COND(light_cyan, FF_COLOR_FG_LIGHT_CYAN)\n            else FF_APPEND_COLOR_CODE_COND(light_white, FF_COLOR_FG_LIGHT_WHITE)\n            else FF_APPEND_COLOR_PROP_COND(keys, colorKeys)\n            else FF_APPEND_COLOR_PROP_COND(title, colorTitle)\n            else FF_APPEND_COLOR_PROP_COND(output, colorOutput)\n            else FF_APPEND_COLOR_PROP_COND(separator, colorSeparator)\n            else\n            {\n                fprintf(stderr, \"Error: invalid color code found: %s\\n\", value);\n                exit(479);\n            }\n        }\n        else if (value[0] == '@')\n        {\n            // Xterm 256 color\n            ++value;\n            char* pend = NULL;\n            uint32_t color = (uint32_t) strtoul(value, &pend, 10);\n            if (pend == value || color > 255) {\n                fprintf(stderr, \"Error: invalid 256 color code found: %s\\n\", value);\n                exit(479);\n            }\n\n            ffStrbufAppendS(buffer, FF_COLOR_FG_256);\n            ffStrbufAppendUInt(buffer, color);\n            value = pend;\n            continue;\n        }\n        else if (value[0] == '#')\n        {\n            // RGB color\n            ++value;\n            char* pend = NULL;\n            uint32_t rgb = (uint32_t) strtoul(value, &pend, 16);\n            if (pend == value) {\n                fprintf(stderr, \"Error: invalid RGB color code found: %s\\n\", value);\n                exit(479);\n            }\n            if (pend - value > 6) {\n                fprintf(stderr, \"Error: RGB color code too long: %s\\n\", value);\n                exit(479);\n            }\n            else if (pend - value == 3) {\n                rgb = ((rgb & 0xF00) >> 8) * 0x110000 +\n                      ((rgb & 0x0F0) >> 4) * 0x001100 +\n                      ((rgb & 0x00F) >> 0) * 0x000011;\n            }\n            else if (pend - value != 6) {\n                fprintf(stderr, \"Error: invalid RGB color code length: %s\\n\", value);\n                exit(479);\n            }\n\n            uint32_t r = rgb >> 16, g = (rgb >> 8) & 0xFF, b = rgb & 0xFF;\n            ffStrbufAppendF(buffer, FF_COLOR_FG_RGB \"%u;%u;%u\", r, g, b);\n            value = pend;\n            continue;\n        }\n        ffStrbufAppendC(buffer, *value);\n        ++value;\n\n        #undef FF_APPEND_COLOR_CODE_COND\n        #undef FF_APPEND_COLOR_PROP_COND\n    }\n}\n"
  },
  {
    "path": "src/common/impl/parsing.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/parsing.h\"\n\n#include <ctype.h>\n\n#ifdef _WIN32\n    #pragma GCC diagnostic push\n    #pragma GCC diagnostic ignored \"-Wformat\"\n#endif\n\nvoid ffParseSemver(FFstrbuf* buffer, const FFstrbuf* major, const FFstrbuf* minor, const FFstrbuf* patch)\n{\n    if(major->length > 0)\n        ffStrbufAppend(buffer, major);\n    else if(minor->length > 0 || patch->length > 0)\n        ffStrbufAppendC(buffer, '1');\n\n    if(minor->length == 0 && patch->length == 0)\n        return;\n\n    ffStrbufAppendC(buffer, '.');\n\n    if(minor->length > 0)\n        ffStrbufAppend(buffer, minor);\n    else if(patch->length > 0)\n        ffStrbufAppendC(buffer, '0');\n\n    if(patch->length == 0)\n        return;\n\n    ffStrbufAppendC(buffer, '.');\n\n    ffStrbufAppend(buffer, patch);\n}\n\nint8_t ffVersionCompare(const FFVersion* version1, const FFVersion* version2)\n{\n    if(version1->major != version2->major)\n        return version1->major > version2->major ? 1 : -1;\n\n    if(version1->minor != version2->minor)\n        return version1->minor > version2->minor ? 1 : -1;\n\n    if(version1->patch != version2->patch)\n        return version1->patch > version2->patch ? 1 : -1;\n\n    return 0;\n}\n\nvoid ffVersionToPretty(const FFVersion* version, FFstrbuf* pretty)\n{\n    if(version->major > 0 || version->minor > 0 || version->patch > 0)\n    {\n        ffStrbufAppendUInt(pretty, version->major);\n    }\n\n    if(version->minor > 0 || version->patch > 0)\n    {\n        ffStrbufAppendC(pretty, '.');\n        ffStrbufAppendUInt(pretty, version->minor);\n    }\n\n    if(version->patch > 0)\n    {\n        ffStrbufAppendC(pretty, '.');\n        ffStrbufAppendUInt(pretty, version->patch);\n    }\n}\n\nvoid ffParseGTK(FFstrbuf* buffer, const FFstrbuf* gtk2, const FFstrbuf* gtk3, const FFstrbuf* gtk4)\n{\n    if(gtk2->length > 0 && gtk3->length > 0 && gtk4->length > 0)\n    {\n        if((ffStrbufIgnCaseEqual(gtk2, gtk3)) && (ffStrbufIgnCaseEqual(gtk2, gtk4)))\n        {\n            ffStrbufAppend(buffer, gtk4);\n            ffStrbufAppendS(buffer, \" [GTK2/3/4]\");\n        }\n        else if(ffStrbufIgnCaseEqual(gtk2, gtk3))\n        {\n            ffStrbufAppend(buffer, gtk3);\n            ffStrbufAppendS(buffer, \" [GTK2/3], \");\n            ffStrbufAppend(buffer, gtk4);\n            ffStrbufAppendS(buffer, \" [GTK4]\");\n        }\n        else if(ffStrbufIgnCaseEqual(gtk3, gtk4))\n        {\n            ffStrbufAppend(buffer, gtk2);\n            ffStrbufAppendS(buffer, \" [GTK2], \");\n            ffStrbufAppend(buffer, gtk4);\n            ffStrbufAppendS(buffer, \" [GTK3/4]\");\n        }\n        else\n        {\n            ffStrbufAppend(buffer, gtk2);\n            ffStrbufAppendS(buffer, \" [GTK2], \");\n            ffStrbufAppend(buffer, gtk3);\n            ffStrbufAppendS(buffer, \" [GTK3], \");\n            ffStrbufAppend(buffer, gtk4);\n            ffStrbufAppendS(buffer, \" [GTK4]\");\n        }\n    }\n    else if(gtk2->length > 0 && gtk3->length > 0)\n    {\n        if(ffStrbufIgnCaseEqual(gtk2, gtk3))\n        {\n            ffStrbufAppend(buffer, gtk3);\n            ffStrbufAppendS(buffer, \" [GTK2/3]\");\n        }\n        else\n        {\n            ffStrbufAppend(buffer, gtk2);\n            ffStrbufAppendS(buffer, \" [GTK2], \");\n            ffStrbufAppend(buffer, gtk3);\n            ffStrbufAppendS(buffer, \" [GTK3]\");\n        }\n    }\n    else if(gtk3->length > 0 && gtk4->length > 0)\n    {\n        if(ffStrbufIgnCaseEqual(gtk3, gtk4))\n        {\n            ffStrbufAppend(buffer, gtk4);\n            ffStrbufAppendS(buffer, \" [GTK3/4]\");\n        }\n        else\n        {\n            ffStrbufAppend(buffer, gtk3);\n            ffStrbufAppendS(buffer, \" [GTK3], \");\n            ffStrbufAppend(buffer, gtk4);\n            ffStrbufAppendS(buffer, \" [GTK4]\");\n        }\n    }\n    else if(gtk2->length > 0)\n    {\n        ffStrbufAppend(buffer, gtk2);\n        ffStrbufAppendS(buffer, \" [GTK2]\");\n    }\n    else if(gtk3->length > 0)\n    {\n        ffStrbufAppend(buffer, gtk3);\n        ffStrbufAppendS(buffer, \" [GTK3]\");\n    }\n    else if(gtk4->length > 0)\n    {\n        ffStrbufAppend(buffer, gtk4);\n        ffStrbufAppendS(buffer, \" [GTK4]\");\n    }\n}\n\n#ifdef _WIN32\n    #pragma GCC diagnostic pop\n#endif\n"
  },
  {
    "path": "src/common/impl/path.c",
    "content": "#include \"common/path.h\"\n#include \"common/io.h\"\n#include \"common/arrayUtils.h\"\n\n#if !_WIN32\nconst char* ffFindExecutableInPath(const char* name, FFstrbuf* result)\n{\n    char* path = getenv(\"PATH\");\n    if(!path)\n        return \"$PATH not set\";\n\n    #ifdef _WIN32\n    const bool appendExe = !ffStrEndsWithIgnCase(name, \".exe\");\n    #endif\n\n    for (char* token = path; *token; path = token + 1)\n    {\n        token = strchr(path,\n            #ifdef _WIN32\n                ';'\n            #else\n                ':'\n            #endif\n        );\n        if (!token) token = path + strlen(path);\n\n        ffStrbufSetNS(result, (uint32_t)(token - path), path);\n        ffStrbufEnsureEndsWithC(result,\n            #ifdef _WIN32\n                '\\\\'\n            #else\n                '/'\n            #endif\n        );\n        ffStrbufAppendS(result, name);\n        #ifdef _WIN32\n        if (appendExe) ffStrbufAppendS(result, \".exe\");\n        if (!ffPathExists(result->chars, FF_PATHTYPE_FILE))\n            continue;\n        #else\n        if (access(result->chars, X_OK) != 0)\n            continue;\n        #endif\n\n        return NULL;\n    }\n    ffStrbufClear(result);\n    return \"Executable not found\";\n}\n#else\n#include <windows.h>\n#include <winioctl.h>\n#include <errno.h>\n#include <stdalign.h>\n\nconst char* ffFindExecutableInPath(const char* name, FFstrbuf* result)\n{\n    char buffer[MAX_PATH + 1];\n    DWORD length = SearchPathA(NULL, name, \".exe\", sizeof(buffer), buffer, NULL);\n    if (length == 0)\n    {\n        ffStrbufClear(result);\n        return \"Executable not found\";\n    }\n    ffStrbufSetS(result, buffer);\n    return NULL;\n}\n\nstatic inline int winerr2Errno(DWORD err)\n{\n    switch (err)\n    {\n        case ERROR_FILE_NOT_FOUND:\n        case ERROR_PATH_NOT_FOUND:\n        case ERROR_INVALID_NAME:\n            return ENOENT;\n        case ERROR_ACCESS_DENIED:\n        case ERROR_SHARING_VIOLATION:\n        case ERROR_LOCK_VIOLATION:\n            return EACCES;\n        case ERROR_BUFFER_OVERFLOW:\n        case ERROR_INSUFFICIENT_BUFFER:\n            return ENAMETOOLONG;\n        case ERROR_INVALID_PARAMETER:\n        case ERROR_NOT_A_REPARSE_POINT:\n            return EINVAL;\n        default:\n            return EIO;\n    }\n}\n\nchar* frealpath(HANDLE hFile, char* resolved_name)\n{\n    if (__builtin_expect(hFile == INVALID_HANDLE_VALUE || !hFile, false))\n    {\n        errno = EINVAL;\n        return NULL;\n    }\n\n    wchar_t resolvedNameW[MAX_PATH + 4]; /* +4 for \"\\\\\\\\?\\\\\" prefix */\n    DWORD lenW = GetFinalPathNameByHandleW(hFile, resolvedNameW, (DWORD)ARRAY_SIZE(resolvedNameW), FILE_NAME_NORMALIZED);\n\n    if (lenW == 0)\n    {\n        errno = winerr2Errno(GetLastError());\n        return NULL;\n    }\n    if (lenW >= ARRAY_SIZE(resolvedNameW))\n    {\n        errno = E2BIG;\n        return NULL;\n    }\n    lenW++; // Include null terminator\n\n    wchar_t* srcW = resolvedNameW;\n    DWORD srcLenW = lenW;\n\n    if (srcLenW >= 8 && wcsncmp(resolvedNameW, L\"\\\\\\\\?\\\\UNC\\\\\", 8) == 0)\n    {\n        /* Convert \"\\\\?\\UNC\\server\\share\" to \"\\\\server\\share\" */\n        srcW += 6;\n        srcLenW -= 6;\n        *srcW = L'\\\\';\n    }\n    else if (srcLenW >= 4 && wcsncmp(resolvedNameW, L\"\\\\\\\\?\\\\\", 4) == 0)\n    {\n        srcW += 4;\n        srcLenW -= 4;\n    }\n\n    if (resolved_name)\n    {\n        ULONG outBytes = 0;\n        if (!NT_SUCCESS(RtlUnicodeToUTF8N(resolved_name, MAX_PATH, &outBytes, srcW, (ULONG)(srcLenW * sizeof(wchar_t)))))\n        {\n            errno = E2BIG;\n            return NULL;\n        }\n\n        if (outBytes > MAX_PATH)\n        {\n            errno = E2BIG;\n            return NULL;\n        }\n\n        return resolved_name;\n    }\n    else\n    {\n        /* UTF-8 worst-case: up to 4 bytes per UTF-16 code unit */\n        char tmp[(MAX_PATH + 4) * 4];\n        ULONG outBytes = 0;\n\n        if (!NT_SUCCESS(RtlUnicodeToUTF8N(tmp, (ULONG)sizeof(tmp), &outBytes, srcW, (ULONG)(srcLenW * sizeof(wchar_t)))))\n        {\n            errno = E2BIG;\n            return NULL;\n        }\n\n        resolved_name = (char*)malloc(outBytes);\n        if (!resolved_name)\n        {\n            errno = ENOMEM;\n            return NULL;\n        }\n\n        memcpy(resolved_name, tmp, outBytes);\n        return resolved_name;\n    }\n\n    return resolved_name;\n}\n\nchar* realpath(const char* __restrict file_name, char* __restrict resolved_name)\n{\n    if (!file_name)\n    {\n        errno = EINVAL;\n        return NULL;\n    }\n\n    wchar_t fileNameW[MAX_PATH];\n    ULONG lenBytes = 0;\n\n    if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG)sizeof(fileNameW), &lenBytes, file_name, (ULONG)strlen(file_name) + 1)))\n    {\n        errno = EINVAL;\n        return NULL;\n    }\n\n    FF_AUTO_CLOSE_FD HANDLE hFile = CreateFileW(\n        fileNameW,\n        0,\n        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n        NULL,\n        OPEN_EXISTING,\n        FILE_FLAG_BACKUP_SEMANTICS,\n        NULL);\n\n    if (hFile == INVALID_HANDLE_VALUE)\n    {\n        errno = winerr2Errno(GetLastError());\n        return NULL;\n    }\n\n    return frealpath(hFile, resolved_name);\n}\n\nssize_t freadlink(HANDLE hFile, char* buf, size_t bufsiz)\n{\n    if (__builtin_expect(hFile == INVALID_HANDLE_VALUE || !buf || bufsiz == 0, false))\n    {\n        errno = EINVAL;\n        return -1;\n    }\n\n    alignas(REPARSE_DATA_BUFFER) BYTE reparseBuf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];\n    DWORD bytesReturned = 0;\n    if (!DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, NULL, 0, reparseBuf, (DWORD) sizeof(reparseBuf), &bytesReturned, NULL))\n    {\n        errno = winerr2Errno(GetLastError());\n        return -1;\n    }\n\n    REPARSE_DATA_BUFFER* rp = (REPARSE_DATA_BUFFER*) reparseBuf;\n    const wchar_t* targetW = NULL;\n    USHORT targetBytes = 0;\n\n    if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK)\n    {\n        if (rp->SymbolicLinkReparseBuffer.PrintNameLength > 0)\n        {\n            targetW = rp->SymbolicLinkReparseBuffer.PathBuffer +\n                (rp->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(wchar_t));\n            targetBytes = rp->SymbolicLinkReparseBuffer.PrintNameLength;\n        }\n        else\n        {\n            targetW = rp->SymbolicLinkReparseBuffer.PathBuffer +\n                (rp->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(wchar_t));\n            targetBytes = rp->SymbolicLinkReparseBuffer.SubstituteNameLength;\n\n            if (targetBytes >= 8 &&\n                wcsncmp(targetW, L\"\\\\??\\\\\", 4) == 0)\n            {\n                targetW += 4;\n                targetBytes -= 8;\n            }\n        }\n    }\n    else if (rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)\n    {\n        if (rp->MountPointReparseBuffer.PrintNameLength > 0)\n        {\n            targetW = rp->MountPointReparseBuffer.PathBuffer +\n                (rp->MountPointReparseBuffer.PrintNameOffset / sizeof(wchar_t));\n            targetBytes = rp->MountPointReparseBuffer.PrintNameLength;\n        }\n        else\n        {\n            targetW = rp->MountPointReparseBuffer.PathBuffer +\n                (rp->MountPointReparseBuffer.SubstituteNameOffset / sizeof(wchar_t));\n            targetBytes = rp->MountPointReparseBuffer.SubstituteNameLength;\n\n            if (targetBytes >= 8 &&\n                wcsncmp(targetW, L\"\\\\??\\\\\", 4) == 0)\n            {\n                targetW += 4;\n                targetBytes -= 8;\n            }\n        }\n    }\n    else\n    {\n        errno = EINVAL;\n        return -1;\n    }\n\n    ULONG outBytes = 0;\n    if (!NT_SUCCESS(RtlUnicodeToUTF8N(buf, (ULONG) bufsiz, &outBytes, targetW, targetBytes)))\n    {\n        errno = E2BIG;\n        return -1;\n    }\n\n    // Not null-terminated\n    return (ssize_t) outBytes;\n}\n\nssize_t readlink(const char* path, char* buf, size_t bufsiz)\n{\n    if (!path || !buf || bufsiz == 0)\n    {\n        errno = EINVAL;\n        return -1;\n    }\n\n    wchar_t pathW[MAX_PATH];\n    ULONG pathWBytes = 0;\n    if (!NT_SUCCESS(RtlUTF8ToUnicodeN(pathW, (ULONG) sizeof(pathW), &pathWBytes, path, (ULONG) strlen(path) + 1)))\n    {\n        errno = EINVAL;\n        return -1;\n    }\n\n    FF_AUTO_CLOSE_FD HANDLE hFile = CreateFileW(\n        pathW,\n        0,\n        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n        NULL,\n        OPEN_EXISTING,\n        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,\n        NULL\n    );\n\n    if (hFile == INVALID_HANDLE_VALUE)\n    {\n        errno = winerr2Errno(GetLastError());\n        return -1;\n    }\n\n    return freadlink(hFile, buf, bufsiz);\n}\n#endif\n"
  },
  {
    "path": "src/common/impl/percent.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/percent.h\"\n#include \"common/color.h\"\n#include \"common/option.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/textModifier.h\"\n#include \"common/stringUtils.h\"\n\nstatic void appendOutputColor(FFstrbuf* buffer, const FFModuleArgs* module)\n{\n    if (module->outputColor.length)\n        ffStrbufAppendF(buffer, \"\\e[%sm\", module->outputColor.chars);\n    else if (instance.config.display.colorOutput.length)\n        ffStrbufAppendF(buffer, \"\\e[%sm\", instance.config.display.colorOutput.chars);\n}\n\nconst char* ffPercentParseTypeJsonConfig(yyjson_val* jsonVal, FFPercentageTypeFlags* result)\n{\n    if (yyjson_is_uint(jsonVal))\n    {\n        *result = (FFPercentageTypeFlags) yyjson_get_uint(jsonVal);\n        return NULL;\n    }\n    if (yyjson_is_arr(jsonVal))\n    {\n        FFPercentageTypeFlags flags = 0;\n\n        yyjson_val* item;\n        size_t idx, max;\n        yyjson_arr_foreach(jsonVal, idx, max, item)\n        {\n            const char* flag = yyjson_get_str(item);\n            if (!flag)\n                return \"Error: percent.type: invalid flag string\";\n            if (ffStrEqualsIgnCase(flag, \"num\"))\n                flags |= FF_PERCENTAGE_TYPE_NUM_BIT;\n            else if (ffStrEqualsIgnCase(flag, \"bar\"))\n                flags |= FF_PERCENTAGE_TYPE_BAR_BIT;\n            else if (ffStrEqualsIgnCase(flag, \"hide-others\"))\n                flags |= FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT;\n            else if (ffStrEqualsIgnCase(flag, \"num-color\"))\n                flags |= FF_PERCENTAGE_TYPE_NUM_COLOR_BIT;\n            else if (ffStrEqualsIgnCase(flag, \"bar-monochrome\"))\n                flags |= FF_PERCENTAGE_TYPE_BAR_MONOCHROME_BIT;\n            else\n                return \"Error: percent.type: unknown flag string\";\n        }\n\n        *result = flags;\n        return NULL;\n    }\n\n    return \"Error: usage: percent.type must be a number or an array of strings\";\n}\n\nvoid ffPercentAppendBar(FFstrbuf* buffer, double percent, FFPercentageModuleConfig config, const FFModuleArgs* module)\n{\n    uint8_t green = config.green, yellow = config.yellow;\n    assert(green <= 100 && yellow <= 100);\n\n    const FFOptionsDisplay* options = &instance.config.display;\n\n    const bool borderAsValue = options->barBorderLeftElapsed.length && options->barBorderRightElapsed.length;\n\n    uint8_t blocksPercent = (uint8_t) (percent / 100.0 * options->barWidth + 0.5);\n    assert(blocksPercent <= options->barWidth);\n\n    if(!borderAsValue && options->barBorderLeft.length)\n    {\n        if(!options->pipe && options->barColorBorder.length > 0)\n            ffStrbufAppendF(buffer, \"\\e[%sm\", options->barColorBorder.chars);\n        ffStrbufAppend(buffer, &options->barBorderLeft);\n    }\n\n    if (percent == -DBL_MAX)\n    {\n        // Use total color for simplification\n        if(!options->pipe && options->barColorTotal.length > 0)\n            ffStrbufAppendS(buffer, \"\\e[\" FF_COLOR_FG_LIGHT_BLACK \"m\");\n\n        for (uint8_t i = 0; i < options->barWidth; ++i)\n        {\n            ffStrbufAppend(buffer, borderAsValue && i == 0\n                ? &options->barBorderLeft\n                : borderAsValue && i == options->barWidth - 1\n                    ? &options->barBorderRight\n                    : &options->barCharTotal);\n        }\n    }\n    else\n    {\n        const char* colorGreen = options->percentColorGreen.chars;\n        const char* colorYellow = options->percentColorYellow.chars;\n        const char* colorRed = options->percentColorRed.chars;\n\n        FFPercentageTypeFlags percentType = config.type == 0 ? options->percentType : config.type;\n\n        bool autoColorElapsed = ffStrbufIgnCaseEqualS(&options->barColorElapsed, \"auto\");\n\n        bool monochrome = (percentType & FF_PERCENTAGE_TYPE_BAR_MONOCHROME_BIT) || !autoColorElapsed;\n        if (!options->pipe && options->barColorElapsed.length > 0 && monochrome)\n        {\n            const char* color = NULL;\n            if (!autoColorElapsed)\n                color = options->barColorElapsed.chars;\n            else if (green <= yellow)\n            {\n                if (percent < green) color = colorGreen;\n                else if (percent < yellow) color = colorYellow;\n                else color = colorRed;\n            }\n            else\n            {\n                if (percent < yellow) color = colorRed;\n                else if (percent < green) color = colorYellow;\n                else color = colorGreen;\n            }\n            ffStrbufAppendF(buffer, \"\\e[%sm\", color);\n        }\n        for (uint8_t i = 0; i < blocksPercent; ++i)\n        {\n            if (!options->pipe && options->barColorElapsed.length > 0 && !monochrome)\n            {\n                uint32_t section1Begin = (uint32_t) ((green <= yellow ? green : yellow) / 100.0 * options->barWidth + 0.5);\n                uint32_t section2Begin = (uint32_t) ((green > yellow ? green : yellow) / 100.0 * options->barWidth + 0.5);\n                if (i == section2Begin)\n                    ffStrbufAppendF(buffer, \"\\e[%sm\", (green > yellow ? colorGreen : colorRed));\n                else if (i == section1Begin)\n                    ffStrbufAppendF(buffer, \"\\e[%sm\", colorYellow);\n                else if (i == 0)\n                    ffStrbufAppendF(buffer, \"\\e[%sm\", (green <= yellow ? colorGreen : colorRed));\n            }\n            ffStrbufAppend(buffer, borderAsValue && i == 0\n                ? &options->barBorderLeftElapsed\n                : borderAsValue && i == options->barWidth - 1\n                    ? &options->barBorderRightElapsed\n                    : &options->barCharElapsed);\n        }\n\n        if (blocksPercent < options->barWidth)\n        {\n            if(!options->pipe && options->barColorTotal.length > 0)\n                ffStrbufAppendF(buffer, \"\\e[%sm\", options->barColorTotal.chars);\n            for (uint8_t i = blocksPercent; i < options->barWidth; ++i)\n            {\n                ffStrbufAppend(buffer, borderAsValue && i == 0\n                    ? &options->barBorderLeft\n                    : borderAsValue && i == options->barWidth - 1\n                        ? &options->barBorderRight\n                        : &options->barCharTotal);\n            }\n        }\n    }\n\n    if(!borderAsValue && options->barBorderRight.length)\n    {\n        if(!options->pipe && options->barColorBorder.length > 0)\n            ffStrbufAppendF(buffer, \"\\e[%sm\", options->barColorBorder.chars);\n        ffStrbufAppend(buffer, &options->barBorderRight);\n    }\n\n    if(!options->pipe && (options->barColorElapsed.length > 0 || options->barColorTotal.length > 0 || options->barColorBorder.length > 0))\n    {\n        ffStrbufAppendS(buffer, FASTFETCH_TEXT_MODIFIER_RESET);\n        appendOutputColor(buffer, module);\n    }\n}\n\nvoid ffPercentAppendNum(FFstrbuf* buffer, double percent, FFPercentageModuleConfig config, bool parentheses, const FFModuleArgs* module)\n{\n    uint8_t green = config.green, yellow = config.yellow;\n    assert(green <= 100 && yellow <= 100);\n\n    const FFOptionsDisplay* options = &instance.config.display;\n    FFPercentageTypeFlags percentType = config.type == 0 ? options->percentType : config.type;\n\n    bool colored = !!(percentType & FF_PERCENTAGE_TYPE_NUM_COLOR_BIT);\n\n    if (parentheses)\n        ffStrbufAppendC(buffer, '(');\n\n    if (colored && !options->pipe)\n    {\n        const char* colorGreen = options->percentColorGreen.chars;\n        const char* colorYellow = options->percentColorYellow.chars;\n        const char* colorRed = options->percentColorRed.chars;\n\n        if(percent == -DBL_MAX)\n            ffStrbufAppendS(buffer, \"\\e[\" FF_COLOR_FG_LIGHT_BLACK \"m\");\n        else if(green <= yellow)\n        {\n            if (percent > yellow)\n                ffStrbufAppendF(buffer, \"\\e[%sm\", colorRed);\n            else if (percent > green)\n                ffStrbufAppendF(buffer, \"\\e[%sm\", colorYellow);\n            else\n                ffStrbufAppendF(buffer, \"\\e[%sm\", colorGreen);\n        }\n        else\n        {\n            if (percent < yellow)\n                ffStrbufAppendF(buffer, \"\\e[%sm\", colorRed);\n            else if (percent < green)\n                ffStrbufAppendF(buffer, \"\\e[%sm\", colorYellow);\n            else\n                ffStrbufAppendF(buffer, \"\\e[%sm\", colorGreen);\n        }\n    }\n    ffStrbufAppendF(buffer, \"%*.*f%s%%\", options->percentWidth, options->percentNdigits, percent,\n        options->percentSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_ALWAYS ? \" \" : \"\");\n\n    if (colored && !options->pipe)\n    {\n        ffStrbufAppendS(buffer, FASTFETCH_TEXT_MODIFIER_RESET);\n        appendOutputColor(buffer, module);\n    }\n\n    if (parentheses)\n        ffStrbufAppendC(buffer, ')');\n}\n\nbool ffPercentParseCommandOptions(const char* key, const char* subkey, const char* value, FFPercentageModuleConfig* config)\n{\n    if (!ffStrStartsWithIgnCase(subkey, \"percent-\"))\n        return false;\n\n    subkey += strlen(\"percent-\");\n\n    if (ffStrEqualsIgnCase(subkey, \"green\"))\n    {\n        uint32_t num = ffOptionParseUInt32(key, value);\n        if (num > 100)\n        {\n            fprintf(stderr, \"Error: usage: %s must be between 0 and 100\\n\", key);\n            exit(480);\n        }\n        config->green = (uint8_t) num;\n        return true;\n    }\n\n    if (ffStrEqualsIgnCase(subkey, \"yellow\"))\n    {\n        uint32_t num = ffOptionParseUInt32(key, value);\n        if (num > 100)\n        {\n            fprintf(stderr, \"Error: usage: %s must be between 0 and 100\\n\", key);\n            exit(480);\n        }\n        config->yellow = (uint8_t) num;\n        return true;\n    }\n\n    if (ffStrEqualsIgnCase(subkey, \"type\"))\n    {\n        config->type = (FFPercentageTypeFlags) ffOptionParseUInt32(key, value);\n        return true;\n    }\n\n    return false;\n}\n\nbool ffPercentParseJsonObject(yyjson_val* key, yyjson_val* value, FFPercentageModuleConfig* config)\n{\n    assert(key);\n\n    if (!unsafe_yyjson_equals_str(key, \"percent\"))\n        return false;\n\n    if (!yyjson_is_obj(value))\n    {\n        fprintf(stderr, \"Error: usage: %s must be an object\\n\", unsafe_yyjson_get_str(key));\n        exit(480);\n    }\n\n    yyjson_val* greenVal = yyjson_obj_get(value, \"green\");\n    if (greenVal)\n    {\n        int num = yyjson_get_int(greenVal);\n        if (num < 0 || num > 100)\n        {\n            fputs(\"Error: usage: percent.green must be between 0 and 100\\n\", stderr);\n            exit(480);\n        }\n        config->green = (uint8_t) num;\n    }\n\n    yyjson_val* yellowVal = yyjson_obj_get(value, \"yellow\");\n    if (yellowVal)\n    {\n        int num = yyjson_get_int(yellowVal);\n        if (num < 0 || num > 100)\n        {\n            fputs(\"Error: usage: percent.yellow must be between 0 and 100\\n\", stderr);\n            exit(480);\n        }\n        config->yellow = (uint8_t) num;\n    }\n\n    yyjson_val* typeVal = yyjson_obj_get(value, \"type\");\n    if (typeVal)\n    {\n        const char* error = ffPercentParseTypeJsonConfig(typeVal, &config->type);\n        if (error)\n        {\n            fputs(error, stderr);\n            exit(480);\n        }\n    }\n\n    return true;\n}\n\nvoid ffPercentGenerateJsonConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, FFPercentageModuleConfig config)\n{\n    yyjson_mut_val* percent = yyjson_mut_obj_add_obj(doc, module, \"percent\");\n    yyjson_mut_obj_add_uint(doc, percent, \"green\", config.green);\n    yyjson_mut_obj_add_uint(doc, percent, \"yellow\", config.yellow);\n    yyjson_mut_obj_add_uint(doc, percent, \"type\", config.type);\n}\n"
  },
  {
    "path": "src/common/impl/printing.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/printing.h\"\n#include \"common/textModifier.h\"\n#include \"logo/logo.h\"\n\nvoid ffPrintLogoAndKey(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType)\n{\n    ffLogoPrintLine();\n\n    //This is used by --set-keyless, in this case we want neither the module name nor the separator\n    if(moduleName == NULL)\n        return;\n\n    //This is used as a magic value for hiding keys\n    if (!(moduleArgs && ffStrbufEqualS(&moduleArgs->key, \" \")) && instance.config.display.keyType != FF_MODULE_KEY_TYPE_NONE)\n    {\n        ffPrintCharTimes(' ', instance.config.display.keyPaddingLeft);\n\n        if(!instance.config.display.pipe)\n        {\n            fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout);\n            if (instance.config.display.brightColor)\n                fputs(FASTFETCH_TEXT_MODIFIER_BOLT, stdout);\n\n            if(moduleArgs && !(printType & FF_PRINT_TYPE_NO_CUSTOM_KEY_COLOR) && moduleArgs->keyColor.length > 0)\n                ffPrintColor(&moduleArgs->keyColor);\n            else\n                ffPrintColor(&instance.config.display.colorKeys);\n        }\n\n        if (instance.config.display.keyType & FF_MODULE_KEY_TYPE_ICON && moduleArgs && moduleArgs->keyIcon.length > 0)\n            ffStrbufWriteTo(&moduleArgs->keyIcon, stdout);\n\n        if (instance.config.display.keyType & FF_MODULE_KEY_TYPE_STRING)\n        {\n            ffPrintCharTimes(' ', instance.config.display.keyType >> FF_MODULE_KEY_TYPE_SPACE_SHIFT);\n\n            //NULL check is required for modules with custom keys, e.g. disk with the folder path\n            if((printType & FF_PRINT_TYPE_NO_CUSTOM_KEY) || !moduleArgs || moduleArgs->key.length == 0)\n            {\n                fputs(moduleName, stdout);\n\n                if(moduleIndex > 0)\n                    printf(\" %hhu\", moduleIndex);\n            }\n            else\n            {\n                FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();\n                FF_PARSE_FORMAT_STRING_CHECKED(&key, &moduleArgs->key, ((FFformatarg[]) {\n                    FF_ARG(moduleIndex, \"index\"),\n                    FF_ARG(moduleArgs->keyIcon, \"icon\"),\n                }));\n                ffStrbufWriteTo(&key, stdout);\n            }\n        }\n\n        if(!instance.config.display.pipe)\n        {\n            fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout);\n            ffPrintColor(&instance.config.display.colorSeparator);\n        }\n\n        ffStrbufWriteTo(&instance.config.display.keyValueSeparator, stdout);\n\n        if(!instance.config.display.pipe && instance.config.display.colorSeparator.length)\n            fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout);\n\n        if (!(printType & FF_PRINT_TYPE_NO_CUSTOM_KEY_WIDTH))\n        {\n            uint32_t keyWidth = moduleArgs && moduleArgs->keyWidth > 0 ? moduleArgs->keyWidth : instance.config.display.keyWidth;\n            if (keyWidth > 0)\n                printf(\"\\e[%uG\", (unsigned) (keyWidth + instance.state.logoWidth));\n        }\n    }\n\n    if(!instance.config.display.pipe)\n    {\n        fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout);\n        if (moduleArgs && moduleArgs->outputColor.length)\n            ffPrintColor(&moduleArgs->outputColor);\n        else if (instance.config.display.colorOutput.length)\n            ffPrintColor(&instance.config.display.colorOutput);\n    }\n}\n\nvoid ffPrintFormat(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, uint32_t numArgs, const FFformatarg* arguments)\n{\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n    if (moduleArgs)\n        ffParseFormatString(&buffer, &moduleArgs->outputFormat, numArgs, arguments);\n    else\n        ffStrbufAppendS(&buffer, \"unknown\");\n\n    ffPrintLogoAndKey(moduleName, moduleIndex, moduleArgs, printType);\n    ffStrbufPutTo(&buffer, stdout);\n}\n\nvoid ffPrintError(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, const char* message, ...)\n{\n    if(!instance.config.display.showErrors)\n        return;\n\n    ffPrintLogoAndKey(moduleName, moduleIndex, moduleArgs, printType);\n\n    if(!instance.config.display.pipe)\n        fputs(FASTFETCH_TEXT_MODIFIER_ERROR, stdout);\n\n    va_list arguments;\n    va_start(arguments, message);\n    vprintf(message, arguments);\n    va_end(arguments);\n\n    if(!instance.config.display.pipe)\n        fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout);\n\n    putchar('\\n');\n}\n\nvoid ffPrintColor(const FFstrbuf* colorValue)\n{\n    //If the color is not set, this would reset in \\033[m, which resets everything.\n    //So we only print it, if the main color is at least one char.\n    if(colorValue->length == 0)\n        return;\n\n    printf(\"\\e[%sm\", colorValue->chars);\n}\n\nvoid ffPrintCharTimes(char c, uint32_t times)\n{\n    if(times == 0)\n        return;\n\n    if(times == 1)\n    {\n        putchar(c);\n        return;\n    }\n\n    char str[32];\n    memset(str, c, sizeof(str)); //2 instructions when compiling with AVX2 enabled\n    for(uint32_t i = sizeof(str); i <= times; i += (uint32_t)sizeof(str))\n        fwrite(str, 1, sizeof(str), stdout);\n    uint32_t remaining = times % sizeof(str);\n    if(remaining > 0)\n        fwrite(str, 1, remaining, stdout);\n}\n"
  },
  {
    "path": "src/common/impl/processing_linux.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/processing.h\"\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n#include \"common/mallocHelper.h\"\n\n#include <stdlib.h>\n#include <unistd.h>\n#include <signal.h>\n#include <poll.h>\n#include <fcntl.h>\n#include <errno.h>\n#include <sys/wait.h>\n\n#if !(__ANDROID__ || __OpenBSD__)\n    #include <spawn.h>\n#endif\n\n#if defined(__FreeBSD__) || defined(__APPLE__)\n    #include <sys/types.h>\n    #include <sys/user.h>\n    #include <sys/sysctl.h>\n#endif\n#if defined(__APPLE__)\n    #include <libproc.h>\n#elif defined(__sun)\n    #include <procfs.h>\n#elif defined(__OpenBSD__)\n    #include <sys/param.h>\n    #include <sys/sysctl.h>\n    #include <kvm.h>\n#elif defined(__NetBSD__)\n    #include <sys/types.h>\n    #include <sys/sysctl.h>\n#elif defined(__HAIKU__)\n    #include <OS.h>\n    #include <image.h>\n#endif\n\n#ifndef environ\nextern char** environ;\n#endif\n\nenum { FF_PIPE_BUFSIZ = 8192 };\n\nstatic inline int ffPipe2(int* fds, int flags)\n{\n    #ifndef FF_HAVE_PIPE2\n        if(pipe(fds) == -1)\n            return -1;\n        fcntl(fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | flags);\n        fcntl(fds[1], F_SETFL, fcntl(fds[1], F_GETFL) | flags);\n        return 0;\n    #else\n        return pipe2(fds, flags);\n    #endif\n}\n\n\n// Not thread-safe\nconst char* ffProcessSpawn(char* const argv[], bool useStdErr, FFProcessHandle* outHandle)\n{\n    int pipes[2];\n    if(ffPipe2(pipes, O_CLOEXEC) == -1)\n        return \"pipe() failed\";\n\n    pid_t childPid = -1;\n    int nullFile = ffGetNullFD();\n\n    #if !(__ANDROID__ || __OpenBSD__)\n\n    // NetBSD / Darwin: native syscall\n    // Linux (glibc): clone3-execve\n    // FreeBSD: vfork-execve\n    // illumos: vforkx-execve\n    // OpenBSD / Android (bionic): fork-execve\n\n    posix_spawn_file_actions_t file_actions;\n    posix_spawn_file_actions_init(&file_actions);\n    posix_spawn_file_actions_adddup2(&file_actions, pipes[1], useStdErr ? STDERR_FILENO : STDOUT_FILENO);\n    posix_spawn_file_actions_adddup2(&file_actions, nullFile, useStdErr ? STDOUT_FILENO : STDERR_FILENO);\n\n    static char* oldLang = NULL;\n    static int langIndex = -1;\n\n    if (langIndex >= 0)\n    {\n        // Found before\n        if (oldLang) // oldLang was set only if it needed to be changed\n        {\n            if (environ[langIndex] != oldLang)\n            {\n                // environ is changed outside of this function\n                langIndex = -1;\n            }\n            else\n                environ[langIndex] = (char*) \"LANG=C.UTF-8\";\n        }\n    }\n    if (langIndex < 0)\n    {\n        for (int i = 0; environ[i] != NULL; i++)\n        {\n            if (ffStrStartsWith(environ[i], \"LANG=\"))\n            {\n                langIndex = i;\n                const char* langValue = environ[i] + 5; // Skip \"LANG=\"\n                if (ffStrEqualsIgnCase(langValue, \"C\") ||\n                    ffStrStartsWithIgnCase(environ[i], \"C.\") ||\n                    ffStrEqualsIgnCase(langValue, \"en_US\") ||\n                    ffStrStartsWithIgnCase(langValue, \"en_US.\"))\n                    break; // No need to change LANG\n                oldLang = environ[i];\n                environ[i] = (char*) \"LANG=C.UTF-8\"; // Set LANG to C.UTF-8 for consistent output\n                break;\n            }\n        }\n    }\n\n    int ret = posix_spawnp(&childPid, argv[0], &file_actions, NULL, argv, environ);\n\n    if (oldLang)\n        environ[langIndex] = oldLang;\n\n    posix_spawn_file_actions_destroy(&file_actions);\n\n    if (ret != 0)\n    {\n        close(pipes[0]);\n        close(pipes[1]);\n        return \"posix_spawnp() failed\";\n    }\n\n    #else\n\n    // https://github.com/termux/termux-packages/issues/25369\n    childPid = fork();\n    if(childPid == -1)\n    {\n        close(pipes[0]);\n        close(pipes[1]);\n        return \"fork() failed\";\n    }\n\n    if(childPid == 0)\n    {\n        // Child process\n        dup2(pipes[1], useStdErr ? STDERR_FILENO : STDOUT_FILENO);\n        dup2(nullFile, useStdErr ? STDOUT_FILENO : STDERR_FILENO);\n        putenv(\"LANG=C.UTF-8\");\n        execvp(argv[0], argv);\n        _exit(127);\n    }\n\n    #endif\n\n    close(pipes[1]);\n    outHandle->pid = childPid;\n    outHandle->pipeRead = pipes[0];\n    return NULL;\n}\n\nconst char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer)\n{\n    assert(handle->pipeRead != -1);\n    assert(handle->pid != -1);\n\n    const int32_t timeout = instance.config.general.processingTimeout;\n    FF_AUTO_CLOSE_FD int childPipeFd = handle->pipeRead;\n    pid_t childPid = handle->pid;\n    handle->pipeRead = -1;\n    handle->pid = -1;\n    char str[FF_PIPE_BUFSIZ];\n\n    for (;;)\n    {\n        if (timeout >= 0)\n        {\n            struct pollfd pollfd = { childPipeFd, POLLIN, 0 };\n            int pollret = poll(&pollfd, 1, timeout);\n            if (pollret == 0)\n            {\n                kill(childPid, SIGTERM);\n                waitpid(childPid, NULL, 0);\n                return \"poll(&pollfd, 1, timeout) timeout (try increasing --processing-timeout)\";\n            }\n            else if (pollret < 0 || (pollfd.revents & POLLERR))\n            {\n                kill(childPid, SIGTERM);\n                waitpid(childPid, NULL, 0);\n                return pollret < 0\n                    ? \"poll(&pollfd, 1, timeout) error: pollret < 0\"\n                    : \"poll(&pollfd, 1, timeout) error: pollfd.revents & POLLERR\";\n            }\n        }\n\n        ssize_t nRead = read(childPipeFd, str, FF_PIPE_BUFSIZ);\n        if (nRead > 0)\n            ffStrbufAppendNS(buffer, (uint32_t) nRead, str);\n        else if (nRead == 0)\n        {\n            int stat_loc = 0;\n            if (childPid > 0 && waitpid(childPid, &stat_loc, 0) == childPid)\n            {\n                if (!WIFEXITED(stat_loc))\n                    return \"child process exited abnormally\";\n                if (WEXITSTATUS(stat_loc) == 127)\n                    return \"command not found\";\n                // We only handle 127 as an error. See `getTerminalVersionUrxvt` in `terminalshell.c`\n                return NULL;\n            }\n            return NULL;\n        }\n        else if (nRead < 0)\n            break;\n    }\n\n    return \"read(childPipeFd, str, FF_PIPE_BUFSIZ) failed\";\n}\n\nvoid ffProcessGetInfoLinux(pid_t pid, FFstrbuf* processName, FFstrbuf* exe, const char** exeName, FFstrbuf* exePath)\n{\n    assert(processName->length > 0);\n    ffStrbufClear(exe);\n    if (exePath) ffStrbufClear(exePath);\n\n    #if defined(__linux__) || defined(__GNU__)\n\n    char filePath[64];\n    snprintf(filePath, sizeof(filePath), \"/proc/%d/cmdline\", (int)pid);\n\n    if(ffReadFileBuffer(filePath, exe))\n    {\n        const char* p = exe->chars;\n        uint32_t len = (uint32_t) strlen(p);\n\n        if (len + 1 < exe->length)\n        {\n            const char* name = memrchr(p, '/', len);\n            if (name) name++; else name = p;\n\n            // For interpreters, try to find the real script path in the arguments\n            if (ffStrStartsWith(name, \"python\")\n                #ifndef __ANDROID__\n                || ffStrEquals(name, \"guile\") // for shepherd\n                #endif\n            )\n            {\n                // `cmdline` always ends with a trailing '\\0', and ffReadFileBuffer appends another \\0\n                // So `exe->chars` is always double '\\0' terminated\n                for (p = p + len + 1; *p && *p == '-'; p += strlen(p) + 1) // Skip arguments\n                    assert(p - exe->chars < exe->allocated);\n                if (*p)\n                {\n                    len = (uint32_t) strlen(p);\n                    memmove(exe->chars, p, len + 1);\n                }\n            }\n        }\n\n        assert(len < exe->allocated);\n        exe->length = len;\n        ffStrbufTrimLeft(exe, '-'); //Login shells start with a dash\n    }\n\n    if (exePath)\n    {\n        snprintf(filePath, sizeof(filePath), \"/proc/%d/exe\", (int)pid);\n        char buf[PATH_MAX];\n        ssize_t length = readlink(filePath, buf, PATH_MAX - 1);\n        if (length > 0) // doesn't contain trailing NUL\n        {\n            buf[length] = '\\0';\n            // When the process is a deleted executable, the resolved path is like `/usr/bin/app (deleted)`\n            // But we can still access the binary via `/proc/pid/exe`. See #2136\n            if (ffPathExists(buf, FF_PATHTYPE_ANY))\n                ffStrbufSetNS(exePath, (uint32_t)length, buf);\n        }\n\n        if (exePath->length == 0)\n            ffStrbufSetS(exePath, filePath);\n    }\n\n    #elif defined(__APPLE__)\n\n    size_t len = 0;\n    int mibs[] = { CTL_KERN, KERN_PROCARGS2, pid };\n    if (sysctl(mibs, ARRAY_SIZE(mibs), NULL, &len, NULL, 0) == 0)\n    {// try get arg0\n        //don't know why if don't let len longer, proArgs2 and len will change during the following sysctl() in old MacOS version.\n        len++;\n        FF_AUTO_FREE char* const procArgs2 = malloc(len);\n        if (sysctl(mibs, ARRAY_SIZE(mibs), procArgs2, &len, NULL, 0) == 0)\n        {\n            // https://gist.github.com/nonowarn/770696#file-getargv-c-L46\n            uint32_t argc = *(uint32_t*) procArgs2;\n            const char* realExePath = procArgs2 + sizeof(argc);\n\n            const char* arg0 = memchr(realExePath, '\\0', len - (size_t) (realExePath - procArgs2));\n            if (exePath)\n                ffStrbufSetNS(exePath, (uint32_t) (arg0 - realExePath), realExePath);\n\n            do arg0++; while (*arg0 == '\\0');\n            assert(arg0 < procArgs2 + len);\n\n            if (argc > 1)\n            {\n                // #977\n                const char* p = strrchr(arg0, '/');\n                if (p)\n                    p++;\n                else\n                    p = arg0;\n                if (ffStrStartsWithIgnCase(p, \"python\")) // /opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/Resources/Python.app/Contents/MacOS/Python /Users/carter/.local/bin/xonsh\n                    arg0 = p + strlen(p) + 1;\n            }\n\n            if (*arg0 == '-') arg0++; // Login shells\n\n            ffStrbufSetS(exe, arg0);\n        }\n    }\n\n    if (exePath || exe->length == 0)\n    {\n        char buf[PROC_PIDPATHINFO_MAXSIZE];\n        int length = proc_pidpath(pid, buf, ARRAY_SIZE(buf));\n        if (length > 0)\n        {\n            if (exe->length == 0)\n                ffStrbufSetNS(exe, (uint32_t) length, buf);\n            if (exePath)\n            {\n                // We don't use exec_path above as exePath because it's a relative path and can be different\n                // from the actual executable being run (for example, when the original file is moved)\n                ffStrbufSetNS(exePath, (uint32_t) length, buf);\n            }\n        }\n    }\n\n    #elif defined(__FreeBSD__) || defined(__NetBSD__)\n\n    size_t size = ARG_MAX;\n    FF_AUTO_FREE char* args = malloc(size);\n\n    static_assert(ARG_MAX > PATH_MAX, \"\");\n\n    if(exePath && sysctl(\n        (int[]){CTL_KERN,\n        #if __FreeBSD__\n            KERN_PROC, KERN_PROC_PATHNAME, pid\n        #else\n            KERN_PROC_ARGS, pid, KERN_PROC_PATHNAME\n        #endif\n        }, 4,\n        args, &size,\n        NULL, 0\n    ) == 0)\n        ffStrbufSetNS(exePath, (uint32_t) (size - 1), args);\n\n    size = ARG_MAX;\n    if(sysctl(\n        (int[]){CTL_KERN,\n            #if __FreeBSD__\n                KERN_PROC, KERN_PROC_ARGS, pid\n            #else\n                KERN_PROC_ARGS, pid, KERN_PROC_ARGV,\n            #endif\n        }, 4,\n        args, &size,\n        NULL, 0\n    ) == 0)\n    {\n        char* arg0 = args;\n        size_t arg0Len = strlen(args);\n        if (size > arg0Len + 1)\n        {\n            char* p = (char*) memrchr(args, '/', arg0Len);\n            if (p)\n                p++;\n            else\n                p = arg0;\n            if (ffStrStartsWith(p, \"python\")) // /usr/local/bin/python3.9 /home/carter/.local/bin/xonsh\n            {\n                arg0 += arg0Len + 1;\n            }\n        }\n        if (arg0[0] == '-') arg0++;\n        ffStrbufSetS(exe, arg0);\n    }\n\n    #elif defined(__sun)\n\n    char filePath[128];\n    snprintf(filePath, sizeof(filePath), \"/proc/%d/psinfo\", (int) pid);\n    psinfo_t proc;\n    if (ffReadFileData(filePath, sizeof(proc), &proc) == sizeof(proc))\n    {\n        const char* args = proc.pr_psargs;\n        if (args[0] == '-') ++args;\n        const char* end = strchr(args, ' ');\n        ffStrbufSetNS(exe, end ? (uint32_t) (end - args) : (uint32_t) strlen(args), args);\n    }\n\n    if (exePath)\n    {\n        snprintf(filePath, sizeof(filePath), \"/proc/%d/path/a.out\", (int) pid);\n        char buf[PATH_MAX];\n        ssize_t length = readlink(filePath, buf, PATH_MAX - 1);\n        if (length > 0) // doesn't contain trailing NUL\n        {\n            buf[length] = '\\0';\n            ffStrbufSetNS(exePath, (uint32_t)length, buf);\n        }\n    }\n\n    #elif defined(__OpenBSD__)\n\n    kvm_t* kd = kvm_open(NULL, NULL, NULL, KVM_NO_FILES, NULL);\n    int count = 0;\n    const struct kinfo_proc* proc = kvm_getprocs(kd, KERN_PROC_PID, pid, sizeof(struct kinfo_proc), &count);\n    if (proc)\n    {\n        char** argv = kvm_getargv(kd, proc, 0);\n        if (argv)\n        {\n            const char* arg0 = argv[0];\n            if (arg0[0] == '-') arg0++;\n            ffStrbufSetS(exe, arg0);\n        }\n    }\n    kvm_close(kd);\n\n    #elif defined(__HAIKU__)\n\n    image_info info;\n    int32 cookie = 0;\n\n    while (get_next_image_info(pid, &cookie, &info) == B_OK)\n    {\n        if (info.type != B_APP_IMAGE) continue;\n        ffStrbufSetS(exe, info.name);\n\n        if (exePath)\n            ffStrbufSet(exePath, exe);\n        break;\n    }\n\n    #endif\n\n    if(exe->length == 0)\n        ffStrbufSet(exe, processName);\n\n    assert(exe->length > 0);\n    uint32_t lastSlashIndex = ffStrbufLastIndexC(exe, '/');\n    if(lastSlashIndex < exe->length)\n        *exeName = exe->chars + lastSlashIndex + 1;\n}\n\nconst char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, int32_t* tty)\n{\n    if (pid <= 0)\n        return \"Invalid pid\";\n\n    #if defined(__linux__) || defined(__GNU__)\n\n    char procFilePath[64];\n    #if __linux__\n    if (ppid || tty)\n    #endif\n    {\n        snprintf(procFilePath, sizeof(procFilePath), \"/proc/%d/stat\", (int)pid);\n        char buf[PROC_FILE_BUFFSIZ];\n        ssize_t nRead = ffReadFileData(procFilePath, sizeof(buf) - 1, buf);\n        if(nRead <= 8)\n            return \"ffReadFileData(/proc/pid/stat, PROC_FILE_BUFFSIZ-1, buf) failed\";\n        buf[nRead] = '\\0'; // pid (comm) state ppid pgrp session tty\n\n        const char* pState = NULL;\n\n        {\n            // comm in `/proc/pid/stat` is not encoded, and may contain ' ', ')' or even `\\n`\n            const char* start = memchr(buf, '(', (size_t) nRead);\n            if (!start)\n                return \"memchr(stat, '(') failed\";\n            start++;\n            const char* end = memrchr(start, ')', (size_t) nRead - (size_t) (start - buf));\n            if (!end)\n                return \"memrchr(stat, ')') failed\";\n            ffStrbufSetNS(name, (uint32_t) (end - start), start);\n            ffStrbufTrimRightSpace(name);\n            if (name->chars[0] == '\\0')\n                return \"process name is empty\";\n            pState = end + 2; // skip \") \"\n        }\n\n        #if !__linux__\n        if (ppid || tty)\n        #endif\n        {\n            int ppid_, tty_;\n            if(sscanf(pState + 2, \"%d %*d %*d %d\", &ppid_, &tty_) < 2)\n                return \"sscanf(stat) failed\";\n\n            if (ppid)\n                *ppid = (pid_t) ppid_;\n            if (tty)\n                *tty = tty_ & 0xFF;\n        }\n    }\n    #if __linux__\n    else\n    {\n        snprintf(procFilePath, sizeof(procFilePath), \"/proc/%d/comm\", (int)pid);\n        ssize_t nRead = ffReadFileBuffer(procFilePath, name);\n        if(nRead <= 0)\n            return \"ffReadFileBuffer(/proc/pid/comm, name) failed\";\n        ffStrbufTrimRightSpace(name);\n    }\n    #endif\n\n    #elif defined(__APPLE__)\n\n    struct kinfo_proc proc;\n    size_t size = sizeof(proc);\n    if(sysctl(\n        (int[]){CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}, 4,\n        &proc, &size,\n        NULL, 0\n    ))\n        return \"sysctl(KERN_PROC_PID) failed\";\n\n    ffStrbufSetS(name, proc.kp_proc.p_comm); //trancated to 16 chars\n    if (ppid)\n        *ppid = (pid_t)proc.kp_eproc.e_ppid;\n    if (tty)\n    {\n        *tty = ((proc.kp_eproc.e_tdev >> 24) & 0xFF) == 0x10\n            ? proc.kp_eproc.e_tdev & 0xFFFFFF\n            : -1;\n    }\n\n    #elif defined(__FreeBSD__)\n\n    #ifdef __DragonFly__\n        #define ki_comm kp_comm\n        #define ki_ppid kp_ppid\n        #define ki_tdev kp_tdev\n        #define ki_flag kp_flags\n    #endif\n\n    struct kinfo_proc proc;\n    size_t size = sizeof(proc);\n    if(sysctl(\n        (int[]){CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}, 4,\n        &proc, &size,\n        NULL, 0\n    ))\n        return \"sysctl(KERN_PROC_PID) failed\";\n\n    ffStrbufSetS(name, proc.ki_comm);\n    if (ppid)\n        *ppid = (pid_t)proc.ki_ppid;\n    if (tty)\n    {\n        if (proc.ki_tdev != NODEV && proc.ki_flag & P_CONTROLT)\n        {\n            const char* ttyName = devname(proc.ki_tdev, S_IFCHR);\n            if (ffStrStartsWith(ttyName, \"pts/\"))\n                *tty = (int32_t) strtol(ttyName + strlen(\"pts/\"), NULL, 10);\n            else\n                *tty = -1;\n        }\n        else\n            *tty = -1;\n    }\n\n    #elif defined(__NetBSD__)\n\n    struct kinfo_proc2 proc;\n    size_t size = sizeof(proc);\n    if(sysctl(\n        (int[]){CTL_KERN, KERN_PROC2, KERN_PROC_PID, pid, sizeof(proc), 1}, 6,\n        &proc, &size,\n        NULL, 0\n    ) != 0)\n        return \"sysctl(KERN_PROC_PID) failed\";\n\n    ffStrbufSetS(name, proc.p_comm);\n    if (ppid)\n        *ppid = (pid_t)proc.p_ppid;\n    if (tty)\n    {\n        if (proc.p_flag & P_CONTROLT)\n        {\n            const char* ttyName = devname(proc.p_tdev, S_IFCHR);\n            if (ffStrStartsWith(ttyName, \"pts/\"))\n                *tty = (int32_t) strtol(ttyName + strlen(\"pts/\"), NULL, 10);\n            else\n                *tty = -1;\n        }\n        else\n            *tty = -1;\n    }\n\n    #elif defined(__sun)\n    char path[128];\n    snprintf(path, sizeof(path), \"/proc/%d/psinfo\", (int) pid);\n    psinfo_t proc;\n    if (ffReadFileData(path, sizeof(proc), &proc) != sizeof(proc))\n        return \"ffReadFileData(psinfo) failed\";\n\n    ffStrbufSetS(name, proc.pr_fname);\n    if (ppid)\n        *ppid = proc.pr_ppid;\n    if (tty)\n        *tty = (int) proc.pr_ttydev;\n\n    #elif defined(__OpenBSD__)\n\n    kvm_t* kd = kvm_open(NULL, NULL, NULL, KVM_NO_FILES, NULL);\n    int count = 0;\n    const struct kinfo_proc* proc = kvm_getprocs(kd, KERN_PROC_PID, pid, sizeof(struct kinfo_proc), &count);\n    if (proc)\n    {\n        ffStrbufSetS(name, proc->p_comm);\n        if (ppid)\n            *ppid = proc->p_ppid;\n        if (tty)\n            *tty = (int) proc->p_tdev;\n    }\n    kvm_close(kd);\n    if (!proc)\n        return \"kvm_getprocs() failed\";\n\n    #elif defined(__HAIKU__)\n\n    team_info info;\n    if (get_team_info(pid, &info) == B_OK)\n    {\n        ffStrbufSetS(name, info.name);\n        if (ppid)\n            *ppid = info.parent;\n    }\n\n    FF_UNUSED(tty);\n\n    #else\n\n    return \"Unsupported platform\";\n\n    #endif\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/common/impl/processing_windows.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/processing.h\"\n#include \"common/io.h\"\n#include \"common/windows/unicode.h\"\n#include \"common/windows/nt.h\"\n\n#include <stdalign.h>\n#include <windows.h>\n#include <ntstatus.h>\n\nenum { FF_PIPE_BUFSIZ = 8192 };\n\nstatic void argvToCmdline(char* const argv[], FFstrbuf* result)\n{\n    // From https://gist.github.com/jin-x/cdd641d98887524b091fb1f82a68717d\n\n    FF_STRBUF_AUTO_DESTROY temp = ffStrbufCreate();\n    for (int i = 0; argv[i] != NULL; i++)\n    {\n        ffStrbufSetS(&temp, argv[i]);\n        // Add slash (\\) before double quotes (\") and duplicate slashes before it\n        for (\n            uint32_t pos = ffStrbufFirstIndexC(&temp, '\"'), cnt;\n            pos != temp.length;\n            pos = ffStrbufNextIndexC(&temp, pos + cnt * 2, '\"')\n        ) {\n            cnt = 1;\n            while (pos > 0 && temp.chars[pos - 1] == '\\\\') { ++cnt, --pos; }\n            ffStrbufInsertNC(&temp, pos, cnt, '\\\\');\n        }\n\n        // Add quotes around string if whitespace chars are present (with slash duplicating at the end of string)\n        if (ffStrbufFirstIndexS(&temp, \" \\t\") != temp.length)\n        {\n            uint32_t pos = temp.length;\n            uint32_t cnt = 0;\n            while (pos > 0 && temp.chars[pos - 1] == '\\\\') { ++cnt, --pos; }\n            if (cnt > 0) ffStrbufAppendNC(&temp, cnt, '\\\\');\n            ffStrbufPrependC(&temp, '\"');\n            ffStrbufAppendC(&temp, '\"');\n        }\n\n        // Add space delimiter\n        if (i > 0) ffStrbufAppendC(result, ' ');\n        ffStrbufAppend(result, &temp);\n        ffStrbufClear(&temp);\n    }\n}\n\nconst char* ffProcessSpawn(char* const argv[], bool useStdErr, FFProcessHandle* outHandle)\n{\n    const int32_t timeout = instance.config.general.processingTimeout;\n\n    wchar_t pipeName[32];\n    static unsigned pidCounter = 0;\n    swprintf(pipeName, ARRAY_SIZE(pipeName), L\"\\\\\\\\.\\\\pipe\\\\FASTFETCH-%u-%u\", instance.state.platform.pid, ++pidCounter);\n\n    FF_AUTO_CLOSE_FD HANDLE hChildPipeRead = CreateNamedPipeW(\n        pipeName,\n        PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE | (timeout < 0 ? 0 : FILE_FLAG_OVERLAPPED),\n        0,\n        1,\n        FF_PIPE_BUFSIZ,\n        FF_PIPE_BUFSIZ,\n        0,\n        NULL\n    );\n    if (hChildPipeRead == INVALID_HANDLE_VALUE)\n        return \"CreateNamedPipeW(L\\\"\\\\\\\\.\\\\pipe\\\\FASTFETCH-$(PID)\\\") failed\";\n\n    HANDLE hChildPipeWrite = CreateFileW(\n        pipeName,\n        GENERIC_WRITE,\n        0,\n        &(SECURITY_ATTRIBUTES){\n            .nLength = sizeof(SECURITY_ATTRIBUTES),\n            .lpSecurityDescriptor = NULL,\n            .bInheritHandle = TRUE,\n        },\n        OPEN_EXISTING,\n        0,\n        NULL\n    );\n    if (hChildPipeWrite == INVALID_HANDLE_VALUE)\n        return \"CreateFileW(L\\\"\\\\\\\\.\\\\pipe\\\\FASTFETCH-$(PID)\\\") failed\";\n\n    PROCESS_INFORMATION piProcInfo = {};\n    STARTUPINFOW siStartInfo = {\n        .cb = sizeof(siStartInfo),\n        .dwFlags = STARTF_USESTDHANDLES,\n    };\n    if (useStdErr)\n    {\n        siStartInfo.hStdOutput = ffGetNullFD();\n        siStartInfo.hStdError = hChildPipeWrite;\n    }\n    else\n    {\n        siStartInfo.hStdOutput = hChildPipeWrite;\n        siStartInfo.hStdError = ffGetNullFD();\n    }\n\n    FF_AUTO_FREE wchar_t* cmdline = NULL;\n    {\n        FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();\n        argvToCmdline(argv, &buf);\n        uint32_t cmdlineBytes = (buf.length + 1) * sizeof(wchar_t);\n        cmdline = malloc(cmdlineBytes);\n        if (!NT_SUCCESS(RtlUTF8ToUnicodeN(cmdline, cmdlineBytes, NULL, buf.chars, buf.length + 1)))\n            return \"RtlUTF8ToUnicodeN() failed\";\n    }\n\n    BOOL success = CreateProcessW(\n        NULL,          // application name\n        cmdline,       // command line\n        NULL,          // process security attributes\n        NULL,          // primary thread security attributes\n        TRUE,          // handles are inherited\n        0,             // creation flags\n        NULL,          // use parent's environment\n        NULL,          // use parent's current directory\n        &siStartInfo,  // STARTUPINFO pointer\n        &piProcInfo    // receives PROCESS_INFORMATION\n    );\n\n    NtClose(hChildPipeWrite);\n    if(!success)\n    {\n        if (GetLastError() == ERROR_FILE_NOT_FOUND)\n            return \"command not found\";\n        return \"CreateProcessW() failed\";\n    }\n\n    NtClose(piProcInfo.hThread); // we don't need the thread handle\n    outHandle->pid   = piProcInfo.hProcess;\n    outHandle->pipeRead  = hChildPipeRead;\n    hChildPipeRead = INVALID_HANDLE_VALUE; // ownership transferred, don't close it\n\n    return NULL;\n}\n\nconst char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer)\n{\n    assert(handle->pipeRead != INVALID_HANDLE_VALUE);\n    assert(handle->pid != INVALID_HANDLE_VALUE);\n\n    int32_t timeout = instance.config.general.processingTimeout;\n    FF_AUTO_CLOSE_FD HANDLE hProcess = handle->pid;\n    FF_AUTO_CLOSE_FD HANDLE hChildPipeRead = handle->pipeRead;\n    FF_AUTO_CLOSE_FD HANDLE hReadEvent = NULL;\n    handle->pid = INVALID_HANDLE_VALUE;\n    handle->pipeRead = INVALID_HANDLE_VALUE;\n\n    if (timeout >= 0 && !NT_SUCCESS(NtCreateEvent(&hReadEvent, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE)))\n        return \"NtCreateEvent() failed\";\n\n    char str[FF_PIPE_BUFSIZ];\n    uint32_t nRead = 0;\n    IO_STATUS_BLOCK iosb = {};\n    do\n    {\n        NTSTATUS status = NtReadFile(\n            hChildPipeRead,\n            hReadEvent,\n            NULL,\n            NULL,\n            &iosb,\n            str,\n            (ULONG) sizeof(str),\n            NULL,\n            NULL\n        );\n        if (status == STATUS_PENDING)\n        {\n            switch (NtWaitForSingleObject(hReadEvent, TRUE, &(LARGE_INTEGER) { .QuadPart = (int64_t) timeout * -10000 }))\n            {\n            case STATUS_WAIT_0:\n                status = iosb.Status;\n                break;\n\n            case STATUS_TIMEOUT:\n                CancelIo(hChildPipeRead);\n                TerminateProcess(hProcess, 1);\n                return \"NtReadFile(hChildPipeRead) timed out\";\n\n            default:\n                CancelIo(hChildPipeRead);\n                TerminateProcess(hProcess, 1);\n                return \"NtWaitForSingleObject(hReadEvent) failed\";\n            }\n        }\n\n        if (status == STATUS_PIPE_BROKEN || status == STATUS_END_OF_FILE)\n            goto exit;\n\n        if (!NT_SUCCESS(status))\n        {\n            CancelIo(hChildPipeRead);\n            TerminateProcess(hProcess, 1);\n            return \"NtReadFile(hChildPipeRead) failed\";\n        }\n\n        nRead = (uint32_t) iosb.Information;\n        ffStrbufAppendNS(buffer, nRead, str);\n    } while (nRead > 0);\n\nexit:\n    {\n        PROCESS_BASIC_INFORMATION info = {};\n        ULONG size;\n        if(NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessBasicInformation, &info, sizeof(info), &size)))\n        {\n            assert(size == sizeof(info));\n            if (info.ExitStatus != STILL_ACTIVE && info.ExitStatus != 0)\n                return \"Child process exited with an error\";\n        }\n        else\n            return \"NtQueryInformationProcess(ProcessBasicInformation) failed\";\n    }\n\n    return NULL;\n}\n\nbool ffProcessGetInfoWindows(uint32_t pid, uint32_t* ppid, FFstrbuf* pname, FFstrbuf* exe, const char** exeName, FFstrbuf* exePath, bool* gui)\n{\n    FF_AUTO_CLOSE_FD HANDLE hProcess = NtCurrentProcess();\n    if(pid != 0)\n    {\n        if (!NT_SUCCESS(NtOpenProcess(&hProcess, PROCESS_QUERY_LIMITED_INFORMATION, &(OBJECT_ATTRIBUTES) {\n            .Length = sizeof(OBJECT_ATTRIBUTES),\n        }, &(CLIENT_ID) { .UniqueProcess = (HANDLE)(uintptr_t) pid })))\n            return false;\n    }\n\n    if(ppid)\n    {\n        PROCESS_BASIC_INFORMATION info = {};\n        ULONG size;\n        if(NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessBasicInformation, &info, sizeof(info), &size)))\n        {\n            assert(size == sizeof(info));\n            *ppid = (uint32_t)info.InheritedFromUniqueProcessId;\n        }\n        else\n            return false;\n    }\n\n    if(exe)\n    {\n        // TODO: It's possible to query the command line with `NtQueryInformationProcess(60/*ProcessCommandLineInformation*/)` since Windows 8.1\n\n        alignas(UNICODE_STRING) uint8_t buffer[4096];\n        ULONG size;\n        if(NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessImageFileNameWin32, &buffer, sizeof(buffer), &size)))\n        {\n            UNICODE_STRING* imagePath = (UNICODE_STRING*)buffer;\n            ffStrbufSetNWS(exe, imagePath->Length / sizeof(wchar_t), imagePath->Buffer);\n\n            if (exePath) ffStrbufSet(exePath, exe);\n\n            if (pname && exeName)\n            {\n                *exeName = exe->chars + ffStrbufLastIndexC(exe, '\\\\') + 1;\n                ffStrbufSetS(pname, *exeName);\n            }\n        }\n        else\n            return false;\n    }\n\n    if (gui)\n    {\n        SECTION_IMAGE_INFORMATION info = {};\n        ULONG size;\n        if(NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessImageInformation, &info, sizeof(info), &size)))\n        {\n            assert(size == sizeof(info));\n            *gui = info.SubSystemType == IMAGE_SUBSYSTEM_WINDOWS_GUI;\n        }\n        else\n            return false;\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "src/common/impl/properties.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/properties.h\"\n#include \"common/io.h\"\n#include \"common/mallocHelper.h\"\n\n#include <stdlib.h>\n#include <ctype.h>\n#ifdef _WIN32\n    #include \"common/windows/getline.h\"\n#endif\n\nbool ffParsePropLinePointer(const char** line, const char* start, FFstrbuf* buffer)\n{\n    if(**line == '\\0')\n        return false;\n\n    //Skip any amount of whitespace at the begin of line\n    while(**line == ' ' || **line == '\\t')\n        ++(*line);\n\n    while(*start != '\\0')\n    {\n        // Any amount of whitespace in the format string matches any amount of whitespace in the line, even none\n        if(*start == ' ' || *start == '\\t')\n        {\n            while(*start == ' ' || *start == '\\t')\n                ++start;\n\n            while(**line == ' ' || **line == '\\t')\n                ++(*line);\n\n            continue;\n        }\n\n        //Line doesn't match start, skip it\n        if(tolower(**line) != tolower(*start) || **line == '\\0')\n            return false;\n\n        //Line and start match, continue testing\n        ++(*line);\n        ++start;\n    }\n\n    char valueEnd = '\\n';\n\n    //Allow faster parsing of XML\n    if(*(*line - 1) == '>')\n        valueEnd = '<';\n\n    //Skip any amount of whitespace at the begin of the value\n    while(**line == ' ' || **line == '\\t')\n        ++(*line);\n\n    //Allow faster parsing of quotet values\n    if(**line == '\"' || **line == '\\'')\n    {\n        valueEnd = **line;\n        ++(*line);\n    }\n\n    //Copy the value to the buffer\n    while(**line != valueEnd && **line != '\\n' && **line != '\\0')\n    {\n        ffStrbufAppendC(buffer, **line);\n        ++(*line);\n    }\n\n    ffStrbufTrimRight(buffer, ' ');\n\n    return true;\n}\n\nbool ffParsePropLines(const char* lines, const char* start, FFstrbuf* buffer)\n{\n    while(!ffParsePropLinePointer(&lines, start, buffer))\n    {\n        while(*lines != '\\0' && *lines != '\\n')\n            ++lines;\n\n        if(*lines == '\\0')\n            return false;\n\n        //Skip '\\n'\n        ++lines;\n    }\n\n    return true;\n}\n\n// The following functions return true if the file was found, independently if start was found\n// Buffers which already contain content are not overwritten\n// The last occurrence of start in the first file will be the one used\n\nbool ffParsePropFileValues(const char* filename, uint32_t numQueries, FFpropquery* queries)\n{\n    FF_AUTO_CLOSE_FILE FILE* file = fopen(filename, \"r\");\n    if (file == NULL)\n        return false;\n\n    bool valueStorage[32];\n    bool* unsetValues = valueStorage;\n\n    if (numQueries > ARRAY_SIZE(valueStorage))\n        unsetValues = malloc(sizeof(bool) * numQueries);\n\n    bool allSet = true;\n    for (uint32_t i = 0; i < numQueries; i++)\n    {\n        unsetValues[i] = queries[i].buffer->length == 0;\n        if (unsetValues[i])\n            allSet = false;\n    }\n\n    if (!allSet)\n    {\n        FF_AUTO_FREE char* line = NULL;\n        size_t len = 0;\n\n        while (getline(&line, &len, file) != -1)\n        {\n            for(uint32_t i = 0; i < numQueries; i++)\n            {\n                if(!unsetValues[i])\n                    continue;\n\n                uint32_t currentLength = queries[i].buffer->length;\n                queries[i].buffer->length = 0;\n                if(!ffParsePropLine(line, queries[i].start, queries[i].buffer))\n                    queries[i].buffer->length = currentLength;\n            }\n        }\n    }\n\n    if(unsetValues != valueStorage)\n        free(unsetValues);\n    return true;\n}\n\nbool ffParsePropFileHomeValues(const char* relativeFile, uint32_t numQueries, FFpropquery* queries)\n{\n    FF_STRBUF_AUTO_DESTROY absolutePath = ffStrbufCreateF(\"%s/%s\", instance.state.platform.homeDir.chars, relativeFile);\n    return ffParsePropFileValues(absolutePath.chars, numQueries, queries);\n}\n\nbool ffParsePropFileListValues(const FFlist* list, const char* relativeFile, uint32_t numQueries, FFpropquery* queries)\n{\n    bool foundAFile = false;\n\n    FF_LIST_FOR_EACH(FFstrbuf, dirPrefix, *list)\n    {\n        const uint32_t dirPrefixLength = dirPrefix->length;\n        ffStrbufAppendS(dirPrefix, relativeFile);\n        if(ffParsePropFileValues(dirPrefix->chars, numQueries, queries))\n            foundAFile = true;\n        ffStrbufSubstrBefore(dirPrefix, dirPrefixLength);\n\n        bool allSet = true;\n        for(uint32_t k = 0; k < numQueries; k++)\n        {\n            if(queries[k].buffer->length == 0)\n            {\n                allSet = false;\n                break;\n            }\n        }\n\n        if(allSet)\n            break;\n    }\n\n    return foundAFile;\n}\n"
  },
  {
    "path": "src/common/impl/settings.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/settings.h\"\n#include \"common/library.h\"\n#include \"common/thread.h\"\n#include \"common/io.h\"\n\n#include <string.h>\n\n#ifdef FF_HAVE_GIO\n#include <gio/gio.h>\n\ntypedef struct GVariantGetters\n{\n    FF_LIBRARY_SYMBOL(g_variant_dup_string)\n    FF_LIBRARY_SYMBOL(g_variant_get_boolean)\n    FF_LIBRARY_SYMBOL(g_variant_get_int32)\n    FF_LIBRARY_SYMBOL(g_variant_unref)\n} GVariantGetters;\n\nstatic FFvariant getGVariantValue(GVariant* variant, FFvarianttype type, const GVariantGetters* variantGetters)\n{\n    FFvariant result;\n\n    if(variant == NULL)\n        result = FF_VARIANT_NULL;\n    else if(type == FF_VARIANT_TYPE_STRING)\n        result = (FFvariant) {.strValue = variantGetters->ffg_variant_dup_string(variant, NULL)}; // Dup string, so that variant itself can be freed\n    else if(type == FF_VARIANT_TYPE_BOOL)\n        result = (FFvariant) {.boolValue = (bool) variantGetters->ffg_variant_get_boolean(variant), .boolValueSet = true};\n    else if(type == FF_VARIANT_TYPE_INT)\n        result = (FFvariant) {.intValue = variantGetters->ffg_variant_get_int32(variant)};\n    else\n        result = FF_VARIANT_NULL;\n\n    if(variant)\n        variantGetters->ffg_variant_unref(variant);\n\n    return result;\n}\n\ntypedef struct GSettingsData\n{\n    FF_LIBRARY_SYMBOL(g_settings_schema_source_lookup)\n    FF_LIBRARY_SYMBOL(g_settings_schema_has_key)\n    FF_LIBRARY_SYMBOL(g_settings_new_full)\n    FF_LIBRARY_SYMBOL(g_settings_get_value)\n    FF_LIBRARY_SYMBOL(g_settings_get_user_value)\n    FF_LIBRARY_SYMBOL(g_settings_get_default_value)\n    FF_LIBRARY_SYMBOL(g_settings_schema_source_get_default)\n    GSettingsSchemaSource* schemaSource;\n    GVariantGetters variantGetters;\n\n    bool inited;\n} GSettingsData;\n\nstatic const GSettingsData* getGSettingsData(void)\n{\n    static GSettingsData data;\n\n    if (!data.inited)\n    {\n        data.inited = true;\n        FF_LIBRARY_LOAD(libgsettings, NULL, \"libgio-2.0\" FF_LIBRARY_EXTENSION, 1);\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data, g_settings_schema_source_lookup, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data, g_settings_schema_has_key, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data, g_settings_new_full, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data, g_settings_get_value, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data, g_settings_get_user_value, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data, g_settings_get_default_value, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data, g_settings_schema_source_get_default, NULL)\n\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data.variantGetters, g_variant_dup_string, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data.variantGetters, g_variant_get_boolean, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data.variantGetters, g_variant_get_int32, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data.variantGetters, g_variant_unref, NULL);\n\n        data.schemaSource = data.ffg_settings_schema_source_get_default();\n        if (data.schemaSource)\n            libgsettings = NULL;\n    }\n    if(!data.schemaSource)\n        return NULL;\n\n    return &data;\n}\n\nFFvariant ffSettingsGetGSettings(const char* schemaName, const char* path, const char* key, FFvarianttype type)\n{\n    const GSettingsData* data = getGSettingsData();\n    if(data == NULL)\n        return FF_VARIANT_NULL;\n\n    GSettingsSchema* schema = data->ffg_settings_schema_source_lookup(data->schemaSource, schemaName, true);\n    if(schema == NULL)\n        return FF_VARIANT_NULL;\n\n    if(data->ffg_settings_schema_has_key(schema, key) == false)\n        return FF_VARIANT_NULL;\n\n    GSettings* settings = data->ffg_settings_new_full(schema, NULL, path);\n    if(settings == NULL)\n        return FF_VARIANT_NULL;\n\n    GVariant* variant = data->ffg_settings_get_value(settings, key);\n    if(variant != NULL)\n        return getGVariantValue(variant, type, &data->variantGetters);\n\n    variant = data->ffg_settings_get_user_value(settings, key);\n    if(variant != NULL)\n        return getGVariantValue(variant, type, &data->variantGetters);\n\n    variant = data->ffg_settings_get_default_value(settings, key);\n    return getGVariantValue(variant, type, &data->variantGetters);\n}\n#else //FF_HAVE_GIO\nFFvariant ffSettingsGetGSettings(const char* schemaName, const char* path, const char* key, FFvarianttype type)\n{\n    FF_UNUSED(schemaName, path, key, type)\n    return FF_VARIANT_NULL;\n}\n#endif //FF_HAVE_GIO\n\n#ifdef FF_HAVE_DCONF\n#include <dconf.h>\n\ntypedef struct DConfData\n{\n    FF_LIBRARY_SYMBOL(dconf_client_read_full)\n    FF_LIBRARY_SYMBOL(dconf_client_new)\n    GVariantGetters variantGetters;\n    DConfClient* client;\n\n    bool inited;\n} DConfData;\n\nstatic const DConfData* getDConfData(void)\n{\n    static DConfData data;\n\n    if (!data.inited)\n    {\n        data.inited = true;\n\n        FF_LIBRARY_LOAD(libdconf, NULL, \"libdconf\" FF_LIBRARY_EXTENSION, 2);\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libdconf, data, dconf_client_read_full, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libdconf, data, dconf_client_new, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libdconf, data.variantGetters, g_variant_dup_string, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libdconf, data.variantGetters, g_variant_get_boolean, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libdconf, data.variantGetters, g_variant_get_int32, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libdconf, data.variantGetters, g_variant_unref, NULL)\n\n        data.client = data.ffdconf_client_new();\n        if (data.client)\n            libdconf = NULL;\n    }\n    if(!data.client)\n        return NULL;\n\n    return &data;\n}\n\nFFvariant ffSettingsGetDConf(const char* key, FFvarianttype type)\n{\n    const DConfData* data = getDConfData();\n    if(data == NULL)\n        return FF_VARIANT_NULL;\n\n    GVariant* variant = data->ffdconf_client_read_full(data->client, key, DCONF_READ_FLAGS_NONE, NULL);\n    if(variant != NULL)\n        return getGVariantValue(variant, type, &data->variantGetters);\n\n    variant = data->ffdconf_client_read_full(data->client, key, DCONF_READ_USER_VALUE, NULL);\n    if(variant != NULL)\n        return getGVariantValue(variant, type, &data->variantGetters);\n\n    variant = data->ffdconf_client_read_full(data->client, key, DCONF_READ_DEFAULT_VALUE, NULL);\n    return getGVariantValue(variant, type, &data->variantGetters);\n}\n#else //FF_HAVE_DCONF\nFFvariant ffSettingsGetDConf(const char* key, FFvarianttype type)\n{\n    FF_UNUSED(key, type)\n    return FF_VARIANT_NULL;\n}\n#endif //FF_HAVE_DCONF\n\nFFvariant ffSettingsGetGnome(const char* dconfKey, const char* gsettingsSchemaName, const char* gsettingsPath, const char* gsettingsKey, FFvarianttype type)\n{\n    FFvariant gsettings = ffSettingsGetGSettings(gsettingsSchemaName, gsettingsPath, gsettingsKey, type);\n\n    if(\n        (type == FF_VARIANT_TYPE_BOOL && gsettings.boolValueSet) ||\n        (type != FF_VARIANT_TYPE_BOOL && gsettings.strValue != NULL)\n    ) return gsettings;\n\n    return ffSettingsGetDConf(dconfKey, type);\n}\n\n#ifdef FF_HAVE_DBUS\n#include \"common/dbus.h\"\n\nFFvariant ffSettingsGetXFConf(const char* channelName, const char* propertyName, FFvarianttype type)\n{\n    FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};\n    if (ffDBusLoadData(DBUS_BUS_SESSION, &dbus) != NULL)\n        return FF_VARIANT_NULL;\n\n    DBusMessage* reply = ffDBusGetMethodReply(&dbus, \"org.xfce.Xfconf\", \"/org/xfce/Xfconf\", \"org.xfce.Xfconf\", \"GetProperty\", channelName, propertyName);\n    if(!reply)\n        return FF_VARIANT_NULL;\n\n    DBusMessageIter rootIterator;\n    if(!dbus.lib->ffdbus_message_iter_init(reply, &rootIterator))\n    {\n        dbus.lib->ffdbus_message_unref(reply);\n        return FF_VARIANT_NULL;\n    }\n\n    if(type == FF_VARIANT_TYPE_INT)\n    {\n        int32_t value;\n        if (ffDBusGetInt(&dbus, &rootIterator, &value))\n        {\n            dbus.lib->ffdbus_message_unref(reply);\n            return (FFvariant) { .intValue = value };\n        }\n        return FF_VARIANT_NULL;\n    }\n\n    if(type == FF_VARIANT_TYPE_STRING)\n    {\n        FFstrbuf value = ffStrbufCreate();\n        if (ffDBusGetString(&dbus, &rootIterator, &value))\n        {\n            dbus.lib->ffdbus_message_unref(reply);\n            return (FFvariant) { .strValue = value.chars }; // Leaks value.chars\n        }\n        return FF_VARIANT_NULL;\n    }\n\n    if(type == FF_VARIANT_TYPE_BOOL)\n    {\n        bool value;\n        if (ffDBusGetBool(&dbus, &rootIterator, &value))\n        {\n            dbus.lib->ffdbus_message_unref(reply);\n            return (FFvariant) { .boolValue = value, .boolValueSet = true };\n        }\n    }\n\n    return FF_VARIANT_NULL;\n}\n\n#define FF_DBUS_ITER_CONTINUE(dbus, iterator) \\\n    { \\\n        if(!(dbus).lib->ffdbus_message_iter_next(iterator)) \\\n            break; \\\n        continue; \\\n    }\n\nFFvariant ffSettingsGetXFConfFirstMatch(const char* channelName, const char* propertyPrefix, FFvarianttype type, void* data, FFTestXfconfPropCallback* cb)\n{\n    FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};\n    if (ffDBusLoadData(DBUS_BUS_SESSION, &dbus) != NULL)\n        return FF_VARIANT_NULL;\n\n    DBusMessage* reply = ffDBusGetMethodReply(&dbus, \"org.xfce.Xfconf\", \"/org/xfce/Xfconf\", \"org.xfce.Xfconf\", \"GetAllProperties\", channelName, propertyPrefix);\n    if(!reply)\n        return FF_VARIANT_NULL;\n\n    DBusMessageIter rootIterator;\n    if(!dbus.lib->ffdbus_message_iter_init(reply, &rootIterator))\n    {\n        dbus.lib->ffdbus_message_unref(reply);\n        return FF_VARIANT_NULL;\n    }\n\n    DBusMessageIter arrayIterator;\n    dbus.lib->ffdbus_message_iter_recurse(&rootIterator, &arrayIterator);\n\n    while(true)\n    {\n        if(dbus.lib->ffdbus_message_iter_get_arg_type(&arrayIterator) != DBUS_TYPE_DICT_ENTRY)\n            FF_DBUS_ITER_CONTINUE(dbus, &arrayIterator)\n\n        DBusMessageIter dictIterator;\n        dbus.lib->ffdbus_message_iter_recurse(&arrayIterator, &dictIterator);\n\n        const char* key;\n        dbus.lib->ffdbus_message_iter_get_basic(&dictIterator, &key);\n\n        if (cb(data, key)) FF_DBUS_ITER_CONTINUE(dbus, &arrayIterator)\n        dbus.lib->ffdbus_message_iter_next(&dictIterator);\n\n        if(type == FF_VARIANT_TYPE_INT)\n        {\n            int32_t value;\n            if (ffDBusGetInt(&dbus, &dictIterator, &value))\n            {\n                dbus.lib->ffdbus_message_unref(reply);\n                return (FFvariant) { .intValue = value };\n            }\n            return FF_VARIANT_NULL;\n        }\n\n        if(type == FF_VARIANT_TYPE_STRING)\n        {\n            FFstrbuf value = ffStrbufCreate();\n            if (ffDBusGetString(&dbus, &dictIterator, &value))\n            {\n                dbus.lib->ffdbus_message_unref(reply);\n                return (FFvariant) { .strValue = value.chars }; // Leaks value.chars\n            }\n            return FF_VARIANT_NULL;\n        }\n\n        if(type == FF_VARIANT_TYPE_BOOL)\n        {\n            bool value;\n            if (ffDBusGetBool(&dbus, &dictIterator, &value))\n            {\n                dbus.lib->ffdbus_message_unref(reply);\n                return (FFvariant) { .boolValue = value, .boolValueSet = true };\n            }\n        }\n\n        return FF_VARIANT_NULL;\n    }\n\n    return FF_VARIANT_NULL;\n}\n#else //FF_HAVE_DBUS\nFFvariant ffSettingsGetXFConf(const char* channelName, const char* propertyName, FFvarianttype type)\n{\n    FF_UNUSED(channelName, propertyName, type)\n    return FF_VARIANT_NULL;\n}\nFFvariant ffSettingsGetXFConfFirstMatch(const char* channelName, const char* propertyPrefix, FFvarianttype type, void* data, FFTestXfconfPropCallback* cb)\n{\n    FF_UNUSED(channelName, propertyPrefix, type, data, cb);\n    return FF_VARIANT_NULL;\n}\n#endif //FF_HAVE_DBUS\n\n#ifdef FF_HAVE_SQLITE3\n#include <sqlite3.h>\n\ntypedef struct SQLiteData\n{\n    FF_LIBRARY_SYMBOL(sqlite3_open_v2)\n    FF_LIBRARY_SYMBOL(sqlite3_prepare_v2)\n    FF_LIBRARY_SYMBOL(sqlite3_step)\n    FF_LIBRARY_SYMBOL(sqlite3_data_count)\n    FF_LIBRARY_SYMBOL(sqlite3_column_int)\n    FF_LIBRARY_SYMBOL(sqlite3_column_text)\n    FF_LIBRARY_SYMBOL(sqlite3_finalize)\n    FF_LIBRARY_SYMBOL(sqlite3_close)\n\n    bool inited;\n} SQLiteData;\n\nstatic const SQLiteData* getSQLiteData(void)\n{\n    static SQLiteData data;\n\n    if (!data.inited)\n    {\n        data.inited = true;\n        FF_LIBRARY_LOAD(libsqlite, NULL, \"libsqlite3\" FF_LIBRARY_EXTENSION, 1);\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libsqlite, data, sqlite3_open_v2, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libsqlite, data, sqlite3_prepare_v2, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libsqlite, data, sqlite3_step, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libsqlite, data, sqlite3_data_count, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libsqlite, data, sqlite3_column_int, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libsqlite, data, sqlite3_column_text, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libsqlite, data, sqlite3_finalize, NULL)\n        FF_LIBRARY_LOAD_SYMBOL_VAR(libsqlite, data, sqlite3_close, NULL)\n        libsqlite = NULL;\n    }\n\n    if (!data.ffsqlite3_close)\n        return NULL;\n\n    return &data;\n}\n\nint ffSettingsGetSQLite3Int(const char* dbPath, const char* query)\n{\n    if(!ffPathExists(dbPath, FF_PATHTYPE_FILE))\n        return 0;\n\n    const SQLiteData* data = getSQLiteData();\n    if(data == NULL)\n        return 0;\n\n    sqlite3* db;\n    if(data->ffsqlite3_open_v2(dbPath, &db, SQLITE_OPEN_READONLY, NULL) != SQLITE_OK)\n        return 0;\n\n    sqlite3_stmt* stmt;\n    if(data->ffsqlite3_prepare_v2(db, query, (int) strlen(query), &stmt, NULL) != SQLITE_OK)\n    {\n        data->ffsqlite3_close(db);\n        return 0;\n    }\n\n    if(data->ffsqlite3_step(stmt) != SQLITE_ROW || data->ffsqlite3_data_count(stmt) < 1)\n    {\n        data->ffsqlite3_finalize(stmt);\n        data->ffsqlite3_close(db);\n        return 0;\n    }\n\n    int result = data->ffsqlite3_column_int(stmt, 0);\n\n    data->ffsqlite3_finalize(stmt);\n    data->ffsqlite3_close(db);\n\n    return result;\n}\n\nbool ffSettingsGetSQLite3String(const char* dbPath, const char* query, FFstrbuf* result)\n{\n    if(!ffPathExists(dbPath, FF_PATHTYPE_FILE))\n        return false;\n\n    const SQLiteData* data = getSQLiteData();\n    if(data == NULL)\n        return false;\n\n    sqlite3* db;\n    if(data->ffsqlite3_open_v2(dbPath, &db, SQLITE_OPEN_READONLY, NULL) != SQLITE_OK)\n        return false;\n\n    sqlite3_stmt* stmt;\n    if(data->ffsqlite3_prepare_v2(db, query, (int) strlen(query), &stmt, NULL) != SQLITE_OK)\n    {\n        data->ffsqlite3_close(db);\n        return false;\n    }\n\n    if(data->ffsqlite3_step(stmt) != SQLITE_ROW || data->ffsqlite3_data_count(stmt) < 1)\n    {\n        data->ffsqlite3_finalize(stmt);\n        data->ffsqlite3_close(db);\n        return false;\n    }\n\n    ffStrbufSetS(result, (const char *) data->ffsqlite3_column_text(stmt, 0));\n\n    data->ffsqlite3_finalize(stmt);\n    data->ffsqlite3_close(db);\n\n    return true;\n}\n#else //FF_HAVE_SQLITE3\nint ffSettingsGetSQLite3Int(const char* dbPath, const char* query)\n{\n    FF_UNUSED(dbPath, query)\n    return 0;\n}\nbool ffSettingsGetSQLite3String(const char* dbPath, const char* query, FFstrbuf* result)\n{\n    FF_UNUSED(dbPath, query, result)\n    return false;\n}\n#endif //FF_HAVE_SQLITE3\n\n#ifdef __ANDROID__\n#include <sys/system_properties.h>\nbool ffSettingsGetAndroidProperty(const char* propName, FFstrbuf* result) {\n    ffStrbufEnsureFree(result, PROP_VALUE_MAX);\n    int len = __system_property_get(propName, result->chars + result->length);\n    if (len <= 0) return false;\n    result->length += (uint32_t) len;\n    result->chars[result->length] = '\\0';\n    return true;\n}\n#elif defined(__FreeBSD__)\n#include <kenv.h>\nbool ffSettingsGetFreeBSDKenv(const char* propName, FFstrbuf* result)\n{\n    //https://wiki.ghostbsd.org/index.php/Kenv\n    ffStrbufEnsureFree(result, KENV_MVALLEN);\n    int len = kenv(KENV_GET, propName, result->chars + result->length, KENV_MVALLEN);\n    if (len <= 1) return false; // number of bytes copied, including NUL terminator\n    result->length += (uint32_t) len - 1;\n    return true;\n}\n#endif\n"
  },
  {
    "path": "src/common/impl/size.c",
    "content": "#include \"common/size.h\"\n\n#include <inttypes.h>\n\nstatic void appendNum(FFstrbuf* result, uint64_t bytes, uint32_t base, const char** prefixes)\n{\n    const FFOptionsDisplay* options = &instance.config.display;\n    double size = (double) bytes;\n    uint8_t counter = 0;\n\n    while(size >= base && counter < options->sizeMaxPrefix && prefixes[counter + 1])\n    {\n        size /= base;\n        counter++;\n    }\n\n    if (counter == 0)\n        ffStrbufAppendUInt(result, bytes);\n    else\n        ffStrbufAppendDouble(result, size, (int8_t) options->sizeNdigits, true);\n    if (options->sizeSpaceBeforeUnit != FF_SPACE_BEFORE_UNIT_NEVER)\n        ffStrbufAppendC(result, ' ');\n    ffStrbufAppendS(result, prefixes[counter]);\n}\n\nvoid ffSizeAppendNum(uint64_t bytes, FFstrbuf* result)\n{\n    const FFOptionsDisplay* options = &instance.config.display;\n    switch (options->sizeBinaryPrefix)\n    {\n        case FF_SIZE_BINARY_PREFIX_TYPE_IEC:\n            appendNum(result, bytes, 1024, (const char*[]) {\"B\", \"KiB\", \"MiB\", \"GiB\", \"TiB\", \"PiB\", \"EiB\", \"ZiB\", \"YiB\", NULL});\n            break;\n        case FF_SIZE_BINARY_PREFIX_TYPE_SI:\n            appendNum(result, bytes, 1000, (const char*[]) {\"B\", \"kB\", \"MB\", \"GB\", \"TB\", \"PB\", \"EB\", \"ZB\", \"YB\", NULL});\n            break;\n        case FF_SIZE_BINARY_PREFIX_TYPE_JEDEC:\n            appendNum(result, bytes, 1024, (const char*[]) {\"B\", \"KB\", \"MB\", \"GB\", \"TB\", NULL});\n            break;\n        default:\n            appendNum(result, bytes, 1024, (const char*[]) {\"B\", NULL});\n            break;\n    }\n}\n"
  },
  {
    "path": "src/common/impl/smbiosHelper.c",
    "content": "#include \"common/smbiosHelper.h\"\n#include \"common/io.h\"\n#include \"common/unused.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/debug.h\"\n\nbool ffIsSmbiosValueSet(FFstrbuf* value)\n{\n    ffStrbufTrimRightSpace(value);\n    return\n        value->length > 0 &&\n        !ffStrbufStartsWithIgnCaseS(value, \"To be filled\") &&\n        !ffStrbufStartsWithIgnCaseS(value, \"To be set\") &&\n        !ffStrbufStartsWithIgnCaseS(value, \"OEM\") &&\n        !ffStrbufStartsWithIgnCaseS(value, \"O.E.M.\") &&\n        !ffStrbufStartsWithIgnCaseS(value, \"System Product\") &&\n        !ffStrbufStartsWithIgnCaseS(value, \"Unknown Product\") &&\n        !ffStrbufIgnCaseEqualS(value, \"None\") &&\n        !ffStrbufIgnCaseEqualS(value, \"System Name\") &&\n        !ffStrbufIgnCaseEqualS(value, \"System Version\") &&\n        !ffStrbufIgnCaseEqualS(value, \"Default string\") &&\n        !ffStrbufIgnCaseEqualS(value, \"Undefined\") &&\n        !ffStrbufIgnCaseEqualS(value, \"Not Specified\") &&\n        !ffStrbufIgnCaseEqualS(value, \"Not Applicable\") &&\n        !ffStrbufIgnCaseEqualS(value, \"Not Defined\") &&\n        !ffStrbufIgnCaseEqualS(value, \"Not Available\") &&\n        !ffStrbufIgnCaseEqualS(value, \"INVALID\") &&\n        !ffStrbufIgnCaseEqualS(value, \"Type1ProductConfigId\") &&\n        !ffStrbufIgnCaseEqualS(value, \"TBD by OEM\") &&\n        !ffStrbufIgnCaseEqualS(value, \"No Enclosure\") &&\n        !ffStrbufIgnCaseEqualS(value, \"Chassis Version\") &&\n        !ffStrbufIgnCaseEqualS(value, \"All Series\") &&\n        !ffStrbufIgnCaseEqualS(value, \"N/A\") &&\n        !ffStrbufIgnCaseEqualS(value, \"Unknown\") &&\n        !ffStrbufIgnCaseEqualS(value, \"Standard\") &&\n        !ffStrbufIgnCaseEqualS(value, \"0x0000\")\n    ;\n}\n\nconst FFSmbiosHeader* ffSmbiosNextEntry(const FFSmbiosHeader* header)\n{\n    const char* p = ((const char*) header) + header->Length;\n    if (*p)\n    {\n        do\n            p += strlen(p) + 1;\n        while (*p);\n    }\n    else // The terminator is always double 0 even if there is no string\n        p ++;\n\n    return (const FFSmbiosHeader*) (p + 1);\n}\n\n#if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__sun) || defined(__HAIKU__) || defined(__OpenBSD__) || defined(__GNU__)\n#include <fcntl.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/mman.h>\n#include <stddef.h>\n\n#ifdef __linux__\n    #include \"common/properties.h\"\n#elif defined(__FreeBSD__)\n    #include \"common/settings.h\"\n    #define loff_t off_t // FreeBSD doesn't have loff_t\n#elif defined(__sun)\n    #define loff_t off_t\n#elif defined(__NetBSD__)\n    #include \"common/sysctl.h\"\n    #define loff_t off_t\n#endif\n\n#ifdef __linux__\nbool ffGetSmbiosValue(const char* devicesPath, const char* classPath, FFstrbuf* buffer)\n{\n    if (ffReadFileBuffer(devicesPath, buffer))\n    {\n        ffStrbufTrimRightSpace(buffer);\n        if(ffIsSmbiosValueSet(buffer))\n            return true;\n    }\n\n    if (ffReadFileBuffer(classPath, buffer))\n    {\n        ffStrbufTrimRightSpace(buffer);\n        if(ffIsSmbiosValueSet(buffer))\n            return true;\n    }\n\n    ffStrbufClear(buffer);\n    return false;\n}\n#endif\n\ntypedef struct FFSmbios20EntryPoint\n{\n    uint8_t AnchorString[4];\n    uint8_t EntryPointStructureChecksum;\n    uint8_t EntryPointLength;\n    uint8_t SmbiosMajorVersion;\n    uint8_t SmbiosMinorVersion;\n    uint16_t MaximumStructureSize;\n    uint8_t EntryPointRevision;\n    uint8_t FormattedArea[5];\n    uint8_t IntermediateAnchorString[5];\n    uint8_t IntermediateChecksum;\n    uint16_t StructureTableLength;\n    uint32_t StructureTableAddress;\n    uint16_t NumberOfSmbiosStructures;\n    uint8_t SmbiosBcdRevision;\n} __attribute__((__packed__)) FFSmbios20EntryPoint;\nstatic_assert(offsetof(FFSmbios20EntryPoint, SmbiosBcdRevision) == 0x1E,\n    \"FFSmbios20EntryPoint: Wrong struct alignment\");\n\ntypedef struct FFSmbios30EntryPoint\n{\n    uint8_t AnchorString[5];\n    uint8_t EntryPointStructureChecksum;\n    uint8_t EntryPointLength;\n    uint8_t SmbiosMajorVersion;\n    uint8_t SmbiosMinorVersion;\n    uint8_t SmbiosDocrev;\n    uint8_t EntryPointRevision;\n    uint8_t Reversed;\n    uint32_t StructureTableMaximumSize;\n    uint64_t StructureTableAddress;\n} __attribute__((__packed__)) FFSmbios30EntryPoint;\n\nstatic_assert(offsetof(FFSmbios30EntryPoint, StructureTableAddress) == 0x10,\n    \"FFSmbios30EntryPoint: Wrong struct alignment\");\n\ntypedef union FFSmbiosEntryPoint\n{\n    FFSmbios20EntryPoint Smbios20;\n    FFSmbios30EntryPoint Smbios30;\n} FFSmbiosEntryPoint;\n\nconst FFSmbiosHeaderTable* ffGetSmbiosHeaderTable()\n{\n    static FFstrbuf buffer;\n    static FFSmbiosHeaderTable table;\n\n    if (buffer.chars == NULL)\n    {\n        FF_DEBUG(\"Initializing SMBIOS buffer\");\n        ffStrbufInit(&buffer);\n        #if !__HAIKU__ && !__OpenBSD__ && !__DragonFly__ && !__GNU__\n        #ifdef __linux__\n        FF_DEBUG(\"Using Linux implementation - trying /sys/firmware/dmi/tables/DMI\");\n        if (!ffAppendFileBuffer(\"/sys/firmware/dmi/tables/DMI\", &buffer))\n        #endif\n        {\n            #if !defined(__sun) && !defined(__NetBSD__)\n            FF_DEBUG(\"Using memory-mapped implementation\");\n            FF_STRBUF_AUTO_DESTROY strEntryAddress = ffStrbufCreate();\n            #ifdef __FreeBSD__\n            FF_DEBUG(\"Using FreeBSD kenv implementation\");\n            if (!ffSettingsGetFreeBSDKenv(\"hint.smbios.0.mem\", &strEntryAddress)) {\n                FF_DEBUG(\"Failed to get SMBIOS address from FreeBSD kenv\");\n                return NULL;\n            }\n            FF_DEBUG(\"Got SMBIOS address from kenv: %s\", strEntryAddress.chars);\n            #elif defined(__linux__)\n            {\n                FF_DEBUG(\"Using Linux EFI systab implementation\");\n                FF_STRBUF_AUTO_DESTROY systab = ffStrbufCreate();\n                if (!ffAppendFileBuffer(\"/sys/firmware/efi/systab\", &systab)) {\n                    FF_DEBUG(\"Failed to read /sys/firmware/efi/systab\");\n                    return NULL;\n                }\n                if (!ffParsePropLines(systab.chars, \"SMBIOS3=\", &strEntryAddress) &&\n                    !ffParsePropLines(systab.chars, \"SMBIOS=\", &strEntryAddress)) {\n                    FF_DEBUG(\"Failed to find SMBIOS entry in systab\");\n                    return NULL;\n                }\n                FF_DEBUG(\"Found SMBIOS entry in systab: %s\", strEntryAddress.chars);\n            }\n            #endif\n\n            loff_t entryAddress = (loff_t) strtol(strEntryAddress.chars, NULL, 16);\n            if (entryAddress == 0) {\n                FF_DEBUG(\"Invalid SMBIOS entry address: 0\");\n                return NULL;\n            }\n            FF_DEBUG(\"Parsed SMBIOS entry address: 0x%lx\", (unsigned long)entryAddress);\n\n            FF_AUTO_CLOSE_FD int fd = open(\"/dev/mem\", O_RDONLY | O_CLOEXEC);\n            if (fd < 0) {\n                FF_DEBUG(\"Failed to open /dev/mem: %s\", strerror(errno));\n                return NULL;\n            }\n            FF_DEBUG(\"/dev/mem opened successfully with fd=%d\", fd);\n\n            FFSmbiosEntryPoint entryPoint;\n            FF_DEBUG(\"Attempting to read %zu bytes from physical address 0x%lx\",\n                sizeof(entryPoint), (unsigned long)entryAddress);\n            if (pread(fd, &entryPoint, sizeof(entryPoint), entryAddress) < 0x10)\n            {\n                FF_DEBUG(\"pread failed, trying mmap\");\n                // `pread /dev/mem` returns EFAULT in FreeBSD\n                // https://stackoverflow.com/questions/69372330/how-to-read-dev-mem-using-read\n                void* p = mmap(NULL, sizeof(entryPoint), PROT_READ, MAP_SHARED, fd, entryAddress);\n                if (p == MAP_FAILED) {\n                    FF_DEBUG(\"mmap failed: %s\", strerror(errno));\n                    return NULL;\n                }\n                memcpy(&entryPoint, p, sizeof(entryPoint));\n                munmap(p, sizeof(entryPoint));\n                FF_DEBUG(\"Successfully read entry point data via mmap\");\n            } else {\n                FF_DEBUG(\"Successfully read entry point data via pread\");\n            }\n            #else\n            // Sun or NetBSD\n            FF_DEBUG(\"Using %s specific implementation\",\n                #ifdef __NetBSD__\n                \"NetBSD\"\n                #else\n                \"SunOS\"\n                #endif\n            );\n\n            FF_AUTO_CLOSE_FD int fd = open(\"/dev/smbios\", O_RDONLY | O_CLOEXEC);\n            if (fd < 0) {\n                FF_DEBUG(\"Failed to open /dev/smbios: %s\", strerror(errno));\n                return NULL;\n            }\n            FF_DEBUG(\"/dev/smbios opened successfully with fd=%d\", fd);\n\n            FFSmbiosEntryPoint entryPoint;\n            #ifdef __NetBSD__\n            off_t addr = (off_t) ffSysctlGetInt64(\"machdep.smbios\", 0);\n            if (addr == 0) {\n                FF_DEBUG(\"Failed to get SMBIOS address from sysctl\");\n                return NULL;\n            }\n            FF_DEBUG(\"Got SMBIOS address from sysctl: 0x%lx\", (unsigned long)addr);\n\n            if (pread(fd, &entryPoint, sizeof(entryPoint), addr) < 1) {\n                FF_DEBUG(\"Failed to read SMBIOS entry point: %s\", strerror(errno));\n                return NULL;\n            }\n            FF_DEBUG(\"Successfully read SMBIOS entry point\");\n            #else\n            FF_DEBUG(\"Reading SMBIOS entry point from /dev/smbios\");\n            if (ffReadFDData(fd, sizeof(entryPoint), &entryPoint) < 1) {\n                FF_DEBUG(\"Failed to read SMBIOS entry point: %s\", strerror(errno));\n                return NULL;\n            }\n            FF_DEBUG(\"Successfully read SMBIOS entry point\");\n            #endif\n            #endif\n\n            uint32_t tableLength = 0;\n            loff_t tableAddress = 0;\n            if (memcmp(entryPoint.Smbios20.AnchorString, \"_SM_\", sizeof(entryPoint.Smbios20.AnchorString)) == 0)\n            {\n                FF_DEBUG(\"Found SMBIOS 2.0 entry point\");\n                if (entryPoint.Smbios20.EntryPointLength != sizeof(entryPoint.Smbios20)) {\n                    FF_DEBUG(\"Invalid SMBIOS 2.0 entry point length: %u (expected %zu)\",\n                        entryPoint.Smbios20.EntryPointLength, sizeof(entryPoint.Smbios20));\n                    return NULL;\n                }\n                tableLength = entryPoint.Smbios20.StructureTableLength;\n                tableAddress = (loff_t) entryPoint.Smbios20.StructureTableAddress;\n                FF_DEBUG(\"SMBIOS 2.0: tableLength=0x%x, tableAddress=0x%lx, version=%u.%u\",\n                    tableLength, (unsigned long)tableAddress,\n                    entryPoint.Smbios20.SmbiosMajorVersion, entryPoint.Smbios20.SmbiosMinorVersion);\n            }\n            else if (memcmp(entryPoint.Smbios30.AnchorString, \"_SM3_\", sizeof(entryPoint.Smbios30.AnchorString)) == 0)\n            {\n                FF_DEBUG(\"Found SMBIOS 3.0 entry point\");\n                if (entryPoint.Smbios30.EntryPointLength != sizeof(entryPoint.Smbios30)) {\n                    FF_DEBUG(\"Invalid SMBIOS 3.0 entry point length: %u (expected %zu)\",\n                        entryPoint.Smbios30.EntryPointLength, sizeof(entryPoint.Smbios30));\n                    return NULL;\n                }\n                tableLength = entryPoint.Smbios30.StructureTableMaximumSize;\n                tableAddress = (loff_t) entryPoint.Smbios30.StructureTableAddress;\n                FF_DEBUG(\"SMBIOS 3.0: tableLength=0x%x, tableAddress=0x%lx, version=%u.%u.%u\",\n                    tableLength, (unsigned long)tableAddress,\n                    entryPoint.Smbios30.SmbiosMajorVersion, entryPoint.Smbios30.SmbiosMinorVersion, entryPoint.Smbios30.SmbiosDocrev);\n            }\n            else {\n                FF_DEBUG(\"Unknown SMBIOS entry point format\");\n                return NULL;\n            }\n\n            ffStrbufEnsureFixedLengthFree(&buffer, tableLength);\n            FF_DEBUG(\"Attempting to read SMBIOS table data: %u bytes at 0x%lx\", tableLength, (unsigned long)tableAddress);\n            if (pread(fd, buffer.chars, tableLength, tableAddress) == (ssize_t) tableLength)\n            {\n                buffer.length = tableLength;\n                buffer.chars[buffer.length] = '\\0';\n                FF_DEBUG(\"Successfully read SMBIOS table data: %u bytes\", tableLength);\n            }\n            else\n            {\n                FF_DEBUG(\"pread failed, trying mmap\");\n                // entryPoint.StructureTableAddress must be page aligned.\n                // Unaligned physical memory access results in all kinds of crashes.\n                void* p = mmap(NULL, tableLength, PROT_READ, MAP_SHARED, fd, tableAddress);\n                if (p == MAP_FAILED)\n                {\n                    FF_DEBUG(\"mmap failed: %s\", strerror(errno));\n                    ffStrbufDestroy(&buffer); // free buffer and reset state\n                    return NULL;\n                }\n                ffStrbufSetNS(&buffer, tableLength, (char*) p);\n                munmap(p, tableLength);\n                FF_DEBUG(\"Successfully read SMBIOS table data via mmap: %u bytes\", tableLength);\n            }\n        }\n        #else\n        {\n            FF_DEBUG(\"Using %s implementation\",\n                #if __HAIKU__\n                \"Haiku\"\n                #else\n                \"OpenBSD\"\n                #endif\n            );\n\n            uint32_t tableLength = 0;\n            off_t tableAddress = 0;\n            FF_AUTO_CLOSE_FD int fd = open(\n                #if __HAIKU__\n                \"/dev/misc/mem\"\n                #else\n                \"/dev/mem\" // kern.securelevel must be -1\n                #endif\n            , O_RDONLY | O_CLOEXEC);\n            if (fd < 0) {\n                FF_DEBUG(\"Failed to open memory device: %s\", strerror(errno));\n                return NULL;\n            }\n            FF_DEBUG(\"Memory device opened successfully with fd=%d\", fd);\n\n            // Works on legacy BIOS only\n            // See: https://wiki.osdev.org/System_Management_BIOS#UEFI_systems\n            // On BSD systems, we can get EFI system resource table (ESRT) via EFIIOC_GET_TABLE\n            // However, to acquire SMBIOS entry point, we need EFI configuration table (provided by EFI system table)\n            // which is not available via EFIIOC_GET_TABLE.\n            FF_AUTO_FREE uint8_t* smBiosBase = malloc(0x10000);\n            if (pread(fd, smBiosBase, 0x10000, 0xF0000) != 0x10000) {\n                FF_DEBUG(\"Failed to read SMBIOS memory region: %s\", strerror(errno));\n                return NULL;\n            }\n            FF_DEBUG(\"Successfully read 0x10000 bytes from physical address 0xF0000\");\n\n            for (off_t offset = 0; offset <= 0xffe0; offset += 0x10)\n            {\n                FFSmbiosEntryPoint* p = (void*)(smBiosBase + offset);\n                if (memcmp(p, \"_SM3_\", sizeof(p->Smbios30.AnchorString)) == 0)\n                {\n                    FF_DEBUG(\"Found SMBIOS 3.0 entry point at offset 0x%lx\", (unsigned long)offset);\n                    if (p->Smbios30.EntryPointLength != sizeof(p->Smbios30)) {\n                        FF_DEBUG(\"Invalid SMBIOS 3.0 entry point length: %u (expected %zu)\",\n                            p->Smbios30.EntryPointLength, sizeof(p->Smbios30));\n                        return NULL;\n                    }\n                    tableLength = p->Smbios30.StructureTableMaximumSize;\n                    tableAddress = (off_t) p->Smbios30.StructureTableAddress;\n                    FF_DEBUG(\"SMBIOS 3.0: tableLength=0x%x, tableAddress=0x%lx, version=%u.%u.%u\",\n                        tableLength, (unsigned long)tableAddress,\n                        p->Smbios30.SmbiosMajorVersion, p->Smbios30.SmbiosMinorVersion, p->Smbios30.SmbiosDocrev);\n                    break;\n                }\n                else if (memcmp(p, \"_SM_\", sizeof(p->Smbios20.AnchorString)) == 0)\n                {\n                    FF_DEBUG(\"Found SMBIOS 2.0 entry point at offset 0x%lx\", (unsigned long)offset);\n                    if (p->Smbios20.EntryPointLength != sizeof(p->Smbios20)) {\n                        FF_DEBUG(\"Invalid SMBIOS 2.0 entry point length: %u (expected %zu)\",\n                            p->Smbios20.EntryPointLength, sizeof(p->Smbios20));\n                        return NULL;\n                    }\n                    tableLength = p->Smbios20.StructureTableLength;\n                    tableAddress = (off_t) p->Smbios20.StructureTableAddress;\n                    FF_DEBUG(\"SMBIOS 2.0: tableLength=0x%x, tableAddress=0x%lx, version=%u.%u\",\n                        tableLength, (unsigned long)tableAddress,\n                        p->Smbios20.SmbiosMajorVersion, p->Smbios20.SmbiosMinorVersion);\n                    break;\n                }\n            }\n            if (tableLength == 0) {\n                FF_DEBUG(\"No valid SMBIOS entry point found in memory region\");\n                return NULL;\n            }\n\n            ffStrbufEnsureFixedLengthFree(&buffer, tableLength);\n            FF_DEBUG(\"Attempting to read SMBIOS table data: %u bytes at 0x%lx\", tableLength, (unsigned long)tableAddress);\n            if (pread(fd, buffer.chars, tableLength, tableAddress) == tableLength)\n            {\n                buffer.length = tableLength;\n                buffer.chars[buffer.length] = '\\0';\n                FF_DEBUG(\"Successfully read SMBIOS table data: %u bytes\", tableLength);\n            }\n            else {\n                FF_DEBUG(\"Failed to read SMBIOS table data: %s\", strerror(errno));\n                return NULL;\n            }\n        }\n        #endif\n\n        FF_DEBUG(\"Parsing SMBIOS table structures\");\n        FF_MAYBE_UNUSED int structureCount = 0;\n        for (\n            const FFSmbiosHeader* header = (const FFSmbiosHeader*) buffer.chars;\n            (const uint8_t*) header < (const uint8_t*) buffer.chars + buffer.length;\n            header = ffSmbiosNextEntry(header)\n        )\n        {\n            if (header->Type < FF_SMBIOS_TYPE_END_OF_TABLE)\n            {\n                if (!table[header->Type]) {\n                    table[header->Type] = header;\n                    FF_DEBUG(\"Found SMBIOS structure type %u, handle 0x%04X, length %u\",\n                        header->Type, header->Handle, header->Length);\n                    structureCount++;\n                }\n            }\n            else if (header->Type == FF_SMBIOS_TYPE_END_OF_TABLE) {\n                FF_DEBUG(\"Reached end-of-table marker\");\n                break;\n            }\n        }\n        FF_DEBUG(\"Parsed %d SMBIOS structures\", structureCount);\n    }\n\n    if (buffer.length == 0) {\n        FF_DEBUG(\"No valid SMBIOS data available\");\n        return NULL;\n    }\n\n    return &table;\n}\n#elif defined(_WIN32)\n#include \"common/windows/nt.h\"\n\n#pragma GCC diagnostic ignored \"-Wmultichar\"\n\ntypedef struct FFRawSmbiosData\n{\n    uint8_t Used20CallingMethod;\n    uint8_t SMBIOSMajorVersion;\n    uint8_t SMBIOSMinorVersion;\n    uint8_t DmiRevision;\n    uint32_t Length;\n    uint8_t SMBIOSTableData[];\n} FFRawSmbiosData;\n\nconst FFSmbiosHeaderTable* ffGetSmbiosHeaderTable()\n{\n    static SYSTEM_FIRMWARE_TABLE_INFORMATION* buffer;\n    static FFSmbiosHeaderTable table;\n\n    if (!buffer)\n    {\n        FF_DEBUG(\"Initializing Windows SMBIOS buffer\");\n\n        FF_DEBUG(\"Querying system firmware table size with signature 'RSMB'\");\n        SYSTEM_FIRMWARE_TABLE_INFORMATION sfti = {\n            .ProviderSignature = 'RSMB',\n            .Action = SystemFirmwareTableGet,\n        };\n        ULONG bufSize = 0;\n        NtQuerySystemInformation(SystemFirmwareTableInformation, &sfti, sizeof(sfti), &bufSize);\n        if (bufSize <= sizeof(FFRawSmbiosData) + sizeof(sfti)) {\n            FF_DEBUG(\"Invalid firmware table size: %lu (must be > %zu)\", bufSize, sizeof(FFRawSmbiosData) + sizeof(sfti));\n            return NULL;\n        }\n        if (bufSize != sfti.TableBufferLength + (ULONG) sizeof(sfti)) {\n            FF_DEBUG(\"Firmware table size mismatch: NtQuerySystemInformation returned %lu but expected %lu\",\n                bufSize, sfti.TableBufferLength + (ULONG) sizeof(sfti));\n            return NULL;\n        }\n        FF_DEBUG(\"Firmware table size: %lu bytes\", bufSize);\n\n        buffer = malloc(bufSize);\n        *buffer = sfti;\n        FF_DEBUG(\"Allocated buffer for SMBIOS data\");\n\n        if (!NT_SUCCESS(NtQuerySystemInformation(SystemFirmwareTableInformation, buffer, bufSize, &bufSize)))\n        {\n            FF_DEBUG(\"NtQuerySystemInformation(SystemFirmwareTableInformation) failed\");\n            free(buffer);\n            buffer = NULL;\n            return NULL;\n        }\n        FFRawSmbiosData* rawData = (FFRawSmbiosData*) buffer->TableBuffer;\n\n        FF_DEBUG(\"Successfully retrieved SMBIOS data: version %u.%u, length %u bytes\",\n            rawData->SMBIOSMajorVersion, rawData->SMBIOSMinorVersion, rawData->Length);\n\n        FF_DEBUG(\"Parsing SMBIOS table structures\");\n        FF_MAYBE_UNUSED int structureCount = 0;\n        for (\n            const FFSmbiosHeader* header = (const FFSmbiosHeader*) rawData->SMBIOSTableData;\n            (const uint8_t*) header < rawData->SMBIOSTableData + rawData->Length;\n            header = ffSmbiosNextEntry(header)\n        )\n        {\n            if (header->Type < FF_SMBIOS_TYPE_END_OF_TABLE)\n            {\n                if (!table[header->Type]) {\n                    table[header->Type] = header;\n                    FF_DEBUG(\"Found SMBIOS structure type %u, handle 0x%04X, length %u\",\n                        header->Type, header->Handle, header->Length);\n                    structureCount++;\n                }\n            }\n            else if (header->Type == FF_SMBIOS_TYPE_END_OF_TABLE) {\n                FF_DEBUG(\"Reached end-of-table marker\");\n                break;\n            }\n        }\n        FF_DEBUG(\"Parsed %d SMBIOS structures\", structureCount);\n    }\n\n    return &table;\n}\n#endif\n"
  },
  {
    "path": "src/common/impl/sysctl.c",
    "content": "#include \"common/sysctl.h\"\n\n#include <stdlib.h>\n\n#ifdef __OpenBSD__\nconst char* ffSysctlGetString(int mib1, int mib2, FFstrbuf* result)\n{\n    size_t neededLength;\n    if (sysctl((int[]) {mib1, mib2}, 2, NULL, &neededLength, NULL, 0) != 0 || neededLength == 1) //neededLength is 1 for empty strings, because of the null terminator\n        return \"sysctlbyname() failed\";\n\n    ffStrbufEnsureFree(result, (uint32_t) neededLength - 1);\n\n    if (sysctl((int[]) {mib1, mib2}, 2, result->chars + result->length, &neededLength, NULL, 0) == 0)\n        result->length += (uint32_t) neededLength - 1;\n\n    result->chars[result->length] = '\\0';\n\n    return NULL;\n}\n\nint ffSysctlGetInt(int mib1, int mib2, int defaultValue)\n{\n    int result;\n    size_t neededLength = sizeof(result);\n    if (sysctl((int[]) {mib1, mib2}, 2, &result, &neededLength, NULL, 0) != 0)\n        return defaultValue;\n    return result;\n}\n\nint64_t ffSysctlGetInt64(int mib1, int mib2, int64_t defaultValue)\n{\n    int64_t result;\n    size_t neededLength = sizeof(result);\n    if(sysctl((int[]) {mib1, mib2}, 2, &result, &neededLength, NULL, 0) != 0)\n        return defaultValue;\n    return result;\n}\n#else\nconst char* ffSysctlGetString(const char* propName, FFstrbuf* result)\n{\n    size_t neededLength;\n    if(sysctlbyname(propName, NULL, &neededLength, NULL, 0) != 0 || neededLength == 1) //neededLength is 1 for empty strings, because of the null terminator\n        return \"sysctlbyname() failed\";\n\n    ffStrbufEnsureFree(result, (uint32_t) neededLength - 1);\n\n    if(sysctlbyname(propName, result->chars + result->length, &neededLength, NULL, 0) == 0)\n        result->length += (uint32_t) neededLength - 1;\n\n    result->chars[result->length] = '\\0';\n\n    return NULL;\n}\n\nint ffSysctlGetInt(const char* propName, int defaultValue)\n{\n    int result;\n    size_t neededLength = sizeof(result);\n    if(sysctlbyname(propName, &result, &neededLength, NULL, 0) != 0)\n        return defaultValue;\n    return result;\n}\n\nint64_t ffSysctlGetInt64(const char* propName, int64_t defaultValue)\n{\n    int64_t result;\n    size_t neededLength = sizeof(result);\n    if(sysctlbyname(propName, &result, &neededLength, NULL, 0) != 0)\n        return defaultValue;\n    return result;\n}\n#endif // OpenBSD\n\nvoid* ffSysctlGetData(int* request, u_int requestLength, size_t* resultLength)\n{\n    if(sysctl(request, requestLength, NULL, resultLength, NULL, 0) != 0)\n        return NULL;\n\n    void* data = malloc(*resultLength);\n    if(data == NULL)\n        return NULL;\n\n    if(sysctl(request, requestLength, data, resultLength, NULL, 0) != 0)\n    {\n        free(data);\n        return NULL;\n    }\n\n    return data;\n}\n"
  },
  {
    "path": "src/common/impl/temps.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/temps.h\"\n#include \"common/textModifier.h\"\n#include \"common/stringUtils.h\"\n\nvoid ffTempsAppendNum(double celsius, FFstrbuf* buffer, FFColorRangeConfig config, const FFModuleArgs* module)\n{\n    if (celsius == -DBL_MAX) // ignores invalid value\n        return;\n\n    const FFOptionsDisplay* options = &instance.config.display;\n    const char* colorGreen = options->tempColorGreen.chars;\n    const char* colorYellow = options->tempColorYellow.chars;\n    const char* colorRed = options->tempColorRed.chars;\n\n    uint8_t green = config.green, yellow = config.yellow;\n\n    if (!options->pipe)\n    {\n        if(green <= yellow)\n        {\n            if (celsius > yellow)\n                ffStrbufAppendF(buffer, \"\\e[%sm\", colorRed);\n            else if (celsius > green)\n                ffStrbufAppendF(buffer, \"\\e[%sm\", colorYellow);\n            else\n                ffStrbufAppendF(buffer, \"\\e[%sm\", colorGreen);\n        }\n        else\n        {\n            if (celsius < yellow)\n                ffStrbufAppendF(buffer, \"\\e[%sm\", colorRed);\n            else if (celsius < green)\n                ffStrbufAppendF(buffer, \"\\e[%sm\", colorYellow);\n            else\n                ffStrbufAppendF(buffer, \"\\e[%sm\", colorGreen);\n        }\n    }\n\n    switch (options->tempUnit)\n    {\n        case FF_TEMPERATURE_UNIT_DEFAULT:\n        case FF_TEMPERATURE_UNIT_CELSIUS:\n            ffStrbufAppendF(buffer, \"%.*f%s°C\", options->tempNdigits, celsius,\n                options->tempSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_ALWAYS ? \" \" : \"\");\n            break;\n        case FF_TEMPERATURE_UNIT_FAHRENHEIT:\n            ffStrbufAppendF(buffer, \"%.*f%s°F\", options->tempNdigits, celsius * 1.8 + 32,\n                options->tempSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_ALWAYS ? \" \" : \"\");\n            break;\n        case FF_TEMPERATURE_UNIT_KELVIN:\n            ffStrbufAppendF(buffer, \"%.*f%sK\", options->tempNdigits, celsius + 273.15,\n                options->tempSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_NEVER ? \"\" : \" \");\n            break;\n    }\n\n    if (!options->pipe)\n    {\n        ffStrbufAppendS(buffer, FASTFETCH_TEXT_MODIFIER_RESET);\n        if (module->outputColor.length)\n            ffStrbufAppendF(buffer, \"\\e[%sm\", module->outputColor.chars);\n        else if (instance.config.display.colorOutput.length)\n            ffStrbufAppendF(buffer, \"\\e[%sm\", instance.config.display.colorOutput.chars);\n    }\n}\n\nbool ffTempsParseCommandOptions(const char* key, const char* subkey, const char* value, bool* useTemp, FFColorRangeConfig* config)\n{\n    if (!ffStrStartsWithIgnCase(subkey, \"temp\"))\n        return false;\n\n    if (subkey[strlen(\"temp\")] == '\\0')\n    {\n        *useTemp = ffOptionParseBoolean(value);\n        return true;\n    }\n\n    if (subkey[strlen(\"temp\")] != '-')\n        return false;\n\n    subkey += strlen(\"temp-\");\n\n    if (ffStrEqualsIgnCase(subkey, \"green\"))\n    {\n        uint32_t num = ffOptionParseUInt32(key, value);\n        if (num > 100)\n        {\n            fprintf(stderr, \"Error: usage: %s must be between 0 and 100\\n\", key);\n            exit(480);\n        }\n        config->green = (uint8_t) num;\n        return true;\n    }\n\n    if (ffStrEqualsIgnCase(subkey, \"yellow\"))\n    {\n        uint32_t num = ffOptionParseUInt32(key, value);\n        if (num > 100)\n        {\n            fprintf(stderr, \"Error: usage: %s must be between 0 and 100\\n\", key);\n            exit(480);\n        }\n        config->yellow = (uint8_t) num;\n        return true;\n    }\n\n    return false;\n}\n\nbool ffTempsParseJsonObject(yyjson_val* key, yyjson_val* value, bool* useTemp, FFColorRangeConfig* config)\n{\n    assert(key);\n\n    if (!unsafe_yyjson_equals_str(key, \"temp\"))\n        return false;\n\n    if (yyjson_is_bool(value))\n    {\n        *useTemp = yyjson_get_bool(value);\n        return true;\n    }\n\n    if (yyjson_is_null(value))\n    {\n        *useTemp = false;\n        return true;\n    }\n\n    if (!yyjson_is_obj(value))\n    {\n        fprintf(stderr, \"Error: usage: %s must be an object or a boolean\\n\", unsafe_yyjson_get_str(key));\n        exit(480);\n    }\n\n    *useTemp = true;\n\n    yyjson_val* greenVal = yyjson_obj_get(value, \"green\");\n    if (greenVal)\n    {\n        int num = yyjson_get_int(greenVal);\n        if (num < 0 || num > 100)\n        {\n            fputs(\"Error: usage: temp.green must be between 0 and 100\\n\", stderr);\n            exit(480);\n        }\n        config->green = (uint8_t) num;\n    }\n\n    yyjson_val* yellowVal = yyjson_obj_get(value, \"yellow\");\n    if (yellowVal)\n    {\n        int num = yyjson_get_int(yellowVal);\n        if (num < 0 || num > 100)\n        {\n            fputs(\"Error: usage: temp.yellow must be between 0 and 100\\n\", stderr);\n            exit(480);\n        }\n        config->yellow = (uint8_t) num;\n    }\n\n    return true;\n}\n\nvoid ffTempsGenerateJsonConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, bool temp, FFColorRangeConfig config)\n{\n    if (!temp)\n        yyjson_mut_obj_add_bool(doc, module, \"temp\", false);\n    else\n    {\n        yyjson_mut_val* temp = yyjson_mut_obj_add_obj(doc, module, \"temp\");\n        yyjson_mut_obj_add_uint(doc, temp, \"green\", config.green);\n        yyjson_mut_obj_add_uint(doc, temp, \"yellow\", config.yellow);\n    }\n}\n"
  },
  {
    "path": "src/common/impl/time.c",
    "content": "#include \"common/time.h\"\n\n#include <stdio.h>\n\nchar ffTimeInternalBuffer[64]; // Reduce memory usage and prevent redundant allocations\n\n#ifdef _WIN32\n    #pragma GCC diagnostic push\n    #pragma GCC diagnostic ignored \"-Wformat\"\n#endif\n\nconst char* ffTimeToFullStr(uint64_t msec)\n{\n    if (msec == 0) return \"\";\n    time_t tsec = (time_t) (msec / 1000);\n    const struct tm* tm = localtime(&tsec);\n\n    uint32_t len = 0;\n    len += (uint32_t) strftime(ffTimeInternalBuffer, ARRAY_SIZE(ffTimeInternalBuffer) - len, \"%FT%T\", tm);\n    len += (uint32_t) snprintf(ffTimeInternalBuffer + len, ARRAY_SIZE(ffTimeInternalBuffer) - len, \".%03u\", (unsigned) (msec % 1000));\n    len += (uint32_t) strftime(ffTimeInternalBuffer + len, ARRAY_SIZE(ffTimeInternalBuffer) - len, \"%z\", tm);\n    return ffTimeInternalBuffer;\n}\n\nconst char* ffTimeToShortStr(uint64_t msec)\n{\n    if (msec == 0) return \"\";\n    time_t tsec = (time_t) (msec / 1000);\n\n    strftime(ffTimeInternalBuffer, ARRAY_SIZE(ffTimeInternalBuffer), \"%F %T\", localtime(&tsec));\n    return ffTimeInternalBuffer;\n}\n\nconst char* ffTimeToTimeStr(uint64_t msec)\n{\n    if (msec == 0) return \"\";\n    time_t tsec = (time_t) (msec / 1000);\n\n    uint32_t len = (uint32_t) strftime(ffTimeInternalBuffer, ARRAY_SIZE(ffTimeInternalBuffer), \"%T\", localtime(&tsec));\n    sprintf(ffTimeInternalBuffer + len, \".%03u\", (unsigned) (msec % 1000));\n    return ffTimeInternalBuffer;\n}\n\n#ifdef _WIN32\n    #pragma GCC diagnostic pop\n#endif\n\nFFTimeGetAgeResult ffTimeGetAge(uint64_t birthMs, uint64_t nowMs)\n{\n    FFTimeGetAgeResult result = {};\n    if (__builtin_expect(birthMs == 0 || nowMs < birthMs, 0))\n        return result;\n\n    time_t birth_s = (time_t) (birthMs / 1000);\n    struct tm birth_tm;\n    #ifdef _WIN32\n    localtime_s(&birth_tm, &birth_s);\n    #else\n    localtime_r(&birth_s, &birth_tm);\n    #endif\n\n    time_t now_s = (time_t) (nowMs / 1000);\n    struct tm now_tm;\n    #ifdef _WIN32\n    localtime_s(&now_tm, &now_s);\n    #else\n    localtime_r(&now_s, &now_tm);\n    #endif\n\n    result.years = (uint32_t) (now_tm.tm_year - birth_tm.tm_year);\n    if (now_tm.tm_yday < birth_tm.tm_yday)\n        result.years--;\n\n    birth_tm.tm_year += (int) result.years;\n    birth_s = mktime(&birth_tm);\n    uint32_t diff_s = (uint32_t) (now_s - birth_s);\n    result.daysOfYear = diff_s / (24 * 60 * 60);\n\n    birth_tm.tm_year += 1;\n    result.yearsFraction = (double) diff_s / (double) (mktime(&birth_tm) - birth_s) + result.years;\n\n    return result;\n}\n\n#ifdef _WIN32\n    double ffQpcMultiplier;\n\n    __attribute__((constructor))\n    static void ffTimeInitQpcMultiplier(void)\n    {\n        LARGE_INTEGER frequency;\n        QueryPerformanceFrequency(&frequency);\n        ffQpcMultiplier = 1000. / (double) frequency.QuadPart;\n    }\n#endif\n"
  },
  {
    "path": "src/common/impl/wcwidth.c",
    "content": "#include \"common/wcwidth.h\"\n#include \"3rdparty/widecharwidth/widechar_width_c.h\"\n\nint mk_wcwidth(uint32_t wc)\n{\n    // // We render U+1F6E1 (🛡) with a width of 2,\n    // // but widechar_width says it has a width of 1 because Unicode classifies it as \"neutral\".\n    // //\n    // // So we simply decide the width ourselves\n    // if (wc == 0x1F6E1) return 2;\n    //\n    // Well terminals do show it as width 1 after all\n\n    int width = widechar_wcwidth(wc);\n\n    switch (width) {\n        case widechar_ambiguous:\n        case widechar_private_use:\n            return 1;\n        case widechar_widened_in_9:\n            // Our renderer supports Unicode 9\n            return 2;\n        // case widechar_nonprint:\n        // case widechar_combining:\n        // case widechar_unassigned:\n        // case widechar_non_character:\n        //    return -1;\n        default:\n            // Use the width widechar_width gave us.\n            return width;\n    }\n}\n"
  },
  {
    "path": "src/common/init.h",
    "content": "#pragma once\n\nvoid ffInitInstance(void);\nvoid ffStart(void);\nvoid ffFinish(void);\nvoid ffDestroyInstance(void);\nvoid ffListFeatures(void);\n"
  },
  {
    "path": "src/common/io.h",
    "content": "#pragma once\n\n#include \"common/FFstrbuf.h\"\n#include \"common/FFlist.h\"\n\n#ifdef _WIN32\n    #include <fileapi.h>\n    #include <handleapi.h>\n    #include <io.h>\n    #include \"common/windows/nt.h\"\n    typedef HANDLE FFNativeFD;\n    #define FF_INVALID_FD INVALID_HANDLE_VALUE\n#else\n    #include <unistd.h>\n    #include <dirent.h>\n    #include <sys/stat.h>\n    #include <errno.h>\n    #include <limits.h>\n    typedef int FFNativeFD;\n    #define FF_INVALID_FD (-1)\n    // procfs's file can be changed between read calls such as /proc/meminfo and /proc/uptime.\n    // one safe way to read correct data is reading the whole file in a single read syscall\n    #define PROC_FILE_BUFFSIZ (32 * 1024)\n#endif\n\nstatic inline FFNativeFD FFUnixFD2NativeFD(int unixfd)\n{\n    #ifndef _WIN32\n        return unixfd;\n    #else\n        return (FFNativeFD) _get_osfhandle(unixfd);\n    #endif\n}\n\nFF_C_NONNULL(3)\nstatic inline bool ffWriteFDData(FFNativeFD fd, size_t dataSize, const void* data)\n{\n    #ifndef _WIN32\n        return write(fd, data, dataSize) != -1;\n    #else\n        DWORD written;\n        return WriteFile(fd, data, (DWORD) dataSize, &written, NULL) && written == dataSize;\n    #endif\n}\n\nFF_C_NONNULL(2)\nstatic inline bool ffWriteFDBuffer(FFNativeFD fd, const FFstrbuf* content)\n{\n    return ffWriteFDData(fd, content->length, content->chars);\n}\n\nFF_C_NONNULL(1, 3)\nbool ffWriteFileData(const char* fileName, size_t dataSize, const void* data);\n\nFF_C_NONNULL(1, 2)\nstatic inline bool ffWriteFileBuffer(const char* fileName, const FFstrbuf* buffer)\n{\n    return ffWriteFileData(fileName, buffer->length, buffer->chars);\n}\n\nFF_C_NONNULL(3)\nstatic inline ssize_t ffReadFDData(FFNativeFD fd, size_t dataSize, void* data)\n{\n    #ifndef _WIN32\n        return read(fd, data, dataSize);\n    #else\n        DWORD bytesRead;\n        if(!ReadFile(fd, data, (DWORD)dataSize, &bytesRead, NULL))\n            return -1;\n\n        return (ssize_t)bytesRead;\n    #endif\n}\n\nFF_C_NONNULL(1, 3)\nssize_t ffReadFileData(const char* fileName, size_t dataSize, void* data);\nFF_C_NONNULL(2, 4)\nssize_t ffReadFileDataRelative(FFNativeFD dfd, const char* fileName, size_t dataSize, void* data);\n\nFF_C_NONNULL(2)\nbool ffAppendFDBuffer(FFNativeFD fd, FFstrbuf* buffer);\nFF_C_NONNULL(1, 2)\nbool ffAppendFileBuffer(const char* fileName, FFstrbuf* buffer);\nFF_C_NONNULL(2, 3)\nbool ffAppendFileBufferRelative(FFNativeFD dfd, const char* fileName, FFstrbuf* buffer);\n\nFF_C_NONNULL(2)\nstatic inline bool ffReadFDBuffer(FFNativeFD fd, FFstrbuf* buffer)\n{\n    ffStrbufClear(buffer);\n    return ffAppendFDBuffer(fd, buffer);\n}\n\nFF_C_NONNULL(1, 2)\nstatic inline bool ffReadFileBuffer(const char* fileName, FFstrbuf* buffer)\n{\n    ffStrbufClear(buffer);\n    return ffAppendFileBuffer(fileName, buffer);\n}\n\nFF_C_NONNULL(2, 3)\nstatic inline bool ffReadFileBufferRelative(FFNativeFD dfd, const char* fileName, FFstrbuf* buffer)\n{\n    ffStrbufClear(buffer);\n    return ffAppendFileBufferRelative(dfd, fileName, buffer);\n}\n\ntypedef enum __attribute__((__packed__)) FFPathType\n{\n    FF_PATHTYPE_FILE = 1 << 0,\n    FF_PATHTYPE_DIRECTORY = 1 << 1,\n    FF_PATHTYPE_ANY = FF_PATHTYPE_FILE | FF_PATHTYPE_DIRECTORY,\n    FF_PATHTYPE_FORCE_UNSIGNED = UINT8_MAX,\n} FFPathType;\n\nFF_C_NONNULL(1)\nstatic inline bool ffPathExists(const char* path, FFPathType pathType)\n{\n    #ifdef _WIN32\n\n    wchar_t wPath[MAX_PATH];\n    if (!NT_SUCCESS(RtlUTF8ToUnicodeN(wPath, (ULONG) sizeof(wPath), NULL, path, (ULONG)strlen(path) + 1)))\n        return false;\n\n    DWORD attr = GetFileAttributesW(wPath);\n\n    if(attr == INVALID_FILE_ATTRIBUTES)\n        return false;\n\n    if(pathType & FF_PATHTYPE_FILE && !(attr & FILE_ATTRIBUTE_DIRECTORY))\n        return true;\n\n    if(pathType & FF_PATHTYPE_DIRECTORY && (attr & FILE_ATTRIBUTE_DIRECTORY))\n        return true;\n\n    #else\n\n    if (pathType == FF_PATHTYPE_ANY)\n    {\n        // Zero overhead\n        return access(path, F_OK) == 0;\n    }\n    else\n    {\n        struct stat fileStat;\n        if(stat(path, &fileStat) != 0)\n            return false;\n\n        unsigned int mode = fileStat.st_mode & S_IFMT;\n\n        if(pathType & FF_PATHTYPE_FILE && mode != S_IFDIR)\n            return true;\n\n        if(pathType & FF_PATHTYPE_DIRECTORY && mode == S_IFDIR)\n            return true;\n    }\n\n    #endif\n\n    return false;\n}\n\nFF_C_NONNULL(1, 2)\nbool ffPathExpandEnv(const char* in, FFstrbuf* out);\n\n#define FF_IO_TERM_RESP_WAIT_MS 100 // #554\n\nFF_C_SCANF(3, 4)\nFF_C_NONNULL(1, 3)\nconst char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...);\n\n// Not thread safe!\nbool ffSuppressIO(bool suppress);\n\nstatic inline void ffUnsuppressIO(bool* suppressed)\n{\n    if (!*suppressed) return;\n    ffSuppressIO(false);\n    *suppressed = false;\n}\n\n#define FF_SUPPRESS_IO() bool __attribute__((__cleanup__(ffUnsuppressIO), __unused__)) io_suppressed__ = ffSuppressIO(true)\n\nvoid ffListFilesRecursively(const char* path, bool pretty);\n\nstatic inline bool ffIsValidNativeFD(FFNativeFD fd)\n{\n    #ifndef _WIN32\n        return fd >= 0;\n    #else\n        // https://devblogs.microsoft.com/oldnewthing/20040302-00/?p=40443\n        return fd != INVALID_HANDLE_VALUE && fd != NULL;\n    #endif\n}\n\nFF_C_NONNULL(1)\nstatic inline bool wrapClose(FFNativeFD* pfd)\n{\n    assert(pfd);\n\n    if (!ffIsValidNativeFD(*pfd))\n        return false;\n\n    #ifndef _WIN32\n        close(*pfd);\n    #else\n        NtClose(*pfd);\n    #endif\n\n    return true;\n}\n#define FF_AUTO_CLOSE_FD __attribute__((__cleanup__(wrapClose)))\n\nFF_C_NONNULL(1)\nstatic inline bool wrapFclose(FILE** pfile)\n{\n    assert(pfile);\n    if (!*pfile)\n        return false;\n    fclose(*pfile);\n    return true;\n}\n#define FF_AUTO_CLOSE_FILE __attribute__((__cleanup__(wrapFclose)))\n\nFF_C_NONNULL(1)\n#ifndef _WIN32\nstatic inline bool wrapClosedir(DIR** pdir)\n{\n    assert(pdir);\n    if (!*pdir)\n        return false;\n    closedir(*pdir);\n    return true;\n}\n#else\nstatic inline bool wrapClosedir(HANDLE* pdir)\n{\n    assert(pdir);\n    if (!*pdir)\n        return false;\n    FindClose(*pdir);\n    return true;\n}\n#endif\n#define FF_AUTO_CLOSE_DIR __attribute__((__cleanup__(wrapClosedir)))\n\nFF_C_NONNULL(1, 2, 3)\nstatic inline bool ffSearchUserConfigFile(const FFlist* configDirs, const char* fileSubpath, FFstrbuf* result)\n{\n    // configDirs is a list of FFstrbufs include the trailing slash\n    FF_LIST_FOR_EACH(FFstrbuf, dir, *configDirs)\n    {\n        ffStrbufClear(result);\n        ffStrbufAppend(result, dir);\n        ffStrbufAppendS(result, fileSubpath);\n        if (ffPathExists(result->chars, FF_PATHTYPE_FILE))\n            return true;\n    }\n\n    return false;\n}\n\nFFNativeFD ffGetNullFD(void);\nbool ffRemoveFile(const char* fileName);\n\n#ifdef _WIN32\n// Only O_RDONLY is supported\nHANDLE openat(HANDLE dfd, const char* fileName, bool directory);\nHANDLE openatW(HANDLE dfd, const wchar_t* fileName, uint16_t fileNameLen, bool directory);\n#endif\n"
  },
  {
    "path": "src/common/jsonconfig.h",
    "content": "#pragma once\n\n#include \"common/ffdata.h\"\n#include \"common/option.h\"\n\nbool ffJsonConfigParseModuleArgs(yyjson_val* key, yyjson_val* val, FFModuleArgs* moduleArgs);\nconst char* ffJsonConfigParseEnum(yyjson_val* val, int* result, FFKeyValuePair pairs[]);\n\nyyjson_api_inline yyjson_mut_val* yyjson_mut_strbuf(yyjson_mut_doc *doc, const FFstrbuf* buf) {\n    return yyjson_mut_strncpy(doc, buf->chars, buf->length);\n}\n\nyyjson_api_inline bool yyjson_mut_obj_add_strbuf(yyjson_mut_doc *doc,\n                                                  yyjson_mut_val *obj,\n                                                  const char *_key,\n                                                  const FFstrbuf* buf) {\n    return yyjson_mut_obj_add_strncpy(doc, obj, _key, buf->chars, buf->length);\n}\n\nyyjson_api_inline bool yyjson_mut_arr_add_strbuf(yyjson_mut_doc *doc,\n                                                  yyjson_mut_val *obj,\n                                                  const FFstrbuf* buf) {\n    return yyjson_mut_arr_add_strncpy(doc, obj, buf->chars, buf->length);\n}\n\nvoid ffPrintJsonConfig(FFdata* data, bool prepare);\nvoid ffJsonConfigGenerateModuleArgsConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, FFModuleArgs* moduleArgs);\n"
  },
  {
    "path": "src/common/kmod.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\nbool ffKmodLoaded(const char* modName);\n"
  },
  {
    "path": "src/common/library.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"common/FFcheckmacros.h\"\n\n#ifndef FF_DISABLE_DLOPEN\n\n#if defined(_WIN32)\n    #define FF_DLOPEN_FLAGS 0\n    FF_C_NODISCARD void* dlopen(const char* path, int mode);\n    FF_C_NODISCARD void* dlsym(void* handle, const char* symbol);\n    int dlclose(void* handle);\n#else\n    #include <dlfcn.h>\n#endif\n\n#ifdef _WIN32\n    #define FF_LIBRARY_EXTENSION \".dll\"\n#elif defined(__APPLE__)\n    #define FF_LIBRARY_EXTENSION \".dylib\"\n#else\n    #define FF_LIBRARY_EXTENSION \".so\"\n#endif\n\nstatic inline void ffLibraryUnload(void** handle)\n{\n    assert(handle);\n    if (*handle)\n        dlclose(*handle);\n}\n\n#if __cplusplus\n#define __auto_type auto\n#endif\n\n#define FF_LIBRARY_SYMBOL(symbolName) \\\n    __typeof__(&symbolName) ff ## symbolName;\n\n#define FF_LIBRARY_LOAD(libraryObjectName, returnValue, ...) \\\n    void* __attribute__((__cleanup__(ffLibraryUnload))) libraryObjectName = ffLibraryLoad(__VA_ARGS__, NULL);\\\n    if(libraryObjectName == NULL) \\\n        return returnValue;\n\n#define FF_LIBRARY_LOAD_MESSAGE(libraryObjectName, libraryFileName, maxVersion, ...) \\\n    FF_LIBRARY_LOAD(libraryObjectName, \"dlopen(\" libraryFileName \") failed\", libraryFileName, maxVersion, ##__VA_ARGS__)\n\n#define FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, symbolMapping, symbolName, returnValue) \\\n    symbolMapping = (__typeof__(&symbolName)) dlsym(library, #symbolName); \\\n    if(symbolMapping == NULL) \\\n        return returnValue;\n\n#define FF_LIBRARY_LOAD_SYMBOL(library, symbolName, returnValue) \\\n    __auto_type FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, ff ## symbolName, symbolName, returnValue);\n\n#define FF_LIBRARY_LOAD_SYMBOL_LAZY(library, symbolName) \\\n    __auto_type ff ## symbolName = (__typeof__(&symbolName)) dlsym(library, #symbolName);\n\n#define FF_LIBRARY_LOAD_SYMBOL_MESSAGE(library, symbolName) \\\n    __auto_type FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, ff ## symbolName, symbolName, \"dlsym \" #symbolName \" failed\");\n\n#define FF_LIBRARY_LOAD_SYMBOL_VAR(library, varName, symbolName, returnValue) \\\n    FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName).ff ## symbolName, symbolName, returnValue);\n\n#define FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(library, varName, symbolName) \\\n    FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName).ff ## symbolName, symbolName, \"dlsym \" #symbolName \" failed\");\n\n#define FF_LIBRARY_LOAD_SYMBOL_PTR(library, varName, symbolName, returnValue) \\\n    FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName)->ff ## symbolName, symbolName, returnValue);\n\nvoid* ffLibraryLoad(const char* path, int maxVersion, ...);\n\n#else\n\n#define FF_LIBRARY_EXTENSION \"\"\n\n#define FF_LIBRARY_SYMBOL(symbolName) \\\n    __typeof__(&symbolName) ff ## symbolName;\n\n#define FF_LIBRARY_LOAD(libraryObjectName, returnValue, ...) \\\n    FF_MAYBE_UNUSED void* libraryObjectName = NULL; // Placeholder\n\n#define FF_LIBRARY_LOAD_MESSAGE(libraryObjectName, libraryFileName, maxVersion, ...) \\\n    FF_LIBRARY_LOAD(libraryObjectName, , libraryFileName, maxVersion, ##__VA_ARGS__)\n\n#define FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, symbolMapping, symbolName, returnValue) \\\n    symbolMapping = (__typeof__(&symbolName)) &symbolName;\n\n#define FF_LIBRARY_LOAD_SYMBOL(library, symbolName, returnValue) \\\n    FF_MAYBE_UNUSED __auto_type FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, ff ## symbolName, symbolName, returnValue);\n\n#define FF_LIBRARY_LOAD_SYMBOL_LAZY(library, symbolName) \\\n    FF_MAYBE_UNUSED __auto_type ff ## symbolName = (__typeof__(&symbolName)) &symbolName;\n\n#define FF_LIBRARY_LOAD_SYMBOL_MESSAGE(library, symbolName) \\\n    FF_MAYBE_UNUSED __auto_type FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, ff ## symbolName, symbolName, \"dlsym \" #symbolName \" failed\");\n\n#define FF_LIBRARY_LOAD_SYMBOL_VAR(library, varName, symbolName, returnValue) \\\n    FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName).ff ## symbolName, symbolName, returnValue);\n\n#define FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(library, varName, symbolName) \\\n    FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName).ff ## symbolName, symbolName, \"dlsym \" #symbolName \" failed\");\n\n#define FF_LIBRARY_LOAD_SYMBOL_PTR(library, varName, symbolName, returnValue) \\\n    FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName)->ff ## symbolName, symbolName, returnValue);\n\n#endif\n\n#if _WIN32\nvoid* ffLibraryGetModule(const wchar_t* libraryFileName);\n#endif\n"
  },
  {
    "path": "src/common/mallocHelper.h",
    "content": "#pragma once\n\n#include <stdlib.h>\n#include <assert.h>\n\n#if FF_HAVE_MALLOC_USABLE_SIZE || FF_HAVE_MSVC_MSIZE\n    #if __has_include(<malloc.h>)\n        #include <malloc.h>\n    #else\n        #include <malloc_np.h> // For DragonFly BSD\n    #endif\n#elif FF_HAVE_MALLOC_SIZE\n    #include <malloc/malloc.h>\n#endif\n\nstatic inline void ffWrapFree(const void* pPtr)\n{\n    assert(pPtr);\n    if(*(void**)pPtr)\n        free(*(void**)pPtr);\n}\n\n#define FF_AUTO_FREE __attribute__((__cleanup__(ffWrapFree)))\n\n// ptr MUST be a malloc'ed pointer\nstatic inline size_t ffMallocUsableSize(const void* ptr)\n{\n    assert(ptr);\n    #if FF_HAVE_MALLOC_USABLE_SIZE\n        return malloc_usable_size((void*) ptr);\n    #elif FF_HAVE_MALLOC_SIZE\n        return malloc_size((void*) ptr);\n    #elif FF_HAVE_MSVC_MSIZE\n        return _msize((void*) ptr);\n    #else\n        (void) ptr;\n        return 0; // Not supported\n    #endif\n}\n"
  },
  {
    "path": "src/common/memrchr.h",
    "content": "#pragma once\n\n#include <stddef.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n// `memrchr` is a GNU extension and may not be declared by system headers even when the symbol exists.\n// Declare it unconditionally; the build system provides a fallback implementation when missing.\nvoid* memrchr(const void* s, int c, size_t n);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "src/common/netif.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\n#ifndef _WIN32\n    #include <net/if.h>\n    #include <netinet/in.h>\n#endif\n\ntypedef enum __attribute__((__packed__)) FFNetifDefaultRouteResultStatus {\n    FF_NETIF_UNINITIALIZED,\n    FF_NETIF_INVALID,\n    FF_NETIF_OK\n} FFNetifDefaultRouteResultStatus;\n\ntypedef struct FFNetifDefaultRouteResult {\n    uint32_t ifIndex;\n\n    #ifndef _WIN32\n    char ifName[IF_NAMESIZE + 1];\n    uint32_t preferredSourceAddrV4;\n    #endif\n    enum FFNetifDefaultRouteResultStatus status;\n} FFNetifDefaultRouteResult;\n\nbool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result);\nbool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result);\n\nconst FFNetifDefaultRouteResult* ffNetifGetDefaultRouteV4(void);\nconst FFNetifDefaultRouteResult* ffNetifGetDefaultRouteV6(void);\n"
  },
  {
    "path": "src/common/networking.h",
    "content": "#pragma once\n\n#include \"common/thread.h\"\n#include \"common/FFstrbuf.h\"\n\n#ifdef _WIN32\n    #include <minwindef.h>\n#endif\n\nstruct addrinfo;\n\ntypedef struct FFNetworkingState {\n    #ifdef _WIN32\n        uintptr_t sockfd;\n        OVERLAPPED overlapped;\n    #else\n        int sockfd;\n        struct addrinfo* addr;\n\n        #ifdef FF_HAVE_THREADS\n            FFThreadType thread;\n        #endif\n    #endif\n\n    FFstrbuf command;\n    uint32_t timeout;\n    bool ipv6;\n    bool compression; // if true, HTTP content compression will be enabled if supported\n    bool tfo; // if true, TCP Fast Open will be attempted first, and fallback to traditional connection if it fails\n} FFNetworkingState;\n\nconst char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, const char* path, const char* headers);\nconst char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer);\n\n#ifdef FF_HAVE_ZLIB\nconst char* ffNetworkingLoadZlibLibrary(void);\nbool ffNetworkingDecompressGzip(FFstrbuf* buffer, char* headerEnd);\n#endif\n"
  },
  {
    "path": "src/common/option.h",
    "content": "#pragma once\n\n#include \"common/FFstrbuf.h\"\n\nstruct yyjson_val;\nstruct yyjson_mut_doc;\nstruct yyjson_mut_val;\n\ntypedef struct FFModuleFormatArg\n{\n    const char* desc;\n    const char* name;\n} FFModuleFormatArg;\n\ntypedef struct FFModuleFormatArgList\n{\n    FFModuleFormatArg* args;\n    uint32_t count;\n} FFModuleFormatArgList;\n\n#define FF_FORMAT_ARG_LIST(list) { .args = list, .count = sizeof(list) / sizeof(FFModuleFormatArg) }\n\n// Must be the first field of FFModuleOptions\ntypedef struct FFModuleBaseInfo\n{\n    const char* name;\n    const char* description;\n    // A dirty polymorphic implementation in C.\n    // This is UB, because `void*` is not compatible with `FF*Options*`.\n    // However we can't do it better unless we move to C++, so that `option` becomes a `this` pointer\n    // https://stackoverflow.com/questions/559581/casting-a-function-pointer-to-another-type\n\n    void (*initOptions)(void* options);\n    void (*destroyOptions)(void* options);\n    void (*parseJsonObject)(void* options, struct yyjson_val *module);\n    bool (*printModule)(void* options); // true on success\n    bool (*generateJsonResult)(void* options, struct yyjson_mut_doc* doc, struct yyjson_mut_val* module); // true on success\n    void (*generateJsonConfig)(void* options, struct yyjson_mut_doc* doc, struct yyjson_mut_val* obj);\n    FFModuleFormatArgList formatArgs;\n} FFModuleBaseInfo;\n\ntypedef enum __attribute__((__packed__)) FFModuleKeyType\n{\n    FF_MODULE_KEY_TYPE_NONE = 0,\n    FF_MODULE_KEY_TYPE_STRING = 1 << 0,\n    FF_MODULE_KEY_TYPE_ICON = 1 << 1,\n    FF_MODULE_KEY_TYPE_SPACE_SHIFT = 4,\n    FF_MODULE_KEY_TYPE_BOTH_0 = FF_MODULE_KEY_TYPE_STRING | FF_MODULE_KEY_TYPE_ICON,\n    FF_MODULE_KEY_TYPE_BOTH_1 = FF_MODULE_KEY_TYPE_BOTH_0 | (1 << FF_MODULE_KEY_TYPE_SPACE_SHIFT),\n    FF_MODULE_KEY_TYPE_BOTH = FF_MODULE_KEY_TYPE_BOTH_1, // alias\n    FF_MODULE_KEY_TYPE_BOTH_2 = FF_MODULE_KEY_TYPE_BOTH_0 | (2 << FF_MODULE_KEY_TYPE_SPACE_SHIFT),\n    FF_MODULE_KEY_TYPE_BOTH_3 = FF_MODULE_KEY_TYPE_BOTH_0 | (3 << FF_MODULE_KEY_TYPE_SPACE_SHIFT),\n    FF_MODULE_KEY_TYPE_BOTH_4 = FF_MODULE_KEY_TYPE_BOTH_0 | (4 << FF_MODULE_KEY_TYPE_SPACE_SHIFT),\n    FF_MODULE_KEY_TYPE_FORCE_UNSIGNED = UINT8_MAX,\n} FFModuleKeyType;\n\ntypedef struct FFModuleArgs\n{\n    FFstrbuf key;\n    FFstrbuf keyColor;\n    FFstrbuf keyIcon;\n    FFstrbuf outputFormat;\n    FFstrbuf outputColor;\n    uint32_t keyWidth;\n} FFModuleArgs;\n\ntypedef struct FFKeyValuePair\n{\n    const char* key;\n    int value;\n} FFKeyValuePair;\n\nconst char* ffOptionTestPrefix(const char* argumentKey, const char* moduleName);\nvoid ffOptionParseString(const char* argumentKey, const char* value, FFstrbuf* buffer);\nFF_C_NODISCARD uint32_t ffOptionParseUInt32(const char* argumentKey, const char* value);\nFF_C_NODISCARD int32_t ffOptionParseInt32(const char* argumentKey, const char* value);\nFF_C_NODISCARD int ffOptionParseEnum(const char* argumentKey, const char* requestedKey, FFKeyValuePair pairs[]);\nFF_C_NODISCARD bool ffOptionParseBoolean(const char* str);\nvoid ffOptionParseColorNoClear(const char* value, FFstrbuf* buffer);\nstatic inline void ffOptionParseColor(const char* value, FFstrbuf* buffer)\n{\n    ffStrbufClear(buffer);\n    ffOptionParseColorNoClear(value, buffer);\n}\n\nstatic inline void ffOptionInitModuleArg(FFModuleArgs* args, const char* icon)\n{\n    ffStrbufInit(&args->key);\n    ffStrbufInit(&args->keyColor);\n    ffStrbufInitStatic(&args->keyIcon, icon);\n    ffStrbufInit(&args->outputFormat);\n    ffStrbufInit(&args->outputColor);\n    args->keyWidth = 0;\n}\n\nstatic inline void ffOptionDestroyModuleArg(FFModuleArgs* args)\n{\n    ffStrbufDestroy(&args->key);\n    ffStrbufDestroy(&args->keyColor);\n    ffStrbufDestroy(&args->keyIcon);\n    ffStrbufDestroy(&args->outputFormat);\n    ffStrbufDestroy(&args->outputColor);\n}\n\nenum { FF_OPTION_MAX_SIZE = 1 << 8 }; // Maximum size of a single option value, used for static allocation\n"
  },
  {
    "path": "src/common/parsing.h",
    "content": "#pragma once\n\n#include \"common/FFstrbuf.h\"\n\n#include <stdint.h>\n\ntypedef struct FFVersion\n{\n    uint32_t major;\n    uint32_t minor;\n    uint32_t patch;\n} FFVersion;\n\ntypedef struct FFColorRangeConfig\n{\n    uint8_t green;\n    uint8_t yellow;\n} FFColorRangeConfig;\n\n#define FF_VERSION_INIT ((FFVersion) {0})\n\nvoid ffParseSemver(FFstrbuf* buffer, const FFstrbuf* major, const FFstrbuf* minor, const FFstrbuf* patch);\nvoid ffParseGTK(FFstrbuf* buffer, const FFstrbuf* gtk2, const FFstrbuf* gtk3, const FFstrbuf* gtk4);\n\nvoid ffVersionToPretty(const FFVersion* version, FFstrbuf* pretty);\nint8_t ffVersionCompare(const FFVersion* version1, const FFVersion* version2);\n"
  },
  {
    "path": "src/common/path.h",
    "content": "#pragma once\n\n#include \"common/FFstrbuf.h\"\n#include \"common/stringUtils.h\"\n\nconst char* ffFindExecutableInPath(const char* name, FFstrbuf* result);\nstatic inline bool ffIsAbsolutePath(const char* path)\n{\n    #ifdef _WIN32\n    return (ffCharIsEnglishAlphabet(path[0]) && path[1] == ':' && (path[2] == '\\\\' || path[2] == '/')) // drive letter path\n        || (path[0] == '\\\\' && path[1] == '\\\\'); // UNC path\n    #else\n    return path[0] == '/';\n    #endif\n}\n\n#if _WIN32\nchar* frealpath(void* __restrict hFile, char* __restrict resolved_name /*MAX_PATH*/);\nchar* realpath(const char* __restrict file_name, char* __restrict resolved_name  /*MAX_PATH*/);\nssize_t freadlink(void* hFile, char* buf, size_t bufsiz);\nssize_t readlink(const char* path, char* buf, size_t bufsiz);\n#endif\n"
  },
  {
    "path": "src/common/percent.h",
    "content": "#pragma once\n\n#include \"common/FFstrbuf.h\"\n#include \"common/parsing.h\"\n#include \"common/option.h\"\n\ntypedef enum __attribute__((__packed__)) FFPercentageTypeFlags\n{\n    FF_PERCENTAGE_TYPE_NONE = 0,\n    FF_PERCENTAGE_TYPE_NUM_BIT = 1 << 0,\n    FF_PERCENTAGE_TYPE_BAR_BIT = 1 << 1,\n    FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT = 1 << 2,\n    FF_PERCENTAGE_TYPE_NUM_COLOR_BIT = 1 << 3,\n    FF_PERCENTAGE_TYPE_BAR_MONOCHROME_BIT = 1 << 4,\n    FF_PERCENTAGE_TYPE_FORCE_UNSIGNED_ = UINT8_MAX,\n} FFPercentageTypeFlags;\nstatic_assert(sizeof(FFPercentageTypeFlags) == 1, \"\");\n\ntypedef struct FFPercentageModuleConfig\n{\n    uint8_t green;\n    uint8_t yellow;\n    FFPercentageTypeFlags type;\n} FFPercentageModuleConfig;\n\n// if (green <= yellow)\n// [0, green]: print green\n// (green, yellow]: print yellow\n// (yellow, 100]: print red\n//\n// if (green > yellow)\n// [green, 100]: print green\n// [yellow, green): print yellow\n// [0, yellow): print red\n\nvoid ffPercentAppendBar(FFstrbuf* buffer, double percent, FFPercentageModuleConfig config, const FFModuleArgs* module);\nvoid ffPercentAppendNum(FFstrbuf* buffer, double percent, FFPercentageModuleConfig config, bool parentheses, const FFModuleArgs* module);\n\ntypedef struct yyjson_val yyjson_val;\ntypedef struct yyjson_mut_doc yyjson_mut_doc;\ntypedef struct yyjson_mut_val yyjson_mut_val;\nbool ffPercentParseCommandOptions(const char* key, const char* subkey, const char* value, FFPercentageModuleConfig* config);\nbool ffPercentParseJsonObject(yyjson_val* key, yyjson_val* value, FFPercentageModuleConfig* config);\nvoid ffPercentGenerateJsonConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, FFPercentageModuleConfig config);\nconst char* ffPercentParseTypeJsonConfig(yyjson_val* value, FFPercentageTypeFlags* result);\n"
  },
  {
    "path": "src/common/printing.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"common/format.h\"\n\ntypedef enum __attribute__((__packed__)) FFPrintType {\n    FF_PRINT_TYPE_DEFAULT = 0,\n    FF_PRINT_TYPE_NO_CUSTOM_KEY = 1 << 0, // key has been formatted outside\n    FF_PRINT_TYPE_NO_CUSTOM_KEY_COLOR = 1 << 1,\n    FF_PRINT_TYPE_NO_CUSTOM_KEY_WIDTH = 1 << 2,\n    FF_PRINT_TYPE_NO_CUSTOM_OUTPUT_FORMAT = 1 << 3, // reserved\n    FF_PRINT_TYPE_FORCE_UNSIGNED = UINT8_MAX,\n} FFPrintType;\n\nvoid ffPrintLogoAndKey(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType);\nvoid ffPrintFormat(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, uint32_t numArgs, const FFformatarg* arguments);\n#define FF_PRINT_FORMAT_CHECKED(moduleName, moduleIndex, moduleArgs, printType, arguments) \\\n    ffPrintFormat((moduleName), (moduleIndex), (moduleArgs), (printType), (sizeof(arguments) / sizeof(*arguments)), (arguments));\nFF_C_PRINTF(5, 6) void ffPrintError(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, const char* message, ...);\nvoid ffPrintColor(const FFstrbuf* colorValue);\nvoid ffPrintCharTimes(char c, uint32_t times);\n"
  },
  {
    "path": "src/common/processing.h",
    "content": "#pragma once\n\n#include \"common/FFstrbuf.h\"\n\n#ifndef _WIN32\n#include <sys/types.h> // pid_t\n#endif\n\ntypedef struct FFProcessHandle {\n    #if _WIN32\n    void* pid; // HANDLE\n    void* pipeRead; // HANDLE\n    #else\n    pid_t pid;\n    int pipeRead;\n    #endif\n} FFProcessHandle;\n\nconst char* ffProcessSpawn(char* const argv[], bool useStdErr, FFProcessHandle* outHandle);\nconst char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer); // Destroys handle internally\n\nstatic inline const char* ffProcessAppendStdOut(FFstrbuf* buffer, char* const argv[])\n{\n    FFProcessHandle handle;\n    const char* error = ffProcessSpawn(argv, false, &handle);\n    if (error) return error;\n\n    error = ffProcessReadOutput(&handle, buffer);\n    if (!error)\n        ffStrbufTrimRightSpace(buffer);\n    return error;\n}\n\nstatic inline const char* ffProcessAppendStdErr(FFstrbuf* buffer, char* const argv[])\n{\n    FFProcessHandle handle;\n    const char* error = ffProcessSpawn(argv, true, &handle);\n    if (error) return error;\n\n    error = ffProcessReadOutput(&handle, buffer);\n    if (!error)\n        ffStrbufTrimRightSpace(buffer);\n    return error;\n}\n\n#ifdef _WIN32\nbool ffProcessGetInfoWindows(uint32_t pid, uint32_t* ppid, FFstrbuf* pname, FFstrbuf* exe, const char** exeName, FFstrbuf* exePath, bool* gui);\n#else\nvoid ffProcessGetInfoLinux(pid_t pid, FFstrbuf* processName, FFstrbuf* exe, const char** exeName, FFstrbuf* exePath);\nconst char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, int32_t* tty);\n#endif\n"
  },
  {
    "path": "src/common/properties.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\ntypedef struct FFpropquery\n{\n    const char* start;\n    FFstrbuf* buffer;\n} FFpropquery;\n\nbool ffParsePropLines(const char* lines, const char* start, FFstrbuf* buffer);\nbool ffParsePropFileValues(const char* filename, uint32_t numQueries, FFpropquery* queries);\nbool ffParsePropFileHomeValues(const char* relativeFile, uint32_t numQueries, FFpropquery* queries);\nbool ffParsePropFileListValues(const FFlist* list, const char* relativeFile, uint32_t numQueries, FFpropquery* queries);\n\nbool ffParsePropLinePointer(const char** line, const char* start, FFstrbuf* buffer);\n\nstatic inline bool ffParsePropLine(const char* line, const char* start, FFstrbuf* buffer)\n{\n    return ffParsePropLinePointer(&line, start, buffer);\n}\n\nstatic inline bool ffParsePropFile(const char* filename, const char* start, FFstrbuf* buffer)\n{\n    return ffParsePropFileValues(filename, 1, (FFpropquery[]){{start, buffer}});\n}\n\nstatic inline bool ffParsePropFileHome(const char* relativeFile, const char* start, FFstrbuf* buffer)\n{\n    return ffParsePropFileHomeValues(relativeFile, 1, (FFpropquery[]){{start, buffer}});\n}\n\nstatic inline bool ffParsePropFileList(const FFlist* list, const char* relativeFile, const char* start, FFstrbuf* buffer)\n{\n    return ffParsePropFileListValues(list, relativeFile, 1, (FFpropquery[]){{start, buffer}});\n}\n\nstatic inline bool ffParsePropFileConfigValues(const char* relativeFile, uint32_t numQueries, FFpropquery* queries)\n{\n    return ffParsePropFileListValues(&instance.state.platform.configDirs, relativeFile, numQueries, queries);\n}\n\nstatic inline bool ffParsePropFileConfig(const char* relativeFile, const char* start, FFstrbuf* buffer)\n{\n    return ffParsePropFileConfigValues(relativeFile, 1, (FFpropquery[]){{start, buffer}});\n}\n\nstatic inline bool ffParsePropFileDataValues(const char* relativeFile, uint32_t numQueries, FFpropquery* queries)\n{\n    return ffParsePropFileListValues(&instance.state.platform.dataDirs, relativeFile, numQueries, queries);\n}\n\nstatic inline bool ffParsePropFileData(const char* relativeFile, const char* start, FFstrbuf* buffer)\n{\n    return ffParsePropFileDataValues(relativeFile, 1, (FFpropquery[]){{start, buffer}});\n}\n"
  },
  {
    "path": "src/common/settings.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\ntypedef enum __attribute__((__packed__)) FFvarianttype\n{\n    FF_VARIANT_TYPE_STRING,\n    FF_VARIANT_TYPE_BOOL,\n    FF_VARIANT_TYPE_INT\n} FFvarianttype;\n\ntypedef union FFvariant\n{\n    const char* strValue;\n    int32_t intValue;\n    struct\n    {\n        bool boolValueSet;\n        bool boolValue;\n    };\n} FFvariant;\n\n#define FF_VARIANT_NULL ((FFvariant){.strValue = NULL})\n\nFFvariant ffSettingsGetDConf(const char* key, FFvarianttype type);\nFFvariant ffSettingsGetGSettings(const char* schemaName, const char* path, const char* key, FFvarianttype type);\nFFvariant ffSettingsGetGnome(const char* dconfKey, const char* gsettingsSchemaName, const char* gsettingsPath, const char* gsettingsKey, FFvarianttype type);\nFFvariant ffSettingsGetXFConf(const char* channelName, const char* propertyName, FFvarianttype type);\ntypedef bool FFTestXfconfPropCallback(void* data, const char* propertyName); // Return false to break loop\nFFvariant ffSettingsGetXFConfFirstMatch(const char* channelName, const char* propertyPrefix, FFvarianttype type, void* data, FFTestXfconfPropCallback* cb);\n\nint ffSettingsGetSQLite3Int(const char* dbPath, const char* query);\nbool ffSettingsGetSQLite3String(const char* dbPath, const char* query, FFstrbuf* result);\n\n#ifdef __ANDROID__\nbool ffSettingsGetAndroidProperty(const char* propName, FFstrbuf* result);\n#elif defined(__FreeBSD__)\nbool ffSettingsGetFreeBSDKenv(const char* propName, FFstrbuf* result);\n#endif\n"
  },
  {
    "path": "src/common/size.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\nvoid ffSizeAppendNum(uint64_t bytes, FFstrbuf* result);\n"
  },
  {
    "path": "src/common/smbiosHelper.h",
    "content": "#pragma once\n\n#include \"common/FFstrbuf.h\"\n\nbool ffIsSmbiosValueSet(FFstrbuf* value);\nstatic inline void ffCleanUpSmbiosValue(FFstrbuf* value)\n{\n    if (!ffIsSmbiosValueSet(value))\n        ffStrbufClear(value);\n}\n\n// https://github.com/KunYi/DumpSMBIOS\n// https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.7.0.pdf\n\ntypedef enum __attribute__((__packed__)) FFSmbiosType // : uint8_t\n{\n    FF_SMBIOS_TYPE_BIOS = 0,\n    FF_SMBIOS_TYPE_SYSTEM_INFO = 1,\n    FF_SMBIOS_TYPE_BASEBOARD_INFO = 2,\n    FF_SMBIOS_TYPE_SYSTEM_ENCLOSURE = 3,\n    FF_SMBIOS_TYPE_PROCESSOR_INFO = 4,\n    FF_SMBIOS_TYPE_MEMORY_CONTROLLER_INFO = 5, // obsolete\n    FF_SMBIOS_TYPE_MEMORY_MODULE_INFO = 6, // obsolete\n    FF_SMBIOS_TYPE_CACHE_INFO = 7,\n    FF_SMBIOS_TYPE_PORT_CONNECTOR_INFO = 8,\n    FF_SMBIOS_TYPE_SYSTEM_SLOTS = 9,\n    FF_SMBIOS_TYPE_ON_BOARD_DEVICES_INFO = 10, // obsolete\n    FF_SMBIOS_TYPE_OEM_STRING = 11,\n    FF_SMBIOS_TYPE_SYSTEM_CONFIGURATION_OPTIONS = 12,\n    FF_SMBIOS_TYPE_BIOS_LANGUAGE_INFO = 13,\n    FF_SMBIOS_TYPE_GROUP_ASSOCIATIONS = 14,\n    FF_SMBIOS_TYPE_SYSTEM_EVENT_LOG = 15,\n    FF_SMBIOS_TYPE_PHYSICAL_MEMORY_ARRAY = 16,\n    FF_SMBIOS_TYPE_MEMORY_DEVICE = 17,\n    FF_SMBIOS_TYPE_32BIT_MEMORY_ERROR_INFO = 18,\n    FF_SMBIOS_TYPE_MEMORY_ARRAY_MAPPED_ADDRESS = 19,\n    FF_SMBIOS_TYPE_MEMORY_DEVICE_MAPPED_ADDRESS = 20,\n    FF_SMBIOS_TYPE_BUILTIN_POINTING_DEVICE = 21,\n    FF_SMBIOS_TYPE_PORTABLE_BATTERY = 22,\n    FF_SMBIOS_TYPE_SYSTEM_RESET = 23,\n    FF_SMBIOS_TYPE_HARDWARE_SECURITY = 24,\n    FF_SMBIOS_TYPE_SYSTEM_POWER_CONTROLS = 25,\n    FF_SMBIOS_TYPE_VOLTAGE_PROBE = 26,\n    FF_SMBIOS_TYPE_COOLING_DEVICE = 27,\n    FF_SMBIOS_TYPE_TEMPERATURE_PROBE = 28,\n    FF_SMBIOS_TYPE_ELECTRICAL_CURRENT_PROBE = 29,\n    FF_SMBIOS_TYPE_OUT_OF_BAND_REMOTE_ACCESS = 30,\n    FF_SMBIOS_TYPE_BOOT_INTEGRITY_SERVICES_ENTRY_POINT = 31, // reserved\n    FF_SMBIOS_TYPE_SYSTEM_BOOT_INFO = 32,\n    FF_SMBIOS_TYPE_64BIT_MEMORY_ERROR_INFO = 33,\n    FF_SMBIOS_TYPE_MANAGEMENT_DEVICE = 34,\n    FF_SMBIOS_TYPE_MANAGEMENT_DEVICE_COMPONENT = 35,\n    FF_SMBIOS_TYPE_MANAGEMENT_DEVICE_THRESHOLD_DATA = 36,\n    FF_SMBIOS_TYPE_MEMORY_CHANNEL = 37,\n    FF_SMBIOS_TYPE_IPMI_DEVICE_INFO = 38,\n    FF_SMBIOS_TYPE_SYSTEM_POWER_SUPPLY = 39,\n    FF_SMBIOS_TYPE_ADDITIONAL_INFO = 40,\n    FF_SMBIOS_TYPE_ONBOARD_DEVICE_EXTENDED_INFO = 41,\n    FF_SMBIOS_TYPE_MANAGEMENT_CONTROLLER_HOST_INTERFACE = 42,\n    FF_SMBIOS_TYPE_TPM_DEVICE = 43,\n    FF_SMBIOS_TYPE_PROCESSOR_ADDITIONAL_INFO = 44,\n    FF_SMBIOS_TYPE_FIRMWARE_INVENTORY_INFO = 45,\n    FF_SMBIOS_TYPE_STRING_PROPERTY = 46,\n    FF_SMBIOS_TYPE_INACTIVE = 126,\n    FF_SMBIOS_TYPE_END_OF_TABLE = 127,\n    // system- and OEM-specific information 128~256\n} FFSmbiosType;\nstatic_assert(sizeof(FFSmbiosType) == 1, \"FFSmbiosType should be 1 byte\");\n\ntypedef struct FFSmbiosHeader\n{\n    FFSmbiosType Type;\n    uint8_t Length;\n    uint16_t Handle;\n} __attribute__((__packed__)) FFSmbiosHeader;\nstatic_assert(sizeof(FFSmbiosHeader) == 4, \"FFSmbiosHeader should be 4 bytes\");\n\nstatic inline const char* ffSmbiosLocateString(const char* start, uint8_t index /* start from 1 */)\n{\n    if (index == 0 || *start == '\\0')\n        return NULL;\n    while (--index)\n        start += strlen(start) + 1;\n    return start;\n}\n\ntypedef const FFSmbiosHeader* FFSmbiosHeaderTable[FF_SMBIOS_TYPE_END_OF_TABLE];\n\nconst FFSmbiosHeader* ffSmbiosNextEntry(const FFSmbiosHeader* header);\nconst FFSmbiosHeaderTable* ffGetSmbiosHeaderTable();\n\n#ifdef __linux__\nbool ffGetSmbiosValue(const char* devicesPath, const char* classPath, FFstrbuf* buffer);\n#endif\n"
  },
  {
    "path": "src/common/stringUtils.h",
    "content": "#pragma once\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <string.h>\n#include <ctype.h>\n\nstatic inline bool ffStrSet(const char* str)\n{\n    if(str == NULL)\n        return false;\n\n    while(isspace(*str))\n        str++;\n\n    return *str != '\\0';\n}\n\n\nstatic inline bool ffStrStartsWithIgnCase(const char* str, const char* compareTo)\n{\n    return strncasecmp(str, compareTo, strlen(compareTo)) == 0;\n}\n\nstatic inline bool ffStrEqualsIgnCase(const char* str, const char* compareTo)\n{\n    return strcasecmp(str, compareTo) == 0;\n}\n\nstatic inline bool ffStrStartsWith(const char* str, const char* compareTo)\n{\n    return strncmp(str, compareTo, strlen(compareTo)) == 0;\n}\n\nstatic inline bool ffStrEndsWith(const char* str, const char* compareTo)\n{\n    size_t strLength = strlen(str);\n    size_t compareToLength = strlen(compareTo);\n    if (strLength < compareToLength)\n        return false;\n    return memcmp(str + strLength - compareToLength, compareTo, compareToLength) == 0;\n}\n\nstatic inline bool ffStrEndsWithIgnCase(const char* str, const char* compareTo)\n{\n    size_t strLength = strlen(str);\n    size_t compareToLength = strlen(compareTo);\n    if (strLength < compareToLength)\n        return false;\n    return strncasecmp(str + strLength - compareToLength, compareTo, compareToLength) == 0;\n}\n\nstatic inline bool ffStrEquals(const char* str, const char* compareTo)\n{\n    return strcmp(str, compareTo) == 0;\n}\n\nstatic inline bool ffStrContains(const char* str, const char* compareTo)\n{\n    return strstr(str, compareTo) != NULL;\n}\n\nstatic inline bool ffStrContainsIgnCase(const char* str, const char* compareTo)\n{\n    return strcasestr(str, compareTo) != NULL;\n}\n\nstatic inline bool ffStrContainsC(const char* str, char compareTo)\n{\n    return strchr(str, compareTo) != NULL;\n}\n\nstatic inline bool ffCharIsEnglishAlphabet(char c)\n{\n    return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');\n}\n\nstatic inline bool ffCharIsDigit(char c)\n{\n    return '0' <= c && c <= '9';\n}\n\n// Copies at most (dstBufSiz - 1) bytes from src to dst; dst is always null-terminated\nstatic inline char* ffStrCopy(char* __restrict__ dst, const char* __restrict__ src, size_t dstBufSiz)\n{\n    if (__builtin_expect(dst == NULL, false) || dstBufSiz == 0) return dst;\n\n    size_t len = strnlen(src, dstBufSiz - 1);\n    memcpy(dst, src, len);\n    dst[len] = '\\0';\n    return dst + len;\n}\n"
  },
  {
    "path": "src/common/sysctl.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"common/FFcheckmacros.h\"\n\n#include <sys/types.h>\n#include <sys/sysctl.h>\n\n#ifdef __OpenBSD__\nconst char* ffSysctlGetString(int mib1, int mib2, FFstrbuf* result);\nFF_C_NODISCARD int ffSysctlGetInt(int mib1, int mib2, int defaultValue);\nFF_C_NODISCARD int64_t ffSysctlGetInt64(int mib1, int mib2, int64_t defaultValue);\n#else\nconst char* ffSysctlGetString(const char* propName, FFstrbuf* result);\nFF_C_NODISCARD int ffSysctlGetInt(const char* propName, int defaultValue);\nFF_C_NODISCARD int64_t ffSysctlGetInt64(const char* propName, int64_t defaultValue);\n#endif\nFF_C_NODISCARD void* ffSysctlGetData(int* request, u_int requestLength, size_t* resultLength);\n"
  },
  {
    "path": "src/common/temps.h",
    "content": "#pragma once\n\n#include \"common/parsing.h\"\n#include \"common/option.h\"\n\nvoid ffTempsAppendNum(double celsius, FFstrbuf* buffer, FFColorRangeConfig config, const FFModuleArgs* module);\nbool ffTempsParseCommandOptions(const char* key, const char* subkey, const char* value, bool* useTemp, FFColorRangeConfig* config);\nbool ffTempsParseJsonObject(yyjson_val* key, yyjson_val* value, bool* useTemp, FFColorRangeConfig* config);\nvoid ffTempsGenerateJsonConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, bool temp, FFColorRangeConfig config);\n"
  },
  {
    "path": "src/common/textModifier.h",
    "content": "#pragma once\n\n#define FASTFETCH_TEXT_MODIFIER_BOLT  \"\\033[1m\"\n#define FASTFETCH_TEXT_MODIFIER_ERROR \"\\033[1;31m\"\n#define FASTFETCH_TEXT_MODIFIER_RESET \"\\033[m\"\n"
  },
  {
    "path": "src/common/thread.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\n#ifdef FF_HAVE_THREADS\n    #if defined(_WIN32)\n        #include <winternl.h>\n        #include <synchapi.h>\n        #include <process.h>\n        #include <processthreadsapi.h>\n        #define FF_THREAD_MUTEX_INITIALIZER SRWLOCK_INIT\n        typedef SRWLOCK FFThreadMutex;\n        typedef HANDLE FFThreadType;\n        static inline void ffThreadMutexLock(FFThreadMutex* mutex) { AcquireSRWLockExclusive(mutex); }\n        static inline void ffThreadMutexUnlock(FFThreadMutex* mutex) { ReleaseSRWLockExclusive(mutex); }\n        static inline FFThreadType ffThreadCreate(unsigned (__stdcall* func)(void*), void* data) {\n            return (FFThreadType)_beginthreadex(NULL, 0, func, data, 0, NULL);\n        }\n        #define FF_THREAD_ENTRY_DECL_WRAPPER(fn, paramType) static __stdcall unsigned fn ## ThreadMain (void* data) { fn((paramType)data); return 0; }\n        #define FF_THREAD_ENTRY_DECL_WRAPPER_NOPARAM(fn) static __stdcall unsigned fn ## ThreadMain () { fn(); return 0; }\n        static inline void ffThreadDetach(FFThreadType thread) { NtClose(thread); }\n        static inline bool ffThreadJoin(FFThreadType thread, uint32_t timeout)\n        {\n            if (NtWaitForSingleObject(thread, TRUE, timeout == 0 ? NULL : &(LARGE_INTEGER) { .QuadPart = (int64_t) timeout * -10000 }) != STATUS_WAIT_0)\n            {\n                TerminateThread(thread, (DWORD) -1);\n                NtClose(thread);\n                return false;\n            }\n            NtClose(thread);\n            return true;\n        }\n    #else\n        #include <pthread.h>\n        #include <signal.h>\n        #if FF_HAVE_PTHREAD_NP\n            #include <pthread_np.h>\n        #endif\n        typedef pthread_t FFThreadType;\n        #if __APPLE__\n            #include <os/lock.h>\n            #define FF_THREAD_MUTEX_INITIALIZER OS_UNFAIR_LOCK_INIT\n            typedef os_unfair_lock FFThreadMutex;\n            static inline void ffThreadMutexLock(os_unfair_lock* mutex) { os_unfair_lock_lock(mutex); }\n            static inline void ffThreadMutexUnlock(os_unfair_lock* mutex) { os_unfair_lock_unlock(mutex); }\n        #else\n            #define FF_THREAD_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER\n            typedef pthread_mutex_t FFThreadMutex;\n            static inline void ffThreadMutexLock(FFThreadMutex* mutex) { pthread_mutex_lock(mutex); }\n            static inline void ffThreadMutexUnlock(FFThreadMutex* mutex) { pthread_mutex_unlock(mutex); }\n        #endif\n        static inline FFThreadType ffThreadCreate(void* (* func)(void*), void* data) {\n            FFThreadType newThread = 0;\n            pthread_create(&newThread, NULL, func, data);\n            return newThread;\n        }\n        #define FF_THREAD_ENTRY_DECL_WRAPPER(fn, paramType) static void* fn ## ThreadMain (void* data) { fn((paramType)data); return NULL; }\n        #define FF_THREAD_ENTRY_DECL_WRAPPER_NOPARAM(fn) static void* fn ## ThreadMain () { fn(); return NULL; }\n        static inline void ffThreadDetach(FFThreadType thread) { pthread_detach(thread); }\n        static inline bool ffThreadJoin(FFThreadType thread, FF_MAYBE_UNUSED uint32_t timeout)\n        {\n            #if HAVE_TIMEDJOIN_NP\n                if (timeout > 0)\n                {\n                    struct timespec ts;\n                    if (clock_gettime(CLOCK_REALTIME, &ts) == 0)\n                    {\n                        ts.tv_sec += timeout / 1000;\n                        ts.tv_nsec += (timeout % 1000) * 1000000;\n                        if (pthread_timedjoin_np(thread, NULL, &ts) != 0)\n                        {\n                            pthread_kill(thread, SIGTERM);\n                            return false;\n                        }\n                        return true;\n                    }\n                }\n            #endif\n            pthread_join(thread, NULL);\n            return true;\n        }\n    #endif\n#else //FF_HAVE_THREADS\n    #define FF_THREAD_MUTEX_INITIALIZER 0\n    typedef char FFThreadMutex;\n    static inline void ffThreadMutexLock(FFThreadMutex* mutex) { FF_UNUSED(mutex) }\n    static inline void ffThreadMutexUnlock(FFThreadMutex* mutex) { FF_UNUSED(mutex) }\n    #define FF_THREAD_ENTRY_DECL_WRAPPER(fn, paramType)\n#endif //FF_HAVE_THREADS\n"
  },
  {
    "path": "src/common/time.h",
    "content": "#pragma once\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <time.h>\n#ifdef _WIN32\n    #include <ntstatus.h>\n    #include \"common/windows/nt.h\"\n    #include <profileapi.h>\n#elif defined(__HAIKU__)\n    #include <OS.h>\n#endif\n\n#include \"common/arrayUtils.h\"\n\nstatic inline double ffTimeGetTick(void) //In msec\n{\n    #ifdef _WIN32\n        extern double ffQpcMultiplier;\n        LARGE_INTEGER start;\n        QueryPerformanceCounter(&start);\n        return (double) start.QuadPart * ffQpcMultiplier;\n    #elif defined(__HAIKU__)\n        return (double) system_time() / 1000.;\n    #else\n        struct timespec timeNow;\n        clock_gettime(CLOCK_MONOTONIC, &timeNow);\n        return (double) timeNow.tv_sec * 1000. + (double) timeNow.tv_nsec / 1000000.;\n    #endif\n}\n\n#if _WIN32\nstatic inline uint64_t ffFileTimeToUnixMs(uint64_t value)\n{\n    if (__builtin_expect(__builtin_usubll_overflow(value, 116444736000000000ull, &value), false))\n        return 0;\n    return value / 10000ull;\n}\n#endif\n\nstatic inline uint64_t ffTimeGetNow(void)\n{\n    #ifdef _WIN32\n        uint64_t timeNow = ffKSystemTimeToUInt64(&SharedUserData->SystemTime);\n        return ffFileTimeToUnixMs((uint64_t) timeNow);\n    #elif defined(__HAIKU__)\n        return (uint64_t) real_time_clock_usecs() / 1000u;\n    #else\n        struct timespec timeNow;\n        clock_gettime(CLOCK_REALTIME, &timeNow);\n        return (uint64_t)(((uint64_t) timeNow.tv_sec * 1000u) + ((uint64_t) timeNow.tv_nsec / 1000000u));\n    #endif\n}\n\n// Returns true if not interrupted\nstatic inline bool ffTimeSleep(uint32_t msec)\n{\n    #ifdef _WIN32\n        LARGE_INTEGER interval;\n        interval.QuadPart = -(int64_t) msec * 10000; // Relative time in 100-nanosecond intervals\n        return NT_SUCCESS(NtDelayExecution(TRUE, &interval));\n    #else\n        return nanosleep(&(struct timespec){ msec / 1000, (long) (msec % 1000) * 1000000 }, NULL) == 0;\n    #endif\n}\n\n// Not thread-safe\nconst char* ffTimeToFullStr(uint64_t msec);\n\n// Not thread-safe\nconst char* ffTimeToShortStr(uint64_t msec);\n\n// Not thread-safe\nconst char* ffTimeToTimeStr(uint64_t msec);\n\ntypedef struct FFTimeGetAgeResult\n{\n    uint32_t years;\n    uint32_t daysOfYear;\n    double yearsFraction;\n} FFTimeGetAgeResult;\n\nFFTimeGetAgeResult ffTimeGetAge(uint64_t birthMs, uint64_t nowMs);\n"
  },
  {
    "path": "src/common/unused.h",
    "content": "#pragma once\n\nstatic inline void ffUnused(int dummy, ...) { (void) dummy; }\n#define FF_UNUSED(...) ffUnused(0, __VA_ARGS__);\n#if defined(__GNUC__) || defined(__clang__)\n    #define FF_MAYBE_UNUSED __attribute__ ((__unused__))\n#else\n    #define FF_MAYBE_UNUSED\n#endif\n"
  },
  {
    "path": "src/common/wcwidth.h",
    "content": "#pragma once\n\n#include <stdint.h>\n\n#ifdef FF_HAVE_WCWIDTH\n#include <wchar.h>\n\n// Should be char32_t but it's not defined on macOS\nstatic_assert(sizeof(wchar_t) == sizeof(uint32_t), \"wcwidth implementation requires wchar_t to be 32 bits\");\n\nstatic inline int mk_wcwidth(uint32_t ucs) {\n    return wcwidth((wchar_t) ucs);\n}\n#else\nint mk_wcwidth(uint32_t wc);\n#endif\n"
  },
  {
    "path": "src/common/windows/c-logo.sh",
    "content": "#!/bin/sh\n# Convert logo.svg to logo.ico\nrsvg-convert -w 16 -h 16 logo.svg > logo16.png\nrsvg-convert -w 32 -h 32 logo.svg > logo32.png\nrsvg-convert -w 48 -h 48 logo.svg > logo48.png\nrsvg-convert -w 64 -h 64 logo.svg > logo64.png\nrsvg-convert -w 128 -h 128 logo.svg > logo128.png\nrsvg-convert -w 256 -h 256 logo.svg > logo256.png\nconvert logo16.png logo32.png logo48.png logo64.png logo128.png logo256.png logo.ico\nrm logo16.png logo32.png logo48.png logo64.png logo128.png logo256.png\n"
  },
  {
    "path": "src/common/windows/com.cpp",
    "content": "#include \"com.hpp\"\n#include \"fastfetch.h\"\n\n#include <stdlib.h>\n\n//https://learn.microsoft.com/en-us/windows/win32/wmisdk/example--getting-wmi-data-from-the-local-computer\n//https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/computer-system-hardware-classes\nstatic void CoUninitializeWrap(void)\n{\n    CoUninitialize();\n}\n\nstatic const char* doInitCom()\n{\n    // Initialize COM\n    if (FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED)))\n        return \"CoInitializeEx() failed\";\n\n    // Set general COM security levels\n\n    HRESULT hRes = CoInitializeSecurity(\n        NULL,\n        -1,                          // COM authentication\n        NULL,                        // Authentication services\n        NULL,                        // Reserved\n        RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication\n        RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation\n        NULL,                        // Authentication info\n        EOAC_NONE,                   // Additional capabilities\n        NULL                         // Reserved\n    );\n\n    if (FAILED(hRes) && hRes != RPC_E_TOO_LATE /* Has been set by a random dll */)\n    {\n        CoUninitialize();\n        return \"CoInitializeSecurity() failed\";\n    }\n\n    atexit(CoUninitializeWrap);\n    return NULL;\n}\n\nconst char* ffInitCom(void)\n{\n    static const char* error = \"\";\n    if (error && error[0] == '\\0')\n        error = doInitCom();\n    return error;\n}\n"
  },
  {
    "path": "src/common/windows/com.hpp",
    "content": "#pragma once\n\n#ifdef __cplusplus\n\n#include <unknwn.h>\n\nconst char* ffInitCom(void);\n\nstatic inline void ffReleaseComObject(void* ppUnknown)\n{\n    IUnknown* pUnknown = *(IUnknown**) ppUnknown;\n    if (pUnknown) pUnknown->Release();\n}\n\n#define FF_AUTO_RELEASE_COM_OBJECT __attribute__((__cleanup__(ffReleaseComObject)))\n\n#else\n    // Win32 COM headers requires C++ compiler\n    #error Must be included in C++ source file\n#endif\n"
  },
  {
    "path": "src/common/windows/getline.c",
    "content": "#include \"getline.h\"\n\n#include <stdlib.h>\n#include <errno.h>\n\nssize_t getline(char **lineptr, size_t *n, FILE *stream) {\n    ssize_t pos = -1;\n    int c;\n\n    if (lineptr == NULL || stream == NULL || n == NULL) {\n        errno = EINVAL;\n        return -1;\n    }\n\n    _lock_file(stream);\n\n    c = _getc_nolock(stream);\n    if (c == EOF) {\n        goto exit;\n    }\n\n    if (*lineptr == NULL) {\n        *lineptr = malloc(128);\n        if (*lineptr == NULL) {\n            goto exit;\n        }\n        *n = 128;\n    }\n\n    pos = 0;\n    while(c != EOF) {\n        if ((size_t)(pos + 1) >= *n) {\n            size_t new_size = *n + (*n >> 2);\n            if (new_size < 128) {\n                new_size = 128;\n            }\n            char *new_ptr = realloc(*lineptr, new_size);\n            if (new_ptr == NULL) {\n                pos = -1;\n                goto exit;\n            }\n            *n = new_size;\n            *lineptr = new_ptr;\n        }\n\n        ((char *)(*lineptr))[pos ++] = (char)c;\n        if (c == '\\n') {\n            break;\n        }\n        c = _getc_nolock(stream);\n    }\n\n    (*lineptr)[pos] = '\\0';\n\nexit:\n    _unlock_file(stream);\n    return pos;\n}\n"
  },
  {
    "path": "src/common/windows/getline.h",
    "content": "#pragma once\n\n#include <stdint.h>\n#include <stdio.h>\n\nssize_t getline(char **lineptr, size_t *n, FILE *stream);\n"
  },
  {
    "path": "src/common/windows/manifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\" xmlns:asmv3=\"urn:schemas-microsoft-com:asm.v3\">\n  <compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">\n    <application>\n      <!--This Id value indicates the application supports Windows 7 functionality-->\n      <supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\"/>\n      <!--This Id value indicates the application supports Windows 8 functionality-->\n      <supportedOS Id=\"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}\"/>\n      <!--This Id value indicates the application supports Windows 8.1 functionality-->\n      <supportedOS Id=\"{1f676c76-80e1-4239-95bb-83d0f6d0da78}\"/>\n      <!--This Id value indicates the application supports Windows 10, 11 functionality-->\n      <supportedOS Id=\"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}\"/>\n    </application>\n  </compatibility>\n  <assemblyIdentity type=\"win32\" name=\"com.github.fastfetch\" version=\"0.0.0.0\"/>\n  <asmv3:application>\n    <asmv3:windowsSettings>\n      <activeCodePage xmlns=\"http://schemas.microsoft.com/SMI/2019/WindowsSettings\">UTF-8</activeCodePage>\n      <dpiAware xmlns=\"http://schemas.microsoft.com/SMI/2005/WindowsSettings\">true/pm</dpiAware>\n      <dpiAwareness xmlns=\"http://schemas.microsoft.com/SMI/2016/WindowsSettings\">PerMonitor</dpiAwareness>\n      <heapType xmlns=\"http://schemas.microsoft.com/SMI/2020/WindowsSettings\">SegmentHeap</heapType>\n    </asmv3:windowsSettings>\n  </asmv3:application>\n</assembly>\n"
  },
  {
    "path": "src/common/windows/nt.h",
    "content": "#pragma once\n\n#include <ntdef.h>\n#include <winternl.h>\n#include <winnt.h>\n#include <stdint.h>\n#include <assert.h>\n\nenum {\n    SystemModuleInformation = 11,\n    SystemFirmwareTableInformation = 76,\n    SystemBootEnvironmentInformation = 90,\n    SystemLogicalProcessorAndGroupInformation = 107,\n    SystemSecureBootInformation = 146,\n};\n\n#define D3DKMT_ALIGN64 __attribute__((aligned(8)))\n\ntypedef struct _PROCESSOR_POWER_INFORMATION {\n    ULONG Number;\n    ULONG MaxMhz;\n    ULONG CurrentMhz;\n    ULONG MhzLimit;\n    ULONG MaxIdleState;\n    ULONG CurrentIdleState;\n} PROCESSOR_POWER_INFORMATION, *PPROCESSOR_POWER_INFORMATION;\n\nNTSYSAPI NTSTATUS NTAPI NtPowerInformation(\n    IN POWER_INFORMATION_LEVEL InformationLevel,\n    IN PVOID InputBuffer OPTIONAL,\n    IN ULONG InputBufferLength,\n    OUT PVOID OutputBuffer OPTIONAL,\n    IN ULONG OutputBufferLength);\n\n\nNTSYSAPI NTSTATUS NTAPI RtlGetVersion(\n    _Inout_ PRTL_OSVERSIONINFOW lpVersionInformation\n);\n\n#if __has_include(<d3dkmthk.h>)\n#include <d3dkmthk.h>\n#else\ntypedef UINT D3DKMT_HANDLE;\n\ntypedef struct _D3DKMT_OPENADAPTERFROMLUID\n{\n    LUID            AdapterLuid;\n    D3DKMT_HANDLE   hAdapter;\n} D3DKMT_OPENADAPTERFROMLUID;\nEXTERN_C _Check_return_ NTSTATUS APIENTRY D3DKMTOpenAdapterFromLuid(_Inout_ CONST D3DKMT_OPENADAPTERFROMLUID*);\n\ntypedef struct _D3DKMT_CLOSEADAPTER\n{\n    D3DKMT_HANDLE   hAdapter;   // in: adapter handle\n} D3DKMT_CLOSEADAPTER;\nEXTERN_C _Check_return_ NTSTATUS APIENTRY D3DKMTCloseAdapter(_In_ CONST D3DKMT_CLOSEADAPTER*);\n\ntypedef struct _D3DKMT_ADAPTERTYPE\n{\n    union\n    {\n        struct\n        {\n            UINT   RenderSupported              :  1; // WDDM 1.2, Windows 8\n            UINT   DisplaySupported             :  1;\n            UINT   SoftwareDevice               :  1;\n            UINT   PostDevice                   :  1;\n            UINT   HybridDiscrete               :  1; // WDDM 1.3, Windows 8.1\n            UINT   HybridIntegrated             :  1;\n            UINT   IndirectDisplayDevice        :  1;\n            UINT   Paravirtualized              :  1; // WDDM 2.3, Windows 10 Fall Creators Update (version 1709)\n            UINT   ACGSupported                 :  1;\n            UINT   SupportSetTimingsFromVidPn   :  1;\n            UINT   Detachable                   :  1;\n            UINT   ComputeOnly                  :  1; // WDDM 2.6, Windows 10 May 2019 Update (Version 1903)\n            UINT   Prototype                    :  1;\n            UINT   RuntimePowerManagement       :  1; // WDDM 2.9, Windows 10 Insider Preview \"Iron\"\n            UINT   Reserved                     : 18;\n        };\n        UINT Value;\n    };\n} D3DKMT_ADAPTERTYPE;\n\ntypedef enum _KMTQUERYADAPTERINFOTYPE\n{\n    KMTQAITYPE_ADAPTERTYPE = 15,  // WDDM 1.2, Windows 8\n    KMTQAITYPE_NODEMETADATA = 25, // WDDM 2.0, Windows 10\n} KMTQUERYADAPTERINFOTYPE;\ntypedef struct _D3DKMT_QUERYADAPTERINFO\n{\n    D3DKMT_HANDLE           hAdapter;\n    KMTQUERYADAPTERINFOTYPE Type;\n    VOID*                   pPrivateDriverData;\n    UINT                    PrivateDriverDataSize;\n} D3DKMT_QUERYADAPTERINFO;\nEXTERN_C _Check_return_ NTSTATUS APIENTRY D3DKMTQueryAdapterInfo(_Inout_ CONST D3DKMT_QUERYADAPTERINFO*);\n\ntypedef enum _D3DKMT_QUERYSTATISTICS_TYPE\n{\n    D3DKMT_QUERYSTATISTICS_PHYSICAL_ADAPTER       = 10, // WDDM 2.4, Windows 10 April 2018 Update (version 1803)\n    D3DKMT_QUERYSTATISTICS_NODE2                  = 18, // WDDM 3.1, Windows 11 2022 Update (version 22H2)\n} D3DKMT_QUERYSTATISTICS_TYPE;\ntypedef struct _D3DKMT_QUERYSTATISTICS_QUERY_PHYSICAL_ADAPTER\n{\n    ULONG PhysicalAdapterIndex;\n} D3DKMT_QUERYSTATISTICS_QUERY_PHYSICAL_ADAPTER;\ntypedef struct _D3DKMT_QUERYSTATISTICS_QUERY_NODE2\n{\n    UINT16 PhysicalAdapterIndex;\n    UINT16 NodeOrdinal;\n} D3DKMT_QUERYSTATISTICS_QUERY_NODE2;\ntypedef struct _D3DKMT_ADAPTER_PERFDATA\n{\n    UINT32          PhysicalAdapterIndex;   // in: The physical adapter index, in an LDA chain\n    D3DKMT_ALIGN64 ULONGLONG MemoryFrequency;        // out: Clock frequency of the memory in hertz\n    D3DKMT_ALIGN64 ULONGLONG MaxMemoryFrequency;     // out: Max memory clock frequency\n    D3DKMT_ALIGN64 ULONGLONG MaxMemoryFrequencyOC;   // out: Clock frequency of the memory while overclocked in hertz.\n    D3DKMT_ALIGN64 ULONGLONG MemoryBandwidth;        // out: Amount of memory transferred in bytes\n    D3DKMT_ALIGN64 ULONGLONG PCIEBandwidth;          // out: Amount of memory transferred over PCI-E in bytes\n    ULONG           FanRPM;                 // out: Fan rpm\n    ULONG           Power;                  // out: Power draw of the adapter in tenths of a percentage\n    ULONG           Temperature;            // out: Temperature in deci-Celsius 1 = 0.1C\n    UCHAR           PowerStateOverride;     // out: Overrides dxgkrnls power view of linked adapters.\n} D3DKMT_ADAPTER_PERFDATA;\ntypedef struct _D3DKMT_ADAPTER_PERFDATACAPS\n{\n    UINT32      PhysicalAdapterIndex;   // in: The physical adapter index, in an LDA chain\n    D3DKMT_ALIGN64 ULONGLONG MaxMemoryBandwidth;     // out: Max memory bandwidth in bytes for 1 second\n    D3DKMT_ALIGN64 ULONGLONG MaxPCIEBandwidth;       // out: Max pcie bandwidth in bytes for 1 second\n    ULONG       MaxFanRPM;              // out: Max fan rpm\n    ULONG       TemperatureMax;         // out: Max temperature before damage levels\n    ULONG       TemperatureWarning;     // out: The temperature level where throttling begins.\n} D3DKMT_ADAPTER_PERFDATACAPS;\n\n#define DXGK_MAX_GPUVERSION_NAME_LENGTH 32\ntypedef struct _D3DKMT_GPUVERSION\n{\n    UINT32          PhysicalAdapterIndex;                             // in: The physical adapter index, in an LDA chain\n    WCHAR           BiosVersion[DXGK_MAX_GPUVERSION_NAME_LENGTH];     //out: The gpu bios version\n    WCHAR           GpuArchitecture[DXGK_MAX_GPUVERSION_NAME_LENGTH]; //out: The gpu architectures name.\n} D3DKMT_GPUVERSION;\ntypedef struct _D3DKMT_QUERYSTATISTICS_PHYSICAL_ADAPTER_INFORMATION\n{\n    D3DKMT_ADAPTER_PERFDATA      AdapterPerfData;\n    D3DKMT_ADAPTER_PERFDATACAPS  AdapterPerfDataCaps;\n    D3DKMT_GPUVERSION            GpuVersion;\n} D3DKMT_QUERYSTATISTICS_PHYSICAL_ADAPTER_INFORMATION;\ntypedef struct _D3DKMT_QUERYSTATISTICS_PROCESS_NODE_INFORMATION {\n    D3DKMT_ALIGN64 UINT64                         Reserved[34];\n} D3DKMT_QUERYSTATISTICS_PROCESS_NODE_INFORMATION;\ntypedef struct _D3DKMT_NODE_PERFDATA\n{\n    UINT32          NodeOrdinal;            // in: Node ordinal of the requested engine.\n    UINT32          PhysicalAdapterIndex;   // in: The physical adapter index, in an LDA chain\n    D3DKMT_ALIGN64 ULONGLONG Frequency;     // out: Clock frequency of the engine in hertz\n    D3DKMT_ALIGN64 ULONGLONG MaxFrequency;  // out: Max engine clock frequency\n    D3DKMT_ALIGN64 ULONGLONG MaxFrequencyOC;// out: Max engine over clock frequency\n    ULONG           Voltage;                // out: Voltage of the engine in milli volts mV\n    ULONG           VoltageMax;             // out: Max voltage levels in milli volts.\n    ULONG           VoltageMaxOC;           // out: Max voltage level while overclocked in milli volts.\n    // WDDM 2.5\n    D3DKMT_ALIGN64 ULONGLONG MaxTransitionLatency;   // out: Max transition latency to change the frequency in 100 nanoseconds\n} D3DKMT_NODE_PERFDATA;\ntypedef struct _D3DKMT_QUERYSTATISTICS_NODE_INFORMATION {\n    D3DKMT_QUERYSTATISTICS_PROCESS_NODE_INFORMATION GlobalInformation; //Global statistics\n    D3DKMT_QUERYSTATISTICS_PROCESS_NODE_INFORMATION SystemInformation; //Statistics for system thread\n    D3DKMT_NODE_PERFDATA                            NodePerfData;\n    UINT32                                          Reserved[3];\n} D3DKMT_QUERYSTATISTICS_NODE_INFORMATION;\ntypedef union _D3DKMT_QUERYSTATISTICS_RESULT\n{\n    D3DKMT_QUERYSTATISTICS_PHYSICAL_ADAPTER_INFORMATION PhysAdapterInformation;\n    D3DKMT_QUERYSTATISTICS_NODE_INFORMATION NodeInformation;\n    uint8_t Padding[776];\n} D3DKMT_QUERYSTATISTICS_RESULT;\ntypedef struct _D3DKMT_QUERYSTATISTICS\n{\n    D3DKMT_QUERYSTATISTICS_TYPE   Type;        // in: type of data requested\n    LUID                          AdapterLuid; // in: adapter to get export / statistics from\n    HANDLE*                       hProcess;    // in: process to get statistics for, if required for this query type\n    D3DKMT_QUERYSTATISTICS_RESULT QueryResult; // out: requested data\n\n    union\n    {\n        D3DKMT_QUERYSTATISTICS_QUERY_PHYSICAL_ADAPTER QueryPhysAdapter; // in: id of physical adapter to get statistics for\n        D3DKMT_QUERYSTATISTICS_QUERY_NODE2 QueryNode2; // in: id of node to get statistics for\n    };\n} D3DKMT_QUERYSTATISTICS;\nstatic_assert(sizeof(D3DKMT_QUERYSTATISTICS) ==\n    #if _WIN64\n    0x328\n    #else\n    0x320\n    #endif\n, \"D3DKMT_QUERYSTATISTICS structure size mismatch\");\nEXTERN_C _Check_return_ NTSTATUS APIENTRY D3DKMTQueryStatistics(_In_ CONST D3DKMT_QUERYSTATISTICS*);\n\n#define DXGK_MAX_METADATA_NAME_LENGTH 32\ntypedef enum\n{\n    DXGK_ENGINE_TYPE_OTHER,\n    DXGK_ENGINE_TYPE_3D,\n    DXGK_ENGINE_TYPE_VIDEO_DECODE,\n    DXGK_ENGINE_TYPE_VIDEO_ENCODE,\n    DXGK_ENGINE_TYPE_VIDEO_PROCESSING,\n    DXGK_ENGINE_TYPE_SCENE_ASSEMBLY,\n    DXGK_ENGINE_TYPE_COPY,\n    DXGK_ENGINE_TYPE_OVERLAY,\n    DXGK_ENGINE_TYPE_CRYPTO,\n    DXGK_ENGINE_TYPE_VIDEO_CODEC,\n    DXGK_ENGINE_TYPE_MAX\n} DXGK_ENGINE_TYPE;\ntypedef struct _DXGK_NODEMETADATA_FLAGS\n{\n    union\n    {\n        struct\n        {\n            UINT ContextSchedulingSupported :  1; // WDDM 2.2\n            UINT RingBufferFenceRelease     :  1; // WDDM 2.5\n            UINT SupportTrackedWorkload     :  1;\n            UINT UserModeSubmission         :  1;\n            UINT SupportBuildTestCommandBuffer :  1; // WDDM 3.2\n            UINT Reserved                   : 11;\n            UINT MaxInFlightHwQueueBuffers  : 16;\n        };\n        UINT32 Value;\n    };\n} DXGK_NODEMETADATA_FLAGS;\ntypedef struct _DXGK_NODEMETADATA\n{\n    DXGK_ENGINE_TYPE EngineType;\n    WCHAR            FriendlyName[DXGK_MAX_METADATA_NAME_LENGTH];\n    DXGK_NODEMETADATA_FLAGS Flags; // WDDM 2.2\n    BOOLEAN          GpuMmuSupported; // WDDM 2.0 ???\n    BOOLEAN          IoMmuSupported;\n} __attribute__((packed))  DXGK_NODEMETADATA;\ntypedef struct _D3DKMT_NODEMETADATA\n{\n    _In_ UINT NodeOrdinalAndAdapterIndex;     // WDDMv2: High word is physical adapter index, low word is node ordinal\n    _Out_ DXGK_NODEMETADATA NodeData;\n} __attribute__((packed))  D3DKMT_NODEMETADATA;\nstatic_assert(sizeof(D3DKMT_NODEMETADATA) == 0x4E, \"D3DKMT_NODEMETADATA structure size mismatch\");\n\n#endif\n\nNTSYSAPI NTSTATUS NTAPI NtQueryDirectoryFile(\n    IN HANDLE FileHandle,\n    IN HANDLE Event OPTIONAL,\n    IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,\n    IN PVOID ApcContext OPTIONAL,\n    OUT PIO_STATUS_BLOCK IoStatusBlock,\n    OUT PVOID FileInformation,\n    IN ULONG Length,\n    IN FILE_INFORMATION_CLASS FileInformationClass,\n    IN BOOLEAN ReturnSingleEntry,\n    IN PUNICODE_STRING FileName OPTIONAL,\n    IN BOOLEAN RestartScan);\n\n// https://ntdoc.m417z.com/process_devicemap_information_ex\ntypedef struct _PROCESS_DEVICEMAP_INFORMATION_EX\n{\n    union\n    {\n        struct\n        {\n            HANDLE DirectoryHandle; // A handle to a directory object that can be set as the new device map for the process. This handle must have DIRECTORY_TRAVERSE access.\n        } Set;\n        struct\n        {\n            ULONG DriveMap;         // A bitmask that indicates which drive letters are currently in use in the process's device map.\n            UCHAR DriveType[32];    // A value that indicates the type of each drive (e.g., local disk, network drive, etc.). // DRIVE_* WinBase.h\n        } Query;\n    };\n    ULONG Flags; // PROCESS_LUID_DOSDEVICES_ONLY\n} PROCESS_DEVICEMAP_INFORMATION_EX, *PPROCESS_DEVICEMAP_INFORMATION_EX;\n\n#ifndef NtCurrentProcess\n#define NtCurrentProcess() ((HANDLE)(LONG_PTR)-1)\n#endif\n\ntypedef struct _CURDIR\n{\n    UNICODE_STRING DosPath;\n    HANDLE Handle;\n} CURDIR, *PCURDIR;\n\nNTSYSAPI PIMAGE_NT_HEADERS NTAPI RtlImageNtHeader(IN PVOID BaseOfImage);\n\n/**\n * The SECTION_IMAGE_INFORMATION structure contains detailed information about an image section.\n */\ntypedef struct _SECTION_IMAGE_INFORMATION\n{\n    PVOID TransferAddress;          // The address of the image entry point function.\n    ULONG ZeroBits;                 // The number of high-order address bits that must be zero in the image base address.\n    SIZE_T MaximumStackSize;        // The maximum stack size of threads from the PE file header.\n    SIZE_T CommittedStackSize;      // The initial stack size of threads from the PE file header.\n    ULONG SubSystemType;            // The image subsystem from the PE file header (e.g., Windows GUI, Windows CUI, POSIX).\n    union\n    {\n        struct\n        {\n            USHORT SubSystemMinorVersion;\n            USHORT SubSystemMajorVersion;\n        };\n        ULONG SubSystemVersion;\n    };\n    union\n    {\n        struct\n        {\n            USHORT MajorOperatingSystemVersion;\n            USHORT MinorOperatingSystemVersion;\n        };\n        ULONG OperatingSystemVersion;\n    };\n    USHORT ImageCharacteristics;    // The image characteristics from the PE file header.\n    USHORT DllCharacteristics;      // The DLL characteristics flags (e.g., ASLR, NX compatibility).\n    USHORT Machine;                 // The image architecture (e.g., x86, x64, ARM).\n    BOOLEAN ImageContainsCode;      // The image contains native executable code.\n    union\n    {\n        UCHAR ImageFlags;\n        struct\n        {\n            UCHAR ComPlusNativeReady : 1;           // The image contains precompiled .NET assembly generated by NGEN (Native Image Generator).\n            UCHAR ComPlusILOnly : 1;                // the image contains only Microsoft Intermediate Language (IL) assembly.\n            UCHAR ImageDynamicallyRelocated : 1;    // The image was mapped using a random base address rather than the preferred base address.\n            UCHAR ImageMappedFlat : 1;              // The image was mapped using a single contiguous region, rather than separate regions for each section.\n            UCHAR BaseBelow4gb : 1;                 // The image was mapped using a base address below the 4 GB boundary.\n            UCHAR ComPlusPrefer32bit : 1;           // The image prefers to run as a 32-bit process, even on a 64-bit system.\n            UCHAR Reserved : 2;\n        };\n    };\n    ULONG LoaderFlags;               // Reserved by ntdll.dll for the Windows loader.\n    ULONG ImageFileSize;             // The size of the image, in bytes, including all headers.\n    ULONG CheckSum;                  // The image file checksum, from the PE optional header.\n} SECTION_IMAGE_INFORMATION, *PSECTION_IMAGE_INFORMATION;\n\ntypedef struct _SYSTEM_BOOT_ENVIRONMENT_INFORMATION\n{\n    GUID BootIdentifier;\n    FIRMWARE_TYPE FirmwareType;\n    union\n    {\n        ULONGLONG BootFlags;\n        struct\n        {\n            ULONGLONG DbgMenuOsSelection : 1; // REDSTONE4\n            ULONGLONG DbgHiberBoot : 1;\n            ULONGLONG DbgSoftBoot : 1;\n            ULONGLONG DbgMeasuredLaunch : 1;\n            ULONGLONG DbgMeasuredLaunchCapable : 1; // 19H1\n            ULONGLONG DbgSystemHiveReplace : 1;\n            ULONGLONG DbgMeasuredLaunchSmmProtections : 1;\n            ULONGLONG DbgMeasuredLaunchSmmLevel : 7; // 20H1\n            ULONGLONG DbgBugCheckRecovery : 1; // 24H2\n            ULONGLONG DbgFASR : 1;\n            ULONGLONG DbgUseCachedBcd : 1;\n        };\n    };\n} SYSTEM_BOOT_ENVIRONMENT_INFORMATION;\n\ntypedef struct _RTL_PROCESS_MODULE_INFORMATION\n{\n    PVOID Section;\n    PVOID MappedBase;\n    PVOID ImageBase;\n    ULONG ImageSize;\n    ULONG Flags;\n    USHORT LoadOrderIndex;\n    USHORT InitOrderIndex;\n    USHORT LoadCount;\n    USHORT OffsetToFileName;\n    UCHAR FullPathName[256];\n} RTL_PROCESS_MODULE_INFORMATION, *PRTL_PROCESS_MODULE_INFORMATION;\n\ntypedef struct _RTL_PROCESS_MODULES\n{\n    ULONG NumberOfModules;\n    _Field_size_(NumberOfModules) RTL_PROCESS_MODULE_INFORMATION Modules[1];\n} RTL_PROCESS_MODULES, *PRTL_PROCESS_MODULES;\n\nNTSTATUS NTAPI NtQuerySystemEnvironmentValueEx(\n    _In_ PCUNICODE_STRING VariableName,\n    _In_ const GUID* VendorGuid,\n    _Out_writes_bytes_opt_(*BufferLength) PVOID Buffer,\n    _Inout_ PULONG BufferLength,\n    _Out_opt_ PULONG Attributes // EFI_VARIABLE_*\n);\n\nNTSTATUS NTAPI RtlGUIDFromString(IN PCUNICODE_STRING GuidString, OUT GUID* Guid);\n\ntypedef struct _SYSTEM_SECUREBOOT_INFORMATION\n{\n    BOOLEAN SecureBootEnabled;\n    BOOLEAN SecureBootCapable;\n} SYSTEM_SECUREBOOT_INFORMATION, *PSYSTEM_SECUREBOOT_INFORMATION;\n\nNTSTATUS NTAPI NtQuerySystemInformationEx(\n    _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,\n    _In_reads_bytes_(InputBufferLength) PVOID InputBuffer,\n    _In_ ULONG InputBufferLength,\n    _Out_writes_bytes_opt_(SystemInformationLength) PVOID SystemInformation,\n    _In_ ULONG SystemInformationLength,\n    _Out_opt_ PULONG ReturnLength\n);\n\ntypedef enum _SYSTEM_FIRMWARE_TABLE_ACTION\n{\n    SystemFirmwareTableEnumerate,\n    SystemFirmwareTableGet,\n    SystemFirmwareTableMax\n} SYSTEM_FIRMWARE_TABLE_ACTION;\n\ntypedef struct _SYSTEM_FIRMWARE_TABLE_INFORMATION\n{\n    ULONG ProviderSignature; // (same as the GetSystemFirmwareTable function)\n    SYSTEM_FIRMWARE_TABLE_ACTION Action;\n    ULONG TableID;\n    ULONG TableBufferLength;\n    _Field_size_bytes_(TableBufferLength) UCHAR TableBuffer[];\n} SYSTEM_FIRMWARE_TABLE_INFORMATION, *PSYSTEM_FIRMWARE_TABLE_INFORMATION;\n\nNTSYSAPI NTSTATUS NTAPI NtDelayExecution(_In_ BOOLEAN Alertable, _In_ PLARGE_INTEGER DelayInterval);\n\n/**\n * The KSYSTEM_TIME structure represents interrupt time, system time, and time zone bias.\n */\ntypedef struct _KSYSTEM_TIME\n{\n    ULONG LowPart;\n    LONG High1Time;\n    LONG High2Time;\n} KSYSTEM_TIME, *PKSYSTEM_TIME;\n\n/**\n * PROCESSOR_FEATURE_MAX defines the maximum number of processor feature flags\n * that may be reported by the system.\n */\n#define PROCESSOR_FEATURE_MAX 64\n\n/**\n * The ALTERNATIVE_ARCHITECTURE_TYPE enumeration specifies the hardware\n * architecture variant used by the system.\n *\n * \\remarks NEC98x86 represents the NEC PC-98 architecture,\n * supported only on very early Windows releases.\n */\ntypedef enum _ALTERNATIVE_ARCHITECTURE_TYPE\n{\n    StandardDesign,\n    NEC98x86,\n    EndAlternatives\n} ALTERNATIVE_ARCHITECTURE_TYPE;\n\n/**\n * The KUSER_SHARED_DATA structure contains information shared with user-mode.\n *\n * \\sa https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data\n */\ntypedef struct _KUSER_SHARED_DATA\n{\n    //\n    // Current low 32-bit of tick count and tick count multiplier.\n    //\n    // N.B. The tick count is updated each time the clock ticks.\n    //\n\n    ULONG TickCountLowDeprecated;\n    ULONG TickCountMultiplier;\n\n    //\n    // Current 64-bit interrupt time in 100ns units.\n    //\n\n    volatile KSYSTEM_TIME InterruptTime;\n\n    //\n    // Current 64-bit system time in 100ns units.\n    //\n\n    volatile KSYSTEM_TIME SystemTime;\n\n    //\n    // Current 64-bit time zone bias.\n    //\n\n    volatile KSYSTEM_TIME TimeZoneBias;\n\n    //\n    // Support image magic number range for the host system.\n    //\n    // N.B. This is an inclusive range.\n    //\n\n    USHORT ImageNumberLow;\n    USHORT ImageNumberHigh;\n\n    //\n    // Copy of system root in unicode.\n    //\n    // N.B. This field must be accessed via the RtlGetNtSystemRoot API for\n    //      an accurate result.\n    //\n\n    WCHAR NtSystemRoot[260];\n\n    //\n    // Maximum stack trace depth if tracing enabled.\n    //\n\n    ULONG MaxStackTraceDepth;\n\n    //\n    // Crypto exponent value.\n    //\n\n    ULONG CryptoExponent;\n\n    //\n    // Time zone ID.\n    //\n\n    ULONG TimeZoneId;\n\n    //\n    // Minimum size of a large page on the system, in bytes.\n    //\n    // N.B. Returned by GetLargePageMinimum() function.\n    //\n\n    ULONG LargePageMinimum;\n\n    //\n    // This value controls the Application Impact Telemetry (AIT) Sampling rate.\n    //\n    // This value determines how frequently the system records AIT events,\n    // which are used by the Application Experience and compatibility\n    // subsystems to evaluate application behavior, performance, and\n    // potential compatibility issues.\n    //\n    // Lower values increase sampling frequency, while higher values reduce it.\n    // The kernel updates this field as part of its internal telemetry and\n    // heuristics logic.\n    //\n\n    ULONG AitSamplingValue;\n\n    //\n    // This value controls Application Compatibility (AppCompat) switchback processing.\n    //\n\n    union\n    {\n        ULONG AppCompatFlag;\n        struct\n        {\n            ULONG SwitchbackEnabled : 1;    // Basic switchback processing\n            ULONG ExtendedHeuristics : 1;   // Extended switchback heuristics\n            ULONG TelemetryFallback : 1;    // Telemetry-driven fallback\n            ULONG Reserved : 29;\n        } AppCompatFlags;\n    };\n\n    //\n    // Current Kernel Root RNG state seed version\n    //\n\n    ULONGLONG RNGSeedVersion;\n\n    //\n    // This value controls assertion failure handling.\n    //\n    // Historically (prior to Windows 10), this value was also used by\n    // Code Integrity (CI), AppLocker, and related security components to\n    // determine the minimum validation requirements for executable images,\n    // drivers, and privileged operations.\n    //\n    // In modern Windows versions, this field is used primarily by the kernel's\n    // diagnostic and validation infrastructure to decide how assertion failures\n    // should be handled (e.g., logging, debugger break-in, or bugcheck).\n\n    ULONG GlobalValidationRunlevel;\n\n    //\n    // Monotonic stamp incremented by the kernel whenever the system's\n    // time zone bias value changes.\n    //\n    // N.B. This field must be accessed via the RtlGetSystemTimeAndBias API for\n    //      an accurate result.\n    // This value is read before and after accessing the bias fields to determine\n    // whether the time zone data changed during the read. If the stamp differs,\n    // the caller must re-read the bias values to ensure consistency.\n    //\n\n    volatile LONG TimeZoneBiasStamp;\n\n    //\n    // The shared collective build number undecorated with C or F.\n    // GetVersionEx hides the real number\n    //\n\n    ULONG NtBuildNumber;\n\n    //\n    // Product type.\n    //\n    // N.B. This field must be accessed via the RtlGetNtProductType API for\n    //      an accurate result.\n    //\n\n    NT_PRODUCT_TYPE NtProductType;\n    BOOLEAN ProductTypeIsValid;\n    BOOLEAN Reserved0[1];\n\n    //\n    // Native hardware processor architecture of the running system.\n    //\n    // N.B. User-mode components read this field to determine the true system\n    // architecture, especially in WOW64 scenarios where the process architecture\n    // differs from the native one.\n    //\n\n    USHORT NativeProcessorArchitecture;\n\n    //\n    // The NT Version.\n    //\n    // N. B. Note that each process sees a version from its PEB, but if the\n    //       process is running with an altered view of the system version,\n    //       the following two fields are used to correctly identify the\n    //       version\n    //\n\n    ULONG NtMajorVersion;\n    ULONG NtMinorVersion;\n\n    //\n    // Processor features.\n    //\n\n    BOOLEAN ProcessorFeatures[PROCESSOR_FEATURE_MAX];\n\n\n    //\n    // Reserved fields - do not use.\n    //\n\n    ULONG MaximumUserModeAddressDeprecated; // Deprecated, use SystemBasicInformation instead.\n    ULONG SystemRangeStartDeprecated; // Deprecated, use SystemRangeStartInformation instead.\n\n    //\n    // Time slippage while in debugger.\n    //\n\n    volatile ULONG TimeSlip;\n\n    //\n    // Alternative system architecture, e.g., NEC PC98xx on x86.\n    //\n\n    ALTERNATIVE_ARCHITECTURE_TYPE AlternativeArchitecture;\n\n    //\n    // Boot sequence, incremented for each boot attempt by the OS loader.\n    //\n\n    ULONG BootId;\n\n    //\n    // If the system is an evaluation unit, the following field contains the\n    // date and time that the evaluation unit expires. A value of 0 indicates\n    // that there is no expiration. A non-zero value is the UTC absolute time\n    // that the system expires.\n    //\n\n    LARGE_INTEGER SystemExpirationDate;\n\n    //\n    // Suite support.\n    //\n    // N.B. This field must be accessed via the RtlGetSuiteMask API for\n    //      an accurate result.\n    //\n\n    ULONG SuiteMask;\n\n    //\n    // TRUE if a kernel debugger is connected/enabled.\n    //\n\n    BOOLEAN KdDebuggerEnabled;\n\n    //\n    // Mitigation policies.\n    //\n\n    union\n    {\n        UCHAR MitigationPolicies;\n        struct\n        {\n            UCHAR NXSupportPolicy : 2;\n            UCHAR SEHValidationPolicy : 2;\n            UCHAR CurDirDevicesSkippedForDlls : 2;\n            UCHAR Reserved : 2;\n        };\n    };\n\n    //\n    // Measured duration of a single processor yield, in cycles. This is used by\n    // lock packages to determine how many times to spin waiting for a state\n    // change before blocking.\n    //\n\n    USHORT CyclesPerYield;\n\n    //\n    // Current console session Id. Always zero on non-TS systems.\n    //\n    // N.B. This field must be accessed via the RtlGetActiveConsoleId API for an\n    //      accurate result.\n    //\n\n    volatile ULONG ActiveConsoleId;\n\n    //\n    // Force-dismounts cause handles to become invalid. Rather than always\n    // probe handles, a serial number of dismounts is maintained that clients\n    // can use to see if they need to probe handles.\n    //\n\n    volatile ULONG DismountCount;\n\n    //\n    // This field indicates the status of the 64-bit COM+ package on the\n    // system. It indicates whether the Intermediate Language (IL) COM+\n    // images need to use the 64-bit COM+ runtime or the 32-bit COM+ runtime.\n    //\n\n    ULONG ComPlusPackage;\n\n    //\n    // Time in tick count for system-wide last user input across all terminal\n    // sessions. For MP performance, it is not updated all the time (e.g. once\n    // a minute per session). It is used for idle detection.\n    //\n\n    ULONG LastSystemRITEventTickCount;\n\n    //\n    // Number of physical pages in the system. This can dynamically change as\n    // physical memory can be added or removed from a running system.  This\n    // cell is too small to hold the non-truncated value on very large memory\n    // machines so code that needs the full value should access\n    // FullNumberOfPhysicalPages instead.\n    //\n\n    ULONG NumberOfPhysicalPages;\n\n    //\n    // True if the system was booted in safe boot mode.\n    //\n\n    BOOLEAN SafeBootMode;\n\n    //\n    // Virtualization flags.\n    //\n\n    union\n    {\n        UCHAR VirtualizationFlags;\n\n#if defined(_ARM64_)\n\n        //\n        // N.B. Keep this bitfield in sync with the one in arc.w.\n        //\n\n        struct\n        {\n            UCHAR ArchStartedInEl2 : 1;\n            UCHAR QcSlIsSupported : 1;\n            UCHAR : 6;\n        };\n\n#endif\n\n    };\n\n    //\n    // Reserved (available for reuse).\n    //\n\n    UCHAR Reserved12[2];\n\n    //\n    // This is a packed bitfield that contains various flags concerning\n    // the system state. They must be manipulated using interlocked\n    // operations.\n    //\n    // N.B. DbgMultiSessionSku must be accessed via the RtlIsMultiSessionSku\n    //      API for an accurate result\n    //\n\n    union\n    {\n        ULONG SharedDataFlags;\n        struct\n        {\n            //\n            // The following bit fields are for the debugger only. Do not use.\n            // Use the bit definitions instead.\n            //\n\n            ULONG DbgErrorPortPresent       : 1;\n            ULONG DbgElevationEnabled       : 1;\n            ULONG DbgVirtEnabled            : 1;\n            ULONG DbgInstallerDetectEnabled : 1;\n            ULONG DbgLkgEnabled             : 1;\n            ULONG DbgDynProcessorEnabled    : 1;\n            ULONG DbgConsoleBrokerEnabled   : 1;\n            ULONG DbgSecureBootEnabled      : 1;\n            ULONG DbgMultiSessionSku        : 1;\n            ULONG DbgMultiUsersInSessionSku : 1;\n            ULONG DbgStateSeparationEnabled : 1;\n            ULONG DbgSplitTokenEnabled      : 1;\n            ULONG DbgShadowAdminEnabled     : 1;\n            ULONG SpareBits                 : 19;\n        };\n    };\n\n    // ... more fields follow, but we don't need them\n} KUSER_SHARED_DATA, *PKUSER_SHARED_DATA;\n\n#define SharedUserData ((const KUSER_SHARED_DATA*) 0x7FFE0000UL)\n\nstatic inline uint64_t ffKSystemTimeToUInt64(const volatile KSYSTEM_TIME* pTime)\n{\n    #if _WIN64\n\n    return *(uint64_t*) pTime;\n\n    #else\n\n    uint32_t low, high1, high2;\n\n    do {\n        high1 = pTime->High1Time;\n        low   = pTime->LowPart;\n        high2 = pTime->High2Time;\n    } while (high1 != high2);\n\n    return ((uint64_t) high1 << 32) | low;\n    #endif\n}\n\nstatic inline bool ffIsWindows10OrGreater()\n{\n    #if FF_WIN81_COMPAT\n    return SharedUserData->NtMajorVersion >= 10;\n    #else\n    return true;\n    #endif\n}\n\nstatic inline bool ffIsWindows11OrGreater()\n{\n    return ffIsWindows10OrGreater() && SharedUserData->NtBuildNumber >= 22000;\n}\n\nNTSYSAPI NTSTATUS NTAPI NtOpenProcessToken(\n    _In_ HANDLE ProcessHandle,\n    _In_ ACCESS_MASK DesiredAccess,\n    _Out_ PHANDLE TokenHandle\n);\nNTSYSAPI NTSTATUS NTAPI NtAdjustPrivilegesToken(\n    _In_ HANDLE TokenHandle,\n    _In_ BOOLEAN DisableAllPrivileges,\n    _In_opt_ PTOKEN_PRIVILEGES NewState,\n    _In_ ULONG BufferLength,\n    _Out_writes_bytes_to_opt_(BufferLength, *ReturnLength) PTOKEN_PRIVILEGES PreviousState,\n    _Out_opt_ PULONG ReturnLength\n);\nNTSYSAPI NTSTATUS NTAPI NtQueryInformationToken(\n    _In_ HANDLE TokenHandle,\n    _In_ TOKEN_INFORMATION_CLASS TokenInformationClass,\n    _Out_writes_bytes_to_opt_(TokenInformationLength, *ReturnLength) PVOID TokenInformation,\n    _In_ ULONG TokenInformationLength,\n    _Out_ PULONG ReturnLength\n);\n#define NtCurrentProcessToken() ((HANDLE)(LONG_PTR)-4) // for NtQueryInformationToken only; Windows 8+\n\nNTSYSAPI NTSTATUS NTAPI NtReadFile(\n    _In_ HANDLE FileHandle,\n    _In_opt_ HANDLE Event,\n    _In_opt_ PIO_APC_ROUTINE ApcRoutine,\n    _In_opt_ PVOID ApcContext,\n    _Out_ PIO_STATUS_BLOCK IoStatusBlock,\n    _Out_writes_bytes_(Length) PVOID Buffer,\n    _In_ ULONG Length,\n    _In_opt_ PLARGE_INTEGER ByteOffset,\n    _In_opt_ PULONG Key\n);\n\nNTSYSAPI NTSTATUS NTAPI NtCreateEvent(\n    _Out_ PHANDLE EventHandle,\n    _In_ ACCESS_MASK DesiredAccess,\n    _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,\n    _In_ EVENT_TYPE EventType,\n    _In_ BOOLEAN InitialState\n);\n\nNTSYSAPI NTSTATUS NTAPI NtQueryAttributesFile(\n    _In_ PCOBJECT_ATTRIBUTES ObjectAttributes,\n    _Out_ PFILE_BASIC_INFORMATION FileInformation\n);\n\nNTSYSAPI NTSTATUS NTAPI RtlUnicodeToUTF8N(\n    _Out_writes_bytes_to_(UTF8StringMaxByteCount, *UTF8StringActualByteCount) PCHAR UTF8StringDestination,\n    _In_ ULONG UTF8StringMaxByteCount,\n    _Out_opt_ PULONG UTF8StringActualByteCount,\n    _In_reads_bytes_(UnicodeStringByteCount) PCWCH UnicodeStringSource,\n    _In_ ULONG UnicodeStringByteCount\n);\n\nNTSYSAPI NTSTATUS NTAPI RtlUTF8ToUnicodeN(\n    _Out_writes_bytes_to_(UnicodeStringMaxByteCount, *UnicodeStringActualByteCount) PWSTR UnicodeStringDestination,\n    _In_ ULONG UnicodeStringMaxByteCount,\n    _Out_opt_ PULONG UnicodeStringActualByteCount,\n    _In_reads_bytes_(UTF8StringByteCount) PCCH UTF8StringSource,\n    _In_ ULONG UTF8StringByteCount\n);\n\n#define RTL_MAX_DRIVE_LETTERS 32\ntypedef struct _RTL_DRIVE_LETTER_CURDIR\n{\n    USHORT Flags;\n    USHORT Length;\n    ULONG TimeStamp;\n    STRING DosPath;\n} RTL_DRIVE_LETTER_CURDIR, *PRTL_DRIVE_LETTER_CURDIR;\n\ntypedef struct _RTL_USER_PROCESS_PARAMETERS_FULL\n{\n    ULONG MaximumLength;\n    ULONG Length;\n\n    ULONG Flags;\n    ULONG DebugFlags;\n\n    HANDLE ConsoleHandle;\n    ULONG ConsoleFlags;\n    HANDLE StandardInput;\n    HANDLE StandardOutput;\n    HANDLE StandardError;\n\n    CURDIR CurrentDirectory;\n    UNICODE_STRING DllPath;\n    UNICODE_STRING ImagePathName;\n    UNICODE_STRING CommandLine;\n    PVOID Environment;\n\n    ULONG StartingX;\n    ULONG StartingY;\n    ULONG CountX;\n    ULONG CountY;\n    ULONG CountCharsX;\n    ULONG CountCharsY;\n    ULONG FillAttribute;\n\n    ULONG WindowFlags;\n    ULONG ShowWindowFlags;\n    UNICODE_STRING WindowTitle;\n    UNICODE_STRING DesktopInfo;\n    UNICODE_STRING ShellInfo;\n    UNICODE_STRING RuntimeData;\n    RTL_DRIVE_LETTER_CURDIR CurrentDirectories[RTL_MAX_DRIVE_LETTERS];\n\n    // Windows Vista\n    ULONG_PTR EnvironmentSize;\n    // Windows 7\n    ULONG_PTR EnvironmentVersion;\n\n    // Windows 8\n    PVOID PackageDependencyData;\n    ULONG ProcessGroupId;\n\n    // ...\n} RTL_USER_PROCESS_PARAMETERS_FULL, *PRTL_USER_PROCESS_PARAMETERS_FULL;\n\ntypedef struct _PEB_FULL\n{\n    //\n    // The process was cloned with an inherited address space.\n    //\n    BOOLEAN InheritedAddressSpace;\n\n    //\n    // The process has image file execution options (IFEO).\n    //\n    BOOLEAN ReadImageFileExecOptions;\n\n    //\n    // The process has a debugger attached.\n    //\n    BOOLEAN BeingDebugged;\n\n    union\n    {\n        BOOLEAN BitField;\n        struct\n        {\n            BOOLEAN ImageUsesLargePages : 1;            // The process uses large image regions (4 MB).\n            BOOLEAN IsProtectedProcess : 1;             // The process is a protected process.\n            BOOLEAN IsImageDynamicallyRelocated : 1;    // The process image base address was relocated.\n            BOOLEAN SkipPatchingUser32Forwarders : 1;   // The process skipped forwarders for User32.dll functions. 1 for 64-bit, 0 for 32-bit.\n            BOOLEAN IsPackagedProcess : 1;              // The process is a packaged store process (APPX/MSIX).\n            BOOLEAN IsAppContainerProcess : 1;          // The process has an AppContainer token.\n            BOOLEAN IsProtectedProcessLight : 1;        // The process is a protected process (light).\n            BOOLEAN IsLongPathAwareProcess : 1;         // The process is long path aware.\n        };\n    };\n\n    //\n    // Handle to a mutex for synchronization.\n    //\n    HANDLE Mutant;\n\n    //\n    // Pointer to the base address of the process image.\n    //\n    PVOID ImageBaseAddress;\n\n    //\n    // Pointer to the process loader data.\n    //\n    PPEB_LDR_DATA Ldr;\n\n    //\n    // Pointer to the process parameters.\n    //\n    PRTL_USER_PROCESS_PARAMETERS_FULL ProcessParameters;\n\n    //\n    // Reserved.\n    //\n    PVOID SubSystemData;\n\n    //\n    // Pointer to the process default heap.\n    //\n    PVOID ProcessHeap;\n\n    // ...\n} PEB_FULL, *PPEB_FULL;\n\ntypedef struct _TEB_FULL\n{\n    //\n    // Thread Information Block (TIB) contains the thread's stack, base and limit addresses, the current stack pointer, and the exception list.\n    //\n    NT_TIB NtTib;\n\n    //\n    // Reserved.\n    //\n    PVOID EnvironmentPointer;\n\n    //\n    // Client ID for this thread.\n    //\n    CLIENT_ID ClientId;\n\n    //\n    // A handle to an active Remote Procedure Call (RPC) if the thread is currently involved in an RPC operation.\n    //\n    PVOID ActiveRpcHandle;\n\n    //\n    // A pointer to the __declspec(thread) local storage array.\n    //\n    PVOID ThreadLocalStoragePointer;\n\n    //\n    // A pointer to the Process Environment Block (PEB), which contains information about the process.\n    //\n    PPEB_FULL ProcessEnvironmentBlock;\n\n    //\n    // The previous Win32 error value for this thread.\n    //\n    ULONG LastErrorValue;\n\n    //\n    // The number of critical sections currently owned by this thread.\n    //\n    ULONG CountOfOwnedCriticalSections;\n\n    //\n    // Reserved.\n    //\n    PVOID CsrClientThread;\n\n    //\n    // Reserved for win32k.sys\n    //\n    PVOID Win32ThreadInfo;\n\n    //\n    // Reserved for user32.dll\n    //\n    ULONG User32Reserved[26];\n\n    //\n    // Reserved for winsrv.dll\n    //\n    ULONG UserReserved[5];\n\n    //\n    // Reserved.\n    //\n    PVOID WOW32Reserved;\n\n    //\n    // The LCID of the current thread. (Kernel32!GetThreadLocale)\n    //\n    LCID CurrentLocale;\n} TEB_FULL, *PTEB_FULL;\n\nstatic inline PTEB_FULL ffGetTeb()\n{\n    return (PTEB_FULL) NtCurrentTeb();\n}\n\nstatic inline PPEB_FULL ffGetPeb()\n{\n    return ffGetTeb()->ProcessEnvironmentBlock;\n}\n\nNTSYSAPI NTSTATUS NTAPI RtlExpandEnvironmentStrings(\n    _In_opt_ PVOID Environment,\n    _In_reads_(SourceLength) PCWSTR Source,\n    _In_ SIZE_T SourceLength,\n    _Out_writes_(DestinationLength) PWSTR Destination,\n    _In_ SIZE_T DestinationLength,\n    _Out_opt_ PSIZE_T ReturnLength\n);\n\nNTSYSAPI NTSTATUS NTAPI NtOpenKey(\n    _Out_ PHANDLE KeyHandle,\n    _In_ ACCESS_MASK DesiredAccess,\n    _In_ POBJECT_ATTRIBUTES ObjectAttributes\n);\n\ntypedef enum _KEY_VALUE_INFORMATION_CLASS\n{\n    KeyValueBasicInformation, // KEY_VALUE_BASIC_INFORMATION\n    KeyValueFullInformation, // KEY_VALUE_FULL_INFORMATION\n    KeyValuePartialInformation, // KEY_VALUE_PARTIAL_INFORMATION\n    KeyValueFullInformationAlign64, // KEY_VALUE_FULL_INFORMATION_ALIGN64\n    KeyValuePartialInformationAlign64,  // KEY_VALUE_PARTIAL_INFORMATION_ALIGN64\n    KeyValueLayerInformation, // KEY_VALUE_LAYER_INFORMATION\n    MaxKeyValueInfoClass\n} KEY_VALUE_INFORMATION_CLASS;\n\nNTSYSAPI NTSTATUS NTAPI NtQueryValueKey(\n    _In_ HANDLE KeyHandle,\n    _In_ PCUNICODE_STRING ValueName,\n    _In_ KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,\n    _Out_writes_bytes_to_opt_(Length, *ResultLength) PVOID KeyValueInformation,\n    _In_ ULONG Length,\n    _Out_ PULONG ResultLength\n);\n\nNTSYSAPI NTSTATUS NTAPI RtlFormatCurrentUserKeyPath(\n    _Out_ PUNICODE_STRING CurrentUserKeyPath\n);\n\ntypedef struct _KEY_VALUE_PARTIAL_INFORMATION\n{\n    ULONG TitleIndex;\n    ULONG Type;\n    ULONG DataLength;\n    _Field_size_bytes_(DataLength) UCHAR Data[];\n} KEY_VALUE_PARTIAL_INFORMATION, *PKEY_VALUE_PARTIAL_INFORMATION;\n\ntypedef enum _KEY_INFORMATION_CLASS\n{\n    KeyBasicInformation, // KEY_BASIC_INFORMATION\n    KeyNodeInformation, // KEY_NODE_INFORMATION\n    KeyFullInformation, // KEY_FULL_INFORMATION\n    KeyNameInformation, // KEY_NAME_INFORMATION\n    KeyCachedInformation, // KEY_CACHED_INFORMATION\n    KeyFlagsInformation, // KEY_FLAGS_INFORMATION\n    KeyVirtualizationInformation, // KEY_VIRTUALIZATION_INFORMATION\n    KeyHandleTagsInformation, // KEY_HANDLE_TAGS_INFORMATION\n    KeyTrustInformation, // KEY_TRUST_INFORMATION\n    KeyLayerInformation, // KEY_LAYER_INFORMATION\n    MaxKeyInfoClass\n} KEY_INFORMATION_CLASS;\n\nNTSYSAPI NTSTATUS NTAPI NtEnumerateKey(\n    _In_ HANDLE KeyHandle,\n    _In_ ULONG Index,\n    _In_ KEY_INFORMATION_CLASS KeyInformationClass,\n    _Out_writes_bytes_to_opt_(Length, *ResultLength) PVOID KeyInformation,\n    _In_ ULONG Length,\n    _Out_ PULONG ResultLength\n);\n\ntypedef struct _KEY_BASIC_INFORMATION\n{\n    LARGE_INTEGER LastWriteTime;                    // Number of 100-nanosecond intervals since this key or any of its values changed.\n    ULONG TitleIndex;                               // Reserved // A legacy field originally intended for use with localization such as an index of a resource table.\n    ULONG NameLength;                               // The size, in bytes, of the key name string in the Name array.\n    _Field_size_bytes_(NameLength) WCHAR Name[];   // The name of the registry key. This string is not null-terminated.\n} KEY_BASIC_INFORMATION, *PKEY_BASIC_INFORMATION;\n\ntypedef struct _KEY_FULL_INFORMATION\n{\n    LARGE_INTEGER LastWriteTime;\n    ULONG TitleIndex;\n    ULONG ClassOffset;\n    ULONG ClassLength;\n    ULONG SubKeys;\n    ULONG MaxNameLength;\n    ULONG MaxClassLength;\n    ULONG Values;\n    ULONG MaxValueNameLength;\n    ULONG MaxValueDataLength;\n    WCHAR Class[];\n} KEY_FULL_INFORMATION, *PKEY_FULL_INFORMATION;\n\nNTSYSAPI NTSTATUS NTAPI NtQueryKey(\n    _In_ HANDLE KeyHandle,\n    _In_ KEY_INFORMATION_CLASS KeyInformationClass,\n    _Out_writes_bytes_to_opt_(Length, *ResultLength) PVOID KeyInformation,\n    _In_ ULONG Length,\n    _Out_ PULONG ResultLength\n);\n\nNTSYSAPI NTSTATUS NTAPI NtOpenProcess(\n    _Out_ PHANDLE ProcessHandle,\n    _In_ ACCESS_MASK DesiredAccess,\n    _In_ PCOBJECT_ATTRIBUTES ObjectAttributes,\n    _In_opt_ PCLIENT_ID ClientId\n);\n\nNTSYSAPI NTSTATUS NTAPI LdrLoadDll(\n    _In_opt_ PCWSTR DllPath,\n    _In_opt_ PULONG DllCharacteristics,\n    _In_ PCUNICODE_STRING DllName,\n    _Out_ PVOID *DllHandle\n);\n\nNTSYSAPI NTSTATUS NTAPI LdrUnloadDll(\n    _In_ PVOID DllHandle\n);\n\nNTSYSAPI NTSTATUS NTAPI LdrGetDllHandle(\n    _In_opt_ PCWSTR DllPath,\n    _In_opt_ PULONG DllCharacteristics,\n    _In_ PCUNICODE_STRING DllName,\n    _Out_ PVOID *DllHandle\n);\n\nNTSYSAPI NTSTATUS NTAPI LdrGetProcedureAddress(\n    _In_ PVOID DllHandle,\n    _In_opt_ PCANSI_STRING ProcedureName,\n    _In_opt_ ULONG ProcedureNumber,\n    _Out_ PVOID *ProcedureAddress\n);\n"
  },
  {
    "path": "src/common/windows/perflib_.h",
    "content": "#pragma once\n\n#include <windows.h>\n#include <perflib.h>\n\n// Missing from <perflib.h> of MinGW-w64 SDK\n\n#define PERF_WILDCARD_COUNTER   0xFFFFFFFF\n#define PERF_WILDCARD_INSTANCE  L\"*\"\n#define PERF_AGGREGATE_INSTANCE L\"_Total\"\n#define PERF_MAX_INSTANCE_NAME  1024\n\ntypedef struct _PERF_INSTANCE_HEADER {\n    ULONG Size;       // = sizeof(PERF_INSTANCE_HEADER) + sizeof(InstanceName) + sizeof(Padding)\n    ULONG InstanceId; // Instance ID.\n    // Followed by:\n    // WCHAR InstanceName[]; // Nul-terminated.\n    // WCHAR Padding[];      // Pad to a multiple of 8 bytes\n} PERF_INSTANCE_HEADER, *PPERF_INSTANCE_HEADER;\n\ntypedef struct _PERF_COUNTER_IDENTIFIER {\n    GUID   CounterSetGuid; // The GUID of the counterset.\n    ULONG  Status;         // Win32 error code indicating success/failure of the add/delete operation.\n    ULONG  Size;           // sizeof(PERF_COUNTER_IDENTIFIER) + sizeof(InstanceName) + sizeof(Padding)\n    ULONG  CounterId;      // CounterId, or PERF_WILDCARD_COUNTER for all counters.\n    ULONG  InstanceId;     // InstanceId, or 0xFFFFFFFF to not filter on instance ID.\n    ULONG  Index;          // Set by PerfQueryCounterInfo to the position in which the corresponding counter data is returned.\n    ULONG  Reserved;       // Reserved.\n    // Followed by:\n    // WCHAR InstanceName[];\n    // WCHAR Padding[];\n} PERF_COUNTER_IDENTIFIER, * PPERF_COUNTER_IDENTIFIER;\n\ntypedef struct _PERF_DATA_HEADER {\n    ULONG      dwTotalSize;     // = sizeof(PERF_DATA_HEADER) + sizeof(PERF_COUNTER_HEADER blocks...)\n    ULONG      dwNumCounters;   // The number of PERF_COUNTER_HEADER blocks.\n    LONGLONG   PerfTimeStamp;   // Timestamp from a high-resolution clock.\n    LONGLONG   PerfTime100NSec; // The number of 100 nanosecond intervals since January 1, 1601, in Coordinated Universal Time (UTC).\n    LONGLONG   PerfFreq;        // The frequency of a high-resolution clock.\n    SYSTEMTIME SystemTime;      // The time at which data is collected on the provider side.\n    // Followed by:\n    // PERF_COUNTER_HEADER blocks...;\n} PERF_DATA_HEADER, * PPERF_DATA_HEADER;\n\ntypedef enum _PerfCounterDataType {\n    PERF_ERROR_RETURN = 0,       /* An error occurred when the performance counter value was queried. */\n    PERF_SINGLE_COUNTER = 1,     /* Query returned a single counter from a single-instance. */\n    PERF_MULTIPLE_COUNTERS = 2,  /* Query returned multiple counters from a single instance. */\n    PERF_MULTIPLE_INSTANCES = 4, /* Query returned a single counter from each of multiple instances. */\n    PERF_COUNTERSET = 6          /* Query returned multiple counters from each of multiple instances. */\n} PerfCounterDataType;\n\ntypedef struct _PERF_COUNTER_HEADER {\n    ULONG      dwStatus;        // Win32 error code indicating success/failure of the query operation.\n    PerfCounterDataType dwType; // Result type - error, single/single, multi/single, single/multi, multi/multi.\n    ULONG      dwSize;          // = sizeof(PERF_COUNTER_HEADER) + sizeof(Additional data)\n    ULONG      Reserved;        // Reserved.\n    // Followed by additional data:\n    // If dwType == PERF_ERROR_RETURN:       nothing.\n    // If dwType == PERF_SINGLE_COUNTER:     PERF_COUNTER_DATA block.\n    // If dwType == PERF_MULTIPLE_COUNTERS:  PERF_MULTI_COUNTERS block + PERF_COUNTER_DATA blocks.\n    // If dwType == PERF_MULTIPLE_INSTANCES: PERF_MULTI_INSTANCES block.\n    // If dwType == PERF_COUNTERSET:         PERF_MULTI_COUNTERS block + PERF_MULTI_INSTANCES block.\n} PERF_COUNTER_HEADER, * PPERF_COUNTER_HEADER;\n\ntypedef struct _PERF_MULTI_INSTANCES {\n    ULONG      dwTotalSize; // = sizeof(PERF_MULTI_INSTANCES) + sizeof(instance data blocks...)\n    ULONG      dwInstances; // Number of instance data blocks.\n    // Followed by:\n    // Instance data blocks...;\n} PERF_MULTI_INSTANCES, * PPERF_MULTI_INSTANCES;\n\ntypedef struct _PERF_MULTI_COUNTERS {\n    ULONG      dwSize;     // sizeof(PERF_MULTI_COUNTERS) + sizeof(CounterIds)\n    ULONG      dwCounters; // Number of counter ids.\n    // Followed by:\n    // DWORD CounterIds[dwCounters];\n} PERF_MULTI_COUNTERS, * PPERF_MULTI_COUNTERS;\n\ntypedef struct _PERF_COUNTER_DATA {\n    ULONG      dwDataSize; // Size of the counter data, in bytes.\n    ULONG      dwSize;     // = sizeof(PERF_COUNTER_DATA) + sizeof(Data) + sizeof(Padding)\n    // Followed by:\n    // BYTE Data[dwDataSize];\n    // BYTE Padding[];\n} PERF_COUNTER_DATA, * PPERF_COUNTER_DATA;\n\n_Success_(return == ERROR_SUCCESS)\nULONG\nWINAPI\nPerfEnumerateCounterSetInstances(\n    _In_opt_z_ LPCWSTR szMachine,\n    _In_ LPCGUID pCounterSetId,\n    _Out_opt_bytecap_post_bytecount_(cbInstances, *pcbInstancesActual) PPERF_INSTANCE_HEADER pInstances,\n    DWORD cbInstances,\n    _Out_ LPDWORD pcbInstancesActual\n    );\n\n_Success_(return == ERROR_SUCCESS)\nULONG\nWINAPI\nPerfOpenQueryHandle(\n    _In_opt_z_ LPCWSTR szMachine,\n    _Out_ HANDLE * phQuery\n    );\n\n_Success_(return == ERROR_SUCCESS)\nULONG\nWINAPI\nPerfCloseQueryHandle(\n    _In_ HANDLE hQuery\n    );\n\n_Success_(return == ERROR_SUCCESS)\nULONG\nWINAPI\nPerfAddCounters(\n    _In_ HANDLE hQuery,\n    _Inout_bytecount_(cbCounters) PPERF_COUNTER_IDENTIFIER pCounters,\n    DWORD cbCounters\n    );\n\n_Success_(return == ERROR_SUCCESS)\nULONG\nWINAPI\nPerfDeleteCounters(\n    _In_ HANDLE hQuery,\n    _Inout_bytecount_(cbCounters) PPERF_COUNTER_IDENTIFIER pCounters,\n    DWORD cbCounters\n    );\n\n_Success_(return == ERROR_SUCCESS)\nULONG\nWINAPI\nPerfQueryCounterData(\n    _In_ HANDLE hQuery,\n    _Out_opt_bytecap_post_bytecount_(cbCounterBlock, *pcbCounterBlockActual) PPERF_DATA_HEADER pCounterBlock,\n    DWORD cbCounterBlock,\n    _Out_ LPDWORD pcbCounterBlockActual\n    );\n"
  },
  {
    "path": "src/common/windows/registry.c",
    "content": "#include \"registry.h\"\n#include \"unicode.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/debug.h\"\n#include \"common/windows/nt.h\"\n\n#include <stdalign.h>\n#include <ntstatus.h>\n\nstatic HANDLE hRootKeys[8 /*(uintptr_t) HKEY_CURRENT_USER_LOCAL_SETTINGS - (uintptr_t) HKEY_CLASSES_ROOT + 1*/];\n\nstatic const char* hKey2Str(HANDLE hRootKey)\n{\n    #define HKEY_CASE(compareKey) if(hRootKey == hRootKeys[(uintptr_t)compareKey - (uintptr_t)HKEY_CLASSES_ROOT]) return #compareKey;\n    HKEY_CASE(HKEY_CLASSES_ROOT)\n    HKEY_CASE(HKEY_CURRENT_USER)\n    HKEY_CASE(HKEY_LOCAL_MACHINE)\n    HKEY_CASE(HKEY_USERS)\n    HKEY_CASE(HKEY_PERFORMANCE_DATA)\n    HKEY_CASE(HKEY_CURRENT_CONFIG)\n    HKEY_CASE(HKEY_DYN_DATA)\n    HKEY_CASE(HKEY_CURRENT_USER_LOCAL_SETTINGS)\n    #undef HKEY_CASE\n\n    return \"UNKNOWN\";\n}\n\nHANDLE ffRegGetRootKeyHandle(HKEY hKey)\n{\n    assert(hKey);\n    assert((uintptr_t) hKey >= (uintptr_t) HKEY_CLASSES_ROOT && (uintptr_t) hKey <= (uintptr_t) HKEY_CURRENT_USER_LOCAL_SETTINGS);\n\n    FF_DEBUG(\"Getting root key handle for HKEY %08llx\", (uint64_t)(uintptr_t) hKey);\n\n    HANDLE result = hRootKeys[(uintptr_t) hKey - (uintptr_t) HKEY_CLASSES_ROOT];\n    if (result)\n    {\n        FF_DEBUG(\"Found cached root key handle for %s -> %p\", hKey2Str(result), result);\n        return result;\n    }\n\n    switch ((uintptr_t) hKey)\n    {\n        case (uintptr_t) HKEY_CURRENT_USER: {\n            UNICODE_STRING path = {};\n            NTSTATUS status = RtlFormatCurrentUserKeyPath(&path);\n            if (!NT_SUCCESS(status))\n            {\n                FF_DEBUG(\"RtlFormatCurrentUserKeyPath() failed: %s\", ffDebugNtStatus(status));\n                return NULL;\n            }\n            status = NtOpenKey(&result, KEY_READ, &(OBJECT_ATTRIBUTES) {\n                .Length = sizeof(OBJECT_ATTRIBUTES),\n                .RootDirectory = NULL,\n                .ObjectName = &path,\n            });\n            if (!NT_SUCCESS(status))\n            {\n                FF_DEBUG(\"NtOpenKey(%ls) failed: %s (0x%08lx)\", path.Buffer, ffDebugNtStatus(status), status);\n                RtlFreeUnicodeString(&path);\n                return NULL;\n            }\n            RtlFreeUnicodeString(&path);\n            break;\n        }\n        case (uintptr_t) HKEY_LOCAL_MACHINE: {\n            UNICODE_STRING path = RTL_CONSTANT_STRING(L\"\\\\Registry\\\\Machine\");\n            NTSTATUS status = NtOpenKey(&result, KEY_READ, &(OBJECT_ATTRIBUTES) {\n                .Length = sizeof(OBJECT_ATTRIBUTES),\n                .RootDirectory = NULL,\n                .ObjectName = &path,\n                .Attributes = OBJ_CASE_INSENSITIVE,\n            });\n            if (!NT_SUCCESS(status))\n            {\n                FF_DEBUG(\"NtOpenKey(%ls) failed: %s (0x%08lx)\", path.Buffer, ffDebugNtStatus(status), status);\n                return NULL;\n            }\n            break;\n        }\n        default:\n            // Unsupported\n            FF_DEBUG(\"Unsupported root key: %p\", hKey);\n            assert(false);\n            return NULL;\n    }\n    hRootKeys[(uintptr_t) hKey - (uintptr_t) HKEY_CLASSES_ROOT] = result;\n    FF_DEBUG(\"Opened root key %s -> %p\", hKey2Str(result), result);\n    return result;\n}\n\nbool ffRegOpenSubkeyForRead(HANDLE hKey, const wchar_t* subKeyW, HANDLE* result, FFstrbuf* error)\n{\n    assert(hKey);\n    assert(subKeyW);\n    assert(result);\n\n    FF_DEBUG(\"Opening subkey %s\\\\%ls for read\", hKey2Str(hKey), subKeyW);\n\n    USHORT subKeyLen = (USHORT) (wcslen(subKeyW) * sizeof(wchar_t));\n    if (!NT_SUCCESS(NtOpenKey(result, KEY_READ, &(OBJECT_ATTRIBUTES) {\n        .Length = sizeof(OBJECT_ATTRIBUTES),\n        .RootDirectory = hKey,\n        .ObjectName = &(UNICODE_STRING) {\n            .Length = subKeyLen,\n            .MaximumLength = subKeyLen + (USHORT) sizeof(wchar_t),\n            .Buffer = (wchar_t*) subKeyW,\n        },\n    })))\n    {\n        FF_DEBUG(\"NtOpenKey(%s\\\\<subkey>) failed\", hKey2Str(hKey));\n        if (error)\n        {\n            FF_STRBUF_AUTO_DESTROY subKeyA = ffStrbufCreateWS(subKeyW);\n            ffStrbufAppendF(error, \"NtOpenKey(%s\\\\%s) failed\", hKey2Str(hKey), subKeyA.chars);\n        }\n        return false;\n    }\n    FF_DEBUG(\"Opened subkey under %s -> %p\", hKey2Str(hKey), *result);\n    return true;\n}\n\nstatic bool processRegValue(const FFRegValueArg* arg, const ULONG regType, const void* regData, ULONG regDataLen, FFstrbuf* error)\n{\n    switch (arg->type)\n    {\n        case FF_ARG_TYPE_STRBUF:\n        {\n            if (regType != REG_SZ && regType != REG_EXPAND_SZ)\n                goto type_mismatch;\n\n            FFstrbuf* strbuf = (FFstrbuf*) arg->value;\n            uint32_t strLen = regDataLen / sizeof(wchar_t);\n            if (strLen == 0)\n                ffStrbufClear(strbuf);\n            else\n            {\n                const wchar_t* ws = (const wchar_t*) regData;\n                if (ws[strLen - 1] == L'\\0')\n                    --strLen;\n                ffStrbufSetNWS(strbuf, strLen, ws);\n            }\n            break;\n        }\n\n        case FF_ARG_TYPE_UINT:\n        case FF_ARG_TYPE_UINT64:\n        case FF_ARG_TYPE_UINT16:\n        case FF_ARG_TYPE_UINT8:\n        case FF_ARG_TYPE_BOOL:\n        {\n            uint64_t value = 0;\n\n            if (regType == REG_DWORD)\n            {\n                if (regDataLen < sizeof(uint32_t))\n                    goto type_mismatch;\n                value = *(uint32_t*) regData;\n            }\n            else if (regType == REG_QWORD)\n            {\n                if (regDataLen < sizeof(uint64_t))\n                    goto type_mismatch;\n                value = *(uint64_t*) regData;\n            }\n            else\n                goto type_mismatch;\n\n            if      (arg->type == FF_ARG_TYPE_UINT)   *(uint32_t*) arg->value = (uint32_t) value;\n            else if (arg->type == FF_ARG_TYPE_UINT64) *(uint64_t*) arg->value = (uint64_t) value;\n            else if (arg->type == FF_ARG_TYPE_UINT16) *(uint16_t*) arg->value = (uint16_t) value;\n            else if (arg->type == FF_ARG_TYPE_UINT8)  *(uint8_t*) arg->value = (uint8_t) value;\n            else if (arg->type == FF_ARG_TYPE_BOOL)   *(bool*) arg->value = value != 0;\n            break;\n        }\n\n        case FF_ARG_TYPE_FLOAT:\n        {\n            if (regDataLen < sizeof(float))\n                goto type_mismatch;\n            *(float*) arg->value = *(float*) regData;\n            break;\n        }\n\n        case FF_ARG_TYPE_DOUBLE:\n        {\n            if (regDataLen < sizeof(double))\n                goto type_mismatch;\n            *(double*) arg->value = *(double*) regData;\n            break;\n        }\n\n        case FF_ARG_TYPE_LIST:\n        {\n            if (regType != REG_MULTI_SZ && regType != REG_BINARY)\n                goto type_mismatch;\n\n            FFlist* list = (FFlist*) arg->value;\n            ffListClear(list);\n\n            if (regType == REG_MULTI_SZ)\n            {\n                if (list->elementSize != sizeof(FFstrbuf))\n                {\n                    if (error)\n                    {\n                        FF_STRBUF_AUTO_DESTROY nameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic(\"(default)\");\n                        ffStrbufAppendF(error, \"ffRegReadValues(%s) type mismatch: expected list of strbuf for REG_MULTI_SZ\", nameA.chars);\n                    }\n                    return false;\n                }\n\n                for (\n                    const wchar_t* ptr = (const wchar_t*) regData;\n                    (const uint8_t*) ptr < (const uint8_t*) regData + regDataLen && *ptr;\n                    ptr++\n                )\n                {\n                    uint32_t strLen = (uint32_t) wcsnlen(ptr, regDataLen / sizeof(wchar_t) - (size_t) (ptr - (const wchar_t*) regData));\n                    ffStrbufInitNWS(FF_LIST_ADD(FFstrbuf, *list), strLen, ptr);\n                    ptr += strLen;\n                }\n            }\n            else\n            {\n                if (list->elementSize != sizeof(uint8_t))\n                {\n                    if (error)\n                    {\n                        FF_STRBUF_AUTO_DESTROY nameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic(\"(default)\");\n                        ffStrbufAppendF(error, \"ffRegReadValues(%s) type mismatch: expected list of uint8_t for REG_BINARY\", nameA.chars);\n                    }\n                    return false;\n                }\n\n                ffListReserve(list, regDataLen);\n                memcpy(list->data, regData, regDataLen);\n                list->length = regDataLen;\n            }\n            break;\n        }\n\n        case FF_ARG_TYPE_INT: // Use UINT instead\n        case FF_ARG_TYPE_STRING:\n        case FF_ARG_TYPE_NULL:\n        default:\n            if (error)\n            {\n                FF_STRBUF_AUTO_DESTROY nameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic(\"(default)\");\n                ffStrbufAppendF(error, \"processRegValue(%s) unsupported FFArgType %u\", nameA.chars, (unsigned) arg->type);\n            }\n            return false;\n    }\n\n    return true;\n\ntype_mismatch:\n    FF_DEBUG(\"ffRegReadValues(%ls) type mismatch: regType=%u, argType=%u, dataLen=%u\",\n        arg->name ?: L\"(default)\", (unsigned) regType, (unsigned) arg->type, (unsigned) regDataLen);\n    if (error)\n    {\n        FF_STRBUF_AUTO_DESTROY nameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic(\"(default)\");\n        ffStrbufAppendF(error, \"ffRegReadValues(%s) type mismatch: regType=%u, argType=%u, dataLen=%u\",\n            nameA.chars, (unsigned) regType, (unsigned) arg->type, (unsigned) regDataLen);\n    }\n    return false;\n}\n\nbool ffRegReadValue(HANDLE hKey, const FFRegValueArg* arg, FFstrbuf* error)\n{\n    UNICODE_STRING* valueNameU = &(UNICODE_STRING) {\n        .Length = arg->name ? (USHORT) (wcslen(arg->name) * sizeof(wchar_t)) : 0 /*(default)*/,\n        .MaximumLength = 0,\n        .Buffer = (wchar_t*) arg->name,\n    };\n\n    alignas(KEY_VALUE_PARTIAL_INFORMATION) uint8_t staticBuffer[128 + sizeof(KEY_VALUE_PARTIAL_INFORMATION)];\n    FF_AUTO_FREE uint8_t* dynamicBuffer = NULL;\n\n    KEY_VALUE_PARTIAL_INFORMATION* buffer = (KEY_VALUE_PARTIAL_INFORMATION*) &staticBuffer;\n    DWORD bufSize = sizeof(staticBuffer);\n    if (NT_SUCCESS(NtQueryValueKey(hKey, valueNameU, KeyValuePartialInformation, buffer, bufSize, &bufSize)))\n        goto process_value;\n\n    if (bufSize == 0)\n    {\n        FF_DEBUG(\"NtQueryValueKey(%p, %ls) failed (bufSize=0)\", hKey, arg->name ?: L\"(default)\");\n        if (error)\n        {\n            FF_STRBUF_AUTO_DESTROY valueNameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic(\"(default)\");\n            ffStrbufAppendF(error, \"NtQueryValueKey(%p, %s) failed\", hKey, valueNameA.chars);\n        }\n        return false;\n    }\n\n    dynamicBuffer = (uint8_t*) malloc(bufSize);\n    buffer = (KEY_VALUE_PARTIAL_INFORMATION*) dynamicBuffer;\n\n    if (!NT_SUCCESS(NtQueryValueKey(hKey, valueNameU, KeyValuePartialInformation, buffer, bufSize, &bufSize)))\n    {\n        FF_DEBUG(\"NtQueryValueKey(%p, %ls, buffer=%u) failed\", hKey, arg->name ?: L\"(default)\", (unsigned) bufSize);\n        if (error)\n        {\n            FF_STRBUF_AUTO_DESTROY valueNameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic(\"(default)\");\n            ffStrbufAppendF(error, \"NtQueryValueKey(%p, %s, buffer) failed\", hKey, valueNameA.chars);\n        }\n        return false;\n    }\n\nprocess_value:\n    FF_DEBUG(\"Read value from %p (%ls), type=%u, len=%u\", hKey, arg->name ?: L\"(default)\", (unsigned) buffer->Type, (unsigned) buffer->DataLength);\n    return processRegValue(arg, buffer->Type, buffer->Data, buffer->DataLength, error);\n}\n\nbool ffRegReadValues(HANDLE hKey, uint32_t argc, const FFRegValueArg argv[], FFstrbuf* error)\n{\n    if (__builtin_expect(argc == 0, false))\n        return true;\n\n    assert(argv);\n\n    FF_AUTO_FREE UNICODE_STRING* names = (UNICODE_STRING*) calloc(argc, sizeof(*names));\n    FF_AUTO_FREE KEY_VALUE_ENTRY* entries = (KEY_VALUE_ENTRY*) calloc(argc, sizeof(*entries));\n\n    for (uint32_t i = 0; i < argc; ++i)\n    {\n        if (__builtin_expect(!argv[i].value, false))\n        {\n            FF_DEBUG(\"ffRegReadValues(argv[%u].value) is NULL\", (unsigned) i);\n            if (error) ffStrbufAppendF(error, \"ffRegReadValues(argv[%u].pVar) is NULL\", (unsigned) i);\n            return false;\n        }\n\n        names[i] = (UNICODE_STRING) {\n            .Length = argv[i].name ? (USHORT) (wcslen(argv[i].name) * sizeof(wchar_t)) : 0 /*(default)*/,\n            .MaximumLength = 0,\n            .Buffer = (wchar_t*) argv[i].name,\n        };\n        entries[i].ValueName = &names[i];\n    }\n\n    ULONG bufferSize = argc * 128;\n    if (bufferSize < 512)\n        bufferSize = 512;\n\n    FF_AUTO_FREE uint8_t* buffer = NULL;\n\n    while (true)\n    {\n        buffer = (uint8_t*) realloc(buffer, bufferSize);\n\n        ULONG writtenSize = bufferSize;\n        ULONG requiredSize = 0;\n        NTSTATUS status = NtQueryMultipleValueKey(hKey, entries, argc, buffer, &writtenSize, &requiredSize);\n\n        if (!NT_SUCCESS(status))\n        {\n            // Buffer too small: docs guarantee requiredSize is returned when provided.\n            if (requiredSize > bufferSize)\n            {\n                FF_DEBUG(\"NtQueryMultipleValueKey(%p) resize buffer: %u -> %u\", hKey, (unsigned) bufferSize, (unsigned) requiredSize);\n                bufferSize = requiredSize;\n                continue;\n            }\n\n            FF_DEBUG(\"NtQueryMultipleValueKey(%p, argc=%u) failed, status=0x%08X\", hKey, (unsigned) argc, (unsigned) status);\n            if (error)\n                ffStrbufAppendF(error, \"NtQueryMultipleValueKey(%p, argc=%u) failed, status=0x%08X\",\n                    hKey, (unsigned) argc, (unsigned) status);\n            return false;\n        }\n\n        break;\n    }\n\n    for (uint32_t i = 0; i < argc; ++i)\n    {\n        const FFRegValueArg* arg = &argv[i];\n        const KEY_VALUE_ENTRY* entry = &entries[i];\n\n        FF_DEBUG(\"Read value[%u] from %p: type=%u, len=%u\", (unsigned) i, hKey, (unsigned) entry->Type, (unsigned) entry->DataLength);\n        if (!processRegValue(arg, entry->Type, buffer + entry->DataOffset, entry->DataLength, error))\n            return false;\n    }\n\n    return true;\n}\n\nbool ffRegGetSubKey(HANDLE hKey, uint32_t index, FFstrbuf* result, FFstrbuf* error)\n{\n    assert(hKey);\n    assert(result);\n\n    alignas(KEY_BASIC_INFORMATION) uint8_t buffer[sizeof(KEY_BASIC_INFORMATION) + MAX_PATH * sizeof(wchar_t)];\n    ULONG bufSize = (ULONG) sizeof(buffer);\n    KEY_BASIC_INFORMATION* keyInfo = (KEY_BASIC_INFORMATION*) buffer;\n\n    if (!NT_SUCCESS(NtEnumerateKey(hKey, index, KeyBasicInformation, keyInfo, bufSize, &bufSize)))\n    {\n        FF_DEBUG(\"NtEnumerateKey(hKey=%p, index=%u) failed\", hKey, (unsigned) index);\n        if (error)\n            ffStrbufAppendF(error, \"NtEnumerateKey(hKey=%p, %u, keyInfo) failed\", hKey, (unsigned) index);\n        return false;\n    }\n\n    ffStrbufSetNWS(result, keyInfo->NameLength / sizeof(wchar_t), keyInfo->Name);\n    return true;\n}\n\nbool ffRegGetNSubKeys(HANDLE hKey, uint32_t* result, FFstrbuf* error)\n{\n    assert(hKey);\n    assert(result);\n\n    alignas(KEY_FULL_INFORMATION) uint8_t buffer[sizeof(KEY_FULL_INFORMATION) + MAX_PATH * sizeof(wchar_t)];\n    ULONG bufSize = sizeof(buffer);\n    KEY_FULL_INFORMATION* keyInfo = (KEY_FULL_INFORMATION*) buffer;\n\n    if (!NT_SUCCESS(NtQueryKey(hKey, KeyFullInformation, keyInfo, bufSize, &bufSize)))\n    {\n        FF_DEBUG(\"NtQueryKey(hKey=%p, KeyFullInformation) failed\", hKey);\n        if (error)\n            ffStrbufAppendF(error, \"NtQueryKey(hKey=%p, KeyFullInformation, keyInfo) failed\", hKey);\n        return false;\n    }\n\n    *result = (uint32_t) keyInfo->SubKeys;\n    return true;\n}\n"
  },
  {
    "path": "src/common/windows/registry.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"common/argType.h\"\n#include \"common/io.h\"\n\n#ifndef HKEY_CURRENT_USER\n#define HKEY_CLASSES_ROOT ((HKEY) (ULONG_PTR)((LONG)0x80000000))\n#define HKEY_CURRENT_USER ((HKEY) (ULONG_PTR)((LONG)0x80000001))\n#define HKEY_LOCAL_MACHINE ((HKEY) (ULONG_PTR)((LONG)0x80000002))\n#define HKEY_USERS ((HKEY) (ULONG_PTR)((LONG)0x80000003))\n#define HKEY_PERFORMANCE_DATA ((HKEY) (ULONG_PTR)((LONG)0x80000004))\n#define HKEY_CURRENT_CONFIG ((HKEY) (ULONG_PTR)((LONG)0x80000005))\n#define HKEY_DYN_DATA ((HKEY) (ULONG_PTR)((LONG)0x80000006))\n#define HKEY_CURRENT_USER_LOCAL_SETTINGS ((HKEY) (ULONG_PTR)((LONG)0x80000007))\n#endif\n\ntypedef struct FFRegValueArg\n{\n    FFArgType type;\n    const void* value;\n    const wchar_t* name;\n} FFRegValueArg;\n\nHANDLE ffRegGetRootKeyHandle(HKEY hKey);\nbool ffRegOpenSubkeyForRead(HANDLE hKey, const wchar_t* subKeyW, HANDLE* result, FFstrbuf* error);\nbool ffRegReadValue(HANDLE hKey, const FFRegValueArg* arg, FFstrbuf* error);\nbool ffRegReadValues(HANDLE hKey, uint32_t argc, const FFRegValueArg argv[], FFstrbuf* error);\nbool ffRegGetSubKey(HANDLE hKey, uint32_t index, FFstrbuf* result, FFstrbuf* error);\nbool ffRegGetNSubKeys(HANDLE hKey, uint32_t* result, FFstrbuf* error);\n\nstatic inline bool ffRegOpenKeyForRead(HKEY hRootKey, const wchar_t* subKeyW, HANDLE* result, FFstrbuf* error)\n{\n    return ffRegOpenSubkeyForRead(ffRegGetRootKeyHandle(hRootKey), subKeyW, result, error);\n}\n\nstatic inline bool ffRegReadStrbuf(HANDLE hKey, const wchar_t* valueNameW, FFstrbuf* result, FFstrbuf* error)\n{\n    return ffRegReadValue(hKey, &(FFRegValueArg) {\n        .type = FF_ARG_TYPE_STRBUF,\n        .value = result,\n        .name = valueNameW,\n    }, error);\n}\nstatic inline bool ffRegReadUint(HANDLE hKey, const wchar_t* valueNameW, uint32_t* result, FFstrbuf* error)\n{\n    return ffRegReadValue(hKey, &(FFRegValueArg) {\n        .type = FF_ARG_TYPE_UINT,\n        .value = result,\n        .name = valueNameW,\n    }, error);\n}\nstatic inline bool ffRegReadUint64(HANDLE hKey, const wchar_t* valueNameW, uint64_t* result, FFstrbuf* error)\n{\n    return ffRegReadValue(hKey, &(FFRegValueArg) {\n        .type = FF_ARG_TYPE_UINT64,\n        .value = result,\n        .name = valueNameW,\n    }, error);\n}\nstatic inline bool ffRegReadData(HANDLE hKey, const wchar_t* valueNameW, FFlist* result /*list of uint8_t*/, FFstrbuf* error)\n{\n    return ffRegReadValue(hKey, &(FFRegValueArg) {\n        .type = FF_ARG_TYPE_LIST,\n        .value = result,\n        .name = valueNameW,\n    }, error);\n}\n"
  },
  {
    "path": "src/common/windows/unicode.c",
    "content": "#include \"unicode.h\"\n\n#include \"common/windows/nt.h\"\n\nvoid ffStrbufSetNWS(FFstrbuf* result, uint32_t length, const wchar_t* source)\n{\n    if(!length)\n    {\n        ffStrbufClear(result);\n        return;\n    }\n\n    ULONG size_needed = 0;\n    NTSTATUS status = RtlUnicodeToUTF8N(NULL, 0, &size_needed, source, length * sizeof(wchar_t));\n\n    if (size_needed == 0)\n    {\n        ffStrbufSetF(result, \"RtlUnicodeToUTF8N failed: %X\", (unsigned) status);\n        return;\n    }\n\n    ffStrbufEnsureFixedLengthFree(result, size_needed);\n    RtlUnicodeToUTF8N(result->chars, size_needed, &size_needed, source, length * sizeof(wchar_t));\n\n    result->length = size_needed;\n    result->chars[size_needed] = '\\0';\n}\n\nvoid ffStrbufAppendNWS(FFstrbuf* result, uint32_t length, const wchar_t* source)\n{\n    if(!length)\n        return;\n\n    ULONG size_needed = 0;\n    NTSTATUS status = RtlUnicodeToUTF8N(NULL, 0, &size_needed, source, length * sizeof(wchar_t));\n\n    if (size_needed == 0)\n    {\n        ffStrbufAppendF(result, \"RtlUnicodeToUTF8N failed: %X\", (unsigned) status);\n        return;\n    }\n\n    ffStrbufEnsureFree(result, size_needed);\n    RtlUnicodeToUTF8N(result->chars + result->length, size_needed, &size_needed, source, length * sizeof(wchar_t));\n\n    result->length += size_needed;\n    result->chars[result->length] = '\\0';\n}\n"
  },
  {
    "path": "src/common/windows/unicode.h",
    "content": "#pragma once\n\n#include \"common/FFstrbuf.h\"\n#include <wchar.h>\n\nvoid ffStrbufSetNWS(FFstrbuf* result, uint32_t length, const wchar_t* source);\nvoid ffStrbufAppendNWS(FFstrbuf* result, uint32_t length, const wchar_t* source);\n\nstatic inline void ffStrbufSetWS(FFstrbuf* result, const wchar_t* source)\n{\n    if (!source) return ffStrbufClear(result);\n    return ffStrbufSetNWS(result, (uint32_t)wcslen(source), source);\n}\n\nstatic inline void ffStrbufAppendWS(FFstrbuf* result, const wchar_t* source)\n{\n    if (!source) return;\n    return ffStrbufAppendNWS(result, (uint32_t)wcslen(source), source);\n}\n\nstatic inline void ffStrbufInitNWS(FFstrbuf* result, uint32_t length, const wchar_t* source)\n{\n    ffStrbufInit(result);\n    return ffStrbufSetNWS(result, length, source);\n}\n\nstatic inline void ffStrbufInitWS(FFstrbuf* result, const wchar_t* source)\n{\n    if (!source) return ffStrbufInit(result);\n    return ffStrbufInitNWS(result, (uint32_t)wcslen(source), source);\n}\n\nstatic inline FFstrbuf ffStrbufCreateNWS(uint32_t length, const wchar_t* source)\n{\n    FFstrbuf result;\n    ffStrbufInitNWS(&result, length, source);\n    return result;\n}\n\nstatic inline FFstrbuf ffStrbufCreateWS(const wchar_t* source)\n{\n    if (!source) return ffStrbufCreate();\n    return ffStrbufCreateNWS((uint32_t)wcslen(source), source);\n}\n"
  },
  {
    "path": "src/common/windows/unicode.hpp",
    "content": "#pragma once\n\n#ifdef __cplusplus\n\nextern \"C\" {\n#include \"unicode.h\"\n}\n\n#include <string_view>\n\nstatic inline void ffStrbufInitWSV(FFstrbuf* result, const std::wstring_view source)\n{\n    return ffStrbufInitNWS(result, (uint32_t) source.size(), source.data());\n}\n\nstatic inline FFstrbuf ffStrbufCreateWSV(const std::wstring_view source)\n{\n    return ffStrbufCreateNWS((uint32_t) source.size(), source.data());\n}\n\nstatic inline void ffStrbufSetWSV(FFstrbuf* result, const std::wstring_view source)\n{\n    return ffStrbufSetNWS(result, (uint32_t) source.size(), source.data());\n}\n\n#else\n\n    #error Must be included in C++ source file\n\n#endif\n"
  },
  {
    "path": "src/common/windows/util.hpp",
    "content": "#pragma once\n\n#include <utility>\n#include <type_traits>\n\ntemplate <typename Fn>\nstruct on_scope_exit {\n    static_assert(std::is_nothrow_move_constructible<Fn>::value,\n                    \"Fn must be nothrow move constructible\");\n\n    explicit on_scope_exit(Fn &&fn) noexcept\n        : _fn(std::move(fn)) {};\n    on_scope_exit(const on_scope_exit&) = delete;\n    on_scope_exit& operator=(const on_scope_exit&) = delete;\n    ~on_scope_exit() noexcept { this->_fn(); }\n\nprivate:\n    Fn _fn;\n};\n"
  },
  {
    "path": "src/common/windows/variant.cpp",
    "content": "#include \"variant.hpp\"\n\n#include <oleauto.h>\n\nFFWmiVariant::FFWmiVariant(std::initializer_list<PCWSTR> strings): FFWmiVariant()\n{\n    SAFEARRAYBOUND bound = {\n        .cElements = (ULONG) strings.size(),\n        .lLbound = 0,\n    };\n    SAFEARRAY* psa = SafeArrayCreate(VT_BSTR, 1, &bound);\n\n    LONG i = 0;\n    for (PCWSTR str : strings) {\n        SafeArrayPutElement(psa, &i, bstr_t(str));\n        ++i;\n    }\n\n    this->vt = VT_ARRAY | VT_BSTR;\n    this->parray = psa;\n}\n"
  },
  {
    "path": "src/common/windows/variant.hpp",
    "content": "#include <oaidl.h>\n#include <propidl.h>\n#include <type_traits>\n#include <utility>\n#include <string_view>\n#include <cassert>\n#include <cstdint>\n\ntemplate <typename TVariant>\nstruct FFBaseVariant: TVariant\n{\n    bool hasValue() {\n        return this->vt != VT_EMPTY;\n    }\n\n    explicit operator bool() {\n        return this->hasValue();\n    }\n\n    template <typename T> T get()\n    {\n        // boolean\n        if constexpr (std::is_same_v<T, bool>) {\n            assert(this->vt == VT_BOOL);\n            return this->boolVal != VARIANT_FALSE;\n        }\n\n        // signed\n        else if constexpr (std::is_same_v<T, int8_t>) {\n            assert(this->vt == VT_I1);\n            return this->cVal;\n        }\n        else if constexpr (std::is_same_v<T, int16_t>) {\n            assert(this->vt == VT_I2);\n            return this->iVal;\n        }\n        else if constexpr (std::is_same_v<T, int32_t>) {\n            assert(this->vt == VT_I4 || this->vt == VT_INT);\n            return this->intVal;\n        }\n        else if constexpr (std::is_same_v<T, int64_t>) {\n            assert(this->vt == VT_I8);\n            return this->llVal;\n        }\n\n        // unsigned\n        else if constexpr (std::is_same_v<T, uint8_t>) {\n            assert(this->vt == VT_UI1);\n            return this->bVal;\n        }\n        else if constexpr (std::is_same_v<T, uint16_t>) {\n            assert(this->vt == VT_UI2);\n            return this->uiVal;\n        }\n        else if constexpr (std::is_same_v<T, uint32_t>) {\n            assert(this->vt == VT_UI4 || this->vt == VT_UINT);\n            return this->uintVal;\n        }\n        else if constexpr (std::is_same_v<T, uint64_t>) {\n            assert(this->vt == VT_UI8);\n            return this->ullVal;\n        }\n\n        // decimal\n        else if constexpr (std::is_same_v<T, float>) {\n            assert(this->vt == VT_R4);\n            return this->fltVal;\n        }\n        else if constexpr (std::is_same_v<T, double>) {\n            assert(this->vt == VT_R8);\n            return this->dblVal;\n        }\n\n        // string\n        else if constexpr (std::is_same_v<T, std::string_view>) {\n            assert(this->vt == VT_LPSTR);\n            return this->pcVal;\n        }\n        else if constexpr (std::is_same_v<T, std::wstring_view>) {\n            assert(this->vt == VT_BSTR || this->vt == VT_LPWSTR);\n            if (this->vt == VT_LPWSTR)\n                return this->bstrVal;\n            else\n                return { this->bstrVal, SysStringLen(this->bstrVal) };\n        }\n\n        // array signed\n        else if constexpr (std::is_same_v<T, std::pair<const int8_t*, uint32_t>>) {\n            assert(this->vt & VT_ARRAY);\n            assert((this->vt & ~VT_ARRAY) == VT_I1);\n            return std::make_pair((int8_t*)this->parray->pvData, this->parray->cDims);\n        }\n        else if constexpr (std::is_same_v<T, std::pair<const int16_t*, uint32_t>>) {\n            assert(this->vt & VT_ARRAY);\n            assert((this->vt & ~VT_ARRAY) == VT_I2);\n            return std::make_pair((int16_t*)this->parray->pvData, this->parray->cDims);\n        }\n        else if constexpr (std::is_same_v<T, std::pair<const int32_t*, uint32_t>>) {\n            assert(this->vt & VT_ARRAY);\n            assert((this->vt & ~VT_ARRAY) == VT_I4);\n            return std::make_pair((int32_t*)this->parray->pvData, this->parray->cDims);\n        }\n        else if constexpr (std::is_same_v<T, std::pair<const int64_t*, uint32_t>>) {\n            assert(this->vt & VT_ARRAY);\n            assert((this->vt & ~VT_ARRAY) == VT_I8);\n            return std::make_pair((int64_t*)this->parray->pvData, this->parray->cDims);\n        }\n\n        // array unsigned\n        else if constexpr (std::is_same_v<T, std::pair<const uint8_t*, uint32_t>>) {\n            assert(this->vt & VT_ARRAY);\n            assert((this->vt & ~VT_ARRAY) == VT_UI1);\n            return std::make_pair((uint8_t*)this->parray->pvData, this->parray->cDims);\n        }\n        else if constexpr (std::is_same_v<T, std::pair<const uint16_t*, uint32_t>>) {\n            assert(this->vt & VT_ARRAY);\n            assert((this->vt & ~VT_ARRAY) == VT_UI2);\n            return std::make_pair((uint16_t*)this->parray->pvData, this->parray->cDims);\n        }\n        else if constexpr (std::is_same_v<T, std::pair<const uint32_t*, uint32_t>>) {\n            assert(this->vt & VT_ARRAY);\n            assert((this->vt & ~VT_ARRAY) == VT_UI4);\n            return std::make_pair((uint32_t*)this->parray->pvData, this->parray->cDims);\n        }\n        else if constexpr (std::is_same_v<T, std::pair<const uint64_t*, uint32_t>>) {\n            assert(this->vt & VT_ARRAY);\n            assert((this->vt & ~VT_ARRAY) == VT_UI8);\n            return std::make_pair((uint64_t*)this->parray->pvData, this->parray->cDims);\n        }\n        else {\n            assert(false && \"unsupported type\");\n            __builtin_unreachable();\n        }\n    }\n};\n\nstruct FFWmiVariant: FFBaseVariant<VARIANT>\n{\n    FFWmiVariant(const FFWmiVariant&) = delete;\n    FFWmiVariant(FFWmiVariant&&); // don't define it to enforce NRVO optimization\n    explicit FFWmiVariant() { VariantInit(this); }\n    explicit FFWmiVariant(std::initializer_list<PCWSTR> strings);\n    ~FFWmiVariant() { VariantClear(this); }\n};\nstatic_assert(sizeof(FFWmiVariant) == sizeof(VARIANT), \"\");\n\nstruct FFPropVariant: FFBaseVariant<PROPVARIANT>\n{\n    FFPropVariant(const FFPropVariant&) = delete;\n    FFPropVariant(FFPropVariant&&); // don't define it to enforce NRVO optimization\n    explicit FFPropVariant() { PropVariantInit(this); }\n    ~FFPropVariant() { PropVariantClear(this); }\n};\nstatic_assert(sizeof(FFPropVariant) == sizeof(PROPVARIANT), \"\");\n\nnamespace\n{\n    // Provide our bstr_t to avoid libstdc++ dependency\n    struct bstr_t\n    {\n        explicit bstr_t(const wchar_t* str) noexcept: _bstr(SysAllocString(str)) {}\n        ~bstr_t(void) noexcept { SysFreeString(_bstr); }\n        explicit operator const wchar_t*(void) const noexcept { return _bstr; }\n        operator BSTR(void) const noexcept { return _bstr; }\n\n        private:\n            BSTR _bstr;\n    };\n}\n"
  },
  {
    "path": "src/common/windows/version.c",
    "content": "#include \"common/debug.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/windows/version.h\"\n#include \"common/windows/unicode.h\"\n\n#include <windows.h>\n\n#define FF_VERSION_LANG_EN_US L\"040904b0\"\n\nbool ffGetFileVersion(const wchar_t* filePath, const wchar_t* stringName, FFstrbuf* version)\n{\n    FF_DEBUG(\"ffGetFileVersion: enter filePath=%ls stringName=%ls\", filePath, stringName);\n\n    DWORD handle;\n    DWORD size = GetFileVersionInfoSizeW(filePath, &handle);\n    if (size == 0)\n    {\n        DWORD err = GetLastError();\n        FF_DEBUG(\"GetFileVersionInfoSizeW failed: err=%lu (%s)\",\n            (unsigned long) err, ffDebugWin32Error(err));\n        return false;\n    }\n\n    FF_DEBUG(\"GetFileVersionInfoSizeW ok: size=%lu handle=%lu\",\n        (unsigned long) size, (unsigned long) handle);\n\n    FF_AUTO_FREE void* versionData = malloc(size);\n    if (!versionData)\n    {\n        FF_DEBUG(\"malloc failed: size=%lu\", (unsigned long) size);\n        return false;\n    }\n\n    if (!GetFileVersionInfoW(filePath, handle, size, versionData))\n    {\n        DWORD err = GetLastError();\n        FF_DEBUG(\"GetFileVersionInfoW failed: err=%lu (%s)\",\n            (unsigned long) err, ffDebugWin32Error(err));\n        return false;\n    }\n\n    FF_DEBUG(\"GetFileVersionInfoW ok\");\n\n    if (!stringName)\n    {\n        VS_FIXEDFILEINFO* verInfo;\n        UINT len;\n        if (VerQueryValueW(versionData, L\"\\\\\", (void**) &verInfo, &len) &&\n            len &&\n            verInfo->dwSignature == 0xFEEF04BD)\n        {\n            ffStrbufSetF(version, \"%u.%u.%u.%u\",\n                (unsigned) ((verInfo->dwProductVersionMS >> 16) & 0xffff),\n                (unsigned) ((verInfo->dwProductVersionMS >> 0) & 0xffff),\n                (unsigned) ((verInfo->dwProductVersionLS >> 16) & 0xffff),\n                (unsigned) ((verInfo->dwProductVersionLS >> 0) & 0xffff));\n            FF_DEBUG(\"fixed version resolved: %s\", version->chars);\n            return true;\n        }\n\n        FF_DEBUG(\"fixed version query failed or invalid signature\");\n        return false;\n    }\n\n    wchar_t* value;\n    UINT valueLen;\n\n    wchar_t subBlock[128];\n    snwprintf(subBlock, ARRAY_SIZE(subBlock), L\"\\\\StringFileInfo\\\\\" FF_VERSION_LANG_EN_US L\"\\\\%ls\", stringName);\n    FF_DEBUG(\"query version string with default lang (en_US): %ls\", subBlock);\n\n    if (VerQueryValueW(versionData, subBlock, (void**) &value, &valueLen) && valueLen > 0)\n    {\n        ffStrbufSetNWS(version, valueLen / sizeof(wchar_t), value);\n        FF_DEBUG(\"version string resolved (default lang): %s\", version->chars);\n        return true;\n    }\n\n    FF_DEBUG(\"default lang query failed, trying translation fallback\");\n\n    struct { WORD language; WORD codePage; }* translations;\n    UINT translationsLen;\n\n    if (VerQueryValueW(versionData, L\"\\\\VarFileInfo\\\\Translation\", (void**) &translations, &translationsLen) &&\n        translationsLen >= sizeof(*translations))\n    {\n        snwprintf(subBlock, ARRAY_SIZE(subBlock), L\"\\\\StringFileInfo\\\\%04x%04x\\\\%ls\",\n                  translations[0].language, translations[0].codePage, stringName);\n        FF_DEBUG(\"query version string with translation: %ls\", subBlock);\n\n        if (VerQueryValueW(versionData, subBlock, (void**) &value, &valueLen) && valueLen > 0)\n        {\n            ffStrbufSetNWS(version, valueLen / sizeof(wchar_t), value);\n            FF_DEBUG(\"version string resolved (translation fallback): %s\", version->chars);\n            return true;\n        }\n\n        FF_DEBUG(\"translation fallback query failed\");\n    }\n    else\n    {\n        FF_DEBUG(\"no translation table found in version resource\");\n    }\n\n    FF_DEBUG(\"ffGetFileVersion failed\");\n    return false;\n}\n"
  },
  {
    "path": "src/common/windows/version.h",
    "content": "#include \"fastfetch.h\"\n\n\n/**\n * @brief Retrieves a specific version string for a Windows file.\n *\n * This function gets a version string from a Windows file's version information.\n *\n * @param filePath The path to the file for which version information is requested.\n * @param stringName The name of the specific version string to retrieve (e.g., \"FileVersion\", \"ProductVersion\").\n * @param version Pointer to an FFstrbuf where the version string will be stored.\n *\n * @return true if the version string was successfully retrieved, false otherwise.\n */\nbool ffGetFileVersion(const wchar_t* filePath, const wchar_t* stringName, FFstrbuf* version);\n"
  },
  {
    "path": "src/common/windows/version.rc",
    "content": "#ifdef RC_INVOKED\n\n#include <winuser.h>\n#include <winver.h>\n#include <ntdef.h>\n#include \"fastfetch_config.h\"\n\n#define FF_TO_STR1(str) #str\n#define FF_TO_STR(str) FF_TO_STR1(str)\n\nCREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST \"manifest.xml\"\nid ICON \"logo.ico\"\n\nVS_VERSION_INFO VERSIONINFO\n    FILEVERSION             FASTFETCH_PROJECT_VERSION_MAJOR,FASTFETCH_PROJECT_VERSION_MINOR,FASTFETCH_PROJECT_VERSION_PATCH,FASTFETCH_PROJECT_VERSION_TWEAK_NUM\n    PRODUCTVERSION          FASTFETCH_PROJECT_VERSION_MAJOR,FASTFETCH_PROJECT_VERSION_MINOR,FASTFETCH_PROJECT_VERSION_PATCH,FASTFETCH_PROJECT_VERSION_TWEAK_NUM\n    FILEOS                  VOS_NT\n    FILETYPE                VFT_APP\n{\n    BLOCK \"StringFileInfo\" {\n        BLOCK \"040904b0\" {\n            VALUE \"Comments\",         FASTFETCH_PROJECT_DESCRIPTION\n            VALUE \"FileDescription\",  FF_TO_STR(FASTFETCH_TARGET_BINARY_NAME)\n            VALUE \"FileVersion\",      FASTFETCH_PROJECT_VERSION FASTFETCH_PROJECT_VERSION_TWEAK\n            VALUE \"InternalName\",     FF_TO_STR(FASTFETCH_TARGET_BINARY_NAME)\n            VALUE \"LegalCopyright\",   FASTFETCH_PROJECT_LICENSE\n            VALUE \"OriginalFilename\", FF_TO_STR(FASTFETCH_TARGET_BINARY_NAME) \".exe\"\n            VALUE \"ProductName\",      FASTFETCH_PROJECT_NAME \" - \" FASTFETCH_PROJECT_DESCRIPTION\n            VALUE \"ProductVersion\",   FASTFETCH_PROJECT_VERSION FASTFETCH_PROJECT_VERSION_TWEAK\n            VALUE \"CompanyName\",      FASTFETCH_PROJECT_HOMEPAGE_URL\n        }\n    }\n    BLOCK \"VarFileInfo\" {\n        VALUE \"Translation\", 0x0409, 1200\n    }\n}\n#endif\n"
  },
  {
    "path": "src/common/windows/wmi.cpp",
    "content": "#include \"wmi.hpp\"\n#include \"common/windows/com.hpp\"\n#include \"common/windows/unicode.hpp\"\n\n#include <synchapi.h>\n#include <wchar.h>\n#include <math.h>\n\nstatic const char* doInitService(const wchar_t* networkResource, IWbemServices** result)\n{\n    HRESULT hres;\n\n    // Obtain the initial locator to WMI\n    IWbemLocator* pLoc = nullptr;\n    hres = CoCreateInstance(\n        CLSID_WbemLocator,\n        nullptr,\n        CLSCTX_INPROC_SERVER,\n        IID_IWbemLocator,\n        (LPVOID*) &pLoc);\n\n    if (FAILED(hres))\n        return \"Failed to create IWbemLocator object\";\n\n    // Connect to WMI through the IWbemLocator::ConnectServer method\n    IWbemServices* pSvc = nullptr;\n\n    // Connect to the root\\cimv2 namespace with\n    // the current user and obtain pointer pSvc\n    // to make IWbemServices calls.\n    hres = pLoc->ConnectServer(\n        bstr_t(networkResource),              // Object path of WMI namespace\n        nullptr,                              // User name. nullptr = current user\n        nullptr,                              // User password. nullptr = current\n        0,                                    // Locale. nullptr indicates current\n        0,                                    // Security flags.\n        0,                                    // Authority (for example, Kerberos)\n        0,                                    // Context object\n        &pSvc                                 // pointer to IWbemServices proxy\n    );\n    pLoc->Release();\n    pLoc = nullptr;\n\n    if (FAILED(hres))\n        return \"Could not connect WMI server\";\n\n    *result = pSvc;\n    return NULL;\n}\n\nFFWmiQuery::FFWmiQuery(const wchar_t* queryStr, FFstrbuf* error, FFWmiNamespace wmiNs)\n{\n    const char* errStr;\n    if ((errStr = ffInitCom()))\n    {\n        if (error)\n            ffStrbufSetS(error, errStr);\n        return;\n    }\n\n    static IWbemServices* contexts[(int) FFWmiNamespace::LAST];\n\n    IWbemServices* context = contexts[(int)wmiNs];\n    if (!context)\n    {\n        if ((errStr = doInitService(wmiNs == FFWmiNamespace::CIMV2 ? L\"ROOT\\\\CIMV2\" : L\"ROOT\\\\WMI\", &context)))\n        {\n            if (error)\n                ffStrbufSetS(error, errStr);\n            return;\n        }\n        contexts[(int)wmiNs] = context;\n    }\n\n    this->pService = context;\n\n    // Use the IWbemServices pointer to make requests of WMI\n    HRESULT hres = context->ExecQuery(\n        bstr_t(L\"WQL\"),\n        bstr_t(queryStr),\n        WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,\n        nullptr,\n        &this->pEnumerator);\n\n    if (FAILED(hres))\n    {\n        if(error)\n            ffStrbufAppendF(error, \"Query for '%ls' failed. Error code = 0x%lX\", queryStr, hres);\n    }\n}\n\nbool FFWmiRecord::getString(const wchar_t* key, FFstrbuf* strbuf)\n{\n    bool result = true;\n\n    FFWmiVariant vtProp;\n\n    CIMTYPE type;\n    if(FAILED(obj->Get(key, 0, &vtProp, &type, nullptr)) || vtProp.vt != VT_BSTR)\n    {\n        result = false;\n    }\n    else\n    {\n        switch(vtProp.vt)\n        {\n            case VT_BSTR:\n                if(type == CIM_DATETIME)\n                {\n                    FF_AUTO_RELEASE_COM_OBJECT ISWbemDateTime *pDateTime = nullptr;\n                    BSTR dateStr;\n                    if(FAILED(CoCreateInstance(__uuidof(SWbemDateTime), 0, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDateTime))))\n                        result = false;\n                    else if(FAILED(pDateTime->put_Value(vtProp.bstrVal)))\n                        result = false;\n                    else if(FAILED(pDateTime->GetFileTime(VARIANT_TRUE, &dateStr)))\n                        result = false;\n                    else\n                        ffStrbufSetNWS(strbuf, SysStringLen(dateStr), dateStr);\n                }\n                else\n                {\n                    ffStrbufSetNWS(strbuf, SysStringLen(vtProp.bstrVal), vtProp.bstrVal);\n                }\n                break;\n\n            case VT_LPSTR:\n                ffStrbufAppendS(strbuf, vtProp.pcVal);\n                break;\n\n            case VT_LPWSTR:\n            default:\n                ffStrbufSetWS(strbuf, vtProp.bstrVal);\n                break;\n        }\n    }\n    return result;\n}\n\nbool FFWmiRecord::getSigned(const wchar_t* key, int64_t* integer)\n{\n    bool result = true;\n\n    FFWmiVariant vtProp;\n\n    CIMTYPE type;\n    if(FAILED(obj->Get(key, 0, &vtProp, &type, nullptr)))\n    {\n        result = false;\n    }\n    else\n    {\n        switch(vtProp.vt)\n        {\n            case VT_BSTR: *integer = wcstoll(vtProp.bstrVal, nullptr, 10); break;\n            case VT_I1: *integer = vtProp.cVal; break;\n            case VT_I2: *integer = vtProp.iVal; break;\n            case VT_INT:\n            case VT_I4: *integer = vtProp.intVal; break;\n            case VT_I8: *integer = vtProp.llVal; break;\n            case VT_UI1: *integer = (int64_t)vtProp.bVal; break;\n            case VT_UI2: *integer = (int64_t)vtProp.uiVal; break;\n            case VT_UINT:\n            case VT_UI4: *integer = (int64_t)vtProp.uintVal; break;\n            case VT_UI8: *integer = (int64_t)vtProp.ullVal; break;\n            case VT_BOOL: *integer = vtProp.boolVal != VARIANT_FALSE; break;\n            default: *integer = 0; result = false;\n        }\n    }\n    return result;\n}\n\nbool FFWmiRecord::getUnsigned(const wchar_t* key, uint64_t* integer)\n{\n    bool result = true;\n\n    FFWmiVariant vtProp;\n\n    if(FAILED(obj->Get(key, 0, &vtProp, nullptr, nullptr)))\n    {\n        result = false;\n    }\n    else\n    {\n        switch(vtProp.vt)\n        {\n            case VT_BSTR: *integer = wcstoull(vtProp.bstrVal, nullptr, 10); break;\n            case VT_I1: *integer = (uint64_t)vtProp.cVal; break;\n            case VT_I2: *integer = (uint64_t)vtProp.iVal; break;\n            case VT_INT:\n            case VT_I4: *integer = (uint64_t)vtProp.intVal; break;\n            case VT_I8: *integer = (uint64_t)vtProp.llVal; break;\n            case VT_UI1: *integer = vtProp.bVal; break;\n            case VT_UI2: *integer = vtProp.uiVal; break;\n            case VT_UINT:\n            case VT_UI4: *integer = vtProp.uintVal; break;\n            case VT_UI8: *integer = vtProp.ullVal; break;\n            case VT_BOOL: *integer = vtProp.boolVal != VARIANT_FALSE; break;\n            default: *integer = 0; result = false;\n        }\n    }\n    return result;\n}\n\nbool FFWmiRecord::getReal(const wchar_t* key, double* real)\n{\n    bool result = true;\n\n    FFWmiVariant vtProp;\n\n    if(FAILED(obj->Get(key, 0, &vtProp, nullptr, nullptr)))\n    {\n        result = false;\n    }\n    else\n    {\n        switch(vtProp.vt)\n        {\n            case VT_BSTR: *real = wcstod(vtProp.bstrVal, nullptr); break;\n            case VT_I1: *real = vtProp.cVal; break;\n            case VT_I2: *real = vtProp.iVal; break;\n            case VT_INT:\n            case VT_I4: *real = vtProp.intVal; break;\n            case VT_I8: *real = (double)vtProp.llVal; break;\n            case VT_UI1: *real = vtProp.bVal; break;\n            case VT_UI2: *real = vtProp.uiVal; break;\n            case VT_UINT:\n            case VT_UI4: *real = vtProp.uintVal; break;\n            case VT_UI8: *real = (double)vtProp.ullVal; break;\n            case VT_R4: *real = vtProp.fltVal; break;\n            case VT_R8: *real = vtProp.dblVal; break;\n            case VT_BOOL: *real = vtProp.boolVal != VARIANT_FALSE; break;\n            default: *real = -DBL_MAX; result = false;\n        }\n    }\n    return result;\n}\n"
  },
  {
    "path": "src/common/windows/wmi.hpp",
    "content": "#pragma once\n\n#ifdef __cplusplus\n\nextern \"C\" {\n    #include \"fastfetch.h\"\n}\n\n#include <initguid.h>\n#include <wbemidl.h>\n\n#include \"variant.hpp\"\n\nenum class FFWmiNamespace {\n    CIMV2,\n    WMI,\n    LAST,\n};\n\nstruct FFWmiRecord\n{\n    IWbemClassObject* obj = nullptr;\n\n    explicit FFWmiRecord(IWbemClassObject* obj): obj(obj) {};\n    FFWmiRecord(const FFWmiRecord&) = delete;\n    FFWmiRecord(FFWmiRecord&& other) { *this = (FFWmiRecord&&)other; }\n    ~FFWmiRecord() { if(obj) obj->Release(); }\n    explicit operator bool() { return !!obj; }\n    FFWmiRecord& operator =(FFWmiRecord&& other) {\n        if(obj) obj->Release();\n        obj = other.obj;\n        other.obj = nullptr;\n        return *this;\n    }\n\n    bool getString(const wchar_t* key, FFstrbuf* strbuf);\n    bool getSigned(const wchar_t* key, int64_t* integer);\n    bool getUnsigned(const wchar_t* key, uint64_t* integer);\n    bool getReal(const wchar_t* key, double* real);\n    FFWmiVariant get(const wchar_t* key) {\n        FFWmiVariant result;\n        obj->Get(key, 0, &result, nullptr, nullptr);\n        return result;\n    }\n};\n\nstruct FFWmiQuery\n{\n    IWbemServices* pService = nullptr;\n    IEnumWbemClassObject* pEnumerator = nullptr;\n\n    FFWmiQuery(const wchar_t* queryStr, FFstrbuf* error = nullptr, FFWmiNamespace wmiNs = FFWmiNamespace::CIMV2);\n    explicit FFWmiQuery(IEnumWbemClassObject* pEnumerator): pEnumerator(pEnumerator) {}\n    FFWmiQuery(const FFWmiQuery& other) = delete;\n    FFWmiQuery(FFWmiQuery&& other) { *this = (FFWmiQuery&&)other; }\n    ~FFWmiQuery() { if(pEnumerator) pEnumerator->Release(); }\n\n    explicit operator bool() { return !!pEnumerator; }\n    FFWmiQuery& operator =(FFWmiQuery&& other) {\n        if(pEnumerator) pEnumerator->Release();\n        pEnumerator = other.pEnumerator;\n        other.pEnumerator = nullptr;\n        return *this;\n    }\n\n    FFWmiRecord next() {\n        IWbemClassObject* obj = nullptr;\n        ULONG ret;\n        pEnumerator->Next(instance.config.general.wmiTimeout, 1, &obj, &ret);\n\n        FFWmiRecord result(obj);\n        return result;\n    }\n};\n\n#else\n    // Win32 COM headers requires C++ compiler\n    #error Must be included in C++ source file\n#endif //__cplusplus\n"
  },
  {
    "path": "src/data/help.json",
    "content": "{\n    \"Informative\": [\n        {\n            \"short\": \"h\",\n            \"long\": \"help\",\n            \"desc\": \"Display this help message or help for a specific command\",\n            \"arg\": {\n                \"type\": \"command\",\n                \"optional\": true\n            }\n        },\n        {\n            \"short\": \"v\",\n            \"long\": \"version\",\n            \"desc\": \"Show the full version of fastfetch\"\n        },\n        {\n            \"long\": \"version-raw\",\n            \"desc\": \"Display the raw version string (major.minor.patch)\"\n        },\n        {\n            \"long\": \"list-config-paths\",\n            \"desc\": \"List search paths for config files\"\n        },\n        {\n            \"long\": \"list-data-paths\",\n            \"desc\": \"List search paths for presets and logos\"\n        },\n        {\n            \"long\": \"list-logos\",\n            \"desc\": \"List available logos\"\n        },\n        {\n            \"long\": \"list-modules\",\n            \"desc\": \"List available modules\"\n        },\n        {\n            \"long\": \"list-presets\",\n            \"desc\": \"List presets that fastfetch knows about\",\n            \"remark\": \"Presets can be loaded with \\\"--config <preset-name>\\\"\"\n        },\n        {\n            \"long\": \"list-features\",\n            \"desc\": \"List the supported features that fastfetch was compiled with\",\n            \"remark\": \"Mainly for development\"\n        },\n        {\n            \"long\": \"print-logos\",\n            \"desc\": \"Display available logos\"\n        },\n        {\n            \"long\": \"print-structure\",\n            \"desc\": \"Display the default structure\"\n        },\n        {\n            \"long\": \"format\",\n            \"desc\": \"Set output format\",\n            \"arg\": {\n                \"type\": \"enum\",\n                \"enum\": {\n                    \"default\": \"Default format\",\n                    \"json\": \"JSON format\"\n                },\n                \"default\": \"default\"\n            }\n        },\n        {\n            \"long\": \"json\",\n            \"short\": \"j\",\n            \"arg\": {\n                \"type\": \"bool\",\n                \"default\": false,\n                \"optional\": true\n            },\n            \"desc\": \"Enable or disable JSON output\",\n            \"remark\": \"Shortcut for `--format json`\"\n        },\n        {\n            \"long\": \"dynamic-interval\",\n            \"desc\": \"Keep fastfetch open and update the output every <num> milliseconds\",\n            \"remark\": \"0 (default) to disable the behavior; don't work with --json\",\n            \"arg\": {\n                \"type\": \"num\",\n                \"default\": 0\n            }\n        }\n    ],\n    \"Config\": [\n        {\n            \"short\": \"c\",\n            \"long\": \"config\",\n            \"desc\": \"Specify the config file or preset to load\",\n            \"remark\": \"The file will be searched according to the order shown by \\\"fastfetch --list-config-paths\\\". Use \\\"-\\\" to read config from stdin or \\\"none\\\" to disable further config loading. See also https://github.com/fastfetch-cli/fastfetch/wiki/Configuration for more info\",\n            \"arg\": {\n                \"type\": \"config\"\n            }\n        },\n        {\n            \"long\": \"gen-config\",\n            \"desc\": \"Generate a minimal config file at the specified path\",\n            \"remark\": \"Defaults to \\\"~/.config/fastfetch/config.jsonc\\\". Will print the generated config if <path> is \\\"-\\\"\",\n            \"arg\": {\n                \"type\": \"path\",\n                \"optional\": true\n            }\n        },\n        {\n            \"long\": \"gen-config-full\",\n            \"desc\": \"Generate a full config file with all optional options at the specified path\",\n            \"remark\": \"Defaults to \\\"~/.config/fastfetch/config.jsonc\\\". Will print the generated config if <path> is \\\"-\\\"\",\n            \"arg\": {\n                \"type\": \"path\",\n                \"optional\": true\n            }\n        },\n        {\n            \"long\": \"gen-config-force\",\n            \"desc\": \"Generate a config file at the specified path, overwriting any existing file\",\n            \"remark\": \"Defaults to \\\"~/.config/fastfetch/config.jsonc\\\"\",\n            \"arg\": {\n                \"type\": \"path\",\n                \"optional\": true\n            }\n        }\n    ],\n    \"General\": [\n        {\n            \"long\": \"thread\",\n            \"desc\": \"Use separate threads for HTTP requests\",\n            \"arg\": {\n                \"type\": \"bool\",\n                \"optional\": true,\n                \"default\": true\n            }\n        },\n        {\n            \"long\": \"wmi-timeout\",\n            \"desc\": \"Set the timeout (ms) for WMI queries\",\n            \"remark\": \"Windows only\",\n            \"arg\": {\n                \"type\": \"num\",\n                \"default\": 5000\n            }\n        },\n        {\n            \"long\": \"processing-timeout\",\n            \"desc\": \"Set the timeout (ms) when waiting for child processes\",\n            \"arg\": {\n                \"type\": \"num\",\n                \"default\": 5000\n            }\n        },\n        {\n            \"long\": \"ds-force-drm\",\n            \"desc\": \"Specify whether only DRM should be used to detect displays\",\n            \"remark\": [\n                \"Use this option if you encounter problems with other detection methods.\",\n                \"Linux only\"\n            ],\n            \"arg\": {\n                \"type\": \"enum\",\n                \"optional\": true,\n                \"default\": \"false\",\n                \"enum\": {\n                    \"true\": \"Try `libdrm` first, then `sysfs` if libdrm fails\",\n                    \"sysfs-only\": \"Use `/sys/class/drm` only\",\n                    \"false\": \"Try `wayland`, then `x11`, then `drm`\"\n                }\n            }\n        },\n        {\n            \"long\": \"detect-version\",\n            \"desc\": \"Specify whether to detect and display versions of terminal, shell, editor, and others\",\n            \"remark\": \"Mainly for benchmarking\",\n            \"arg\": {\n                \"type\": \"bool\",\n                \"optional\": true,\n                \"default\": true\n            }\n        }\n    ],\n    \"Logo\": [\n        {\n            \"short\": \"l\",\n            \"long\": \"logo\",\n            \"desc\": \"Set the logo source. Use \\\"none\\\" to disable the logo\",\n            \"remark\": \"Should be the name of a built-in logo or a path to an image file. See also https://github.com/fastfetch-cli/fastfetch/wiki/Logo-options\",\n            \"arg\": {\n                \"type\": \"logo\"\n            }\n        },\n        {\n            \"long\": \"logo-type\",\n            \"desc\": \"Set the type of the logo specified in \\\"--logo\\\"\",\n            \"remark\": \"See also https://github.com/fastfetch-cli/fastfetch/wiki/Logo-options\",\n            \"arg\": {\n                \"type\": \"enum\",\n                \"enum\": {\n                    \"auto\": \"If something is given, first try built-in, then file. Otherwise detect logo\",\n                    \"builtin\": \"Built-in ASCII art\",\n                    \"small\": \"Built-in ASCII art, small version\",\n                    \"file\": \"Text file, printed with color code replacement\",\n                    \"file-raw\": \"Text file, printed as is\",\n                    \"data\": \"Text data, printed with color code replacement\",\n                    \"data-raw\": \"Text data, printed as is\",\n                    \"sixel\": \"Image file, printed as sixel codes\",\n                    \"kitty\": \"Image file, printed using kitty graphics protocol\",\n                    \"kitty-direct\": \"Image file, tells the terminal emulator to read image data from the specified file\",\n                    \"kitty-icat\": \"Image file, uses `kitten icat` to display the image. Requires binary `kitten` to be installed\",\n                    \"iterm\": \"Image file, printed using iterm graphics protocol\",\n                    \"chafa\": \"Image file, printed as ASCII art using libchafa\",\n                    \"raw\": \"Image file, printed as raw binary string\",\n                    \"none\": \"Disable logo printing\"\n                }\n            }\n        },\n        {\n            \"long\": \"logo-width\",\n            \"desc\": \"Set the width of the logo (in characters) if it is an image\",\n            \"remark\": \"Required for iTerm image protocol\",\n            \"arg\": {\n                \"type\": \"num\"\n            }\n        },\n        {\n            \"long\": \"logo-height\",\n            \"desc\": \"Set the height of the logo (in characters) if it is an image\",\n            \"remark\": \"Required for iTerm image protocol\",\n            \"arg\": {\n                \"type\": \"num\"\n            }\n        },\n        {\n            \"long\": \"logo-preserve-aspect-ratio\",\n            \"desc\": \"Specify whether the logo should fill the specified width and height as much as possible without stretching\",\n            \"remark\": \"Supported by iTerm image protocol only\",\n            \"arg\": {\n                \"type\": \"bool\",\n                \"optional\": true,\n                \"default\": false\n            }\n        },\n        {\n            \"long\": \"logo-color-[1-9]\",\n            \"desc\": \"Override a color in the logo\",\n            \"remark\": \"See `-h color` for the list of available colors\",\n            \"arg\": {\n                \"type\": \"color\"\n            },\n            \"pseudo\": true\n        },\n        {\n            \"long\": \"logo-padding\",\n            \"desc\": \"Set the padding on the left and right sides of the logo\",\n            \"arg\": {\n                \"type\": \"num\"\n            }\n        },\n        {\n            \"long\": \"logo-padding-left\",\n            \"desc\": \"Set the padding on the left side of the logo\",\n            \"arg\": {\n                \"type\": \"num\"\n            }\n        },\n        {\n            \"long\": \"logo-padding-right\",\n            \"desc\": \"Set the padding on the right side of the logo\",\n            \"arg\": {\n                \"type\": \"num\"\n            }\n        },\n        {\n            \"long\": \"logo-padding-top\",\n            \"desc\": \"Set the padding at the top of the logo\",\n            \"arg\": {\n                \"type\": \"num\"\n            }\n        },\n        {\n            \"long\": \"logo-print-remaining\",\n            \"desc\": \"Specify whether to print the remaining logo if it has more lines than modules to display\",\n            \"arg\": {\n                \"type\": \"bool\",\n                \"optional\": true,\n                \"default\": true\n            }\n        },\n        {\n            \"long\": \"logo-position\",\n            \"desc\": \"Set the position where the logo should be displayed\",\n            \"arg\": {\n                \"type\": \"enum\",\n                \"enum\": {\n                    \"left\": \"Left\",\n                    \"top\": \"Top\",\n                    \"right\": \"Right\"\n                }\n            }\n        },\n        {\n            \"long\": \"logo-recache\",\n            \"desc\": \"If true, regenerate the image logo cache\",\n            \"arg\": {\n                \"type\": \"bool\",\n                \"optional\": true,\n                \"default\": false\n            }\n        },\n        {\n            \"long\": \"file\",\n            \"desc\": \"Short for --logo-type file --logo <path>\",\n            \"remark\": \"See \\\"--help logo-type\\\" for more info\",\n            \"arg\": {\n                \"type\": \"path\"\n            }\n        },\n        {\n            \"long\": \"file-raw\",\n            \"desc\": \"Short for --logo-type file-raw --logo <path>\",\n            \"remark\": \"See \\\"--help logo-type\\\" for more info\",\n            \"arg\": {\n                \"type\": \"path\"\n            }\n        },\n        {\n            \"long\": \"data\",\n            \"desc\": \"Short for --logo-type data --logo <data>\",\n            \"remark\": \"See \\\"--help logo-type\\\" for more info\",\n            \"arg\": {\n                \"type\": \"data\"\n            }\n        },\n        {\n            \"long\": \"data-raw\",\n            \"desc\": \"Short for --logo-type data-raw --logo <data>\",\n            \"remark\": \"See \\\"--help logo-type\\\" for more info\",\n            \"arg\": {\n                \"type\": \"data\"\n            }\n        },\n        {\n            \"long\": \"raw\",\n            \"desc\": \"Short for --logo-type raw --logo <path>\",\n            \"remark\": \"See \\\"--help logo-type\\\" for more info\",\n            \"arg\": {\n                \"type\": \"path\"\n            }\n        },\n        {\n            \"long\": \"sixel\",\n            \"desc\": \"Short for --logo-type sixel --logo <path>\",\n            \"remark\": \"See \\\"--help logo-type\\\" for more info\",\n            \"arg\": {\n                \"type\": \"path\"\n            }\n        },\n        {\n            \"long\": \"kitty\",\n            \"desc\": \"Short for --logo-type kitty --logo <path>\",\n            \"remark\": \"See \\\"--help logo-type\\\" for more info\",\n            \"arg\": {\n                \"type\": \"path\"\n            }\n        },\n        {\n            \"long\": \"kitty-direct\",\n            \"desc\": \"Short for --logo-type kitty-direct --logo <path>\",\n            \"remark\": \"See \\\"--help logo-type\\\" for more info\",\n            \"arg\": {\n                \"type\": \"path\"\n            }\n        },\n        {\n            \"long\": \"kitty-icat\",\n            \"desc\": \"Short for --logo-type kitty-icat --logo <path>\",\n            \"remark\": \"See \\\"--help logo-type\\\" for more info\",\n            \"arg\": {\n                \"type\": \"path\"\n            }\n        },\n        {\n            \"long\": \"iterm\",\n            \"desc\": \"Short for --logo-type iterm --logo <path>\",\n            \"remark\": \"See \\\"--help logo-type\\\" for more info\",\n            \"arg\": {\n                \"type\": \"path\"\n            }\n        },\n        {\n            \"long\": \"chafa\",\n            \"desc\": \"Short for --logo-type chafa --logo <path>\",\n            \"remark\": \"See \\\"--help logo-type\\\" for more info\",\n            \"arg\": {\n                \"type\": \"path\"\n            }\n        },\n        {\n            \"long\": \"chafa-fg-only\",\n            \"desc\": \"Produce character-cell output using foreground colors only\",\n            \"remark\": \"See chafa document for detail\",\n            \"arg\": {\n                \"type\": \"bool\",\n                \"optional\": true,\n                \"default\": false\n            }\n        },\n        {\n            \"long\": \"chafa-symbols\",\n            \"desc\": \"Specify character symbols to employ in final output\",\n            \"remark\": \"See chafa document for detail\",\n            \"arg\": {\n                \"type\": \"str\"\n            }\n        },\n        {\n            \"long\": \"chafa-canvas-mode\",\n            \"desc\": \"Determine how colors are used in the output\",\n            \"remark\": \"This value maps the int value of enum ChafaCanvasMode. See chafa document for detail\",\n            \"arg\": {\n                \"type\": \"enum\",\n                \"enum\": {\n                    \"TRUECOLOR\": \"Truecolor\",\n                    \"INDEXED_256\": \"256 colors\",\n                    \"INDEXED_240\": \"256 colors, but avoid using the lower 16 whose values vary between terminal environments\",\n                    \"INDEXED_16\": \"16 colors using the aixterm ANSI extension\",\n                    \"FGBG_BGFG\": \"Default foreground and background colors, plus inversion\",\n                    \"FGBG\": \"Default foreground and background colors. No ANSI codes will be used\",\n                    \"INDEXED_8\": \"8 colors, compatible with original ANSI X3.64\",\n                    \"INDEXED_16_8\": \"16 FG colors (8 of which enabled with bold/bright) and 8 BG colors\"\n                }\n            }\n        },\n        {\n            \"long\": \"chafa-color-space\",\n            \"desc\": \"Set color space used for quantization\",\n            \"remark\": \"This value maps the int value of enum ChafaColorSpace. See chafa document for detail\",\n            \"arg\": {\n                \"type\": \"enum\",\n                \"enum\": {\n                    \"RGB\": \"RGB color space. Fast but imprecise\",\n                    \"DIN99D\": \"DIN99d color space. Slower, but good perceptual color precision\"\n                }\n            }\n        },\n        {\n            \"long\": \"chafa-dither-mode\",\n            \"desc\": \"Set output dither mode (No effect with 24-bit color)\",\n            \"remark\": \"This value maps the int value of enum ChafaDitherMode. See chafa document for detail\",\n            \"arg\": {\n                \"type\": \"enum\",\n                \"enum\": {\n                    \"NONE\": \"No dithering\",\n                    \"ORDERED\": \"Ordered dithering (Bayer or similar)\",\n                    \"DIFFUSION\": \"Error diffusion dithering (Floyd-Steinberg or similar)\"\n                }\n            }\n        }\n    ],\n    \"Display\": [\n        {\n            \"short\": \"s\",\n            \"long\": \"structure\",\n            \"desc\": \"Set the structure of the fetch\",\n            \"remark\": \"Must be a colon-separated list of keys. Use \\\"fastfetch --list-modules\\\" to see available options\",\n            \"arg\": {\n                \"type\": \"structure\",\n                \"default\": \"\\\"fastfetch --print-structure\\\"\"\n            }\n        },\n        {\n            \"long\": \"structure-disabled\",\n            \"desc\": \"Disable specific modules in the structure\",\n            \"remark\": \"Must be a colon-separated list of keys\",\n            \"arg\": {\n                \"type\": \"structure\"\n            }\n        },\n        {\n            \"long\": \"stat\",\n            \"desc\": \"Show time usage (in ms) for individual modules\",\n            \"arg\": {\n                \"type\": \"bool\",\n                \"optional\": true,\n                \"default\": false\n            }\n        },\n        {\n            \"long\": \"pipe\",\n            \"desc\": \"Disable colors\",\n            \"remark\": \"Auto-detected based on isatty(1) by default\",\n            \"arg\": {\n                \"type\": \"bool\",\n                \"optional\": true,\n                \"default\": false\n            }\n        },\n        {\n            \"long\": \"color\",\n            \"desc\": \"Set the color of both keys and title\",\n            \"remark\": [\n                \"Shortcut for \\\"--color-keys <color>\\\" and \\\"--color-title <color>\\\"\",\n                \"For color syntax, see <https://github.com/fastfetch-cli/fastfetch/wiki/Color-Format-Specification>\"\n            ],\n            \"arg\": {\n                \"type\": \"color\"\n            }\n        },\n        {\n            \"long\": \"color-keys\",\n            \"desc\": \"Set the color of the keys\",\n            \"remark\": \"Doesn't affect Title, Separator, and Colors modules. See `-h color` for the list of available colors\",\n            \"arg\": {\n                \"type\": \"color\"\n            }\n        },\n        {\n            \"long\": \"color-title\",\n            \"desc\": \"Set the color of the title\",\n            \"remark\": \"See `-h color` for the list of available colors\",\n            \"arg\": {\n                \"type\": \"color\"\n            }\n        },\n        {\n            \"long\": \"color-output\",\n            \"desc\": \"Set the color of module output\",\n            \"remark\": \"See `-h color` for the list of available colors\",\n            \"arg\": {\n                \"type\": \"color\"\n            }\n        },\n        {\n            \"long\": \"color-separator\",\n            \"desc\": \"Set the color of the key-value separator\",\n            \"remark\": \"See `-h color` for the list of available colors\",\n            \"arg\": {\n                \"type\": \"color\"\n            }\n        },\n        {\n            \"long\": \"duration-abbreviation\",\n            \"desc\": \"Specify whether to abbreviate duration values\",\n            \"remark\": \"If true, the output will be in the form of \\\"1h 2m\\\" instead of \\\"1 hour, 2 mins\\\"\",\n            \"arg\": {\n                \"type\": \"bool\",\n                \"optional\": true,\n                \"default\": false\n            }\n        },\n        {\n            \"long\": \"duration-space-before-unit\",\n            \"desc\": \"Specify whether to put a space before the unit in duration values\",\n            \"arg\": {\n                \"type\": \"enum\",\n                \"enum\": {\n                    \"default\": \"Use the default behavior of the module\",\n                    \"always\": \"Always put a space before the unit\",\n                    \"never\": \"Never put a space before the unit\"\n                }\n            }\n        },\n        {\n            \"long\": \"key-width\",\n            \"desc\": \"Align the width of keys to <num> characters\",\n            \"arg\": {\n                \"type\": \"num\"\n            }\n        },\n        {\n            \"long\": \"key-padding-left\",\n            \"desc\": \"Set the left padding of keys to <num> characters\",\n            \"arg\": {\n                \"type\": \"num\"\n            }\n        },\n        {\n            \"long\": \"key-type\",\n            \"desc\": \"Specify whether to show an icon before string keys\",\n            \"arg\": {\n                \"type\": \"enum\",\n                \"enum\": {\n                    \"none\": \"Disable keys\",\n                    \"string\": \"Show string\",\n                    \"icon\": \"Show icon (requires newest nerd font)\",\n                    \"both\": \"Show both icon and string (alias of `both-1`)\",\n                    \"both-0\": \"Show both icon and string with no spaces between them\",\n                    \"both-1\": \"Show both icon and string with a space between them\",\n                    \"both-2\": \"Show both icon and string with 2 spaces between them\",\n                    \"both-3\": \"Show both icon and string with 3 spaces between them\",\n                    \"both-4\": \"Show both icon and string with 4 spaces between them\"\n                },\n                \"default\": \"string\"\n            }\n        },\n        {\n            \"long\": \"bright-color\",\n            \"desc\": \"Specify whether keys, title, and ASCII logo should be printed in bright color\",\n            \"arg\": {\n                \"type\": \"bool\",\n                \"optional\": true,\n                \"default\": true\n            }\n        },\n        {\n            \"long\": \"separator\",\n            \"desc\": \"Set the separator between key and value\",\n            \"arg\": {\n                \"type\": \"str\",\n                \"default\": \": \"\n            }\n        },\n        {\n            \"long\": \"show-errors\",\n            \"desc\": \"Print errors when they occur\",\n            \"arg\": {\n                \"type\": \"bool\",\n                \"optional\": true,\n                \"default\": false\n            }\n        },\n        {\n            \"long\": \"disable-linewrap\",\n            \"desc\": \"Specify whether to disable line wrap during execution\",\n            \"arg\": {\n                \"type\": \"bool\",\n                \"optional\": true,\n                \"default\": true\n            }\n        },\n        {\n            \"long\": \"hide-cursor\",\n            \"desc\": \"Specify whether to hide the cursor during execution\",\n            \"arg\": {\n                \"type\": \"bool\",\n                \"optional\": true,\n                \"default\": false\n            }\n        },\n        {\n            \"long\": \"percent-type\",\n            \"desc\": \"Set the percentage output type\",\n            \"remark\": [\n                \"1 for percentage number\",\n                \"2 for multi-color bar\",\n                \"3 for both\",\n                \"6 for bar only\",\n                \"9 for colored number\",\n                \"10 for monochrome bar\"\n            ],\n            \"arg\": {\n                \"type\": \"num\",\n                \"default\": 9\n            }\n        },\n        {\n            \"long\": \"percent-ndigits\",\n            \"desc\": \"Set the number of digits to keep after the decimal point when formatting percentage numbers\",\n            \"arg\": {\n                \"type\": \"num\",\n                \"default\": 0\n            }\n        },\n        {\n            \"long\": \"percent-color-green\",\n            \"desc\": \"Set color used for the green state of percentage bars and numbers\",\n            \"remark\": \"See `-h color` for the list of available colors\",\n            \"arg\": {\n                \"type\": \"color\",\n                \"default\": \"green\"\n            }\n        },\n        {\n            \"long\": \"percent-color-yellow\",\n            \"desc\": \"Set color used for the yellow state of percentage bars and numbers\",\n            \"remark\": \"See `-h color` for the list of available colors\",\n            \"arg\": {\n                \"type\": \"color\",\n                \"default\": \"light_yellow\"\n            }\n        },\n        {\n            \"long\": \"percent-color-red\",\n            \"desc\": \"Set color used for the red state of percentage bars and numbers\",\n            \"remark\": \"See `-h color` for the list of available colors\",\n            \"arg\": {\n                \"type\": \"color\",\n                \"default\": \"light_red\"\n            }\n        },\n        {\n            \"long\": \"percent-space-before-unit\",\n            \"desc\": \"Specify whether to put a space before the percentage symbol\",\n            \"arg\": {\n                \"type\": \"enum\",\n                \"enum\": {\n                    \"default\": \"Use the default behavior of the module\",\n                    \"always\": \"Always put a space before the unit\",\n                    \"never\": \"Never put a space before the unit\"\n                }\n            }\n        },\n        {\n            \"long\": \"percent-width\",\n            \"desc\": \"Specify the width of the percentage number, in number of characters\",\n            \"remark\": \"This option affects only percentage numbers, not bars\",\n            \"arg\": {\n                \"type\": \"num\",\n                \"default\": 0\n            }\n        },\n        {\n            \"long\": \"bar-char-elapsed\",\n            \"desc\": \"Set the character to use in the elapsed part of percentage bars\",\n            \"arg\": {\n                \"type\": \"str\",\n                \"default\": \"\\u25a0\"\n            }\n        },\n        {\n            \"long\": \"bar-char-total\",\n            \"desc\": \"Set the character to use in the total part of percentage bars\",\n            \"arg\": {\n                \"type\": \"str\",\n                \"default\": \"-\"\n            }\n        },\n        {\n            \"long\": \"bar-border-left\",\n            \"desc\": \"Set the string to use at the left border of percentage bars\",\n            \"arg\": {\n                \"type\": \"string\",\n                \"default\": \"[ \"\n            }\n        },\n        {\n            \"long\": \"bar-border-right\",\n            \"desc\": \"Set the string to use at the right border of percentage bars\",\n            \"arg\": {\n                \"type\": \"string\",\n                \"default\": \" ]\"\n            }\n        },\n        {\n            \"long\": \"bar-border-left-elapsed\",\n            \"desc\": \"If both bar-border-left-elapsed and bar-border-right-elapsed are set, the border will be used as parts of bar content\",\n            \"arg\": {\n                \"type\": \"string\",\n                \"default\": \"\"\n            }\n        },\n        {\n            \"long\": \"bar-border-right-elapsed\",\n            \"desc\": \"If both bar-border-left-elapsed and bar-border-right-elapsed are set, the border will be used as parts of bar content\",\n            \"arg\": {\n                \"type\": \"string\",\n                \"default\": \"\"\n            }\n        },\n        {\n            \"long\": \"bar-color-elapsed\",\n            \"desc\": \"Set the color to use in the elapsed part of percentage bars\",\n            \"remark\": \"By default, auto selected by percent-color-{green,yellow,red}\",\n            \"arg\": {\n                \"type\": \"color\",\n                \"default\": \"<auto>\"\n            }\n        },\n        {\n            \"long\": \"bar-color-total\",\n            \"desc\": \"Set the color to use in the total part of percentage bars\",\n            \"arg\": {\n                \"type\": \"color\",\n                \"default\": \"light_white\"\n            }\n        },\n        {\n            \"long\": \"bar-color-border\",\n            \"desc\": \"Set the color to use in the borders of percentage bars\",\n            \"arg\": {\n                \"type\": \"color\",\n                \"default\": \"light_white\"\n            }\n        },\n        {\n            \"long\": \"bar-width\",\n            \"desc\": \"Set the width of percentage bars in characters\",\n            \"arg\": {\n                \"type\": \"num\",\n                \"default\": 10\n            }\n        },\n        {\n            \"long\": \"no-buffer\",\n            \"desc\": \"Specify whether the stdout application buffer should be disabled\",\n            \"arg\": {\n                \"type\": \"bool\",\n                \"optional\": true,\n                \"default\": false\n            }\n        },\n        {\n            \"long\": \"size-ndigits\",\n            \"desc\": \"Set the number of digits to keep after the decimal point when formatting sizes\",\n            \"arg\": {\n                \"type\": \"num\"\n            }\n        },\n        {\n            \"long\": \"size-binary-prefix\",\n            \"desc\": \"Set the binary prefix to use when formatting sizes\",\n            \"arg\": {\n                \"type\": \"enum\",\n                \"enum\": {\n                    \"IEC\": \"1024 Bytes = 1 KiB, 1024 KiB = 1 MiB, ...\",\n                    \"SI\": \"1000 Bytes = 1 kB, 1000 kB = 1 MB, ...\",\n                    \"JEDEC\": \"1024 Bytes = 1 KB, 1024 KB = 1 MB, ...\"\n                },\n                \"default\": \"IEC\"\n            }\n        },\n        {\n            \"long\": \"size-max-prefix\",\n            \"desc\": \"Set the largest binary prefix to use when formatting sizes\",\n            \"arg\": {\n                \"type\": \"enum\",\n                \"enum\": {\n                    \"B\": \"Bytes\",\n                    \"kB\": \"KiB\",\n                    \"MB\": \"MiB\",\n                    \"GB\": \"GiB\",\n                    \"TB\": \"TiB\",\n                    \"PB\": \"PiB\",\n                    \"EB\": \"EiB\",\n                    \"ZB\": \"ZiB\",\n                    \"YB\": \"YiB\"\n                },\n                \"default\": \"YB\"\n            }\n        },\n        {\n            \"long\": \"size-space-before-unit\",\n            \"desc\": \"Specify whether to put a space before the unit\",\n            \"arg\": {\n                \"type\": \"enum\",\n                \"enum\": {\n                    \"default\": \"Use the default behavior of the module\",\n                    \"always\": \"Always put a space before the unit\",\n                    \"never\": \"Never put a space before the unit\"\n                }\n            }\n        },\n        {\n            \"long\": \"freq-ndigits\",\n            \"desc\": \"Set the number of digits to keep after the decimal point when printing CPU/GPU frequency in GHz\",\n            \"arg\": {\n                \"type\": \"num\",\n                \"default\": 2\n            }\n        },\n        {\n            \"long\": \"freq-space-before-unit\",\n            \"desc\": \"Specify whether to put a space before the unit\",\n            \"arg\": {\n                \"type\": \"enum\",\n                \"enum\": {\n                    \"default\": \"Use the default behavior of the module\",\n                    \"always\": \"Always put a space before the unit\",\n                    \"never\": \"Never put a space before the unit\"\n                }\n            }\n        },\n        {\n            \"long\": \"fraction-ndigits\",\n            \"desc\": \"Set the number of digits to keep after the decimal point when printing ordinary fraction numbers\",\n            \"remark\": \"If negative, the number of digits will be automatically determined based on the value\",\n            \"arg\": {\n                \"type\": \"num\",\n                \"default\": -1\n            }\n        },\n        {\n            \"long\": \"fraction-trailing-zeros\",\n            \"desc\": \"Set when to keep trailing zeros\",\n            \"arg\": {\n                \"type\": \"enum\",\n                \"enum\": {\n                    \"default\": \"Use the behavior defined internally\",\n                    \"always\": \"Always keep trailing zeros\",\n                    \"never\": \"Never keep trailing zeros\"\n                }\n            }\n        },\n        {\n            \"long\": \"temp-unit\",\n            \"desc\": \"Set the temperature unit\",\n            \"arg\": {\n                \"type\": \"enum\",\n                \"enum\": {\n                    \"D\": \"Default\",\n                    \"C\": \"Celsius\",\n                    \"F\": \"Fahrenheit\",\n                    \"K\": \"Kelvin\"\n                },\n                \"default\": \"D\"\n            }\n        },\n        {\n            \"long\": \"temp-ndigits\",\n            \"desc\": \"Set the number of digits to keep after the decimal point when printing temperature\",\n            \"arg\": {\n                \"type\": \"num\",\n                \"default\": 2\n            }\n        },\n        {\n            \"long\": \"temp-color-green\",\n            \"desc\": \"Set color used for the green state of temperature values\",\n            \"remark\": \"See `-h color` for the list of available colors\",\n            \"arg\": {\n                \"type\": \"color\",\n                \"default\": \"green\"\n            }\n        },\n        {\n            \"long\": \"temp-color-yellow\",\n            \"desc\": \"Set color used for the yellow state of temperature values\",\n            \"remark\": \"See `-h color` for the list of available colors\",\n            \"arg\": {\n                \"type\": \"color\",\n                \"default\": \"light_yellow\"\n            }\n        },\n        {\n            \"long\": \"temp-color-red\",\n            \"desc\": \"Set color used for the red state of temperature values\",\n            \"remark\": \"See `-h color` for the list of available colors\",\n            \"arg\": {\n                \"type\": \"color\",\n                \"default\": \"light_red\"\n            }\n        },\n        {\n            \"long\": \"temp-space-before-unit\",\n            \"desc\": \"Specify whether to put a space before the unit\",\n            \"arg\": {\n                \"type\": \"enum\",\n                \"enum\": {\n                    \"default\": \"Use the default behavior of the module\",\n                    \"always\": \"Always put a space before the unit\",\n                    \"never\": \"Never put a space before the unit\"\n                }\n            }\n        }\n    ]\n}\n"
  },
  {
    "path": "src/data/structure.txt",
    "content": "Title:Separator:OS:Host:Kernel:Uptime:Packages:Shell:Display:DE:WM:WMTheme:Theme:Icons:Font:Cursor:Terminal:TerminalFont:CPU:GPU:Memory:Swap:Disk:LocalIp:Battery:PowerAdapter:Locale:Break:Colors\n"
  },
  {
    "path": "src/detection/battery/battery.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/battery/option.h\"\n\n#define FF_BATTERY_TEMP_UNSET (-DBL_MAX)\n\ntypedef struct FFBatteryResult\n{\n    FFstrbuf manufacturer;\n    FFstrbuf manufactureDate;\n    FFstrbuf modelName;\n    FFstrbuf technology;\n    FFstrbuf status;\n    FFstrbuf serial;\n    double capacity;\n    double temperature;\n    uint32_t cycleCount;\n    int32_t timeRemaining; // in seconds, -1 if unknown\n} FFBatteryResult;\n\nconst char* ffDetectBattery(FFBatteryOptions* options, FFlist* results);\n"
  },
  {
    "path": "src/detection/battery/battery_android.c",
    "content": "#include \"fastfetch.h\"\n#include \"battery.h\"\n#include \"common/stringUtils.h\"\n#include \"common/processing.h\"\n#include \"common/properties.h\"\n\n#define FF_TERMUX_API_PATH FASTFETCH_TARGET_DIR_ROOT \"/libexec/termux-api\"\n#define FF_TERMUX_API_PARAM \"BatteryStatus\"\n\nstatic inline void wrapYyjsonFree(yyjson_doc** doc)\n{\n    assert(doc);\n    if (*doc)\n        yyjson_doc_free(*doc);\n}\n\nstatic const char* parseTermuxApi(FFBatteryOptions* options, FFlist* results)\n{\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n\n    if(ffProcessAppendStdOut(&buffer, (char* const[]){\n        FF_TERMUX_API_PATH,\n        FF_TERMUX_API_PARAM,\n        NULL\n    }))\n        return \"Starting `\" FF_TERMUX_API_PATH \" \" FF_TERMUX_API_PARAM \"` failed\";\n\n    yyjson_doc* __attribute__((__cleanup__(wrapYyjsonFree))) doc = yyjson_read_opts(buffer.chars, buffer.length, 0, NULL, NULL);\n    if (!doc)\n        return \"Failed to parse battery info\";\n\n    yyjson_val* root = yyjson_doc_get_root(doc);\n    if (!yyjson_is_obj(root))\n        return \"Battery info result is not a JSON object\";\n\n    FFBatteryResult* battery = ffListAdd(results);\n    battery->temperature = FF_BATTERY_TEMP_UNSET;\n    battery->cycleCount = 0;\n    battery->timeRemaining = -1;\n    ffStrbufInit(&battery->manufacturer);\n    ffStrbufInit(&battery->modelName);\n    ffStrbufInit(&battery->status);\n    ffStrbufInit(&battery->technology);\n    ffStrbufInit(&battery->serial);\n    ffStrbufInit(&battery->manufactureDate);\n\n    battery->capacity = yyjson_get_num(yyjson_obj_get(root, \"percentage\"));\n    const char* acStatus = yyjson_get_str(yyjson_obj_get(root, \"plugged\"));\n    if (acStatus)\n    {\n        if (ffStrEquals(acStatus, \"PLUGGED_AC\"))\n            ffStrbufAppendS(&battery->status, \"AC Connected, \");\n        else if (ffStrEquals(acStatus, \"PLUGGED_USB\"))\n            ffStrbufAppendS(&battery->status, \"USB Connected, \");\n        else if (ffStrEquals(acStatus, \"PLUGGED_WIRELESS\"))\n            ffStrbufAppendS(&battery->status, \"Wireless Connected, \");\n    }\n    const char* status = yyjson_get_str(yyjson_obj_get(root, \"status\"));\n    if (status)\n    {\n        if (ffStrEquals(status, \"CHARGING\"))\n            ffStrbufAppendS(&battery->status, \"Charging\");\n        else if (ffStrEquals(status, \"DISCHARGING\"))\n            ffStrbufAppendS(&battery->status, \"Discharging\");\n    }\n    ffStrbufTrimRight(&battery->status, ' ');\n    ffStrbufTrimRight(&battery->status, ',');\n\n    if(options->temp)\n        battery->temperature = yyjson_get_num(yyjson_obj_get(root, \"temperature\"));\n\n    return NULL;\n}\n\nstatic const char* parseDumpsys(FFBatteryOptions* options, FFlist* results)\n{\n    FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();\n    if (ffProcessAppendStdOut(&buf, (char* []) {\n        \"/system/bin/dumpsys\",\n        \"battery\",\n        NULL,\n    }) != NULL || buf.length == 0)\n        return \"Executing `/system/bin/dumpsys battery` failed\"; // Only works in `adb shell`, or when rooted\n\n    if (!ffStrbufStartsWithS(&buf, \"Current Battery Service state:\\n\"))\n        return \"Invalid `/system/bin/dumpsys battery` result\";\n\n    const char* start = buf.chars + strlen(\"Current Battery Service state:\\n\");\n\n    FF_STRBUF_AUTO_DESTROY temp = ffStrbufCreate();\n    if (!ffParsePropLines(start, \"present: \", &temp) || !ffStrbufEqualS(&temp, \"true\"))\n        return NULL;\n    ffStrbufClear(&temp);\n\n    FFBatteryResult* battery = ffListAdd(results);\n    battery->temperature = FF_BATTERY_TEMP_UNSET;\n    battery->cycleCount = 0;\n    battery->timeRemaining = -1;\n    battery->capacity = 0;\n    ffStrbufInit(&battery->manufacturer);\n    ffStrbufInit(&battery->modelName);\n    ffStrbufInit(&battery->status);\n    ffStrbufInit(&battery->technology);\n    ffStrbufInit(&battery->serial);\n    ffStrbufInit(&battery->manufactureDate);\n\n    if (ffParsePropLines(start, \"AC powered: \", &temp) && ffStrbufEqualS(&temp, \"true\"))\n        ffStrbufAppendS(&battery->status, \"AC powered\");\n    ffStrbufClear(&temp);\n\n    if (ffParsePropLines(start, \"USB powered: \", &temp) && ffStrbufEqualS(&temp, \"true\"))\n    {\n        if (battery->status.length) ffStrbufAppendS(&battery->status, \", \");\n        ffStrbufAppendS(&battery->status, \"USB powered\");\n    }\n    ffStrbufClear(&temp);\n\n    if (ffParsePropLines(start, \"Wireless powered: \", &temp) && ffStrbufEqualS(&temp, \"true\"))\n    {\n        if (battery->status.length) ffStrbufAppendS(&battery->status, \", \");\n        ffStrbufAppendS(&battery->status, \"Wireless powered\");\n    }\n    ffStrbufClear(&temp);\n\n    {\n        double level = 0, scale = 0;\n        if (ffParsePropLines(start, \"level: \", &temp))\n            level = ffStrbufToDouble(&temp, -DBL_MAX);\n        ffStrbufClear(&temp);\n\n        if (ffParsePropLines(start, \"scale: \", &temp))\n            scale = ffStrbufToDouble(&temp, -DBL_MAX);\n        ffStrbufClear(&temp);\n\n        if (level > 0 && scale > 0)\n            battery->capacity = level * 100 / scale;\n    }\n\n    if(options->temp)\n    {\n        if (ffParsePropLines(start, \"temperature: \", &temp))\n        {\n            battery->temperature = ffStrbufToDouble(&temp, FF_BATTERY_TEMP_UNSET);\n            if (battery->temperature != FF_BATTERY_TEMP_UNSET)\n                battery->temperature /= 10.0; // Android returns temperature in tenths of a degree\n        }\n        ffStrbufClear(&temp);\n    }\n\n    ffParsePropLines(start, \"technology: \", &battery->technology);\n\n    return NULL;\n}\n\nconst char* ffDetectBattery(FFBatteryOptions* options, FFlist* results)\n{\n    const char* error = parseTermuxApi(options, results);\n    if (error && parseDumpsys(options, results) == NULL)\n        return NULL;\n    return error;\n}\n"
  },
  {
    "path": "src/detection/battery/battery_apple.c",
    "content": "#include \"fastfetch.h\"\n#include \"battery.h\"\n#include \"common/apple/cf_helpers.h\"\n#include \"common/apple/smc_temps.h\"\n\n#include <IOKit/IOKitLib.h>\n#include <IOKit/pwr_mgt/IOPM.h>\n\nconst char* ffDetectBattery(FFBatteryOptions* options, FFlist* results)\n{\n    FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL;\n    if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceMatching(\"AppleSmartBattery\"), &iterator) != kIOReturnSuccess)\n        return \"IOServiceGetMatchingServices() failed\";\n\n    io_registry_entry_t registryEntry;\n    while ((registryEntry = IOIteratorNext(iterator)) != IO_OBJECT_NULL)\n    {\n        FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryBattery = registryEntry;\n        FF_CFTYPE_AUTO_RELEASE CFMutableDictionaryRef properties = NULL;\n        if (IORegistryEntryCreateCFProperties(entryBattery, &properties, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess)\n            continue;\n\n        int currentCapacity, maxCapacity;\n\n        if (ffCfDictGetInt(properties, CFSTR(kIOPMPSMaxCapacityKey), &maxCapacity) != NULL || maxCapacity <= 0)\n            continue;\n\n        if (ffCfDictGetInt(properties, CFSTR(kIOPMPSCurrentCapacityKey), &currentCapacity) != NULL || currentCapacity <= 0)\n            continue;\n\n        bool boolValue;\n\n        FFBatteryResult* battery = ffListAdd(results);\n        battery->temperature = FF_BATTERY_TEMP_UNSET;\n        ffStrbufInit(&battery->manufacturer);\n        ffStrbufInit(&battery->modelName);\n        ffStrbufInit(&battery->serial);\n        ffStrbufInit(&battery->technology);\n        ffStrbufInit(&battery->status);\n        ffStrbufInit(&battery->manufactureDate);\n        battery->capacity = currentCapacity * 100.0 / maxCapacity;\n\n        ffCfDictGetString(properties, CFSTR(kIOPMDeviceNameKey), &battery->modelName);\n        ffCfDictGetString(properties, CFSTR(kIOPMPSSerialKey), &battery->serial);\n        ffCfDictGetString(properties, CFSTR(kIOPMPSManufacturerKey), &battery->manufacturer);\n\n        if (!ffCfDictGetBool(properties, CFSTR(\"built-in\"), &boolValue) && boolValue)\n        {\n            if (!battery->manufacturer.length)\n                ffStrbufAppendS(&battery->manufacturer, \"Apple Inc.\");\n            ffStrbufAppendS(&battery->technology, \"Lithium\");\n            if (!battery->modelName.length)\n                ffStrbufAppendS(&battery->modelName, \"Built-in\");\n        }\n\n        int32_t cycleCount = 0;\n        ffCfDictGetInt(properties, CFSTR(kIOPMPSCycleCountKey), &cycleCount);\n        battery->cycleCount = cycleCount < 0 ? 0 : (uint32_t) cycleCount;\n\n        battery->timeRemaining = -1;\n        if (ffCfDictGetBool(properties, CFSTR(kIOPMPSExternalConnectedKey), &boolValue) == NULL)\n        {\n            if (boolValue)\n                ffStrbufAppendS(&battery->status, \"AC connected, \");\n            else\n            {\n                ffStrbufAppendS(&battery->status, \"Discharging, \");\n                ffCfDictGetInt(properties, CFSTR(\"AvgTimeToEmpty\"), &battery->timeRemaining); // in minutes\n                if (battery->timeRemaining < 0 || battery->timeRemaining >= 0xFFFF)\n                    battery->timeRemaining = -1;\n                else\n                    battery->timeRemaining *= 60;\n            }\n        }\n        if (ffCfDictGetBool(properties, CFSTR(kIOPMPSIsChargingKey), &boolValue) == NULL && boolValue)\n            ffStrbufAppendS(&battery->status, \"Charging, \");\n        if (ffCfDictGetBool(properties, CFSTR(kIOPMPSAtCriticalLevelKey), &boolValue) == NULL && boolValue)\n            ffStrbufAppendS(&battery->status, \"Critical, \");\n        ffStrbufTrimRight(&battery->status, ' ');\n        ffStrbufTrimRight(&battery->status, ',');\n\n        int sbdsManufactureDate = 0;\n        if (ffCfDictGetInt(properties, CFSTR(kIOPMPSManufactureDateKey), &sbdsManufactureDate) == NULL)\n        {\n            int day = sbdsManufactureDate & 0b11111;\n            int month = (sbdsManufactureDate >> 5) & 0b1111;\n            int year = (sbdsManufactureDate >> 9) + 1800;\n            ffStrbufSetF(&battery->manufactureDate, \"%.4d-%.2d-%.2d\", year, month, day);\n        }\n        else\n        {\n            CFDictionaryRef batteryData;\n            if (ffCfDictGetDict(properties, CFSTR(\"BatteryData\"), &batteryData) == NULL)\n            {\n                char manufactureDate[sizeof(uint64_t)];\n                if (ffCfDictGetInt64(batteryData, CFSTR(kIOPMPSManufactureDateKey), (int64_t*) manufactureDate) == NULL)\n                {\n                    // https://github.com/AsahiLinux/linux/blob/b5c05cbffb0488c7618106926d522cc3b43d93d5/drivers/power/supply/macsmc_power.c#L410-L419\n                    int year = (manufactureDate[0] - '0') * 10 + (manufactureDate[1] - '0') + 2000 - 8;\n                    int month = (manufactureDate[2] - '0') * 10 + (manufactureDate[3] - '0');\n                    int day = (manufactureDate[4] - '0') * 10 + (manufactureDate[3] - '0');\n                    ffStrbufSetF(&battery->manufactureDate, \"%.4d-%.2d-%.2d\", year, month, day);\n                }\n            }\n        }\n\n        if (options->temp)\n        {\n            int64_t temp;\n            if (!ffCfDictGetInt64(properties, CFSTR(kIOPMPSBatteryTemperatureKey), &temp))\n                battery->temperature = (double) temp / 10 - 273.15;\n            else\n                ffDetectSmcTemps(FF_TEMP_BATTERY, &battery->temperature);\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/battery/battery_bsd.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/sysctl.h\"\n#include \"common/io.h\"\n#include \"battery.h\"\n\n#include <dev/acpica/acpiio.h>\n#include <sys/ioctl.h>\n#include <sys/fcntl.h>\n#include <unistd.h>\n\nconst char* ffDetectBattery(FF_MAYBE_UNUSED FFBatteryOptions* options, FFlist* results)\n{\n    //https://www.freebsd.org/cgi/man.cgi?acpi_battery(4)\n    //https://gitlab.xfce.org/panel-plugins/xfce4-battery-plugin/-/blob/master/panel-plugin/libacpi.c\n\n    int units = ffSysctlGetInt(\"hw.acpi.battery.units\", -100);\n    if (units < 0)\n        return \"sysctlbyname(\\\"hw.acpi.battery.units\\\") failed\";\n\n    if(units == 0)\n        return NULL;\n\n    FF_AUTO_CLOSE_FD int acpifd = open(\"/dev/acpi\", O_RDONLY | O_CLOEXEC);\n    if(acpifd < 0)\n        return \"open(\\\"/dev/acpi\\\", O_RDONLY | O_CLOEXEC) failed\";\n\n    for(int i = 0; i < units; ++i)\n    {\n        union acpi_battery_ioctl_arg battio;\n        battio.unit = i;\n\n        if(ioctl(acpifd, ACPIIO_BATT_GET_BATTINFO, &battio) < 0 || (battio.battinfo.state == ACPI_BATT_STAT_NOT_PRESENT))\n            continue;\n\n        FFBatteryResult* battery = ffListAdd(results);\n        battery->temperature = FF_BATTERY_TEMP_UNSET;\n        battery->cycleCount = 0;\n        ffStrbufInit(&battery->manufacturer);\n        ffStrbufInit(&battery->modelName);\n        ffStrbufInit(&battery->status);\n        ffStrbufInit(&battery->technology);\n        ffStrbufInit(&battery->serial);\n        ffStrbufInit(&battery->manufactureDate);\n        battery->timeRemaining = -1;\n        if (battio.battinfo.min > 0)\n            battery->timeRemaining = battio.battinfo.min * 60;\n        battery->capacity = battio.battinfo.cap;\n        if(battio.battinfo.state == ACPI_BATT_STAT_INVALID)\n        {\n            ffStrbufAppendS(&battery->status, \"Unknown, \");\n        }\n        else\n        {\n            if(battio.battinfo.state & ACPI_BATT_STAT_DISCHARG)\n                ffStrbufAppendS(&battery->status, \"Discharging, \");\n            else if(battio.battinfo.state & ACPI_BATT_STAT_CHARGING)\n                ffStrbufAppendS(&battery->status, \"Charging, \");\n            if(battio.battinfo.state & ACPI_BATT_STAT_CRITICAL)\n                ffStrbufAppendS(&battery->status, \"Critical, \");\n        }\n\n        int acadStatus;\n        if (ioctl(acpifd, ACPIIO_ACAD_GET_STATUS, &acadStatus) >= 0 && acadStatus)\n        {\n            ffStrbufAppendS(&battery->status, \"AC Connected\");\n        }\n        else\n        {\n            ffStrbufTrimRight(&battery->status, ' ');\n            ffStrbufTrimRight(&battery->status, ',');\n        }\n\n        #ifdef ACPIIO_BATT_GET_BIX\n        battio.unit = i;\n        if (ioctl(acpifd, ACPIIO_BATT_GET_BIX, &battio) >= 0)\n        {\n            ffStrbufAppendS(&battery->manufacturer, battio.bix.oeminfo);\n            ffStrbufAppendS(&battery->modelName, battio.bix.model);\n            ffStrbufAppendS(&battery->technology, battio.bix.type);\n            ffStrbufAppendS(&battery->serial, battio.bix.serial);\n            battery->cycleCount = battio.bix.cycles;\n        }\n        #elif defined(ACPIIO_BATT_GET_BIF)\n        battio.unit = i;\n        if (ioctl(acpifd, ACPIIO_BATT_GET_BIF, &battio) >= 0)\n        {\n            ffStrbufAppendS(&battery->manufacturer, battio.bif.oeminfo);\n            ffStrbufAppendS(&battery->modelName, battio.bif.model);\n            ffStrbufAppendS(&battery->technology, battio.bif.type);\n            ffStrbufAppendS(&battery->serial, battio.bif.serial);\n        }\n        #endif\n    }\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/battery/battery_haiku.c",
    "content": "#include \"fastfetch.h\"\n#include \"battery.h\"\n#include \"common/io.h\"\n\n#include <private/device/power_managment.h>\n#include <sys/ioctl.h>\n#include <fcntl.h>\n\nconst char* parseBattery(int dfd, const char* battId, FFlist* results)\n{\n    FF_AUTO_CLOSE_FD int fd = openat(dfd, battId, O_RDWR);\n    if (fd < 0) return \"openat() failed\";\n\n    acpi_battery_info basic = {};\n    if (ioctl(fd, GET_BATTERY_INFO, &basic, sizeof(basic)) != 0)\n        return \"ioctl(GET_BATTERY_INFO) failed\";\n    acpi_extended_battery_info extended = {};\n    if (ioctl(fd, GET_EXTENDED_BATTERY_INFO, &extended, sizeof(extended)) != 0)\n        return \"ioctl(GET_EXTENDED_BATTERY_INFO) failed\";\n\n    if (extended.last_full_charge == (uint32)-1)\n        return \"Skipped\";\n\n    FFBatteryResult* battery = (FFBatteryResult*)ffListAdd(results);\n    ffStrbufInitS(&battery->modelName, extended.model_number);\n    ffStrbufInitS(&battery->manufacturer, extended.oem_info);\n    ffStrbufInit(&battery->manufactureDate);\n    ffStrbufInitS(&battery->technology, extended.type); // extended.technology?\n    ffStrbufInit(&battery->status);\n    ffStrbufInitS(&battery->serial, extended.serial_number);\n    battery->temperature = FF_BATTERY_TEMP_UNSET;\n    battery->cycleCount = extended.cycles;\n    battery->timeRemaining = -1;\n    battery->capacity = (double) basic.capacity * 100. / (double) extended.last_full_charge;\n\n    if (basic.state & BATTERY_DISCHARGING)\n        ffStrbufAppendS(&battery->status, \"Discharging, \");\n    if (basic.state & BATTERY_CHARGING)\n        ffStrbufAppendS(&battery->status, \"Charging, \");\n    if (basic.state & BATTERY_CRITICAL_STATE)\n        ffStrbufAppendS(&battery->status, \"Critical, \");\n    if (basic.state & BATTERY_NOT_CHARGING)\n        ffStrbufAppendS(&battery->status, \"AC Connected, \");\n    ffStrbufTrimRight(&battery->status, ' ');\n    ffStrbufTrimRight(&battery->status, ',');\n\n    return NULL;\n}\n\nconst char* ffDetectBattery(FF_MAYBE_UNUSED FFBatteryOptions* options, FFlist* results)\n{\n    FF_AUTO_CLOSE_DIR DIR* dir = opendir(\"/dev/power/acpi_battery/\");\n    if (!dir) return \"opendir(/dev/power/acpi_battery) failed\";\n\n    struct dirent* entry;\n    while ((entry = readdir(dir)))\n    {\n        if (entry->d_name[0] == '.') continue;\n        parseBattery(dirfd(dir), entry->d_name, results);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/battery/battery_linux.c",
    "content": "#include \"battery.h\"\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n\n#include <dirent.h>\n#include <unistd.h>\n#include <fcntl.h>\n\n// https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-power\n\nstatic bool checkAc(const char* id, FFstrbuf* tmpBuffer)\n{\n    if (ffStrStartsWith(id, \"BAT\"))\n        ffStrbufSetS(tmpBuffer, \"/sys/class/power_supply/ADP1/online\");\n    else if (ffStrStartsWith(id, \"macsmc-battery\"))\n        ffStrbufSetS(tmpBuffer, \"/sys/class/power_supply/macsmc-ac/online\");\n    else\n        ffStrbufClear(tmpBuffer);\n\n    char online = '\\0';\n    return ffReadFileData(tmpBuffer->chars, 1, &online) == 1 && online == '1';\n}\n\nstatic void parseBattery(int dfd, const char* id, FFBatteryOptions* options, FFlist* results)\n{\n    FF_STRBUF_AUTO_DESTROY tmpBuffer = ffStrbufCreate();\n\n    // type must exist and be \"Battery\"\n    if (!ffReadFileBufferRelative(dfd, \"type\", &tmpBuffer))\n        return;\n    ffStrbufTrimRightSpace(&tmpBuffer);\n    if(!ffStrbufIgnCaseEqualS(&tmpBuffer, \"Battery\"))\n        return;\n\n    // scope may not exist or must not be \"Device\"\n    if (ffReadFileBufferRelative(dfd, \"scope\", &tmpBuffer))\n        ffStrbufTrimRightSpace(&tmpBuffer);\n\n    if(ffStrbufIgnCaseEqualS(&tmpBuffer, \"Device\"))\n        return;\n\n    // capacity must exist and be not empty\n    // This is expensive in my laptop\n    if (!ffReadFileBufferRelative(dfd, \"capacity\", &tmpBuffer))\n        return;\n\n    FFBatteryResult* result = ffListAdd(results);\n    result->capacity = ffStrbufToDouble(&tmpBuffer, 0);\n\n    //At this point, we have a battery. Try to get as much values as possible.\n\n    ffStrbufInit(&result->manufacturer);\n    if (ffReadFileBufferRelative(dfd, \"manufacturer\", &result->manufacturer))\n        ffStrbufTrimRightSpace(&result->manufacturer);\n    else if (ffStrEquals(id, \"macsmc-battery\")) // asahi\n        ffStrbufSetStatic(&result->manufacturer, \"Apple Inc.\");\n\n    ffStrbufInit(&result->modelName);\n    if (ffReadFileBufferRelative(dfd, \"model_name\", &result->modelName))\n        ffStrbufTrimRightSpace(&result->modelName);\n\n    ffStrbufInit(&result->technology);\n    if (ffReadFileBufferRelative(dfd, \"technology\", &result->technology))\n        ffStrbufTrimRightSpace(&result->technology);\n\n    ffStrbufInit(&result->status);\n    if (ffReadFileBufferRelative(dfd, \"status\", &result->status))\n        ffStrbufTrimRightSpace(&result->status);\n\n    // Unknown, Charging, Discharging, Not charging, Full\n\n    result->timeRemaining = -1;\n    if (ffStrbufEqualS(&result->status, \"Discharging\"))\n    {\n        if (ffReadFileBufferRelative(dfd, \"time_to_empty_now\", &tmpBuffer))\n            result->timeRemaining = (int32_t) ffStrbufToSInt(&tmpBuffer, 0);\n        else\n        {\n            if (ffReadFileBufferRelative(dfd, \"charge_now\", &tmpBuffer))\n            {\n                int64_t chargeNow = ffStrbufToSInt(&tmpBuffer, 0);\n                if (chargeNow > 0)\n                {\n                    if (ffReadFileBufferRelative(dfd, \"current_now\", &tmpBuffer))\n                    {\n                        int64_t currentNow = ffStrbufToSInt(&tmpBuffer, INT64_MIN);\n                        if (currentNow < 0) currentNow = -currentNow;\n                        if (currentNow > 0)\n                            result->timeRemaining = (int32_t) ((chargeNow * 3600) / currentNow);\n                    }\n                }\n            }\n        }\n\n        if (checkAc(id, &tmpBuffer))\n            ffStrbufAppendS(&result->status, \", AC Connected\");\n    }\n    else if (ffStrbufEqualS(&result->status, \"Not charging\") || ffStrbufEqualS(&result->status, \"Full\"))\n        ffStrbufSetStatic(&result->status, \"AC Connected\");\n    else if (ffStrbufEqualS(&result->status, \"Charging\"))\n        ffStrbufAppendS(&result->status, \", AC Connected\");\n    else if (ffStrbufEqualS(&result->status, \"Unknown\"))\n    {\n        ffStrbufClear(&result->status);\n        if (checkAc(id, &tmpBuffer))\n            ffStrbufAppendS(&result->status, \"AC Connected\");\n    }\n\n    if (ffReadFileBufferRelative(dfd, \"capacity_level\", &tmpBuffer))\n    {\n        ffStrbufTrimRightSpace(&tmpBuffer);\n        if (ffStrbufEqualS(&tmpBuffer, \"Critical\"))\n        {\n            if (result->status.length)\n                ffStrbufAppendS(&result->status, \", Critical\");\n            else\n                ffStrbufSetStatic(&result->status, \"Critical\");\n        }\n    }\n\n    ffStrbufInit(&result->serial);\n    if (ffReadFileBufferRelative(dfd, \"serial_number\", &result->serial))\n        ffStrbufTrimRightSpace(&result->serial);\n\n    if (ffReadFileBufferRelative(dfd, \"cycle_count\", &tmpBuffer))\n    {\n        int64_t cycleCount = ffStrbufToSInt(&tmpBuffer, 0);\n        result->cycleCount = cycleCount < 0 || cycleCount > UINT32_MAX ? 0 : (uint32_t) cycleCount;\n    }\n\n    ffStrbufInit(&result->manufactureDate);\n    if (ffReadFileBufferRelative(dfd, \"manufacture_year\", &tmpBuffer))\n    {\n        int year = (int) ffStrbufToSInt(&tmpBuffer, 0);\n        if (year > 0)\n        {\n            if (ffReadFileBufferRelative(dfd, \"manufacture_month\", &tmpBuffer))\n            {\n                int month = (int) ffStrbufToSInt(&tmpBuffer, 0);\n                if (month > 0)\n                {\n                    if (ffReadFileBufferRelative(dfd, \"manufacture_day\", &tmpBuffer))\n                    {\n                        int day = (int) ffStrbufToSInt(&tmpBuffer, 0);\n                        if (day > 0)\n                            ffStrbufSetF(&result->manufactureDate, \"%.4d-%.2d-%.2d\", year, month, day);\n                    }\n                }\n            }\n        }\n    }\n\n    result->temperature = FF_BATTERY_TEMP_UNSET;\n    if (options->temp)\n    {\n        if (ffReadFileBufferRelative(dfd, \"temp\", &tmpBuffer))\n        {\n            result->temperature = ffStrbufToDouble(&tmpBuffer, FF_BATTERY_TEMP_UNSET);\n            if (result->temperature != FF_BATTERY_TEMP_UNSET)\n                result->temperature /= 10;\n        }\n    }\n}\n\nconst char* ffDetectBattery(FFBatteryOptions* options, FFlist* results)\n{\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(\"/sys/class/power_supply/\");\n    if(dirp == NULL)\n        return \"opendir(\\\"/sys/class/power_supply/\\\") == NULL\";\n\n    struct dirent* entry;\n    while((entry = readdir(dirp)) != NULL)\n    {\n        if(entry->d_name[0] == '.')\n            continue;\n\n        FF_AUTO_CLOSE_FD int dfd = openat(dirfd(dirp), entry->d_name, O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY);\n        if (dfd > 0) parseBattery(dfd, entry->d_name, options, results);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/battery/battery_nbsd.c",
    "content": "#include \"battery.h\"\n#include \"common/io.h\"\n#include \"common/FFstrbuf.h\"\n#include \"common/stringUtils.h\"\n\n#include <prop/prop_array.h>\n#include <prop/prop_bool.h>\n#include <prop/prop_dictionary.h>\n#include <prop/prop_object.h>\n#include <sys/envsys.h>\n#include <prop/proplib.h>\n#include <paths.h>\n#include <time.h>\n#include <unistd.h>\n#include <fcntl.h>\n\nconst char* ffDetectBattery(FF_MAYBE_UNUSED FFBatteryOptions* options, FFlist* results)\n{\n    FF_AUTO_CLOSE_FD int fd = open(_PATH_SYSMON, O_RDONLY | O_CLOEXEC);\n    if (fd < 0) return \"open(_PATH_SYSMON, O_RDONLY | O_CLOEXEC) failed\";\n\n    prop_dictionary_t root = NULL;\n    if (prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &root) < 0)\n        return \"prop_dictionary_recv_ioctl(ENVSYS_GETDICTIONARY) failed\";\n\n    bool acConnected = false;\n    {\n        prop_array_t acad = prop_dictionary_get(root, \"acpiacad0\");\n        if (acad)\n        {\n            prop_dictionary_t dict = prop_array_get(acad, 0);\n            prop_dictionary_get_uint8(dict, \"cur-value\", (uint8_t*) &acConnected);\n        }\n    }\n\n    prop_object_iterator_t itKey = prop_dictionary_iterator(root);\n    for (prop_dictionary_keysym_t key; (key = prop_object_iterator_next(itKey)) != NULL; )\n    {\n        if (!ffStrStartsWith(prop_dictionary_keysym_value(key), \"acpibat\")) continue;\n\n        prop_array_t bat = prop_dictionary_get_keysym(root, key);\n        uint32_t max = 0, curr = 0, dischargeRate = 0;\n        bool charging = false, critical = false;\n        prop_object_iterator_t iter = prop_array_iterator(bat);\n        for (prop_dictionary_t dict; (dict = prop_object_iterator_next(iter)) != NULL;)\n        {\n            if (prop_object_type(dict) != PROP_TYPE_DICTIONARY)\n                continue;\n\n            const char* desc = NULL;\n            if (!prop_dictionary_get_string(dict, \"description\", &desc))\n                continue;\n\n            if (ffStrEquals(desc, \"present\"))\n            {\n                int value = 0;\n                if (prop_dictionary_get_int(dict, \"cur-value\", &value) && value == 0)\n                    continue;\n            }\n            else if (ffStrEquals(desc, \"charging\"))\n            {\n                prop_dictionary_get_uint8(dict, \"cur-value\", (uint8_t*) &charging);\n            }\n            else if (ffStrEquals(desc, \"charge\"))\n            {\n                prop_dictionary_get_uint32(dict, \"max-value\", &max);\n                prop_dictionary_get_uint32(dict, \"cur-value\", &curr);\n                const char* state = NULL;\n                if (prop_dictionary_get_string(dict, \"state\", &state) && ffStrEquals(state, \"critical\"))\n                    critical = true;\n            }\n            else if (ffStrEquals(desc, \"discharge rate\"))\n                prop_dictionary_get_uint(dict, \"cur-value\", &dischargeRate);\n        }\n\n        if (max > 0)\n        {\n            FFBatteryResult* battery = ffListAdd(results);\n            battery->temperature = FF_BATTERY_TEMP_UNSET;\n            battery->cycleCount = 0;\n            ffStrbufInit(&battery->manufacturer);\n            ffStrbufInit(&battery->modelName);\n            ffStrbufInit(&battery->status);\n            ffStrbufInit(&battery->technology);\n            ffStrbufInit(&battery->serial);\n            ffStrbufInit(&battery->manufactureDate);\n            battery->timeRemaining = -1;\n\n            battery->capacity = (double) curr / (double) max * 100.;\n            if (charging)\n                ffStrbufAppendS(&battery->status, \"Charging, \");\n            else if (dischargeRate)\n            {\n                ffStrbufAppendS(&battery->status, \"Discharging, \");\n                battery->timeRemaining = (int32_t)((double)curr / dischargeRate * 3600);\n            }\n            if (critical)\n                ffStrbufAppendS(&battery->status, \"Critical, \");\n            if (acConnected)\n                ffStrbufAppendS(&battery->status, \"AC Connected\");\n            ffStrbufTrimRight(&battery->status, ' ');\n            ffStrbufTrimRight(&battery->status, ',');\n        }\n\n        prop_object_iterator_release(iter);\n    }\n    prop_object_iterator_release(itKey);\n    prop_object_release(root);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/battery/battery_nosupport.c",
    "content": "#include \"fastfetch.h\"\n#include \"battery.h\"\n\nconst char* ffDetectBattery(FFBatteryOptions* options, FFlist* results)\n{\n    FF_UNUSED(options, results)\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/battery/battery_obsd.c",
    "content": "#include \"battery.h\"\n#include \"common/io.h\"\n\n#include <machine/apmvar.h>\n#include <sys/ioctl.h>\n#include <unistd.h>\n#include <fcntl.h>\n\nconst char* ffDetectBattery(FF_MAYBE_UNUSED FFBatteryOptions* options, FFlist* result)\n{\n    FF_AUTO_CLOSE_FD int devfd = open(\"/dev/apm\", O_RDONLY | O_CLOEXEC);\n\n    if (devfd < 0) return \"open(dev/apm, O_RDONLY | O_CLOEXEC) failed\";\n\n    struct apm_power_info info = {};\n\n    if (ioctl(devfd, APM_IOC_GETPOWER, &info) < 0)\n        return \"ioctl(APM_IOC_GETPOWER) failed\";\n\n    if (info.battery_state == APM_BATTERY_ABSENT)\n        return NULL;\n\n    FFBatteryResult* battery = (FFBatteryResult*) ffListAdd(result);\n    battery->temperature = FF_BATTERY_TEMP_UNSET;\n    battery->cycleCount = 0;\n    battery->timeRemaining = -1;\n    battery->capacity = info.battery_life;\n    ffStrbufInit(&battery->manufacturer);\n    ffStrbufInit(&battery->modelName);\n    ffStrbufInit(&battery->status);\n    ffStrbufInit(&battery->technology);\n    ffStrbufInit(&battery->serial);\n    ffStrbufInit(&battery->manufactureDate);\n\n    if (info.ac_state == APM_AC_ON)\n        ffStrbufAppendS(&battery->status, \"AC Connected\");\n    else if (info.ac_state == APM_AC_BACKUP)\n        ffStrbufAppendS(&battery->status, \"Backup In Use\");\n    else if (info.ac_state == APM_AC_OFF)\n    {\n        battery->timeRemaining = (int) info.minutes_left * 60;\n        ffStrbufAppendS(&battery->status, \"Discharging\");\n    }\n\n    if (info.battery_state == APM_BATT_CRITICAL || info.battery_state == APM_BATT_CHARGING)\n    {\n        if (battery->status.length) ffStrbufAppendS(&battery->status, \", \");\n        ffStrbufAppendS(&battery->status, info.battery_state == APM_BATT_CRITICAL ? \"Critical\" : \"Charging\");\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/battery/battery_windows.c",
    "content": "#include \"battery.h\"\n\n#include \"common/io.h\"\n#include \"common/windows/nt.h\"\n#include \"common/windows/unicode.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/smbiosHelper.h\"\n\n#undef WIN32_LEAN_AND_MEAN\n#include <windows.h>\n#include <batclass.h>\n#include <devguid.h>\n#include <cfgmgr32.h>\n\nstatic const char* detectWithCmApi(FFBatteryOptions* options, FFlist* results)\n{\n    //https://learn.microsoft.com/en-us/windows-hardware/drivers/install/using-device-interfaces\n    ULONG cchDeviceInterfaces = 0;\n    CONFIGRET cr = CM_Get_Device_Interface_List_SizeW(&cchDeviceInterfaces, (LPGUID)&GUID_DEVCLASS_BATTERY, NULL, CM_GET_DEVICE_INTERFACE_LIST_PRESENT);\n    if (cr != CR_SUCCESS)\n        return \"CM_Get_Device_Interface_List_SizeW() failed\";\n\n    if (cchDeviceInterfaces <= 1)\n        return NULL; // Not found\n\n    wchar_t* FF_AUTO_FREE mszDeviceInterfaces = (wchar_t*)malloc(cchDeviceInterfaces * sizeof(wchar_t));\n    cr = CM_Get_Device_Interface_ListW((LPGUID)&GUID_DEVCLASS_BATTERY, NULL, mszDeviceInterfaces, cchDeviceInterfaces, CM_GET_DEVICE_INTERFACE_LIST_PRESENT);\n    if (cr != CR_SUCCESS)\n        return \"CM_Get_Device_Interface_ListW() failed\";\n\n    for (const wchar_t* pDeviceInterface = mszDeviceInterfaces; *pDeviceInterface; pDeviceInterface += wcslen(pDeviceInterface) + 1)\n    {\n        HANDLE FF_AUTO_CLOSE_FD hBattery =\n            CreateFileW(pDeviceInterface, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);\n\n        if(hBattery == INVALID_HANDLE_VALUE)\n            continue;\n\n        BATTERY_QUERY_INFORMATION bqi = { .InformationLevel = BatteryInformation };\n\n        DWORD dwWait = 0;\n        DWORD dwOut;\n\n        if(!DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_TAG, &dwWait, sizeof(dwWait), &bqi.BatteryTag, sizeof(bqi.BatteryTag), &dwOut, NULL) && bqi.BatteryTag)\n            continue;\n\n        BATTERY_INFORMATION bi = {0};\n        if(!DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), &bi, sizeof(bi), &dwOut, NULL))\n            continue;\n\n        if(!(bi.Capabilities & BATTERY_SYSTEM_BATTERY))\n            continue;\n\n        FFBatteryResult* battery = (FFBatteryResult*)ffListAdd(results);\n\n        if(memcmp(bi.Chemistry, \"PbAc\", 4) == 0)\n            ffStrbufInitStatic(&battery->technology, \"Lead Acid\");\n        else if(memcmp(bi.Chemistry, \"LION\", 4) == 0 || memcmp(bi.Chemistry, \"Li-I\", 4) == 0)\n            ffStrbufInitStatic(&battery->technology, \"Lithium Ion\");\n        else if(memcmp(bi.Chemistry, \"NiCd\", 4) == 0)\n            ffStrbufInitStatic(&battery->technology, \"Nickel Cadmium\");\n        else if(memcmp(bi.Chemistry, \"NiMH\", 4) == 0)\n            ffStrbufInitStatic(&battery->technology, \"Nickel Metal Hydride\");\n        else if(memcmp(bi.Chemistry, \"NiZn\", 4) == 0)\n            ffStrbufInitStatic(&battery->technology, \"Nickel Zinc\");\n        else if(memcmp(bi.Chemistry, \"RAM\\0\", 4) == 0)\n            ffStrbufInitStatic(&battery->technology, \"Rechargeable Alkaline-Manganese\");\n        else\n            ffStrbufInitStatic(&battery->technology, \"Unknown\");\n\n        {\n            ffStrbufInit(&battery->modelName);\n            bqi.InformationLevel = BatteryDeviceName;\n            wchar_t name[64];\n            if(DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), name, sizeof(name), &dwOut, NULL))\n                ffStrbufSetWS(&battery->modelName, name);\n        }\n\n        {\n            ffStrbufInit(&battery->manufacturer);\n            bqi.InformationLevel = BatteryManufactureName;\n            wchar_t name[64];\n            if(DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), name, sizeof(name), &dwOut, NULL))\n                ffStrbufSetWS(&battery->manufacturer, name);\n        }\n\n        {\n            ffStrbufInit(&battery->manufactureDate);\n            bqi.InformationLevel = BatteryManufactureDate;\n            BATTERY_MANUFACTURE_DATE date;\n            if(DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), &date, sizeof(date), &dwOut, NULL))\n                ffStrbufSetF(&battery->manufactureDate, \"%.4d-%.2d-%.2d\", date.Year < 1000 ? date.Year + 1900 : date.Year, date.Month, date.Day);\n        }\n\n        {\n            ffStrbufInit(&battery->serial);\n            bqi.InformationLevel = BatterySerialNumber;\n            wchar_t name[64];\n            if(DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), name, sizeof(name), &dwOut, NULL))\n                ffStrbufSetWS(&battery->serial, name);\n        }\n\n        battery->cycleCount = bi.CycleCount;\n\n        battery->temperature = FF_BATTERY_TEMP_UNSET;\n        if(options->temp)\n        {\n            bqi.InformationLevel = BatteryTemperature;\n            ULONG temp;\n            if(DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), &temp, sizeof(temp), &dwOut, NULL))\n                battery->temperature = temp / 10.0 - 273.15;\n        }\n\n        {\n            bqi.InformationLevel = BatteryEstimatedTime;\n            ULONG time;\n            if(DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), &time, sizeof(time), &dwOut, NULL))\n                battery->timeRemaining = time == BATTERY_UNKNOWN_TIME ? -1 : (int32_t) time;\n        }\n\n        {\n            BATTERY_STATUS bs;\n            BATTERY_WAIT_STATUS bws = { .BatteryTag = bqi.BatteryTag };\n            if(DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_STATUS, &bws, sizeof(bws), &bs, sizeof(bs), &dwOut, NULL) && bs.Capacity != BATTERY_UNKNOWN_CAPACITY && bi.FullChargedCapacity != 0)\n                battery->capacity = bs.Capacity * 100.0 / bi.FullChargedCapacity;\n            else\n                battery->capacity = 0;\n\n            ffStrbufInit(&battery->status);\n            if(bs.PowerState & BATTERY_POWER_ON_LINE)\n                ffStrbufAppendS(&battery->status, \"AC Connected, \");\n            if(bs.PowerState & BATTERY_DISCHARGING)\n                ffStrbufAppendS(&battery->status, \"Discharging, \");\n            if(bs.PowerState & BATTERY_CHARGING)\n                ffStrbufAppendS(&battery->status, \"Charging, \");\n            if(bs.PowerState & BATTERY_CRITICAL)\n                ffStrbufAppendS(&battery->status, \"Critical, \");\n            ffStrbufTrimRight(&battery->status, ' ');\n            ffStrbufTrimRight(&battery->status, ',');\n        }\n    }\n    return NULL;\n}\n\ntypedef struct FFSmbiosPortableBattery\n{\n    FFSmbiosHeader Header;\n\n    // 2.1+\n    uint8_t Location; // string\n    uint8_t Manufacturer; // string\n    uint8_t ManufactureDate; // string\n    uint8_t SerialNumber; // string\n    uint8_t DeviceName; // string\n    uint8_t DeviceChemistry; // enum\n    uint16_t DesignCapacity; // varies\n    uint16_t DesignVoltage; // varies\n    uint8_t SbdsVersionNumber; // string\n    uint8_t MaximumErrorInBatteryData; // varies\n\n    // 2.2+\n    uint16_t SbdsSerialNumber; // varies\n    uint16_t SbdsManufactureDate; // varies\n    uint8_t SbdsDeviceChemistry; // string\n    uint8_t DesignCapacityMultiplier; // varies\n    uint16_t OEMSpecific; // varies\n} __attribute__((__packed__)) FFSmbiosPortableBattery;\n\nstatic_assert(offsetof(FFSmbiosPortableBattery, OEMSpecific) == 0x16,\n    \"FFSmbiosPortableBattery: Wrong struct alignment\");\n\nstatic const char* detectBySmbios(FFBatteryResult* battery)\n{\n    const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();\n    if (!smbiosTable)\n        return \"Failed to get SMBIOS data\";\n\n    const FFSmbiosPortableBattery* data = (const FFSmbiosPortableBattery*) (*smbiosTable)[FF_SMBIOS_TYPE_PORTABLE_BATTERY];\n    if (!data)\n        return \"Portable battery section is not found in SMBIOS data\";\n\n    const char* strings = (const char*) data + data->Header.Length;\n\n    ffStrbufSetStatic(&battery->modelName, ffSmbiosLocateString(strings, data->DeviceName));\n    ffCleanUpSmbiosValue(&battery->modelName);\n    ffStrbufSetStatic(&battery->manufacturer, ffSmbiosLocateString(strings, data->Manufacturer));\n    ffCleanUpSmbiosValue(&battery->manufacturer);\n\n    if (data->ManufactureDate)\n    {\n        ffStrbufSetStatic(&battery->manufactureDate, ffSmbiosLocateString(strings, data->ManufactureDate));\n        ffCleanUpSmbiosValue(&battery->manufactureDate);\n    }\n    else if (data->Header.Length > offsetof(FFSmbiosPortableBattery, SbdsManufactureDate))\n    {\n        int day = data->SbdsManufactureDate & 0b11111;\n        int month = (data->SbdsManufactureDate >> 5) & 0b1111;\n        int year = (data->SbdsManufactureDate >> 9) + 1800;\n        ffStrbufSetF(&battery->manufactureDate, \"%.4d-%.2d-%.2d\", year, month, day);\n    }\n\n    switch (data->DeviceChemistry)\n    {\n    case 0x01: ffStrbufSetStatic(&battery->technology, \"Other\"); break;\n    case 0x02: ffStrbufSetStatic(&battery->technology, \"Unknown\"); break;\n    case 0x03: ffStrbufSetStatic(&battery->technology, \"Lead Acid\"); break;\n    case 0x04: ffStrbufSetStatic(&battery->technology, \"Nickel Cadmium\"); break;\n    case 0x05: ffStrbufSetStatic(&battery->technology, \"Nickel metal hydride\"); break;\n    case 0x06: ffStrbufSetStatic(&battery->technology, \"Lithium-ion\"); break;\n    case 0x07: ffStrbufSetStatic(&battery->technology, \"Zinc air\"); break;\n    case 0x08: ffStrbufSetStatic(&battery->technology, \"Lithium Polymer\"); break;\n    }\n\n    if (data->SerialNumber)\n    {\n        ffStrbufSetStatic(&battery->serial, ffSmbiosLocateString(strings, data->SerialNumber));\n        ffCleanUpSmbiosValue(&battery->serial);\n    }\n    else if (data->Header.Length > offsetof(FFSmbiosPortableBattery, SbdsSerialNumber))\n    {\n        ffStrbufSetF(&battery->serial, \"%4X\", data->SbdsSerialNumber);\n    }\n\n    return NULL;\n}\n\nstatic const char* detectWithNtApi(FF_MAYBE_UNUSED FFBatteryOptions* options, FFlist* results)\n{\n    SYSTEM_BATTERY_STATE info;\n    if (NT_SUCCESS(NtPowerInformation(SystemBatteryState, NULL, 0, &info, sizeof(info))) && info.BatteryPresent)\n    {\n        FFBatteryResult* battery = (FFBatteryResult*)ffListAdd(results);\n        ffStrbufInit(&battery->modelName);\n        ffStrbufInit(&battery->manufacturer);\n        ffStrbufInit(&battery->manufactureDate);\n        ffStrbufInit(&battery->technology);\n        ffStrbufInit(&battery->status);\n        ffStrbufInit(&battery->serial);\n        battery->temperature = FF_BATTERY_TEMP_UNSET;\n        battery->cycleCount = 0;\n        battery->timeRemaining = info.EstimatedTime == BATTERY_UNKNOWN_TIME ? -1 : (int32_t) info.EstimatedTime;\n\n        battery->capacity = info.RemainingCapacity * 100.0 / info.MaxCapacity;\n        if(info.AcOnLine)\n        {\n            ffStrbufAppendS(&battery->status, \"AC Connected\");\n            if(info.Charging)\n                ffStrbufAppendS(&battery->status, \", Charging\");\n        }\n        else if(info.Discharging)\n            ffStrbufAppendS(&battery->status, \"Discharging\");\n\n\n        detectBySmbios(battery);\n\n        return NULL;\n    }\n    return \"NtPowerInformation(SystemBatteryState) failed\";\n}\n\nconst char* ffDetectBattery(FFBatteryOptions* options, FFlist* results)\n{\n    return options->useSetupApi\n        ? detectWithCmApi(options, results)\n        : detectWithNtApi(options, results);\n}\n"
  },
  {
    "path": "src/detection/bios/bios.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/bios/option.h\"\n\ntypedef struct FFBiosResult\n{\n    FFstrbuf date;\n    FFstrbuf release;\n    FFstrbuf vendor;\n    FFstrbuf version;\n    FFstrbuf type;\n} FFBiosResult;\n\nconst char* ffDetectBios(FFBiosResult* bios);\n"
  },
  {
    "path": "src/detection/bios/bios_android.c",
    "content": "#include \"bios.h\"\n#include \"common/settings.h\"\n\nconst char* ffDetectBios(FFBiosResult* bios)\n{\n    if (!ffSettingsGetAndroidProperty(\"ro.bootloader\", &bios->version))\n        ffSettingsGetAndroidProperty(\"ro.boot.bootloader\", &bios->version);\n\n    if (ffStrbufIgnCaseEqualS(&bios->version, \"unknown\"))\n        ffStrbufClear(&bios->version);\n\n    ffStrbufSetStatic(&bios->type, \"Bootloader\");\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/bios/bios_apple.c",
    "content": "#include \"bios.h\"\n#include \"common/apple/cf_helpers.h\"\n\n#include <IOKit/IOKitLib.h>\n\nconst char* ffDetectBios(FFBiosResult* bios)\n{\n    #ifndef __aarch64__\n\n    //https://github.com/osquery/osquery/blob/master/osquery/tables/system/darwin/smbios_tables.cpp\n    //For Intel\n    FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t deviceRom = IORegistryEntryFromPath(MACH_PORT_NULL, \"IODeviceTree:/rom\");\n    if (!deviceRom)\n        return \"IODeviceTree:/rom not found\";\n\n    FF_CFTYPE_AUTO_RELEASE CFMutableDictionaryRef deviceRomProps = NULL;\n    if(IORegistryEntryCreateCFProperties(deviceRom, &deviceRomProps, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess)\n        return \"IORegistryEntryCreateCFProperties(deviceRom) failed\";\n\n    ffCfDictGetString(deviceRomProps, CFSTR(\"vendor\"), &bios->vendor);\n    ffCfDictGetString(deviceRomProps, CFSTR(\"version\"), &bios->version);\n    ffCfDictGetString(deviceRomProps, CFSTR(\"release-date\"), &bios->date);\n    ffStrbufSetStatic(&bios->type, \"UEFI\");\n\n    #else\n\n    //For arm64\n    FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t device = IORegistryEntryFromPath(MACH_PORT_NULL, \"IODeviceTree:/\");\n    if (!device)\n        return \"IODeviceTree:/ not found\";\n\n    FF_CFTYPE_AUTO_RELEASE CFMutableDictionaryRef deviceProps = NULL;\n    if(IORegistryEntryCreateCFProperties(device, &deviceProps, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess)\n        return \"IORegistryEntryCreateCFProperties(device) failed\";\n\n    ffCfDictGetString(deviceProps, CFSTR(\"manufacturer\"), &bios->vendor);\n    ffCfDictGetString(deviceProps, CFSTR(\"time-stamp\"), &bios->date);\n\n    FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t deviceChosen = IORegistryEntryFromPath(MACH_PORT_NULL, \"IODeviceTree:/chosen\");\n    if (deviceChosen)\n    {\n        FF_CFTYPE_AUTO_RELEASE CFStringRef systemFirmWareVersion = IORegistryEntryCreateCFProperty(deviceChosen, CFSTR(\"system-firmware-version\"), kCFAllocatorDefault, kNilOptions);\n        if (systemFirmWareVersion)\n        {\n            ffCfStrGetString(systemFirmWareVersion, &bios->version);\n            uint32_t index = ffStrbufFirstIndexC(&bios->version, '-');\n            if (index != bios->version.length)\n            {\n                ffStrbufAppendNS(&bios->type, index, bios->version.chars);\n                ffStrbufRemoveSubstr(&bios->version, 0, index + 1);\n            }\n        }\n    }\n    if (!bios->type.length)\n        ffStrbufSetStatic(&bios->type, \"iBoot\");\n    #endif\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/bios/bios_bsd.c",
    "content": "#include \"bios.h\"\n\n#include \"common/settings.h\"\n#include \"common/sysctl.h\"\n#include \"common/io.h\"\n#include \"common/smbiosHelper.h\"\n\nconst char* ffDetectBios(FFBiosResult* result)\n{\n    ffSettingsGetFreeBSDKenv(\"smbios.bios.reldate\", &result->date);\n    ffCleanUpSmbiosValue(&result->date);\n    ffSettingsGetFreeBSDKenv(\"smbios.bios.revision\", &result->release);\n    ffCleanUpSmbiosValue(&result->release);\n    ffSettingsGetFreeBSDKenv(\"smbios.bios.vendor\", &result->vendor);\n    ffCleanUpSmbiosValue(&result->vendor);\n    ffSettingsGetFreeBSDKenv(\"smbios.bios.version\", &result->version);\n    ffCleanUpSmbiosValue(&result->version);\n    ffSysctlGetString(\"machdep.bootmethod\", &result->type);\n\n    if (result->type.length == 0)\n    {\n        if (ffSettingsGetFreeBSDKenv(\"loader.efi\", &result->type))\n            ffStrbufSetStatic(&result->type, ffStrbufEqualS(&result->type, \"1\") ? \"UEFI\" : \"BIOS\");\n        else\n        {\n            ffStrbufSetStatic(&result->type,\n                ffPathExists(\"/dev/efi\" /*efidev*/, FF_PATHTYPE_FILE) ||\n                ffPathExists(\"/boot/efi/efi/\" /*efi partition. Note /boot/efi exists on BIOS system*/, FF_PATHTYPE_DIRECTORY)\n                    ? \"UEFI\" : \"BIOS\");\n        }\n    }\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/bios/bios_linux.c",
    "content": "#include \"bios.h\"\n#include \"common/io.h\"\n#include \"common/smbiosHelper.h\"\n\nconst char* ffDetectBios(FFBiosResult* bios)\n{\n    if (ffGetSmbiosValue(\"/sys/devices/virtual/dmi/id/bios_date\", \"/sys/class/dmi/id/bios_date\", &bios->date))\n    {\n        ffGetSmbiosValue(\"/sys/devices/virtual/dmi/id/bios_release\", \"/sys/class/dmi/id/bios_release\", &bios->release);\n        ffGetSmbiosValue(\"/sys/devices/virtual/dmi/id/bios_vendor\", \"/sys/class/dmi/id/bios_vendor\", &bios->vendor);\n        ffGetSmbiosValue(\"/sys/devices/virtual/dmi/id/bios_version\", \"/sys/class/dmi/id/bios_version\", &bios->version);\n    }\n    else if (ffReadFileBuffer(\"/proc/device-tree/chosen/u-boot,version\", &bios->version))\n    {\n        ffStrbufTrimRight(&bios->version, '\\0');\n        ffStrbufSetStatic(&bios->vendor, \"U-Boot\");\n    }\n\n    if (ffPathExists(\"/sys/firmware/efi/\", FF_PATHTYPE_DIRECTORY) || ffPathExists(\"/sys/firmware/acpi/tables/UEFI\", FF_PATHTYPE_FILE))\n        ffStrbufSetStatic(&bios->type, \"UEFI\");\n    else\n        ffStrbufSetStatic(&bios->type, \"BIOS\");\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/bios/bios_nbsd.c",
    "content": "#include \"bios.h\"\n#include \"common/sysctl.h\"\n#include \"common/smbiosHelper.h\"\n\nconst char* ffDetectBios(FFBiosResult* bios)\n{\n    if (ffSysctlGetString(\"machdep.dmi.bios-date\", &bios->date) == NULL)\n        ffCleanUpSmbiosValue(&bios->date);\n    if (ffSysctlGetString(\"machdep.dmi.bios-version\", &bios->version) == NULL)\n        ffCleanUpSmbiosValue(&bios->version);\n    if (ffSysctlGetString(\"machdep.dmi.bios-vendor\", &bios->vendor) == NULL)\n        ffCleanUpSmbiosValue(&bios->vendor);\n    ffSysctlGetString(\"machdep.bootmethod\", &bios->type);\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/bios/bios_nosupport.c",
    "content": "#include \"bios.h\"\n\nconst char* ffDetectBios(FF_MAYBE_UNUSED FFBiosResult* bios)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/bios/bios_windows.c",
    "content": "#include \"bios.h\"\n#include \"common/smbiosHelper.h\"\n\n#ifdef _WIN32\n#include \"common/windows/registry.h\"\n\n#include <ntstatus.h>\n#include \"common/windows/nt.h\"\n#elif __OpenBSD__\n#include \"common/io.h\"\n\n#include <fcntl.h>\n#include <unistd.h>\n\n#elif __sun\n#include <libdevinfo.h>\n#include <sys/sunddi.h>\n#endif\n\ntypedef struct FFSmbiosBios\n{\n    FFSmbiosHeader Header;\n\n    uint8_t Vendor; // string\n    uint8_t BiosVersion; // string\n    uint16_t BiosStartingAddressSegment; // varies\n    uint8_t BiosReleaseDate; // string\n    uint8_t BiosRomSize; // string\n    uint64_t BiosCharacteristics; // bit field\n\n    // 2.4+\n    uint8_t BiosCharacteristicsExtensionBytes[2]; // bit field\n    uint8_t SystemBiosMajorRelease; // varies\n    uint8_t SystemBiosMinorRelease; // varies\n    uint8_t EmbeddedControllerFirmwareMajorRelease; // varies\n    uint8_t EmbeddedControllerFirmwareMinorRelease; // varies\n\n    // 3.1+\n    uint16_t ExtendedBiosRomSize; // bit field\n} __attribute__((__packed__)) FFSmbiosBios;\n\nstatic_assert(offsetof(FFSmbiosBios, ExtendedBiosRomSize) == 0x18,\n    \"FFSmbiosBios: Wrong struct alignment\");\n\n\nconst char* ffDetectBios(FFBiosResult* bios)\n{\n    const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();\n    if (!smbiosTable)\n        return \"Failed to get SMBIOS data\";\n\n    const FFSmbiosBios* data = (const FFSmbiosBios*) (*smbiosTable)[FF_SMBIOS_TYPE_BIOS];\n    if (!data)\n        return \"BIOS section is not found in SMBIOS data\";\n\n    const char* strings = (const char*) data + data->Header.Length;\n\n    ffStrbufSetStatic(&bios->version, ffSmbiosLocateString(strings, data->BiosVersion));\n    ffCleanUpSmbiosValue(&bios->version);\n    ffStrbufSetStatic(&bios->vendor, ffSmbiosLocateString(strings, data->Vendor));\n    ffCleanUpSmbiosValue(&bios->vendor);\n    ffStrbufSetStatic(&bios->date, ffSmbiosLocateString(strings, data->BiosReleaseDate));\n    ffCleanUpSmbiosValue(&bios->date);\n\n    if (data->Header.Length > offsetof(FFSmbiosBios, SystemBiosMajorRelease))\n        ffStrbufSetF(&bios->release, \"%u.%u\", data->SystemBiosMajorRelease, data->SystemBiosMinorRelease);\n\n    #ifdef _WIN32\n    // Same as GetFirmwareType, but support (?) Windows 7\n    // https://ntdoc.m417z.com/system_information_class\n    SYSTEM_BOOT_ENVIRONMENT_INFORMATION sbei;\n    if (NT_SUCCESS(NtQuerySystemInformation(SystemBootEnvironmentInformation, &sbei, sizeof(sbei), NULL)))\n    {\n        switch (sbei.FirmwareType)\n        {\n            case FirmwareTypeBios: ffStrbufSetStatic(&bios->type, \"BIOS\"); break;\n            case FirmwareTypeUefi: ffStrbufSetStatic(&bios->type, \"UEFI\"); break;\n            default: break;\n        }\n    }\n    #elif __sun\n    di_node_t rootNode = di_init(\"/\", DINFOPROP);\n    if (rootNode != DI_NODE_NIL)\n    {\n        char* efiVersion = NULL;\n        if (di_prop_lookup_strings(DDI_DEV_T_ANY, rootNode, \"efi-version\", &efiVersion) > 0)\n            ffStrbufSetStatic(&bios->type, \"UEFI\");\n        else\n            ffStrbufSetStatic(&bios->type, \"BIOS\");\n    }\n    di_fini(rootNode);\n    #elif __HAIKU__ || __OpenBSD__\n    // Currently SMBIOS detection is supported in legancy BIOS only\n    ffStrbufSetStatic(&bios->type, \"BIOS\");\n    #endif\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/bluetooth/bluetooth.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/bluetooth/option.h\"\n\ntypedef struct FFBluetoothResult\n{\n    FFstrbuf name;\n    FFstrbuf address;\n    FFstrbuf type;\n    uint8_t battery; // 0-100%\n    bool connected;\n} FFBluetoothResult;\n\nconst char* ffDetectBluetooth(FFBluetoothOptions* options, FFlist* devices /* FFBluetoothResult */);\n"
  },
  {
    "path": "src/detection/bluetooth/bluetooth_apple.m",
    "content": "#include \"bluetooth.h\"\n\n#import <IOBluetooth/IOBluetooth.h>\n\n@interface IOBluetoothDevice()\n    @property (nonatomic) uint8_t batteryPercentCase;\n    @property (nonatomic) uint8_t batteryPercentCombined;\n    @property (nonatomic) uint8_t batteryPercentLeft;\n    @property (nonatomic) uint8_t batteryPercentRight;\n    @property (nonatomic) uint8_t batteryPercentSingle;\n@end\n\nconst char* ffDetectBluetooth(FFBluetoothOptions* options, FFlist* devices /* FFBluetoothResult */)\n{\n    NSArray<IOBluetoothDevice*>* ioDevices = IOBluetoothDevice.pairedDevices;\n    if(!ioDevices)\n        return \"IOBluetoothDevice.pairedDevices failed\";\n\n    for(IOBluetoothDevice* ioDevice in ioDevices)\n    {\n        if (!options->showDisconnected && !ioDevice.isConnected)\n            continue;\n\n        FFBluetoothResult* device = ffListAdd(devices);\n        ffStrbufInitS(&device->name, ioDevice.name.UTF8String);\n        ffStrbufInitS(&device->address, ioDevice.addressString.UTF8String);\n        ffStrbufReplaceAllC(&device->address, '-', ':');\n        ffStrbufUpperCase(&device->address);\n        ffStrbufInit(&device->type);\n\n        if (ioDevice.batteryPercentSingle)\n            device->battery = ioDevice.batteryPercentSingle;\n        else if (ioDevice.batteryPercentCombined)\n            device->battery = ioDevice.batteryPercentCombined;\n        else if (ioDevice.batteryPercentCase)\n            device->battery = ioDevice.batteryPercentCase;\n\n        device->connected = !!ioDevice.isConnected;\n        if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorLimitedDiscoverableMode)\n            ffStrbufAppendS(&device->type, \"Limited Discoverable Mode, \");\n        if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorReserved1)\n            ffStrbufAppendS(&device->type, \"LE audio, \");\n        if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorReserved2)\n            ffStrbufAppendS(&device->type, \"Reserved for future use, \");\n        if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorPositioning)\n            ffStrbufAppendS(&device->type, \"Positioning, \");\n        if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorNetworking)\n            ffStrbufAppendS(&device->type, \"Networking, \");\n        if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorRendering)\n            ffStrbufAppendS(&device->type, \"Rendering, \");\n        if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorCapturing)\n            ffStrbufAppendS(&device->type, \"Capturing, \");\n        if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorObjectTransfer)\n            ffStrbufAppendS(&device->type, \"Object Transfer, \");\n        if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorAudio)\n            ffStrbufAppendS(&device->type, \"Audio, \");\n        if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorTelephony)\n            ffStrbufAppendS(&device->type, \"Telephony, \");\n        if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorInformation)\n            ffStrbufAppendS(&device->type, \"Information, \");\n\n        if(device->type.length == 0)\n        {\n            switch(ioDevice.deviceClassMajor)\n            {\n                case kBluetoothDeviceClassMajorMiscellaneous:\n                    ffStrbufAppendS(&device->type, \"Miscellaneous\");\n                    break;\n                case kBluetoothDeviceClassMajorComputer:\n                    ffStrbufAppendS(&device->type, \"Computer\");\n                    break;\n                case kBluetoothDeviceClassMajorPhone:\n                    ffStrbufAppendS(&device->type, \"Phone\");\n                    break;\n                case kBluetoothDeviceClassMajorLANAccessPoint:\n                    ffStrbufAppendS(&device->type, \"LAN/Network Access point\");\n                    break;\n                case kBluetoothDeviceClassMajorAudio:\n                    ffStrbufAppendS(&device->type, \"Audio/Video\");\n                    break;\n                case kBluetoothDeviceClassMajorPeripheral:\n                    ffStrbufAppendS(&device->type, \"Peripheral\");\n                    break;\n                case kBluetoothDeviceClassMajorImaging:\n                    ffStrbufAppendS(&device->type, \"Imaging\");\n                    break;\n                case kBluetoothDeviceClassMajorWearable:\n                    ffStrbufAppendS(&device->type, \"Wearable\");\n                    break;\n                case kBluetoothDeviceClassMajorToy:\n                    ffStrbufAppendS(&device->type, \"Toy\");\n                    break;\n                case kBluetoothDeviceClassMajorHealth:\n                    ffStrbufAppendS(&device->type, \"Health\");\n                    break;\n                case kBluetoothDeviceClassMajorUnclassified:\n                    ffStrbufAppendS(&device->type, \"Uncategorized\");\n                    break;\n                default:\n                    ffStrbufAppendS(&device->type, \"Unknown\");\n                    break;\n            }\n        }\n        else\n        {\n            ffStrbufTrimRight(&device->type, ' ');\n            ffStrbufTrimRight(&device->type, ',');\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/bluetooth/bluetooth_bsd.c",
    "content": "#include \"bluetooth.h\"\n\n#define L2CAP_SOCKET_CHECKED\n#include <bluetooth.h>\n\nstatic int enumDev(FF_MAYBE_UNUSED int sockfd, struct bt_devinfo const* dev, FFlist* devices)\n{\n    FFBluetoothResult* device = ffListAdd(devices);\n    ffStrbufInitS(&device->name,\n        #if __FreeBSD__\n        bt_devremote_name_gen(dev->devname, &dev->bdaddr)\n        #else\n        dev->devname\n        #endif\n    );\n    ffStrbufInitS(&device->address, bt_ntoa(&dev->bdaddr, NULL));\n    ffStrbufUpperCase(&device->address);\n    ffStrbufInit(&device->type);\n    device->battery = 0;\n    device->connected = true;\n    return 0;\n}\n\nconst char* ffDetectBluetooth(FF_MAYBE_UNUSED FFBluetoothOptions* options, FF_MAYBE_UNUSED FFlist* devices /* FFBluetoothResult */)\n{\n    // struct hostent* ent = bt_gethostent();\n    if (bt_devenum((void*) enumDev, devices) < 0)\n        return \"bt_devenum() failed\";\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/bluetooth/bluetooth_haiku.cpp",
    "content": "extern \"C\" {\n#include \"bluetooth.h\"\n#include \"common/io.h\"\n}\n\n#include <bluetooth/LocalDevice.h>\n\nconst char* ffDetectBluetooth(FF_MAYBE_UNUSED FFBluetoothOptions* options, FFlist* devices /* FFBluetoothResult */)\n{\n    using namespace Bluetooth;\n    FF_SUPPRESS_IO();\n\n    LocalDevice* dev = LocalDevice::GetLocalDevice();\n    if (!dev) return NULL;\n\n    BString devClass;\n    dev->GetDeviceClass().DumpDeviceClass(devClass);\n\n    FFBluetoothResult* device = (FFBluetoothResult*) ffListAdd(devices);\n    ffStrbufInitS(&device->name, dev->GetFriendlyName());\n    ffStrbufInitS(&device->address, bdaddrUtils::ToString(dev->GetBluetoothAddress()).String());\n    ffStrbufInitS(&device->type, devClass.String());\n    device->battery = 0;\n    device->connected = true;\n\n    // TODO: more devices?\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/bluetooth/bluetooth_linux.c",
    "content": "#include \"bluetooth.h\"\n#include \"common/stringUtils.h\"\n\n#ifdef FF_HAVE_DBUS\n#include \"common/dbus.h\"\n#include \"common/io.h\"\n\n/* Example dbus reply, striped to only the relevant parts:\narray [                                                     //root\n    dict entry(                                             //object\n        object path \"/org/bluez/hci0/dev_03_21_8B_91_16_4D\"\n        array [\n           dict entry(                                      //property\n              string \"org.bluez.Device1\"\n              array [\n                 dict entry(                                //value\n                    string \"Address\"\n                    variant string \"03:21:8B:91:16:4D\"\n                 )\n                 dict entry(                                //value\n                    string \"Name\"\n                    variant string \"JBL TUNE160BT\"\n                 )\n                 dict entry(                                //value\n                    string \"Icon\"\n                    variant string \"audio-headset\"\n                 )\n                 dict entry(                                //value\n                    string \"Connected\"\n                    variant boolean true\n                 )\n              ]\n           )\n           dict entry(                                      //property\n              string \"org.bluez.Battery1\"\n              array [\n                 dict entry(                                //value\n                    string \"Percentage\"\n                    variant byte 100\n                 )\n              ]\n           )\n        ]\n    )\n]\n*/\n\nstatic bool detectBluetoothValue(FFDBusData* dbus, DBusMessageIter* iter, FFBluetoothResult* device)\n{\n    if(dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DICT_ENTRY)\n        return true;\n\n    DBusMessageIter dictIter;\n    dbus->lib->ffdbus_message_iter_recurse(iter, &dictIter);\n\n    if(dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_STRING)\n        return true;\n\n    const char* deviceProperty;\n    dbus->lib->ffdbus_message_iter_get_basic(&dictIter, &deviceProperty);\n\n    dbus->lib->ffdbus_message_iter_next(&dictIter);\n\n    if(ffStrEquals(deviceProperty, \"Address\"))\n        ffDBusGetString(dbus, &dictIter, &device->address);\n    else if(ffStrEquals(deviceProperty, \"Name\"))\n        ffDBusGetString(dbus, &dictIter, &device->name);\n    else if(ffStrEquals(deviceProperty, \"Icon\"))\n        ffDBusGetString(dbus, &dictIter, &device->type);\n    else if(ffStrEquals(deviceProperty, \"Percentage\"))\n    {\n        uint32_t percentage;\n        if (ffDBusGetUint(dbus, &dictIter, &percentage))\n            device->battery = (uint8_t) percentage;\n    }\n    else if(ffStrEquals(deviceProperty, \"Connected\"))\n        ffDBusGetBool(dbus, &dictIter, &device->connected);\n    else if(ffStrEquals(deviceProperty, \"Paired\"))\n    {\n        bool paired = true;\n        ffDBusGetBool(dbus, &dictIter, &paired);\n        if (!paired) return false;\n    }\n    return true;\n}\n\nstatic void detectBluetoothProperty(FFDBusData* dbus, DBusMessageIter* iter, FFBluetoothResult* device)\n{\n    if(dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DICT_ENTRY)\n        return;\n\n    DBusMessageIter dictIter;\n    dbus->lib->ffdbus_message_iter_recurse(iter, &dictIter);\n\n    if(dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_STRING)\n        return;\n\n    const char* propertyType;\n    dbus->lib->ffdbus_message_iter_get_basic(&dictIter, &propertyType);\n\n    if(!ffStrContains(propertyType, \".Device\") && !ffStrContains(propertyType, \".Battery\"))\n        return; // We don't care about other properties\n\n    dbus->lib->ffdbus_message_iter_next(&dictIter);\n\n    if(dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_ARRAY)\n        return;\n\n    DBusMessageIter arrayIter;\n    dbus->lib->ffdbus_message_iter_recurse(&dictIter, &arrayIter);\n\n    do\n    {\n        bool shouldContinue = detectBluetoothValue(dbus, &arrayIter, device);\n        if (!shouldContinue)\n        {\n            ffStrbufClear(&device->name);\n            break;\n        }\n    } while (dbus->lib->ffdbus_message_iter_next(&arrayIter));\n}\n\nstatic FFBluetoothResult* detectBluetoothObject(FFlist* devices, FFDBusData* dbus, DBusMessageIter* iter)\n{\n    if(dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DICT_ENTRY)\n        return NULL;\n\n    DBusMessageIter dictIter;\n    dbus->lib->ffdbus_message_iter_recurse(iter, &dictIter);\n\n    if(dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_OBJECT_PATH)\n        return NULL;\n\n    const char* objectPath;\n    dbus->lib->ffdbus_message_iter_get_basic(&dictIter, &objectPath);\n\n    // We don't want adapter objects\n    if(!ffStrContains(objectPath, \"/dev_\"))\n        return NULL;\n\n    dbus->lib->ffdbus_message_iter_next(&dictIter);\n\n    if(dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_ARRAY)\n        return NULL;\n\n    DBusMessageIter arrayIter;\n    dbus->lib->ffdbus_message_iter_recurse(&dictIter, &arrayIter);\n\n    FFBluetoothResult* device = ffListAdd(devices);\n    ffStrbufInit(&device->name);\n    ffStrbufInit(&device->address);\n    ffStrbufInit(&device->type);\n    device->battery = 0;\n    device->connected = false;\n\n    do\n    {\n        detectBluetoothProperty(dbus, &arrayIter, device);\n    } while (dbus->lib->ffdbus_message_iter_next(&arrayIter));\n\n    return device;\n}\n\nstatic void detectBluetoothRoot(FFlist* devices, FFDBusData* dbus, DBusMessageIter* iter, int32_t connectedCount)\n{\n    if(dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)\n        return;\n\n    DBusMessageIter arrayIter;\n    dbus->lib->ffdbus_message_iter_recurse(iter, &arrayIter);\n\n    do\n    {\n        FFBluetoothResult* device = detectBluetoothObject(devices, dbus, &arrayIter);\n\n        if (device)\n        {\n            if(device->name.length == 0 || (connectedCount > 0 && !device->connected))\n            {\n                ffStrbufDestroy(&device->name);\n                ffStrbufDestroy(&device->address);\n                ffStrbufDestroy(&device->type);\n                --devices->length;\n            }\n\n            if (device->connected && --connectedCount == 0)\n                break;\n        }\n    } while (dbus->lib->ffdbus_message_iter_next(&arrayIter));\n}\n\nstatic const char* detectBluetooth(FFlist* devices, int32_t connectedCount)\n{\n    FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};\n    const char* error = ffDBusLoadData(DBUS_BUS_SYSTEM, &dbus);\n    if(error)\n        return error;\n\n    DBusMessage* managedObjects = ffDBusGetMethodReply(&dbus, \"org.bluez\", \"/\", \"org.freedesktop.DBus.ObjectManager\", \"GetManagedObjects\", NULL, NULL);\n    if(!managedObjects)\n        return \"Failed to call GetManagedObjects\";\n\n    DBusMessageIter rootIter;\n    if(!dbus.lib->ffdbus_message_iter_init(managedObjects, &rootIter))\n    {\n        dbus.lib->ffdbus_message_unref(managedObjects);\n        return \"Failed to get root iterator of GetManagedObjects\";\n    }\n\n    detectBluetoothRoot(devices, &dbus, &rootIter, connectedCount);\n\n    dbus.lib->ffdbus_message_unref(managedObjects);\n    return NULL;\n}\n\nstatic uint32_t connectedDevices(void)\n{\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(\"/sys/class/bluetooth\");\n    if(dirp == NULL)\n        return 0;\n\n    uint32_t result = 0;\n    struct dirent* entry;\n    while ((entry = readdir(dirp)) != NULL)\n    {\n        if (strchr(entry->d_name, ':') != NULL)\n            ++result;\n    }\n\n    return result;\n}\n\n#endif\n\nconst char* ffDetectBluetooth(FF_MAYBE_UNUSED FFBluetoothOptions* options, FF_MAYBE_UNUSED FFlist* devices /* FFBluetoothResult */)\n{\n    #ifdef FF_HAVE_DBUS\n        int32_t connectedCount = -1;\n        if (!options->showDisconnected)\n        {\n            connectedCount = (int32_t) connectedDevices();\n            if (connectedCount == 0)\n                return NULL;\n        }\n\n        return detectBluetooth(devices, connectedCount);\n    #else\n        return \"Fastfetch was compiled without DBus support\";\n    #endif\n}\n"
  },
  {
    "path": "src/detection/bluetooth/bluetooth_nosupport.c",
    "content": "#include \"bluetooth.h\"\n\nconst char* ffDetectBluetooth(FF_MAYBE_UNUSED FFBluetoothOptions* options, FF_MAYBE_UNUSED FFlist* devices /* FFBluetoothResult */)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/bluetooth/bluetooth_windows.c",
    "content": "#include \"bluetooth.h\"\n#include \"common/library.h\"\n#include \"common/windows/unicode.h\"\n\n#include <windows.h>\n#include <bluetoothapis.h>\n\n#pragma GCC diagnostic ignored \"-Wpointer-sign\"\n\nconst char* ffDetectBluetooth(FFBluetoothOptions* options, FFlist* devices /* FFBluetoothResult */)\n{\n    FF_LIBRARY_LOAD_MESSAGE(bluetoothapis, \"bluetoothapis.dll\", 1)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindFirstDevice)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindNextDevice)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindDeviceClose)\n\n    BLUETOOTH_DEVICE_INFO btdi = {\n        .dwSize = sizeof(btdi)\n    };\n    HBLUETOOTH_DEVICE_FIND hFind = ffBluetoothFindFirstDevice(&(BLUETOOTH_DEVICE_SEARCH_PARAMS) {\n        .fReturnConnected = TRUE,\n        .fReturnRemembered = options->showDisconnected,\n        .fReturnAuthenticated = options->showDisconnected,\n        .dwSize = sizeof(BLUETOOTH_DEVICE_SEARCH_PARAMS)\n    }, &btdi);\n    if(!hFind)\n    {\n        if (GetLastError() == ERROR_NO_MORE_ITEMS)\n            return NULL;\n        return \"BluetoothFindFirstDevice() failed\";\n    }\n\n    do {\n        FFBluetoothResult* device = ffListAdd(devices);\n        ffStrbufInitWS(&device->name, btdi.szName);\n        ffStrbufInitF(&device->address, \"%02X:%02X:%02X:%02X:%02X:%02X\",\n            btdi.Address.rgBytes[5],\n            btdi.Address.rgBytes[4],\n            btdi.Address.rgBytes[3],\n            btdi.Address.rgBytes[2],\n            btdi.Address.rgBytes[1],\n            btdi.Address.rgBytes[0]);\n        ffStrbufInit(&device->type);\n        device->battery = 0;\n        device->connected = !!btdi.fConnected;\n\n        //https://btprodspecificationrefs.blob.core.windows.net/assigned-numbers/Assigned%20Number%20Types/Assigned%20Numbers.pdf\n\n        if(BitTest(&btdi.ulClassofDevice, 13))\n            ffStrbufAppendS(&device->type, \"Limited Discoverable Mode, \");\n        if(BitTest(&btdi.ulClassofDevice, 14))\n            ffStrbufAppendS(&device->type, \"LE audio, \");\n        if(BitTest(&btdi.ulClassofDevice, 15))\n            ffStrbufAppendS(&device->type, \"Reserved for future use, \");\n        if(BitTest(&btdi.ulClassofDevice, 16))\n            ffStrbufAppendS(&device->type, \"Positioning, \");\n        if(BitTest(&btdi.ulClassofDevice, 17))\n            ffStrbufAppendS(&device->type, \"Networking, \");\n        if(BitTest(&btdi.ulClassofDevice, 18))\n            ffStrbufAppendS(&device->type, \"Rendering, \");\n        if(BitTest(&btdi.ulClassofDevice, 19))\n            ffStrbufAppendS(&device->type, \"Capturing, \");\n        if(BitTest(&btdi.ulClassofDevice, 20))\n            ffStrbufAppendS(&device->type, \"Object Transfer, \");\n        if(BitTest(&btdi.ulClassofDevice, 21))\n            ffStrbufAppendS(&device->type, \"Audio, \");\n        if(BitTest(&btdi.ulClassofDevice, 22))\n            ffStrbufAppendS(&device->type, \"Telephony, \");\n        if(BitTest(&btdi.ulClassofDevice, 23))\n            ffStrbufAppendS(&device->type, \"Information, \");\n\n        if(device->type.length == 0)\n        {\n            uint32_t majorDeviceClasses = (btdi.ulClassofDevice >> 8) & ~(UINT32_MAX << 5);\n            switch(majorDeviceClasses)\n            {\n                case 0b00000:\n                    ffStrbufAppendS(&device->type, \"Miscellaneous\");\n                    break;\n                case 0b00001:\n                    ffStrbufAppendS(&device->type, \"Computer\");\n                    break;\n                case 0b00010:\n                    ffStrbufAppendS(&device->type, \"Phone\");\n                    break;\n                case 0b00011:\n                    ffStrbufAppendS(&device->type, \"LAN/Network Access point\");\n                    break;\n                case 0b00100:\n                    ffStrbufAppendS(&device->type, \"Audio/Video\");\n                    break;\n                case 0b00101:\n                    ffStrbufAppendS(&device->type, \"Peripheral\");\n                    break;\n                case 0b00110:\n                    ffStrbufAppendS(&device->type, \"Imaging\");\n                    break;\n                case 0b00111:\n                    ffStrbufAppendS(&device->type, \"Wearable\");\n                    break;\n                case 0b01000:\n                    ffStrbufAppendS(&device->type, \"Toy\");\n                    break;\n                case 0b01001:\n                    ffStrbufAppendS(&device->type, \"Health\");\n                    break;\n                case 0b11111:\n                    ffStrbufAppendS(&device->type, \"Uncategorized\");\n                    break;\n                default:\n                    ffStrbufAppendS(&device->type, \"Unknown\");\n                    break;\n            }\n        }\n        else\n        {\n            ffStrbufTrimRight(&device->type, ' ');\n            ffStrbufTrimRight(&device->type, ',');\n        }\n    } while (ffBluetoothFindNextDevice(hFind, &btdi));\n\n    ffBluetoothFindDeviceClose(hFind);\n\n    const char* ffBluetoothDetectBattery(FFlist* result);\n    ffBluetoothDetectBattery(devices);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/bluetooth/bluetooth_windows.cpp",
    "content": "extern \"C\"\n{\n#include \"bluetooth.h\"\n}\n#include \"common/windows/wmi.hpp\"\n#include \"common/windows/unicode.hpp\"\n#include \"common/windows/util.hpp\"\n\nextern \"C\"\nconst char* ffBluetoothDetectBattery(FFlist* devices)\n{\n    FFWmiQuery query(L\"SELECT __PATH FROM Win32_PnPEntity WHERE Service = 'BthHFEnum'\", nullptr, FFWmiNamespace::CIMV2);\n    if(!query)\n        return \"Query WMI service failed\";\n\n    IWbemClassObject* pInParams = nullptr;\n    on_scope_exit releaseInParams([&] { pInParams && pInParams->Release(); });\n    {\n        IWbemClassObject* pnpEntityClass = nullptr;\n\n        if (FAILED(query.pService->GetObjectW(bstr_t(L\"Win32_PnPEntity\"), 0, nullptr, &pnpEntityClass, nullptr)))\n            return \"Failed to get PnP entity class\";\n        on_scope_exit releasePnpEntityClass([&] { pnpEntityClass && pnpEntityClass->Release(); });\n\n        if (FAILED(pnpEntityClass->GetMethod(bstr_t(L\"GetDeviceProperties\"), 0, &pInParams, NULL)))\n            return \"Failed to get GetDeviceProperties method\";\n\n        FFWmiVariant devicePropertyKeys({ L\"{104EA319-6EE2-4701-BD47-8DDBF425BBE5} 2\", L\"DEVPKEY_Bluetooth_DeviceAddress\" });\n        if (FAILED(pInParams->Put(L\"devicePropertyKeys\", 0, &devicePropertyKeys, CIM_FLAG_ARRAY | CIM_STRING)))\n            return \"Failed to put devicePropertyKeys\";\n    }\n\n    while (FFWmiRecord record = query.next())\n    {\n        IWbemCallResult* pCallResult = nullptr;\n\n        if (FAILED(query.pService->ExecMethod(record.get(L\"__PATH\").bstrVal, bstr_t(L\"GetDeviceProperties\"), 0, nullptr, pInParams, nullptr, &pCallResult)))\n            continue;\n        on_scope_exit releaseCallResult([&] { pCallResult && pCallResult->Release(); });\n\n        IWbemClassObject* pResultObject = nullptr;\n        if (FAILED(pCallResult->GetResultObject((LONG) WBEM_INFINITE, &pResultObject)))\n            continue;\n        on_scope_exit releaseResultObject([&] { pResultObject && pResultObject->Release(); });\n\n        VARIANT propArray;\n        if (FAILED(pResultObject->Get(L\"deviceProperties\", 0, &propArray, nullptr, nullptr)))\n            continue;\n        on_scope_exit releasePropArray([&] { VariantClear(&propArray); });\n\n        if (propArray.vt != (VT_ARRAY | VT_UNKNOWN) ||\n            (propArray.parray->fFeatures & FADF_UNKNOWN) == 0 ||\n            propArray.parray->cDims != 1 ||\n            propArray.parray->rgsabound[0].cElements != 2\n        )\n            continue;\n\n        uint8_t batt = 0;\n        for (LONG i = 0; i < 2; i++)\n        {\n            IWbemClassObject* object = nullptr;\n            if (FAILED(SafeArrayGetElement(propArray.parray, &i, &object)))\n                continue;\n\n            FFWmiRecord rec(object);\n            auto data = rec.get(L\"Data\");\n            if (data.vt == VT_EMPTY)\n                break;\n\n            if (i == 0)\n                batt = data.get<uint8_t>();\n            else\n            {\n                FF_STRBUF_AUTO_DESTROY addr = ffStrbufCreateWSV(data.get<std::wstring_view>()); // MAC address without colon\n                if (__builtin_expect(addr.length != 12, 0))\n                    continue;\n\n                FF_LIST_FOR_EACH(FFBluetoothResult, bt, *devices)\n                {\n                    if (bt->address.length != 12 + 5)\n                        continue;\n\n                    if (addr.chars[0] == bt->address.chars[0] &&\n                        addr.chars[1] == bt->address.chars[1] &&\n                        addr.chars[2] == bt->address.chars[3] &&\n                        addr.chars[3] == bt->address.chars[4] &&\n                        addr.chars[4] == bt->address.chars[6] &&\n                        addr.chars[5] == bt->address.chars[7] &&\n                        addr.chars[6] == bt->address.chars[9] &&\n                        addr.chars[7] == bt->address.chars[10] &&\n                        addr.chars[8] == bt->address.chars[12] &&\n                        addr.chars[9] == bt->address.chars[13] &&\n                        addr.chars[10] == bt->address.chars[15] &&\n                        addr.chars[11] == bt->address.chars[16])\n                    {\n                        bt->battery = batt;\n                        break;\n                    }\n                }\n            }\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/bluetoothradio/bluetoothradio.c",
    "content": "#include \"bluetoothradio.h\"\n\n// https://github.com/ziglang/zig/blob/a84951465b409495095a9598db0cae745f34fa7b/lib/libc/include/any-windows-any/bthdef.h#L187-L236\n\n#define BTH_MFG_ERICSSON 0\n#define BTH_MFG_NOKIA 1\n#define BTH_MFG_INTEL 2\n#define BTH_MFG_IBM 3\n#define BTH_MFG_TOSHIBA 4\n#define BTH_MFG_3COM 5\n#define BTH_MFG_MICROSOFT 6\n#define BTH_MFG_LUCENT 7\n#define BTH_MFG_MOTOROLA 8\n#define BTH_MFG_INFINEON 9\n#define BTH_MFG_CSR 10\n#define BTH_MFG_SILICONWAVE 11\n#define BTH_MFG_DIGIANSWER 12\n#define BTH_MFG_TI 13\n#define BTH_MFG_PARTHUS 14\n#define BTH_MFG_BROADCOM 15\n#define BTH_MFG_MITEL 16\n#define BTH_MFG_WIDCOMM 17\n#define BTH_MFG_ZEEVO 18\n#define BTH_MFG_ATMEL 19\n#define BTH_MFG_MITSIBUSHI 20\n#define BTH_MFG_RTX_TELECOM 21\n#define BTH_MFG_KC_TECHNOLOGY 22\n#define BTH_MFG_NEWLOGIC 23\n#define BTH_MFG_TRANSILICA 24\n#define BTH_MFG_ROHDE_SCHWARZ 25\n#define BTH_MFG_TTPCOM 26\n#define BTH_MFG_SIGNIA 27\n#define BTH_MFG_CONEXANT 28\n#define BTH_MFG_QUALCOMM 29\n#define BTH_MFG_INVENTEL 30\n#define BTH_MFG_AVM_BERLIN 31\n#define BTH_MFG_BANDSPEED 32\n#define BTH_MFG_MANSELLA 33\n#define BTH_MFG_NEC 34\n#define BTH_MFG_WAVEPLUS_TECHNOLOGY_CO 35\n#define BTH_MFG_ALCATEL 36\n#define BTH_MFG_PHILIPS_SEMICONDUCTOR 37\n#define BTH_MFG_C_TECHNOLOGIES 38\n#define BTH_MFG_OPEN_INTERFACE 39\n#define BTH_MFG_RF_MICRO_DEVICES 40\n#define BTH_MFG_HITACHI 41\n#define BTH_MFG_SYMBOL_TECHNOLOGIES 42\n#define BTH_MFG_TENOVIS 43\n#define BTH_MFG_MACRONIX_INTERNATIONAL 44\n#define BTH_MFG_MARVELL 72\n#define BTH_MFG_APPLE 76\n#define BTH_MFG_NORDIC_SEMICONDUCTORS_ASA 89\n#define BTH_MFG_ARUBA_NETWORKS 283\n#define BTH_MFG_INTERNAL_USE 65535\n\nconst char* ffBluetoothRadioGetVendor(uint32_t manufacturerId)\n{\n    switch (manufacturerId)\n    {\n        case BTH_MFG_ERICSSON: return \"Ericsson\";\n        case BTH_MFG_NOKIA: return \"Nokia\";\n        case BTH_MFG_INTEL: return \"Intel\";\n        case BTH_MFG_IBM: return \"IBM\";\n        case BTH_MFG_TOSHIBA: return \"Toshiba\";\n        case BTH_MFG_3COM: return \"3Com\";\n        case BTH_MFG_MICROSOFT: return \"Microsoft\";\n        case BTH_MFG_LUCENT: return \"Lucent\";\n        case BTH_MFG_MOTOROLA: return \"Motorola\";\n        case BTH_MFG_INFINEON: return \"Infineon\";\n        case BTH_MFG_CSR: return \"CSR\";\n        case BTH_MFG_SILICONWAVE: return \"Silicon-Wave\";\n        case BTH_MFG_DIGIANSWER: return \"Digi-Answer\";\n        case BTH_MFG_TI: return \"Ti\";\n        case BTH_MFG_PARTHUS: return \"Parthus\";\n        case BTH_MFG_BROADCOM: return \"Broadcom\";\n        case BTH_MFG_MITEL: return \"Mitel\";\n        case BTH_MFG_WIDCOMM: return \"Widcomm\";\n        case BTH_MFG_ZEEVO: return \"Zeevo\";\n        case BTH_MFG_ATMEL: return \"Atmel\";\n        case BTH_MFG_MITSIBUSHI: return \"Mitsubishi\";\n        case BTH_MFG_RTX_TELECOM: return \"RTX Telecom\";\n        case BTH_MFG_KC_TECHNOLOGY: return \"KC Technology\";\n        case BTH_MFG_NEWLOGIC: return \"Newlogic\";\n        case BTH_MFG_TRANSILICA: return \"Transilica\";\n        case BTH_MFG_ROHDE_SCHWARZ: return \"Rohde-Schwarz\";\n        case BTH_MFG_TTPCOM: return \"TTPCom\";\n        case BTH_MFG_SIGNIA: return \"Signia\";\n        case BTH_MFG_CONEXANT: return \"Conexant\";\n        case BTH_MFG_QUALCOMM: return \"Qualcomm\";\n        case BTH_MFG_INVENTEL: return \"Inventel\";\n        case BTH_MFG_AVM_BERLIN: return \"AVM Berlin\";\n        case BTH_MFG_BANDSPEED: return \"Bandspeed\";\n        case BTH_MFG_MANSELLA: return \"Mansella\";\n        case BTH_MFG_NEC: return \"NEC\";\n        case BTH_MFG_WAVEPLUS_TECHNOLOGY_CO: return \"Waveplus\";\n        case BTH_MFG_ALCATEL: return \"Alcatel\";\n        case BTH_MFG_PHILIPS_SEMICONDUCTOR: return \"Philips Semiconductors\";\n        case BTH_MFG_C_TECHNOLOGIES: return \"C Technologies\";\n        case BTH_MFG_OPEN_INTERFACE: return \"Open Interface\";\n        case BTH_MFG_RF_MICRO_DEVICES: return \"RF Micro Devices\";\n        case BTH_MFG_HITACHI: return \"Hitachi\";\n        case BTH_MFG_SYMBOL_TECHNOLOGIES: return \"Symbol Technologies\";\n        case BTH_MFG_TENOVIS: return \"Tenovis\";\n        case BTH_MFG_MACRONIX_INTERNATIONAL: return \"Macronix International\";\n        case BTH_MFG_MARVELL: return \"Marvell\";\n        case BTH_MFG_APPLE: return \"Apple\";\n        case BTH_MFG_NORDIC_SEMICONDUCTORS_ASA: return \"Nordic Semiconductor ASA\";\n        case BTH_MFG_ARUBA_NETWORKS: return \"Aruba Networks\";\n        case BTH_MFG_INTERNAL_USE: return \"Internal Use\";\n        default: return \"Unknown\";\n    }\n}\n"
  },
  {
    "path": "src/detection/bluetoothradio/bluetoothradio.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/bluetoothradio/option.h\"\n\ntypedef struct FFBluetoothRadioResult\n{\n    FFstrbuf name;\n    FFstrbuf address;\n    FFstrbuf vendor;\n    int32_t lmpVersion;\n    int32_t lmpSubversion;\n    bool enabled;\n    bool discoverable;\n    bool connectable;\n} FFBluetoothRadioResult;\n\nconst char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */);\nconst char* ffBluetoothRadioGetVendor(uint32_t manufacturerId);\n"
  },
  {
    "path": "src/detection/bluetoothradio/bluetoothradio_apple.m",
    "content": "#include \"bluetoothradio.h\"\n#include \"common/processing.h\"\n\n#import <IOBluetooth/IOBluetooth.h>\n\n// For some reason the official declaration of IOBluetoothHostController doesn't include property `controllers`\n@interface IOBluetoothHostController()\n+ (id)controllers;\n@end\n\nconst char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */)\n{\n    NSArray<IOBluetoothHostController*>* ctrls = IOBluetoothHostController.controllers;\n    if(!ctrls)\n        return \"IOBluetoothHostController.controllers returns nil\";\n\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n    if (ffProcessAppendStdOut(&buffer, (char* const[]) {\n        \"system_profiler\",\n        \"SPBluetoothDataType\",\n        \"-xml\",\n        \"-detailLevel\",\n        \"basic\",\n        NULL\n    }) != NULL)\n        return \"Starting `system_profiler SPBluetoothDataType -xml -detailLevel basic` failed\";\n\n    NSArray* arr = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytes:buffer.chars length:buffer.length]\n                    options:NSPropertyListImmutable\n                    format:nil\n                    error:nil];\n    if (!arr || !arr.count)\n        return \"system_profiler SPBluetoothDataType returned an empty array\";\n\n    for (IOBluetoothHostController* ctrl in ctrls)\n    {\n        FFBluetoothRadioResult* device = ffListAdd(devices);\n        ffStrbufInitS(&device->name, ctrl.nameAsString.UTF8String);\n        ffStrbufInitS(&device->address, ctrl.addressAsString.UTF8String);\n        ffStrbufInitStatic(&device->vendor, \"Apple\");\n        device->lmpVersion = INT_MIN;\n        device->lmpSubversion = INT_MIN;\n        device->enabled = ctrl.powerState == kBluetoothHCIPowerStateON;\n        device->discoverable = false;\n        device->connectable = true;\n\n        for (NSDictionary* itemDict in arr[0][@\"_items\"])\n        {\n            NSDictionary* props = itemDict[@\"controller_properties\"];\n            if (!props) continue;\n\n            if (![ctrl.addressAsString isEqualToString:props[@\"controller_address\"]]) continue;\n\n            NSString* services = props[@\"controller_supportedServices\"];\n            if ([services containsString:@\" LEA \"])\n                device->lmpVersion = -11;\n            else if ([services containsString:@\" GATT \"])\n                device->lmpVersion = -6;\n\n            device->discoverable = ![props[@\"controller_discoverable\"] isEqualToString:@\"attrib_off\"];\n            ffStrbufSetS(&device->vendor, ((NSString*) props[@\"controller_vendorID\"]).UTF8String);\n            ffStrbufSubstrAfterFirstC(&device->vendor, '(');\n            ffStrbufTrimRight(&device->vendor, ')');\n            break;\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/bluetoothradio/bluetoothradio_linux.c",
    "content": "#include \"bluetoothradio.h\"\n#include \"common/stringUtils.h\"\n\n#ifdef FF_HAVE_DBUS\n#include \"common/dbus.h\"\n#include \"common/io.h\"\n\n/* Example dbus reply:\narray [\n    dict entry(\n        string \"Address\"\n        variant string \"XX:XX:XX:XX:XX:XX\"\n    )\n    dict entry(\n        string \"Name\"\n        variant string \"xxxxxxxx\"\n    )\n    dict entry(\n        string \"Powered\"\n        variant boolean true\n    )\n    dict entry(\n        string \"PowerState\"\n        variant string \"on\"\n    )\n    dict entry(\n        string \"Manufacturer\"\n        variant uint16 2\n    )\n    dict entry(\n        string \"Version\"\n        variant byte 12\n    )\n]\n*/\n\nstatic const char* detectBluetoothProperty(FFBluetoothRadioResult* device, FFDBusData* dbus, DBusMessageIter* iter)\n{\n    if(dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DICT_ENTRY)\n        return \"Expected dict entry\";\n\n    DBusMessageIter dictIter;\n    dbus->lib->ffdbus_message_iter_recurse(iter, &dictIter);\n\n    if(dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_STRING)\n        return \"Expected dict entry key to be a string\";\n\n    const char* deviceProperty;\n    dbus->lib->ffdbus_message_iter_get_basic(&dictIter, &deviceProperty);\n\n    dbus->lib->ffdbus_message_iter_next(&dictIter);\n\n    if(ffStrEquals(deviceProperty, \"Address\"))\n        ffDBusGetString(dbus, &dictIter, &device->address);\n    else if(ffStrEquals(deviceProperty, \"Alias\"))\n        ffDBusGetString(dbus, &dictIter, &device->name);\n    else if(ffStrEquals(deviceProperty, \"Manufacturer\"))\n    {\n        uint32_t vendorId;\n        if (ffDBusGetUint(dbus, &dictIter, &vendorId))\n            ffStrbufSetStatic(&device->vendor, ffBluetoothRadioGetVendor(vendorId));\n    }\n    else if(ffStrEquals(deviceProperty, \"Version\"))\n        ffDBusGetUint(dbus, &dictIter, (uint32_t*) &device->lmpVersion);\n    else if(ffStrEquals(deviceProperty, \"Powered\"))\n        ffDBusGetBool(dbus, &dictIter, &device->enabled);\n    else if(ffStrEquals(deviceProperty, \"Discoverable\"))\n        ffDBusGetBool(dbus, &dictIter, &device->discoverable);\n    else if(ffStrEquals(deviceProperty, \"Pairable\"))\n        ffDBusGetBool(dbus, &dictIter, &device->connectable);\n\n    return NULL;\n}\n\nstatic const char* detectBluetoothRoot(FFBluetoothRadioResult* device, const char* hciName, FFDBusData* dbus)\n{\n    char objPath[300];\n    snprintf(objPath, sizeof(objPath), \"/org/bluez/%s\", hciName);\n\n    DBusMessage* properties = ffDBusGetMethodReply(dbus, \"org.bluez\", objPath, \"org.freedesktop.DBus.Properties\", \"GetAll\", \"org.bluez.Adapter1\", NULL);\n    if(!properties)\n        return \"Failed to call org.freedesktop.DBus.Properties.GetAll\";\n\n    DBusMessageIter rootIter;\n    if(!dbus->lib->ffdbus_message_iter_init(properties, &rootIter))\n    {\n        dbus->lib->ffdbus_message_unref(properties);\n        return \"Failed to get root iterator of org.freedesktop.DBus.Properties.GetAll\";\n    }\n\n    if(dbus->lib->ffdbus_message_iter_get_arg_type(&rootIter) != DBUS_TYPE_ARRAY)\n    {\n        dbus->lib->ffdbus_message_unref(properties);\n        return \"Expected array\";\n    }\n\n    DBusMessageIter arrayIter;\n    dbus->lib->ffdbus_message_iter_recurse(&rootIter, &arrayIter);\n\n    do\n    {\n        detectBluetoothProperty(device, dbus, &arrayIter);\n    } while (dbus->lib->ffdbus_message_iter_next(&arrayIter));\n\n    dbus->lib->ffdbus_message_unref(properties);\n    return NULL;\n}\n\nstatic const char* detectBluetooth(FFlist* devices)\n{\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(\"/sys/class/bluetooth\");\n    if(dirp == NULL)\n        return \"Failed to open /sys/class/bluetooth\";\n\n    FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};\n    const char* error = ffDBusLoadData(DBUS_BUS_SYSTEM, &dbus);\n    if(error)\n        return error;\n\n    struct dirent* entry;\n    while ((entry = readdir(dirp)) != NULL)\n    {\n        if (entry->d_name[0] == '.')\n            continue;\n\n        if (strchr(entry->d_name, ':') != NULL) // ignore connected devices\n            continue;\n\n        FFBluetoothRadioResult* device = ffListAdd(devices);\n        ffStrbufInit(&device->name);\n        ffStrbufInit(&device->address);\n        ffStrbufInitStatic(&device->vendor, \"Unknown\");\n        device->lmpVersion = INT_MIN;\n        device->lmpSubversion = INT_MIN;\n        device->enabled = false;\n        detectBluetoothRoot(device, entry->d_name, &dbus);\n    }\n    return NULL;\n}\n\n#endif\n\nconst char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */)\n{\n    #ifdef FF_HAVE_DBUS\n        return detectBluetooth(devices);\n    #else\n        FF_UNUSED(devices)\n        return \"Fastfetch was compiled without DBus support\";\n    #endif\n}\n"
  },
  {
    "path": "src/detection/bluetoothradio/bluetoothradio_nosupport.c",
    "content": "#include \"bluetoothradio.h\"\n\nconst char* ffDetectBluetoothRadio(FF_MAYBE_UNUSED FFlist* devices /* FFBluetoothRadioResult */)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/bluetoothradio/bluetoothradio_windows.c",
    "content": "#include \"bluetoothradio.h\"\n#include \"common/library.h\"\n#include \"common/io.h\"\n#include \"common/windows/unicode.h\"\n\n#include <windows.h>\n#include <bluetoothapis.h>\n#include <winioctl.h>\n\n// #include <bthioctl.h>\n\n#define BTH_IOCTL_BASE              0\n#define BTH_CTL(id)                 CTL_CODE(FILE_DEVICE_BLUETOOTH, (id), METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define IOCTL_BTH_GET_LOCAL_INFO    BTH_CTL(BTH_IOCTL_BASE+0x00)\n#define LMP_LE_SUPPORTED(x)         ((x >> 38) & 1)\n\ntypedef struct _BTH_RADIO_INFO\n{\n    // Supported LMP features of the radio.  Use LMP_XXX() to extract\n    // the desired bits.\n    ULONGLONG lmpSupportedFeatures;\n\n    // Manufacturer ID (possibly BTH_MFG_XXX)\n    USHORT mfg;\n\n    // LMP subversion\n    USHORT lmpSubversion;\n\n    // LMP version\n    UCHAR lmpVersion;\n} __attribute__((__packed__)) BTH_RADIO_INFO;\n\ntypedef struct _BTH_LOCAL_RADIO_INFO\n{\n    // Local BTH_ADDR, class of device, and radio name\n    BTH_DEVICE_INFO         localInfo;\n\n    // Combo of LOCAL_RADIO_XXX values\n    ULONG flags;\n\n    // HCI revision, see core spec\n    USHORT hciRevision;\n\n    // HCI version, see core spec\n    UCHAR hciVersion;\n\n    // More information about the local radio (LMP, MFG)\n    BTH_RADIO_INFO radioInfo;\n} __attribute__((__packed__)) BTH_LOCAL_RADIO_INFO;\nstatic_assert(sizeof(BTH_LOCAL_RADIO_INFO) == 292, \"BTH_LOCAL_RADIO_INFO should be 292 bytes\");\n\n#pragma GCC diagnostic ignored \"-Wpointer-sign\"\n\nconst char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */)\n{\n    // Actually bluetoothapis.dll, but it's missing on Windows 7\n    FF_LIBRARY_LOAD_MESSAGE(bluetoothapis, \"bluetoothapis.dll\", 1)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindFirstRadio)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindNextRadio)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindRadioClose)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothIsConnectable)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothIsDiscoverable)\n\n    HANDLE hRadio = NULL;\n    HBLUETOOTH_DEVICE_FIND hFind = ffBluetoothFindFirstRadio(&(BLUETOOTH_FIND_RADIO_PARAMS) {\n        .dwSize = sizeof(BLUETOOTH_FIND_RADIO_PARAMS)\n    }, &hRadio);\n    if(!hFind)\n    {\n        if (GetLastError() == ERROR_NO_MORE_ITEMS)\n            return \"No Bluetooth radios found or service disabled\";\n        else\n            return \"BluetoothFindFirstRadio() failed\";\n    }\n\n    do {\n        BTH_LOCAL_RADIO_INFO blri;\n        DWORD returned;\n        if (!DeviceIoControl(hRadio, IOCTL_BTH_GET_LOCAL_INFO, NULL, 0, &blri, sizeof(blri), &returned, NULL))\n            continue;\n\n        FFBluetoothRadioResult* device = ffListAdd(devices);\n        ffStrbufInitS(&device->name, blri.localInfo.name);\n\n        BLUETOOTH_ADDRESS_STRUCT addr = { .ullLong = blri.localInfo.address };\n        ffStrbufInitF(&device->address, \"%02X:%02X:%02X:%02X:%02X:%02X\",\n            addr.rgBytes[5],\n            addr.rgBytes[4],\n            addr.rgBytes[3],\n            addr.rgBytes[2],\n            addr.rgBytes[1],\n            addr.rgBytes[0]);\n\n        device->lmpVersion = blri.radioInfo.lmpVersion;\n        device->lmpSubversion = blri.radioInfo.lmpSubversion;\n        ffStrbufInitStatic(&device->vendor, ffBluetoothRadioGetVendor(blri.radioInfo.mfg));\n        device->enabled = true;\n        device->connectable = ffBluetoothIsConnectable(hRadio);\n        device->discoverable = ffBluetoothIsDiscoverable(hRadio);\n\n        NtClose(hRadio);\n    } while (ffBluetoothFindNextRadio(hFind, &hRadio));\n\n    ffBluetoothFindRadioClose(hFind);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/board/board.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/board/option.h\"\n\ntypedef struct FFBoardResult\n{\n    FFstrbuf name;\n    FFstrbuf vendor;\n    FFstrbuf version;\n    FFstrbuf serial;\n} FFBoardResult;\n\nconst char* ffDetectBoard(FFBoardResult* board);\n"
  },
  {
    "path": "src/detection/board/board_android.c",
    "content": "#include \"board.h\"\n#include \"common/settings.h\"\n\nconst char* ffDetectBoard(FFBoardResult* board)\n{\n    if (!ffSettingsGetAndroidProperty(\"ro.product.board\", &board->name))\n        ffSettingsGetAndroidProperty(\"ro.board.platform\", &board->name);\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/board/board_apple.c",
    "content": "#include \"board.h\"\n\n#include \"common/apple/cf_helpers.h\"\n\nconst char* ffDetectBoard(FFBoardResult* result)\n{\n    FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t service = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceMatching(\"IOPlatformExpertDevice\"));\n    if (!service)\n        return \"No IOPlatformExpertDevice found\";\n\n    FF_CFTYPE_AUTO_RELEASE CFTypeRef boardId = IORegistryEntryCreateCFProperty(service, CFSTR(\"board-id\"), kCFAllocatorDefault, kNilOptions);\n    if (boardId)\n        ffCfStrGetString(boardId, &result->name);\n    else\n    {\n        io_name_t name;\n        if (IORegistryEntryGetName(service, name) == kIOReturnSuccess)\n            ffStrbufSetS(&result->name, name);\n    }\n\n    FF_CFTYPE_AUTO_RELEASE CFStringRef version = IORegistryEntryCreateCFProperty(service, CFSTR(\"version\"), kCFAllocatorDefault, kNilOptions);\n    if (version)\n        ffCfStrGetString(version, &result->version);\n\n    FF_CFTYPE_AUTO_RELEASE CFTypeRef manufacturer = IORegistryEntryCreateCFProperty(service, CFSTR(\"manufacturer\"), kCFAllocatorDefault, kNilOptions);\n    if (manufacturer)\n        ffCfStrGetString(manufacturer, &result->vendor);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/board/board_bsd.c",
    "content": "#include \"board.h\"\n#include \"common/settings.h\"\n#include \"common/smbiosHelper.h\"\n\nconst char* ffDetectBoard(FFBoardResult* result)\n{\n    ffSettingsGetFreeBSDKenv(\"smbios.planar.product\", &result->name);\n    ffCleanUpSmbiosValue(&result->name);\n    ffSettingsGetFreeBSDKenv(\"smbios.planar.serial\", &result->serial);\n    ffCleanUpSmbiosValue(&result->serial);\n    ffSettingsGetFreeBSDKenv(\"smbios.planar.maker\", &result->vendor);\n    ffCleanUpSmbiosValue(&result->vendor);\n    ffSettingsGetFreeBSDKenv(\"smbios.planar.version\", &result->version);\n    ffCleanUpSmbiosValue(&result->version);\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/board/board_linux.c",
    "content": "#include \"board.h\"\n#include \"common/io.h\"\n#include \"common/smbiosHelper.h\"\n\nconst char* ffDetectBoard(FFBoardResult* board)\n{\n    if (ffGetSmbiosValue(\"/sys/devices/virtual/dmi/id/board_name\", \"/sys/class/dmi/id/board_name\", &board->name))\n    {\n        ffGetSmbiosValue(\"/sys/devices/virtual/dmi/id/board_serial\", \"/sys/class/dmi/id/board_serial\", &board->serial);\n        ffGetSmbiosValue(\"/sys/devices/virtual/dmi/id/board_vendor\", \"/sys/class/dmi/id/board_vendor\", &board->vendor);\n        ffGetSmbiosValue(\"/sys/devices/virtual/dmi/id/board_version\", \"/sys/class/dmi/id/board_version\", &board->version);\n    }\n    else if (ffReadFileBuffer(\"/sys/firmware/devicetree/base/smbios/smbios/baseboard/product\", &board->name))\n    {\n        ffStrbufTrimRight(&board->name, '\\0');\n        if (ffReadFileBuffer(\"/sys/firmware/devicetree/base/smbios/smbios/baseboard/manufacturer\", &board->vendor))\n            ffStrbufTrimRight(&board->vendor, '\\0');\n    }\n    else if (ffReadFileBuffer(\"/sys/firmware/devicetree/base/board\", &board->name))\n    {\n        ffStrbufTrimRightSpace(&board->name);\n    }\n    else if (ffReadFileBuffer(\"/sys/firmware/devicetree/base/compatible\", &board->vendor))\n    {\n        uint32_t comma = ffStrbufFirstIndexC(&board->vendor, ',');\n        if (comma < board->vendor.length)\n        {\n            ffStrbufSetS(&board->name, board->vendor.chars + comma + 1);\n            ffStrbufTrimRightSpace(&board->name);\n            ffStrbufSubstrBefore(&board->vendor, comma);\n        }\n    }\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/board/board_nbsd.c",
    "content": "#include \"board.h\"\n#include \"common/sysctl.h\"\n#include \"common/smbiosHelper.h\"\n\nconst char* ffDetectBoard(FFBoardResult* board)\n{\n    if (ffSysctlGetString(\"machdep.dmi.board-product\", &board->name) == NULL)\n        ffCleanUpSmbiosValue(&board->name);\n    if (ffSysctlGetString(\"machdep.dmi.board-version\", &board->version) == NULL)\n        ffCleanUpSmbiosValue(&board->version);\n    if (ffSysctlGetString(\"machdep.dmi.board-vendor\", &board->vendor) == NULL)\n        ffCleanUpSmbiosValue(&board->vendor);\n    if (ffSysctlGetString(\"machdep.dmi.board-serial\", &board->serial) == NULL)\n        ffCleanUpSmbiosValue(&board->serial);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/board/board_nosupport.c",
    "content": "#include \"board.h\"\n\nconst char* ffDetectBoard(FF_MAYBE_UNUSED FFBoardResult* board)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/board/board_windows.c",
    "content": "#include \"board.h\"\n#include \"common/smbiosHelper.h\"\n\ntypedef struct FFSmbiosBaseboard\n{\n    FFSmbiosHeader Header;\n\n    uint8_t Manufacturer; // string\n    uint8_t Product; // string\n    uint8_t Version; // string\n    uint8_t SerialNumber; // string\n    uint8_t AssetTag; // string\n    uint8_t FeatureFlags; // bit field\n    uint8_t LocationInChassis; // string\n    uint16_t ChassisHandle; // varies\n    uint8_t BoardType; // enum\n    uint8_t NumberOfContainedObjectHandles; // varies\n    uint16_t ContainedObjectHandles[]; // varies\n} __attribute__((__packed__)) FFSmbiosBaseboard;\n\nstatic_assert(offsetof(FFSmbiosBaseboard, ContainedObjectHandles) == 0x0F,\n    \"FFSmbiosBaseboard: Wrong struct alignment\");\n\nconst char* ffDetectBoard(FFBoardResult* board)\n{\n    const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();\n    if (!smbiosTable)\n        return \"Failed to get SMBIOS data\";\n\n    const FFSmbiosBaseboard* data = (const FFSmbiosBaseboard*) (*smbiosTable)[FF_SMBIOS_TYPE_BASEBOARD_INFO];\n    if (!data)\n        return \"Baseboard information section is not found in SMBIOS data\";\n\n    const char* strings = (const char*) data + data->Header.Length;\n\n    ffStrbufSetStatic(&board->name, ffSmbiosLocateString(strings, data->Product));\n    ffCleanUpSmbiosValue(&board->name);\n    ffStrbufSetStatic(&board->serial, ffSmbiosLocateString(strings, data->SerialNumber));\n    ffCleanUpSmbiosValue(&board->serial);\n    ffStrbufSetStatic(&board->vendor, ffSmbiosLocateString(strings, data->Manufacturer));\n    ffCleanUpSmbiosValue(&board->vendor);\n    ffStrbufSetStatic(&board->version, ffSmbiosLocateString(strings, data->Version));\n    ffCleanUpSmbiosValue(&board->version);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/bootmgr/bootmgr.c",
    "content": "#include \"efi_helper.h\"\n\nstatic inline uint8_t evBits(uint16_t val, uint8_t mask, uint8_t shift)\n{\n    return (uint8_t) ((val & (mask << shift)) >> shift);\n}\n\nstatic void ffEfiUcs2ToUtf8(const uint16_t *const chars, FFstrbuf* result)\n{\n    for (uint32_t i = 0; chars[i]; i++)\n    {\n        if (chars[i] <= 0x007f)\n            ffStrbufAppendC(result, (char) chars[i]);\n        else if (chars[i] > 0x007f && chars[i] <= 0x07ff)\n        {\n            ffStrbufAppendC(result, (char) (0xc0 | evBits(chars[i], 0x1f, 6)));\n            ffStrbufAppendC(result, (char) (0x80 | evBits(chars[i], 0x3f, 0)));\n        }\n        else\n        {\n            ffStrbufAppendC(result, (char) (0xe0 | evBits(chars[i], 0xf, 12)));\n            ffStrbufAppendC(result, (char) (0x80 | evBits(chars[i], 0x3f, 6)));\n            ffStrbufAppendC(result, (char) (0x80 | evBits(chars[i], 0x3f, 0)));\n        }\n    }\n}\n\nbool ffEfiFillLoadOption(const FFEfiLoadOption* efiOption, FFBootmgrResult* result)\n{\n    uint32_t descLen = 0;\n    while (efiOption->Description[descLen]) ++descLen;\n\n    if (descLen)\n        ffEfiUcs2ToUtf8(efiOption->Description, &result->name);\n\n    for (\n        ffEfiDevicePathProtocol* filePathList = (ffEfiDevicePathProtocol*) &efiOption->Description[descLen + 1];\n        filePathList->Type != 0x7F; // End of Hardware Device Path\n        filePathList = (ffEfiDevicePathProtocol*) ((uint8_t*) filePathList + filePathList->Length))\n    {\n        if (filePathList->Type == 4 && filePathList->SubType == 4)\n        {\n            // https://uefi.org/specs/UEFI/2.10/10_Protocols_Device_Path_Protocol.html#file-path-media-device-path\n            ffEfiUcs2ToUtf8((uint16_t*) filePathList->SpecificDevicePathData, &result->firmware);\n            return true;\n        }\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "src/detection/bootmgr/bootmgr.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/bootmgr/option.h\"\n\ntypedef struct FFBootmgrResult\n{\n    FFstrbuf name;\n    FFstrbuf firmware;\n    uint16_t order;\n    bool secureBoot;\n} FFBootmgrResult;\n\nconst char* ffDetectBootmgr(FFBootmgrResult* bios);\n"
  },
  {
    "path": "src/detection/bootmgr/bootmgr_apple.c",
    "content": "#include \"bootmgr.h\"\n#include \"common/io.h\"\n#include \"common/apple/cf_helpers.h\"\n\n#include <IOKit/IOKitLib.h>\n\nstatic const char* detectSecureBoot(bool* result)\n{\n    #if __aarch64__\n    FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDevice = IORegistryEntryFromPath(MACH_PORT_NULL, \"IODeviceTree:/chosen\");\n    if (!entryDevice)\n        return \"IORegistryEntryFromPath() failed\";\n\n    FF_CFTYPE_AUTO_RELEASE CFTypeRef prop = IORegistryEntryCreateCFProperty(entryDevice, CFSTR(\"secure-boot\"), kCFAllocatorDefault, 0);\n    if (!prop)\n        return \"IORegistryEntryCreateCFProperty() failed\";\n\n    if (CFGetTypeID(prop) != CFDataGetTypeID() || CFDataGetLength((CFDataRef) prop) == 0)\n        return \"Invalid secure-boot property\";\n\n    *result = (bool) *CFDataGetBytePtr((CFDataRef) prop);\n    #else\n    FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDevice = IORegistryEntryFromPath(MACH_PORT_NULL, \"IODeviceTree:/options\");\n    if (!entryDevice)\n        return \"IORegistryEntryFromPath() failed\";\n\n    FF_CFTYPE_AUTO_RELEASE CFTypeRef prop = IORegistryEntryCreateCFProperty(entryDevice, CFSTR(\"94b73556-2197-4702-82a8-3e1337dafbfb:AppleSecureBootPolicy\"), kCFAllocatorDefault, 0);\n    if (!prop)\n        return \"IORegistryEntryCreateCFProperty() failed\";\n\n    if (CFGetTypeID(prop) != CFDataGetTypeID() || CFDataGetLength((CFDataRef) prop) == 0)\n        return \"Invalid secure-boot property\";\n\n    *result = *CFDataGetBytePtr((CFDataRef) prop) != 0x02 /* Permissive Security */;\n    #endif\n\n    return NULL;\n}\n\nconst char* ffDetectBootmgr(FFBootmgrResult* result)\n{\n    if (ffPathExists(\"/System/Library/CoreServices/boot.efi\", FF_PATHTYPE_FILE))\n        ffStrbufSetStatic(&result->firmware, \"/System/Library/CoreServices/boot.efi\");\n\n    ffStrbufSetStatic(&result->name, \"iBoot\");\n\n    detectSecureBoot(&result->secureBoot);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/bootmgr/bootmgr_bsd.c",
    "content": "#include \"bootmgr.h\"\n#include \"efi_helper.h\"\n#include \"common/io.h\"\n\n#ifdef __OpenBSD__\n    #include <dev/efi/efiio.h>\n#else\n    #include <sys/efiio.h>\n#endif\n#include <sys/ioctl.h>\n#include <fcntl.h>\n#include <stdalign.h>\n\n#ifdef __NetBSD__\n    typedef uint16_t efi_char;\n#endif\n\n#ifndef EFI_GLOBAL_VARIABLE\n    #define EFI_GLOBAL_VARIABLE { 0x8be4df61, 0x93ca, 0x11d2, 0xaa, 0x0d, { 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c } }\n#endif\n\nconst char* ffDetectBootmgr(FFBootmgrResult* result)\n{\n    FF_AUTO_CLOSE_FD int efifd = open(\"/dev/efi\", O_RDWR | O_CLOEXEC);\n    if (efifd < 0) return \"open(/dev/efi) failed\";\n\n    alignas(uint16_t) uint8_t buffer[2048];\n    struct efi_var_ioc ioc = {\n        .vendor = EFI_GLOBAL_VARIABLE,\n        .data = buffer,\n    };\n\n    ioc.datasize = sizeof(buffer);\n    ioc.name = (efi_char[]){ 'B', 'o', 'o', 't', 'C', 'u', 'r', 'r', 'e', 'n', 't', '\\0' };\n    ioc.namesize = sizeof(\"BootCurrent\") * 2;\n    if (ioctl(efifd, EFIIOC_VAR_GET, &ioc) < 0 || ioc.datasize != 2)\n        return \"ioctl(EFIIOC_VAR_GET, BootCurrent) failed\";\n\n    result->order = *(uint16_t*)buffer;\n\n    unsigned char hex[5];\n    snprintf((char*) hex, sizeof(hex), \"%04X\", result->order);\n    ioc.datasize = sizeof(buffer);\n    ioc.name = (efi_char[]){ 'B', 'o', 'o', 't', hex[0], hex[1], hex[2], hex[3], '\\0' };\n    ioc.namesize = sizeof(\"Boot####\") * 2;\n    if (ioctl(efifd, EFIIOC_VAR_GET, &ioc) < 0 || ioc.datasize == sizeof(buffer))\n        return \"ioctl(EFIIOC_VAR_GET, Boot####) failed\";\n\n    ffEfiFillLoadOption((FFEfiLoadOption *)buffer, result);\n\n    ioc.name = (efi_char[]){ 'S', 'e', 'c', 'u', 'r', 'e', 'B', 'o', 'o', 't', '\\0' };\n    ioc.namesize = sizeof(\"SecureBoot\") * 2;\n    ioc.datasize = sizeof(buffer);\n    if (ioctl(efifd, EFIIOC_VAR_GET, &ioc) == 0 && ioc.datasize == 1)\n        result->secureBoot = !!buffer[0];\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/bootmgr/bootmgr_linux.c",
    "content": "#include \"bootmgr.h\"\n#include \"common/io.h\"\n#include \"efi_helper.h\"\n\n#include <stdalign.h>\n\n#define FF_EFIVARS_PATH_PREFIX \"/sys/firmware/efi/efivars/\"\n\nconst char* ffDetectBootmgr(FFBootmgrResult* result)\n{\n    alignas(uint16_t) uint8_t buffer[2048];\n\n    if (ffReadFileData(FF_EFIVARS_PATH_PREFIX \"BootCurrent-\" FF_EFI_GLOBAL_GUID, sizeof(buffer), buffer) != 6)\n        return \"Failed to read efivar: BootCurrent\";\n\n\n    result->order = *(uint16_t *)&buffer[4];\n\n    snprintf((char*) buffer, sizeof(buffer), FF_EFIVARS_PATH_PREFIX \"Boot%04X-\" FF_EFI_GLOBAL_GUID, result->order);\n\n    ssize_t size = ffReadFileData((const char*) buffer, sizeof(buffer), buffer);\n    if (size < 5 + (int) sizeof(FFEfiLoadOption) || size == (ssize_t) sizeof(buffer))\n        return \"Failed to read efivar: Boot####\";\n\n    ffEfiFillLoadOption((FFEfiLoadOption *)&buffer[4], result);\n\n    if (ffReadFileData(FF_EFIVARS_PATH_PREFIX \"SecureBoot-\" FF_EFI_GLOBAL_GUID, sizeof(buffer), buffer) >= 5)\n        result->secureBoot = buffer[4] == 1;\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/bootmgr/bootmgr_nosupport.c",
    "content": "#include \"bootmgr.h\"\n\nconst char* ffDetectBootmgr(FF_MAYBE_UNUSED FFBootmgrResult* result)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/bootmgr/bootmgr_windows.c",
    "content": "#include \"bootmgr.h\"\n#include \"efi_helper.h\"\n#include \"common/io.h\"\n#include \"common/windows/nt.h\"\n\n#include <ntstatus.h>\n#include <windows.h>\n\nconst char* enablePrivilege(const wchar_t* privilege)\n{\n    FF_AUTO_CLOSE_FD HANDLE token = NULL;\n    if (!NT_SUCCESS(NtOpenProcessToken(NtCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token)))\n        return \"NtOpenProcessToken() failed\";\n\n    TOKEN_PRIVILEGES tp = {\n        .PrivilegeCount = 1,\n        .Privileges = {\n            (LUID_AND_ATTRIBUTES) { .Attributes = SE_PRIVILEGE_ENABLED }\n        },\n    };\n    if (!LookupPrivilegeValueW(NULL, privilege, &tp.Privileges[0].Luid))\n        return \"LookupPrivilegeValue() failed\";\n\n    NTSTATUS status = NtAdjustPrivilegesToken(token, false, &tp, sizeof(tp), NULL, NULL);\n    if (!NT_SUCCESS(status))\n        return \"NtAdjustPrivilegesToken() failed\";\n\n    if (status == STATUS_NOT_ALL_ASSIGNED)\n        return \"The token does not have the specified privilege; try sudo please\";\n\n    return NULL;\n}\n\nconst char* ffDetectBootmgr(FFBootmgrResult* result)\n{\n    const char* err = enablePrivilege(L\"SeSystemEnvironmentPrivilege\");\n    if (err != NULL)\n        return err;\n\n    GUID efiGlobalGuid;\n    if (!NT_SUCCESS(RtlGUIDFromString(&(UNICODE_STRING) RTL_CONSTANT_STRING(L\"{\" FF_EFI_GLOBAL_GUID L\"}\"), &efiGlobalGuid)))\n        return \"RtlGUIDFromString() failed\";\n\n    ULONG size = sizeof(result->order);\n    if (!NT_SUCCESS(NtQuerySystemEnvironmentValueEx(&(UNICODE_STRING) RTL_CONSTANT_STRING(L\"BootCurrent\"), &efiGlobalGuid, &result->order, &size, NULL)))\n        return \"NtQuerySystemEnvironmentValueEx(BootCurrent) failed\";\n    if (size != sizeof(result->order))\n        return \"NtQuerySystemEnvironmentValueEx(BootCurrent) returned unexpected size\";\n\n    uint8_t buffer[2048];\n    wchar_t key[9];\n    swprintf(key, ARRAY_SIZE(key), L\"Boot%04X\", result->order);\n    size = sizeof(buffer);\n    if (!NT_SUCCESS(NtQuerySystemEnvironmentValueEx(&(UNICODE_STRING) RTL_CONSTANT_STRING(key), &efiGlobalGuid, buffer, &size, NULL)))\n        return \"NtQuerySystemEnvironmentValueEx(Boot####) failed\";\n    if (size < sizeof(FFEfiLoadOption) || size == ARRAY_SIZE(buffer))\n        return \"NtQuerySystemEnvironmentValueEx(Boot####) returned unexpected size\";\n\n    ffEfiFillLoadOption((FFEfiLoadOption *)buffer, result);\n\n    SYSTEM_SECUREBOOT_INFORMATION ssi;\n    if (NT_SUCCESS(NtQuerySystemInformation(SystemSecureBootInformation, &ssi, sizeof(ssi), NULL)))\n        result->secureBoot = ssi.SecureBootEnabled;\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/bootmgr/efi_helper.h",
    "content": "#include \"bootmgr.h\"\n\n// https://uefi.org/specs/UEFI/2.10/10_Protocols_Device_Path_Protocol.html#generic-device-path-structures\ntypedef struct ffEfiDevicePathProtocol\n{\n    uint8_t Type;\n    uint8_t SubType;\n    uint16_t Length;\n    uint8_t SpecificDevicePathData[];\n} ffEfiDevicePathProtocol;\n\n// https://uefi.org/specs/UEFI/2.10/03_Boot_Manager.html#load-options\ntypedef struct FFEfiLoadOption\n{\n    uint32_t Attributes;\n    uint16_t FilePathListLength;\n    uint16_t Description[];\n    // ffEfiDevicePathProtocol FilePathList[];\n    // uint8_t OptionalData[];\n} FFEfiLoadOption;\n\nbool ffEfiFillLoadOption(const FFEfiLoadOption* efiOption, FFBootmgrResult* result);\n\n#define FF_EFI_GLOBAL_GUID \"8be4df61-93ca-11d2-aa0d-00e098032b8c\"\n"
  },
  {
    "path": "src/detection/brightness/brightness.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/brightness/option.h\"\n\ntypedef struct FFBrightnessResult\n{\n    FFstrbuf name;\n    double min, max, current;\n    bool builtin;\n} FFBrightnessResult;\n\nconst char* ffDetectBrightness(FFBrightnessOptions* options, FFlist* result); // list of FFBrightnessResult\n"
  },
  {
    "path": "src/detection/brightness/brightness_apple.c",
    "content": "#include \"brightness.h\"\n#include \"detection/displayserver/displayserver.h\"\n#include \"common/apple/cf_helpers.h\"\n#include \"common/edidHelper.h\"\n\n#include <CoreGraphics/CoreGraphics.h>\n\n// DDC/CI\n#ifdef __aarch64__\ntypedef CFTypeRef IOAVServiceRef;\nextern IOAVServiceRef IOAVServiceCreate(CFAllocatorRef allocator) __attribute__((weak_import));\nextern IOAVServiceRef IOAVServiceCreateWithService(CFAllocatorRef allocator, io_service_t service) __attribute__((weak_import));\nextern IOReturn IOAVServiceCopyEDID(IOAVServiceRef service, CFDataRef* x2) __attribute__((weak_import));\nextern IOReturn IOAVServiceReadI2C(IOAVServiceRef service, uint32_t chipAddress, uint32_t offset, void* outputBuffer, uint32_t outputBufferSize) __attribute__((weak_import));\nextern IOReturn IOAVServiceWriteI2C(IOAVServiceRef service, uint32_t chipAddress, uint32_t dataAddress, void* inputBuffer, uint32_t inputBufferSize) __attribute__((weak_import));\n#else\n// DDC/CI (Intel)\n#include <IOKit/IOKitLib.h>\n#include <IOKit/graphics/IOGraphicsLib.h>\n#include <IOKit/i2c/IOI2CInterface.h>\nextern void CGSServiceForDisplayNumber(CGDirectDisplayID display, io_service_t* service) __attribute__((weak_import));\n#endif\n\n// ACPI\nextern int DisplayServicesGetBrightness(CGDirectDisplayID display, float *brightness) __attribute__((weak_import));\n\n// Works for internal display\nstatic const char* detectWithDisplayServices(const FFDisplayServerResult* displayServer, FFlist* result)\n{\n    if(DisplayServicesGetBrightness == NULL)\n        return \"DisplayServices function DisplayServicesGetBrightness is not available\";\n\n    FF_LIST_FOR_EACH(FFDisplayResult, display, displayServer->displays)\n    {\n        if (display->type == FF_DISPLAY_TYPE_BUILTIN || display->type == FF_DISPLAY_TYPE_UNKNOWN)\n        {\n            float value;\n            if(DisplayServicesGetBrightness((CGDirectDisplayID) display->id, &value) == kCGErrorSuccess)\n            {\n                FFBrightnessResult* brightness = (FFBrightnessResult*) ffListAdd(result);\n                brightness->current = value;\n                brightness->max = 1;\n                brightness->min = 0;\n                ffStrbufInitCopy(&brightness->name, &display->name);\n                brightness->builtin = true;\n            }\n        }\n    }\n\n    return NULL;\n}\n\n#ifdef __aarch64__\n// https://github.com/waydabber/m1ddc\n// Works for Apple Silicon and USB-C adapter connection ( but not HTMI )\nstatic const char* detectWithDdcci(FF_MAYBE_UNUSED const FFDisplayServerResult* displayServer, FFBrightnessOptions* options, FFlist* result)\n{\n    if (!IOAVServiceCreate || !IOAVServiceReadI2C || !IOAVServiceWriteI2C)\n        return \"IOAVService is not available\";\n\n    FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL;\n    if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceMatching(\"DCPAVServiceProxy\"), &iterator) != kIOReturnSuccess)\n        return \"IOServiceGetMatchingServices() failed\";\n\n    io_registry_entry_t registryEntry;\n    while ((registryEntry = IOIteratorNext(iterator)) != IO_OBJECT_NULL)\n    {\n        FF_CFTYPE_AUTO_RELEASE IOAVServiceRef service = NULL;\n        {\n            FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryAv = registryEntry;\n\n            FF_CFTYPE_AUTO_RELEASE CFBooleanRef IOAVServiceUserInterfaceSupported = IORegistryEntryCreateCFProperty(entryAv, CFSTR(\"IOAVServiceUserInterfaceSupported\"), kCFAllocatorDefault, kNilOptions);\n            if (IOAVServiceUserInterfaceSupported && !CFBooleanGetValue(IOAVServiceUserInterfaceSupported))\n            {\n                // IOAVServiceCreateWithService won't work\n                continue;\n            }\n\n            FF_CFTYPE_AUTO_RELEASE CFStringRef location = IORegistryEntryCreateCFProperty(entryAv, CFSTR(\"Location\"), kCFAllocatorDefault, kNilOptions);\n            if (location && CFStringCompare(location, CFSTR(\"Embedded\"), 0) == 0)\n            {\n                // Builtin display should be handled by DisplayServices\n                continue;\n            }\n\n            service = IOAVServiceCreateWithService(kCFAllocatorDefault, (io_service_t) registryEntry);\n            if (!service) continue;\n        }\n\n        {\n            uint8_t i2cIn[4] = { 0x82, 0x01, 0x10 /* luminance */ };\n            i2cIn[3] = 0x6e ^ i2cIn[0] ^ i2cIn[1] ^ i2cIn[2];\n\n            for (uint32_t i = 0; i < 2; ++i)\n            {\n                IOAVServiceWriteI2C(service, 0x37, 0x51, i2cIn, ARRAY_SIZE(i2cIn));\n                usleep(options->ddcciSleep * 1000);\n            }\n        }\n\n        uint8_t i2cOut[12] = {};\n        if (IOAVServiceReadI2C(service, 0x37, 0x51, i2cOut, ARRAY_SIZE(i2cOut)) == KERN_SUCCESS)\n        {\n            if (i2cOut[2] != 0x02 || i2cOut[3] != 0x00)\n                continue;\n\n            uint32_t current = ((uint32_t) i2cOut[8] << 8u) + (uint32_t) i2cOut[9];\n            uint32_t max = ((uint32_t) i2cOut[6] << 8u) + (uint32_t) i2cOut[7];\n\n            FFBrightnessResult* brightness = (FFBrightnessResult*) ffListAdd(result);\n            brightness->max = max;\n            brightness->min = 0;\n            brightness->current = current;\n            ffStrbufInit(&brightness->name);\n            brightness->builtin = false;\n\n            uint8_t edid[128] = {};\n            if (IOAVServiceReadI2C(service, 0x50, 0x00, edid, ARRAY_SIZE(edid)) == KERN_SUCCESS)\n                ffEdidGetName(edid, &brightness->name);\n        }\n    }\n\n    return NULL;\n}\n#else\nstatic IOOptionBits getSupportedTransactionType(void)\n{\n    FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL;\n\n    if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceNameMatching(\"IOFramebufferI2CInterface\"), &iterator) != KERN_SUCCESS)\n        return kIOI2CNoTransactionType;\n\n    io_registry_entry_t registryEntry;\n    while ((registryEntry = IOIteratorNext(iterator)) != MACH_PORT_NULL)\n    {\n        FF_IOOBJECT_AUTO_RELEASE io_service_t io_service = registryEntry;\n        FF_CFTYPE_AUTO_RELEASE CFNumberRef IOI2CTransactionTypes = IORegistryEntryCreateCFProperty(io_service, CFSTR(kIOI2CTransactionTypesKey), kCFAllocatorDefault, kNilOptions);\n\n        if (IOI2CTransactionTypes)\n        {\n            int64_t types = 0;\n            ffCfNumGetInt64(IOI2CTransactionTypes, &types);\n\n            if (types) {\n                if ((1 << kIOI2CDDCciReplyTransactionType) & (uint64_t) types)\n                    return kIOI2CDDCciReplyTransactionType;\n                if ((1 << kIOI2CSimpleTransactionType) & (uint64_t) types)\n                    return kIOI2CSimpleTransactionType;\n            }\n        }\n        break;\n    }\n\n    return kIOI2CNoTransactionType;\n}\n\nstatic const char* detectWithDdcci(const FFDisplayServerResult* displayServer, FFBrightnessOptions* options, FFlist* result)\n{\n    if (!CGSServiceForDisplayNumber) return \"CGSServiceForDisplayNumber is not available\";\n    IOOptionBits transactionType = getSupportedTransactionType();\n    if (transactionType == kIOI2CNoTransactionType)\n        return \"No supported IOI2C transaction type found\";\n\n    FF_LIST_FOR_EACH(FFDisplayResult, display, displayServer->displays)\n    {\n        if (display->type == FF_DISPLAY_TYPE_EXTERNAL)\n        {\n            FF_IOOBJECT_AUTO_RELEASE io_service_t framebuffer = IO_OBJECT_NULL;\n            CGSServiceForDisplayNumber((CGDirectDisplayID)display->id, &framebuffer);\n            if (framebuffer == IO_OBJECT_NULL) continue;\n\n            IOItemCount count;\n            if (IOFBGetI2CInterfaceCount(framebuffer, &count) != KERN_SUCCESS || count == 0)\n                continue;\n\n            for (IOItemCount bus = 0; bus < count; ++bus)\n            {\n                FF_IOOBJECT_AUTO_RELEASE io_service_t interface = IO_OBJECT_NULL;\n                if (IOFBCopyI2CInterfaceForBus(framebuffer, bus, &interface) != KERN_SUCCESS) continue;\n\n                uint8_t i2cOut[12] = {};\n                IOI2CConnectRef connect = NULL;\n                if (IOI2CInterfaceOpen(interface, kNilOptions, &connect) != KERN_SUCCESS)\n                    continue;\n\n                uint8_t i2cIn[] = { 0x51, 0x82, 0x01, 0x10 /* luminance */, 0 };\n                i2cIn[4] = 0x6E ^ i2cIn[0] ^ i2cIn[1] ^ i2cIn[2] ^ i2cIn[3];\n\n                IOI2CRequest request = {\n                    .commFlags = kNilOptions,\n                    .sendAddress = 0x6e,\n                    .sendTransactionType = kIOI2CSimpleTransactionType,\n                    .sendBuffer = (vm_address_t) i2cIn,\n                    .sendBytes = ARRAY_SIZE(i2cIn),\n                    .minReplyDelay = options->ddcciSleep * 1000ULL,\n                    .replyAddress = 0x6F,\n                    .replySubAddress = 0x51,\n                    .replyTransactionType = transactionType,\n                    .replyBytes = ARRAY_SIZE(i2cOut),\n                    .replyBuffer = (vm_address_t) i2cOut,\n                };\n                IOReturn ret = IOI2CSendRequest(connect, kNilOptions, &request);\n                IOI2CInterfaceClose(connect, kNilOptions);\n\n                if (ret != KERN_SUCCESS || request.result != kIOReturnSuccess || request.replyBytes < 10) continue;\n                if (i2cOut[2] != 0x02 || i2cOut[3] != 0x00) continue;\n\n                uint32_t current = ((uint32_t) i2cOut[8] << 8u) + (uint32_t) i2cOut[9];\n                uint32_t max = ((uint32_t) i2cOut[6] << 8u) + (uint32_t) i2cOut[7];\n\n                FFBrightnessResult* brightness = (FFBrightnessResult*) ffListAdd(result);\n                brightness->max = max;\n                brightness->min = 0;\n                brightness->current = current;\n                ffStrbufInitCopy(&brightness->name, &display->name);\n                brightness->builtin = false;\n\n                break;\n            }\n        }\n    }\n\n    return NULL;\n}\n#endif\n\nconst char* ffDetectBrightness(FFBrightnessOptions* options, FFlist* result)\n{\n    const FFDisplayServerResult* displayServer = ffConnectDisplayServer();\n\n    detectWithDisplayServices(displayServer, result);\n\n    if (displayServer->displays.length > result->length)\n        detectWithDdcci(displayServer, options, result);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/brightness/brightness_bsd.c",
    "content": "#include \"brightness.h\"\n\n#if __has_include(<sys/backlight.h>)\n\n#include \"common/io.h\"\n\n#include <sys/backlight.h>\n#include <sys/ioctl.h>\n#include <sys/fcntl.h>\n#include <unistd.h>\n\nconst char* ffDetectBrightness(FF_MAYBE_UNUSED FFBrightnessOptions* options, FFlist* result)\n{\n    // https://man.freebsd.org/cgi/man.cgi?query=backlight&sektion=9\n    char path[] = \"/dev/backlight/backlight0\";\n\n    for (char i = '0'; i <= '9'; ++i)\n    {\n        path[ARRAY_SIZE(path) - 2] = i;\n\n        FF_AUTO_CLOSE_FD int blfd = open(path, O_RDONLY | O_CLOEXEC);\n        if (blfd < 0)\n            continue;\n\n        struct backlight_props status;\n        if(ioctl(blfd, BACKLIGHTGETSTATUS, &status) < 0)\n            continue;\n\n        FFBrightnessResult* brightness = (FFBrightnessResult*) ffListAdd(result);\n        ffStrbufInit(&brightness->name);\n\n        brightness->max = BACKLIGHTMAXLEVELS;\n        brightness->min = 0;\n        brightness->current = status.brightness;\n        brightness->builtin = true;\n\n        struct backlight_info info;\n        if(ioctl(blfd, BACKLIGHTGETINFO, &info) == 0)\n            ffStrbufAppendS(&brightness->name, info.name);\n        else\n            ffStrbufAppendS(&brightness->name, path + strlen(\"/dev/backlight/\"));\n    }\n    return NULL;\n}\n\n#else\n\nconst char* ffDetectBrightness(FF_MAYBE_UNUSED FFBrightnessOptions* options, FF_MAYBE_UNUSED FFlist* result)\n{\n    return \"Backlight is supported only on FreeBSD 13 and newer\";\n}\n\n#endif\n"
  },
  {
    "path": "src/detection/brightness/brightness_linux.c",
    "content": "#include \"brightness.h\"\n#include \"common/io.h\"\n#include \"common/edidHelper.h\"\n#include \"common/stringUtils.h\"\n\n#include <dirent.h>\n#include <limits.h>\n\nstatic const char* detectWithBacklight(FFlist* result)\n{\n    //https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-class-backlight\n    const char* backlightDirPath = \"/sys/class/backlight/\";\n\n    DIR* dirp = opendir(backlightDirPath);\n    if(dirp == NULL)\n        return \"Failed to open `/sys/class/backlight/`\";\n\n    FF_STRBUF_AUTO_DESTROY backlightDir = ffStrbufCreateA(64);\n    ffStrbufAppendS(&backlightDir, backlightDirPath);\n\n    uint32_t backlightDirLength = backlightDir.length;\n\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n\n    struct dirent* entry;\n    while((entry = readdir(dirp)) != NULL)\n    {\n        if(entry->d_name[0] == '.')\n            continue;\n\n        ffStrbufAppendS(&backlightDir, entry->d_name);\n        ffStrbufAppendS(&backlightDir, \"/brightness\");\n        if(ffReadFileBuffer(backlightDir.chars, &buffer))\n        {\n            double actualBrightness = ffStrbufToDouble(&buffer, 0);\n            ffStrbufSubstrBefore(&backlightDir, backlightDirLength);\n            ffStrbufAppendS(&backlightDir, entry->d_name);\n            ffStrbufAppendS(&backlightDir, \"/max_brightness\");\n            if(ffReadFileBuffer(backlightDir.chars, &buffer))\n            {\n                FFBrightnessResult* brightness = (FFBrightnessResult*) ffListAdd(result);\n                ffStrbufSubstrBeforeLastC(&backlightDir, '/');\n                ffStrbufAppendS(&backlightDir, \"/device\");\n                ffStrbufInitA(&brightness->name, PATH_MAX);\n                if(realpath(backlightDir.chars, brightness->name.chars))\n                {\n                    ffStrbufRecalculateLength(&brightness->name);\n                    // if we managed to get edid, use it\n                    ffStrbufAppendS(&brightness->name, \"/edid\");\n                    uint8_t edidData[128];\n                    if(ffReadFileData(brightness->name.chars, ARRAY_SIZE(edidData), edidData) == ARRAY_SIZE(edidData))\n                    {\n                        ffStrbufClear(&brightness->name);\n                        ffEdidGetName(edidData, &brightness->name);\n                    }\n                    else\n                    {\n                        ffStrbufSubstrBeforeLastC(&brightness->name, '/'); // remove \"/edid\"\n                        ffStrbufSubstrAfterLastC(&brightness->name, '/'); // try getting DRM connector name\n                        if(ffCharIsDigit(brightness->name.chars[0]))\n                        {\n                            // PCI address or some unknown path, give up\n                            ffStrbufSetS(&brightness->name, entry->d_name);\n                        }\n                        else\n                        {\n                            if(ffStrbufStartsWithS(&brightness->name, \"card\") && ffCharIsDigit(brightness->name.chars[4]))\n                                ffStrbufSubstrAfterFirstC(&brightness->name, '-');\n                        }\n                    }\n                }\n                else\n                    ffStrbufInitS(&brightness->name, entry->d_name);\n                brightness->max = ffStrbufToDouble(&buffer, 0);\n                brightness->min = 0;\n                brightness->current = actualBrightness;\n                brightness->builtin = true;\n            }\n        }\n        ffStrbufSubstrBefore(&backlightDir, backlightDirLength);\n    }\n\n    closedir(dirp);\n\n    return NULL;\n}\n\n#ifdef FF_HAVE_DDCUTIL\n#include \"detection/displayserver/displayserver.h\"\n#include \"common/library.h\"\n#include \"common/mallocHelper.h\"\n\n#include <ddcutil_macros.h>\n#include <ddcutil_c_api.h>\n\n// Try to be compatible with ddcutil 2.0\n#if DDCUTIL_VMAJOR >= 2\ndouble ddca_set_default_sleep_multiplier(double multiplier); // ddcutil 1.4\n#else\nDDCA_Status ddca_init(const char *libopts, int syslog_level, int opts);\n#endif\n\nstatic const char* detectWithDdcci(FF_MAYBE_UNUSED FFBrightnessOptions* options, FFlist* result)\n{\n    FF_LIBRARY_LOAD_MESSAGE(libddcutil, \"libddcutil\" FF_LIBRARY_EXTENSION, 5);\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_get_display_info_list2)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_open_display2)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_get_any_vcp_value_using_explicit_type)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_free_any_vcp_value)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_close_display)\n\n    #ifndef FF_DISABLE_DLOPEN\n    FF_LIBRARY_LOAD_SYMBOL_LAZY(libddcutil, ddca_init)\n    if (ffddca_init)\n    {\n        FF_SUPPRESS_IO();\n        // Ref: https://github.com/rockowitz/ddcutil/issues/344\n        if (ffddca_init(NULL, -1 /*DDCA_SYSLOG_NOT_SET*/, 1 /*DDCA_INIT_OPTIONS_DISABLE_CONFIG_FILE*/) < 0)\n            return \"ddca_init() failed\";\n    }\n    else\n    {\n        FF_LIBRARY_LOAD_SYMBOL_LAZY(libddcutil, ddca_set_default_sleep_multiplier);\n        if (ffddca_set_default_sleep_multiplier)\n            ffddca_set_default_sleep_multiplier(options->ddcciSleep / 40.0);\n\n        libddcutil = NULL; // Don't dlclose libddcutil. See https://github.com/rockowitz/ddcutil/issues/330\n    }\n    #else\n    #if DDCUTIL_VMAJOR >= 2\n        if (ddca_init(NULL, -1 /*DDCA_SYSLOG_NOT_SET*/, 1 /*DDCA_INIT_OPTIONS_DISABLE_CONFIG_FILE*/) < 0)\n            return \"ddca_init() failed\";\n    #else\n        ddca_set_default_sleep_multiplier(options->ddcciSleep / 40.0);\n    #endif\n    #endif\n\n    FF_AUTO_FREE DDCA_Display_Info_List* infoList = NULL;\n    if (ffddca_get_display_info_list2(false, &infoList) < 0)\n        return \"ddca_get_display_info_list2(false, &infoList) failed\";\n\n    if (infoList->ct == 0)\n        return \"No DDC/CI compatible displays found\";\n\n    for (int index = 0; index < infoList->ct; ++index)\n    {\n        const DDCA_Display_Info* display = &infoList->info[index];\n\n        DDCA_Display_Handle handle;\n        if (ffddca_open_display2(display->dref, false, &handle) >= 0)\n        {\n            DDCA_Any_Vcp_Value* vcpValue = NULL;\n            if (ffddca_get_any_vcp_value_using_explicit_type(handle, 0x10 /*brightness*/, DDCA_NON_TABLE_VCP_VALUE, &vcpValue) >= 0)\n            {\n                assert(vcpValue->value_type == DDCA_NON_TABLE_VCP_VALUE);\n                int current = VALREC_CUR_VAL(vcpValue), max = VALREC_MAX_VAL(vcpValue);\n                ffddca_free_any_vcp_value(vcpValue);\n\n                FFBrightnessResult* brightness = (FFBrightnessResult*) ffListAdd(result);\n                brightness->max = max;\n                brightness->min = 0;\n                brightness->current = current;\n                ffStrbufInitS(&brightness->name, display->model_name);\n            }\n            ffddca_close_display(handle);\n        }\n    }\n\n    return NULL;\n}\n#endif\n\nconst char* ffDetectBrightness(FF_MAYBE_UNUSED FFBrightnessOptions* options, FFlist* result)\n{\n    detectWithBacklight(result);\n\n    #ifdef FF_HAVE_DDCUTIL\n    const FFDisplayServerResult* displayServer = ffConnectDisplayServer();\n    if (result->length < displayServer->displays.length)\n        detectWithDdcci(options, result);\n    #endif\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/brightness/brightness_nbsd.c",
    "content": "#include \"brightness.h\"\n\n#include \"common/sysctl.h\"\n\nconst char* ffDetectBrightness(FF_MAYBE_UNUSED FFBrightnessOptions* options, FFlist* result)\n{\n    // https://man.netbsd.org/NetBSD-10.1/acpiout.4#DESCRIPTION\n    char key[] = \"hw.acpi.acpiout0.brightness\";\n    char* pn = key + strlen(\"hw.acpi.acpiout\");\n\n    for (uint32_t i = 0; i <= 9; ++i)\n    {\n        *pn = (char) ('0' + i);\n        int value = ffSysctlGetInt(key, -1);\n        if (value == -1) continue;\n\n        FFBrightnessResult* brightness = (FFBrightnessResult*) ffListAdd(result);\n        ffStrbufInitF(&brightness->name, \"acpiout%d\", i);\n\n        brightness->max = 100;\n        brightness->min = 0;\n        brightness->current = value;\n        brightness->builtin = true;\n    }\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/brightness/brightness_nosupport.c",
    "content": "#include \"brightness.h\"\n\nconst char* ffDetectBrightness(FF_MAYBE_UNUSED FFBrightnessOptions* options, FF_MAYBE_UNUSED FFlist* result)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/brightness/brightness_obsd.c",
    "content": "#include \"brightness.h\"\n#include \"common/io.h\"\n\n#include <dev/wscons/wsconsio.h>\n#include <sys/ioctl.h>\n#include <unistd.h>\n#include <fcntl.h>\n\nconst char* ffDetectBrightness(FF_MAYBE_UNUSED FFBrightnessOptions* options, FFlist* result)\n{\n    char path[] = \"/dev/ttyCX\";\n    for (char i = '0'; i <= '9'; ++i) {\n        path[strlen(\"/dev/ttyC\")] = i;\n\n        FF_AUTO_CLOSE_FD int devfd = open(path, O_RDONLY | O_CLOEXEC);\n\n        if (devfd < 0) {\n            if (errno == EACCES && i == '0')\n                return \"Permission denied when opening tty device\";\n            if (errno == ENOENT)\n                break;\n            continue;\n        }\n\n        struct wsdisplay_param param = {\n            .param = WSDISPLAYIO_PARAM_BRIGHTNESS,\n        };\n\n        if (ioctl(devfd, WSDISPLAYIO_GETPARAM, &param) < 0)\n            continue;\n\n        FFBrightnessResult* brightness = (FFBrightnessResult*) ffListAdd(result);\n        ffStrbufInitF(&brightness->name, \"ttyC%c\", i);\n\n        brightness->max = param.max;\n        brightness->min = param.min;\n        brightness->current = param.curval;\n        brightness->builtin = true;\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/brightness/brightness_windows.cpp",
    "content": "extern \"C\"\n{\n#include \"brightness.h\"\n#include \"detection/displayserver/displayserver.h\"\n#include \"common/library.h\"\n}\n#include \"common/windows/wmi.hpp\"\n#include \"common/windows/unicode.hpp\"\n\n#include <winternl.h>\n\nNTSYSAPI NTSTATUS WINAPI GetPhysicalMonitors(\n  _In_  UNICODE_STRING *pstrDeviceName,\n  _In_  DWORD          dwPhysicalMonitorArraySize,\n  _Out_ DWORD          *pdwNumPhysicalMonitorHandlesInArray,\n  _Out_ HANDLE         *phPhysicalMonitorArray\n);\n\ntypedef enum _MC_VCP_CODE_TYPE {\n  MC_MOMENTARY,\n  MC_SET_PARAMETER\n} MC_VCP_CODE_TYPE, *LPMC_VCP_CODE_TYPE;\n\nNTSYSAPI NTSTATUS WINAPI DDCCIGetVCPFeature(\n  _In_      HANDLE             hMonitor,\n  _In_      DWORD              dwVCPCode,\n  _Out_opt_ LPMC_VCP_CODE_TYPE pvct,\n  _Out_     DWORD              *pdwCurrentValue,\n  _Out_opt_ DWORD              *pdwMaximumValue\n);\n\nNTSYSAPI NTSTATUS WINAPI DestroyPhysicalMonitorInternal(\n  _In_ HANDLE hMonitor\n);\n\nNTSTATUS WINAPI GetPhysicalMonitorDescription(\n  _In_  HANDLE hMonitor,\n  _In_  DWORD  dwPhysicalMonitorDescriptionSizeInChars,\n  _Out_ LPWSTR szPhysicalMonitorDescription\n);\n\nstatic const char* detectWithWmi(FFlist* result)\n{\n    FFWmiQuery query(L\"SELECT CurrentBrightness, InstanceName FROM WmiMonitorBrightness WHERE Active = true\", nullptr, FFWmiNamespace::WMI);\n    if(!query)\n        return \"Query WMI service failed\";\n\n    while(FFWmiRecord record = query.next())\n    {\n        if(FFWmiVariant vtValue = record.get(L\"CurrentBrightness\"))\n        {\n            FFBrightnessResult* brightness = (FFBrightnessResult*) ffListAdd(result);\n            brightness->max = 100;\n            brightness->min = 0;\n            brightness->current = vtValue.get<uint8_t>();\n            brightness->builtin = true;\n\n            ffStrbufInit(&brightness->name);\n            if (FFWmiVariant vtName = record.get(L\"InstanceName\"))\n            {\n                ffStrbufSetWSV(&brightness->name, vtName.get<std::wstring_view>());\n                ffStrbufSubstrAfterFirstC(&brightness->name, '\\\\');\n                ffStrbufSubstrBeforeFirstC(&brightness->name, '\\\\');\n            }\n        }\n    }\n    return NULL;\n}\n\nstatic const char* detectWithDdcci(const FFDisplayServerResult* displayServer, FFlist* result)\n{\n    void* gdi32 = ffLibraryGetModule(L\"gdi32.dll\");\n    if (!gdi32) return \"ffLibraryGetModule(gdi32.dll) failed\";\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(gdi32, GetPhysicalMonitors)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(gdi32, DDCCIGetVCPFeature)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(gdi32, DestroyPhysicalMonitorInternal)\n\n    FF_LIST_FOR_EACH(FFDisplayResult, display, displayServer->displays)\n    {\n        if (display->type == FF_DISPLAY_TYPE_BUILTIN) continue;\n\n        MONITORINFOEXW mi;\n        mi.cbSize = sizeof(mi);\n        if (!GetMonitorInfoW((HMONITOR)(uintptr_t) display->id, (LPMONITORINFO) &mi))\n            continue;\n\n        UNICODE_STRING deviceName = {\n            .Length = (USHORT) (wcslen(mi.szDevice) * sizeof(wchar_t)),\n            .MaximumLength = 0,\n            .Buffer = mi.szDevice,\n        };\n        HANDLE physicalMonitor;\n        DWORD monitorCount = 0;\n        if (NT_SUCCESS(ffGetPhysicalMonitors(&deviceName, 1, &monitorCount, &physicalMonitor)) && monitorCount >= 1)\n        {\n            DWORD curr = 0, max = 0;\n            if (NT_SUCCESS(ffDDCCIGetVCPFeature(physicalMonitor, 0x10 /* luminance */, NULL, &curr, &max)))\n            {\n                FFBrightnessResult* brightness = (FFBrightnessResult*) ffListAdd(result);\n                if (display->name.length > 0)\n                    ffStrbufInitCopy(&brightness->name, &display->name);\n                else\n                {\n                    FF_LIBRARY_LOAD_SYMBOL_LAZY(gdi32, GetPhysicalMonitorDescription)\n                    if (ffGetPhysicalMonitorDescription)\n                    {\n                        wchar_t description[128 /*MUST be PHYSICAL_MONITOR_DESCRIPTION_SIZE*/];\n                        if (NT_SUCCESS(ffGetPhysicalMonitorDescription(physicalMonitor, ARRAY_SIZE(description), description)))\n                            ffStrbufInitWS(&brightness->name, description);\n                    }\n                    if (brightness->name.length == 0)\n                        ffStrbufSetNWS(&brightness->name, deviceName.Length / 2, deviceName.Buffer);\n                }\n                brightness->max = max;\n                brightness->min = 0;\n                brightness->current = curr;\n                brightness->builtin = false;\n            }\n\n            ffDestroyPhysicalMonitorInternal(physicalMonitor);\n        }\n    }\n    return NULL;\n}\n\nstatic bool hasBuiltinDisplay(const FFDisplayServerResult* displayServer)\n{\n    FF_LIST_FOR_EACH(FFDisplayResult, display, displayServer->displays)\n    {\n        if (display->type == FF_DISPLAY_TYPE_BUILTIN || display->type == FF_DISPLAY_TYPE_UNKNOWN)\n            return true;\n    }\n    return false;\n}\n\nextern \"C\"\nconst char* ffDetectBrightness(FF_MAYBE_UNUSED FFBrightnessOptions* options, FFlist* result)\n{\n    const FFDisplayServerResult* displayServer = ffConnectDisplayServer();\n\n    if (hasBuiltinDisplay(displayServer))\n        detectWithWmi(result);\n\n    if (result->length < displayServer->displays.length)\n        detectWithDdcci(displayServer, result);\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/btrfs/btrfs.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/btrfs/option.h\"\n\ntypedef struct FFBtrfsDiskUsage\n{\n    uint64_t total;\n    uint64_t used;\n    const char* type;\n    const char* profile; // single / dup / raidx\n    uint8_t copies;\n} FFBtrfsDiskUsage;\n\ntypedef struct FFBtrfsResult\n{\n    FFstrbuf name;\n    FFstrbuf uuid;\n    FFstrbuf devices;\n    FFstrbuf features;\n    uint32_t generation;\n    uint32_t nodeSize;\n    uint32_t sectorSize;\n    uint64_t totalSize;\n    uint64_t globalReservationUsed;\n    uint64_t globalReservationTotal;\n    FFBtrfsDiskUsage allocation[3];\n} FFBtrfsResult;\n\nconst char* ffDetectBtrfs(FFlist* result /* list of FFBtrfsResult */);\n"
  },
  {
    "path": "src/detection/btrfs/btrfs_linux.c",
    "content": "#include \"btrfs.h\"\n\n#include \"common/io.h\"\n#include <fcntl.h>\n\nenum { uuidLen = (uint32_t) __builtin_strlen(\"00000000-0000-0000-0000-000000000000\") };\n\nstatic const char* enumerateDevices(FFBtrfsResult* item, int dfd, FFstrbuf* buffer)\n{\n    int subfd = openat(dfd, \"devices\", O_RDONLY | O_CLOEXEC | O_DIRECTORY);\n    if (subfd < 0) return \"openat(\\\"/sys/fs/btrfs/UUID/devices\\\") == -1\";\n\n    FF_AUTO_CLOSE_DIR DIR* dirp = fdopendir(subfd);\n    if(dirp == NULL)\n    {\n        close(subfd);\n        return \"fdopendir(\\\"/sys/fs/btrfs/UUID/devices\\\") == NULL\";\n    }\n\n    struct dirent* entry;\n    while ((entry = readdir(dirp)) != NULL)\n    {\n        if (entry->d_name[0] == '.')\n            continue;\n\n        if (item->devices.length)\n            ffStrbufAppendC(&item->devices, ',');\n        ffStrbufAppendS(&item->devices, entry->d_name);\n\n        char path[sizeof(entry->d_name) + sizeof(\"/size\") + 1];\n        snprintf(path, ARRAY_SIZE(path), \"%s/size\", entry->d_name);\n\n        if (ffReadFileBufferRelative(subfd, path, buffer))\n            item->totalSize += ffStrbufToUInt(buffer, 0) * 512;\n    }\n\n    return NULL;\n}\n\nstatic const char* enumerateFeatures(FFBtrfsResult* item, int dfd)\n{\n    int subfd = openat(dfd, \"features\", O_RDONLY | O_CLOEXEC | O_DIRECTORY);\n    if (subfd < 0) return \"openat(\\\"/sys/fs/btrfs/UUID/features\\\") == -1\";\n\n    FF_AUTO_CLOSE_DIR DIR* dirp = fdopendir(subfd);\n    if(dirp == NULL)\n        return \"fdopendir(\\\"/sys/fs/btrfs/UUID/features\\\") == NULL\";\n\n    struct dirent* entry;\n    while ((entry = readdir(dirp)) != NULL)\n    {\n        if (entry->d_name[0] == '.')\n            continue;\n        if (item->features.length)\n            ffStrbufAppendC(&item->features, ',');\n        ffStrbufAppendS(&item->features, entry->d_name);\n    }\n\n    return NULL;\n}\n\nstatic const char* detectAllocation(FFBtrfsResult* item, int dfd, FFstrbuf* buffer)\n{\n    FF_AUTO_CLOSE_FD int subfd = openat(dfd, \"allocation\", O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY);\n    if (subfd < 0) return \"openat(\\\"/sys/fs/btrfs/UUID/allocation\\\") == -1\";\n\n    if (ffReadFileBufferRelative(subfd, \"global_rsv_size\", buffer))\n        item->globalReservationTotal = ffStrbufToUInt(buffer, 0);\n    else\n        return \"ffReadFileBuffer(\\\"/sys/fs/btrfs/UUID/allocation/global_rsv_size\\\") == NULL\";\n\n    if (ffReadFileBufferRelative(subfd, \"global_rsv_reserved\", buffer))\n        item->globalReservationUsed = ffStrbufToUInt(buffer, 0);\n    item->globalReservationUsed = item->globalReservationTotal - item->globalReservationUsed;\n\n    #define FF_BTRFS_DETECT_PROFILE(_index, _type, _profile, _copies) \\\n        else if (faccessat(subfd, _type \"/\" _profile \"/\", F_OK, 0) == 0) { \\\n            item->allocation[_index].profile = _profile; \\\n            item->allocation[_index].copies = _copies; \\\n        }\n\n    #define FF_BTRFS_DETECT_TYPE(_index, _type) \\\n    do { \\\n        item->allocation[_index].type = _type; \\\n        if (ffReadFileBufferRelative(subfd, _type \"/total_bytes\", buffer)) \\\n            item->allocation[_index].total = ffStrbufToUInt(buffer, 0); \\\n        \\\n        if (ffReadFileBufferRelative(subfd, _type \"/bytes_used\", buffer)) \\\n            item->allocation[_index].used = ffStrbufToUInt(buffer, 0); \\\n        \\\n        if (false) {} \\\n        FF_BTRFS_DETECT_PROFILE(_index, _type, \"single\", 1) \\\n        FF_BTRFS_DETECT_PROFILE(_index, _type, \"dup\", 2) \\\n        FF_BTRFS_DETECT_PROFILE(_index, _type, \"raid0\", 1) \\\n        FF_BTRFS_DETECT_PROFILE(_index, _type, \"raid1\", 2) \\\n        FF_BTRFS_DETECT_PROFILE(_index, _type, \"raid10\", 2) \\\n        FF_BTRFS_DETECT_PROFILE(_index, _type, \"raid1c3\", 3) \\\n        FF_BTRFS_DETECT_PROFILE(_index, _type, \"raid1c4\", 4) \\\n        FF_BTRFS_DETECT_PROFILE(_index, _type, \"raid5\", 1) /* (n-1)/n */ \\\n        FF_BTRFS_DETECT_PROFILE(_index, _type, \"raid6\", 1) /* (n-2)/n */ \\\n        else { \\\n            item->allocation[_index].profile = \"unknown\"; \\\n            item->allocation[_index].copies = 1; \\\n        } \\\n    } while (0)\n\n    FF_BTRFS_DETECT_TYPE(0, \"data\");\n    FF_BTRFS_DETECT_TYPE(1, \"metadata\");\n    FF_BTRFS_DETECT_TYPE(2, \"system\");\n\n    #undef FF_BTRFS_DETECT_TYPE\n\n    return NULL;\n}\n\nconst char* ffDetectBtrfs(FFlist* result)\n{\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(\"/sys/fs/btrfs/\");\n    if(dirp == NULL)\n        return \"opendir(\\\"/sys/fs/btrfs\\\") == NULL\";\n\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n\n    struct dirent* entry;\n    while ((entry = readdir(dirp)) != NULL)\n    {\n        if (entry->d_name[0] == '.')\n            continue;\n        if (strlen(entry->d_name) != uuidLen)\n            continue;\n\n        FFBtrfsResult* item = ffListAdd(result);\n        (*item) = (FFBtrfsResult){\n            .uuid = ffStrbufCreateNS(uuidLen, entry->d_name),\n            .name = ffStrbufCreate(),\n            .devices = ffStrbufCreate(),\n            .features = ffStrbufCreate(),\n        };\n\n        FF_AUTO_CLOSE_FD int dfd = openat(dirfd(dirp), entry->d_name, O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY);\n        if (dfd < 0) continue;\n\n        if (ffAppendFileBufferRelative(dfd, \"label\", &item->name))\n            ffStrbufTrimRightSpace(&item->name);\n\n        enumerateDevices(item, dfd, &buffer);\n\n        enumerateFeatures(item, dfd);\n\n        if (ffReadFileBufferRelative(dfd, \"generation\", &buffer))\n            item->generation = (uint32_t) ffStrbufToUInt(&buffer, 0);\n\n        if (ffReadFileBufferRelative(dfd, \"nodesize\", &buffer))\n            item->nodeSize = (uint32_t) ffStrbufToUInt(&buffer, 0);\n\n        if (ffReadFileBufferRelative(dfd, \"sectorsize\", &buffer))\n            item->sectorSize = (uint32_t) ffStrbufToUInt(&buffer, 0);\n\n        detectAllocation(item, dfd, &buffer);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/btrfs/btrfs_nosupport.c",
    "content": "#include \"btrfs.h\"\n\nconst char* ffDetectBtrfs(FF_MAYBE_UNUSED FFlist* result)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/camera/camera.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/camera/option.h\"\n\ntypedef struct FFCameraResult\n{\n    FFstrbuf name;\n    FFstrbuf vendor;\n    FFstrbuf id;\n    FFstrbuf colorspace;\n    uint32_t width;\n    uint32_t height;\n} FFCameraResult;\n\nconst char* ffDetectCamera(FFlist* result /* list of FFCameraResult */);\n"
  },
  {
    "path": "src/detection/camera/camera_android.c",
    "content": "#include \"camera.h\"\n\n#include \"common/processing.h\"\n#include \"common/properties.h\"\n\n#define FF_TERMUX_API_PATH FASTFETCH_TARGET_DIR_ROOT \"/libexec/termux-api\"\n#define FF_TERMUX_API_PARAM \"CameraInfo\"\n\nstatic inline void wrapYyjsonFree(yyjson_doc** doc)\n{\n    assert(doc);\n    if (*doc)\n        yyjson_doc_free(*doc);\n}\n\nconst char* ffDetectCamera(FF_MAYBE_UNUSED FFlist* result)\n{\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n\n    if(ffProcessAppendStdOut(&buffer, (char* const[]){\n        FF_TERMUX_API_PATH,\n        FF_TERMUX_API_PARAM,\n        NULL\n    }))\n        return \"Starting `\" FF_TERMUX_API_PATH \" \" FF_TERMUX_API_PARAM \"` failed\";\n\n    yyjson_doc* __attribute__((__cleanup__(wrapYyjsonFree))) doc = yyjson_read_opts(buffer.chars, buffer.length, 0, NULL, NULL);\n    if (!doc)\n        return \"Failed to parse camera info\";\n\n    yyjson_val* root = yyjson_doc_get_root(doc);\n    if (!yyjson_is_arr(root))\n        return \"Camera info result is not a JSON array\";\n\n    yyjson_val* device;\n    size_t idx, max;\n    yyjson_arr_foreach(root, idx, max, device)\n    {\n        FFCameraResult* camera = (FFCameraResult*) ffListAdd(result);\n        {\n            const char* facing = yyjson_get_str(yyjson_obj_get(device, \"facing\"));\n            if (facing)\n                ffStrbufInitF(&camera->name, \"builtin-%s\", facing);\n            else\n                ffStrbufInitStatic(&camera->name, \"Unknown\");\n        }\n        ffStrbufInit(&camera->vendor);\n        ffStrbufInitJsonVal(&camera->id, yyjson_obj_get(device, \"id\"));\n        yyjson_val* sizes = yyjson_arr_get_first(yyjson_obj_get(device, \"jpeg_output_sizes\"));\n        if (yyjson_is_obj(sizes))\n        {\n            camera->width = (uint32_t) yyjson_get_uint(yyjson_obj_get(sizes, \"width\"));\n            camera->height = (uint32_t) yyjson_get_uint(yyjson_obj_get(sizes, \"height\"));\n        }\n        else\n            camera->width = camera->height = 0;\n        ffStrbufInit(&camera->colorspace);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/camera/camera_apple.m",
    "content": "#include \"camera.h\"\n#include \"common/io.h\"\n\n#import <AVFoundation/AVCaptureDevice.h>\n\n// warning: 'AVCaptureDeviceTypeExternalUnknown' is deprecated\n#pragma GCC diagnostic ignored \"-Wdeprecated-declarations\"\n\n#ifdef MAC_OS_VERSION_14_0\n// To make fastfetch compiled on newer macOS versions runs on older ones\nAVF_EXPORT __attribute__((weak_import)) AVCaptureDeviceType const AVCaptureDeviceTypeExternal;\n#endif\n\nconst char* ffDetectCamera(FFlist* result)\n{\n    #ifdef MAC_OS_X_VERSION_10_15\n    FF_SUPPRESS_IO(); // #822\n\n    AVCaptureDeviceType deviceType = NULL;\n\n    #ifdef MAC_OS_VERSION_14_0\n    // Strangely `@available(macOS 14.0, *)` doesn't work here (#1594)\n    if (@available(macOS 14.0, *))\n    {\n        if (&AVCaptureDeviceTypeExternal)\n            deviceType = AVCaptureDeviceTypeExternal;\n    }\n    #endif\n    if (deviceType == NULL)\n        deviceType = AVCaptureDeviceTypeExternalUnknown;\n\n    AVCaptureDeviceDiscoverySession* session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera, deviceType]\n                                                                                mediaType:AVMediaTypeVideo\n                                                                                position:AVCaptureDevicePositionUnspecified];\n    if (!session)\n        return \"Failed to create AVCaptureDeviceDiscoverySession\";\n\n    for (AVCaptureDevice* device in session.devices)\n    {\n        FFCameraResult* camera = (FFCameraResult*) ffListAdd(result);\n        ffStrbufInitS(&camera->name, device.localizedName.UTF8String);\n        ffStrbufInitS(&camera->vendor, device.manufacturer.UTF8String);\n        ffStrbufInitS(&camera->id, device.uniqueID.UTF8String);\n        switch (device.activeColorSpace)\n        {\n            case AVCaptureColorSpace_sRGB: ffStrbufInitStatic(&camera->colorspace, \"sRGB\"); break;\n            case AVCaptureColorSpace_P3_D65: ffStrbufInitStatic(&camera->colorspace, \"P3-D65\"); break;\n            case 2 /*AVCaptureColorSpace_HLG_BT2020*/: ffStrbufInitStatic(&camera->colorspace, \"BT2020-HLG\"); break;\n            case 3 /*AVCaptureColorSpace_AppleLog*/: ffStrbufInitStatic(&camera->colorspace, \"AppleLog\"); break;\n        }\n\n        CMVideoDimensions size = CMVideoFormatDescriptionGetDimensions(device.activeFormat.formatDescription);\n        camera->width = size.width < 0 ? 0 : (uint32_t) size.width;\n        camera->height = size.height < 0 ? 0 : (uint32_t) size.height;\n    }\n    return NULL;\n    #else\n    return \"No support for old MacOS version\";\n    #endif\n}\n"
  },
  {
    "path": "src/detection/camera/camera_linux.c",
    "content": "#include \"camera.h\"\n#include \"common/io.h\"\n\n#include <unistd.h>\n#include <fcntl.h>\n#include <sys/ioctl.h>\n\n#if FF_HAVE_LINUX_VIDEODEV2\n    #include <linux/videodev2.h>\n#elif __has_include(<sys/videoio.h>) // OpenBSD\n    #include <sys/videoio.h>\n    #define FF_HAVE_LINUX_VIDEODEV2 1\n#endif\n\nconst char* ffDetectCamera(FFlist* result)\n{\n#if FF_HAVE_LINUX_VIDEODEV2\n    char path[] = \"/dev/videoN\";\n\n    for (uint32_t i = 0; i <= 9; ++i)\n    {\n        path[ARRAY_SIZE(path) - 2] = (char) (i + '0');\n        FF_AUTO_CLOSE_FD int fd = open(path, O_RDONLY | O_CLOEXEC);\n        if (fd < 0)\n        {\n            if (errno == ENOENT)\n                break;\n            if (errno == ENXIO)\n                continue;\n            return \"Failed to open /dev/videoN\";\n        }\n\n        struct v4l2_capability cap = {};\n        if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0 || !(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))\n            continue;\n\n        struct v4l2_format fmt = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE };\n        if (ioctl(fd, VIDIOC_G_FMT, &fmt) < 0)\n            continue;\n\n        FFCameraResult* camera = (FFCameraResult*) ffListAdd(result);\n        ffStrbufInitS(&camera->name, (const char*) cap.card);\n        ffStrbufInit(&camera->vendor);\n        ffStrbufInitS(&camera->id, (const char*) cap.bus_info);\n        switch (fmt.fmt.pix.colorspace)\n        {\n        case V4L2_COLORSPACE_SMPTE170M: ffStrbufInitStatic(&camera->colorspace, \"SMPTE 170M\"); break;\n        case V4L2_COLORSPACE_SMPTE240M: ffStrbufInitStatic(&camera->colorspace, \"SMPTE 240M\"); break;\n        case V4L2_COLORSPACE_BT878: ffStrbufInitStatic(&camera->colorspace, \"BT.878\"); break;\n        case V4L2_COLORSPACE_470_SYSTEM_M: ffStrbufInitStatic(&camera->colorspace, \"NTSC\"); break;\n        case V4L2_COLORSPACE_470_SYSTEM_BG: ffStrbufInitStatic(&camera->colorspace, \"EBU 3213\"); break;\n        case V4L2_COLORSPACE_JPEG: ffStrbufInitStatic(&camera->colorspace, \"JPEG\"); break;\n        case V4L2_COLORSPACE_REC709:\n        case V4L2_COLORSPACE_SRGB: ffStrbufInitStatic(&camera->colorspace, \"sRGB\"); break;\n        case 9 /* V4L2_COLORSPACE_OPRGB */: ffStrbufInitStatic(&camera->colorspace, \"Adobe RGB\"); break;\n        case 10 /* V4L2_COLORSPACE_BT2020 */: ffStrbufInitStatic(&camera->colorspace, \"BT.2020\"); break;\n        case 11 /* V4L2_COLORSPACE_RAW */: ffStrbufInitStatic(&camera->colorspace, \"RAW\"); break;\n        case 12 /* V4L2_COLORSPACE_DCI_P3 */: ffStrbufInitStatic(&camera->colorspace, \"DCI-P3\"); break;\n        default: ffStrbufInit(&camera->colorspace); break;\n        }\n        camera->width = fmt.fmt.pix.width;\n        camera->height = fmt.fmt.pix.height;\n    }\n\n    return NULL;\n#else\n    FF_UNUSED(result);\n    return \"Fastfetch was compiled without <linux/videodev2.h>\";\n#endif\n}\n"
  },
  {
    "path": "src/detection/camera/camera_nosupport.c",
    "content": "#include \"camera.h\"\n\nconst char* ffDetectCamera(FF_MAYBE_UNUSED FFlist* result)\n{\n    return \"Not support on this platform\";\n}\n"
  },
  {
    "path": "src/detection/camera/camera_windows.cpp",
    "content": "extern \"C\" {\n#include \"camera.h\"\n#include \"common/library.h\"\n}\n#include \"common/windows/com.hpp\"\n#include \"common/windows/unicode.hpp\"\n#include \"common/windows/util.hpp\"\n\n#include <initguid.h>\n#include <mfapi.h>\n#include <mfidl.h>\n\nextern \"C\"\nconst char* ffDetectCamera(FF_MAYBE_UNUSED FFlist* result)\n{\n    FF_LIBRARY_LOAD_MESSAGE(mfplat, \"mfplat\" FF_LIBRARY_EXTENSION, 1)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mfplat, MFCreateAttributes)\n    FF_LIBRARY_LOAD_MESSAGE(mf, \"mf\" FF_LIBRARY_EXTENSION, 1)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mf, MFEnumDeviceSources)\n\n    const char* error = ffInitCom();\n    if (error)\n        return error;\n\n    IMFAttributes* FF_AUTO_RELEASE_COM_OBJECT attrs = nullptr;\n    if (FAILED(ffMFCreateAttributes(&attrs, 1)))\n        return \"MFCreateAttributes() failed\";\n\n    if (FAILED(attrs->SetGUID(\n        MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,\n        MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID\n    )))\n        return \"SetGUID(MF_*) failed\";\n\n    IMFActivate** devices = NULL;\n    uint32_t count;\n\n    if (FAILED(ffMFEnumDeviceSources(attrs, &devices, &count)))\n        return \"MFEnumDeviceSources() failed\";\n\n    for (uint32_t i = 0; i < count; i++)\n    {\n        IMFActivate* FF_AUTO_RELEASE_COM_OBJECT device = devices[i];\n\n        wchar_t buffer[256];\n        uint32_t length = 0;\n        if (FAILED(device->GetString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, buffer, ARRAY_SIZE(buffer), &length)) || length == 0)\n            continue;\n\n        FFCameraResult* camera = (FFCameraResult*) ffListAdd(result);\n        ffStrbufInitNWS(&camera->name, length, buffer);\n        ffStrbufInit(&camera->colorspace);\n        ffStrbufInit(&camera->vendor);\n        ffStrbufInit(&camera->id);\n        camera->width = 0;\n        camera->height = 0;\n\n        if (SUCCEEDED(device->GetString(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, buffer, ARRAY_SIZE(buffer), &length)) && length > 0)\n            ffStrbufSetNWS(&camera->id, length, buffer);\n\n        IMFMediaSource* FF_AUTO_RELEASE_COM_OBJECT source = nullptr;\n        if (FAILED(device->ActivateObject(IID_PPV_ARGS(&source))))\n            continue;\n\n        on_scope_exit destroySource([&] { source->Shutdown(); });\n\n        IMFPresentationDescriptor* FF_AUTO_RELEASE_COM_OBJECT pd = nullptr;\n        if (FAILED(source->CreatePresentationDescriptor(&pd)))\n            continue;\n\n        IMFStreamDescriptor* FF_AUTO_RELEASE_COM_OBJECT sd = NULL;\n        BOOL selected;\n        if (FAILED(pd->GetStreamDescriptorByIndex(0, &selected, &sd)))\n            continue;\n\n        IMFMediaTypeHandler* FF_AUTO_RELEASE_COM_OBJECT handler = NULL;\n        if (FAILED(sd->GetMediaTypeHandler(&handler)))\n            continue;\n\n        DWORD mediaTypeCount;\n        if (FAILED(handler->GetMediaTypeCount(&mediaTypeCount)))\n            continue;\n\n        // Assume first type is the maximum resolution\n        IMFMediaType* type = NULL;\n        for (DWORD idx = 0; SUCCEEDED(handler->GetMediaTypeByIndex(idx, &type)); ++idx)\n        {\n            on_scope_exit destroyType([=] { type->Release(); });\n\n            GUID majorType;\n            if (FAILED(type->GetMajorType(&majorType)) || majorType != MFMediaType_Video)\n                continue;\n\n            MFVideoPrimaries primaries;\n            static_assert(sizeof(primaries) == sizeof(uint32_t), \"\");\n            if (SUCCEEDED(type->GetUINT32(MF_MT_VIDEO_PRIMARIES, (uint32_t*) &primaries)))\n            {\n                switch (primaries)\n                {\n                case MFVideoPrimaries_BT709: ffStrbufSetStatic(&camera->colorspace, \"sRGB\"); break;\n                case MFVideoPrimaries_BT470_2_SysM:\n                case MFVideoPrimaries_BT470_2_SysBG: ffStrbufSetStatic(&camera->colorspace, \"NTSC\"); break;\n                case MFVideoPrimaries_SMPTE170M: ffStrbufSetStatic(&camera->colorspace, \"SMPTE 170M\"); break;\n                case MFVideoPrimaries_SMPTE240M: ffStrbufSetStatic(&camera->colorspace, \"SMPTE 240M\"); break;\n                case MFVideoPrimaries_EBU3213: ffStrbufSetStatic(&camera->colorspace, \"EBU 3213\"); break;\n                case MFVideoPrimaries_SMPTE_C: ffStrbufSetStatic(&camera->colorspace, \"SMPTE C\"); break;\n                case MFVideoPrimaries_BT2020: ffStrbufSetStatic(&camera->colorspace, \"BT.2020\"); break;\n                case MFVideoPrimaries_XYZ: ffStrbufSetStatic(&camera->colorspace, \"XYZ\"); break;\n                case MFVideoPrimaries_DCI_P3: ffStrbufSetStatic(&camera->colorspace, \"DCI-P3\"); break;\n                case MFVideoPrimaries_ACES: ffStrbufSetStatic(&camera->colorspace, \"ACES\"); break;\n                default: break;\n                }\n            }\n\n            MFGetAttributeSize(type, MF_MT_FRAME_SIZE, &camera->width, &camera->height);\n            break;\n        }\n    }\n\n    if (devices) CoTaskMemFree(devices);\n\n    return nullptr;\n}\n"
  },
  {
    "path": "src/detection/chassis/chassis.c",
    "content": "#include \"chassis.h\"\n\nconst char* ffChassisTypeToString(uint32_t type)\n{\n    // https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.7.0.pdf\n    // 7.4.1 System Enclosure or Chassis Types\n    switch (type & 0b01111111)\n    {\n        case 0x01: return \"Other\";\n        case 0x02: return \"Unknown\";\n        case 0x03: return \"Desktop\";\n        case 0x04: return \"Low Profile Desktop\";\n        case 0x05: return \"Pizza Box\";\n        case 0x06: return \"Mini Tower\";\n        case 0x07: return \"Tower\";\n        case 0x08: return \"Portable\";\n        case 0x09: return \"Laptop\";\n        case 0x0A: return \"Notebook\";\n        case 0x0B: return \"Hand Held\";\n        case 0x0C: return \"Docking Station\";\n        case 0x0D: return \"All in One\";\n        case 0x0E: return \"Sub Notebook\";\n        case 0x0F: return \"Space-saving\";\n        case 0x10: return \"Lunch Box\";\n        case 0x11: return \"Main Server Chassis\";\n        case 0x12: return \"Expansion Chassis\";\n        case 0x13: return \"SubChassis\";\n        case 0x14: return \"Bus Expansion Chassis\";\n        case 0x15: return \"Peripheral Chassis\";\n        case 0x16: return \"RAID Chassis\";\n        case 0x17: return \"Rack Mount Chassis\";\n        case 0x18: return \"Sealed-case PC\";\n        case 0x19: return \"Multi-system chassis\";\n        case 0x1A: return \"Compact PCI\";\n        case 0x1B: return \"Advanced TCA\";\n        case 0x1C: return \"Blade\";\n        case 0x1D: return \"Mobile Workstation\";\n        case 0x1E: return \"Tablet\";\n        case 0x1F: return \"Convertible\";\n        case 0x20: return \"Detachable\";\n        case 0x21: return \"IoT Gateway\";\n        case 0x22: return \"Embedded PC\";\n        case 0x23: return \"Mini PC\";\n        case 0x24: return \"Stick PC\";\n        default: return NULL;\n    }\n}\n"
  },
  {
    "path": "src/detection/chassis/chassis.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/chassis/option.h\"\n\ntypedef struct FFChassisResult\n{\n    FFstrbuf type;\n    FFstrbuf serial;\n    FFstrbuf vendor;\n    FFstrbuf version;\n} FFChassisResult;\n\nconst char* ffDetectChassis(FFChassisResult* result);\nconst char* ffChassisTypeToString(uint32_t type);\n"
  },
  {
    "path": "src/detection/chassis/chassis_apple.c",
    "content": "#include \"chassis.h\"\n#include \"detection/host/host.h\"\n\nconst char* ffDetectChassis(FFChassisResult* result)\n{\n    FFHostResult host = {\n        .family = ffStrbufCreate(),\n        .name = ffStrbufCreate(),\n        .version = ffStrbufCreate(),\n        .sku = ffStrbufCreate(),\n        .serial = ffStrbufCreate(),\n        .uuid = ffStrbufCreate(),\n        .vendor = ffStrbufCreate(),\n    };\n    if (ffDetectHost(&host) != NULL)\n        return \"Failed to detect host\";\n\n    if (ffStrbufStartsWithS(&host.name, \"MacBook \"))\n        ffStrbufSetStatic(&result->type, \"Laptop\");\n    else if (ffStrbufStartsWithS(&host.name, \"Mac mini \") ||\n             ffStrbufStartsWithS(&host.name, \"Mac Studio \"))\n        ffStrbufSetStatic(&result->type, \"Mini PC\");\n    else if (ffStrbufStartsWithS(&host.name, \"iMac \"))\n        ffStrbufSetStatic(&result->type, \"All in One\");\n    else\n        ffStrbufSetStatic(&result->type, \"Desktop\");\n\n    ffStrbufSet(&result->vendor, &host.vendor);\n\n    ffStrbufDestroy(&host.family);\n    ffStrbufDestroy(&host.name);\n    ffStrbufDestroy(&host.version);\n    ffStrbufDestroy(&host.sku);\n    ffStrbufDestroy(&host.serial);\n    ffStrbufDestroy(&host.uuid);\n    ffStrbufDestroy(&host.vendor);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/chassis/chassis_bsd.c",
    "content": "#include \"chassis.h\"\n#include \"common/settings.h\"\n#include \"common/smbiosHelper.h\"\n\nconst char* ffDetectChassis(FFChassisResult* result)\n{\n    // Unlike other platforms, `smbios.chassis.type` return display string directly on my machine\n    ffSettingsGetFreeBSDKenv(\"smbios.chassis.type\", &result->type);\n    ffCleanUpSmbiosValue(&result->type);\n    ffSettingsGetFreeBSDKenv(\"smbios.chassis.maker\", &result->vendor);\n    ffCleanUpSmbiosValue(&result->vendor);\n    ffSettingsGetFreeBSDKenv(\"smbios.chassis.serial\", &result->serial);\n    ffCleanUpSmbiosValue(&result->serial);\n    ffSettingsGetFreeBSDKenv(\"smbios.chassis.version\", &result->version);\n    ffCleanUpSmbiosValue(&result->version);\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/chassis/chassis_linux.c",
    "content": "#include \"chassis.h\"\n#include \"common/io.h\"\n#include \"common/smbiosHelper.h\"\n\n#include <ctype.h>\n\nconst char* ffDetectChassis(FFChassisResult* result)\n{\n    if (ffGetSmbiosValue(\"/sys/devices/virtual/dmi/id/chassis_type\", \"/sys/class/dmi/id/chassis_type\", &result->type))\n    {\n        ffGetSmbiosValue(\"/sys/devices/virtual/dmi/id/chassis_serial\", \"/sys/class/dmi/id/chassis_serial\", &result->serial);\n        ffGetSmbiosValue(\"/sys/devices/virtual/dmi/id/chassis_vendor\", \"/sys/class/dmi/id/chassis_vendor\", &result->vendor);\n        ffGetSmbiosValue(\"/sys/devices/virtual/dmi/id/chassis_version\", \"/sys/class/dmi/id/chassis_version\", &result->version);\n\n        if(result->type.length)\n        {\n            const char* typeStr = ffChassisTypeToString((uint32_t) ffStrbufToUInt(&result->type, 9999));\n            if(typeStr)\n                ffStrbufSetStatic(&result->type, typeStr);\n        }\n    }\n    else\n    {\n        // Available on Asahi Linux\n        uint32_t chassisType = 0;\n        if (ffReadFileData(\"/sys/firmware/devicetree/base/smbios/smbios/chassis/chassis-type\", sizeof(chassisType), &chassisType)) // big endian\n        {\n            chassisType = __builtin_bswap32(chassisType);\n            const char* typeStr = ffChassisTypeToString(chassisType);\n            if(typeStr)\n                ffStrbufSetStatic(&result->type, typeStr);\n\n            if(ffReadFileBuffer(\"/sys/firmware/devicetree/base/smbios/smbios/chassis/manufacturer\", &result->vendor) && result->vendor.length > 0)\n                ffStrbufTrimRight(&result->vendor, '\\0');\n        }\n        else if(ffReadFileBuffer(\"/sys/firmware/devicetree/base/chassis-type\", &result->type) && result->type.length > 0)\n        {\n            ffStrbufTrimRight(&result->type, '\\0');\n            result->type.chars[0] = (char) toupper(result->type.chars[0]);\n        }\n    }\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/chassis/chassis_nbsd.c",
    "content": "#include \"chassis.h\"\n#include \"common/sysctl.h\"\n#include \"common/smbiosHelper.h\"\n\nconst char* ffDetectChassis(FFChassisResult* chassis)\n{\n    if (ffSysctlGetString(\"machdep.dmi.chassis-type\", &chassis->type) == NULL)\n        ffCleanUpSmbiosValue(&chassis->type);\n    if (ffSysctlGetString(\"machdep.dmi.chassis-version\", &chassis->version) == NULL)\n        ffCleanUpSmbiosValue(&chassis->version);\n    if (ffSysctlGetString(\"machdep.dmi.chassis-vendor\", &chassis->vendor) == NULL)\n        ffCleanUpSmbiosValue(&chassis->vendor);\n    if (ffSysctlGetString(\"machdep.dmi.chassis-serial\", &chassis->serial) == NULL)\n        ffCleanUpSmbiosValue(&chassis->serial);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/chassis/chassis_nosupport.c",
    "content": "#include \"chassis.h\"\n\nconst char* ffDetectChassis(FF_MAYBE_UNUSED FFChassisResult* result)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/chassis/chassis_windows.c",
    "content": "#include \"chassis.h\"\n#include \"common/smbiosHelper.h\"\n\n// 7.4\ntypedef struct FFSmbiosSystemEnclosure\n{\n    FFSmbiosHeader Header;\n\n    uint8_t Manufacturer; // string\n    uint8_t Type; // varies\n    uint8_t Version; // string\n    uint8_t SerialNumber; // string\n    uint8_t AssetTagNumber; // string\n\n    // 2.1+\n    uint8_t BootupState; // enum\n    uint8_t PowerSupplyState; // enum\n    uint8_t ThermalState; // enum\n    uint8_t SecurityStatus; // enum\n\n    // 2.3+\n    uint32_t OEMDefined; // varies\n    uint8_t Height; // varies\n    uint8_t NumberOfPowerCords; // varies\n    uint8_t ContainedElementCount; // varies\n    uint8_t ContainedRecordLength; // varies\n    uint8_t ContainedElements[]; // varies\n} __attribute__((__packed__)) FFSmbiosSystemEnclosure;\n\nstatic_assert(offsetof(FFSmbiosSystemEnclosure, ContainedElements) == 0x15,\n    \"FFSmbiosSystemEnclosure: Wrong struct alignment\");\n\nconst char* ffDetectChassis(FFChassisResult* result)\n{\n    const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();\n    if (!smbiosTable)\n        return \"Failed to get SMBIOS data\";\n\n    const FFSmbiosSystemEnclosure* data = (const FFSmbiosSystemEnclosure*) (*smbiosTable)[FF_SMBIOS_TYPE_SYSTEM_ENCLOSURE];\n    if (!data)\n        return \"System enclosure is not found in SMBIOS data\";\n\n    const char* strings = (const char*) data + data->Header.Length;\n\n    ffStrbufSetStatic(&result->vendor, ffSmbiosLocateString(strings, data->Manufacturer));\n    ffCleanUpSmbiosValue(&result->vendor);\n    ffStrbufSetStatic(&result->serial, ffSmbiosLocateString(strings, data->SerialNumber));\n    ffCleanUpSmbiosValue(&result->serial);\n    ffStrbufSetStatic(&result->version, ffSmbiosLocateString(strings, data->Version));\n    ffCleanUpSmbiosValue(&result->version);\n    ffStrbufSetStatic(&result->type, ffChassisTypeToString(data->Type));\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/command/command.c",
    "content": "#include \"detection/command/command.h\"\n#include \"common/processing.h\"\n#include \"common/FFstrbuf.h\"\n\ntypedef struct FFCommandResultBundle\n{\n    FFProcessHandle handle;\n    const char* error;\n} FFCommandResultBundle;\n\n// FIFO, non-thread-safe list of running commands\nstatic FFlist commandQueue = {\n    .elementSize = sizeof(FFCommandResultBundle),\n};\n\nstatic const char* spawnProcess(FFCommandOptions* options, FFProcessHandle* handle)\n{\n    if (options->text.length == 0)\n        return \"No command text specified\";\n\n    return ffProcessSpawn(options->param.length ? (char* const[]){\n        options->shell.chars,\n        options->param.chars,\n        options->text.chars,\n        NULL\n    } : (char* const[]){\n        options->shell.chars,\n        options->text.chars,\n        NULL\n    }, options->useStdErr, handle);\n}\n\nbool ffPrepareCommand(FFCommandOptions* options)\n{\n    if (!options->parallel) return false;\n\n    FFCommandResultBundle* bundle = ffListAdd(&commandQueue);\n    bundle->error = spawnProcess(options, &bundle->handle);\n\n    return true;\n}\n\nconst char* ffDetectCommand(FFCommandOptions* options, FFstrbuf* result)\n{\n    FFCommandResultBundle bundle = {};\n    if (!options->parallel)\n        bundle.error = spawnProcess(options, &bundle.handle);\n    else if (!ffListShift(&commandQueue, &bundle))\n        return \"[BUG] command queue is empty\";\n\n    if (bundle.error)\n        return bundle.error;\n\n    bundle.error = ffProcessReadOutput(&bundle.handle, result);\n    if (bundle.error)\n        return bundle.error;\n\n    ffStrbufTrimRightSpace(result);\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/command/command.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/command/option.h\"\n\nconst char* ffDetectCommand(FFCommandOptions* options, FFstrbuf* result);\n"
  },
  {
    "path": "src/detection/cpu/cpu.c",
    "content": "#include \"cpu.h\"\n\nconst char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu);\n\nconst char* ffDetectCPU(const FFCPUOptions* options, FFCPUResult* cpu)\n{\n    const char* error = ffDetectCPUImpl(options, cpu);\n    if (error) return error;\n\n    const char* removeStrings[] = {\n        \" CPU\", \" FPU\", \" APU\", \" Processor\",\n        \" Dual-Core\", \" Quad-Core\", \" Six-Core\", \" Eight-Core\", \" Ten-Core\",\n        \" 2-Core\", \" 4-Core\", \" 6-Core\", \" 8-Core\", \" 10-Core\", \" 12-Core\", \" 14-Core\", \" 16-Core\"\n    };\n    ffStrbufRemoveStrings(&cpu->name, ARRAY_SIZE(removeStrings), removeStrings);\n    uint32_t radeonGraphics = ffStrbufFirstIndexS(&cpu->name, \" w/ Radeon \"); // w/ Radeon 780M Graphics\n    if (radeonGraphics >= cpu->name.length)\n        radeonGraphics = ffStrbufFirstIndexS(&cpu->name, \" with Radeon \");\n    if (radeonGraphics < cpu->name.length)\n        ffStrbufSubstrBefore(&cpu->name, radeonGraphics);\n    ffStrbufSubstrBeforeFirstC(&cpu->name, '@'); //Cut the speed output in the name as we append our own\n    ffStrbufTrimRight(&cpu->name, ' '); //If we removed the @ in previous step there was most likely a space before it\n    ffStrbufRemoveDupWhitespaces(&cpu->name);\n    return NULL;\n}\n\nconst char* ffCPUAppleCodeToName(uint32_t code)\n{\n    // https://github.com/AsahiLinux/docs/wiki/Codenames\n    switch (code)\n    {\n        case 8103: return \"Apple M1\";\n        case 6000: return \"Apple M1 Pro\";\n        case 6001: return \"Apple M1 Max\";\n        case 6002: return \"Apple M1 Ultra\";\n        case 8112: return \"Apple M2\";\n        case 6020: return \"Apple M2 Pro\";\n        case 6021: return \"Apple M2 Max\";\n        case 6022: return \"Apple M2 Ultra\";\n        case 8122: return \"Apple M3\";\n        case 6030: return \"Apple M3 Pro\";\n        case 6031:\n        case 6034: return \"Apple M3 Max\";\n        case 8132: return \"Apple M4\";\n        case 6040: return \"Apple M4 Pro\";\n        case 6041: return \"Apple M4 Max\";\n        default: return NULL;\n    }\n}\n\nconst char* ffCPUQualcommCodeToName(uint32_t code)\n{\n    // https://github.com/AsahiLinux/docs/wiki/Codenames\n    switch (code)\n    {\n        case 7180: return \"Qualcomm Snapdragon 7c\";\n        case 7280: return \"Qualcomm Snapdragon 7c+ Gen 3\";\n        case 8180: return \"Qualcomm Snapdragon 8cx Gen 2 5G\";\n        case 8280: return \"Qualcomm Snapdragon 8cx Gen 3\";\n        default: return NULL;\n    }\n}\n\n#if defined(__x86_64__) || defined(__i386__)\n\n#include <cpuid.h>\n\nvoid ffCPUDetectByCpuid(FFCPUResult* cpu)\n{\n    uint32_t eax = 0, ebx = 0, ecx = 0, edx = 0;\n    if (__get_cpuid(0x16, &eax, &ebx, &ecx, &edx))\n    {\n        // WARNING: CPUID may report frequencies of efficient cores\n        // cpuid returns 0 MHz when hypervisor is enabled\n        if (eax) cpu->frequencyBase = eax;\n        if (ebx) cpu->frequencyMax = ebx;\n    }\n\n    if (__get_cpuid(1, &eax, &ebx, &ecx, &edx))\n    {\n        // Feature tests (leaf1.ecx, leaf7.ebx)\n        bool sse2     = (ecx & bit_SSE2) != 0;\n        bool sse4_2   = (ecx & bit_SSE4_2) != 0;\n        bool pclmul   = (ecx & bit_PCLMUL) != 0;\n        bool popcnt   = (ecx & bit_POPCNT) != 0;\n        bool fma      = (ecx & bit_FMA) != 0;\n        bool osxsave  = (ecx & bit_OSXSAVE) != 0;\n\n        unsigned int eax7 = 0, ebx7 = 0, ecx7 = 0, edx7 = 0;\n        __get_cpuid_count(7, 0, &eax7, &ebx7, &ecx7, &edx7);\n\n        bool avx2     = (ebx7 & bit_AVX2) != 0;\n        bool bmi2     = (ebx7 & bit_BMI2) != 0;\n        bool avx512f  = (ebx7 & bit_AVX512F) != 0;\n        bool avx512bw = (ebx7 & bit_AVX512BW) != 0;\n        bool avx512dq = (ebx7 & bit_AVX512DQ) != 0;\n\n        // OS support for AVX/AVX512: check XGETBV (requires OSXSAVE)\n        bool avx_os    = false;\n        bool avx512_os = false;\n        if (osxsave)\n        {\n            __asm__ __volatile__(\n                \"xgetbv\"\n                : \"=a\"(eax), \"=d\"(edx)\n                : \"c\"(0)\n                :\n            );\n            uint64_t xcr0 = ((uint64_t)edx << 32) | eax;\n\n            // AVX requires XCR0[1:2] == 11b (XMM and YMM state)\n            avx_os = (xcr0 & 0x6ULL) == 0x6ULL;\n            // AVX512 requires XCR0[7,5,6] etc. common mask 0xE6 (bits 1,2,5,6,7)\n            avx512_os = (xcr0 & 0xE6ULL) == 0xE6ULL;\n        }\n\n        cpu->march = \"unknown\";\n        if (avx512f && avx512bw && avx512dq && avx512_os) cpu->march = \"x86_64-v4\";\n        else if (avx2 && fma && bmi2 && avx_os) cpu->march = \"x86_64-v3\";\n        else if (sse4_2 && popcnt && pclmul) cpu->march = \"x86_64-v2\";\n        else if (sse2) cpu->march = \"x86_64-v1\";\n    }\n}\n\n#elif defined(__aarch64__)\n\n// This is not accurate because a lot of flags are optional from old versions\n// https://developer.arm.com/documentation/109697/2025_06/Feature-descriptions?lang=en\n// https://en.wikipedia.org/wiki/AArch64#ARM-A_(application_architecture)\n// Worth noting: Apple M1 is marked as ARMv8.5-A on Wikipedia, but it lacks BTI (mandatory in v8.5)\n\n#ifdef __linux__\n#include \"common/io.h\"\n#include <elf.h>\n// #include <asm/hwcap.h>\n\nvoid ffCPUDetectByCpuid(FFCPUResult* cpu)\n{\n    char buf[PROC_FILE_BUFFSIZ];\n    ssize_t nRead = ffReadFileData(\"/proc/self/auxv\", ARRAY_SIZE(buf), buf);\n\n    if (nRead < (ssize_t) sizeof(Elf64_auxv_t)) return;\n\n    uint64_t hwcap = 0, hwcap2 = 0;\n\n    for (Elf64_auxv_t* auxv = (Elf64_auxv_t*)buf; (char*)auxv < buf + nRead; ++auxv)\n    {\n        if (auxv->a_type == AT_HWCAP)\n        {\n            hwcap = auxv->a_un.a_val;\n        }\n        else if (auxv->a_type == AT_HWCAP2)\n        {\n            hwcap2 = auxv->a_un.a_val;\n        }\n    }\n\n    if (!hwcap) return;\n\n    cpu->march = \"unknown\";\n\n    // ARMv8-A\n    bool has_fp = (hwcap & (1 << 0) /* HWCAP_FP */) != 0;\n    bool has_asimd = (hwcap & (1 << 1) /* HWCAP_ASIMD */) != 0;\n\n    // ARMv8.1-A\n    bool has_atomics = (hwcap & (1 << 8) /* HWCAP_ATOMICS */) != 0; // optional from v8.0\n    bool has_crc32 = (hwcap & (1 << 7) /* HWCAP_CRC32 */) != 0; // optional from v8.0\n    bool has_asimdrdm = (hwcap & (1 << 12) /* HWCAP_ASIMDRDM */) != 0; // optional from v8.0\n\n    // ARMv8.2-A\n    bool has_fphp = (hwcap & (1 << 9) /* HWCAP_FPHP */) != 0; // optional\n    bool has_dcpop = (hwcap & (1 << 16) /* HWCAP_DCPOP */) != 0; // DC CVAP, optional from v8.1\n\n    // ARMv8.3-A\n    bool has_paca = (hwcap & (1 << 30) /* HWCAP_PACA */) != 0; // optional from v8.2\n    bool has_lrcpc = (hwcap & (1 << 15) /* HWCAP_LRCPC */) != 0; // optional from v8.2\n    bool has_fcma = (hwcap & (1 << 14) /* HWCAP_FCMA */) != 0; // optional from v8.2\n    bool has_jscvt = (hwcap & (1 << 13) /* HWCAP_JSCVT */) != 0; // optional from v8.2\n\n    // ARMv8.4-A\n    bool has_dit = (hwcap & (1 << 24) /* HWCAP_DIT */) != 0; // optional from v8.3\n    bool has_flagm = (hwcap & (1 << 27) /* HWCAP_FLAGM */) != 0; // optional from v8.1\n    bool has_ilrcpc = (hwcap & (1 << 26) /* HWCAP_ILRCPC */) != 0; // optional from v8.2\n\n    // ARMv8.5-A\n    bool has_bti = (hwcap2 & (1 << 17) /* HWCAP2_BTI */) != 0; // optional from v8.4\n    bool has_sb = (hwcap & (1 << 29) /* HWCAP_SB */) != 0; // optional from v8.0\n    bool has_dcpodp = (hwcap2 & (1 << 0) /* HWCAP2_DCPODP */) != 0; // optional from v8.1\n    bool has_flagm2 = (hwcap2 & (1 << 7) /* HWCAP2_FLAGM2 */) != 0; // optional from v8.4\n    bool has_frint = (hwcap2 & (1 << 8) /* HWCAP2_FRINT */) != 0; // optional from v8.4\n\n    // ARMv9.0-A\n    bool has_sve2 = (hwcap2 & (1 << 1) /* HWCAP2_SVE2 */) != 0;\n\n    // ARMv9.1-A\n    // ARMv8.6-A\n    bool has_bf16 = (hwcap2 & (1 << 14) /* HWCAP2_BF16 */) != 0; // optional from v8.2\n    bool has_i8mm = (hwcap2 & (1 << 13) /* HWCAP2_I8MM */) != 0; // optional from v8.1\n\n    // ARMv8.7-A\n    bool has_afp = (hwcap2 & (1 << 20) /* HWCAP2_AFP */) != 0; // optional from v8.6\n\n    // ARMv9.2-A\n    bool has_sme = (hwcap2 & (1 << 23) /* HWCAP2_SME */) != 0;\n\n    // ARMv9.3-A\n    bool has_sme2 = (hwcap2 & (1UL << 37) /* HWCAP2_SME2 */) != 0; // optional from v9.2\n\n    // ARMv8.8-A\n    bool has_mops = (hwcap2 & (1UL << 43) /* HWCAP2_MOPS */) != 0; // optional from v8.7\n\n    // ARMv8.9-A\n    bool has_cssc = (hwcap2 & (1UL << 34) /* HWCAP2_CSSC */) != 0; // optional from v8.7\n\n    // ARMv9.4-A\n    bool has_sme2p1 = (hwcap2 & (1UL << 38) /* HWCAP2_SME2P1 */) != 0; // optional from v9.2\n\n    // ARMv9.5-A\n    bool has_f8e4m3 = (hwcap2 & (1UL << 55) /* HWCAP2_F8E4M3 */) != 0; // optional from v9.2\n    bool has_f8e5m2 = (hwcap2 & (1UL << 56) /* HWCAP2_F8E5M2 */) != 0; // optional from v9.2\n\n    // ARMv9.6-A\n    bool has_cmpbr = (hwcap & (1UL << 33) /* HWCAP_CMPBR */) != 0; // optional from v9.5\n    bool has_fprcvt = (hwcap & (1UL << 34) /* HWCAP_FPRCVT */) != 0; // optional from v9.5\n\n    if (has_sve2 || has_sme) {\n        // ARMv9\n        if (has_cmpbr && has_fprcvt) {\n            cpu->march = \"ARMv9.6-A\";\n        } else if (has_f8e5m2 && has_f8e4m3) {\n            cpu->march = \"ARMv9.5-A\";\n        } else if (has_sme2p1) {\n            cpu->march = \"ARMv9.4-A\";\n        } else if (has_sme2) {\n            cpu->march = \"ARMv9.3-A\";\n        } else if (has_sme) {\n            cpu->march = \"ARMv9.2-A\";\n        } else if (has_i8mm && has_bf16) {\n            cpu->march = \"ARMv9.1-A\";\n        } else {\n            cpu->march = \"ARMv9.0-A\";\n        }\n    } else {\n        // ARMv8\n        if (has_cssc) {\n            cpu->march = \"ARMv8.9-A\";\n        } else if (has_mops) {\n            cpu->march = \"ARMv8.8-A\";\n        } else if (has_afp) {\n            cpu->march = \"ARMv8.7-A\";\n        } else if (has_i8mm && has_bf16) {\n            cpu->march = \"ARMv8.6-A\";\n        } else if (has_bti && has_sb && has_dcpodp && has_flagm2 && has_frint) {\n            cpu->march = \"ARMv8.5-A\";\n        } else if (has_dit && has_flagm && has_ilrcpc) {\n            cpu->march = \"ARMv8.4-A\";\n        } else if (has_paca && has_lrcpc && has_fcma && has_jscvt) {\n            cpu->march = \"ARMv8.3-A\";\n        } else if (has_fphp && has_dcpop) {\n            cpu->march = \"ARMv8.2-A\";\n        } else if (has_atomics && has_crc32 && has_asimdrdm) {\n            cpu->march = \"ARMv8.1-A\";\n        } else if (has_asimd && has_fp) {\n            cpu->march = \"ARMv8-A\";\n        }\n    }\n}\n#elif __APPLE__\n#include <sys/sysctl.h>\n// #include <arm/cpu_capabilities_public.h> // Not available in macOS 14-\n\nvoid ffCPUDetectByCpuid(FFCPUResult* cpu)\n{\n    uint64_t caps[2] = {0}; // 80-bit capability mask, split into two 64-bit values\n    size_t size = sizeof(caps);\n\n    if (sysctlbyname(\"hw.optional.arm.caps\", caps, &size, NULL, 0) != 0) return;\n\n    // Helper macro to test bit in 80-bit capability mask\n    #define FF_HAS_CAP(bit) \\\n        (((bit) < 64) ? ((caps[0] >> (bit)) & 1ULL) : ((caps[1] >> ((bit) - 64U)) & 1ULL))\n\n    cpu->march = \"unknown\";\n\n    // ARMv8-A\n    bool has_fp        = FF_HAS_CAP(50); /* CAP_BIT_AdvSIMD_HPFPCvt */ // Full FP16 support (implies FP/ASIMD)\n    bool has_asimd     = FF_HAS_CAP(49); /* CAP_BIT_AdvSIMD */         // Advanced SIMD (NEON)\n\n    // ARMv8.1-A\n    bool has_lse       = FF_HAS_CAP(6);  /* CAP_BIT_FEAT_LSE */        // Large System Extensions, optional in v8.0\n    bool has_crc32     = FF_HAS_CAP(51); /* CAP_BIT_FEAT_CRC32 */      // CRC32 instructions, optional in v8.0\n    bool has_rdm       = FF_HAS_CAP(5);  /* CAP_BIT_FEAT_RDM */        // AdvSIMD rounding double multiply accumulate, optional in v8.0\n\n    // ARMv8.2-A\n    bool has_fp16      = FF_HAS_CAP(34); /* CAP_BIT_FEAT_FP16 */       // Half-precision FP support, optional\n    bool has_dpb       = FF_HAS_CAP(22); /* CAP_BIT_FEAT_DPB */        // DC CVAP, optional from v8.1\n\n    // ARMv8.3-A\n    bool has_pauth     = FF_HAS_CAP(19); /* CAP_BIT_FEAT_PAuth */      // Pointer Authentication (PAC), optional from v8.2\n    bool has_lrcpc     = FF_HAS_CAP(15); /* CAP_BIT_FEAT_LRCPC */      // LDAPR/LR with RCPC semantics, optional from v8.2\n    bool has_fcma      = FF_HAS_CAP(17); /* CAP_BIT_FEAT_FCMA */       // Complex number multiply-add, optional from v8.2\n    bool has_jscvt     = FF_HAS_CAP(18); /* CAP_BIT_FEAT_JSCVT */      // JavaScript-style conversion (FJCVTZS), optional from v8.2\n\n    // ARMv8.4-A\n    bool has_lse2      = FF_HAS_CAP(30); /* CAP_BIT_FEAT_LSE2 */       // Large System Extensions version 2, optional from v8.2\n    bool has_dit       = FF_HAS_CAP(33); /* CAP_BIT_FEAT_DIT */        // Data Independent Timing, optional from v8.3\n    bool has_flagm     = FF_HAS_CAP(0);  /* CAP_BIT_FEAT_FlagM */      // Flag manipulation (FMOV/FCVT), optional from v8.1\n    bool has_lrcpc2    = FF_HAS_CAP(16); /* CAP_BIT_FEAT_LRCPC2 */     // Enhanced RCPC (LDAPUR/LDAPST), optional from v8.2\n\n    // ARMv8.5-A\n    bool has_bti       = FF_HAS_CAP(36); /* CAP_BIT_FEAT_BTI */        // Branch Target Identification, optional from v8.4\n    bool has_sb        = FF_HAS_CAP(13); /* CAP_BIT_FEAT_SB */         // Speculative Barrier, optional from v8.0\n    bool has_dpb2      = FF_HAS_CAP(23); /* CAP_BIT_FEAT_DPB2 */       // DC CVADP (DPB2), optional from v8.1\n    bool has_flagm2    = FF_HAS_CAP(1);  /* CAP_BIT_FEAT_FlagM2 */     // Enhanced FlagM, optional from v8.4\n    bool has_frintts   = FF_HAS_CAP(14); /* CAP_BIT_FEAT_FRINTTS */    // Floating-point to integer instructions, optional from v8.4\n\n    // ARMv9.0-A\n    bool has_sve2      = false;                                        // Not exposed and not supported by Apple M4\n\n    // ARMv9.1-A\n    // ARMv8.6-A\n    bool has_bf16      = FF_HAS_CAP(24); /* CAP_BIT_FEAT_BF16 */       // Brain float16, optional from v8.2\n    bool has_i8mm      = FF_HAS_CAP(25); /* CAP_BIT_FEAT_I8MM */       // Int8 Matrix Multiply, optional from v8.1\n\n    // ARMv8.7-A\n    bool has_afp       = FF_HAS_CAP(29); /* CAP_BIT_FEAT_AFP */        // Alternate FP16 (FEXPA), optional from v8.6\n\n    // ARMv9.2-A\n    bool has_sme       = FF_HAS_CAP(40); /* CAP_BIT_FEAT_SME */        // Scalable Matrix Extension, optional from v9.2\n\n    // ARMv9.3-A\n    bool has_sme2      = FF_HAS_CAP(41); /* CAP_BIT_FEAT_SME2 */       // SME2, optional from v9.2\n\n    // ARMv8.8-A\n    bool has_hbc       = FF_HAS_CAP(64); /* CAP_BIT_FEAT_HBC */        // Hinted conditional branches, optional from v8.7\n\n    // ARMv8.9-A\n    bool has_cssc      = FF_HAS_CAP(67); /* CAP_BIT_FEAT_CSSC */       // Common Short String Compare, optional from v8.7\n\n    // ARMv9.4-A+ are not exposed yet\n\n    if (has_sve2 || has_sme) {\n        // ARMv9 family\n        if (has_sme2) {\n            cpu->march = \"ARMv9.3-A\";\n        } else if (has_sme) {\n            cpu->march = \"ARMv9.2-A\";\n        } else if (has_i8mm && has_bf16) {\n            cpu->march = \"ARMv9.1-A\";\n        } else {\n            cpu->march = \"ARMv9.0-A\";\n        }\n    } else {\n        // ARMv8 family\n        if (has_cssc) {\n            cpu->march = \"ARMv8.9-A\";\n        } else if (has_hbc) {\n            cpu->march = \"ARMv8.8-A\";\n        } else if (has_afp) {\n            cpu->march = \"ARMv8.7-A\";\n        } else if (has_i8mm && has_bf16) {\n            cpu->march = \"ARMv8.6-A\";\n        } else if (has_bti && has_sb && has_dpb2 && has_flagm2 && has_frintts) {\n            cpu->march = \"ARMv8.5-A\";\n        } else if (has_lse2 && has_dit && has_flagm && has_lrcpc2) {\n            cpu->march = \"ARMv8.4-A\";\n        } else if (has_pauth && has_lrcpc && has_fcma && has_jscvt) {\n            cpu->march = \"ARMv8.3-A\";\n        } else if (has_fp16 && has_dpb) {\n            cpu->march = \"ARMv8.2-A\";\n        } else if (has_lse && has_crc32 && has_rdm) {\n            cpu->march = \"ARMv8.1-A\";\n        } else if (has_asimd && has_fp) {\n            cpu->march = \"ARMv8-A\";\n        }\n    }\n\n    #undef HAS_CAP\n}\n#elif _WIN32\n#include <processthreadsapi.h>\n\n// Missing from winnt.h of MinGW-w64\n#define PF_ARM_LSE2_AVAILABLE                       62\n#define PF_RESERVED_FEATURE                         63\n#define PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE          64\n#define PF_ARM_SHA512_INSTRUCTIONS_AVAILABLE        65\n#define PF_ARM_V82_I8MM_INSTRUCTIONS_AVAILABLE      66\n#define PF_ARM_V82_FP16_INSTRUCTIONS_AVAILABLE      67\n#define PF_ARM_V86_BF16_INSTRUCTIONS_AVAILABLE      68\n#define PF_ARM_V86_EBF16_INSTRUCTIONS_AVAILABLE     69\n#define PF_ARM_SME_INSTRUCTIONS_AVAILABLE           70\n#define PF_ARM_SME2_INSTRUCTIONS_AVAILABLE          71\n#define PF_ARM_SME2_1_INSTRUCTIONS_AVAILABLE        72\n#define PF_ARM_SME2_2_INSTRUCTIONS_AVAILABLE        73\n#define PF_ARM_SME_AES_INSTRUCTIONS_AVAILABLE       74\n#define PF_ARM_SME_SBITPERM_INSTRUCTIONS_AVAILABLE  75\n#define PF_ARM_SME_SF8MM4_INSTRUCTIONS_AVAILABLE    76\n#define PF_ARM_SME_SF8MM8_INSTRUCTIONS_AVAILABLE    77\n#define PF_ARM_SME_SF8DP2_INSTRUCTIONS_AVAILABLE    78\n#define PF_ARM_SME_SF8DP4_INSTRUCTIONS_AVAILABLE    79\n#define PF_ARM_SME_SF8FMA_INSTRUCTIONS_AVAILABLE    80\n#define PF_ARM_SME_F8F32_INSTRUCTIONS_AVAILABLE     81\n#define PF_ARM_SME_F8F16_INSTRUCTIONS_AVAILABLE     82\n#define PF_ARM_SME_F16F16_INSTRUCTIONS_AVAILABLE    83\n#define PF_ARM_SME_B16B16_INSTRUCTIONS_AVAILABLE    84\n#define PF_ARM_SME_F64F64_INSTRUCTIONS_AVAILABLE    85\n#define PF_ARM_SME_I16I64_INSTRUCTIONS_AVAILABLE    86\n#define PF_ARM_SME_LUTv2_INSTRUCTIONS_AVAILABLE     87\n#define PF_ARM_SME_FA64_INSTRUCTIONS_AVAILABLE      88\n\nvoid ffCPUDetectByCpuid(FFCPUResult* cpu)\n{\n    // ARMv8-A\n    bool has_vfp       = IsProcessorFeaturePresent(PF_ARM_VFP_32_REGISTERS_AVAILABLE); // Implies basic FP support\n    bool has_neon      = IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE); // NEON (ASIMD)\n\n    // ARMv8.1-A\n    bool has_atomics   = IsProcessorFeaturePresent(PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE); // LSE atomics\n    bool has_crc32     = IsProcessorFeaturePresent(PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE); // CRC32\n\n    // ARMv8.2-A\n    bool has_fp16      = IsProcessorFeaturePresent(PF_ARM_V82_FP16_INSTRUCTIONS_AVAILABLE); // Half-precision FP\n\n    // ARMv8.3-A\n    bool has_lrcpc     = IsProcessorFeaturePresent(PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE); // LDAPR/LR with RCPC semantics\n    bool has_jscvt     = IsProcessorFeaturePresent(PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE); // FJCVTZS\n\n    // ARMv8.4-A\n    // My CPU (Apple M1 Pro in VM) does support LSE2, but Windows doesn't detect it for some reason\n    bool has_lse2      = IsProcessorFeaturePresent(PF_ARM_LSE2_AVAILABLE); // Large System Extensions version 2, optional from v8.2\n    bool has_dp        = IsProcessorFeaturePresent(PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE); // DotProd, optional from v8.1 (*)\n\n    // ARMv9.0-A\n    bool has_sve2      = IsProcessorFeaturePresent(PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE); // SVE2\n\n    // ARMv9.1-A\n    // ARMv8.6-A\n    bool has_bf16      = IsProcessorFeaturePresent(PF_ARM_V86_BF16_INSTRUCTIONS_AVAILABLE); // BF16, optional from v8.2\n    bool has_i8mm      = IsProcessorFeaturePresent(PF_ARM_V82_I8MM_INSTRUCTIONS_AVAILABLE); // Int8 matrix multiply, optional from v8.2\n\n    // ARMv8.7-A\n    bool has_ebf16     = IsProcessorFeaturePresent(PF_ARM_V86_EBF16_INSTRUCTIONS_AVAILABLE); // Extended BFloat16 behaviors, optional from v8.2\n\n    // ARMv9.2-A\n    bool has_sme       = IsProcessorFeaturePresent(PF_ARM_SME_INSTRUCTIONS_AVAILABLE); // SME\n\n    // ARMv9.3-A\n    bool has_sme2      = IsProcessorFeaturePresent(PF_ARM_SME2_INSTRUCTIONS_AVAILABLE); // SME2\n\n    // ARMv9.4-A\n    bool has_sme2p1    = IsProcessorFeaturePresent(PF_ARM_SME2_1_INSTRUCTIONS_AVAILABLE); // SME2.1\n\n\n    if (has_sve2 || has_sme)\n    {\n        // ARMv9 family\n        if (has_sme2p1) {\n            cpu->march = \"ARMv9.4-A\";\n        } else if (has_sme2) {\n            cpu->march = \"ARMv9.3-A\";\n        } else if (has_sme) {\n            cpu->march = \"ARMv9.2-A\";\n        } else if (has_i8mm && has_bf16) {\n            cpu->march = \"ARMv9.1-A\";\n        } else {\n            cpu->march = \"ARMv9.0-A\";\n        }\n    }\n    else\n    {\n        // ARMv8 family\n        if (has_ebf16) {\n            cpu->march = \"ARMv8.7-A\";\n        } else if (has_i8mm && has_bf16) {\n            cpu->march = \"ARMv8.6-A\";\n        } else if (has_dp && has_lse2) {\n            cpu->march = \"ARMv8.4-A\";\n        } else if (has_lrcpc && has_jscvt) {\n            cpu->march = \"ARMv8.3-A\";\n        } else if (has_fp16) {\n            cpu->march = \"ARMv8.2-A\";\n        } else if (has_atomics && has_crc32) {\n            cpu->march = \"ARMv8.1-A\";\n        } else if (has_neon && has_vfp) {\n            cpu->march = \"ARMv8-A\";\n        }\n    }\n}\n#else\nvoid ffCPUDetectByCpuid(FF_MAYBE_UNUSED FFCPUResult* cpu)\n{\n    // Unsupported system\n}\n#endif\n\n#else\n\nvoid ffCPUDetectByCpuid(FF_MAYBE_UNUSED FFCPUResult* cpu)\n{\n    // Unsupported architecture\n}\n\n#endif\n"
  },
  {
    "path": "src/detection/cpu/cpu.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/cpu/option.h\"\n\n#define FF_CPU_TEMP_UNSET (-DBL_MAX)\n\ntypedef struct FFCPUCore\n{\n    uint32_t freq;\n    uint32_t count;\n} FFCPUCore;\n\ntypedef struct FFCPUResult\n{\n    FFstrbuf name;\n    FFstrbuf vendor;\n    const char* march; // Microarchitecture\n\n    uint16_t packages;\n    uint16_t coresPhysical;\n    uint16_t coresLogical;\n    uint16_t coresOnline;\n    uint16_t numaNodes;\n\n    uint32_t frequencyBase; // GHz\n    uint32_t frequencyMax; // GHz\n\n    FFCPUCore coreTypes[16]; // number of P cores, E cores, etc.\n\n    double temperature;\n} FFCPUResult;\n\nconst char* ffDetectCPU(const FFCPUOptions* options, FFCPUResult* cpu);\nconst char* ffCPUAppleCodeToName(uint32_t code);\nconst char* ffCPUQualcommCodeToName(uint32_t code);\nvoid ffCPUDetectByCpuid(FFCPUResult* cpu);\n"
  },
  {
    "path": "src/detection/cpu/cpu_apple.c",
    "content": "#include \"cpu.h\"\n#include \"common/sysctl.h\"\n#include \"common/apple/smc_temps.h\"\n#include \"common/stringUtils.h\"\n\nstatic double detectCpuTemp(const FFCPUOptions* options, const FFstrbuf* cpuName)\n{\n    double result = 0;\n\n    const char* error = NULL;\n\n    if (options->tempSensor.length)\n    {\n        error = ffDetectSmcSpecificTemp(options->tempSensor.chars, &result);\n    }\n    else\n    {\n        if (ffStrbufStartsWithS(cpuName, \"Apple M\"))\n        {\n            switch (strtol(cpuName->chars + strlen(\"Apple M\"), NULL, 10))\n            {\n                case 1: error = ffDetectSmcTemps(FF_TEMP_CPU_M1X, &result); break;\n                case 2: error = ffDetectSmcTemps(FF_TEMP_CPU_M2X, &result); break;\n                case 3: error = ffDetectSmcTemps(FF_TEMP_CPU_M3X, &result); break;\n                case 4: error = ffDetectSmcTemps(FF_TEMP_CPU_M4X, &result); break;\n                default: error = \"Unsupported Apple Silicon CPU\";\n            }\n        }\n        else // PPC?\n            error = ffDetectSmcTemps(FF_TEMP_CPU_X64, &result);\n    }\n\n    if (error)\n        return FF_CPU_TEMP_UNSET;\n\n    return result;\n}\n\n#ifdef __aarch64__\n#include \"common/apple/cf_helpers.h\"\n\n#include <IOKit/IOKitLib.h>\n\nstatic const char* detectFrequency(FFCPUResult* cpu)\n{\n    // https://github.com/giampaolo/psutil/pull/2222/files\n\n    FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDevice = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceNameMatching(\"pmgr\"));\n    if (!entryDevice)\n        return \"IOServiceGetMatchingService() failed\";\n\n    if (!IOObjectConformsTo(entryDevice, \"AppleARMIODevice\"))\n        return \"\\\"pmgr\\\" should conform to \\\"AppleARMIODevice\\\"\";\n\n    FF_CFTYPE_AUTO_RELEASE CFDataRef freqProperty = (CFDataRef) IORegistryEntryCreateCFProperty(entryDevice, CFSTR(\"voltage-states5-sram\"), kCFAllocatorDefault, kNilOptions);\n    if (!freqProperty || CFGetTypeID(freqProperty) != CFDataGetTypeID())\n        return \"\\\"voltage-states5-sram\\\" in \\\"pmgr\\\" is not found\";\n\n    // voltage-states5-sram stores supported <frequency / voltage> pairs of pcores from the lowest to the highest\n    // voltage-states1-sram stores ecores'\n    CFIndex propLength = CFDataGetLength(freqProperty);\n    if (propLength == 0 || propLength % (CFIndex) sizeof(uint32_t) * 2 != 0)\n        return \"Invalid \\\"voltage-states5-sram\\\" length\";\n\n    uint32_t* pStart = (uint32_t*) CFDataGetBytePtr(freqProperty);\n    uint32_t pMax = *pStart;\n    for (CFIndex i = 2; i < propLength / (CFIndex) sizeof(uint32_t) && pStart[i] > 0; i += 2 /* skip voltage */)\n        pMax = pMax > pStart[i] ? pMax : pStart[i];\n\n    if (pMax > 0)\n    {\n        if (pMax > 100000000) // Assume that pMax is in Hz, M1~M3\n            cpu->frequencyMax = pMax / 1000 / 1000;\n        else // Assume that pMax is in kHz, M4 and later (#1394)\n            cpu->frequencyMax = pMax / 1000;\n    }\n\n    return NULL;\n}\n#else\nstatic const char* detectFrequency(FFCPUResult* cpu)\n{\n    cpu->frequencyBase = (uint32_t) (ffSysctlGetInt64(\"hw.cpufrequency\", 0) / 1000 / 1000);\n    cpu->frequencyMax = (uint32_t) (ffSysctlGetInt64(\"hw.cpufrequency_max\", 0) / 1000 / 1000);\n    if(cpu->frequencyBase == 0)\n    {\n        unsigned current = 0;\n        size_t size = sizeof(current);\n        if (sysctl((int[]){ CTL_HW, HW_CPU_FREQ }, 2, &current, &size, NULL, 0) == 0)\n            cpu->frequencyBase = (uint32_t) (current / 1000 / 1000);\n    }\n    return NULL;\n}\n#endif\n\nstatic const char* detectCoreCount(FFCPUResult* cpu)\n{\n    uint32_t nPerfLevels = (uint32_t) ffSysctlGetInt(\"hw.nperflevels\", 0);\n    if (nPerfLevels <= 0) return \"sysctl(hw.nperflevels) failed\";\n\n    char sysctlKey[] = \"hw.perflevelN.logicalcpu\";\n    if (nPerfLevels > ARRAY_SIZE(cpu->coreTypes))\n        nPerfLevels = ARRAY_SIZE(cpu->coreTypes);\n    for (uint32_t i = 0; i < nPerfLevels; ++i)\n    {\n        sysctlKey[strlen(\"hw.perflevel\")] = (char) ('0' + i);\n        cpu->coreTypes[i] = (FFCPUCore) {\n            .freq = nPerfLevels - i,\n            .count = (uint32_t) ffSysctlGetInt(sysctlKey, 0),\n        };\n    }\n    return NULL;\n}\n\nconst char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu)\n{\n    if (ffSysctlGetString(\"machdep.cpu.brand_string\", &cpu->name) != NULL)\n        return \"sysctlbyname(machdep.cpu.brand_string) failed\";\n\n    ffSysctlGetString(\"machdep.cpu.vendor\", &cpu->vendor);\n    cpu->packages = (uint16_t) ffSysctlGetInt(\"hw.packages\", 1);\n    if (cpu->vendor.length == 0 && ffStrbufStartsWithS(&cpu->name, \"Apple \"))\n        ffStrbufAppendS(&cpu->vendor, \"Apple\");\n\n    cpu->coresPhysical = (uint16_t) ffSysctlGetInt(\"hw.physicalcpu_max\", 1);\n    if(cpu->coresPhysical == 1)\n        cpu->coresPhysical = (uint16_t) ffSysctlGetInt(\"hw.physicalcpu\", 1);\n\n    cpu->coresLogical = (uint16_t) ffSysctlGetInt(\"hw.logicalcpu_max\", 1);\n    if(cpu->coresLogical == 1)\n        cpu->coresLogical = (uint16_t) ffSysctlGetInt(\"hw.ncpu\", 1);\n\n    cpu->coresOnline = (uint16_t) ffSysctlGetInt(\"hw.logicalcpu\", 1);\n    if(cpu->coresOnline == 1)\n        cpu->coresOnline = (uint16_t) ffSysctlGetInt(\"hw.activecpu\", 1);\n\n    ffCPUDetectByCpuid(cpu);\n    detectFrequency(cpu);\n    if (options->showPeCoreCount) detectCoreCount(cpu);\n\n    cpu->temperature = options->temp ? detectCpuTemp(options, &cpu->name) : FF_CPU_TEMP_UNSET;\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/cpu/cpu_arm.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\n// https://github.com/util-linux/util-linux/blob/master/sys-utils/lscpu-arm.c\n// We use the util-linux's data but not its code. Call me if it violates util-linux's GPL license.\n\nstatic const char* hwImplId2Vendor(uint32_t implId)\n{\n    switch (implId)\n    {\n    case 0x41: return \"ARM\";\n    case 0x42: return \"Broadcom\";\n    case 0x43: return \"Cavium\";\n    case 0x44: return \"DEC\";\n    case 0x46: return \"FUJITSU\";\n    case 0x48: return \"HiSilicon\";\n    case 0x49: return \"Infineon\";\n    case 0x4d: return \"Motorola\";\n    case 0x4e: return \"NVIDIA\";\n    case 0x50: return \"APM\";\n    case 0x51: return \"Qualcomm\";\n    case 0x53: return \"Samsung\";\n    case 0x56: return \"Marvell\";\n    case 0x61: return \"Apple\";\n    case 0x66: return \"Faraday\";\n    case 0x69: return \"Intel\";\n    case 0x6D: return \"Microsoft\";\n    case 0x70: return \"Phytium\";\n    case 0xc0: return \"Ampere\";\n    default: return \"Unknown\";\n    }\n}\n\nstatic const char* armPartId2name(uint32_t partId)\n{\n    switch (partId)\n    {\n    case 0x810: return \"ARM810\";\n    case 0x920: return \"ARM920\";\n    case 0x922: return \"ARM922\";\n    case 0x926: return \"ARM926\";\n    case 0x940: return \"ARM940\";\n    case 0x946: return \"ARM946\";\n    case 0x966: return \"ARM966\";\n    case 0xa20: return \"ARM1020\";\n    case 0xa22: return \"ARM1022\";\n    case 0xa26: return \"ARM1026\";\n    case 0xb02: return \"ARM11-MPCore\";\n    case 0xb36: return \"ARM1136\";\n    case 0xb56: return \"ARM1156\";\n    case 0xb76: return \"ARM1176\";\n    case 0xc05: return \"Cortex-A5\";\n    case 0xc07: return \"Cortex-A7\";\n    case 0xc08: return \"Cortex-A8\";\n    case 0xc09: return \"Cortex-A9\";\n    case 0xc0d: return \"Cortex-A17\";\t/* Originally A12 */\n    case 0xc0f: return \"Cortex-A15\";\n    case 0xc0e: return \"Cortex-A17\";\n    case 0xc14: return \"Cortex-R4\";\n    case 0xc15: return \"Cortex-R5\";\n    case 0xc17: return \"Cortex-R7\";\n    case 0xc18: return \"Cortex-R8\";\n    case 0xc20: return \"Cortex-M0\";\n    case 0xc21: return \"Cortex-M1\";\n    case 0xc23: return \"Cortex-M3\";\n    case 0xc24: return \"Cortex-M4\";\n    case 0xc27: return \"Cortex-M7\";\n    case 0xc60: return \"Cortex-M0+\";\n    case 0xd01: return \"Cortex-A32\";\n    case 0xd02: return \"Cortex-A34\";\n    case 0xd03: return \"Cortex-A53\";\n    case 0xd04: return \"Cortex-A35\";\n    case 0xd05: return \"Cortex-A55\";\n    case 0xd06: return \"Cortex-A65\";\n    case 0xd07: return \"Cortex-A57\";\n    case 0xd08: return \"Cortex-A72\";\n    case 0xd09: return \"Cortex-A73\";\n    case 0xd0a: return \"Cortex-A75\";\n    case 0xd0b: return \"Cortex-A76\";\n    case 0xd0c: return \"Neoverse-N1\";\n    case 0xd0d: return \"Cortex-A77\";\n    case 0xd0e: return \"Cortex-A76AE\";\n    case 0xd13: return \"Cortex-R52\";\n    case 0xd14: return \"Cortex-R82AE\";\n    case 0xd15: return \"Cortex-R82\";\n    case 0xd16: return \"Cortex-R52+\";\n    case 0xd20: return \"Cortex-M23\";\n    case 0xd21: return \"Cortex-M33\";\n    case 0xd24: return \"Cortex-M52\";\n    case 0xd22: return \"Cortex-M55\";\n    case 0xd23: return \"Cortex-M85\";\n    case 0xd40: return \"Neoverse-V1\";\n    case 0xd41: return \"Cortex-A78\";\n    case 0xd42: return \"Cortex-A78AE\";\n    case 0xd43: return \"Cortex-A65AE\";\n    case 0xd44: return \"Cortex-X1\";\n    case 0xd46: return \"Cortex-A510\";\n    case 0xd47: return \"Cortex-A710\";\n    case 0xd48: return \"Cortex-X2\";\n    case 0xd49: return \"Neoverse-N2\";\n    case 0xd4a: return \"Neoverse-E1\";\n    case 0xd4b: return \"Cortex-A78C\";\n    case 0xd4c: return \"Cortex-X1C\";\n    case 0xd4d: return \"Cortex-A715\";\n    case 0xd4e: return \"Cortex-X3\";\n    case 0xd4f: return \"Neoverse-V2\";\n    case 0xd80: return \"Cortex-A520\";\n    case 0xd81: return \"Cortex-A720\";\n    case 0xd82: return \"Cortex-X4\";\n    case 0xd83: return \"Neoverse-V3AE\";\n    case 0xd84: return \"Neoverse-V3\";\n    case 0xd85: return \"Cortex-X925\";\n    case 0xd87: return \"Cortex-A725\";\n    case 0xd88: return \"Cortex-A520AE\";\n    case 0xd89: return \"Cortex-A720AE\";\n    case 0xd8a: return \"C1-Nano\";\n    case 0xd8b: return \"C1-Pro\";\n    case 0xd8c: return \"C1-Ultra\";\n    case 0xd8e: return \"Neoverse-N3\";\n    case 0xd8f: return \"Cortex-A320\";\n    case 0xd90: return \"C1-Premium\";\n    default: return NULL;\n    }\n}\n\nstatic const char* brcmPartId2name(uint32_t partId)\n{\n    switch (partId)\n    {\n    case 0x0f: return \"Brahma-B15\";\n    case 0x100: return \"Brahma-B53\";\n    case 0x516: return \"ThunderX2\";\n    default: return NULL;\n    }\n}\n\nstatic const char* decPartId2name(uint32_t partId)\n{\n    switch (partId)\n    {\n    case 0xa10: return \"SA110\";\n    case 0xa11: return \"SA1100\";\n    default: return NULL;\n    }\n}\n\nstatic const char* caviumPartId2name(uint32_t partId)\n{\n    switch (partId)\n    {\n    case 0x0a0: return \"ThunderX\";\n    case 0x0a1: return \"ThunderX-88XX\";\n    case 0x0a2: return \"ThunderX-81XX\";\n    case 0x0a3: return \"ThunderX-83XX\";\n    case 0x0af: return \"ThunderX2-99xx\";\n    case 0x0b0: return \"OcteonTX2\";\n    case 0x0b1: return \"OcteonTX2-98XX\";\n    case 0x0b2: return \"OcteonTX2-96XX\";\n    case 0x0b3: return \"OcteonTX2-95XX\";\n    case 0x0b4: return \"OcteonTX2-95XXN\";\n    case 0x0b5: return \"OcteonTX2-95XXMM\";\n    case 0x0b6: return \"OcteonTX2-95XXO\";\n    case 0x0b8: return \"ThunderX3-T110\";\n    default: return NULL;\n    }\n}\n\nstatic const char* apmPartId2name(uint32_t partId)\n{\n    switch (partId)\n    {\n    case 0x000: return \"X-Gene\";\n    default: return NULL;\n    }\n}\n\nstatic const char* qcomPartId2name(uint32_t partId)\n{\n    switch (partId)\n    {\n    case 0x001: return \"Oryon 1\";\n    case 0x002: return \"Oryon 2\";\n    case 0x00f: return \"Scorpion\";\n    case 0x02d: return \"Scorpion\";\n    case 0x04d: return \"Krait\";\n    case 0x06f: return \"Krait\";\n    case 0x201: return \"Kryo\";\n    case 0x205: return \"Kryo\";\n    case 0x211: return \"Kryo\";\n    case 0x800: return \"Falkor-V1/Kryo\";\n    case 0x801: return \"Kryo-V2\";\n    case 0x802: return \"Kryo-3XX-Gold\";\n    case 0x803: return \"Kryo-3XX-Silver\";\n    case 0x804: return \"Kryo-4XX-Gold\";\n    case 0x805: return \"Kryo-4XX-Silver\";\n    case 0xc00: return \"Falkor\";\n    case 0xc01: return \"Saphira\";\n    default: return NULL;\n    }\n}\n\nstatic const char* samsungPartId2name(uint32_t partId)\n{\n    switch (partId)\n    {\n    case 0x001: return \"Exynos-M1\";\n    case 0x002: return \"Exynos-M3\";\n    case 0x003: return \"Exynos-M4\";\n    case 0x004: return \"Exynos-M5\";\n    default: return NULL;\n    }\n}\n\nstatic const char* nvidiaPartId2name(uint32_t partId)\n{\n    switch (partId)\n    {\n    case 0x000: return \"Denver\";\n    case 0x003: return \"Denver-2\";\n    case 0x004: return \"Carmel\";\n    case 0x010: return \"Olympus\";\n    default: return NULL;\n    }\n}\n\nstatic const char* marvellPartId2name(uint32_t partId)\n{\n    switch (partId)\n    {\n    case 0x131: return \"Feroceon-88FR131\";\n    case 0x581: return \"PJ4/PJ4b\";\n    case 0x584: return \"PJ4B-MP\";\n    default: return NULL;\n    }\n}\n\nstatic const char* applePartId2name(uint32_t partId)\n{\n    switch (partId)\n    {\n    case 0x000: return \"Swift\";\n    case 0x001: return \"Cyclone\";\n    case 0x002: return \"Typhoon\";\n    case 0x003: return \"Typhoon/Capri\";\n    case 0x004: return \"Twister\";\n    case 0x005: return \"Twister/Elba/Malta\";\n    case 0x006: return \"Hurricane\";\n    case 0x007: return \"Hurricane/Myst\";\n    case 0x008: return \"Monsoon\";\n    case 0x009: return \"Mistral\";\n    case 0x00b: return \"Vortex\";\n    case 0x00c: return \"Tempest\";\n    case 0x00f: return \"Tempest-M9\";\n    case 0x010: return \"Vortex/Aruba\";\n    case 0x011: return \"Tempest/Aruba\";\n    case 0x012: return \"Lightning\";\n    case 0x013: return \"Thunder\";\n    case 0x020: return \"Icestorm-A14\";\n    case 0x021: return \"Firestorm-A14\";\n    case 0x022: return \"Icestorm-M1\";\n    case 0x023: return \"Firestorm-M1\";\n    case 0x024: return \"Icestorm-M1-Pro\";\n    case 0x025: return \"Firestorm-M1-Pro\";\n    case 0x026: return \"Thunder-M10\";\n    case 0x028: return \"Icestorm-M1-Max\";\n    case 0x029: return \"Firestorm-M1-Max\";\n    case 0x030: return \"Blizzard-A15\";\n    case 0x031: return \"Avalanche-A15\";\n    case 0x032: return \"Blizzard-M2\";\n    case 0x033: return \"Avalanche-M2\";\n    case 0x034: return \"Blizzard-M2-Pro\";\n    case 0x035: return \"Avalanche-M2-Pro\";\n    case 0x036: return \"Sawtooth-A16\";\n    case 0x037: return \"Everest-A16\";\n    case 0x038: return \"Blizzard-M2-Max\";\n    case 0x039: return \"Avalanche-M2-Max\";\n    case 0x046: return \"Sawtooth-M11\";\n    case 0x048: return \"Sawtooth-M3-Max\";\n    case 0x049: return \"Everest-M3-Max\";\n    default: return NULL;\n    }\n}\n\nstatic const char* faradayPartId2name(uint32_t partId)\n{\n    switch (partId)\n    {\n    case 0x526: return \"FA526\";\n    case 0x626: return \"FA626\";\n    default: return NULL;\n    }\n}\n\nstatic const char* intelPartId2name(uint32_t partId)\n{\n    switch (partId)\n    {\n    case 0x200: return \"i80200\";\n    case 0x210: return \"PXA250A\";\n    case 0x212: return \"PXA210A\";\n    case 0x242: return \"i80321-400\";\n    case 0x243: return \"i80321-600\";\n    case 0x290: return \"PXA250B/PXA26x\";\n    case 0x292: return \"PXA210B\";\n    case 0x2c2: return \"i80321-400-B0\";\n    case 0x2c3: return \"i80321-600-B0\";\n    case 0x2d0: return \"PXA250C/PXA255/PXA26x\";\n    case 0x2d2: return \"PXA210C\";\n    case 0x411: return \"PXA27x\";\n    case 0x41c: return \"IPX425-533\";\n    case 0x41d: return \"IPX425-400\";\n    case 0x41f: return \"IPX425-266\";\n    case 0x682: return \"PXA32x\";\n    case 0x683: return \"PXA930/PXA935\";\n    case 0x688: return \"PXA30x\";\n    case 0x689: return \"PXA31x\";\n    case 0xb11: return \"SA1110\";\n    case 0xc12: return \"IPX1200\";\n    default: return NULL;\n    }\n}\n\nstatic const char* fujitsuPartId2name(uint32_t partId)\n{\n    switch (partId)\n    {\n    case 0x001: return \"A64FX\";\n    case 0x003: return \"MONAKA\";\n    default: return NULL;\n    }\n}\n\nstatic const char* hisiPartId2name(uint32_t partId)\n{\n    switch (partId)\n    {\n    case 0xd01: return \"TaiShan-v110\";\t/* used in Kunpeng-920 SoC */\n    case 0xd02: return \"TaiShan-v120\";\t/* used in Kirin 990A and 9000S SoCs */\n    case 0xd40: return \"Cortex-A76\";\t/* HiSilicon uses this ID though advertises A76 */\n    case 0xd41: return \"Cortex-A77\";\t/* HiSilicon uses this ID though advertises A77 */\n    default: return NULL;\n    }\n}\n\nstatic const char* amperePartId2name(uint32_t partId)\n{\n    switch (partId)\n    {\n    case 0xac3: return \"Ampere-1\";\n    case 0xac4: return \"Ampere-1a\";\n    default: return NULL;\n    }\n}\n\nstatic const char* ftPartId2name(uint32_t partId)\n{\n    switch (partId)\n    {\n    case 0x303: return \"FTC310\";\n    case 0x660: return \"FTC660\";\n    case 0x661: return \"FTC661\";\n    case 0x662: return \"FTC662\";\n    case 0x663: return \"FTC663\";\n    case 0x664: return \"FTC664\";\n    case 0x862: return \"FTC862\";\n    default: return NULL;\n    }\n}\n\nstatic const char* msPartId2name(uint32_t partId)\n{\n    switch (partId)\n    {\n    case 0xd49: return \"Azure-Cobalt-100\";\n    default: return NULL;\n    }\n}\n"
  },
  {
    "path": "src/detection/cpu/cpu_bsd.c",
    "content": "#include \"cpu.h\"\n#include \"common/sysctl.h\"\n#include \"common/stringUtils.h\"\n\n#include <sys/param.h>\n#if __has_include(<sys/cpuset.h>)\n    #include <sys/cpuset.h>\n    #define FF_HAVE_CPUSET 1\n#endif\n\nstatic const char* detectCpuTemp(const FFCPUOptions* options, double* current)\n{\n    int temp;\n    if (options->tempSensor.length > 0)\n    {\n        temp = ffSysctlGetInt(options->tempSensor.chars, -999999);\n        if (temp == -999999)\n            return \"ffSysctlGetInt(options->tempSensor) failed\";\n    }\n    else\n    {\n        temp = ffSysctlGetInt(\"dev.cpu.0.temperature\", -999999);\n        if (temp == -999999)\n        {\n            // Thermal zone temperature\n            temp = ffSysctlGetInt(\"hw.acpi.thermal.tz0.temperature\", -999999);\n            if (temp == -999999)\n                return \"ffSysctlGetInt(\\\"dev.cpu.0.temperature\\\" or \\\"hw.acpi.thermal.tz0.temperature\\\") failed\";\n        }\n    }\n\n    // In tenth of degrees Kelvin\n    *current = (double) temp / 10 - 273.15;\n    return NULL;\n}\n\nconst char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu)\n{\n    if (ffSysctlGetString(\"hw.model\", &cpu->name) != NULL)\n        return \"sysctlbyname(hw.model) failed\";\n\n    cpu->coresLogical = (uint16_t) ffSysctlGetInt(\"hw.ncpu\", 1);\n    cpu->coresPhysical = (uint16_t) ffSysctlGetInt(\"kern.smp.cores\", 0);\n    cpu->coresOnline = (uint16_t) ffSysctlGetInt(\"kern.smp.cpus\", cpu->coresLogical);\n\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n    if (ffSysctlGetString(\"kern.sched.topology_spec\", &buffer) == NULL && buffer.length > 0)\n    {\n        // <groups>\n        //  <group level=\"1\" cache-level=\"3\">\n        //   <cpu count=\"4\" mask=\"f,0,0,0\">0, 1, 2, 3</cpu>\n        //   <children>\n        //    <group level=\"2\" cache-level=\"2\">\n        //     <cpu count=\"2\" mask=\"3,0,0,0\">0, 1</cpu>\n        //     <flags><flag name=\"THREAD\">THREAD group</flag><flag name=\"SMT\">SMT group</flag></flags>\n        //    </group>\n        //    <group level=\"2\" cache-level=\"2\">\n        //     <cpu count=\"2\" mask=\"c,0,0,0\">2, 3</cpu>\n        //     <flags><flag name=\"THREAD\">THREAD group</flag><flag name=\"SMT\">SMT group</flag></flags>\n        //    </group>\n        //   </children>\n        //  </group>\n        // </groups>\n        for (char* p = buffer.chars; (p = strstr(p, \"\\n </group>\\n\")); ++p)\n            cpu->packages++;\n    }\n\n#if FF_HAVE_CPUSET && (__x86_64__ || __i386__)\n    // Bind current process to the first two cores, which is *usually* a performance core\n    cpuset_t currentCPU;\n    CPU_ZERO(&currentCPU);\n    CPU_SET(1, &currentCPU);\n    CPU_SET(2, &currentCPU);\n    cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, sizeof(cpuset_t), &currentCPU);\n#endif\n\n    ffCPUDetectByCpuid(cpu);\n\n    uint32_t clockrate = (uint32_t) ffSysctlGetInt(\"hw.clockrate\", 0);\n    if (clockrate > cpu->frequencyBase) cpu->frequencyBase = clockrate;\n\n    for (uint16_t i = 0; i < cpu->coresLogical; ++i)\n    {\n        ffStrbufClear(&buffer);\n        char key[32];\n        snprintf(key, sizeof(key), \"dev.cpu.%u.freq_levels\", i);\n        if (ffSysctlGetString(key, &buffer) == NULL)\n        {\n            if (buffer.length == 0) continue;\n\n            // MHz/Watts pairs like: 2501/32000 2187/27125 2000/24000\n            uint32_t fmax = (uint32_t) strtoul(buffer.chars, NULL, 10);\n            if (cpu->frequencyMax < fmax) cpu->frequencyMax = fmax;\n        }\n        else\n            break;\n    }\n\n    cpu->temperature = FF_CPU_TEMP_UNSET;\n\n    if (options->temp) detectCpuTemp(options, &cpu->temperature);\n\n    cpu->numaNodes = (uint16_t) ffSysctlGetInt(\"vm.ndomains\", 0);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/cpu/cpu_haiku.c",
    "content": "#include \"cpu.h\"\n#include \"common/mallocHelper.h\"\n\n#include <OS.h>\n#include <private/shared/cpu_type.h>\n\nconst char* ffDetectCPUImpl(FF_MAYBE_UNUSED const FFCPUOptions* options, FFCPUResult* cpu)\n{\n    system_info sysInfo;\n    if (get_system_info(&sysInfo) != B_OK)\n        return \"get_system_info() failed\";\n\n    uint32 topoNodeCount = 0;\n    get_cpu_topology_info(NULL, &topoNodeCount);\n    if (topoNodeCount == 0)\n        return \"get_cpu_topology_info(NULL) failed\";\n\n    FF_AUTO_FREE cpu_topology_node_info* topology = malloc(sizeof(*topology) * topoNodeCount);\n    if (get_cpu_topology_info(topology, &topoNodeCount) != B_OK)\n        return \"get_cpu_topology_info(topology) failed\";\n\n    enum cpu_platform platform = B_CPU_UNKNOWN;\n    enum cpu_vendor cpuVendor = B_CPU_VENDOR_UNKNOWN;\n    uint32 cpuModel = 0, frequency = 0;\n    uint16_t packages = 0, cores = 0;\n\n    for (uint32 i = 0; i < topoNodeCount; i++)\n    {\n        switch (topology[i].type) {\n            case B_TOPOLOGY_ROOT:\n                platform = topology[i].data.root.platform;\n                break;\n\n            case B_TOPOLOGY_PACKAGE:\n                cpuVendor = topology[i].data.package.vendor;\n                ++packages;\n                break;\n\n            case B_TOPOLOGY_CORE:\n                cpuModel = topology[i].data.core.model;\n                uint32_t freq = (uint32_t) (topology[i].data.core.default_frequency / 1000000);\n                frequency = freq > frequency ? freq : frequency;\n                ++cores;\n                break;\n\n            default:\n                break;\n        }\n    }\n\n    const char *model = get_cpu_model_string(platform, cpuVendor, cpuModel);\n    if (model)\n        ffStrbufSetS(&cpu->name, model);\n    else\n        ffStrbufSetF(&cpu->name, \"(Unknown %\" B_PRIx32 \")\", cpuModel);\n    ffStrbufSetS(&cpu->vendor, get_cpu_vendor_string(cpuVendor));\n\n    ffCPUDetectByCpuid(cpu);\n    if (cpu->frequencyBase < frequency) cpu->frequencyBase = frequency;\n    cpu->packages = packages;\n    cpu->coresPhysical = cores;\n    cpu->coresOnline = cpu->coresLogical = (uint16_t) sysInfo.cpu_count;\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/cpu/cpu_linux.c",
    "content": "#include \"cpu.h\"\n#include \"common/io.h\"\n#include \"common/processing.h\"\n#include \"common/properties.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/stringUtils.h\"\n#include \"common/path.h\"\n\n#include <sys/sysinfo.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <dirent.h>\n#include <fcntl.h>\n\n#define FF_CPUINFO_PATH \"/proc/cpuinfo\"\n\nstatic double readTempFile(int dfd, const char* filename, FFstrbuf* buffer)\n{\n    if (filename ? !ffReadFileBufferRelative(dfd, filename, buffer) : !ffReadFDBuffer(dfd, buffer))\n        return FF_CPU_TEMP_UNSET;\n\n    double value = ffStrbufToDouble(buffer, FF_CPU_TEMP_UNSET); // millidegree Celsius\n    if (value == FF_CPU_TEMP_UNSET)\n        return FF_CPU_TEMP_UNSET;\n\n    return value / 1000.;\n}\n\nstatic double parseTZDir(int dfd, FFstrbuf* buffer)\n{\n    if (!ffReadFileBufferRelative(dfd, \"type\", buffer))\n        return FF_CPU_TEMP_UNSET;\n\n    if (!ffStrbufStartsWithS(buffer, \"cpu\") &&\n        !ffStrbufStartsWithS(buffer, \"soc\") &&\n        #if __x86_64__ || __i386__\n        !ffStrbufEqualS(buffer, \"x86_pkg_temp\") &&\n        #endif\n        true\n    ) return FF_CPU_TEMP_UNSET;\n\n    return readTempFile(dfd, \"temp\", buffer);\n}\n\nstatic double parseHwmonDir(int dfd, FFstrbuf* buffer)\n{\n    //https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface\n    if (!ffReadFileBufferRelative(dfd, \"name\", buffer))\n        return FF_CPU_TEMP_UNSET;\n\n    ffStrbufTrimRightSpace(buffer);\n\n    if (\n        !ffStrbufContainS(buffer, \"cpu\") &&\n        #if __x86_64__ || __i386__\n        !ffStrbufEqualS(buffer, \"k10temp\") && // AMD\n        !ffStrbufEqualS(buffer, \"fam15h_power\") && // AMD\n        !ffStrbufEqualS(buffer, \"coretemp\") && // Intel\n        #endif\n        true\n    ) return FF_CPU_TEMP_UNSET;\n\n    return readTempFile(dfd, \"temp1_input\", buffer);\n}\n\nstatic double detectCPUTemp(const FFCPUOptions* options)\n{\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n\n    if (options->tempSensor.length > 0)\n    {\n        FF_AUTO_CLOSE_FD int subfd = -1;\n        const char* fileName = NULL;\n        if (ffStrbufStartsWithS(&options->tempSensor, \"hwmon\") && ffCharIsDigit(options->tempSensor.chars[strlen(\"hwmon\")]))\n        {\n            FF_AUTO_CLOSE_FD int dfd = open(\"/sys/class/hwmon/\", O_PATH | O_CLOEXEC);\n            subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_DIRECTORY | O_CLOEXEC);\n            if (subfd >= 0)\n                fileName = \"temp1_input\";\n            else\n                subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_CLOEXEC);\n        }\n        else if (ffStrbufStartsWithS(&options->tempSensor, \"thermal_zone\") && ffCharIsDigit(options->tempSensor.chars[strlen(\"thermal_zone\")]))\n        {\n            FF_AUTO_CLOSE_FD int dfd = open(\"/sys/class/thermal/\", O_PATH | O_CLOEXEC);\n            subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_DIRECTORY | O_CLOEXEC);\n            if (subfd >= 0)\n                fileName = \"temp\";\n            else\n                subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_CLOEXEC);\n        }\n        else if (ffStrbufStartsWithS(&options->tempSensor, \"cputemp.\") && ffCharIsDigit(options->tempSensor.chars[strlen(\"cputemp.\")]))\n        {\n            FF_AUTO_CLOSE_FD int dfd = open(\"/sys/class/platform/\", O_PATH | O_CLOEXEC);\n            subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_DIRECTORY | O_CLOEXEC);\n            if (subfd >= 0)\n                fileName = \"temp1_input\";\n            else\n                subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_CLOEXEC);\n        }\n        else if (ffIsAbsolutePath(options->tempSensor.chars))\n        {\n            subfd = open(options->tempSensor.chars, O_RDONLY | O_DIRECTORY | O_CLOEXEC);\n            if (subfd >= 0)\n                fileName = \"temp1_input\";\n            else\n                subfd = open(options->tempSensor.chars, O_RDONLY | O_CLOEXEC);\n        }\n        if (subfd < 0)\n            return FF_CPU_TEMP_UNSET;\n\n        return readTempFile(subfd, fileName, &buffer);\n    }\n\n    {\n        FF_AUTO_CLOSE_DIR DIR* dirp = opendir(\"/sys/class/hwmon/\");\n        if(dirp)\n        {\n            int dfd = dirfd(dirp);\n\n            struct dirent* entry;\n            while((entry = readdir(dirp)) != NULL)\n            {\n                if(entry->d_name[0] == '.')\n                    continue;\n\n                FF_AUTO_CLOSE_FD int subfd = openat(dfd, entry->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);\n                if(subfd < 0)\n                    continue;\n\n                double result = parseHwmonDir(subfd, &buffer);\n                if (result != FF_CPU_TEMP_UNSET)\n                    return result;\n            }\n        }\n    }\n    {\n        FF_AUTO_CLOSE_DIR DIR* dirp = opendir(\"/sys/class/thermal/\");\n        if(dirp)\n        {\n            int dfd = dirfd(dirp);\n            struct dirent* entry;\n            while((entry = readdir(dirp)) != NULL)\n            {\n                if(entry->d_name[0] == '.')\n                    continue;\n                if(!ffStrStartsWith(entry->d_name, \"thermal_zone\"))\n                    continue;\n\n                FF_AUTO_CLOSE_FD int subfd = openat(dfd, entry->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);\n                if(subfd < 0)\n                    continue;\n\n                double result = parseTZDir(subfd, &buffer);\n                if (result != FF_CPU_TEMP_UNSET)\n                    return result;\n            }\n        }\n    }\n    {\n        FF_AUTO_CLOSE_DIR DIR* dirp = opendir(\"/sys/devices/platform/\");\n        if(dirp)\n        {\n            int dfd = dirfd(dirp);\n            struct dirent* entry;\n            while((entry = readdir(dirp)) != NULL)\n            {\n                if(entry->d_name[0] == '.')\n                    continue;\n                if(!ffStrStartsWith(entry->d_name, \"cputemp.\"))\n                    continue;\n\n                FF_AUTO_CLOSE_FD int subfd = openat(dfd, entry->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);\n                if(subfd < 0)\n                    continue;\n\n                double result = parseHwmonDir(subfd, &buffer);\n                if (result != FF_CPU_TEMP_UNSET)\n                    return result;\n            }\n        }\n    }\n\n    return FF_CPU_TEMP_UNSET;\n}\n\nstatic void detectNumaNodes(FFCPUResult* cpu)\n{\n    FF_AUTO_CLOSE_DIR DIR* dir = opendir(\"/sys/devices/system/node/\");\n    if (!dir) return;\n\n    struct dirent* entry;\n    while ((entry = readdir(dir)) != NULL)\n    {\n        if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN)\n            continue;\n        if (ffStrStartsWith(entry->d_name, \"node\") && ffCharIsDigit(entry->d_name[strlen(\"node\")]))\n            cpu->numaNodes++;\n    }\n}\n\n#ifdef __ANDROID__\n#include \"common/settings.h\"\n\nstatic void detectQualcomm(FFCPUResult* cpu)\n{\n    // https://en.wikipedia.org/wiki/List_of_Qualcomm_Snapdragon_systems_on_chips\n\n    assert(cpu->name.length >= 2);\n    uint32_t code = (uint32_t) strtoul(cpu->name.chars + 2, NULL, 10);\n    const char* name = NULL;\n\n    switch (code)\n    {\n        case 8845: name = \"8 Gen 5\"; break; // ?\n        case 8850: name = \"8 Elite Gen 5\"; break;\n        case 8735: name = \"8s Gen 4\"; break;\n        case 8750: name = \"8 Elite\"; break;\n        case 8635: name = \"8s Gen 3\"; break;\n        case 8650: name = \"8 Gen 3\"; break;\n        case 8550: name = \"8 Gen 2\"; break;\n        case 8475: name = \"8+ Gen 1\"; break;\n        case 8450: name = \"8 Gen 1\"; break;\n        case 7750: name = \"7 Gen 4\"; break;\n        case 7675: name = \"7+ Gen 3\"; break;\n        case 7635: name = \"7s Gen 3\"; break;\n        case 7550: name = \"7 Gen 3\"; break;\n        case 7475: name = \"7+ Gen 2\"; break;\n        case 7435: name = \"7s Gen 2\"; break;\n        case 7450: name = \"7 Gen 1\"; break;\n        case 6650: name = \"6 Gen 4\"; break;\n        case 6375: name = \"6s Gen 3\"; break;\n        case 6475: name = \"6 Gen 3\"; break;\n        case 6115: name = \"6s Gen 1\"; break;\n        case 6450: name = \"6 Gen 1\"; break;\n        case 4635: name = \"4s Gen 2\"; break;\n        case 4450: name = \"4 Gen 2\"; break;\n        case 4375: name = \"4 Gen 1\"; break;\n    }\n\n    if (name)\n    {\n        char str[32];\n        ffStrCopy(str, cpu->name.chars, sizeof(str));\n        ffStrbufSetF(&cpu->name, \"Qualcomm Snapdragon %s [%s]\", name, str);\n        return;\n    }\n}\n\nstatic void detectMediaTek(FFCPUResult* cpu)\n{\n    // https://en.wikipedia.org/wiki/List_of_MediaTek_systems_on_chips\n\n    assert(cpu->name.length >= 2);\n    uint32_t code = (uint32_t) strtoul(cpu->name.chars + 2, NULL, 10);\n    const char* name = NULL;\n\n    switch (code) // The SOC code of MTK Dimensity series is full of mess\n    {\n        case 6993: name = \"9500\"; break;\n        case 6991: name = \"9400\"; break;\n        case 6989:\n        case 8796: name = \"9300\"; break;\n        case 6985: name = \"9200\"; break;\n        case 6983:\n        case 8798: name = \"9000\"; break;\n\n        case 6899: name = \"8400\"; break;\n        case 6897:\n        case 8792: name = \"8300\"; break;\n        case 6896: name = \"8200\"; break;\n        case 8795: name = \"8100\"; break;\n        case 6895: name = \"8000\"; break;\n    }\n\n    if (name)\n    {\n        char str[32];\n        ffStrCopy(str, cpu->name.chars, sizeof(str));\n        ffStrbufSetF(&cpu->name, \"MediaTek Dimensity %s [%s]\", name, str);\n        return;\n    }\n}\n\nstatic void detectExynos(FFCPUResult* cpu)\n{\n    // https://en.wikipedia.org/wiki/Exynos\n\n    assert(cpu->name.length > 3);\n    uint32_t code = (uint32_t) strtoul(cpu->name.chars + 3, NULL, 10);\n    const char* name = NULL;\n\n    switch (code)\n    {\n        case 9955: name = \"2500\"; break;\n        case 9945: name = \"2400\"; break;\n        // No 2300\n        case 9925: name = \"2200\"; break;\n        case 9840: name = \"2100\"; break;\n\n        case 8855: name = \"1580\"; break;\n        case 8845: name = \"1480\"; break;\n        case 8835: name = \"1380\"; break;\n        case 8535: name = \"1330\"; break;\n        case 8825: name = \"1280\"; break;\n        case 9815: name = \"1080\"; break;\n\n        case 9830: name = \"990\"; break;\n        case 9630: name = \"980\"; break;\n\n        case 8805: name = \"880\"; break;\n        case 3830: name = \"850\"; break;\n    }\n\n    if (name)\n    {\n        char str[32];\n        ffStrCopy(str, cpu->name.chars, sizeof(str));\n        ffStrbufSetF(&cpu->name, \"Samsung Exynos %s [%s]\", name, str);\n        return;\n    }\n}\n\nstatic void detectAndroid(FFCPUResult* cpu)\n{\n    if (cpu->name.length == 0)\n    {\n        if (ffSettingsGetAndroidProperty(\"ro.soc.model\", &cpu->name))\n            ffStrbufClear(&cpu->vendor); // We usually detect the vendor of CPU core as ARM, but instead we want the vendor of SOC\n    }\n    if (cpu->vendor.length == 0)\n    {\n        if (!ffSettingsGetAndroidProperty(\"ro.soc.manufacturer\", &cpu->vendor))\n            if (!ffSettingsGetAndroidProperty(\"ro.product.product.manufacturer\", &cpu->vendor))\n                if (!ffSettingsGetAndroidProperty(\"ro.product.vendor.manufacturer\", &cpu->vendor))\n                    if(ffSettingsGetAndroidProperty(\"ro.mediatek.platform\", &cpu->name))\n                        ffStrbufSetStatic(&cpu->vendor, \"MediaTek\");\n    }\n\n    if (ffStrbufEqualS(&cpu->vendor, \"QTI\"))\n        ffStrbufSetStatic(&cpu->vendor, \"Qualcomm\");\n    else if (ffStrbufIgnCaseEqualS(&cpu->vendor, \"MediaTek\")) // sometimes \"Mediatek\"\n        ffStrbufSetStatic(&cpu->vendor, \"MediaTek\");\n    else if (cpu->vendor.length > 0)\n        cpu->vendor.chars[0] = (char) toupper(cpu->vendor.chars[0]);\n\n    if (ffStrbufEqualS(&cpu->vendor, \"Qualcomm\") && ffStrbufStartsWithS(&cpu->name, \"SM\"))\n        detectQualcomm(cpu);\n    else if (ffStrbufEqualS(&cpu->vendor, \"MediaTek\") && ffStrbufStartsWithS(&cpu->name, \"MT\"))\n        detectMediaTek(cpu);\n    else if (ffStrbufEqualS(&cpu->vendor, \"Samsung\") && ffStrbufStartsWithS(&cpu->name, \"s5e\"))\n    {\n        cpu->name.chars[0] = 'S';\n        cpu->name.chars[2] = 'E';\n        detectExynos(cpu);\n    }\n}\n#endif\n\n#if __arm__ || __aarch64__\n#include \"cpu_arm.h\"\n\nstatic void detectArmName(FFstrbuf* cpuinfo, FFCPUResult* cpu, uint32_t implId)\n{\n    char* line = NULL;\n    size_t len = 0;\n    uint32_t lastPartId = UINT32_MAX;\n    uint32_t num = 0;\n    while(ffStrbufGetline(&line, &len, cpuinfo))\n    {\n        if (!ffStrStartsWith(line, \"CPU part\\t: \")) continue;\n        uint32_t partId = (uint32_t) strtoul(line + strlen(\"CPU part\\t: \"), NULL, 16);\n        const char* name = NULL;\n        switch (implId)\n        {\n            case 0x41: name = armPartId2name(partId); break;\n            case 0x42: name = brcmPartId2name(partId); break;\n            case 0x43: name = caviumPartId2name(partId); break;\n            case 0x44: name = decPartId2name(partId); break;\n            case 0x46: name = fujitsuPartId2name(partId); break;\n            case 0x48: name = hisiPartId2name(partId); break;\n            case 0x4e: name = nvidiaPartId2name(partId); break;\n            case 0x50: name = apmPartId2name(partId); break;\n            case 0x51: name = qcomPartId2name(partId); break;\n            case 0x53: name = samsungPartId2name(partId); break;\n            case 0x56: name = marvellPartId2name(partId); break;\n            case 0x61:\n                if (partId == 0)\n                {\n                    // https://github.com/Dr-Noob/cpufetch/issues/213#issuecomment-1927782105\n                    ffStrbufSetStatic(&cpu->name, \"Virtualized Apple Silicon\");\n                    ffStrbufGetlineRestore(&line, &len, cpuinfo);\n                    return;\n                }\n                name = applePartId2name(partId);\n                break;\n            case 0x66: name = faradayPartId2name(partId); break;\n            case 0x69: name = intelPartId2name(partId); break;\n            case 0x6d: name = msPartId2name(partId); break;\n            case 0x70: name = ftPartId2name(partId); break;\n            case 0xc0: name = amperePartId2name(partId); break;\n        }\n        if (lastPartId != partId)\n        {\n            if (lastPartId != UINT32_MAX)\n            {\n                if (num > 1)\n                    ffStrbufAppendF(&cpu->name, \"*%u\", num);\n                ffStrbufAppendS(&cpu->name, \" + \");\n            }\n            if (name)\n                ffStrbufAppendS(&cpu->name, name);\n            else if (partId)\n                ffStrbufAppendF(&cpu->name, \"%s-%X\", cpu->vendor.chars, partId);\n            else\n                ffStrbufAppend(&cpu->name, &cpu->vendor);\n            lastPartId = partId;\n            num = 1;\n        }\n        else\n            ++num;\n    }\n    if (num > 1)\n        ffStrbufAppendF(&cpu->name, \"*%u\", num);\n}\n#endif\n\nstatic const char* parseCpuInfo(\n    FFstrbuf* cpuinfo,\n    FFCPUResult* cpu,\n    FF_MAYBE_UNUSED FFstrbuf* physicalCoresBuffer,\n    FF_MAYBE_UNUSED FFstrbuf* cpuMHz,\n    FF_MAYBE_UNUSED FFstrbuf* cpuIsa,\n    FF_MAYBE_UNUSED FFstrbuf* cpuUarch,\n    FF_MAYBE_UNUSED FFstrbuf* cpuImplementer)\n{\n    char* line = NULL;\n    size_t len = 0;\n\n    while(ffStrbufGetline(&line, &len, cpuinfo))\n    {\n        //Stop after reasonable information is acquired\n        if((*line == '\\0' || *line == '\\n') && cpu->name.length > 0)\n        {\n            ffStrbufGetlineRestore(&line, &len, cpuinfo);\n            break;\n        }\n\n        (void)(\n            // arm64 doesn't have \"model name\"; arm32 does have \"model name\" but its value is not useful.\n            // \"Hardware\" should always be used in this case\n            #if __x86_64__ || __i386__\n            (cpu->name.length == 0 && ffParsePropLine(line, \"model name :\", &cpu->name)) ||\n            (cpu->vendor.length == 0 && ffParsePropLine(line, \"vendor_id :\", &cpu->vendor)) ||\n            (physicalCoresBuffer->length == 0 && ffParsePropLine(line, \"cpu cores :\", physicalCoresBuffer)) ||\n            (cpuMHz->length == 0 && ffParsePropLine(line, \"cpu MHz :\", cpuMHz)) ||\n            #elif __arm__ || __aarch64__\n            (cpuImplementer->length == 0 && ffParsePropLine(line, \"CPU implementer :\", cpuImplementer)) ||\n            (cpu->name.length == 0 && ffParsePropLine(line, \"Hardware :\", &cpu->name)) || //For Android devices\n            #elif __powerpc__ || __powerpc\n            (cpuMHz->length == 0 && ffParsePropLine(line, \"clock :\", cpuMHz)) ||\n            (cpu->name.length == 0 && ffParsePropLine(line, \"cpu :\", &cpu->name)) ||\n            #elif __mips__ || __mips\n            (cpu->name.length == 0 && ffParsePropLine(line, \"cpu model :\", &cpu->name)) ||\n            #elif __loongarch__\n            (cpu->name.length == 0 && ffParsePropLine(line, \"Model Name :\", &cpu->name)) ||\n            (cpuMHz->length == 0 && ffParsePropLine(line, \"CPU MHz :\", cpuMHz)) ||\n            #elif __riscv__ || __riscv\n            (cpuIsa->length == 0 && ffParsePropLine(line, \"isa :\", cpuIsa)) ||\n            (cpuUarch->length == 0 && ffParsePropLine(line, \"uarch :\", cpuUarch)) ||\n            #elif __s390x__\n            (cpu->name.length == 0 && ffParsePropLine(line, \"machine :\", &cpu->name)) ||\n            (cpu->vendor.length == 0 && ffParsePropLine(line, \"vendor_id :\", &cpu->vendor)) ||\n            (cpuMHz->length == 0 && ffParsePropLine(line, \"cpu MHz static :\", cpuMHz)) ||\n            #elif __ia64__\n            (cpu->name.length == 0 && ffParsePropLine(line, \"model name :\", &cpu->name)) ||\n            (cpu->vendor.length == 0 && ffParsePropLine(line, \"vendor :\", &cpu->vendor)) ||\n            (cpuMHz->length == 0 && ffParsePropLine(line, \"cpu MHz :\", cpuMHz)) ||\n            #elif __hppa__\n            (cpu->name.length == 0 && ffParsePropLine(line, \"cpu :\", &cpu->name)) ||\n            #elif __sh__\n            (cpu->name.length == 0 && ffParsePropLine(line, \"cpu type :\", &cpu->name)) ||\n            #else\n            (cpu->name.length == 0 && ffParsePropLine(line, \"model name :\", &cpu->name)) ||\n            (cpu->name.length == 0 && ffParsePropLine(line, \"model :\", &cpu->name)) ||\n            (cpu->name.length == 0 && ffParsePropLine(line, \"cpu model :\", &cpu->name)) ||\n            (cpu->name.length == 0 && ffParsePropLine(line, \"hardware :\", &cpu->name)) ||\n            (cpu->name.length == 0 && ffParsePropLine(line, \"processor :\", &cpu->name)) ||\n            #endif\n\n            false\n        );\n    }\n\n    return NULL;\n}\n\nstatic uint32_t getFrequency(FFstrbuf* basePath, const char* cpuinfoFileName, const char* scalingFileName, FFstrbuf* buffer)\n{\n    uint32_t baseLen = basePath->length;\n    ffStrbufAppendS(basePath, cpuinfoFileName);\n    bool ok = ffReadFileBuffer(basePath->chars, buffer);\n    ffStrbufSubstrBefore(basePath, baseLen);\n    if (ok)\n        return (uint32_t) (ffStrbufToUInt(buffer, 0) / 1000);\n\n    if (scalingFileName)\n    {\n        ffStrbufAppendS(basePath, scalingFileName);\n        ok = ffReadFileBuffer(basePath->chars, buffer);\n        ffStrbufSubstrBefore(basePath, baseLen);\n        if (ok)\n            return (uint32_t) (ffStrbufToUInt(buffer, 0) / 1000);\n    }\n\n    return 0;\n}\n\nstatic uint8_t getNumCores(FFstrbuf* basePath, FFstrbuf* buffer)\n{\n    uint32_t baseLen = basePath->length;\n    ffStrbufAppendS(basePath, \"/affected_cpus\");\n    bool ok = ffReadFileBuffer(basePath->chars, buffer);\n    ffStrbufSubstrBefore(basePath, baseLen);\n    if (ok)\n        return (uint8_t) (ffStrbufCountC(buffer, ' ') + 1);\n\n    ffStrbufAppendS(basePath, \"/related_cpus\");\n    ok = ffReadFileBuffer(basePath->chars, buffer);\n    ffStrbufSubstrBefore(basePath, baseLen);\n    if (ok)\n        return (uint8_t) (ffStrbufCountC(buffer, ' ') + 1);\n\n    return 0;\n}\n\nstatic bool detectFrequency(FFCPUResult* cpu, const FFCPUOptions* options)\n{\n    FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateS(\"/sys/devices/system/cpu/cpufreq/\");\n    FF_AUTO_CLOSE_DIR DIR* dir = opendir(path.chars);\n    if (!dir) return false;\n\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n    uint32_t baseLen = path.length;\n\n    struct dirent* entry;\n    while ((entry = readdir(dir)) != NULL)\n    {\n        if (ffStrStartsWith(entry->d_name, \"policy\") && ffCharIsDigit(entry->d_name[strlen(\"policy\")]))\n        {\n            ffStrbufAppendS(&path, entry->d_name);\n\n            uint32_t fmax = getFrequency(&path, \"/cpuinfo_max_freq\", \"/scaling_max_freq\", &buffer);\n            if (fmax == 0) continue;\n\n            if (cpu->frequencyMax >= fmax)\n            {\n                if (!options->showPeCoreCount)\n                {\n                    ffStrbufSubstrBefore(&path, baseLen);\n                    continue;\n                }\n            }\n            else\n                cpu->frequencyMax = fmax;\n\n            uint32_t fbase = getFrequency(&path, \"/base_frequency\", NULL, &buffer);\n            if (fbase > 0)\n                cpu->frequencyBase = cpu->frequencyBase > fbase ? cpu->frequencyBase : fbase;\n\n            if (options->showPeCoreCount)\n            {\n                uint32_t freq = fbase == 0 ? fmax : fbase; // seems base frequencies are more stable\n                uint32_t ifreq = 0;\n                while (cpu->coreTypes[ifreq].freq != freq && cpu->coreTypes[ifreq].freq > 0)\n                    ++ifreq;\n                if (cpu->coreTypes[ifreq].freq == 0)\n                    cpu->coreTypes[ifreq].freq = freq;\n                cpu->coreTypes[ifreq].count += getNumCores(&path, &buffer);\n            }\n            ffStrbufSubstrBefore(&path, baseLen);\n        }\n    }\n    return true;\n}\n\n#if __i386__ || __x86_64__\n\nFF_MAYBE_UNUSED static uint16_t getPackageCount(FFstrbuf* cpuinfo)\n{\n    const char* p = cpuinfo->chars;\n    uint64_t low = 0, high = 0;\n\n    while ((p = memmem(p, cpuinfo->length - (uint32_t) (p - cpuinfo->chars), \"\\nphysical id\\t:\", strlen(\"\\nphysical id\\t:\"))))\n    {\n        p += strlen(\"\\nphysical id\\t:\");\n        char* pend;\n        unsigned long long id = strtoul(p, &pend, 10);\n        if (__builtin_expect(id > 64, false)) // Do 129-socket boards exist?\n            high |= 1ULL << (id - 64);\n        else\n            low |= 1ULL << id;\n        p = pend;\n    }\n\n    return (uint16_t) (__builtin_popcountll(low) + __builtin_popcountll(high));\n}\n\nFF_MAYBE_UNUSED static const char* detectCPUX86(const FFCPUOptions* options, FFCPUResult* cpu)\n{\n    FF_STRBUF_AUTO_DESTROY cpuinfo = ffStrbufCreateA(PROC_FILE_BUFFSIZ);\n    if (!ffReadFileBuffer(FF_CPUINFO_PATH, &cpuinfo) || cpuinfo.length == 0)\n        return \"ffReadFileBuffer(\\\"\" FF_CPUINFO_PATH \"\\\") failed\";\n\n    FF_STRBUF_AUTO_DESTROY physicalCoresBuffer = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY cpuMHz = ffStrbufCreate();\n    const char* error = parseCpuInfo(&cpuinfo, cpu, &physicalCoresBuffer, &cpuMHz, NULL,NULL, NULL);\n    if (error) return error;\n\n    cpu->coresLogical = (uint16_t) get_nprocs_conf();\n    cpu->coresOnline = (uint16_t) get_nprocs();\n    cpu->packages = getPackageCount(&cpuinfo);\n    cpu->coresPhysical = (uint16_t) ffStrbufToUInt(&physicalCoresBuffer, 0); // physical cores in single package\n    if (cpu->coresPhysical > 0 && cpu->packages > 1)\n        cpu->coresPhysical *= cpu->packages;\n\n    // Ref https://github.com/fastfetch-cli/fastfetch/issues/1194#issuecomment-2295058252\n    ffCPUDetectByCpuid(cpu);\n    if (!detectFrequency(cpu, options) || cpu->frequencyBase == 0)\n        cpu->frequencyBase = (uint32_t) ffStrbufToUInt(&cpuMHz, 0);\n\n    detectNumaNodes(cpu);\n\n    return NULL;\n}\n\n#else\n\nstatic const char* detectPhysicalCores(FFCPUResult* cpu)\n{\n    int dfd = open(\"/sys/devices/system/cpu/\", O_RDONLY | O_DIRECTORY | O_CLOEXEC);\n    if (dfd < 0) return \"open(\\\"/sys/devices/system/cpu/\\\") failed\";\n\n    FF_AUTO_CLOSE_DIR DIR* dir = fdopendir(dfd);\n    if (!dir) return \"fdopendir(dfd) failed\";\n\n    uint64_t pkgLow = 0, pkgHigh = 0;\n\n    struct dirent* entry;\n    FF_LIST_AUTO_DESTROY cpuList = ffListCreate(sizeof(uint32_t));\n    while ((entry = readdir(dir)) != NULL)\n    {\n        if (entry->d_type != DT_DIR || !ffStrStartsWith(entry->d_name, \"cpu\") || !ffCharIsDigit(entry->d_name[strlen(\"cpu\")]))\n            continue;\n\n        FF_AUTO_CLOSE_FD int cpuxfd = openat(dirfd(dir), entry->d_name, O_RDONLY | O_DIRECTORY);\n        if (cpuxfd < 0)\n            continue;\n\n        char buf[128];\n\n        // Check if the directory contains a file named \"topology/physical_package_id\"\n        // that lists the physical package id of the CPU.\n\n        ssize_t len = ffReadFileDataRelative(cpuxfd, \"topology/physical_package_id\", sizeof(buf) - 1, buf);\n        if (len > 0)\n        {\n            buf[len] = '\\0';\n            unsigned long long id = strtoul(buf, NULL, 10);\n            if (__builtin_expect(id > 64, false)) // Do 129-socket boards exist?\n                pkgHigh |= 1ULL << (id - 64);\n            else\n                pkgLow |= 1ULL << id;\n        }\n\n        // Check if the directory contains a file named \"topology/core_cpus_list\"\n        // that lists the physical cores in the package.\n\n        len = ffReadFileDataRelative(cpuxfd, \"topology/core_cpus_list\", sizeof(buf) - 1, buf);\n        if (len > 0)\n        {\n            buf[len] = '\\0'; // low-high or low\n\n            for (const char* p = buf; *p;)\n            {\n                char* pend;\n                uint32_t coreId = (uint32_t) strtoul(p, &pend, 10);\n                if (pend == p) break;\n\n                bool found = false;\n                FF_LIST_FOR_EACH(uint32_t, id, cpuList)\n                {\n                    if (*id == coreId)\n                    {\n                        // This core is already counted\n                        found = true;\n                        break;\n                    }\n                }\n                if (!found)\n                    *(uint32_t*) ffListAdd(&cpuList) = coreId;\n\n                p = strchr(pend, ',');\n                if (!p) break;\n                ++p;\n            }\n        }\n    }\n\n    cpu->coresPhysical = (uint16_t) cpuList.length;\n    cpu->packages = (uint16_t) (__builtin_popcountll(pkgLow) + __builtin_popcountll(pkgHigh));\n    return NULL;\n}\n\nFF_MAYBE_UNUSED static void parseIsa(FFstrbuf* cpuIsa)\n{\n    // Always use the last part of the ISA string. Ref: #590 #1204\n    ffStrbufSubstrAfterLastC(cpuIsa, ' ');\n\n    if(ffStrbufStartsWithS(cpuIsa, \"rv\"))\n    {\n        // RISC-V ISA string example: \"rv64imafdch_zicsr_zifencei\".\n        // The _z parts are not important for CPU showcasing, so we remove them.\n        if(ffStrbufContainC(cpuIsa, '_'))\n            ffStrbufSubstrBeforeFirstC(cpuIsa, '_');\n        // Then we replace \"imafd\" with \"g\" since \"g\" is a shorthand.\n        if(ffStrbufContainS(cpuIsa, \"imafd\"))\n        {\n            // Remove 4 of the 5 characters and replace the remaining one with \"g\".\n            ffStrbufRemoveSubstr(cpuIsa, 4, 8);\n            cpuIsa->chars[4] = 'g';\n        }\n        // The final ISA output of the above example is \"rv64gch\".\n    }\n}\n\nFF_MAYBE_UNUSED static void detectSocName(FFCPUResult* cpu)\n{\n    if (cpu->name.length > 0)\n        return;\n\n    // [x-vendor,x-model\\0]*N\n    char content[512];\n    ssize_t length = ffReadFileData(\"/sys/firmware/devicetree/base/compatible\", ARRAY_SIZE(content), content);\n    if (length < 4) return; // v,m\\0\n\n    if (content[length - 1] != '\\0') return; // must end with \\0\n\n    --length;\n\n    char* vendor = NULL;\n    char* model = NULL;\n\n    for (char* p; length > 0; length = p ? (ssize_t) (p - content) - 1 : 0)\n    {\n        p = memrchr(content, '\\0', (size_t) length);\n\n        vendor = p /* first entry */ ? p + 1 : content;\n\n        size_t partLen = (size_t) (length - (vendor - content));\n        if (partLen < 3) continue;\n\n        char* comma = memchr(vendor, ',', partLen);\n        if (!comma) continue;\n\n        size_t vendorLen = (size_t) (comma - vendor);\n        if (vendorLen == 0) continue;\n\n        model = comma + 1;\n        size_t modelLen = (size_t) (partLen - (size_t) (model - vendor));\n        if (modelLen == 0) continue;\n\n        if ((modelLen >= strlen(\"-platform\") && ffStrEndsWith(model, \"-platform\")) ||\n            (modelLen >= strlen(\"-soc\") && ffStrEndsWith(model, \"-soc\")))\n            continue;\n\n        *comma = '\\0';\n        break;\n    }\n\n    if (!length) return;\n\n    if (false) {}\n    #if __aarch64__\n    else if (ffStrEquals(vendor, \"apple\"))\n    {\n        // https://elixir.bootlin.com/linux/v6.11/source/arch/arm64/boot/dts/apple\n        if (model[0] == 't')\n        {\n            uint32_t deviceId = (uint32_t) strtoul(model + 1, NULL, 10);\n            ffStrbufSetStatic(&cpu->name, ffCPUAppleCodeToName(deviceId));\n\n            if (!cpu->name.length)\n            {\n                ffStrbufSetS(&cpu->name, \"Apple Silicon \");\n                ffStrbufAppendS(&cpu->name, model);\n            }\n        }\n        else\n            ffStrbufSetS(&cpu->name, model);\n\n        ffStrbufSetStatic(&cpu->vendor, \"Apple\");\n    }\n    #endif\n    else if (ffStrEquals(vendor, \"qcom\"))\n    {\n        // https://elixir.bootlin.com/linux/v6.11/source/arch/arm64/boot/dts/qcom\n        if (ffStrStartsWith(model, \"x\"))\n        {\n            ffStrbufSetS(&cpu->name, \"Qualcomm Snapdragon X Elite \");\n            for (const char* p = model + 1; *p; ++p)\n                ffStrbufAppendC(&cpu->name, (char) toupper(*p));\n        }\n        else if (ffStrStartsWith(model, \"sc\"))\n        {\n            const char* code = model + 2;\n            uint32_t deviceId = (uint32_t) strtoul(code, NULL, 10);\n            ffStrbufSetStatic(&cpu->name, ffCPUQualcommCodeToName(deviceId));\n            if (!cpu->name.length)\n            {\n                ffStrbufAppendS(&cpu->name, \"Qualcomm Snapdragon SC\");\n                ffStrbufAppendS(&cpu->name, code);\n            }\n        }\n        else\n            ffStrbufSetS(&cpu->name, model);\n\n        ffStrbufSetStatic(&cpu->vendor, \"Qualcomm\");\n    }\n    else if (ffStrEquals(vendor, \"brcm\"))\n    {\n        // Raspberry Pi\n        ffStrbufSetStatic(&cpu->vendor, \"Broadcom\");\n        for (const char* p = model; *p; ++p)\n            ffStrbufAppendC(&cpu->name, (char) toupper(*p));\n    }\n    else if (ffStrEquals(vendor, \"thead\"))\n    {\n        // Lichee Pi?\n        ffStrbufSetStatic(&cpu->vendor, \"T-Head\");\n        for (const char* p = model; *p; ++p)\n            ffStrbufAppendC(&cpu->name, (char) toupper(*p));\n    }\n    else\n    {\n        ffStrbufSetS(&cpu->name, model);\n        ffStrbufSetS(&cpu->vendor, vendor);\n        cpu->vendor.chars[0] = (char) toupper(vendor[0]);\n    }\n}\n\n#ifdef __loongarch__\nFF_MAYBE_UNUSED static uint16_t getLoongarchPropCount(FFstrbuf* cpuinfo, const char* key)\n{\n    const char* p = cpuinfo->chars;\n    uint64_t low = 0, high = 0;\n    uint32_t keylen = (uint32_t) strlen(key);\n\n    while ((p = memmem(p, cpuinfo->length - (uint32_t) (p - cpuinfo->chars), key, keylen)))\n    {\n        p += keylen;\n        char* pend;\n        unsigned long id = strtoul(p, &pend, 10);\n        if (__builtin_expect(id > 64, false))\n            high |= 1UL << (id - 64);\n        else\n            low |= 1UL << id;\n        p = pend;\n    }\n\n    return (uint16_t) (__builtin_popcountll(low) + __builtin_popcountll(high));\n}\n#endif\n\nFF_MAYBE_UNUSED static const char* detectCPUOthers(const FFCPUOptions* options, FFCPUResult* cpu)\n{\n    cpu->coresLogical = (uint16_t) get_nprocs_conf();\n    cpu->coresOnline = (uint16_t) get_nprocs();\n\n    #if __ANDROID__\n    detectAndroid(cpu);\n    #elif !__powerpc__ && !__powerpc\n    detectSocName(cpu);\n    #endif\n\n    detectFrequency(cpu, options);\n\n    if (cpu->name.length == 0)\n    {\n        FF_STRBUF_AUTO_DESTROY cpuinfo = ffStrbufCreateA(PROC_FILE_BUFFSIZ);\n        if (!ffReadFileBuffer(FF_CPUINFO_PATH, &cpuinfo) || cpuinfo.length == 0)\n            return \"ffReadFileBuffer(\\\"\" FF_CPUINFO_PATH \"\\\") failed\";\n\n        FF_STRBUF_AUTO_DESTROY cpuMHz = ffStrbufCreate();\n        FF_STRBUF_AUTO_DESTROY cpuIsa = ffStrbufCreate();\n        FF_STRBUF_AUTO_DESTROY cpuUarch = ffStrbufCreate();\n        FF_STRBUF_AUTO_DESTROY cpuImplementerStr = ffStrbufCreate();\n\n        const char* error = parseCpuInfo(&cpuinfo, cpu, NULL, &cpuMHz, &cpuIsa, &cpuUarch, &cpuImplementerStr);\n        if (error) return error;\n\n        if (cpu->frequencyBase == 0)\n            cpu->frequencyBase = (uint32_t) ffStrbufToUInt(&cpuMHz, 0);\n\n        #if __arm__ || __aarch64__\n        uint32_t cpuImplementer = (uint32_t) strtoul(cpuImplementerStr.chars, NULL, 16);\n        ffStrbufSetStatic(&cpu->vendor, hwImplId2Vendor(cpuImplementer));\n\n        if (cpu->name.length == 0)\n            detectArmName(&cpuinfo, cpu, cpuImplementer);\n        #elif __riscv__ || __riscv\n        if (cpu->name.length == 0)\n        {\n            if(cpuUarch.length > 0)\n            {\n                if(cpu->name.length > 0)\n                    ffStrbufAppendC(&cpu->name, ' ');\n                ffStrbufAppend(&cpu->name, &cpuUarch);\n            }\n\n            if(cpuIsa.length > 0)\n            {\n                parseIsa(&cpuIsa);\n                if(cpu->name.length > 0)\n                    ffStrbufAppendC(&cpu->name, ' ');\n                ffStrbufAppend(&cpu->name, &cpuIsa);\n            }\n        }\n        #elif __loongarch__\n        cpu->packages = getLoongarchPropCount(&cpuinfo, \"\\npackage\\t\\t\\t:\");\n        cpu->coresPhysical = getLoongarchPropCount(&cpuinfo, \"\\ncore\\t\\t\\t:\");\n        if (cpu->packages > 1) cpu->coresPhysical *= cpu->packages;\n        #elif __s390x__\n        if (cpu->name.length) ffStrbufPrependS(&cpu->name, \"Machine \");\n        #endif\n    }\n\n    if (cpu->coresPhysical == 0)\n        detectPhysicalCores(cpu);\n\n    ffCPUDetectByCpuid(cpu);\n    detectNumaNodes(cpu);\n\n    return NULL;\n}\n#endif\n\nconst char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu)\n{\n    cpu->temperature = options->temp ? detectCPUTemp(options) : FF_CPU_TEMP_UNSET;\n\n    #if __x86_64__ || __i386__\n    return detectCPUX86(options, cpu);\n    #else\n    return detectCPUOthers(options, cpu);\n    #endif\n}\n"
  },
  {
    "path": "src/detection/cpu/cpu_nbsd.c",
    "content": "#include \"cpu.h\"\n#include \"common/sysctl.h\"\n#include \"common/io.h\"\n\n#include <sys/envsys.h>\n#include <prop/proplib.h>\n#include <paths.h>\n#include <time.h>\n#include <unistd.h>\n#include <fcntl.h>\n\nstatic void freePropDict(prop_dictionary_t* pdict)\n{\n    assert(pdict != NULL);\n    if (*pdict == NULL) return;\n    prop_object_release(*pdict);\n}\n\nstatic const char* detectCpuTemp(const FFCPUOptions* options, double* current)\n{\n    FF_AUTO_CLOSE_FD int fd = open(_PATH_SYSMON, O_RDONLY | O_CLOEXEC);\n    if (fd < 0) return \"open(_PATH_SYSMON, O_RDONLY | O_CLOEXEC) failed\";\n\n    __attribute__((__cleanup__(freePropDict))) prop_dictionary_t root = NULL;\n    if (prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &root) < 0)\n        return \"prop_dictionary_recv_ioctl(ENVSYS_GETDICTIONARY) failed\";\n\n    prop_array_t array;\n\n    if (options->tempSensor.length > 0)\n    {\n        array = prop_dictionary_get(root, options->tempSensor.chars);\n        if (!array) return \"No temp data found in specified sensor\";\n    }\n    else\n    {\n        array = prop_dictionary_get(root, \"coretemp0\");\n        if (!array) array = prop_dictionary_get(root, \"amdzentemp0\");\n        if (!array) array = prop_dictionary_get(root, \"viac7temp0\");\n        if (!array) array = prop_dictionary_get(root, \"acpitz0\"); // Thermal Zones\n        if (!array) return \"No temp data found in root dictionary\";\n    }\n\n    if (prop_array_count(array) != 2)\n        return \"Unexpected `xtemp0` data\";\n\n    prop_dictionary_t dict = prop_array_get(array, 0);\n    if (prop_object_type(dict) != PROP_TYPE_DICTIONARY)\n        return \"Unexpected `xtemp0[0]`\";\n\n    int temp = 0; // in µK\n    if (!prop_dictionary_get_int(dict, \"cur-value\", &temp))\n        return \"Failed to get temperature\";\n\n    *current = temp / 1e6 - 273.15;\n\n    return NULL;\n}\n\nconst char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu)\n{\n    if (ffSysctlGetString(\"machdep.cpu_brand\", &cpu->name) != NULL &&\n        ffSysctlGetString(\"machdep.dmi.processor-version\", &cpu->name) != NULL &&\n        ffSysctlGetString(\"hw.cpu0.name\", &cpu->name) != NULL &&\n        ffSysctlGetString(\"hw.model\", &cpu->name) != NULL)\n    {\n        ffStrbufSetS(&cpu->name, \"Unknown CPU\");\n    }\n\n    if (ffSysctlGetString(\"machdep.dmi.processor-vendor\", &cpu->vendor) == NULL)\n        ffStrbufTrimRightSpace(&cpu->vendor);\n\n    cpu->coresPhysical = (uint16_t) ffSysctlGetInt(\"hw.ncpu\", 1);\n    cpu->coresLogical = cpu->coresPhysical;\n    cpu->coresOnline = (uint16_t) ffSysctlGetInt(\"hw.ncpuonline\", cpu->coresLogical);\n\n    ffCPUDetectByCpuid(cpu);\n\n    uint32_t freq = (uint32_t) ffSysctlGetInt(\"machdep.cpu.frequency.target\", 0);\n    if (freq == 0) freq = (uint32_t) (ffSysctlGetInt64(\"hw.cpu0.clock_frequency\", 0) / 1000000);\n    if (freq == 0) freq = (uint32_t) ffSysctlGetInt(\"machdep.dmi.processor-frequency\", 0);\n    if (freq > cpu->frequencyBase) cpu->frequencyBase = freq;\n\n    cpu->temperature = FF_CPU_TEMP_UNSET;\n\n    if (options->temp) detectCpuTemp(options, &cpu->temperature);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/cpu/cpu_nosupport.c",
    "content": "#include \"cpu.h\"\n\nconst char* ffDetectCPUImpl(FF_MAYBE_UNUSED const FFCPUOptions* options, FF_MAYBE_UNUSED FFCPUResult* cpu)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/cpu/cpu_obsd.c",
    "content": "#include \"cpu.h\"\n#include \"common/sysctl.h\"\n#include \"common/stringUtils.h\"\n\n#include <errno.h>\n#include <sys/time.h>\n#include <sys/sensors.h>\n\nstatic const char* detectCPUTemp(const FFCPUOptions* options, FFCPUResult* cpu)\n{\n    int mib[5] = {CTL_HW, HW_SENSORS, 0, SENSOR_TEMP, 0};\n\n    for (mib[2] = 0; mib[2] < 1024; mib[2]++)\n    {\n        struct sensordev sensordev;\n        size_t sdlen = sizeof(struct sensordev);\n        if (sysctl(mib, 3, &sensordev, &sdlen, NULL, 0) < 0)\n        {\n            if (errno == ENOENT)\n                break;\n            if (errno == ENXIO)\n                continue;\n            return \"sysctl(sensordev) failed\";\n        }\n\n        if (options->tempSensor.length > 0)\n        {\n            if (!ffStrbufEqualS(&options->tempSensor, sensordev.xname))\n                continue;\n        }\n        else\n        {\n            if (!ffStrStartsWith(sensordev.xname, \"cpu\"))\n                continue;\n        }\n\n        for (mib[4] = 0; mib[4] < sensordev.maxnumt[SENSOR_TEMP]; mib[4]++)\n        {\n            struct sensor sensor;\n            size_t slen = sizeof(struct sensor);\n            if (sysctl(mib, 5, &sensor, &slen, NULL, 0) < 0)\n            {\n                if (errno != ENOENT)\n                    return \"sysctl(sensor) failed\";\n                continue;\n            }\n            if (sensor.flags & SENSOR_FINVALID)\n                continue;\n\n            cpu->temperature = (double)(sensor.value - 273150000) / 1E6;\n            return NULL;\n        }\n    }\n\n    return \"No sensor for CPU temp found\";\n}\n\nconst char *ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu)\n{\n    if (ffSysctlGetString(CTL_HW, HW_MODEL, &cpu->name))\n        return \"sysctl(hw.model) failed\";\n\n    cpu->coresPhysical = (uint16_t) ffSysctlGetInt(CTL_HW, HW_NCPU, 1);\n    cpu->coresLogical = cpu->coresPhysical;\n    cpu->coresOnline = (uint16_t) ffSysctlGetInt(CTL_HW, HW_NCPUONLINE, cpu->coresLogical);\n\n    ffCPUDetectByCpuid(cpu);\n\n    uint32_t cpuspeed = (uint32_t) ffSysctlGetInt(CTL_HW, HW_CPUSPEED, 0);\n    if (cpuspeed > cpu->frequencyBase) cpu->frequencyBase = cpuspeed;\n\n    cpu->temperature = FF_CPU_TEMP_UNSET;\n    if (options->temp) detectCPUTemp(options, cpu);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/cpu/cpu_sunos.c",
    "content": "#include \"cpu.h\"\n#include \"common/processing.h\"\n#include \"common/stringUtils.h\"\n#include <kstat.h>\n\nstatic const char* detectCPUTempByKstat(const FFCPUOptions* options, kstat_ctl_t* kc, FFCPUResult* cpu)\n{\n    const char* possibleModules[] = {\"temperature\", \"cpu_temp\", \"acpi_thermal\", NULL};\n\n    if (options->tempSensor.length > 0) {\n        possibleModules[0] = options->tempSensor.chars;\n        possibleModules[1] = NULL;\n    }\n\n    for (int i = 0; possibleModules[i] != NULL; i++) {\n        kstat_t* ks = kstat_lookup(kc, possibleModules[i], -1, NULL);\n        if (ks && kstat_read(kc, ks, NULL) >= 0) {\n            kstat_named_t* kn = kstat_data_lookup(ks, \"temperature\");\n            if (kn) {\n                switch (kn->data_type) {\n                    case KSTAT_DATA_INT32:\n                        cpu->temperature = (float)kn->value.i32;\n                        return NULL;\n                    case KSTAT_DATA_UINT32:\n                        cpu->temperature = (float)kn->value.ui32;\n                        return NULL;\n                    case KSTAT_DATA_FLOAT:\n                        cpu->temperature = kn->value.f;\n                        return NULL;\n                }\n            }\n        }\n    }\n\n    return \"Failed to find CPU temperature using kstat\";\n}\n\nstatic const char* detectCPUTempByIpmiTool(FFCPUResult* cpu)\n{\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n    const char* error = ffProcessAppendStdOut(&buffer, (char* const[]){\n        \"ipmitool\",\n        \"-c\",\n        \"sdr\",\n        \"list\",\n        NULL\n    });\n\n    if (error)\n        return error;\n\n    char* line = NULL;\n    size_t len = 0;\n    while (ffStrbufGetline(&line, &len, &buffer))\n    {\n        if (sscanf(line, \"CPU%*d Temp,%lf,degrees C,ok\", &cpu->temperature) == 1)\n            return NULL;\n    }\n\n    return \"ipmitool sdr list failed to find CPU temperature\";\n}\n\nstatic inline void kstatFreeWrap(kstat_ctl_t** pkc)\n{\n    assert(pkc);\n    if (*pkc)\n        kstat_close(*pkc);\n}\n\nstatic inline uint16_t countTypeId(kstat_ctl_t* kc, const char* type)\n{\n    uint64_t low = 0, high = 0;\n    for (kstat_t* ksp = kc->kc_chain; ksp; ksp = ksp->ks_next)\n    {\n        if (ffStrStartsWith(ksp->ks_module, \"cpu_info\"))\n        {\n            if (kstat_read(kc, ksp, NULL) < 0)\n                continue;\n\n            kstat_named_t* stat = kstat_data_lookup(ksp, type);\n            if (!stat)\n                continue;\n\n            uint32_t id = 0;\n            switch (stat->data_type)\n            {\n                #ifdef _INT64_TYPE\n                case KSTAT_DATA_INT64:\n                case KSTAT_DATA_UINT64:\n                    id = (uint32_t) stat->value.ui64;\n                    break;\n                #endif\n                case KSTAT_DATA_INT32:\n                case KSTAT_DATA_UINT32:\n                    id = stat->value.ui32;\n                    break;\n                default:\n                    continue;\n            }\n            if (__builtin_expect(id > 64, false))\n                high |= 1ULL << (id - 64);\n            else\n                low |= 1ULL << id;\n        }\n    }\n    return (uint16_t) (__builtin_popcountll(low) + __builtin_popcountll(high));\n}\n\nconst char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu)\n{\n    __attribute__((__cleanup__(kstatFreeWrap))) kstat_ctl_t* kc = kstat_open();\n    if (!kc)\n        return \"kstat_open() failed\";\n\n    kstat_t* ks = kstat_lookup(kc, \"cpu_info\", -1, NULL);\n    if (!ks)\n        return \"kstat_lookup() failed\";\n\n    if (kstat_read(kc, ks, NULL) < 0)\n        return \"kstat_read() failed\";\n\n    {\n        kstat_named_t* kn = kstat_data_lookup(ks, \"brand\");\n        if (kn) ffStrbufSetNS(&cpu->name, KSTAT_NAMED_STR_BUFLEN(kn) - 1, KSTAT_NAMED_STR_PTR(kn));\n    }\n    {\n        kstat_named_t* kn = kstat_data_lookup(ks, \"vendor_id\");\n        if (kn) ffStrbufSetNS(&cpu->vendor, KSTAT_NAMED_STR_BUFLEN(kn) - 1, KSTAT_NAMED_STR_PTR(kn));\n    }\n    ffCPUDetectByCpuid(cpu);\n    {\n        kstat_named_t* kn = kstat_data_lookup(ks, \"clock_MHz\");\n        if (kn && kn->value.ui32 > cpu->frequencyBase)\n            cpu->frequencyBase = kn->value.ui32;\n    }\n\n    ks = kstat_lookup(kc, \"unix\", -1, \"system_misc\");\n    if (ks && kstat_read(kc, ks, NULL) >= 0)\n    {\n        kstat_named_t* kn = kstat_data_lookup(ks, \"ncpus\");\n        if (kn) cpu->coresLogical = cpu->coresOnline = (uint16_t) kn->value.ui32;\n    }\n\n    cpu->packages = countTypeId(kc, \"chip_id\");\n    cpu->coresPhysical = countTypeId(kc, \"core_id\");\n\n    if (options->temp)\n    {\n        if (detectCPUTempByKstat(options, kc, cpu) != NULL)\n            detectCPUTempByIpmiTool(cpu);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/cpu/cpu_windows.c",
    "content": "#include \"cpu.h\"\n#include \"common/windows/registry.h\"\n#include \"common/windows/nt.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/smbiosHelper.h\"\n\n#include <windows.h>\n#include \"common/windows/perflib_.h\"\n#include \"common/windows/nt.h\"\n#include <wchar.h>\n\nstatic inline void ffPerfCloseQueryHandle(HANDLE* phQuery)\n{\n    if (*phQuery != NULL)\n    {\n        PerfCloseQueryHandle(*phQuery);\n        *phQuery = NULL;\n    }\n}\n\nconst char* detectThermalTemp(const FFCPUOptions* options, double* result)\n{\n    struct FFPerfQuerySpec\n    {\n        PERF_COUNTER_IDENTIFIER Identifier;\n        WCHAR Name[16];\n    } querySpec = {\n        .Identifier = {\n            // Thermal Zone Information\n            // HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib\\_V2Providers\\{383487a6-3676-4870-a4e7-d45b30c35629}\\{52bc5412-dac2-449c-8bc2-96443888fe6b}\n            .CounterSetGuid = { 0x52bc5412, 0xdac2, 0x449c, {0x8b, 0xc2, 0x96, 0x44, 0x38, 0x88, 0xfe, 0x6b} },\n            .Size = sizeof(querySpec),\n            .CounterId = PERF_WILDCARD_COUNTER,\n            .InstanceId = PERF_WILDCARD_COUNTER,\n        },\n        .Name = L\"\\\\_TZ.CPUZ\", // The standard(?) instance name for CPU temperature in the thermal provider\n    };\n\n    if (options->tempSensor.length > 0)\n    {\n        if (!NT_SUCCESS(RtlUTF8ToUnicodeN(querySpec.Name, (ULONG) sizeof(querySpec.Name), NULL, options->tempSensor.chars, (ULONG)options->tempSensor.length + 1)))\n            return \"Invalid temp sensor string\";\n    }\n\n    DWORD dataSize = 0;\n    if (PerfEnumerateCounterSetInstances(NULL, &querySpec.Identifier.CounterSetGuid, NULL, 0, &dataSize) != ERROR_NOT_ENOUGH_MEMORY)\n        return \"PerfEnumerateCounterSetInstances() failed\";\n\n    if (dataSize <= sizeof(PERF_INSTANCE_HEADER))\n        return \"No `Thermal Zone Information` instances found\";\n\n    {\n        FF_AUTO_FREE PERF_INSTANCE_HEADER* const pHead = malloc(dataSize);\n        if (PerfEnumerateCounterSetInstances(NULL, &querySpec.Identifier.CounterSetGuid, pHead, dataSize, &dataSize) != ERROR_SUCCESS)\n            return \"PerfEnumerateCounterSetInstances() failed to get instance headers\";\n\n        PERF_INSTANCE_HEADER* pInstanceHeader = pHead;\n        while (1)\n        {\n            const wchar_t* instanceName = (const wchar_t*)((BYTE*)pInstanceHeader + sizeof(*pInstanceHeader));\n            if (wcscmp(instanceName, querySpec.Name) == 0)\n                break;\n\n            dataSize -= pInstanceHeader->Size;\n            if (dataSize == 0)\n                break;\n            pInstanceHeader = (PERF_INSTANCE_HEADER*)((BYTE*)pInstanceHeader + pInstanceHeader->Size);\n        }\n\n        if (dataSize == 0)\n        {\n            if (options->tempSensor.length > 0)\n                return \"Unable to find CPU sensor\";\n\n            const wchar_t* instanceName = (const wchar_t*)((BYTE*)pHead + sizeof(*pHead));\n            wcscpy(querySpec.Name, instanceName); // Use the first instance name if the specific one is not found\n        }\n    }\n\n    __attribute__((__cleanup__(ffPerfCloseQueryHandle)))\n    HANDLE hQuery = NULL;\n\n    if (PerfOpenQueryHandle(NULL, &hQuery) != ERROR_SUCCESS)\n        return \"PerfOpenQueryHandle() failed\";\n\n    if (PerfAddCounters(hQuery, &querySpec.Identifier, sizeof(querySpec)) != ERROR_SUCCESS)\n        return \"PerfAddCounters() failed\";\n\n    if (querySpec.Identifier.Status != ERROR_SUCCESS)\n        return \"PerfAddCounters() reports invalid identifier\";\n\n    if (PerfQueryCounterData(hQuery, NULL, 0, &dataSize) != ERROR_NOT_ENOUGH_MEMORY)\n        return \"PerfQueryCounterData(NULL) failed\";\n\n    if (dataSize <= sizeof(PERF_DATA_HEADER) + sizeof(PERF_COUNTER_HEADER)) // PERF_ERROR_RETURN, should not happen\n        return \"instance doesn't exist\";\n\n    FF_AUTO_FREE PERF_DATA_HEADER* const pDataHeader = malloc(dataSize);\n\n    if (PerfQueryCounterData(hQuery, pDataHeader, dataSize, &dataSize) != ERROR_SUCCESS)\n        return \"PerfQueryCounterData(pDataHeader) failed\";\n\n    PERF_COUNTER_HEADER* pCounterHeader = (PERF_COUNTER_HEADER*)(pDataHeader + 1);\n    if (pCounterHeader->dwType != PERF_MULTIPLE_COUNTERS)\n        return \"Invalid counter type\";\n\n    PERF_MULTI_COUNTERS* pMultiCounters = (PERF_MULTI_COUNTERS*)(pCounterHeader + 1);\n    PERF_COUNTER_DATA* pCounterData = (PERF_COUNTER_DATA*)((BYTE*)pMultiCounters + pMultiCounters->dwSize);\n\n    for (ULONG iCounter = 0; iCounter != pMultiCounters->dwCounters; iCounter++)\n    {\n        if (pCounterData->dwDataSize == sizeof(int32_t))\n        {\n            DWORD* pCounterIds = (DWORD*)(pMultiCounters + 1);\n            int32_t value = *(int32_t*)(pCounterData + 1);\n            if (value == 0)\n                return \"Temperature data is zero\";\n\n            switch (pCounterIds[iCounter]) {\n            case 0: // Temperature\n                *result = value - 273;\n                break;\n            case 3: // High Precision Temperature\n                *result = value / 10.0 - 273;\n                break;\n            }\n        }\n\n        pCounterData = (PERF_COUNTER_DATA*)((BYTE*)pCounterData + pCounterData->dwSize);\n    }\n\n    return NULL;\n}\n\n// 7.5\ntypedef struct FFSmbiosProcessorInfo\n{\n    FFSmbiosHeader Header;\n\n    uint8_t SocketDesignation; // string\n    uint8_t ProcessorType; // enum\n    uint8_t ProcessorFamily; // enum\n    uint8_t ProcessorManufacturer; // string\n    uint64_t ProcessorID; // varies\n    uint8_t ProcessorVersion; // string\n    uint8_t Voltage; // varies\n    uint16_t ExternalClock; // varies\n    uint16_t MaxSpeed; // varies\n    uint16_t CurrentSpeed; // varies\n    uint8_t Status; // varies\n    uint8_t ProcessorUpgrade; // enum\n\n    // 2.1+\n    uint16_t L1CacheHandle; // varies\n    uint16_t L2CacheHandle; // varies\n    uint16_t L3CacheHandle; // varies\n\n    // 2.3+\n    uint8_t SerialNumber; // string\n    uint8_t AssertTag; // string\n    uint8_t PartNumber; // string\n\n    // 2.5+\n    uint8_t CoreCount; // varies\n    uint8_t CoreEnabled; // varies\n    uint8_t ThreadCount; // varies\n    uint16_t ProcessorCharacteristics; // bit field\n\n    // 2.6+\n    uint16_t ProcessorFamily2; // enum\n\n    // 3.0+\n    uint16_t CoreCount2; // varies\n    uint16_t CoreEnabled2; // varies\n    uint16_t ThreadCount2; // varies\n\n    // 3.6+\n    uint16_t ThreadEnabled; // varies\n} __attribute__((__packed__)) FFSmbiosProcessorInfo;\n\nstatic_assert(offsetof(FFSmbiosProcessorInfo, ThreadEnabled) == 0x30,\n    \"FFSmbiosProcessorInfo: Wrong struct alignment\");\n\nstatic const char* detectMaxSpeedBySmbios(FFCPUResult* cpu)\n{\n    const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();\n    if (!smbiosTable)\n        return \"Failed to get SMBIOS data\";\n\n    const FFSmbiosProcessorInfo* data = (const FFSmbiosProcessorInfo*) (*smbiosTable)[FF_SMBIOS_TYPE_PROCESSOR_INFO];\n\n    if (!data)\n        return \"Processor information is not found in SMBIOS data\";\n\n    while (data->ProcessorType != 0x03 /*Central Processor*/ || (data->Status & 0b00000111) != 1 /*Enabled*/)\n    {\n        data = (const FFSmbiosProcessorInfo*) ffSmbiosNextEntry(&data->Header);\n        if (data->Header.Type != FF_SMBIOS_TYPE_PROCESSOR_INFO)\n            return \"No active CPU is found in SMBIOS data\";\n    }\n\n    uint32_t speed = data->MaxSpeed;\n    // Sometimes SMBIOS reports invalid value. We assume that max speed is small than 2x of base\n    if (speed < cpu->frequencyBase || speed > cpu->frequencyBase * 2)\n        return \"Possible invalid CPU max speed in SMBIOS data. See #800\";\n\n    cpu->frequencyMax = speed;\n\n    return NULL;\n}\n\nstatic const char* detectNCores(FFCPUResult* cpu)\n{\n    LOGICAL_PROCESSOR_RELATIONSHIP lpr = RelationAll;\n    ULONG length = 0;\n    NtQuerySystemInformationEx(SystemLogicalProcessorAndGroupInformation, &lpr, sizeof(lpr), NULL, 0, &length);\n    if (length == 0)\n        return \"GetLogicalProcessorInformationEx(RelationAll, NULL, &length) failed\";\n\n    SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* FF_AUTO_FREE\n        pProcessorInfo = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)malloc(length);\n\n    if (!NT_SUCCESS(NtQuerySystemInformationEx(SystemLogicalProcessorAndGroupInformation, &lpr, sizeof(lpr), pProcessorInfo, length, &length)))\n        return \"GetLogicalProcessorInformationEx(RelationAll, pProcessorInfo, &length) failed\";\n\n    for(\n        SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* ptr = pProcessorInfo;\n        (uint8_t*)ptr < ((uint8_t*)pProcessorInfo) + length;\n        ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(((uint8_t*)ptr) + ptr->Size)\n    )\n    {\n        if (ptr->Relationship == RelationGroup)\n        {\n            for (uint32_t index = 0; index < ptr->Group.ActiveGroupCount; ++index)\n            {\n                cpu->coresOnline += ptr->Group.GroupInfo[index].ActiveProcessorCount;\n                cpu->coresLogical += ptr->Group.GroupInfo[index].MaximumProcessorCount;\n            }\n        }\n        else if (ptr->Relationship == RelationProcessorCore)\n            ++cpu->coresPhysical;\n        else if (ptr->Relationship == RelationProcessorPackage)\n            ++cpu->packages;\n        else if (ptr->Relationship == RelationNumaNode)\n            ++cpu->numaNodes;\n    }\n\n    return NULL;\n}\n\nstatic const char* detectByRegistry(FFCPUResult* cpu)\n{\n    FF_AUTO_CLOSE_FD HANDLE hKey = NULL;\n    if(!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, L\"HARDWARE\\\\DESCRIPTION\\\\System\\\\CentralProcessor\\\\0\", &hKey, NULL))\n        return \"ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, L\\\"HARDWARE\\\\DESCRIPTION\\\\System\\\\CentralProcessor\\\\0\\\", &hKey, NULL) failed\";\n\n    if (ffRegReadValues(hKey, 3, (FFRegValueArg[]) {\n        FF_ARG(cpu->name, L\"ProcessorNameString\"),\n        FF_ARG(cpu->vendor, L\"VendorIdentifier\"),\n        FF_ARG(cpu->frequencyBase, L\"~MHz\"),\n    }, NULL))\n        ffStrbufTrimRightSpace(&cpu->vendor);\n    else\n        return \"ffRegReadValues() failed for CPU registry key\";\n\n    return NULL;\n}\n\nstatic const char* detectCoreTypes(FFCPUResult* cpu)\n{\n    FF_AUTO_FREE PROCESSOR_POWER_INFORMATION* pinfo = calloc(cpu->coresLogical, sizeof(PROCESSOR_POWER_INFORMATION));\n    if (!NT_SUCCESS(NtPowerInformation(ProcessorInformation, NULL, 0, pinfo, (ULONG) sizeof(PROCESSOR_POWER_INFORMATION) * cpu->coresLogical)))\n        return \"NtPowerInformation(ProcessorInformation, NULL, 0, pinfo, size) failed\";\n\n    for (uint32_t icore = 0; icore < cpu->coresLogical && pinfo[icore].MhzLimit; ++icore)\n    {\n        uint32_t ifreq = 0;\n        while (cpu->coreTypes[ifreq].freq != pinfo[icore].MhzLimit && cpu->coreTypes[ifreq].freq > 0)\n            ++ifreq;\n        if (cpu->coreTypes[ifreq].freq == 0)\n            cpu->coreTypes[ifreq].freq = pinfo[icore].MhzLimit;\n        ++cpu->coreTypes[ifreq].count;\n    }\n\n    if (cpu->frequencyBase == 0)\n        cpu->frequencyBase = pinfo->MaxMhz;\n    return NULL;\n}\n\nconst char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu)\n{\n    detectNCores(cpu);\n\n    const char* error = detectByRegistry(cpu);\n    if (error)\n        return error;\n\n    ffCPUDetectByCpuid(cpu);\n    if (options->showPeCoreCount) detectCoreTypes(cpu);\n\n    if (cpu->frequencyMax == 0)\n        detectMaxSpeedBySmbios(cpu);\n\n    if(options->temp)\n        detectThermalTemp(options, &cpu->temperature);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/cpucache/cpucache.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/cpucache/option.h\"\n\ntypedef enum __attribute__((__packed__)) FFCPUCacheType\n{\n    FF_CPU_CACHE_TYPE_UNIFIED = 0,\n    FF_CPU_CACHE_TYPE_INSTRUCTION = 1,\n    FF_CPU_CACHE_TYPE_DATA = 2,\n    FF_CPU_CACHE_TYPE_TRACE = 3,\n} FFCPUCacheType;\n\ntypedef struct FFCPUCache\n{\n    uint32_t size;\n    uint32_t num;\n    uint32_t lineSize;\n    FFCPUCacheType type;\n} FFCPUCache;\n\ntypedef struct FFCPUCacheResult\n{\n    FFlist caches[4]; // L1, L2, L3, L4(?)\n} FFCPUCacheResult;\n\nconst char* ffDetectCPUCache(FFCPUCacheResult* result);\n\nstatic inline FFCPUCache* ffCPUCacheAddItem(FFCPUCacheResult* result, uint32_t level, uint32_t size, uint32_t lineSize, FFCPUCacheType type)\n{\n    FFlist* cacheLevel = &result->caches[level - 1];\n\n    FF_LIST_FOR_EACH(FFCPUCache, item, *cacheLevel)\n    {\n        if (item->type == type && item->size == size && item->lineSize == lineSize)\n        {\n            item->num++;\n            return item;\n        }\n    }\n\n    FFCPUCache* item = (FFCPUCache*)ffListAdd(cacheLevel);\n    *item = (FFCPUCache) {\n        .size = size,\n        .num = 1,\n        .lineSize = lineSize,\n        .type = type,\n    };\n    return item;\n}\n"
  },
  {
    "path": "src/detection/cpucache/cpucache_apple.c",
    "content": "#include \"cpucache.h\"\n#include \"common/sysctl.h\"\n#include \"common/stringUtils.h\"\n\nconst char* ffDetectCPUCache(FFCPUCacheResult* result)\n{\n    // https://developer.apple.com/documentation/kernel/1387446-sysctlbyname/determining_system_capabilities#3901385\n    uint32_t nPerfLevels = (uint32_t) ffSysctlGetInt(\"hw.nperflevels\", 0);\n    if (nPerfLevels <= 0) return \"sysctl(hw.nperflevels) failed\";\n\n    // macOS provides the global system cache line size\n    uint32_t lineSize = (uint32_t) ffSysctlGetInt64(\"hw.cachelinesize\", 0);\n\n    char sysctlKey[128] = \"hw.perflevelN.\";\n    char* pNum = sysctlKey + strlen(\"hw.perflevel\");\n    char* pSubkey = sysctlKey + strlen(\"hw.perflevelN.\");\n    const size_t lenLeft = ARRAY_SIZE(sysctlKey) - strlen(\"hw.perflevelN.\");\n\n    for (uint32_t i = 0; i < nPerfLevels; ++i)\n    {\n        *pNum = (char) ('0' + i);\n\n        ffStrCopy(pSubkey, \"physicalcpu\", lenLeft);\n        uint32_t ncpu = (uint32_t) ffSysctlGetInt(sysctlKey, 0);\n        if (ncpu <= 0) continue;\n\n        ffStrCopy(pSubkey, \"l1icachesize\", lenLeft);\n        uint32_t size = (uint32_t) ffSysctlGetInt(sysctlKey, 0);\n        if (size)\n            ffCPUCacheAddItem(result, 1, size, lineSize, FF_CPU_CACHE_TYPE_INSTRUCTION)->num = ncpu;\n\n        ffStrCopy(pSubkey, \"l1dcachesize\", lenLeft);\n        size = (uint32_t) ffSysctlGetInt(sysctlKey, 0);\n        if (size)\n            ffCPUCacheAddItem(result, 1, size, lineSize, FF_CPU_CACHE_TYPE_DATA)->num = ncpu;\n\n        ffStrCopy(pSubkey, \"l2cachesize\", lenLeft);\n        size = (uint32_t) ffSysctlGetInt(sysctlKey, 0);\n        if (size)\n        {\n            ffStrCopy(pSubkey, \"cpusperl2\", lenLeft);\n            uint32_t cpuSper = (uint32_t) ffSysctlGetInt(sysctlKey, 0);\n            if (cpuSper)\n                ffCPUCacheAddItem(result, 2, size, lineSize, FF_CPU_CACHE_TYPE_UNIFIED)->num = ncpu / cpuSper;\n        }\n\n        ffStrCopy(pSubkey, \"l3cachesize\", lenLeft);\n        size = (uint32_t) ffSysctlGetInt(sysctlKey, 0);\n        if (size)\n        {\n            ffStrCopy(pSubkey, \"cpusperl3\", lenLeft);\n            uint32_t cpuSper = (uint32_t) ffSysctlGetInt(sysctlKey, 0);\n            if (cpuSper)\n                ffCPUCacheAddItem(result, 3, size, lineSize, FF_CPU_CACHE_TYPE_UNIFIED)->num = ncpu / cpuSper;\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/cpucache/cpucache_linux.c",
    "content": "#include \"cpucache.h\"\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n\nstatic const char* parseCpuCacheIndex(FFstrbuf* path, FFCPUCacheResult* result, FFstrbuf* buffer, FFstrbuf* added)\n{\n    uint32_t baseLen = path->length;\n    ffStrbufAppendS(path, \"/level\");\n    if (!ffReadFileBuffer(path->chars, buffer))\n        return \"ffReadFileBuffer(\\\"/sys/devices/system/cpu/cpuX/cache/indexX/level\\\") == NULL\";\n\n    uint32_t level = (uint32_t) ffStrbufToUInt(buffer, 0);\n    if (level < 1 || level > 4) return \"level < 1 || level > 4\";\n\n    ffStrbufSubstrBefore(path, baseLen);\n    ffStrbufAppendS(path, \"/size\");\n    if (!ffReadFileBuffer(path->chars, buffer))\n        return \"ffReadFileBuffer(\\\"/sys/devices/system/cpu/cpuX/cache/indexX/size\\\") == NULL\";\n\n    uint32_t sizeKb = (uint32_t) ffStrbufToUInt(buffer, 0);\n    if (sizeKb == 0) return \"size == 0\";\n\n    ffStrbufSubstrBefore(path, baseLen);\n    ffStrbufAppendS(path, \"/type\");\n    if (!ffReadFileBuffer(path->chars, buffer))\n        return \"ffReadFileBuffer(\\\"/sys/devices/system/cpu/cpuX/cache/indexX/type\\\") == NULL\";\n    ffStrbufTrimRightSpace(buffer);\n\n    FFCPUCacheType cacheType = 0;\n    switch (buffer->chars[0])\n    {\n        case 'I': cacheType = FF_CPU_CACHE_TYPE_INSTRUCTION; break;\n        case 'D': cacheType = FF_CPU_CACHE_TYPE_DATA; break;\n        case 'U': cacheType = FF_CPU_CACHE_TYPE_UNIFIED; break;\n        case 'T': cacheType = FF_CPU_CACHE_TYPE_TRACE; break;\n        default: return \"unknown cache type\";\n    }\n\n    uint32_t lineSize = 0;\n    ffStrbufSubstrBefore(path, baseLen);\n    ffStrbufAppendS(path, \"/coherency_line_size\");\n    if (ffReadFileBuffer(path->chars, buffer))\n        lineSize = (uint32_t) ffStrbufToUInt(buffer, 0);\n\n    ffStrbufSubstrBefore(path, baseLen);\n    ffStrbufAppendS(path, \"/shared_cpu_list\");\n    ffStrbufClear(buffer);\n    ffStrbufAppendC(buffer, '[');\n    if (!ffAppendFileBuffer(path->chars, buffer))\n        return \"ffAppendFileBuffer(\\\"/sys/devices/system/cpu/cpuX/cache/indexX/shared_cpu_list\\\") == NULL\";\n    ffStrbufTrimRightSpace(buffer);\n\n    // deduplicate shared caches\n    ffStrbufAppendF(buffer, \"_%u_%u_%u_%u]\", level, sizeKb, lineSize, cacheType);\n\n    if (ffStrbufContain(added, buffer)) return NULL;\n    ffStrbufAppend(added, buffer);\n    ffCPUCacheAddItem(result, level, sizeKb * 1024, lineSize, cacheType);\n    return NULL;\n}\n\nstatic const char* parseCpuCache(FFstrbuf* path, FFCPUCacheResult* result, FFstrbuf* buffer, FFstrbuf* added)\n{\n    ffStrbufAppendS(path, \"/cache/\");\n    uint32_t baseLen = path->length;\n    FF_AUTO_CLOSE_DIR DIR* pathCacheDir = opendir(path->chars);\n    if (!pathCacheDir)\n        return \"opendir(\\\"/sys/devices/system/cpu/cpuX/cache/\\\") == NULL\";\n\n    struct dirent* pathCacheEntry;\n    while ((pathCacheEntry = readdir(pathCacheDir)) != NULL)\n    {\n        if (!ffStrStartsWith(pathCacheEntry->d_name, \"index\")\n            || !ffCharIsDigit(pathCacheEntry->d_name[strlen(\"index\")])) continue;\n\n        ffStrbufAppendS(path, pathCacheEntry->d_name);\n        const char* error = parseCpuCacheIndex(path, result, buffer, added);\n        if (error) return error;\n        ffStrbufSubstrBefore(path, baseLen);\n    }\n\n    return NULL;\n}\n\nconst char* ffDetectCPUCache(FFCPUCacheResult* result)\n{\n    // https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-devices-system-cpu\n    FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateS(\"/sys/devices/system/cpu/\");\n    uint32_t baseLen = path.length;\n    FF_AUTO_CLOSE_DIR DIR* pathCpuDir = opendir(path.chars);\n    if (!pathCpuDir)\n        return \"opendir(\\\"/sys/devices/system/cpu/\\\") == NULL\";\n\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY added = ffStrbufCreate();\n\n    struct dirent* pathCpuEntry;\n    while ((pathCpuEntry = readdir(pathCpuDir)) != NULL)\n    {\n        if (!ffStrStartsWith(pathCpuEntry->d_name, \"cpu\") ||\n            !ffCharIsDigit(pathCpuEntry->d_name[strlen(\"cpu\")])) continue;\n\n        ffStrbufAppendS(&path, pathCpuEntry->d_name);\n        const char* error = parseCpuCache(&path, result, &buffer, &added);\n        if (error) return error;\n        ffStrbufSubstrBefore(&path, baseLen);\n    }\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/cpucache/cpucache_nosupport.c",
    "content": "#include \"cpucache.h\"\n\nconst char* ffDetectCPUCache(FF_MAYBE_UNUSED FFCPUCacheResult* result)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/cpucache/cpucache_shared.c",
    "content": "#include \"cpucache.h\"\n#include \"common/smbiosHelper.h\"\n#include \"common/stringUtils.h\"\n\ntypedef struct FFSmbiosCacheInfo\n{\n    FFSmbiosHeader Header;\n\n    uint8_t SocketDesignation; // string\n    uint16_t CacheConfiguration; // varies\n    uint16_t MaximumCacheSize; // varies\n    uint16_t InstalledSize; // varies\n    uint16_t SupportedSramType; // bit field\n    uint16_t CurrentSramType; // bit field\n\n    // 2.1+\n    uint8_t CacheSpeed; // varies\n    uint8_t ErrorCorrectionType; // enum\n    uint8_t SystemCacheType; // enum\n    uint8_t Associativity; // enum\n\n    // 3.1+\n    uint32_t MaximumCacheSize2; // bit field\n    uint32_t InstalledCacheSize2; // bit field\n} __attribute__((__packed__)) FFSmbiosCacheInfo;\n\nstatic_assert(offsetof(FFSmbiosCacheInfo, InstalledCacheSize2) == 0x17,\n    \"FFSmbiosCacheInfo: Wrong struct alignment\");\n\nconst char* ffDetectCPUCache(FFCPUCacheResult* result)\n{\n    const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();\n    if (!smbiosTable)\n        return \"Failed to get SMBIOS data\";\n\n    const FFSmbiosCacheInfo* data = (const FFSmbiosCacheInfo*) (*smbiosTable)[FF_SMBIOS_TYPE_CACHE_INFO];\n    if (!data)\n        return \"Cache information is not found in SMBIOS data\";\n\n    for (; data->Header.Type == FF_SMBIOS_TYPE_CACHE_INFO;\n           data = (const FFSmbiosCacheInfo*) ffSmbiosNextEntry(&data->Header))\n    {\n        bool enabled = !!(data->CacheConfiguration & (1 << 7));\n        if (!enabled)\n            continue;\n\n        uint32_t size = data->InstalledSize;\n        if (size == 0)\n            continue;\n\n        if (data->InstalledSize != 0xFFFF)\n        {\n            size *= (size >> 15 ? 64 : 1) * 1024u;\n        }\n        else if (data->Header.Length > offsetof(FFSmbiosCacheInfo, InstalledCacheSize2))\n        {\n            size = data->InstalledCacheSize2;\n            size *= (size >> 31 ? 64 : 1) * 1024u;\n        }\n\n        uint32_t level = (data->CacheConfiguration & 0b111u) + 1;\n\n        FFCPUCacheType type;\n        switch (data->SystemCacheType)\n        {\n            case 3: type = FF_CPU_CACHE_TYPE_INSTRUCTION; break;\n            case 4: type = FF_CPU_CACHE_TYPE_DATA; break;\n            default: type = FF_CPU_CACHE_TYPE_UNIFIED; break;\n        }\n\n        ffCPUCacheAddItem(result, level, size, 0, type);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/cpucache/cpucache_windows.c",
    "content": "#include \"cpucache.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/windows/nt.h\"\n\nconst char* ffDetectCPUCache(FFCPUCacheResult* result)\n{\n    LOGICAL_PROCESSOR_RELATIONSHIP lpr = RelationCache;\n    DWORD length = 0;\n    NtQuerySystemInformationEx(SystemLogicalProcessorAndGroupInformation, &lpr, sizeof(lpr), NULL, 0, &length);\n    if (length == 0)\n        return \"GetLogicalProcessorInformationEx(RelationCache, NULL, &length) failed\";\n\n    SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* FF_AUTO_FREE\n        pProcessorInfo = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)malloc(length);\n\n    if (!NT_SUCCESS(NtQuerySystemInformationEx(SystemLogicalProcessorAndGroupInformation, &lpr, sizeof(lpr), pProcessorInfo, length, &length)))\n        return \"GetLogicalProcessorInformationEx(RelationCache, pProcessorInfo, &length) failed\";\n\n    for(\n        SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* ptr = pProcessorInfo;\n        (uint8_t*)ptr < ((uint8_t*)pProcessorInfo) + length;\n        ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(((uint8_t*)ptr) + ptr->Size)\n    )\n    {\n        if(__builtin_expect(ptr->Relationship == RelationCache && ptr->Cache.Level > 0 && ptr->Cache.Level <= 4, true))\n        {\n            FFCPUCacheType cacheType = 0;\n            switch (ptr->Cache.Type)\n            {\n            case CacheUnified: cacheType = FF_CPU_CACHE_TYPE_UNIFIED; break;\n            case CacheInstruction: cacheType = FF_CPU_CACHE_TYPE_INSTRUCTION; break;\n            case CacheData: cacheType = FF_CPU_CACHE_TYPE_DATA; break;\n            case CacheTrace: cacheType = FF_CPU_CACHE_TYPE_TRACE; break;\n            default: break;\n            }\n            ffCPUCacheAddItem(result, ptr->Cache.Level, ptr->Cache.CacheSize, ptr->Cache.LineSize, cacheType);\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/cpuusage/cpuusage.c",
    "content": "#include \"fastfetch.h\"\n#include \"detection/cpuusage/cpuusage.h\"\n#include \"common/time.h\"\n\n#include <stdint.h>\n\nstatic FFlist cpuTimes1;\nstatic uint64_t startTime;\n\nvoid ffPrepareCPUUsage(void)\n{\n    if (cpuTimes1.elementSize != 0) return; // Already prepared\n\n    ffListInit(&cpuTimes1, sizeof(FFCpuUsageInfo));\n    ffGetCpuUsageInfo(&cpuTimes1);\n    startTime = ffTimeGetNow();\n}\n\nconst char* ffGetCpuUsageResult(FFCPUUsageOptions* options, FFlist* result)\n{\n    const char* error = NULL;\n    if(cpuTimes1.elementSize == 0)\n    {\n        ffListInit(&cpuTimes1, sizeof(FFCpuUsageInfo));\n        error = ffGetCpuUsageInfo(&cpuTimes1);\n        if(error) return error;\n        ffTimeSleep(options->waitTime);\n    }\n    else\n    {\n        uint64_t elapsedTime = ffTimeGetNow() - startTime;\n        if (elapsedTime < options->waitTime)\n            ffTimeSleep(options->waitTime - (uint32_t)elapsedTime);\n    }\n\n    if(cpuTimes1.length == 0) return \"No CPU cores found\";\n\n    FF_LIST_AUTO_DESTROY cpuTimes2 = ffListCreate(sizeof(FFCpuUsageInfo));\n    uint32_t retryCount = 0;\n\nretry:\n    error = ffGetCpuUsageInfo(&cpuTimes2);\n    if(error) return error;\n    if(cpuTimes1.length != cpuTimes2.length) return \"Unexpected CPU usage result\";\n\n    for (uint32_t i = 0; i < cpuTimes1.length; ++i)\n    {\n        FFCpuUsageInfo* cpuTime1 = FF_LIST_GET(FFCpuUsageInfo, cpuTimes1, i);\n        FFCpuUsageInfo* cpuTime2 = FF_LIST_GET(FFCpuUsageInfo, cpuTimes2, i);\n        if (cpuTime2->totalAll <= cpuTime1->totalAll)\n        {\n            if (++retryCount <= 3)\n            {\n                ffListClear(&cpuTimes2);\n                ffTimeSleep(options->waitTime);\n                goto retry;\n            }\n            return \"CPU time did not increase. Try increasing wait time.\";\n        }\n    }\n\n    for (uint32_t i = 0; i < cpuTimes1.length; ++i)\n    {\n        FFCpuUsageInfo* cpuTime1 = FF_LIST_GET(FFCpuUsageInfo, cpuTimes1, i);\n        FFCpuUsageInfo* cpuTime2 = FF_LIST_GET(FFCpuUsageInfo, cpuTimes2, i);\n        *(double*) ffListAdd(result) = (double)(cpuTime2->inUseAll - cpuTime1->inUseAll) / (double)(cpuTime2->totalAll - cpuTime1->totalAll) * 100;\n        cpuTime1->inUseAll = cpuTime2->inUseAll;\n        cpuTime1->totalAll = cpuTime2->totalAll;\n    }\n    startTime = ffTimeGetNow();\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/cpuusage/cpuusage.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/cpuusage/option.h\"\n\ntypedef struct FFCpuUsageInfo {\n    uint64_t inUseAll;\n    uint64_t totalAll;\n} FFCpuUsageInfo;\nconst char* ffGetCpuUsageInfo(FFlist* cpuTimes);\n\nconst char* ffGetCpuUsageResult(FFCPUUsageOptions* options, FFlist* result); // list of double\n"
  },
  {
    "path": "src/detection/cpuusage/cpuusage_apple.c",
    "content": "#include \"fastfetch.h\"\n#include \"detection/cpuusage/cpuusage.h\"\n\n#include <mach/processor_info.h>\n#include <mach/mach_host.h>\n#include <mach/vm_map.h>\n\nconst char* ffGetCpuUsageInfo(FFlist* cpuTimes)\n{\n    natural_t numCPUs = 0U;\n    processor_info_array_t cpuInfo;\n    mach_msg_type_number_t numCpuInfo;\n\n    if (host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &numCPUs, &cpuInfo, &numCpuInfo) != KERN_SUCCESS)\n        return \"host_processor_info() failed\";\n    if (numCPUs * CPU_STATE_MAX != numCpuInfo)\n        return \"Unexpected host_processor_info() result\";\n\n    for (natural_t i = 0U; i < numCPUs; ++i)\n    {\n        integer_t inUse = cpuInfo[CPU_STATE_MAX * i + CPU_STATE_USER]\n            + cpuInfo[CPU_STATE_MAX * i + CPU_STATE_SYSTEM]\n            + cpuInfo[CPU_STATE_MAX * i + CPU_STATE_NICE];\n        integer_t total = inUse + cpuInfo[CPU_STATE_MAX * i + CPU_STATE_IDLE];\n\n        FFCpuUsageInfo* info = (FFCpuUsageInfo*) ffListAdd(cpuTimes);\n        *info = (FFCpuUsageInfo) {\n            .inUseAll = (uint64_t)inUse,\n            .totalAll = (uint64_t)total,\n        };\n    }\n\n    vm_deallocate(mach_task_self(), (vm_address_t) cpuInfo, numCpuInfo * sizeof(integer_t));\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/cpuusage/cpuusage_bsd.c",
    "content": "#include \"detection/cpuusage/cpuusage.h\"\n#include \"common/mallocHelper.h\"\n\n#include <sys/types.h>\n#include <sys/sysctl.h>\n#include <sys/resource.h>\n#include <stdlib.h>\n\n#if __OpenBSD__ || __NetBSD__\n    #include <sys/sched.h>\n#endif\n\nconst char* ffGetCpuUsageInfo(FFlist* cpuTimes)\n{\n    size_t neededLength = 0;\n#if __OpenBSD__|| __NetBSD__\n    #ifdef KERN_CPTIME\n        int ctls[] = {CTL_KERN, KERN_CPTIME};\n    #else\n        int ctls[] = {CTL_KERN, KERN_CP_TIME};\n    #endif\n    if (sysctl(ctls, 2, NULL, &neededLength, NULL, 0) != 0)\n        return \"sysctl({CTL_KERN, KERN_CPTIME}, 2, NULL) failed\";\n#else\n    if(sysctlbyname(\"kern.cp_times\", NULL, &neededLength, NULL, 0) != 0)\n        return \"sysctlbyname(kern.cp_times, NULL) failed\";\n#endif\n\n    uint32_t coreCount = (uint32_t) (neededLength / (CPUSTATES * sizeof(uint64_t)));\n    assert(coreCount > 0);\n\n    FF_AUTO_FREE uint64_t (*cpTimes)[CPUSTATES] = malloc(neededLength);\n\n#if __OpenBSD__ || __NetBSD__\n    if (sysctl(ctls, 2, cpTimes, &neededLength, NULL, 0) != 0)\n        return \"sysctl({CTL_KERN, KERN_CPTIME}, 2, NULL) failed\";\n#else\n    if(sysctlbyname(\"kern.cp_times\", cpTimes, &neededLength, NULL, 0) != 0)\n        return \"sysctlbyname(kern.cp_times, cpTime) failed\";\n#endif\n\n    for (uint32_t i = 0; i < coreCount; ++i)\n    {\n        uint64_t* cpTime = cpTimes[i];\n        uint64_t inUse = cpTime[CP_USER] + cpTime[CP_NICE] + cpTime[CP_SYS] + cpTime[CP_INTR];\n        uint64_t total = inUse + cpTime[CP_IDLE];\n\n        FFCpuUsageInfo* info = (FFCpuUsageInfo*) ffListAdd(cpuTimes);\n        *info = (FFCpuUsageInfo) {\n            .inUseAll = inUse,\n            .totalAll = total,\n        };\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/cpuusage/cpuusage_haiku.c",
    "content": "#include \"fastfetch.h\"\n#include \"detection/cpuusage/cpuusage.h\"\n#include \"common/mallocHelper.h\"\n\n#include <OS.h>\n\nconst char* ffGetCpuUsageInfo(FFlist* cpuTimes)\n{\n    system_info sysInfo;\n    if (get_system_info(&sysInfo) != B_OK)\n        return \"get_system_info() failed\";\n\n    FF_AUTO_FREE cpu_info* cpuInfo = malloc(sizeof(*cpuInfo) * sysInfo.cpu_count);\n    if (get_cpu_info(0, sysInfo.cpu_count, cpuInfo) != B_OK)\n        return \"get_cpu_info() failed\";\n\n    uint64_t uptime = (uint64_t) system_time();\n\n    for (uint32_t i = 0; i < sysInfo.cpu_count; ++i)\n    {\n        FFCpuUsageInfo* info = (FFCpuUsageInfo*) ffListAdd(cpuTimes);\n        info->inUseAll = (uint64_t) cpuInfo[i].active_time;\n        info->totalAll = uptime;\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/cpuusage/cpuusage_linux.c",
    "content": "#include \"fastfetch.h\"\n#include \"detection/cpuusage/cpuusage.h\"\n#include \"common/io.h\"\n\n#include <stdio.h>\n#include <inttypes.h>\n\nconst char* ffGetCpuUsageInfo(FFlist* cpuTimes)\n{\n    char buf[PROC_FILE_BUFFSIZ];\n    ssize_t nRead = ffReadFileData(\"/proc/stat\", ARRAY_SIZE(buf) - 1, buf);\n    if(nRead < 0)\n    {\n        #ifdef __ANDROID__\n        return \"Accessing \\\"/proc/stat\\\" is restricted on Android O+\";\n        #else\n        return \"ffReadFileData(\\\"/proc/stat\\\", ARRAY_SIZE(buf) - 1, buf) failed\";\n        #endif\n    }\n    buf[nRead] = '\\0';\n\n    // Skip first line\n    char *start = NULL;\n    if((start = strchr(buf, '\\n')) == NULL)\n        return \"skip first line failed\";\n    ++start;\n\n    uint64_t user = 0, nice = 0, system = 0, idle = 0, iowait = 0, irq = 0, softirq = 0;\n    char *token = NULL;\n    while ((token = strchr(start, '\\n')))\n    {\n        if(sscanf(start, \"cpu%*d%\" PRIu64 \"%\" PRIu64 \"%\" PRIu64 \"%\" PRIu64 \"%\" PRIu64 \"%\" PRIu64 \"%\" PRIu64 \"%*[^\\n]\\n\", &user, &nice, &system, &idle, &iowait, &irq, &softirq) == 7)\n        {\n            uint64_t inUse = user + nice + system + irq + softirq;\n            uint64_t total = inUse + idle + iowait;\n\n            FFCpuUsageInfo* info = (FFCpuUsageInfo*) ffListAdd(cpuTimes);\n            *info = (FFCpuUsageInfo) {\n                .inUseAll = inUse,\n                .totalAll = total,\n            };\n        }\n        else\n            break;\n        start = token + 1;\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/cpuusage/cpuusage_nosupport.c",
    "content": "#include \"fastfetch.h\"\n#include \"detection/cpuusage/cpuusage.h\"\n\nconst char* ffGetCpuUsageInfo(FF_MAYBE_UNUSED FFlist* cpuTimes)\n{\n    return \"Not support on this platform\";\n}\n"
  },
  {
    "path": "src/detection/cpuusage/cpuusage_sunos.c",
    "content": "#include \"fastfetch.h\"\n#include \"detection/cpuusage/cpuusage.h\"\n\n#include <kstat.h>\n#include <sys/sysinfo.h>\n\nstatic inline void kstatFreeWrap(kstat_ctl_t** pkc)\n{\n    assert(pkc);\n    if (*pkc)\n        kstat_close(*pkc);\n}\n\nconst char* ffGetCpuUsageInfo(FFlist* cpuTimes)\n{\n    __attribute__((__cleanup__(kstatFreeWrap))) kstat_ctl_t* kc = kstat_open();\n    if (!kc)\n        return \"kstat_open() failed\";\n\n    for (int i = 0;; ++i)\n    {\n        kstat_t* ks = kstat_lookup(kc, \"cpu_stat\", i, NULL);\n\n        cpu_stat_t cs;\n        if (!ks || kstat_read(kc, ks, &cs) < 0)\n            break;\n\n        uint64_t inUse = cs.cpu_sysinfo.cpu[CPU_USER] + cs.cpu_sysinfo.cpu[CPU_KERNEL];\n        uint64_t total = inUse + cs.cpu_sysinfo.cpu[CPU_IDLE] + cs.cpu_sysinfo.cpu[CPU_WAIT];\n\n        FFCpuUsageInfo* info = (FFCpuUsageInfo*) ffListAdd(cpuTimes);\n        *info = (FFCpuUsageInfo) {\n            .inUseAll = inUse,\n            .totalAll = total,\n        };\n    }\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/cpuusage/cpuusage_windows.c",
    "content": "#include \"detection/cpuusage/cpuusage.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/debug.h\"\n\n#include <ntstatus.h>\n#include <windows.h>\n#include <wchar.h>\n#include \"common/windows/perflib_.h\"\n#include \"common/windows/nt.h\"\n\nstatic const char* getInfoByNqsi(FFlist* cpuTimes)\n{\n    ULONG size = 0;\n    if(NtQuerySystemInformation(SystemProcessorPerformanceInformation, NULL, 0, &size) != STATUS_INFO_LENGTH_MISMATCH)\n        return \"NtQuerySystemInformation(SystemProcessorPerformanceInformation, NULL) failed\";\n\n    SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION* FF_AUTO_FREE pinfo = (SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION*)malloc(size);\n    if(!NT_SUCCESS(NtQuerySystemInformation(SystemProcessorPerformanceInformation, pinfo, size, &size)))\n        return \"NtQuerySystemInformation(SystemProcessorPerformanceInformation, size) failed\";\n\n    for (uint32_t i = 0; i < size / sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION); ++i)\n    {\n        SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION* coreInfo = pinfo + i;\n\n        // KernelTime includes IdleTime and DpcTime.\n        coreInfo->KernelTime.QuadPart -= coreInfo->IdleTime.QuadPart;\n\n        uint64_t inUse = (uint64_t) (coreInfo->UserTime.QuadPart + coreInfo->KernelTime.QuadPart);\n        uint64_t total = inUse + (uint64_t)coreInfo->IdleTime.QuadPart;\n\n        FFCpuUsageInfo* info = (FFCpuUsageInfo*) ffListAdd(cpuTimes);\n        *info = (FFCpuUsageInfo) {\n            .inUseAll = inUse,\n            .totalAll = total,\n        };\n    }\n\n    return NULL;\n}\n\nstatic const char* getInfoByPerflib(FFlist* cpuTimes)\n{\n    static HANDLE hQuery = NULL;\n\n    if (hQuery == NULL)\n    {\n        struct FFPerfQuerySpec\n        {\n            PERF_COUNTER_IDENTIFIER Identifier;\n            WCHAR Name[16];\n        } querySpec = {\n            .Identifier = {\n                // Processor Information GUID\n                // HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib\\_V2Providers\\{383487a6-3676-4870-a4e7-d45b30c35629}\\{b4fc721a-0378-476f-89ba-a5a79f810b36}\n                .CounterSetGuid = { 0xb4fc721a, 0x0378, 0x476f, {0x89, 0xba, 0xa5, 0xa7, 0x9f, 0x81, 0x0b, 0x36} },\n                .Size = sizeof(querySpec),\n                .CounterId = PERF_WILDCARD_COUNTER, // https://learn.microsoft.com/en-us/windows/win32/perfctrs/using-the-perflib-functions-to-consume-counter-data\n                .InstanceId = PERF_WILDCARD_COUNTER,\n            },\n            .Name = PERF_WILDCARD_INSTANCE,\n        };\n\n        if (PerfOpenQueryHandle(NULL, &hQuery) != ERROR_SUCCESS)\n        {\n            PerfCloseQueryHandle(hQuery);\n            hQuery = INVALID_HANDLE_VALUE;\n            return \"PerfOpenQueryHandle() failed\";\n        }\n\n        if (PerfAddCounters(hQuery, &querySpec.Identifier, sizeof(querySpec)) != ERROR_SUCCESS)\n        {\n            PerfCloseQueryHandle(hQuery);\n            hQuery = INVALID_HANDLE_VALUE;\n            return \"PerfAddCounters() failed\";\n        }\n\n        if (querySpec.Identifier.Status != ERROR_SUCCESS)\n        {\n            PerfCloseQueryHandle(hQuery);\n            hQuery = INVALID_HANDLE_VALUE;\n            return \"PerfAddCounters() reports invalid identifier\";\n        }\n    }\n\n    if (hQuery == INVALID_HANDLE_VALUE)\n        return \"Init hQuery failed\";\n\n    DWORD dataSize = 0;\n    if (PerfQueryCounterData(hQuery, NULL, 0, &dataSize) != ERROR_NOT_ENOUGH_MEMORY)\n        return \"PerfQueryCounterData(NULL) failed\";\n\n    if (dataSize <= sizeof(PERF_DATA_HEADER) + sizeof(PERF_COUNTER_HEADER))\n        return \"instance doesn't exist\";\n\n    FF_AUTO_FREE PERF_DATA_HEADER* const pDataHeader = (PERF_DATA_HEADER*)malloc(dataSize);\n    if (PerfQueryCounterData(hQuery, pDataHeader, dataSize, &dataSize) != ERROR_SUCCESS)\n        return \"PerfQueryCounterData(pDataHeader) failed\";\n\n    PERF_COUNTER_HEADER* pCounterHeader = (PERF_COUNTER_HEADER*)(pDataHeader + 1);\n    if (pCounterHeader->dwType != PERF_COUNTERSET)\n        return \"Invalid counter type\";\n\n    PERF_MULTI_COUNTERS* pMultiCounters = (PERF_MULTI_COUNTERS*)(pCounterHeader + 1);\n    if (pMultiCounters->dwCounters == 0)\n        return \"No CPU counters found\";\n\n    PERF_MULTI_INSTANCES* pMultiInstances = (PERF_MULTI_INSTANCES*)((BYTE*)pMultiCounters + pMultiCounters->dwSize);\n    if (pMultiInstances->dwInstances == 0)\n        return \"No CPU instances found\";\n\n    PERF_INSTANCE_HEADER* pInstanceHeader = (PERF_INSTANCE_HEADER*)(pMultiInstances + 1);\n    for (DWORD iInstance = 0; iInstance < pMultiInstances->dwInstances; ++iInstance)\n    {\n        const wchar_t* instanceName = (const wchar_t*)((BYTE*)pInstanceHeader + sizeof(*pInstanceHeader));\n\n        PERF_COUNTER_DATA* pCounterData = (PERF_COUNTER_DATA*)((BYTE*)pInstanceHeader + pInstanceHeader->Size);\n\n        uint64_t processorUtility = UINT64_MAX, utilityBase = UINT64_MAX;\n        for (ULONG iCounter = 0; iCounter != pMultiCounters->dwCounters; iCounter++)\n        {\n            DWORD* pCounterIds = (DWORD*)(pMultiCounters + 1);\n            // https://learn.microsoft.com/en-us/windows/win32/perfctrs/using-the-perflib-functions-to-consume-counter-data\n            switch (pCounterIds[iCounter]) {\n            case 26: // % Processor Utility (#26, Type=PERF_AVERAGE_BULK)\n                assert(pCounterData->dwDataSize == sizeof(uint64_t));\n                processorUtility = *(uint64_t*)(pCounterData + 1);\n                break;\n            case 27: // % Utility Base (#27, Type=PERF_AVERAGE_BASE)\n                assert(pCounterData->dwDataSize == sizeof(uint32_t));\n                utilityBase = *(uint32_t*)(pCounterData + 1) * 100LLU;\n                break;\n            }\n\n            pCounterData = (PERF_COUNTER_DATA*)((BYTE*)pCounterData + pCounterData->dwSize);\n        }\n\n        if (wcschr(instanceName, L'_') == NULL /* ignore `_Total` */)\n        {\n            if (processorUtility == UINT64_MAX)\n                return \"Counter \\\"% Processor Utility\\\" are not supported\";\n\n            FFCpuUsageInfo* info = (FFCpuUsageInfo*) ffListAdd(cpuTimes);\n            *info = (FFCpuUsageInfo) {\n                .inUseAll = processorUtility,\n                .totalAll = utilityBase,\n            };\n        }\n\n        pInstanceHeader = (PERF_INSTANCE_HEADER*)pCounterData;\n    }\n\n    return NULL;\n}\n\nconst char* ffGetCpuUsageInfo(FFlist* cpuTimes)\n{\n    const char* error = NULL;\n\n    if (ffIsWindows10OrGreater())\n    {\n        error = getInfoByPerflib(cpuTimes);\n        FF_DEBUG(\"Get CPU usage info by Perflib: %s\", error ?: \"success\");\n        if (!error) return NULL;\n        ffListClear(cpuTimes);\n    }\n\n    error = getInfoByNqsi(cpuTimes);\n    FF_DEBUG(\"Get CPU usage info by NtQuerySystemInformation: %s\", error ?: \"success\");\n    return error;\n}\n"
  },
  {
    "path": "src/detection/cursor/cursor.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/cursor/option.h\"\n\ntypedef struct FFCursorResult\n{\n    FFstrbuf theme;\n    FFstrbuf size;\n    FFstrbuf error;\n} FFCursorResult;\n\nvoid ffDetectCursor(FFCursorResult* result);\n"
  },
  {
    "path": "src/detection/cursor/cursor_apple.m",
    "content": "#include \"cursor.h\"\n\n#import <Foundation/Foundation.h>\n\nstatic void appendColor(FFstrbuf* str, NSDictionary* dict)\n{\n    uint32_t r = (uint32_t) (((NSNumber*) dict[@\"red\"]).doubleValue * 255 + .5);\n    uint32_t g = (uint32_t) (((NSNumber*) dict[@\"green\"]).doubleValue * 255 + .5);\n    uint32_t b = (uint32_t) (((NSNumber*) dict[@\"blue\"]).doubleValue * 255 + .5);\n    uint32_t a = (uint32_t) (((NSNumber*) dict[@\"alpha\"]).doubleValue * 255 + .5);\n    uint32_t color = (r << 24) | (g << 16) | (b << 8) | a;\n\n    switch (color)\n    {\n        case 0x000000FF: ffStrbufAppendS(str, \"Black\"); return;\n        case 0x0433FFFF: ffStrbufAppendS(str, \"Blue\"); return;\n        case 0xAA7942FF: ffStrbufAppendS(str, \"Brown\"); return;\n        case 0x00FDFFFF: ffStrbufAppendS(str, \"Cyan\"); return;\n        case 0x00F900FF: ffStrbufAppendS(str, \"Green\"); return;\n        case 0xFF40FFFF: ffStrbufAppendS(str, \"Magenta\"); return;\n        case 0xFF9300FF: ffStrbufAppendS(str, \"Orange\"); return;\n        case 0x942192FF: ffStrbufAppendS(str, \"Purple\"); return;\n        case 0xFF2600FF: ffStrbufAppendS(str, \"Red\"); return;\n        case 0xFFFB00FF: ffStrbufAppendS(str, \"Yellow\"); return;\n        case 0xFFFFFFFF: ffStrbufAppendS(str, \"White\"); return;\n        case 0x00000000: ffStrbufAppendS(str, \"Transparent\"); return;\n        default: ffStrbufAppendF(str, \"#%08X\", color); return;\n    }\n}\n\nvoid ffDetectCursor(FFCursorResult* result)\n{\n    NSError* error;\n    NSString* fileName = [NSString stringWithFormat:@\"file://%s/Library/Preferences/com.apple.universalaccess.plist\", instance.state.platform.homeDir.chars];\n    NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName]\n                                       error:&error];\n    if(error)\n    {\n        ffStrbufAppendS(&result->error, error.localizedDescription.UTF8String);\n        return;\n    }\n\n    NSDictionary* color;\n\n    ffStrbufAppendS(&result->theme, \"Fill - \");\n    if ((color = dict[@\"cursorFill\"]))\n        appendColor(&result->theme, color);\n    else\n        ffStrbufAppendS(&result->theme, \"Black\");\n\n    ffStrbufAppendS(&result->theme, \", Outline - \");\n\n    if ((color = dict[@\"cursorOutline\"]))\n        appendColor(&result->theme, color);\n    else\n        ffStrbufAppendS(&result->theme, \"White\");\n\n    NSNumber* mouseDriverCursorSize = dict[@\"mouseDriverCursorSize\"];\n    if (mouseDriverCursorSize)\n        ffStrbufAppendF(&result->size, \"%d\", (int) (mouseDriverCursorSize.doubleValue * 32 + 0.5));\n    else\n        ffStrbufAppendS(&result->size, \"32\");\n}\n"
  },
  {
    "path": "src/detection/cursor/cursor_linux.c",
    "content": "#include \"cursor.h\"\n\n#include \"detection/gtk_qt/gtk_qt.h\"\n#include \"detection/displayserver/displayserver.h\"\n#include \"common/properties.h\"\n#include \"common/parsing.h\"\n#include \"common/settings.h\"\n#include \"common/stringUtils.h\"\n\n#include <stdlib.h>\n\nstatic bool detectCursorGTK(FFCursorResult* result)\n{\n    const FFGTKResult* gtk = ffDetectGTK4();\n\n    if(gtk->cursor.length == 0)\n        gtk = ffDetectGTK3();\n\n    if(gtk->cursor.length == 0)\n        gtk = ffDetectGTK2();\n\n    if(gtk->cursor.length == 0)\n        return false;\n\n    ffStrbufAppend(&result->theme, &gtk->cursor);\n    ffStrbufAppend(&result->size, &gtk->cursorSize);\n    return true;\n}\n\nstatic void detectCursorFromConfigFile(const char* relativeFilePath, const char* themeStart, const char* themeDefault, const char* sizeStart, const char* sizeDefault, FFCursorResult* result)\n{\n    if(ffParsePropFileConfigValues(relativeFilePath, 2, (FFpropquery[]) {\n        {themeStart, &result->theme},\n        {sizeStart, &result->size}\n    })) {\n\n        if(result->theme.length == 0)\n            ffStrbufAppendS(&result->theme, themeDefault);\n\n        if(result->size.length == 0)\n            ffStrbufAppendS(&result->size, sizeDefault);\n    }\n\n    if(result->theme.length == 0)\n        ffStrbufAppendF(&result->error, \"Couldn't find cursor in %s\", relativeFilePath);\n}\n\nstatic bool detectCursorFromXResources(FFCursorResult* result)\n{\n    ffParsePropFileHomeValues(\".Xresources\", 2, (FFpropquery[]) {\n        {\"Xcursor.theme :\", &result->theme},\n        {\"Xcursor.size :\", &result->size}\n    });\n\n    return result->theme.length > 0;\n}\n\nstatic bool detectCursorFromEnv(FFCursorResult* result)\n{\n    const char* xcursor_theme = getenv(\"XCURSOR_THEME\");\n\n    if(!ffStrSet(xcursor_theme))\n        return false;\n\n    ffStrbufAppendS(&result->theme, xcursor_theme);\n    ffStrbufAppendS(&result->size, getenv(\"XCURSOR_SIZE\"));\n\n    return true;\n}\n\nstatic bool detectCursorHyprcursor(FFCursorResult* result)\n{\n    const char* hyprcursor_theme = getenv(\"HYPRCURSOR_THEME\");\n\n    if(!ffStrSet(hyprcursor_theme))\n        return false;\n\n    ffStrbufAppendS(&result->theme, hyprcursor_theme);\n    ffStrbufAppendS(&result->size, getenv(\"HYPRCURSOR_SIZE\"));\n\n    return true;\n}\n\nvoid ffDetectCursor(FFCursorResult* result)\n{\n    const FFDisplayServerResult* wmde = ffConnectDisplayServer();\n\n    if(ffStrbufEqualS(&wmde->wmPrettyName, FF_WM_PRETTY_WSLG))\n        ffStrbufAppendS(&result->error, \"WSLg uses native windows cursor\");\n    else if(ffStrbufIgnCaseEqualS(&wmde->wmProtocolName, FF_WM_PROTOCOL_TTY))\n        ffStrbufAppendS(&result->error, \"Cursor isn't supported in TTY\");\n    else if(ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_PLASMA))\n        detectCursorFromConfigFile(\"kcminputrc\", \"cursorTheme =\", \"Breeze\", \"cursorSize =\", \"24\", result);\n    else if(ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_LXQT))\n        detectCursorFromConfigFile(\"lxqt/session.conf\", \"cursor_theme =\", \"Adwaita\", \"cursor_size =\", \"24\", result);\n    else if(ffStrbufIgnCaseEqualS(&wmde->wmPrettyName, FF_WM_PRETTY_HYPRLAND) && detectCursorHyprcursor(result))\n        return;\n    else if(\n        !detectCursorGTK(result) &&\n        !detectCursorFromEnv(result) &&\n        !ffParsePropFileHome(\".icons/default/index.theme\", \"Inherits =\", &result->theme) &&\n        !detectCursorFromXResources(result) &&\n        !ffParsePropFileData(\"icons/default/index.theme\", \"Inherits =\", &result->theme)\n    ) ffStrbufAppendS(&result->error, \"Couldn't find cursor\");\n}\n"
  },
  {
    "path": "src/detection/cursor/cursor_nosupport.c",
    "content": "#include \"cursor.h\"\n\nvoid ffDetectCursor(FF_MAYBE_UNUSED FFCursorResult* result)\n{\n    ffStrbufInitS(&result->error, \"Not supported on this platform\");\n}\n"
  },
  {
    "path": "src/detection/cursor/cursor_windows.c",
    "content": "#include \"cursor.h\"\n\n#include \"common/io.h\"\n#include \"common/windows/registry.h\"\n\nvoid ffDetectCursor(FFCursorResult* result)\n{\n    FF_AUTO_CLOSE_FD HANDLE hKey = NULL;\n    if(ffRegOpenKeyForRead(HKEY_CURRENT_USER, L\"Control Panel\\\\Cursors\", &hKey, &result->error))\n    {\n        uint32_t cursorBaseSize;\n        if (ffRegReadValues(hKey, 2, (FFRegValueArg[]) {\n            FF_ARG(result->theme, NULL),\n            FF_ARG(cursorBaseSize, L\"CursorBaseSize\"),\n        }, &result->error))\n            ffStrbufAppendUInt(&result->size, cursorBaseSize);\n    }\n}\n"
  },
  {
    "path": "src/detection/de/de.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/de/option.h\"\n\nconst char* ffDetectDEVersion(const FFstrbuf* deName, FFstrbuf* result, FFDEOptions* options);\n"
  },
  {
    "path": "src/detection/de/de_linux.c",
    "content": "#include \"de.h\"\n\n#include \"common/dbus.h\"\n#include \"common/io.h\"\n#include \"common/library.h\"\n#include \"common/parsing.h\"\n#include \"common/properties.h\"\n#include \"common/processing.h\"\n#include \"common/binary.h\"\n#include \"common/path.h\"\n#include \"detection/displayserver/displayserver.h\"\n\n#include <ctype.h>\n#ifdef __FreeBSD__\n    #include <paths.h>\n    #ifndef _PATH_LOCALBASE\n        #define _PATH_LOCALBASE \"/usr/local\"\n    #endif\n#elif __OpenBSD__\n    #define _PATH_LOCALBASE \"/usr/local\"\n#elif __NetBSD__\n    #define _PATH_LOCALBASE \"/usr/pkg\"\n#endif\n\nstatic void getKDE(FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options)\n{\n#ifdef _PATH_LOCALBASE\n    ffParsePropFile(_PATH_LOCALBASE \"/share/wayland-sessions/plasma.desktop\", \"X-KDE-PluginInfo-Version =\", result);\n    if(result->length == 0)\n        ffParsePropFile(_PATH_LOCALBASE \"/share/xsessions/plasmax11.desktop\", \"X-KDE-PluginInfo-Version =\", result);\n#else\n    ffParsePropFile(FASTFETCH_TARGET_DIR_USR \"/share/wayland-sessions/plasma.desktop\", \"X-KDE-PluginInfo-Version =\", result);\n    if(result->length == 0)\n        ffParsePropFile(FASTFETCH_TARGET_DIR_USR \"/share/xsessions/plasmax11.desktop\", \"X-KDE-PluginInfo-Version =\", result);\n#endif\n\n    if(result->length == 0)\n        ffParsePropFileData(\"xsessions/plasma.desktop\", \"X-KDE-PluginInfo-Version =\", result);\n    if(result->length == 0)\n        ffParsePropFileData(\"xsessions/plasma5.desktop\", \"X-KDE-PluginInfo-Version =\", result);\n\n    if(result->length == 0)\n        ffParsePropFileData(\"wayland-sessions/plasmawayland.desktop\", \"X-KDE-PluginInfo-Version =\", result);\n    if(result->length == 0)\n        ffParsePropFileData(\"wayland-sessions/plasmawayland5.desktop\", \"X-KDE-PluginInfo-Version =\", result);\n\n    if(result->length == 0)\n    {\n        if (ffProcessAppendStdOut(result, (char* const[]){\n            \"plasmashell\",\n            \"--version\",\n            NULL\n        }) == NULL) // plasmashell 5.27.5\n            ffStrbufSubstrAfterLastC(result, ' ');\n    }\n}\n\nstatic const char* getGnomeByDbus(FF_MAYBE_UNUSED FFstrbuf* result)\n{\n#ifdef FF_HAVE_DBUS\n    FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};\n    if (ffDBusLoadData(DBUS_BUS_SESSION, &dbus) != NULL)\n        return \"ffDBusLoadData() failed\";\n\n    ffDBusGetPropertyString(&dbus, \"org.gnome.Shell\", \"/org/gnome/Shell\", \"org.gnome.Shell\", \"ShellVersion\", result);\n    return NULL;\n#else // FF_HAVE_DBUS\n    return \"ffDBusLoadData() failed: dbus support not compiled in\";\n#endif // FF_HAVE_DBUS\n}\n\nstatic void getGnome(FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options)\n{\n    getGnomeByDbus(result);\n\n    if (result->length == 0)\n    {\n        if (ffProcessAppendStdOut(result, (char* const[]){\n            \"gnome-shell\",\n            \"--version\",\n            NULL\n        }) == NULL) // GNOME Shell 44.1\n            ffStrbufSubstrAfterLastC(result, ' ');\n    }\n}\n\nstatic void getCinnamon(FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options)\n{\n    ffStrbufSetS(result, getenv(\"CINNAMON_VERSION\"));\n\n    if (result->length == 0)\n        ffParsePropFileData(\"applications/cinnamon.desktop\", \"X-GNOME-Bugzilla-Version =\", result);\n\n    if (result->length == 0)\n    {\n        if (ffProcessAppendStdOut(result, (char* const[]){\n            \"cinnamon\",\n            \"--version\",\n            NULL\n        }) == NULL) // Cinnamon 6.2.2\n            ffStrbufSubstrAfterLastC(result, ' ');\n    }\n}\n\nstatic void getMate(FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options)\n{\n    FF_STRBUF_AUTO_DESTROY major = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY minor = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY micro = ffStrbufCreate();\n\n    ffParsePropFileDataValues(\"mate-about/mate-version.xml\", 3, (FFpropquery[]) {\n        {\"<platform>\", &major},\n        {\"<minor>\", &minor},\n        {\"<micro>\", &micro}\n    });\n\n    ffParseSemver(result, &major, &minor, &micro);\n\n    if(result->length == 0)\n    {\n        ffProcessAppendStdOut(result, (char* const[]){\n            \"mate-session\",\n            \"--version\",\n            NULL\n        });\n\n        ffStrbufSubstrAfterFirstC(result, ' ');\n        ffStrbufTrim(result, ' ');\n    }\n}\n\nstatic const char* getXfce4ByLib(FFstrbuf* result)\n{\n#ifndef FF_DISABLE_DLOPEN\n    const char* xfce_version_string(void); // from `xfce4/libxfce4util/xfce-misutils.h\n    FF_LIBRARY_LOAD_MESSAGE(xfce4util, \"libxfce4util\" FF_LIBRARY_EXTENSION, 7);\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xfce4util, xfce_version_string);\n    ffStrbufSetS(result, ffxfce_version_string());\n    return NULL;\n#else\n    FF_UNUSED(result);\n    return \"dlopen is disabled\";\n#endif\n}\n\nstatic void getXFCE4(FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options)\n{\n    getXfce4ByLib(result);\n\n    if(result->length == 0)\n    {\n        //This is somewhat slow\n        ffProcessAppendStdOut(result, (char* const[]){\n            \"xfce4-session\",\n            \"--version\",\n            NULL\n        });\n\n        ffStrbufSubstrBeforeFirstC(result, ')');\n        ffStrbufSubstrAfterLastC(result, ' ');\n        ffStrbufTrim(result, ' ');\n    }\n}\n\nstatic void getLXQt(FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options)\n{\n    ffParsePropFileData(\"gconfig/lxqt.pc\", \"Version:\", result);\n\n    if(result->length == 0)\n        ffParsePropFileData(\"cmake/lxqt/lxqt-config.cmake\", \"set ( LXQT_VERSION\", result);\n    if(result->length == 0)\n        ffParsePropFileData(\"cmake/lxqt/lxqt-config-version.cmake\", \"set ( PACKAGE_VERSION\", result);\n\n    if(result->length == 0)\n    {\n        //This is really, really, really slow. Thank you, LXQt developers\n        ffProcessAppendStdOut(result, (char* const[]){\n            \"lxqt-session\",\n            \"-v\",\n            NULL\n        });\n\n        result->length = 0; //don't set '\\0' byte\n        ffParsePropLines(result->chars , \"liblxqt\", result);\n    }\n}\n\nstatic void getBudgie(FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options)\n{\n    ffParsePropFileData(\"budgie/budgie-version.xml\", \"<str>\", result);\n}\n\nstatic void getUnity(FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options)\n{\n    if (ffParsePropFile(\"/usr/bin/unity\", \"parser = OptionParser(version= \\\"%prog \", result))\n        ffStrbufSubstrBeforeFirstC(result, '\"');\n}\n\nstatic bool extractTdeVersion(const char* line, uint32_t len, void *userdata)\n{\n    int count = 0;\n    sscanf(line, \"R%*d.%*d.%*d%n\", &count);\n    if (count == 0) return true;\n\n    ffStrbufSetNS((FFstrbuf*) userdata, len, line);\n    return false;\n}\n\nstatic const char* getTrinity(FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options)\n{\n    FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();\n    const char* error = ffFindExecutableInPath(\"tde-config\", &path);\n    if (error) return \"Failed to find tde-config path\";\n\n    ffStrbufSubstrBeforeLastC(&path, '/');\n    ffStrbufAppendS(&path, \"/../lib/libtdecore.so\");\n\n    if (ffBinaryExtractStrings(path.chars, extractTdeVersion, result, strlen(\"R0.0.0\")) == NULL)\n        return NULL;\n\n    ffStrbufClear(&path);\n    if (ffProcessAppendStdOut(&path, (char* const[]){\n        \"tde-config\",\n        \"--version\",\n        NULL\n    }) == NULL)\n    {\n        ffParsePropLines(path.chars , \"TDE: \", result);\n        return NULL;\n    }\n\n    return \"All methods failed\";\n}\n\nstatic const char* getCosmic(FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options)\n{\n    if (ffProcessAppendStdOut(result, (char* const[]){\n        \"cosmic-comp\",\n        \"--version\",\n        NULL\n    }) == NULL) {\n        // cosmic-comp 0.1.0 (git commit fa88002ba41d2edec25dd7ffdee9719fbb928fc0)\n        ffStrbufSubstrAfterFirstC(result, ' ');\n        ffStrbufSubstrBeforeFirstC(result, ' ');\n        return NULL;\n    }\n\n    return \"All methods failed\";\n}\n\nconst char* ffDetectDEVersion(const FFstrbuf* deName, FFstrbuf* result, FFDEOptions* options)\n{\n    if (!instance.config.general.detectVersion) return \"Disabled by config\";\n\n    if (ffStrbufEqualS(deName, FF_DE_PRETTY_PLASMA))\n        getKDE(result, options);\n    else if (ffStrbufEqualS(deName, FF_DE_PRETTY_GNOME))\n        getGnome(result, options);\n    else if (ffStrbufEqualS(deName, FF_DE_PRETTY_CINNAMON))\n        getCinnamon(result, options);\n    else if (ffStrbufEqualS(deName, FF_DE_PRETTY_XFCE4))\n        getXFCE4(result, options);\n    else if (ffStrbufEqualS(deName, FF_DE_PRETTY_MATE))\n        getMate(result, options);\n    else if (ffStrbufEqualS(deName, FF_DE_PRETTY_LXQT))\n        getLXQt(result, options);\n    else if (ffStrbufEqualS(deName, FF_DE_PRETTY_BUDGIE))\n        getBudgie(result, options);\n    else if (ffStrbufEqualS(deName, FF_DE_PRETTY_UNITY))\n        getUnity(result, options);\n    else if (ffStrbufEqualS(deName, \"trinity\"))\n        getTrinity(result, options);\n    else if (ffStrbufEqualS(deName, \"COSMIC\"))\n        getCosmic(result, options);\n    else\n        return \"Unsupported DE\";\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/de/de_nosupport.c",
    "content": "#include \"de.h\"\n\nconst char* ffDetectDEVersion(FF_MAYBE_UNUSED const FFstrbuf* deName, FF_MAYBE_UNUSED FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/disk/disk.c",
    "content": "#include \"disk.h\"\n\nstatic int compareDisks(const FFDisk* disk1, const FFDisk* disk2)\n{\n    return ffStrbufComp(&disk1->mountpoint, &disk2->mountpoint);\n}\n\nconst char* ffDetectDisks(FFDiskOptions* options, FFlist* disks)\n{\n    const char* error = ffDetectDisksImpl(options, disks);\n\n    if (error) return error;\n    if (disks->length == 0) return NULL;\n\n    //We need to sort the disks, so that we can detect, which disk a path resides on\n    // For example for /boot/efi/bootmgr we need to check /boot/efi before /boot\n    //Note that we sort alphabetically here for a better ordering when printing the list,\n    // so the check must be done in reverse order\n    ffListSort(disks, (void*) compareDisks);\n    FF_LIST_FOR_EACH(FFDisk, disk, *disks)\n    {\n        if(disk->bytesTotal == 0)\n            disk->type |= FF_DISK_VOLUME_TYPE_UNKNOWN_BIT;\n        else\n        {\n            disk->bytesUsed = disk->bytesTotal - (\n                options->calcType == FF_DISK_CALC_TYPE_FREE ? disk->bytesFree : disk->bytesAvailable\n            );\n        }\n    }\n\n    return NULL;\n}\n\n#ifndef _WIN32\n#include <fnmatch.h>\n\nbool ffDiskMatchesFolderPatterns(FFstrbuf* folders, const char* path, char separator)\n{\n    uint32_t startIndex = 0;\n    while(startIndex < folders->length)\n    {\n        uint32_t sepIndex = ffStrbufNextIndexC(folders, startIndex, separator);\n\n        char savedSep = folders->chars[sepIndex]; // Can be '\\0' if at end\n        folders->chars[sepIndex] = '\\0';\n\n        bool matched = fnmatch(&folders->chars[startIndex], path, 0) == 0;\n        folders->chars[sepIndex] = savedSep;\n\n        if (matched) return true;\n\n        startIndex = sepIndex + 1;\n    }\n    return false;\n}\n#endif\n"
  },
  {
    "path": "src/detection/disk/disk.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/disk/option.h\"\n\n#ifdef _WIN32\n    #define FF_DISK_FOLDER_SEPARATOR ';'\n#else\n    #define FF_DISK_FOLDER_SEPARATOR ':'\n#endif\n\ntypedef struct FFDisk\n{\n    FFstrbuf mountFrom;\n    FFstrbuf mountpoint;\n    FFstrbuf filesystem;\n    FFstrbuf name;\n    FFDiskVolumeType type;\n\n    uint64_t bytesUsed;\n    uint64_t bytesFree;\n    uint64_t bytesAvailable;\n    uint64_t bytesTotal;\n\n    uint32_t filesUsed;\n    uint32_t filesTotal;\n\n    uint64_t createTime;\n} FFDisk;\n\n/**\n * Returns a List of FFDisk, sorted alphabetically by mountpoint.\n * If error is not set, disks contains at least one disk.\n */\nconst char* ffDetectDisks(FFDiskOptions* options, FFlist* disks /* list of FFDisk */);\n\nconst char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks);\n\n#ifndef _WIN32\nbool ffDiskMatchesFolderPatterns(FFstrbuf* folders, const char* path, char separator);\n#endif\n"
  },
  {
    "path": "src/detection/disk/disk_bsd.c",
    "content": "#include \"disk.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/stringUtils.h\"\n\n#include <sys/mount.h>\n#include <sys/stat.h>\n\n#ifdef __NetBSD__\n#include <sys/types.h>\n#include <sys/statvfs.h>\n#define statfs statvfs\n#define f_flags f_flag\n#define f_bsize f_frsize\n#endif\n\n#ifdef __FreeBSD__\n#if __has_include(<libgeom.h>)\n#include <libgeom.h>\n\nstatic const char* detectFsLabel(struct statfs* fs, FFDisk* disk)\n{\n    if (!ffStrStartsWith(fs->f_mntfromname, \"/dev/\"))\n        return \"Only block devices are supported\";\n\n    // Detect volume label in geom tree\n    static struct gmesh geomTree;\n    static struct gclass* cLabels;\n    if (!cLabels)\n    {\n        if (geomTree.lg_ident)\n            return \"Previous geom_gettree() failed\";\n\n        if (geom_gettree(&geomTree) < 0)\n        {\n            geomTree.lg_ident = (void*)(intptr_t)-1;\n            return \"geom_gettree() failed\";\n        }\n\n        for (cLabels = geomTree.lg_class.lh_first; cLabels && !ffStrEquals(cLabels->lg_name, \"LABEL\"); cLabels = cLabels->lg_class.le_next);\n        if (!cLabels)\n            return \"Class LABEL is not found\";\n    }\n\n    for (struct ggeom* label = cLabels->lg_geom.lh_first; label; label = label->lg_geom.le_next)\n    {\n        struct gprovider* provider = label->lg_provider.lh_first;\n        if (!provider || !ffStrEquals(label->lg_name, fs->f_mntfromname + strlen(\"/dev/\"))) continue;\n        const char* str = strchr(provider->lg_name, '/');\n        ffStrbufSetS(&disk->name, str ? str + 1 : provider->lg_name);\n    }\n\n    return NULL;\n}\n#else\nstatic const char* detectFsLabel(FF_MAYBE_UNUSED struct statfs* fs, FF_MAYBE_UNUSED FFDisk* disk)\n{\n    return \"Fastfetch was compiled without libgeom support\";\n}\n#endif\n\nstatic void detectFsInfo(struct statfs* fs, FFDisk* disk)\n{\n    if(ffStrbufEqualS(&disk->filesystem, \"zfs\"))\n    {\n        disk->type = !ffStrbufStartsWithS(&disk->mountFrom, \"zroot/\") || ffStrbufStartsWithS(&disk->mountFrom, \"zroot/ROOT/\")\n            ? FF_DISK_VOLUME_TYPE_REGULAR_BIT\n            : FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT;\n    }\n    else if(fs->f_flags & MNT_IGNORE)\n        disk->type = FF_DISK_VOLUME_TYPE_HIDDEN_BIT;\n    else if(!(fs->f_flags & MNT_LOCAL))\n        disk->type = FF_DISK_VOLUME_TYPE_EXTERNAL_BIT;\n    else\n        disk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT;\n\n    detectFsLabel(fs, disk);\n}\n#elif __APPLE__\n#include \"common/apple/cf_helpers.h\"\n\n#include <sys/attr.h>\n#include <unistd.h>\n\n#ifndef MAC_OS_X_VERSION_10_15\n    #define MNT_REMOVABLE 0x00000200\n#endif\n\nstruct CmnAttrBuf {\n    uint32_t       length;\n    attrreference_t nameRef;\n    char            nameSpace[NAME_MAX * 3 + 1];\n} __attribute__((aligned(4), packed));\n\nvoid detectFsInfo(struct statfs* fs, FFDisk* disk)\n{\n    if(fs->f_flags & MNT_DONTBROWSE)\n        disk->type = FF_DISK_VOLUME_TYPE_HIDDEN_BIT;\n    else if(fs->f_flags & MNT_REMOVABLE || !(fs->f_flags & MNT_LOCAL))\n        disk->type = FF_DISK_VOLUME_TYPE_EXTERNAL_BIT;\n    else\n        disk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT;\n\n    struct CmnAttrBuf attrBuf;\n    if (getattrlist(disk->mountpoint.chars, &(struct attrlist) {\n        .bitmapcount = ATTR_BIT_MAP_COUNT,\n        .commonattr = ATTR_CMN_NAME,\n    }, &attrBuf, sizeof(attrBuf), 0) == 0)\n        ffStrbufInitNS(&disk->name, attrBuf.nameRef.attr_length - 1 /* excluding '\\0' */, attrBuf.nameSpace);\n}\n#else\nstatic void detectFsInfo(struct statfs* fs, FFDisk* disk)\n{\n    #ifdef MNT_IGNORE\n    if(fs->f_flags & MNT_IGNORE)\n        disk->type = FF_DISK_VOLUME_TYPE_HIDDEN_BIT;\n    else\n    #endif\n    if(!(fs->f_flags & MNT_LOCAL))\n        disk->type = FF_DISK_VOLUME_TYPE_EXTERNAL_BIT;\n    else\n        disk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT;\n}\n#endif\n\nconst char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks)\n{\n    #ifndef __NetBSD__\n    int size = getfsstat(NULL, 0, MNT_WAIT);\n    if(size <= 0) return \"getfsstat(NULL, 0, MNT_WAIT) failed\";\n    #else\n    int size = getvfsstat(NULL, 0, ST_WAIT);\n    if(size <= 0) return \"getvfsstat(NULL, 0, ST_WAIT) failed\";\n    #endif\n\n    FF_AUTO_FREE struct statfs* buf = malloc(sizeof(*buf) * (unsigned) size);\n    #ifndef __NetBSD__\n    if(getfsstat(buf, (int) (sizeof(*buf) * (unsigned) size), MNT_NOWAIT) <= 0)\n        return \"getfsstat(buf, size, MNT_NOWAIT) failed\";\n    #else\n    if(getvfsstat(buf, sizeof(*buf) * (unsigned) size, ST_NOWAIT) <= 0)\n        return \"getvfsstat(buf, size, ST_NOWAIT) failed\";\n    #endif\n\n    for(struct statfs* fs = buf; fs < buf + size; ++fs)\n    {\n        if(__builtin_expect(options->folders.length > 0, 0))\n        {\n            if(!ffStrbufSeparatedContainS(&options->folders, fs->f_mntonname, FF_DISK_FOLDER_SEPARATOR))\n                continue;\n        }\n        else if(!ffStrEquals(fs->f_mntonname, \"/\") && !ffStrStartsWith(fs->f_mntfromname, \"/dev/\") && !ffStrEquals(fs->f_fstypename, \"zfs\") && !ffStrEquals(fs->f_fstypename, \"fusefs.sshfs\"))\n            continue;\n\n        if (options->hideFolders.length && ffDiskMatchesFolderPatterns(&options->hideFolders, fs->f_mntonname, FF_DISK_FOLDER_SEPARATOR))\n            continue;\n\n        if (options->hideFS.length && ffStrbufSeparatedContainS(&options->hideFS, fs->f_fstypename, ':'))\n            continue;\n\n        #ifdef __FreeBSD__\n        // f_bavail and f_ffree are signed on FreeBSD...\n        if(fs->f_bavail < 0) fs->f_bavail = 0;\n        if(fs->f_ffree < 0) fs->f_ffree = 0;\n        #endif\n\n        FFDisk* disk = ffListAdd(disks);\n\n        disk->bytesTotal = (uint64_t)fs->f_blocks * (uint64_t)fs->f_bsize;\n        disk->bytesFree = (uint64_t)fs->f_bfree * (uint64_t)fs->f_bsize;\n        disk->bytesAvailable = (uint64_t)fs->f_bavail * (uint64_t)fs->f_bsize;\n        disk->bytesUsed = 0; // To be filled in ./disk.c\n\n        disk->filesTotal = (uint32_t) fs->f_files;\n        disk->filesUsed = (uint32_t) fs->f_files - (uint32_t) fs->f_ffree;\n\n        ffStrbufInitS(&disk->mountFrom, fs->f_mntfromname);\n        ffStrbufInitS(&disk->mountpoint, fs->f_mntonname);\n        ffStrbufInitS(&disk->filesystem, fs->f_fstypename);\n        ffStrbufInit(&disk->name);\n        disk->type = 0;\n        disk->createTime = 0;\n\n        detectFsInfo(fs, disk);\n\n        if(fs->f_flags & MNT_RDONLY)\n            disk->type |= FF_DISK_VOLUME_TYPE_READONLY_BIT;\n\n        #ifdef __OpenBSD__\n        #define st_birthtimespec __st_birthtim\n        #endif\n        #ifndef __DragonFly__\n        struct stat st;\n        if(stat(fs->f_mntonname, &st) == 0 && st.st_birthtimespec.tv_sec > 0)\n            disk->createTime = (uint64_t)(((uint64_t) st.st_birthtimespec.tv_sec * 1000) + ((uint64_t) st.st_birthtimespec.tv_nsec / 1000000));\n        #endif\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/disk/disk_haiku.cpp",
    "content": "extern \"C\"\n{\n#include \"disk.h\"\n#include \"common/stringUtils.h\"\n}\n#include <fs_info.h>\n#include <Directory.h>\n#include <Path.h>\n\nconst char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks)\n{\n    int32 pos = 0;\n\n    for (dev_t dev; (dev = next_dev(&pos)) >= B_OK;)\n    {\n        fs_info fs;\n        if (fs_stat_dev(dev, &fs) < 0) continue;\n\n        node_ref node(fs.dev, fs.root);\n        BDirectory dir(&node);\n        BPath path(&dir);\n        if (path.InitCheck() != B_OK) continue;\n\n        if (__builtin_expect(options->folders.length, 0))\n        {\n            if (!ffStrbufSeparatedContainS(&options->folders, path.Path(), FF_DISK_FOLDER_SEPARATOR))\n                continue;\n        }\n\n        if (options->hideFolders.length && ffDiskMatchesFolderPatterns(&options->hideFolders, path.Path(), FF_DISK_FOLDER_SEPARATOR))\n            continue;\n\n        if (options->hideFS.length && ffStrbufSeparatedContainS(&options->hideFS, fs.fsh_name, ':'))\n            continue;\n\n        FFDisk* disk = (FFDisk*) ffListAdd(disks);\n\n        disk->bytesTotal = (uint64_t)fs.total_blocks * (uint64_t) fs.block_size;\n        disk->bytesFree = (uint64_t)fs.free_blocks * (uint64_t) fs.block_size;\n        disk->bytesAvailable = disk->bytesFree;\n        disk->bytesUsed = 0; // To be filled in ./disk.c\n\n        disk->filesTotal = (uint32_t) fs.total_nodes;\n        disk->filesUsed = (uint32_t) (fs.total_nodes - fs.free_nodes);\n\n        ffStrbufInitS(&disk->mountFrom, fs.device_name);\n        ffStrbufInitS(&disk->mountpoint, path.Path());\n        ffStrbufInitS(&disk->filesystem, fs.fsh_name);\n        ffStrbufInitS(&disk->name, fs.volume_name);\n        disk->type = FF_DISK_VOLUME_TYPE_NONE;\n        if (!(fs.flags & B_FS_IS_PERSISTENT))\n            disk->type = (FFDiskVolumeType) (disk->type | FF_DISK_VOLUME_TYPE_HIDDEN_BIT);\n        if (fs.flags & B_FS_IS_READONLY)\n            disk->type = (FFDiskVolumeType) (disk->type | FF_DISK_VOLUME_TYPE_READONLY_BIT);\n        if (fs.flags & B_FS_IS_REMOVABLE)\n            disk->type = (FFDiskVolumeType) (disk->type | FF_DISK_VOLUME_TYPE_EXTERNAL_BIT);\n        if (disk->type == FF_DISK_VOLUME_TYPE_NONE) disk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT;\n        disk->createTime = 0;\n\n        time_t crTime;\n        if (dir.GetCreationTime(&crTime) == B_OK)\n            disk->createTime = (uint64_t) crTime * 1000;\n    }\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/disk/disk_linux.c",
    "content": "#include \"disk.h\"\n\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n\n#include <limits.h>\n#include <ctype.h>\n#include <dirent.h>\n#include <mntent.h>\n#include <sys/stat.h>\n#include <sys/statvfs.h>\n\n#if defined(STATX_BTIME) && !defined(__ANDROID__)\n    #include <sys/syscall.h>\n#endif\n\n#ifdef __USE_LARGEFILE64\n    #define stat stat64\n    #define statvfs statvfs64\n    #define dirent dirent64\n    #define readdir readdir64\n#endif\n\nstatic bool isPhysicalDevice(const struct mntent* device)\n{\n    #ifndef __ANDROID__ //On Android, `/dev` is not accessible, so that the following checks always fail\n\n    //Always show the root path\n    if(ffStrEquals(device->mnt_dir, \"/\"))\n        return true;\n\n    if(ffStrEquals(device->mnt_fsname, \"none\"))\n        return false;\n\n    //DrvFs is a filesystem plugin to WSL that was designed to support interop between WSL and the Windows filesystem.\n    if(ffStrEquals(device->mnt_type, \"9p\"))\n        return ffStrContains(device->mnt_opts, \"aname=drvfs\");\n\n    //ZFS pool\n    if(ffStrEquals(device->mnt_type, \"zfs\"))\n        return true;\n\n    //sshfs\n    if(ffStrEquals(device->mnt_type, \"fuse.sshfs\"))\n        return true;\n\n    //Pseudo filesystems don't have a device in /dev\n    if(!ffStrStartsWith(device->mnt_fsname, \"/dev/\"))\n        return false;\n\n    //#731\n    if(ffStrEquals(device->mnt_type, \"bcachefs\"))\n        return true;\n\n    if(\n        ffStrStartsWith(device->mnt_fsname + 5, \"loop\") || //Ignore loop devices\n        ffStrStartsWith(device->mnt_fsname + 5, \"ram\")  || //Ignore ram devices\n        ffStrStartsWith(device->mnt_fsname + 5, \"fd\")      //Ignore fd devices\n    ) return false;\n\n    if (ffStrStartsWith(device->mnt_dir, \"/bedrock/\")) // Ignore Bedrock Linux subvolumes\n        return false;\n\n    struct stat deviceStat;\n    if(stat(device->mnt_fsname, &deviceStat) != 0)\n        return false;\n\n    //Ignore all devices that are not block devices\n    if(!S_ISBLK(deviceStat.st_mode))\n        return false;\n\n    #else\n\n    //Pseudo filesystems don't have a device in /dev\n    if(!ffStrStartsWith(device->mnt_fsname, \"/dev/\"))\n        return false;\n\n    if(\n        ffStrStartsWith(device->mnt_fsname + 5, \"loop\") || //Ignore loop devices\n        ffStrStartsWith(device->mnt_fsname + 5, \"ram\")  || //Ignore ram devices\n        ffStrStartsWith(device->mnt_fsname + 5, \"fd\")      //Ignore fd devices\n    ) return false;\n\n    // https://source.android.com/docs/core/ota/apex?hl=zh-cn\n    if(ffStrStartsWith(device->mnt_dir, \"/apex/\"))\n        return false;\n\n    #endif // __ANDROID__\n\n    return true;\n}\n\nstatic void detectNameFromPath(FFDisk* disk, const struct stat* deviceStat, FFstrbuf* basePath)\n{\n    FF_AUTO_CLOSE_DIR DIR* dir = opendir(basePath->chars);\n    if(dir == NULL)\n        return;\n\n    uint32_t basePathLength = basePath->length;\n\n    struct dirent* entry;\n    while((entry = readdir(dir)) != NULL)\n    {\n        if(entry->d_name[0] == '.')\n            continue;\n\n        ffStrbufAppendS(basePath, entry->d_name);\n\n        struct stat entryStat;\n        bool ret = stat(basePath->chars, &entryStat) == 0;\n\n        ffStrbufSubstrBefore(basePath, basePathLength);\n\n        if(!ret || deviceStat->st_ino != entryStat.st_ino)\n            continue;\n\n        ffStrbufAppendS(&disk->name, entry->d_name);\n        break;\n    }\n}\n\nstatic void detectName(FFDisk* disk)\n{\n    struct stat deviceStat;\n    if(stat(disk->mountFrom.chars, &deviceStat) != 0)\n        return;\n\n    FF_STRBUF_AUTO_DESTROY basePath = ffStrbufCreate();\n\n    //Try label first\n    ffStrbufSetS(&basePath, \"/dev/disk/by-label/\");\n    detectNameFromPath(disk, &deviceStat, &basePath);\n\n    if(disk->name.length == 0)\n    {\n        //Try partlabel second\n        ffStrbufSetS(&basePath, \"/dev/disk/by-partlabel/\");\n        detectNameFromPath(disk, &deviceStat, &basePath);\n    }\n\n    if (disk->name.length == 0) return;\n\n    // Basic\\x20data\\x20partition\n    for (uint32_t i = ffStrbufFirstIndexS(&disk->name, \"\\\\x\");\n        i != disk->name.length;\n        i = ffStrbufNextIndexS(&disk->name, i + 1, \"\\\\x\"))\n    {\n        uint32_t len = (uint32_t) strlen(\"\\\\x20\");\n        if (disk->name.length >= len)\n        {\n            char bak = disk->name.chars[i + len];\n            disk->name.chars[i + len] = '\\0';\n            disk->name.chars[i] = (char) strtoul(&disk->name.chars[i + 2], NULL, 16);\n            ffStrbufRemoveSubstr(&disk->name, i + 1, i + len);\n            disk->name.chars[i + 1] = bak;\n        }\n    }\n}\n\n#ifdef __ANDROID__\n\nstatic void detectType(FF_MAYBE_UNUSED const FFlist* disks, FFDisk* currentDisk, FF_MAYBE_UNUSED struct mntent* device)\n{\n    if(ffStrbufEqualS(&currentDisk->mountpoint, \"/\") || ffStrbufEqualS(&currentDisk->mountpoint, \"/storage/emulated\"))\n        currentDisk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT;\n    else if(ffStrbufStartsWithS(&currentDisk->mountpoint, \"/mnt/media_rw/\"))\n        currentDisk->type = FF_DISK_VOLUME_TYPE_EXTERNAL_BIT;\n    else\n        currentDisk->type = FF_DISK_VOLUME_TYPE_HIDDEN_BIT;\n}\n\n#else\n\nstatic bool isSubvolume(const FFlist* disks, FFDisk* currentDisk)\n{\n    if(ffStrbufEqualS(&currentDisk->mountFrom, \"drvfs\")) // WSL Windows drives\n        return false;\n\n    if(ffStrbufEqualS(&currentDisk->filesystem, \"zfs\"))\n    {\n        //ZFS subvolumes\n        uint32_t index = ffStrbufFirstIndexC(&currentDisk->mountFrom, '/');\n        if (index == currentDisk->mountFrom.length)\n            return false;\n\n        FF_STRBUF_AUTO_DESTROY zpoolName = ffStrbufCreateNS(index, currentDisk->mountFrom.chars);\n        for(uint32_t i = 0; i < disks->length - 1; i++)\n        {\n            const FFDisk* otherDevice = FF_LIST_GET(FFDisk, *disks, i);\n            if(ffStrbufEqualS(&otherDevice->filesystem, \"zfs\") && ffStrbufStartsWith(&otherDevice->mountFrom, &zpoolName))\n                return true;\n        }\n\n        return false;\n    }\n    else\n    {\n        //Filter all disks which device was already found. This catches BTRFS subvolumes.\n        for(uint32_t i = 0; i < disks->length - 1; i++)\n        {\n            const FFDisk* otherDevice = FF_LIST_GET(FFDisk, *disks, i);\n\n            if(ffStrbufEqual(&currentDisk->mountFrom, &otherDevice->mountFrom))\n                return true;\n        }\n    }\n\n    return false;\n}\n\nstatic bool isRemovable(FFDisk* currentDisk)\n{\n    if (!ffStrbufStartsWithS(&currentDisk->mountFrom, \"/dev/\"))\n        return false;\n\n    char sysBlockPartition[64];\n    snprintf(sysBlockPartition, ARRAY_SIZE(sysBlockPartition), \"/sys/class/block/%s\", currentDisk->mountFrom.chars + strlen(\"/dev/\"));\n\n    char sysBlockVolume[PATH_MAX]; // /sys/devices/pci0000:00/0000:00:14.0/usb4/4-3/4-3:1.0/host0/target0:0:0/0:0:0:0/block/sda/sda1\n    if (realpath(sysBlockPartition, sysBlockVolume) == NULL)\n        return false;\n    char* lastSlash = strrchr(sysBlockVolume, '/');\n    if (lastSlash == NULL)\n        return false;\n    strcpy(lastSlash + 1, \"removable\");\n\n    char removableChar = '0';\n    return ffReadFileData(sysBlockVolume, 1, &removableChar) > 0 && removableChar == '1';\n}\n\nstatic void detectType(const FFlist* disks, FFDisk* currentDisk, struct mntent* device)\n{\n    if(hasmntopt(device, \"x-gvfs-hide\") || hasmntopt(device, \"hidden\"))\n        currentDisk->type = FF_DISK_VOLUME_TYPE_HIDDEN_BIT;\n    else if(isSubvolume(disks, currentDisk))\n        currentDisk->type = FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT;\n    else if(isRemovable(currentDisk))\n        currentDisk->type = FF_DISK_VOLUME_TYPE_EXTERNAL_BIT;\n    else\n        currentDisk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT;\n    if (hasmntopt(device, MNTOPT_RO))\n        currentDisk->type |= FF_DISK_VOLUME_TYPE_READONLY_BIT;\n}\n\n#endif\n\nstatic void detectStats(FFDisk* disk)\n{\n    struct statvfs fs;\n    if(statvfs(disk->mountpoint.chars, &fs) != 0)\n        memset(&fs, 0, sizeof(fs)); //Set all values to 0, so our values get initialized to 0 too\n\n    disk->bytesTotal = fs.f_blocks * (uint64_t) fs.f_frsize;\n    disk->bytesFree = fs.f_bfree * (uint64_t) fs.f_frsize;\n    disk->bytesAvailable = fs.f_bavail * (uint64_t) fs.f_frsize;\n    disk->bytesUsed = 0; // To be filled in ./disk.c\n\n    if (fs.f_files >= fs.f_ffree)\n    {\n        disk->filesTotal = (uint32_t) fs.f_files;\n        disk->filesUsed = (uint32_t) (disk->filesTotal - fs.f_ffree);\n    }\n    else\n    {\n        // Windows filesystem in WSL\n        disk->filesTotal = disk->filesUsed = 0;\n    }\n\n    disk->createTime = 0;\n    #ifdef SYS_statx\n    struct statx stx;\n    if (syscall(SYS_statx, 0, disk->mountpoint.chars, 0, STATX_BTIME, &stx) == 0 && (stx.stx_mask & STATX_BTIME) && stx.stx_btime.tv_sec > 685065600 /*birth of Linux*/)\n        disk->createTime = (uint64_t)((stx.stx_btime.tv_sec * 1000) + (stx.stx_btime.tv_nsec / 1000000));\n    #endif\n\n    #ifdef __ANDROID__ // hasmntopt requires a higher Android API level\n    if(fs.f_flag & ST_RDONLY)\n        disk->type |= FF_DISK_VOLUME_TYPE_READONLY_BIT;\n    #endif\n}\n\nconst char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks)\n{\n    FILE* mountsFile = setmntent(\"/proc/mounts\", \"r\");\n    if(mountsFile == NULL)\n        return \"setmntent(\\\"/proc/mounts\\\", \\\"r\\\") == NULL\";\n\n    struct mntent* device;\n\n    while((device = getmntent(mountsFile)))\n    {\n        if (__builtin_expect(options->folders.length > 0, false))\n        {\n            if (!ffStrbufSeparatedContainS(&options->folders, device->mnt_dir, FF_DISK_FOLDER_SEPARATOR))\n                continue;\n        }\n        else if(!isPhysicalDevice(device))\n            continue;\n\n        if (options->hideFolders.length && ffDiskMatchesFolderPatterns(&options->hideFolders, device->mnt_dir, FF_DISK_FOLDER_SEPARATOR))\n            continue;\n\n        if (options->hideFS.length && ffStrbufSeparatedContainS(&options->hideFS, device->mnt_type, ':'))\n            continue;\n\n        //We have a valid device, add it to the list\n        FFDisk* disk = ffListAdd(disks);\n        disk->type = FF_DISK_VOLUME_TYPE_NONE;\n\n        //detect mountFrom\n        ffStrbufInitS(&disk->mountFrom, device->mnt_fsname);\n\n        //detect mountpoint\n        ffStrbufInitS(&disk->mountpoint, device->mnt_dir);\n\n        //detect filesystem\n        ffStrbufInitS(&disk->filesystem, device->mnt_type);\n\n        //detect name\n        ffStrbufInit(&disk->name);\n        detectName(disk); // Also detects external devices\n\n        //detect type\n        detectType(disks, disk, device);\n\n        //Detects stats\n        detectStats(disk);\n    }\n\n    endmntent(mountsFile);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/disk/disk_nosupport.c",
    "content": "#include \"disk.h\"\n\nconst char* ffDetectDisksImpl(FF_MAYBE_UNUSED FFDiskOptions* options, FF_MAYBE_UNUSED FFlist* disks)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/disk/disk_sunos.c",
    "content": "#include \"disk.h\"\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n\n#include <sys/mntent.h>\n#include <sys/stat.h>\n#include <sys/statvfs.h>\n#include <sys/mount.h>\n#include <sys/mnttab.h>\n\nstatic bool isPhysicalDevice(const struct mnttab* device)\n{\n    //Always show the root path\n    if(ffStrEquals(device->mnt_mountp, \"/\"))\n        return true;\n\n    if(ffStrEquals(device->mnt_special, \"none\"))\n        return false;\n\n    //ZFS pool\n    if(ffStrEquals(device->mnt_fstype, \"zfs\"))\n        return true;\n\n    //Pseudo filesystems don't have a device in /dev\n    if(!ffStrStartsWith(device->mnt_special, \"/dev/\"))\n        return false;\n\n    struct stat deviceStat;\n    if(stat(device->mnt_special, &deviceStat) != 0)\n        return false;\n\n    //Ignore all devices that are not block devices\n    if(!S_ISBLK(deviceStat.st_mode))\n        return false;\n\n    return true;\n}\n\nstatic bool isSubvolume(const FFlist* disks, FFDisk* currentDisk)\n{\n    if(ffStrbufEqualS(&currentDisk->filesystem, \"zfs\"))\n    {\n        //ZFS subvolumes\n        uint32_t index = ffStrbufFirstIndexC(&currentDisk->mountFrom, '/');\n        if (index == currentDisk->mountFrom.length)\n            return false;\n\n        FF_STRBUF_AUTO_DESTROY zpoolName = ffStrbufCreateNS(index, currentDisk->mountFrom.chars);\n        for(uint32_t i = 0; i < disks->length - 1; i++)\n        {\n            const FFDisk* otherDevice = FF_LIST_GET(FFDisk, *disks, i);\n            if(ffStrbufEqualS(&otherDevice->filesystem, \"zfs\") && ffStrbufStartsWith(&otherDevice->mountFrom, &zpoolName))\n                return true;\n        }\n\n        return false;\n    }\n    else\n    {\n        //Filter all disks which device was already found. This catches BTRFS subvolumes.\n        for(uint32_t i = 0; i < disks->length - 1; i++)\n        {\n            const FFDisk* otherDevice = FF_LIST_GET(FFDisk, *disks, i);\n\n            if(ffStrbufEqual(&currentDisk->mountFrom, &otherDevice->mountFrom))\n                return true;\n        }\n    }\n\n    return false;\n}\n\nstatic void detectType(const FFlist* disks, FFDisk* currentDisk, struct mnttab* device)\n{\n    if(hasmntopt(device, MNTOPT_NOBROWSE))\n        currentDisk->type = FF_DISK_VOLUME_TYPE_HIDDEN_BIT;\n    else if(isSubvolume(disks, currentDisk))\n        currentDisk->type = FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT;\n    else\n        currentDisk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT;\n    if (hasmntopt(device, MNTOPT_RO))\n        currentDisk->type |= FF_DISK_VOLUME_TYPE_READONLY_BIT;\n}\n\nstatic void detectStats(FFDisk* disk)\n{\n    struct statvfs fs;\n    if(statvfs(disk->mountpoint.chars, &fs) != 0)\n        memset(&fs, 0, sizeof(fs));\n\n    disk->bytesTotal = fs.f_blocks * fs.f_frsize;\n    disk->bytesFree = fs.f_bfree * fs.f_frsize;\n    disk->bytesAvailable = fs.f_bavail * fs.f_frsize;\n    disk->bytesUsed = 0; // To be filled in ./disk.c\n\n    disk->filesTotal = (uint32_t) fs.f_files;\n    disk->filesUsed = (uint32_t) (disk->filesTotal - fs.f_ffree);\n\n    ffStrbufSetS(&disk->name, fs.f_fstr);\n\n    disk->createTime = 0;\n}\n\nconst char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks)\n{\n    FF_AUTO_CLOSE_FILE FILE* mountsFile = fopen(MNTTAB, \"r\");\n    if(mountsFile == NULL)\n        return \"fopen(\\\"\" MNTTAB \"\\\", \\\"r\\\") == NULL\";\n\n    struct mnttab device;\n\n    while (getmntent(mountsFile, &device) == 0)\n    {\n        if (__builtin_expect(options->folders.length, 0))\n        {\n            if (!ffStrbufSeparatedContainS(&options->folders, device.mnt_mountp, FF_DISK_FOLDER_SEPARATOR))\n                continue;\n        }\n        else if(!isPhysicalDevice(&device))\n            continue;\n\n        if (options->hideFolders.length && ffDiskMatchesFolderPatterns(&options->hideFolders, device.mnt_mountp, FF_DISK_FOLDER_SEPARATOR))\n            continue;\n\n        if (options->hideFS.length && ffStrbufSeparatedContainS(&options->hideFS, device.mnt_fstype, ':'))\n            continue;\n\n        //We have a valid device, add it to the list\n        FFDisk* disk = ffListAdd(disks);\n        disk->type = FF_DISK_VOLUME_TYPE_NONE;\n        ffStrbufInitS(&disk->mountFrom, device.mnt_special);\n        ffStrbufInitS(&disk->mountpoint, device.mnt_mountp);\n        ffStrbufInitS(&disk->filesystem, device.mnt_fstype);\n        ffStrbufInit(&disk->name);\n        detectType(disks, disk, &device);\n        detectStats(disk);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/disk/disk_windows.c",
    "content": "#include \"disk.h\"\n#include \"common/io.h\"\n#include \"common/time.h\"\n#include \"common/windows/unicode.h\"\n#include \"common/windows/nt.h\"\n\n#include <windows.h>\n#include <winioctl.h>\n#include <ntstatus.h>\n#include <stdalign.h>\n\nconst char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks)\n{\n    PROCESS_DEVICEMAP_INFORMATION_EX info = {};\n    ULONG size = 0;\n    if(!NT_SUCCESS(NtQueryInformationProcess(NtCurrentProcess(), ProcessDeviceMap, &info, sizeof(info), &size)))\n        return \"NtQueryInformationProcess(ProcessDeviceMap) failed\";\n\n    // For cross-platform portability; used by `presets/examples/13.jsonc`\n    if (options->folders.length == 1 && options->folders.chars[0] == '/')\n    {\n        options->folders.chars[0] = (char) SharedUserData->NtSystemRoot[0];\n        ffStrbufAppendS(&options->folders, \":\\\\\");\n    }\n\n    wchar_t mountpointW[] = L\"X:\\\\\";\n    char mountpointA[] = \"X:\\\\\";\n\n    for (wchar_t i = L'A'; i <= L'Z'; i++)\n    {\n        if (!(info.Query.DriveMap & (1 << (i - L'A'))))\n            continue;\n        mountpointW[0] = i;\n        mountpointA[0] = (char) i;\n\n        UINT driveType = info.Query.DriveType[i - L'A'];\n\n        if (__builtin_expect((long) options->folders.length, 0))\n        {\n            if (!ffStrbufSeparatedContainNS(&options->folders, 3, mountpointA, FF_DISK_FOLDER_SEPARATOR))\n                continue;\n        }\n        else if(driveType == DRIVE_NO_ROOT_DIR)\n            continue;\n\n        if (options->hideFolders.length && ffStrbufSeparatedContainNS(&options->hideFolders, 3, mountpointA, FF_DISK_FOLDER_SEPARATOR))\n            continue;\n\n        FF_AUTO_CLOSE_FD HANDLE handle = CreateFileW(mountpointW, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);\n        if (handle == INVALID_HANDLE_VALUE)\n            continue;\n\n        IO_STATUS_BLOCK iosb;\n\n        alignas(FILE_FS_ATTRIBUTE_INFORMATION) uint8_t bufFsAttr[1024];\n        FILE_FS_ATTRIBUTE_INFORMATION* fsAttr = NT_SUCCESS(NtQueryVolumeInformationFile(handle, &iosb, bufFsAttr, sizeof(bufFsAttr), FileFsAttributeInformation))\n            ? (FILE_FS_ATTRIBUTE_INFORMATION*) bufFsAttr\n            : NULL;\n\n        FF_STRBUF_AUTO_DESTROY diskFileSystemBuf = ffStrbufCreate();\n        if (fsAttr)\n        {\n            ffStrbufSetNWS(&diskFileSystemBuf, fsAttr->FileSystemNameLength / sizeof(WCHAR), fsAttr->FileSystemName);\n            if (options->hideFS.length && ffStrbufSeparatedContain(&options->hideFS, &diskFileSystemBuf, ':'))\n                continue;\n        }\n\n        FFDisk* disk = ffListAdd(disks);\n\n        disk->filesUsed = 0;\n        disk->filesTotal = 0;\n        disk->bytesTotal = 0;\n        disk->bytesFree = 0;\n        disk->bytesUsed = 0; // To be filled in ./disk.c\n        disk->bytesAvailable = 0;\n        disk->createTime = 0;\n        ffStrbufInit(&disk->filesystem);\n        ffStrbufInit(&disk->name);\n        ffStrbufInitNS(&disk->mountpoint, 3, mountpointA);\n        ffStrbufInit(&disk->mountFrom);\n        disk->type = driveType == DRIVE_REMOVABLE || driveType == DRIVE_REMOTE || driveType == DRIVE_CDROM\n            ? FF_DISK_VOLUME_TYPE_EXTERNAL_BIT\n            : driveType == DRIVE_FIXED\n                ? FF_DISK_VOLUME_TYPE_REGULAR_BIT\n                : FF_DISK_VOLUME_TYPE_HIDDEN_BIT;\n\n        {\n            wchar_t volumeName[MAX_PATH + 1];\n            mountpointW[2] = L'\\0';\n            if(QueryDosDeviceW(mountpointW, volumeName, ARRAY_SIZE(volumeName)))\n                ffStrbufSetWS(&disk->mountFrom, volumeName);\n            mountpointW[2] = L'\\\\';\n        }\n\n        alignas(FILE_FS_VOLUME_INFORMATION) uint8_t bufFsVolume[1024];\n        FILE_FS_VOLUME_INFORMATION* fsVolume = NT_SUCCESS(NtQueryVolumeInformationFile(handle, &iosb, bufFsVolume, sizeof(bufFsVolume), FileFsVolumeInformation))\n            ? (FILE_FS_VOLUME_INFORMATION*) bufFsVolume\n            : NULL;\n\n        if (fsVolume)\n        {\n            if (fsVolume->VolumeLabelLength > 0)\n                ffStrbufSetNWS(&disk->name, fsVolume->VolumeLabelLength / sizeof(WCHAR), fsVolume->VolumeLabel);\n            if (fsVolume->VolumeCreationTime.QuadPart)\n                disk->createTime = ffFileTimeToUnixMs((uint64_t) fsVolume->VolumeCreationTime.QuadPart);\n        }\n\n        if (fsAttr)\n        {\n            ffStrbufInitMove(&disk->filesystem, &diskFileSystemBuf);\n            if(fsAttr->FileSystemAttributes & FILE_READ_ONLY_VOLUME)\n                disk->type |= FF_DISK_VOLUME_TYPE_READONLY_BIT;\n        }\n\n        FILE_FS_FULL_SIZE_INFORMATION fsFullSize;\n        if (NT_SUCCESS(NtQueryVolumeInformationFile(handle, &iosb, &fsFullSize, sizeof(fsFullSize), FileFsFullSizeInformation)))\n        {\n            uint64_t units = fsFullSize.BytesPerSector * fsFullSize.SectorsPerAllocationUnit;\n            disk->bytesTotal = (uint64_t) fsFullSize.TotalAllocationUnits.QuadPart * units;\n            disk->bytesFree = (uint64_t) fsFullSize.ActualAvailableAllocationUnits.QuadPart * units;\n            disk->bytesAvailable = (uint64_t) fsFullSize.CallerAvailableAllocationUnits.QuadPart * units;\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/diskio/diskio.c",
    "content": "#include \"diskio.h\"\n\n#include \"common/time.h\"\n\nconst char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options);\n\nstatic FFlist ioCounters1;\nstatic uint64_t time1;\n\nvoid ffPrepareDiskIO(FFDiskIOOptions* options)\n{\n    if (options->detectTotal) return;\n\n    if (time1 != 0) return; // Already prepared\n\n    ffListInit(&ioCounters1, sizeof(FFDiskIOResult));\n    ffDiskIOGetIoCounters(&ioCounters1, options);\n    time1 = ffTimeGetNow();\n}\n\nconst char* ffDetectDiskIO(FFlist* result, FFDiskIOOptions* options)\n{\n    const char* error = NULL;\n\n    if (options->detectTotal)\n    {\n        error = ffDiskIOGetIoCounters(result, options);\n        if (error)\n            return error;\n        return NULL;\n    }\n\n    if (time1 == 0)\n    {\n        ffListInit(&ioCounters1, sizeof(FFDiskIOResult));\n        error = ffDiskIOGetIoCounters(&ioCounters1, options);\n        if (error)\n            return error;\n        time1 = ffTimeGetNow();\n    }\n\n    if (ioCounters1.length == 0)\n        return \"No physical disk found\";\n\n    uint64_t time2 = ffTimeGetNow();\n    while (time2 - time1 < options->waitTime)\n    {\n        ffTimeSleep((uint32_t) (options->waitTime - (time2 - time1)));\n        time2 = ffTimeGetNow();\n    }\n\n    error = ffDiskIOGetIoCounters(result, options);\n    if (error)\n        return error;\n\n    if (result->length != ioCounters1.length)\n        return \"Different number of physical disks. Hardware change?\";\n\n    for (uint32_t i = 0; i < result->length; ++i)\n    {\n        FFDiskIOResult* icPrev = FF_LIST_GET(FFDiskIOResult, ioCounters1, i);\n        FFDiskIOResult* icCurr = FF_LIST_GET(FFDiskIOResult, *result, i);\n        if (!ffStrbufEqual(&icPrev->devPath, &icCurr->devPath))\n            return \"Physical disk device path changed\";\n\n        static_assert(sizeof(FFDiskIOResult) - offsetof(FFDiskIOResult, bytesRead) == sizeof(uint64_t) * 4, \"Unexpected struct FFDiskIOResult layout\");\n        for (size_t off = offsetof(FFDiskIOResult, bytesRead); off < sizeof(FFDiskIOResult); off += sizeof(uint64_t))\n        {\n            uint64_t* prevValue = (uint64_t*) ((uint8_t*) icPrev + off);\n            uint64_t* currValue = (uint64_t*) ((uint8_t*) icCurr + off);\n            uint64_t temp = *currValue;\n            *currValue -= *prevValue;\n            *currValue /= (time2 - time1) / 1000 /* seconds */;\n\n            // For next function call\n            *prevValue = temp;\n        }\n    }\n\n    // For next function call\n    time1 = time2;\n    // Leak ioCounters1 here\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/diskio/diskio.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/diskio/option.h\"\n\ntypedef struct FFDiskIOResult\n{\n    FFstrbuf name;\n    FFstrbuf devPath;\n    uint64_t bytesRead;\n    uint64_t readCount;\n    uint64_t bytesWritten;\n    uint64_t writeCount;\n} FFDiskIOResult;\n\nconst char* ffDetectDiskIO(FFlist* result, FFDiskIOOptions* options);\n"
  },
  {
    "path": "src/detection/diskio/diskio_apple.c",
    "content": "#include \"diskio.h\"\n#include \"common/apple/cf_helpers.h\"\n\n#include <IOKit/IOKitLib.h>\n#include <IOKit/IOBSD.h>\n#include <IOKit/storage/IOMedia.h>\n#include <IOKit/storage/IOBlockStorageDriver.h>\n#include <IOKit/storage/IOStorageDeviceCharacteristics.h>\n#include <IOKit/storage/IOStorageProtocolCharacteristics.h>\n\nconst char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options)\n{\n    FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = 0;\n    if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceMatching(kIOMediaClass), &iterator) != KERN_SUCCESS)\n        return \"IOServiceGetMatchingServices() failed\";\n\n    io_registry_entry_t registryEntry;\n    while ((registryEntry = IOIteratorNext(iterator)) != IO_OBJECT_NULL)\n    {\n        FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryPartition = registryEntry;\n\n        io_name_t deviceName;\n        if (IORegistryEntryGetName(registryEntry, deviceName) != KERN_SUCCESS)\n            continue;\n\n        if (options->namePrefix.length && strncmp(deviceName, options->namePrefix.chars, options->namePrefix.length) != 0)\n            continue;\n\n        FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDriver = 0;\n        if (IORegistryEntryGetParentEntry(entryPartition, kIOServicePlane, &entryDriver) != KERN_SUCCESS)\n            continue;\n\n        if (!IOObjectConformsTo(entryDriver, kIOBlockStorageDriverClass)) // physical disk only\n            continue;\n\n        FF_CFTYPE_AUTO_RELEASE CFDictionaryRef statistics = IORegistryEntryCreateCFProperty(entryDriver, CFSTR(kIOBlockStorageDriverStatisticsKey), kCFAllocatorDefault, kNilOptions);\n        if (!statistics)\n            continue;\n\n        FFDiskIOResult* device = (FFDiskIOResult*) ffListAdd(result);\n        ffStrbufInitS(&device->name, deviceName);\n        ffStrbufInit(&device->devPath);\n\n        ffCfDictGetInt64(statistics, CFSTR(kIOBlockStorageDriverStatisticsBytesReadKey), (int64_t*) &device->bytesRead);\n        ffCfDictGetInt64(statistics, CFSTR(kIOBlockStorageDriverStatisticsBytesWrittenKey), (int64_t*) &device->bytesWritten);\n        ffCfDictGetInt64(statistics, CFSTR(kIOBlockStorageDriverStatisticsReadsKey), (int64_t*) &device->readCount);\n        ffCfDictGetInt64(statistics, CFSTR(kIOBlockStorageDriverStatisticsWritesKey), (int64_t*) &device->writeCount);\n\n        FF_CFTYPE_AUTO_RELEASE CFStringRef bsdName = IORegistryEntryCreateCFProperty(entryPartition, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, kNilOptions);\n        if (bsdName)\n        {\n            ffCfStrGetString(bsdName, &device->devPath);\n            ffStrbufPrependS(&device->devPath, \"/dev/\");\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/diskio/diskio_bsd.c",
    "content": "#include \"diskio.h\"\n\n#if __has_include(<libgeom.h>)\n\n#include \"common/stringUtils.h\"\n\n#include <devstat.h>\n#include <memory.h>\n#include <fcntl.h>\n#include <sys/ioctl.h>\n#include <sys/disk.h>\n#include <libgeom.h>\n\nconst char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options)\n{\n    __attribute__((__cleanup__(geom_deletetree)))\n    struct gmesh geomTree = {};\n    if (geom_gettree(&geomTree) < 0)\n        return \"geom_gettree() failed\";\n\n    if (geom_stats_open() < 0)\n        return \"geom_stats_open() failed\";\n\n    void* snap = geom_stats_snapshot_get();\n    if (!snap)\n        return \"geom_stats_snapshot_get() failed\";\n\n    struct devstat* snapIter;\n    while ((snapIter = geom_stats_snapshot_next(snap)) != NULL)\n    {\n        if (snapIter->device_type & DEVSTAT_TYPE_PASS)\n            continue;\n        struct gident* geomId = geom_lookupid(&geomTree, snapIter->id);\n        if (geomId == NULL)\n            continue;\n        if (geomId->lg_what != ISPROVIDER)\n            continue;\n        struct gprovider* provider = (struct gprovider*) geomId->lg_ptr;\n        if (provider->lg_geom->lg_rank != 1)\n            continue;\n\n        FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate();\n        for (struct gconfig* ptr = provider->lg_config.lh_first; ptr; ptr = ptr->lg_config.le_next)\n        {\n            if (ffStrEquals(ptr->lg_name, \"descr\"))\n                ffStrbufSetS(&name, ptr->lg_val);\n        }\n        if (name.length == 0)\n            ffStrbufSetS(&name, provider->lg_name);\n\n        if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix))\n            continue;\n\n        FFDiskIOResult* device = (FFDiskIOResult*) ffListAdd(result);\n        ffStrbufInitF(&device->devPath, \"/dev/%s\", provider->lg_name);\n        device->bytesRead = snapIter->bytes[DEVSTAT_READ];\n        device->readCount = snapIter->operations[DEVSTAT_READ];\n        device->bytesWritten = snapIter->bytes[DEVSTAT_WRITE];\n        device->writeCount = snapIter->operations[DEVSTAT_WRITE];\n        ffStrbufInitMove(&device->name, &name);\n    }\n\n    geom_stats_snapshot_free(snap);\n    geom_stats_close();\n\n    return NULL;\n}\n\n#else\n\n#include <devstat.h>\n#include <memory.h>\n\nconst char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options)\n{\n    if (checkversion() < 0)\n        return \"checkversion() failed\";\n\n    struct statinfo stats = {\n        .dinfo = (struct devinfo *)calloc(1, sizeof(struct devinfo)),\n    };\n    if (getdevs(&stats) < 0)\n        return \"getdevs() failed\";\n\n    for (int i = 0; i < stats.dinfo->numdevs; i++)\n    {\n        struct devstat* current = &stats.dinfo->devices[i];\n        if (current->device_type & DEVSTAT_TYPE_PASS)\n            continue;\n\n        char deviceName[128];\n        snprintf(deviceName, sizeof(deviceName), \"%s%d\", current->device_name, current->unit_number);\n\n        if (options->namePrefix.length && strncmp(deviceName, options->namePrefix.chars, options->namePrefix.length) != 0)\n            continue;\n\n        FFDiskIOResult* device = (FFDiskIOResult*) ffListAdd(result);\n        ffStrbufInitS(&device->name, deviceName);\n        ffStrbufInitF(&device->devPath, \"/dev/%s\", deviceName);\n        device->bytesRead = current->bytes_read;\n        device->readCount = current->num_reads;\n        device->bytesWritten = current->bytes_written;\n        device->writeCount = current->num_writes;\n    }\n\n    free(stats.dinfo->mem_ptr);\n    free(stats.dinfo);\n\n    return NULL;\n}\n\n#endif\n"
  },
  {
    "path": "src/detection/diskio/diskio_linux.c",
    "content": "#include \"diskio.h\"\n#include \"common/io.h\"\n#include \"common/properties.h\"\n#include \"common/stringUtils.h\"\n\n#include <ctype.h>\n#include <limits.h>\n#include <inttypes.h>\n#include <fcntl.h>\n\nstatic const char* parseDiskIOCounters(int dfd, const char* devName, FFlist* result, FFDiskIOOptions* options)\n{\n    FF_AUTO_CLOSE_FD int devfd = openat(dfd, \"device\", O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY);\n    if (devfd < 0) return \"virtual device\";\n\n    FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate();\n\n    {\n        if (ffAppendFileBufferRelative(devfd, \"vendor\", &name))\n        {\n            ffStrbufTrimRightSpace(&name);\n            if (name.length > 0)\n                ffStrbufAppendC(&name, ' ');\n        }\n\n        ffAppendFileBufferRelative(devfd, \"model\", &name);\n        ffStrbufTrimRightSpace(&name);\n\n        if (name.length == 0)\n            ffStrbufSetS(&name, devName);\n        else if (ffStrStartsWith(devName, \"nvme\"))\n        {\n            int devid, nsid;\n            if (sscanf(devName, \"nvme%dn%d\", &devid, &nsid) == 2)\n            {\n                bool multiNs = nsid > 1;\n                if (!multiNs)\n                {\n                    char pathSysBlock[16];\n                    snprintf(pathSysBlock, ARRAY_SIZE(pathSysBlock), \"nvme%dn2\", devid);\n                    multiNs = faccessat(devfd, pathSysBlock, F_OK, 0) == 0;\n                }\n                if (multiNs)\n                {\n                    // In Asahi Linux, there are multiple namespaces for the same NVMe drive.\n                    ffStrbufAppendF(&name, \" - %d\", nsid);\n                }\n            }\n        }\n\n        if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix))\n            return \"ignored\";\n    }\n\n    // I/Os merges sectors ticks ...\n    uint64_t nRead, sectorRead, nWritten, sectorWritten;\n    {\n        char sysBlockStat[PROC_FILE_BUFFSIZ];\n        ssize_t fileSize = ffReadFileDataRelative(dfd, \"stat\", ARRAY_SIZE(sysBlockStat) - 1, sysBlockStat);\n        if (fileSize <= 0) return \"failed to read stat file\";\n        sysBlockStat[fileSize] = '\\0';\n        if (sscanf(sysBlockStat, \"%\" PRIu64 \"%*u%\" PRIu64 \"%*u%\" PRIu64 \"%*u%\" PRIu64 \"%*u\", &nRead, &sectorRead, &nWritten, &sectorWritten) <= 0)\n            return \"invalid stat file format\";\n    }\n\n    FFDiskIOResult* device = (FFDiskIOResult*) ffListAdd(result);\n    ffStrbufInitMove(&device->name, &name);\n    ffStrbufInitF(&device->devPath, \"/dev/%s\", devName);\n    device->bytesRead = sectorRead * 512;\n    device->bytesWritten = sectorWritten * 512;\n    device->readCount = nRead;\n    device->writeCount = nWritten;\n\n    return NULL;\n}\n\nconst char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options)\n{\n    FF_AUTO_CLOSE_DIR DIR* sysBlockDirp = opendir(\"/sys/block/\");\n    if(sysBlockDirp == NULL)\n        return \"opendir(\\\"/sys/block/\\\") == NULL\";\n\n    struct dirent* sysBlockEntry;\n    while ((sysBlockEntry = readdir(sysBlockDirp)) != NULL)\n    {\n        const char* const devName = sysBlockEntry->d_name;\n\n        if (devName[0] == '.') continue;\n\n        FF_AUTO_CLOSE_FD int dfd = openat(dirfd(sysBlockDirp), devName, O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY);\n        if (dfd > 0) parseDiskIOCounters(dfd, devName, result, options);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/diskio/diskio_nbsd.c",
    "content": "#include \"diskio.h\"\n#include \"common/stringUtils.h\"\n#include \"common/mallocHelper.h\"\n\n#include <sys/iostat.h>\n#include <sys/sysctl.h>\n\nconst char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options)\n{\n    int mib[] = {CTL_HW, HW_IOSTATS, sizeof(struct io_sysctl)};\n    size_t len;\n    if (sysctl(mib, ARRAY_SIZE(mib), NULL, &len, NULL, 0) < 0)\n        return \"sysctl({HW_IOSTATS}, NULL) failed\";\n    uint32_t nDrive = (uint32_t) (len / sizeof(struct io_sysctl));\n\n    FF_AUTO_FREE struct io_sysctl* stats = malloc(len);\n\n    if (sysctl(mib, ARRAY_SIZE(mib), stats, &len, NULL, 0) < 0)\n        return \"sysctl({HW_IOSTATS}, stats) failed\";\n\n    for (uint32_t i = 0; i < nDrive; ++i)\n    {\n        struct io_sysctl* st = &stats[i];\n\n        if (options->namePrefix.length && strncmp(st->name, options->namePrefix.chars, options->namePrefix.length) != 0)\n            continue;\n\n        FFDiskIOResult* device = (FFDiskIOResult*) ffListAdd(result);\n        ffStrbufInitF(&device->devPath, \"/dev/%s\", st->name);\n        ffStrbufInitS(&device->name, st->name);\n        device->bytesRead = st->rbytes;\n        device->readCount = st->rxfer;\n        device->bytesWritten = st->wbytes;\n        device->writeCount = st->wxfer;\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/diskio/diskio_nosupport.c",
    "content": "#include \"diskio.h\"\n\nconst char* ffDiskIOGetIoCounters(FF_MAYBE_UNUSED FFlist* result, FF_MAYBE_UNUSED FFDiskIOOptions* options)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/diskio/diskio_obsd.c",
    "content": "#include \"diskio.h\"\n#include \"common/stringUtils.h\"\n#include \"common/mallocHelper.h\"\n\n#include <sys/disk.h>\n#include <sys/sysctl.h>\n\nconst char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options)\n{\n    int mib[] = {CTL_HW, HW_DISKSTATS};\n    size_t len;\n    if (sysctl(mib, ARRAY_SIZE(mib), NULL, &len, NULL, 0) < 0)\n        return \"sysctl({HW_DISKSTATS}, NULL) failed\";\n    uint32_t nDrive = (uint32_t) (len / sizeof(struct diskstats));\n\n    FF_AUTO_FREE struct diskstats* stats = malloc(len);\n\n    if (sysctl(mib, ARRAY_SIZE(mib), stats, &len, NULL, 0) < 0)\n        return \"sysctl({HW_DISKSTATS}, stats) failed\";\n\n    for (uint32_t i = 0; i < nDrive; ++i)\n    {\n        struct diskstats* st = &stats[i];\n\n        if (options->namePrefix.length && strncmp(st->ds_name, options->namePrefix.chars, options->namePrefix.length) != 0)\n            continue;\n\n        FFDiskIOResult* device = (FFDiskIOResult*) ffListAdd(result);\n        ffStrbufInitF(&device->devPath, \"/dev/%s\", st->ds_name);\n        ffStrbufInitS(&device->name, st->ds_name);\n        device->bytesRead = st->ds_rbytes;\n        device->readCount = st->ds_rxfer;\n        device->bytesWritten = st->ds_wbytes;\n        device->writeCount = st->ds_wxfer;\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/diskio/diskio_sunos.c",
    "content": "#include \"diskio.h\"\n#include \"common/stringUtils.h\"\n#include <kstat.h>\n\nstatic inline void kstatFreeWrap(kstat_ctl_t** pkc)\n{\n    assert(pkc);\n    if (*pkc)\n        kstat_close(*pkc);\n}\n\nconst char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options)\n{\n    __attribute__((__cleanup__(kstatFreeWrap))) kstat_ctl_t* kc = kstat_open();\n    if (!kc)\n        return \"kstat_open() failed\";\n\n    for (kstat_t* ks = kc->kc_chain; ks; ks = ks->ks_next)\n    {\n        if (ks->ks_type != KSTAT_TYPE_IO || !ffStrEquals(ks->ks_class, \"disk\")) continue;\n\n        if (options->namePrefix.length && strncmp(ks->ks_name, options->namePrefix.chars, options->namePrefix.length) != 0)\n            continue;\n\n        kstat_io_t kio;\n        if (kstat_read(kc, ks, &kio) < 0)\n            continue;\n\n        FFDiskIOResult* device = (FFDiskIOResult*) ffListAdd(result);\n        ffStrbufInit(&device->devPath); // unlike other platforms, `/dev/ks_name` is not available\n        ffStrbufInitS(&device->name, ks->ks_name);\n        device->bytesRead = kio.nread;\n        device->readCount = kio.reads;\n        device->bytesWritten = kio.nwritten;\n        device->writeCount = kio.writes;\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/diskio/diskio_windows.c",
    "content": "#include \"diskio.h\"\n#include \"common/io.h\"\n#include \"common/windows/unicode.h\"\n\n#include <windows.h>\n#include <winioctl.h>\n\nstatic bool detectPhysicalDisk(const wchar_t* szDevice, FFlist* result, FFDiskIOOptions* options)\n{\n    FF_AUTO_CLOSE_FD HANDLE hDevice = CreateFileW(szDevice, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);\n    if (hDevice == INVALID_HANDLE_VALUE)\n        return false;\n\n    DWORD retSize;\n    char sddBuffer[4096];\n    if(!DeviceIoControl(\n        hDevice,\n        IOCTL_STORAGE_QUERY_PROPERTY,\n        &(STORAGE_PROPERTY_QUERY) {\n            .PropertyId = StorageDeviceProperty,\n            .QueryType = PropertyStandardQuery,\n        },\n        sizeof(STORAGE_PROPERTY_QUERY),\n        &sddBuffer,\n        ARRAY_SIZE(sddBuffer),\n        &retSize,\n        NULL\n    ) || retSize == 0)\n        return true;\n\n    FFDiskIOResult* device = (FFDiskIOResult*) ffListAdd(result);\n    STORAGE_DEVICE_DESCRIPTOR* sdd = (STORAGE_DEVICE_DESCRIPTOR*) sddBuffer;\n\n    ffStrbufInit(&device->name);\n    if (sdd->VendorIdOffset != 0)\n    {\n        ffStrbufSetS(&device->name, (const char*) sddBuffer + sdd->VendorIdOffset);\n        ffStrbufTrim(&device->name, ' ');\n    }\n    if (sdd->ProductIdOffset != 0)\n    {\n        if (device->name.length)\n            ffStrbufAppendC(&device->name, ' ');\n\n        ffStrbufAppendS(&device->name, (const char*) sddBuffer + sdd->ProductIdOffset);\n        ffStrbufTrimRight(&device->name, ' ');\n    }\n\n    if (!device->name.length)\n        ffStrbufSetWS(&device->name, szDevice);\n\n    if (options->namePrefix.length && !ffStrbufStartsWith(&device->name, &options->namePrefix))\n    {\n        ffStrbufDestroy(&device->name);\n        result->length--;\n        return true;\n    }\n\n    DISK_PERFORMANCE dp = {};\n    if (DeviceIoControl(hDevice, IOCTL_DISK_PERFORMANCE, NULL, 0, &dp, sizeof(dp), &retSize, NULL))\n    {\n        device->bytesRead = (uint64_t) dp.BytesRead.QuadPart;\n        device->readCount = (uint64_t) dp.ReadCount;\n        device->bytesWritten = (uint64_t) dp.BytesWritten.QuadPart;\n        device->writeCount = (uint64_t) dp.WriteCount;\n    }\n    else\n    {\n        ffStrbufDestroy(&device->name);\n        result->length--;\n    }\n\n    ffStrbufInitWS(&device->devPath, szDevice);\n\n    return true;\n}\n\nconst char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options)\n{\n    {\n        wchar_t szPhysicalDrive[32] = L\"\\\\\\\\.\\\\PhysicalDrive\";\n        wchar_t* pNum = szPhysicalDrive + strlen(\"\\\\\\\\.\\\\PhysicalDrive\");\n        for (uint32_t idev = 0; ; ++idev)\n        {\n            _ultow(idev, pNum, 10);\n\n            if (!detectPhysicalDisk(szPhysicalDrive, result, options))\n                break;\n        }\n    }\n\n    {\n        wchar_t szCdrom[32] = L\"\\\\\\\\.\\\\CDROM\";\n        wchar_t* pNum = szCdrom + strlen(\"\\\\\\\\.\\\\CDROM\");\n        for (uint32_t idev = 0; ; ++idev)\n        {\n            _ultow(idev, pNum, 10);\n\n            if (!detectPhysicalDisk(szCdrom, result, options))\n                break;\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/displayserver/displayserver.c",
    "content": "#include \"displayserver.h\"\n\nFFDisplayResult* ffdsAppendDisplay(\n    FFDisplayServerResult* result,\n    uint32_t width,\n    uint32_t height,\n    double refreshRate,\n    uint32_t dpi,\n    uint32_t preferredWidth,\n    uint32_t preferredHeight,\n    double preferredRefreshRate,\n    uint32_t rotation,\n    FFstrbuf* name,\n    FFDisplayType type,\n    bool primary,\n    uint64_t id,\n    uint32_t physicalWidth,\n    uint32_t physicalHeight,\n    const char* platformApi)\n{\n    if(width == 0 || height == 0)\n        return NULL;\n\n    FFDisplayResult* display = (FFDisplayResult*) ffListAdd(&result->displays);\n    display->width = width;\n    display->height = height;\n    display->refreshRate = refreshRate;\n    display->dpi = dpi ?: 96; // 0 means unknown\n    display->preferredWidth = preferredWidth;\n    display->preferredHeight = preferredHeight;\n    display->preferredRefreshRate = preferredRefreshRate;\n    display->rotation = rotation;\n    ffStrbufInitMove(&display->name, name);\n    display->type = type;\n    display->id = id;\n    display->physicalWidth = physicalWidth;\n    display->physicalHeight = physicalHeight;\n    display->primary = primary;\n    display->platformApi = platformApi;\n\n    display->bitDepth = 0;\n    display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN;\n    display->manufactureYear = 0;\n    display->manufactureWeek = 0;\n    display->serial = 0;\n    display->drrStatus = FF_DISPLAY_DRR_STATUS_UNKNOWN;\n\n    return display;\n}\n\nvoid ffConnectDisplayServerImpl(FFDisplayServerResult* ds);\n\nconst FFDisplayServerResult* ffConnectDisplayServer()\n{\n    static FFDisplayServerResult result;\n    if (result.displays.elementSize == 0)\n    {\n        ffStrbufInit(&result.wmProcessName);\n        ffStrbufInit(&result.wmPrettyName);\n        ffStrbufInit(&result.wmProtocolName);\n        ffStrbufInit(&result.deProcessName);\n        ffStrbufInit(&result.dePrettyName);\n        ffListInit(&result.displays, sizeof(FFDisplayResult));\n        ffConnectDisplayServerImpl(&result);\n    }\n    return &result;\n}\n"
  },
  {
    "path": "src/detection/displayserver/displayserver.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/display/option.h\"\n\n#define FF_DE_PRETTY_PLASMA \"KDE Plasma\"\n#define FF_DE_PRETTY_GNOME \"GNOME\"\n#define FF_DE_PRETTY_GNOME_CLASSIC \"GNOME Classic\"\n#define FF_DE_PRETTY_XFCE4 \"Xfce4\"\n#define FF_DE_PRETTY_CINNAMON \"Cinnamon\"\n#define FF_DE_PRETTY_MATE \"Mate\"\n#define FF_DE_PRETTY_LXDE \"LXDE\"\n#define FF_DE_PRETTY_LXQT \"LXQt\"\n#define FF_DE_PRETTY_BUDGIE \"Budgie\"\n#define FF_DE_PRETTY_CDE \"CDE\"\n#define FF_DE_PRETTY_UNITY \"Unity\"\n#define FF_DE_PRETTY_UKUI \"UKUI\"\n\n#define FF_WM_PRETTY_KWIN \"KWin\"\n#define FF_WM_PRETTY_MUTTER \"Mutter\"\n#define FF_WM_PRETTY_MUFFIN \"Muffin\"\n#define FF_WM_PRETTY_MARCO \"Marco\"\n#define FF_WM_PRETTY_XFWM4 \"Xfwm4\"\n#define FF_WM_PRETTY_OPENBOX \"Openbox\"\n#define FF_WM_PRETTY_I3 \"i3\"\n#define FF_WM_PRETTY_HYPRLAND \"Hyprland\"\n#define FF_WM_PRETTY_WAYFIRE \"Wayfire\"\n#define FF_WM_PRETTY_SWAY \"Sway\"\n#define FF_WM_PRETTY_BSPWM \"bspwm\"\n#define FF_WM_PRETTY_DWM \"dwm\"\n#define FF_WM_PRETTY_WESTON \"Weston\"\n#define FF_WM_PRETTY_XMONAD \"XMonad\"\n#define FF_WM_PRETTY_WSLG \"WSLg\"\n#define FF_WM_PRETTY_TINYWM \"TinyWM\"\n#define FF_WM_PRETTY_QTILE \"Qtile\"\n#define FF_WM_PRETTY_HERBSTLUFTWM \"herbstluftwm\"\n#define FF_WM_PRETTY_ICEWM \"IceWM\"\n#define FF_WM_PRETTY_SPECTRWM \"spectrwm\"\n#define FF_WM_PRETTY_DTWM \"dtwm\"\n#define FF_WM_PRETTY_FVWM \"fvwm\"\n#define FF_WM_PRETTY_CTWM \"ctwm\"\n#define FF_WM_PRETTY_RATPOISON \"ratpoison\"\n\n#define FF_WM_PROTOCOL_TTY \"TTY\"\n#define FF_WM_PROTOCOL_X11 \"X11\"\n#define FF_WM_PROTOCOL_WAYLAND \"Wayland\"\n#define FF_WM_PROTOCOL_SURFACEFLINGER \"SurfaceFlinger\"\n\ntypedef enum __attribute__((__packed__)) FFDisplayType {\n    FF_DISPLAY_TYPE_UNKNOWN,\n    FF_DISPLAY_TYPE_BUILTIN,\n    FF_DISPLAY_TYPE_EXTERNAL,\n} FFDisplayType;\n\ntypedef enum __attribute__((__packed__)) FFDisplayHdrStatus\n{\n    FF_DISPLAY_HDR_STATUS_UNKNOWN,\n    FF_DISPLAY_HDR_STATUS_UNSUPPORTED,\n    FF_DISPLAY_HDR_STATUS_SUPPORTED,\n    FF_DISPLAY_HDR_STATUS_ENABLED,\n} FFDisplayHdrStatus;\n\ntypedef enum __attribute__((__packed__)) FFDisplayVrrStatus\n{\n    FF_DISPLAY_DRR_STATUS_UNKNOWN,\n    FF_DISPLAY_DRR_STATUS_DISABLED,\n    FF_DISPLAY_DRR_STATUS_ENABLED,\n} FFDisplayVrrStatus;\n\ntypedef struct FFDisplayResult\n{\n    uint32_t width; // in px\n    uint32_t height; // in px\n    double refreshRate; // in Hz\n    uint32_t dpi; // Base 96\n    uint32_t preferredWidth; // in px\n    uint32_t preferredHeight; // in px\n    double preferredRefreshRate; // in Hz\n    FFstrbuf name;\n    FFDisplayType type;\n    uint32_t rotation;\n    uint64_t id; // platform dependent\n    uint32_t physicalWidth; // in mm\n    uint32_t physicalHeight; // in mm\n    bool primary;\n    const char* platformApi;\n    uint8_t bitDepth;\n    FFDisplayHdrStatus hdrStatus;\n    uint16_t manufactureYear;\n    uint16_t manufactureWeek;\n    uint32_t serial;\n    FFDisplayVrrStatus drrStatus;\n} FFDisplayResult;\n\ntypedef struct FFDisplayServerResult\n{\n    FFstrbuf wmProcessName;\n    FFstrbuf wmPrettyName;\n    FFstrbuf wmProtocolName;\n    FFstrbuf deProcessName;\n    FFstrbuf dePrettyName;\n    FFlist displays; //List of FFDisplayResult\n} FFDisplayServerResult;\n\nconst FFDisplayServerResult* ffConnectDisplayServer();\n\nFFDisplayResult* ffdsAppendDisplay(\n    FFDisplayServerResult* result,\n    uint32_t width,\n    uint32_t height,\n    double refreshRate,\n    uint32_t dpi,\n    uint32_t preferredWidth,\n    uint32_t preferredHeight,\n    double preferredRefreshRate,\n    uint32_t rotation,\n    FFstrbuf* name,\n    FFDisplayType type,\n    bool primary,\n    uint64_t id,\n    uint32_t physicalWidth,\n    uint32_t physicalHeight,\n    const char* platformApi);\n"
  },
  {
    "path": "src/detection/displayserver/displayserver_android.c",
    "content": "#include \"displayserver.h\"\n#include \"common/settings.h\"\n#include \"common/processing.h\"\n#include \"linux/displayserver_linux.h\"\n\n#include <math.h>\n\nstatic bool checkHdrStatus(FFDisplayResult* display)\n{\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n\n    if (ffSettingsGetAndroidProperty(\"ro.surface_flinger.has_HDR_display\", &buffer))\n    {\n        if (ffStrbufIgnCaseEqualS(&buffer, \"true\"))\n        {\n            display->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED;\n\n            if (ffSettingsGetAndroidProperty(\"persist.sys.hdr_mode\", &buffer) &&\n                ffStrbufToUInt(&buffer, 0) > 0)\n                display->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED;\n\n            return true;\n        }\n        else\n        {\n            display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED;\n            return true;\n        }\n    }\n\n    display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN;\n    return false;\n}\n\nstatic void detectWithDumpsys(FFDisplayServerResult* ds)\n{\n    FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();\n    if (ffProcessAppendStdOut(&buf, (char* []) {\n        \"/system/bin/dumpsys\",\n        \"display\",\n        NULL,\n    }) != NULL || buf.length == 0)\n        return; // Only works in `adb shell`, or when rooted\n\n    uint32_t index = 0;\n    while ((index = ffStrbufNextIndexS(&buf, index, \"DisplayDeviceInfo\")) < buf.length)\n    {\n        index += strlen(\"DisplayDeviceInfo\");\n        uint32_t nextIndex = ffStrbufNextIndexC(&buf, index, '\\n');\n        buf.chars[nextIndex] = '\\0';\n        const char* info = buf.chars + index;\n\n        // {\"Builtin display\": uniqueId=\"local:4630947134992368259\", 1440 x 3200, modeId 2, defaultModeId 1, supportedModes [{id=1, width=1440, height=3200, fps=60.000004, alternativeRefreshRates=[24.000002, 30.000002, 40.0, 120.00001, 120.00001, 120.00001, 120.00001, 120.00001]},\n        FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateA(64);\n        unsigned width = 0, height = 0, modeId = 0;\n        double refreshRate = 0;\n        // {\"Builtin display\": uniqueId=\"local:4630947134992368259\", 1440 x 3200, modeId 2\n        int res = sscanf(info, \"{\\\"%63[^\\\"]\\\":%*s%u x %u, modeId%u\", name.chars, &width, &height, &modeId);\n        if (res >= 3)\n        {\n            if (res == 4)\n            {\n                ++info; // skip first '{'\n                while ((info = strchr(info, '{')))\n                {\n                    ++info;\n\n                    unsigned id;\n                    double fps;\n                    // id=1, width=1440, height=3200, fps=60.000004,\n                    if (sscanf(info, \"id=%u, %*s%*s fps=%lf\", &id, &fps) >= 2)\n                    {\n                        if (id == modeId)\n                        {\n                            refreshRate = fps;\n                            break;\n                        }\n                    }\n                    else\n                        break;\n                }\n            }\n\n            ffStrbufRecalculateLength(&name);\n            FFDisplayResult* display = ffdsAppendDisplay(ds,\n                (uint32_t)width, (uint32_t)height,\n                refreshRate,\n                0,\n                0, 0,\n                0,\n                0,\n                &name,\n                FF_DISPLAY_TYPE_UNKNOWN,\n                false,\n                0,\n                0,\n                0,\n                \"dumpsys\"\n            );\n            if (display) display->hdrStatus = checkHdrStatus(display);\n        }\n\n        index = nextIndex + 1;\n    }\n}\n\nstatic bool detectWithGetprop(FFDisplayServerResult* ds)\n{\n    // Only for MiUI\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n\n    if (ffSettingsGetAndroidProperty(\"persist.sys.miui_resolution\", &buffer) &&\n        ffStrbufContainC(&buffer, ','))\n    {\n        // 1440,3200,560 => width,height,densityDpi\n        uint32_t width = (uint32_t) ffStrbufToUInt(&buffer, 0);\n        ffStrbufSubstrAfterFirstC(&buffer, ',');\n        uint32_t height = (uint32_t) ffStrbufToUInt(&buffer, 0);\n        ffStrbufSubstrAfterFirstC(&buffer, ',');\n        uint32_t dpi = (uint32_t) ffStrbufToUInt(&buffer, 0) * 96 / 160;\n        FFDisplayResult* display = ffdsAppendDisplay(ds,\n            width, height,\n            0,\n            dpi,\n            0, 0,\n            0,\n            0,\n            NULL,\n            FF_DISPLAY_TYPE_BUILTIN,\n            false,\n            0,\n            0,\n            0,\n            \"getprop\"\n        );\n        if (display) display->hdrStatus = checkHdrStatus(display);\n        return !!display;\n    }\n\n    return false;\n}\n\nstatic bool detectDE(FFDisplayServerResult* ds)\n{\n    if (ffSettingsGetAndroidProperty(\"ro.vivo.os.build.display.id\", &ds->dePrettyName)) // OriginOS 6\n    {\n        ffStrbufAppendC(&ds->dePrettyName, ' ');\n        ffSettingsGetAndroidProperty(\"ro.vivo.product.version\", &ds->dePrettyName); // PD2505D_xxx\n        return true;\n    }\n    if (ffSettingsGetAndroidProperty(\"ro.build.version.magic\", &ds->dePrettyName) ||\n        ffSettingsGetAndroidProperty(\"ro.build.version.emui\", &ds->dePrettyName))\n    {\n        ffStrbufReplaceAllC(&ds->dePrettyName, '_', ' ');\n        return true;\n    }\n    if (ffSettingsGetAndroidProperty(\"ro.mi.os.version.name\", &ds->dePrettyName))\n    {\n        // MiUI like\n        ffStrbufClear(&ds->dePrettyName);\n        ffSettingsGetAndroidProperty(\"ro.build.version.incremental\", &ds->dePrettyName); // Detail version number\n        if (ffStrbufStartsWithS(&ds->dePrettyName, \"OS\"))\n        {\n            ds->dePrettyName.chars[0] = 'S';\n            ds->dePrettyName.chars[1] = ' ';\n            ffStrbufPrependS(&ds->dePrettyName, \"HyperO\");\n        }\n        else if (ffStrbufStartsWithS(&ds->dePrettyName, \"V\"))\n        {\n            ds->dePrettyName.chars[0] = ' ';\n            ffStrbufPrependS(&ds->dePrettyName, \"MiUI\");\n        }\n        else\n            ffStrbufSetStatic(&ds->dePrettyName, \"MiUI\");\n        return true;\n    }\n    if (ffSettingsGetAndroidProperty(\"ro.build.version.oplusrom\", &ds->dePrettyName))\n    {\n        if (ffStrbufStartsWithS(&ds->dePrettyName, \"V\"))\n            ffStrbufSubstrAfter(&ds->dePrettyName, 0);\n        ffStrbufPrependS(&ds->dePrettyName, \"ColorOS\");\n        return true;\n    }\n    if (ffSettingsGetAndroidProperty(\"ro.oxygen.version\", &ds->dePrettyName))\n    {\n        ffStrbufPrependS(&ds->dePrettyName, \"OxygenOS\");\n        return true;\n    }\n    if (ffSettingsGetAndroidProperty(\"ro.build.display.id\", &ds->dePrettyName))\n    {\n        if (ffStrbufStartsWithS(&ds->dePrettyName, \"RedMagicOS\"))\n            ffStrbufInsertNC(&ds->dePrettyName, strlen(\"RedMagicOS\"), 1, ' ');\n\n        // Google Pixel uses native Android\n        return true;\n    }\n\n    return false;\n}\n\nvoid ffConnectDisplayServerImpl(FFDisplayServerResult* ds)\n{\n    const char* error = ffdsConnectXcbRandr(ds);\n    if (error)\n        error = ffdsConnectXrandr(ds);\n    if (!error)\n    {\n        ffdsDetectWMDE(ds);\n        return;\n    }\n\n    // https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager\n    ffStrbufSetStatic(&ds->wmProcessName, \"system_server\");\n    ffStrbufSetStatic(&ds->wmPrettyName, \"WindowManager\"); // A system service managed by system_server\n    ffStrbufSetStatic(&ds->wmProtocolName, FF_WM_PROTOCOL_SURFACEFLINGER);\n\n    if (!detectWithGetprop(ds))\n        detectWithDumpsys(ds);\n\n    detectDE(ds);\n}\n"
  },
  {
    "path": "src/detection/displayserver/displayserver_apple.c",
    "content": "#include \"displayserver.h\"\n#include \"common/apple/cf_helpers.h\"\n#include \"common/stringUtils.h\"\n#include \"common/edidHelper.h\"\n#include \"detection/os/os.h\"\n\n#include <stdlib.h>\n#include <string.h>\n#include <assert.h>\n#include <CoreGraphics/CGDirectDisplay.h>\n#include <CoreVideo/CVDisplayLink.h>\n\n#ifdef MAC_OS_X_VERSION_10_15\nextern Boolean CoreDisplay_Display_SupportsHDRMode(CGDirectDisplayID display) __attribute__((weak_import));\nextern Boolean CoreDisplay_Display_IsHDRModeEnabled(CGDirectDisplayID display) __attribute__((weak_import));\nextern CFDictionaryRef CoreDisplay_DisplayCreateInfoDictionary(CGDirectDisplayID display) __attribute__((weak_import));\n#else\n#include <IOKit/graphics/IOGraphicsLib.h>\n#endif\n\nstatic void detectDisplays(FFDisplayServerResult* ds)\n{\n    CGDirectDisplayID screens[128];\n    uint32_t screenCount;\n    if(CGGetOnlineDisplayList(ARRAY_SIZE(screens), screens, &screenCount) != kCGErrorSuccess)\n        return;\n\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n    for(uint32_t i = 0; i < screenCount; i++)\n    {\n        CGDirectDisplayID screen = screens[i];\n        CGDisplayModeRef mode = CGDisplayCopyDisplayMode(screen);\n        if(mode)\n        {\n            //https://github.com/glfw/glfw/commit/aab08712dd8142b642e2042e7b7ba563acd07a45\n            double refreshRate = CGDisplayModeGetRefreshRate(mode);\n\n            if (refreshRate == 0)\n            {\n                #pragma clang diagnostic push\n                #pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n                CVDisplayLinkRef link;\n                if(CVDisplayLinkCreateWithCGDisplay(screen, &link) == kCVReturnSuccess)\n                {\n                    const CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link);\n                    if (!(time.flags & kCVTimeIsIndefinite))\n                        refreshRate = time.timeScale / (double) time.timeValue; //59.97...\n                    CVDisplayLinkRelease(link);\n                }\n                #pragma clang diagnostic pop\n            }\n\n            ffStrbufClear(&buffer);\n            CFDictionaryRef FF_CFTYPE_AUTO_RELEASE displayInfo = NULL;\n            #ifdef MAC_OS_X_VERSION_10_15\n            if(CoreDisplay_DisplayCreateInfoDictionary)\n                displayInfo = CoreDisplay_DisplayCreateInfoDictionary(screen);\n            #else\n            {\n                io_service_t servicePort = CGDisplayIOServicePort(screen);\n                displayInfo = IODisplayCreateInfoDictionary(servicePort, kIODisplayOnlyPreferredName);\n            }\n            #endif\n\n            uint32_t physicalWidth = 0, physicalHeight = 0;\n            uint32_t preferredWidth = 0, preferredHeight = 0;\n            double preferredRefreshRate = 0;\n\n            if(displayInfo)\n            {\n                CFDictionaryRef productNames;\n                if(ffCfDictGetDict(displayInfo, CFSTR(kDisplayProductName), &productNames) == NULL)\n                    ffCfDictGetString(productNames, CFSTR(\"en_US\"), &buffer);\n\n                // CGDisplayScreenSize reports invalid result for external displays on old Intel MacBook Pro\n                CFDataRef edidRef = (CFDataRef) CFDictionaryGetValue(displayInfo, CFSTR(kIODisplayEDIDKey));\n                if (edidRef && CFGetTypeID(edidRef) == CFDataGetTypeID())\n                {\n                    const uint8_t* edidData = CFDataGetBytePtr(edidRef);\n                    uint32_t edidLength = (uint32_t) CFDataGetLength(edidRef);\n                    if (edidLength >= 128)\n                        ffEdidGetPhysicalSize(edidData, &physicalWidth, &physicalHeight);\n                }\n\n                if (!physicalWidth || !physicalHeight)\n                {\n                    if (ffCfDictGetInt(displayInfo, CFSTR(kDisplayHorizontalImageSize), (int*) &physicalWidth) == NULL)\n                        ffCfDictGetInt(displayInfo, CFSTR(kDisplayVerticalImageSize), (int*) &physicalHeight);\n                }\n\n                ffCfDictGetInt(displayInfo, CFSTR(\"kCGDisplayPixelWidth\"), (int*) &preferredWidth);\n                ffCfDictGetInt(displayInfo, CFSTR(\"kCGDisplayPixelHeight\"), (int*) &preferredHeight);\n                if (preferredWidth && preferredHeight)\n                {\n                    FF_CFTYPE_AUTO_RELEASE CFArrayRef allModes = CGDisplayCopyAllDisplayModes(screen, NULL);\n                    if (allModes)\n                    {\n                        for (CFIndex i = 0, count = CFArrayGetCount(allModes); i < count; i++)\n                        {\n                            CGDisplayModeRef modeInfo = (CGDisplayModeRef) CFArrayGetValueAtIndex(allModes, i);\n                            if (CGDisplayModeGetPixelWidth(modeInfo) == preferredWidth && CGDisplayModeGetPixelHeight(modeInfo) == preferredHeight)\n                            {\n                                double rr = CGDisplayModeGetRefreshRate(modeInfo);\n                                if (rr > preferredRefreshRate) preferredRefreshRate = rr;\n                                break;\n                            }\n                        }\n                    }\n                }\n            }\n\n            if ((!physicalWidth || !physicalHeight) && CGDisplayPrimaryDisplay(screen) == screen) // #1406\n            {\n                CGSize size = CGDisplayScreenSize(screen);\n                physicalWidth = (uint32_t) (size.width + 0.5);\n                physicalHeight = (uint32_t) (size.height + 0.5);\n            }\n\n            uint32_t pixelWidth = (uint32_t) CGDisplayModeGetPixelWidth(mode);\n            uint32_t pixelHeight = (uint32_t) CGDisplayModeGetPixelHeight(mode);\n\n            FFDisplayResult* display = ffdsAppendDisplay(ds,\n                pixelWidth,\n                pixelHeight,\n                refreshRate,\n                pixelHeight * 96 / (uint32_t)CGDisplayModeGetHeight(mode),\n                preferredWidth,\n                preferredHeight,\n                preferredRefreshRate,\n                (uint32_t)CGDisplayRotation(screen),\n                &buffer,\n                CGDisplayIsBuiltin(screen) ? FF_DISPLAY_TYPE_BUILTIN : FF_DISPLAY_TYPE_EXTERNAL,\n                CGDisplayIsMain(screen),\n                (uint64_t)screen,\n                physicalWidth,\n                physicalHeight,\n                \"CoreGraphics\"\n            );\n            if (display)\n            {\n                #ifndef MAC_OS_X_VERSION_10_11\n                FF_CFTYPE_AUTO_RELEASE CFStringRef pe = CGDisplayModeCopyPixelEncoding(mode);\n                if (pe) display->bitDepth = (uint8_t) (CFStringGetLength(pe) - CFStringFind(pe, CFSTR(\"B\"), 0).location);\n                #else\n                // https://stackoverflow.com/a/33519316/9976392\n                // Also shitty, but better than parsing `CFCopyDescription(mode)`\n                CFDictionaryRef dict = (CFDictionaryRef) *((int64_t *)mode + 2);\n                if (CFGetTypeID(dict) == CFDictionaryGetTypeID())\n                {\n                    int32_t bitDepth;\n                    ffCfDictGetInt(dict, kCGDisplayBitsPerSample, &bitDepth);\n                    display->bitDepth = (uint8_t) bitDepth;\n                }\n                #endif\n\n                if (display->type == FF_DISPLAY_TYPE_BUILTIN && displayInfo)\n                    display->hdrStatus = CFDictionaryContainsKey(displayInfo, CFSTR(\"ReferencePeakHDRLuminance\"))\n                        ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED;\n                #ifdef MAC_OS_X_VERSION_10_15\n                else if (CoreDisplay_Display_SupportsHDRMode)\n                {\n                    if (CoreDisplay_Display_SupportsHDRMode(screen))\n                    {\n                        display->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED;\n                        if (CoreDisplay_Display_IsHDRModeEnabled && CoreDisplay_Display_IsHDRModeEnabled(screen))\n                            display->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED;\n                    }\n                    else\n                        display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED;\n                }\n                #endif\n\n                display->serial = CGDisplaySerialNumber(screen);\n\n                if (displayInfo)\n                {\n                    int value;\n                    if (ffCfDictGetInt(displayInfo, CFSTR(kDisplayYearOfManufacture), &value) == NULL)\n                        display->manufactureYear = (uint16_t) value;\n                    if (ffCfDictGetInt(displayInfo, CFSTR(kDisplayWeekOfManufacture), &value) == NULL)\n                        display->manufactureWeek = (uint16_t) value;\n                }\n            }\n            CGDisplayModeRelease(mode);\n        }\n        CGDisplayRelease(screen);\n    }\n}\n\nvoid ffConnectDisplayServerImpl(FFDisplayServerResult* ds)\n{\n    {\n        FF_CFTYPE_AUTO_RELEASE CFMachPortRef port = CGWindowServerCreateServerPort();\n        if (port)\n        {\n            ffStrbufSetStatic(&ds->wmProcessName, \"WindowServer\");\n            ffStrbufSetStatic(&ds->wmPrettyName, \"Quartz Compositor\");\n        }\n    }\n\n    detectDisplays(ds);\n}\n"
  },
  {
    "path": "src/detection/displayserver/displayserver_haiku.cpp",
    "content": "extern \"C\" {\n#include \"displayserver.h\"\n}\n\n#include <math.h>\n\n#include <Application.h>\n#include <Screen.h>\n\nextern \"C\" void ffConnectDisplayServerImpl(FFDisplayServerResult* ds);\n\nstatic void detectDisplays(FFDisplayServerResult* ds)\n{\n    // We need a valid be_app to query the app_server here.\n    BApplication app(\"application/x-vnd.fastfetch-cli-fastfetch\");\n    BScreen s{}; // default screen is the main one\n    bool main = true;\n\n    do\n    {\n        if (!s.IsValid())\n            continue;\n\n        display_mode mode;\n        if (s.GetMode(&mode) != B_OK)\n            continue;\n\n        FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateA(128);\n        monitor_info monitor;\n        // WARNING: This is experimental new Haiku API\n        status_t err = s.GetMonitorInfo(&monitor);\n        if (err == B_OK)\n        {\n            ffStrbufSetF(&name, \"%s %s\", monitor.vendor, monitor.name);\n            ffStrbufTrimRightSpace(&name);\n        }\n\n        uint32_t width = (uint32_t) s.Frame().Width() + 1;\n        uint32_t height = (uint32_t) (uint32_t)s.Frame().Height() + 1;\n        FFDisplayResult* res = ffdsAppendDisplay(ds,\n            width,\n            height,\n            (double)mode.timing.pixel_clock * 1000 / (mode.timing.v_total * mode.timing.h_total),\n            0,\n            0,\n            0,\n            0,\n            0,\n            &name,\n            FF_DISPLAY_TYPE_UNKNOWN,\n            main,\n            (uint64_t) s.ID().id,\n            0,\n            0,\n            \"BScreen\"\n        );\n        if (err == B_OK)\n        {\n            res->manufactureWeek = monitor.produced.week;\n            res->manufactureYear = monitor.produced.year;\n        }\n        main = false;\n    } while (s.SetToNext() == B_OK);\n\n    return;\n}\n\nvoid ffConnectDisplayServerImpl(FFDisplayServerResult* ds)\n{\n    ffStrbufSetStatic(&ds->wmProcessName, \"app_server\");\n    ffStrbufSetStatic(&ds->wmPrettyName, \"Application Server\");\n    ffStrbufSetStatic(&ds->dePrettyName, \"Application Kit\");\n\n    detectDisplays(ds);\n}\n"
  },
  {
    "path": "src/detection/displayserver/displayserver_windows.c",
    "content": "#include \"displayserver.h\"\n#include \"common/edidHelper.h\"\n#include \"common/windows/registry.h\"\n#include \"common/windows/unicode.h\"\n\n#include <windows.h>\n#include <shellscalingapi.h>\n\n// http://undoc.airesoft.co.uk/user32.dll/IsThreadDesktopComposited.php\nBOOL WINAPI IsThreadDesktopComposited();\nBOOL WINAPI GetDpiForMonitorInternal(HMONITOR hmonitor, MONITOR_DPI_TYPE dpiType, UINT* dpiX, UINT* dpiY);\n\nstatic void detectDisplays(FFDisplayServerResult* ds)\n{\n    DISPLAYCONFIG_PATH_INFO paths[128];\n    uint32_t pathCount = ARRAY_SIZE(paths);\n    DISPLAYCONFIG_MODE_INFO modes[256];\n    uint32_t modeCount = ARRAY_SIZE(modes);\n\n    if (QueryDisplayConfig(\n        QDC_ONLY_ACTIVE_PATHS,\n        &pathCount,\n        paths,\n        &modeCount,\n        modes,\n        NULL) == ERROR_SUCCESS)\n    {\n        for (uint32_t i = 0; i < pathCount; ++i)\n        {\n            const DISPLAYCONFIG_PATH_INFO* path = &paths[i];\n            const DISPLAYCONFIG_SOURCE_MODE* sourceMode = &modes[path->sourceInfo.modeInfoIdx].sourceMode;\n\n            FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate();\n            uint32_t physicalWidth = 0, physicalHeight = 0;\n\n            DISPLAYCONFIG_TARGET_DEVICE_NAME targetName = {\n                .header = {\n                    .type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME,\n                    .size = sizeof(targetName),\n                    .adapterId = path->targetInfo.adapterId,\n                    .id = path->targetInfo.id,\n                },\n            };\n            FF_LIST_AUTO_DESTROY edid = ffListCreate(sizeof(uint8_t));\n\n            if(DisplayConfigGetDeviceInfo(&targetName.header) == ERROR_SUCCESS)\n            {\n                wchar_t regPath[256] = L\"SYSTEM\\\\CurrentControlSet\\\\Enum\";\n                wchar_t* pRegPath = regPath + strlen(\"SYSTEM\\\\CurrentControlSet\\\\Enum\");\n                wchar_t* pDevPath = targetName.monitorDevicePath + strlen(\"\\\\\\\\?\");\n                while (*pDevPath && *pDevPath != L'{')\n                {\n                    if (*pDevPath == L'#')\n                        *pRegPath = L'\\\\';\n                    else\n                        *pRegPath = *pDevPath;\n                    ++pRegPath;\n                    ++pDevPath;\n                    assert(pRegPath < regPath + ARRAY_SIZE(regPath) + strlen(\"Device Parameters\"));\n                }\n                wcscpy(pRegPath, L\"Device Parameters\");\n\n                FF_AUTO_CLOSE_FD HANDLE hKey = NULL;\n                if (ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, regPath, &hKey, NULL) &&\n                    ffRegReadData(hKey, L\"EDID\", &edid, NULL) &&\n                    ffEdidIsValid(edid.data, edid.length))\n                {\n                    ffEdidGetName(edid.data, &name);\n                    ffEdidGetPhysicalSize(edid.data, &physicalWidth, &physicalHeight);\n                }\n                else\n                {\n                    ffListClear(&edid);\n                    if (targetName.flags.friendlyNameFromEdid)\n                        ffStrbufSetWS(&name, targetName.monitorFriendlyDeviceName);\n                    else\n                    {\n                        ffStrbufSetWS(&name, targetName.monitorDevicePath);\n                        ffStrbufSubstrAfterFirstC(&name, '#');\n                        ffStrbufSubstrBeforeFirstC(&name, '#');\n                    }\n                }\n            }\n\n            uint32_t width = sourceMode->width;\n            uint32_t height = sourceMode->height;\n            uint32_t rotation;\n            switch (path->targetInfo.rotation)\n            {\n                case DISPLAYCONFIG_ROTATION_ROTATE90: rotation = 90; break;\n                case DISPLAYCONFIG_ROTATION_ROTATE180: rotation = 180; break;\n                case DISPLAYCONFIG_ROTATION_ROTATE270: rotation = 270; break;\n                default: rotation = 0; break;\n            }\n\n            DISPLAYCONFIG_TARGET_PREFERRED_MODE preferredMode = {\n                .header = {\n                    .type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_PREFERRED_MODE,\n                    .size = sizeof(preferredMode),\n                    .adapterId = path->targetInfo.adapterId,\n                    .id = path->targetInfo.id,\n                }\n            };\n            double preferredRefreshRate = 0;\n            if (DisplayConfigGetDeviceInfo(&preferredMode.header) == ERROR_SUCCESS)\n            {\n                DISPLAYCONFIG_RATIONAL freq = preferredMode.targetMode.targetVideoSignalInfo.vSyncFreq;\n                preferredRefreshRate = freq.Numerator / (double) freq.Denominator;\n            }\n\n            uint32_t systemDpi = 0;\n            HMONITOR hMonitor = MonitorFromPoint(*(POINT*)&sourceMode->position, MONITOR_DEFAULTTONULL);\n            if (hMonitor)\n            {\n                UINT ignored;\n                GetDpiForMonitorInternal(hMonitor, MDT_EFFECTIVE_DPI, &systemDpi, &ignored);\n            }\n\n            if (systemDpi == 0)\n            {\n                HDC hdc = GetDC(NULL);\n                systemDpi = (uint32_t) GetDeviceCaps(hdc, LOGPIXELSX);\n                if (systemDpi == 0) systemDpi = 96;\n                ReleaseDC(NULL, hdc);\n            }\n\n            if (path->targetInfo.rotation == DISPLAYCONFIG_ROTATION_ROTATE90 ||\n                path->targetInfo.rotation == DISPLAYCONFIG_ROTATION_ROTATE270)\n            {\n                uint32_t temp = width;\n                width = height;\n                height = temp;\n            }\n\n            FFDisplayResult* display = ffdsAppendDisplay(ds,\n                width,\n                height,\n                path->targetInfo.refreshRate.Numerator / (double) path->targetInfo.refreshRate.Denominator,\n                systemDpi,\n                preferredMode.width,\n                preferredMode.height,\n                preferredRefreshRate,\n                rotation,\n                &name,\n                path->targetInfo.outputTechnology == DISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER ? FF_DISPLAY_TYPE_UNKNOWN :\n                    path->targetInfo.outputTechnology == DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL ||\n                    path->targetInfo.outputTechnology == DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EMBEDDED ||\n                    path->targetInfo.outputTechnology == DISPLAYCONFIG_OUTPUT_TECHNOLOGY_UDI_EMBEDDED\n                    ? FF_DISPLAY_TYPE_BUILTIN : FF_DISPLAY_TYPE_EXTERNAL,\n                sourceMode->position.x == 0 && sourceMode->position.y == 0,\n                (uintptr_t) hMonitor,\n                physicalWidth,\n                physicalHeight,\n                \"GDI\"\n            );\n\n            if (display)\n            {\n                DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO_2 advColorInfo2 = {\n                    .header = {\n                        .type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO_2,\n                        .size = sizeof(advColorInfo2),\n                        .adapterId = path->targetInfo.adapterId,\n                        .id = path->targetInfo.id,\n                    }\n                };\n                if (DisplayConfigGetDeviceInfo(&advColorInfo2.header) == ERROR_SUCCESS)\n                {\n                    if (advColorInfo2.highDynamicRangeUserEnabled)\n                        display->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED;\n                    else if (advColorInfo2.highDynamicRangeSupported)\n                        display->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED;\n                    else\n                        display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED;\n                    display->bitDepth = (uint8_t) advColorInfo2.bitsPerColorChannel;\n                }\n                else\n                {\n                    DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO advColorInfo = {\n                        .header = {\n                            .type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO,\n                            .size = sizeof(advColorInfo),\n                            .adapterId = path->targetInfo.adapterId,\n                            .id = path->targetInfo.id,\n                        }\n                    };\n                    if (DisplayConfigGetDeviceInfo(&advColorInfo.header) == ERROR_SUCCESS)\n                    {\n                        if (advColorInfo.advancedColorEnabled)\n                            display->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED;\n                        else if (advColorInfo.advancedColorSupported)\n                            display->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED;\n                        else\n                            display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED;\n                        display->bitDepth = (uint8_t) advColorInfo.bitsPerColorChannel;\n                    }\n                    else\n                        display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN;\n                }\n                if (edid.length > 0)\n                    ffEdidGetSerialAndManufactureDate(edid.data, &display->serial, &display->manufactureYear, &display->manufactureWeek);\n                display->drrStatus = path->flags & DISPLAYCONFIG_PATH_BOOST_REFRESH_RATE ? FF_DISPLAY_DRR_STATUS_ENABLED : FF_DISPLAY_DRR_STATUS_DISABLED;\n            }\n        }\n    }\n}\n\nvoid ffConnectDisplayServerImpl(FFDisplayServerResult* ds)\n{\n    if (IsThreadDesktopComposited())\n    {\n        ffStrbufSetStatic(&ds->wmProcessName, \"dwm.exe\");\n        ffStrbufSetStatic(&ds->wmPrettyName, \"Desktop Window Manager\");\n    }\n    else\n    {\n        // `explorer.exe` only provides a subset of WM functions, as well as the taskbar and desktop icons.\n        // While a window itself is drawn by kernel (GDI). Killing `explorer.exe` won't affect how windows are displayed generally.\n        ffStrbufSetStatic(&ds->wmProcessName, \"explorer.exe\");\n        ffStrbufSetStatic(&ds->wmPrettyName, \"Internal\");\n    }\n\n    detectDisplays(ds);\n}\n"
  },
  {
    "path": "src/detection/displayserver/linux/common.c",
    "content": "#include \"displayserver_linux.h\"\n#include \"common/stringUtils.h\"\n\nFFDisplayType ffdsGetDisplayType(const char* name)\n{\n    if(ffStrStartsWith(name, \"eDP-\") || ffStrStartsWith(name, \"LVDS-\"))\n        return FF_DISPLAY_TYPE_BUILTIN;\n    else if(ffStrStartsWith(name, \"HDMI-\") ||\n            ffStrStartsWith(name, \"DP-\") ||\n            ffStrStartsWith(name, \"DisplayPort-\") ||\n            ffStrStartsWith(name, \"DVI-\") ||\n            ffStrStartsWith(name, \"VGA-\"))\n        return FF_DISPLAY_TYPE_EXTERNAL;\n\n    return FF_DISPLAY_TYPE_UNKNOWN;\n}\n"
  },
  {
    "path": "src/detection/displayserver/linux/displayserver_linux.c",
    "content": "#include \"displayserver_linux.h\"\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n\n#ifdef __FreeBSD__\n    #include \"common/settings.h\"\n#endif\n\nstatic void getWMProtocolNameFromEnv(FFDisplayServerResult* result)\n{\n    const char* env = getenv(\"XDG_SESSION_TYPE\");\n    if(env)\n    {\n        if(ffStrEqualsIgnCase(env, \"wayland\"))\n            ffStrbufSetS(&result->wmProtocolName, FF_WM_PROTOCOL_WAYLAND);\n        else if(ffStrEqualsIgnCase(env, \"x11\") || ffStrEqualsIgnCase(env, \"xorg\"))\n            ffStrbufSetS(&result->wmProtocolName, FF_WM_PROTOCOL_X11);\n        else if(ffStrEqualsIgnCase(env, \"tty\"))\n            ffStrbufSetS(&result->wmProtocolName, FF_WM_PROTOCOL_TTY);\n        else\n            ffStrbufSetS(&result->wmProtocolName, env);\n\n        return;\n    }\n\n    if(getenv(\"WAYLAND_DISPLAY\") != NULL || getenv(\"WAYLAND_SOCKET\") != NULL)\n    {\n        ffStrbufSetStatic(&result->wmProtocolName, FF_WM_PROTOCOL_WAYLAND);\n        return;\n    }\n\n    if(getenv(\"DISPLAY\") != NULL) // XWayland also set this\n    {\n        ffStrbufSetStatic(&result->wmProtocolName, FF_WM_PROTOCOL_X11);\n        return;\n    }\n\n    env = getenv(\"TERM\");\n    if(ffStrSet(env) && ffStrEqualsIgnCase(env, \"linux\"))\n    {\n        ffStrbufSetStatic(&result->wmProtocolName, FF_WM_PROTOCOL_TTY);\n        return;\n    }\n}\n\nvoid ffConnectDisplayServerImpl(FFDisplayServerResult* ds)\n{\n    if (instance.config.general.dsForceDrm == FF_DS_FORCE_DRM_TYPE_FALSE)\n    {\n        //We try wayland as our preferred display server, as it supports the most features.\n        //This method can't detect the name of our WM / DE\n        ffdsConnectWayland(ds);\n\n        //Try the x11 libs, from most feature rich to least.\n        //We use the display list to detect if a connection is needed.\n        //They respect wmProtocolName, and only detect display if it is set.\n        if(ds->displays.length == 0)\n            ffdsConnectXcbRandr(ds);\n\n        if(ds->displays.length == 0)\n            ffdsConnectXrandr(ds);\n    }\n\n    //This display detection method is display server independent.\n    //Use it if all connections failed\n    if(ds->displays.length == 0)\n        ffdsConnectDrm(ds);\n\n    #ifdef __FreeBSD__\n    if(ds->displays.length == 0)\n    {\n        FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();\n        if (ffSettingsGetFreeBSDKenv(\"screen.width\", &buf))\n        {\n            uint32_t width = (uint32_t) ffStrbufToUInt(&buf, 0);\n            if (width)\n            {\n                ffStrbufClear(&buf);\n                if (ffSettingsGetFreeBSDKenv(\"screen.height\", &buf))\n                {\n                    uint32_t height = (uint32_t) ffStrbufToUInt(&buf, 0);\n                    ffdsAppendDisplay(ds, width, height, 0, 0, 0, 0, 0, 0, NULL, FF_DISPLAY_TYPE_UNKNOWN, false, 0, 0, 0, \"kenv\");\n                }\n            }\n        }\n    }\n    #endif\n\n    if (ds->wmProtocolName.length == 0)\n        getWMProtocolNameFromEnv(ds);\n\n    if(!ffStrbufEqualS(&ds->wmProtocolName, FF_WM_PROTOCOL_TTY))\n    {\n        //This fills in missing information about WM / DE by using env vars and iterating processes\n        ffdsDetectWMDE(ds);\n    }\n}\n"
  },
  {
    "path": "src/detection/displayserver/linux/displayserver_linux.h",
    "content": "#pragma once\n\n#include \"detection/displayserver/displayserver.h\"\n\nconst char* ffdsConnectWayland(FFDisplayServerResult* result);\n\nconst char* ffdsConnectXcbRandr(FFDisplayServerResult* result);\nconst char* ffdsConnectXrandr(FFDisplayServerResult* result);\nconst char* ffdsConnectDrm(FFDisplayServerResult* result);\n\nvoid ffdsDetectWMDE(FFDisplayServerResult* result);\n\nFFDisplayType ffdsGetDisplayType(const char* drmConnectorName);\n"
  },
  {
    "path": "src/detection/displayserver/linux/drm.c",
    "content": "#include \"displayserver_linux.h\"\n#include \"common/io.h\"\n#include \"common/edidHelper.h\"\n#include \"common/stringUtils.h\"\n\n#ifdef __linux__\n#include <dirent.h>\n\nstatic const char* drmParseSysfs(FFDisplayServerResult* result)\n{\n    const char* drmDirPath = \"/sys/class/drm/\";\n\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(drmDirPath);\n    if(dirp == NULL)\n        return \"opendir(drmDirPath) failed\";\n\n    FF_STRBUF_AUTO_DESTROY drmDir = ffStrbufCreateA(64);\n    ffStrbufAppendS(&drmDir, drmDirPath);\n\n    uint32_t drmDirLength = drmDir.length;\n\n    struct dirent* entry;\n    while((entry = readdir(dirp)) != NULL)\n    {\n        if(entry->d_name[0] == '.')\n            continue;\n\n        ffStrbufAppendS(&drmDir, entry->d_name);\n        uint32_t drmDirWithDnameLength = drmDir.length;\n\n        char buf;\n        ffStrbufAppendS(&drmDir, \"/enabled\");\n        if (ffReadFileData(drmDir.chars, sizeof(buf), &buf) <= 0 || buf != 'e') {\n          /* read failed or enabled != \"enabled\" */\n          ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength);\n          ffStrbufAppendS(&drmDir, \"/status\");\n          buf = 'd';\n          ffReadFileData(drmDir.chars, sizeof(buf), &buf);\n          if (buf != 'c') {\n            /* read failed or status != \"connected\" */\n            ffStrbufSubstrBefore(&drmDir, drmDirLength);\n            continue;\n          }\n        }\n\n        unsigned width = 0, height = 0, physicalWidth = 0, physicalHeight = 0;\n        double refreshRate = 0;\n        FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate();\n\n        ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength);\n        ffStrbufAppendS(&drmDir, \"/edid\");\n\n        const char* plainName = entry->d_name;\n        if (ffStrStartsWith(plainName, \"card\"))\n        {\n            const char* tmp = strchr(plainName + strlen(\"card\"), '-');\n            if (tmp) plainName = tmp + 1;\n        }\n\n        uint8_t edidData[512];\n        ssize_t edidLength = ffReadFileData(drmDir.chars, ARRAY_SIZE(edidData), edidData);\n        if(edidLength <= 0 || edidLength % 128 != 0)\n        {\n            edidLength = 0;\n            ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength);\n            ffStrbufAppendS(&drmDir, \"/modes\");\n\n            char modes[32];\n            if (ffReadFileData(drmDir.chars, ARRAY_SIZE(modes), modes) >= 3)\n            {\n                sscanf(modes, \"%ux%u\", &width, &height);\n                ffStrbufAppendS(&name, plainName);\n            }\n        }\n        else\n        {\n            ffEdidGetName(edidData, &name);\n            ffEdidGetPreferredResolutionAndRefreshRate(edidData, &width, &height, &refreshRate);\n            ffEdidGetPhysicalSize(edidData, &physicalWidth, &physicalHeight);\n        }\n\n        FFDisplayResult* item = ffdsAppendDisplay(\n            result,\n            width, height,\n            refreshRate,\n            0,\n            0, 0,\n            0,\n            0,\n            &name,\n            ffdsGetDisplayType(plainName),\n            false,\n            0,\n            physicalWidth,\n            physicalHeight,\n            \"sysfs-drm\"\n        );\n        if (item && edidLength)\n        {\n            item->hdrStatus = ffEdidGetHdrCompatible(edidData, (uint32_t) edidLength) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED;\n            ffEdidGetSerialAndManufactureDate(edidData, &item->serial, &item->manufactureYear, &item->manufactureWeek);\n        }\n\n        ffStrbufSubstrBefore(&drmDir, drmDirLength);\n    }\n\n    return NULL;\n}\n#endif\n\n#ifdef FF_HAVE_DRM\n\n#include \"common/library.h\"\n\n#include <xf86drm.h>\n#include <xf86drmMode.h>\n#include <fcntl.h>\n\n// https://gitlab.freedesktop.org/mesa/drm/-/blob/main/xf86drmMode.c#L1785\n// It's not supported on Ubuntu 20.04\nstatic inline const char* drmType2Name(uint32_t connector_type)\n{\n    /* Keep the strings in sync with the kernel's drm_connector_enum_list in\n     * drm_connector.c. */\n    switch (connector_type)\n    {\n    case DRM_MODE_CONNECTOR_Unknown:\n        return \"Unknown\";\n    case DRM_MODE_CONNECTOR_VGA:\n        return \"VGA\";\n    case DRM_MODE_CONNECTOR_DVII:\n        return \"DVI-I\";\n    case DRM_MODE_CONNECTOR_DVID:\n        return \"DVI-D\";\n    case DRM_MODE_CONNECTOR_DVIA:\n        return \"DVI-A\";\n    case DRM_MODE_CONNECTOR_Composite:\n        return \"Composite\";\n    case DRM_MODE_CONNECTOR_SVIDEO:\n        return \"SVIDEO\";\n    case DRM_MODE_CONNECTOR_LVDS:\n        return \"LVDS\";\n    case DRM_MODE_CONNECTOR_Component:\n        return \"Component\";\n    case DRM_MODE_CONNECTOR_9PinDIN:\n        return \"DIN\";\n    case DRM_MODE_CONNECTOR_DisplayPort:\n        return \"DP\";\n    case DRM_MODE_CONNECTOR_HDMIA:\n        return \"HDMI-A\";\n    case DRM_MODE_CONNECTOR_HDMIB:\n        return \"HDMI-B\";\n    case DRM_MODE_CONNECTOR_TV:\n        return \"TV\";\n    case DRM_MODE_CONNECTOR_eDP:\n        return \"eDP\";\n    case DRM_MODE_CONNECTOR_VIRTUAL:\n        return \"Virtual\";\n    case DRM_MODE_CONNECTOR_DSI:\n        return \"DSI\";\n    case DRM_MODE_CONNECTOR_DPI:\n        return \"DPI\";\n    case DRM_MODE_CONNECTOR_WRITEBACK:\n        return \"Writeback\";\n    case 19 /*DRM_MODE_CONNECTOR_SPI*/:\n        return \"SPI\";\n    case 20 /*DRM_MODE_CONNECTOR_USB*/:\n        return \"USB\";\n    default:\n        return \"Unsupported\";\n    }\n}\n\nFF_MAYBE_UNUSED static const char* drmGetEdidByConnId(uint32_t connId, uint8_t* edidData, ssize_t* edidLength)\n{\n    const char* drmDirPath = \"/sys/class/drm/\";\n\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(drmDirPath);\n    if(dirp == NULL)\n        return \"opendir(drmDirPath) failed\";\n\n    FF_STRBUF_AUTO_DESTROY drmDir = ffStrbufCreateA(64);\n    ffStrbufAppendS(&drmDir, drmDirPath);\n\n    uint32_t drmDirLength = drmDir.length;\n\n    struct dirent* entry;\n    while((entry = readdir(dirp)) != NULL)\n    {\n        if(entry->d_name[0] == '.')\n            continue;\n\n        ffStrbufAppendS(&drmDir, entry->d_name);\n        uint32_t drmDirWithDnameLength = drmDir.length;\n\n        char connectorId[16] = {};\n\n        ffStrbufAppendS(&drmDir, \"/connector_id\");\n        ffReadFileData(drmDir.chars, ARRAY_SIZE(connectorId), connectorId);\n        if (strtoul(connectorId, NULL, 10) != connId)\n        {\n            ffStrbufSubstrBefore(&drmDir, drmDirLength);\n            continue;\n        }\n\n        ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength);\n        ffStrbufAppendS(&drmDir, \"/edid\");\n        *edidLength = ffReadFileData(drmDir.chars, (uint32_t) *edidLength, edidData);\n        return NULL;\n    }\n\n    return \"Failed to match connector ID\";\n}\n\nstatic const char* drmConnectLibdrm(FFDisplayServerResult* result)\n{\n    FF_LIBRARY_LOAD_MESSAGE(libdrm, \"libdrm\" FF_LIBRARY_EXTENSION, 2)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmGetDevices)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetResources)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetConnectorCurrent)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetCrtc)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetEncoder)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetFB)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetProperty)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetPropertyBlob)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeResources)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeCrtc)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeConnector)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeEncoder)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeFB)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeProperty)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreePropertyBlob)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmFreeDevices)\n\n    drmDevice* devices[64];\n    int nDevices = ffdrmGetDevices(devices, ARRAY_SIZE(devices));\n    if (nDevices <= 0)\n        return \"drmGetDevices() failed\";\n\n    FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate();\n\n    for (int iDev = 0; iDev < nDevices; ++iDev)\n    {\n        drmDevice* dev = devices[iDev];\n\n        if (!(dev->available_nodes & (1 << DRM_NODE_PRIMARY)))\n            continue;\n\n        const char* path = dev->nodes[DRM_NODE_PRIMARY];\n\n        #if __linux__\n        ffStrbufSetF(&name, \"/sys/class/drm/%s/device/power/runtime_status\", strrchr(path, '/') + 1);\n\n        char buffer[8] = \"\";\n        if (ffReadFileData(name.chars, strlen(\"suspend\"), buffer) > 0 && ffStrStartsWith(buffer, \"suspend\"))\n            continue;\n        #endif\n\n        FF_AUTO_CLOSE_FD int primaryFd = open(path, O_RDWR | O_CLOEXEC);\n        if (primaryFd < 0)\n            continue;\n\n        drmModeRes* res = ffdrmModeGetResources(primaryFd);\n        if (!res)\n            continue;\n\n        for (int iConn = 0; iConn < res->count_connectors; ++iConn)\n        {\n            drmModeConnector* conn = ffdrmModeGetConnectorCurrent(primaryFd, res->connectors[iConn]);\n            if (!conn)\n                continue;\n\n            if (conn->connection != DRM_MODE_DISCONNECTED)\n            {\n                drmModeEncoder* encoder = ffdrmModeGetEncoder(primaryFd, conn->encoder_id);\n                uint32_t width = 0, height = 0, refreshRate = 0;\n                uint8_t bitDepth = 0;\n\n                if (encoder)\n                {\n                    drmModeCrtc* crtc = ffdrmModeGetCrtc(primaryFd, encoder->crtc_id);\n                    if (crtc)\n                    {\n                        width = crtc->mode.hdisplay;\n                        height = crtc->mode.vdisplay;\n                        refreshRate = crtc->mode.vrefresh;\n                        if (refreshRate == 0)\n                        {\n                            // There are weird cases that we can't get the refresh rate from the CRTC but from the modes\n                            for (int iMode = 0; iMode < conn->count_modes; ++iMode)\n                            {\n                                drmModeModeInfo* mode = &conn->modes[iMode];\n                                if (mode->clock == crtc->mode.clock && mode->htotal == crtc->mode.htotal)\n                                {\n                                    refreshRate = mode->vrefresh;\n                                    break;\n                                }\n                            }\n                        }\n\n                        drmModeFBPtr fb = ffdrmModeGetFB(primaryFd, crtc->buffer_id);\n                        if (fb)\n                        {\n                            bitDepth = (uint8_t) (fb->depth / 3);\n                            ffdrmModeFreeFB(fb);\n                        }\n\n                        ffdrmModeFreeCrtc(crtc);\n                    }\n\n                    ffdrmModeFreeEncoder(encoder);\n                }\n\n                uint32_t preferredWidth = 0, preferredHeight = 0, preferredRefreshRate = 0;\n\n                for (int iMode = 0; iMode < conn->count_modes; ++iMode)\n                {\n                    drmModeModeInfo* mode = &conn->modes[iMode];\n\n                    if (mode->type & DRM_MODE_TYPE_PREFERRED)\n                    {\n                        preferredWidth = mode->hdisplay;\n                        preferredHeight = mode->vdisplay;\n                        preferredRefreshRate = mode->vrefresh;\n                        break;\n                    }\n                }\n\n                // NVIDIA DRM driver seems incomplete and conn->encoder_id == 0\n                // Assume preferred resolution is used as what we do in drmParseSys\n                if (width == 0 || height == 0)\n                {\n                    width = preferredWidth;\n                    height = preferredHeight;\n                    refreshRate = preferredRefreshRate;\n                }\n\n\n                ffStrbufClear(&name);\n                uint16_t myear = 0, mweak = 0;\n                uint32_t serial = 0;\n                FFDisplayHdrStatus hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN;\n\n                for (int iProp = 0; iProp < conn->count_props; ++iProp)\n                {\n                    drmModePropertyRes *prop = ffdrmModeGetProperty(primaryFd, conn->props[iProp]);\n                    if (!prop)\n                        continue;\n\n                    uint32_t type = prop->flags & (DRM_MODE_PROP_LEGACY_TYPE | DRM_MODE_PROP_EXTENDED_TYPE);\n                    if (type == DRM_MODE_PROP_BLOB && ffStrEquals(prop->name, \"EDID\"))\n                    {\n                        drmModePropertyBlobPtr blob = NULL;\n\n                        if (prop->count_blobs > 0 && prop->blob_ids != NULL)\n                            blob = ffdrmModeGetPropertyBlob(primaryFd, prop->blob_ids[0]);\n                        else\n                            blob = ffdrmModeGetPropertyBlob(primaryFd, (uint32_t) conn->prop_values[iProp]);\n\n                        if (blob)\n                        {\n                            if (blob->length >= 128)\n                            {\n                                ffEdidGetName(blob->data, &name);\n                                hdrStatus = ffEdidGetHdrCompatible(blob->data, blob->length) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED;\n                                ffEdidGetSerialAndManufactureDate(blob->data, &serial, &myear, &mweak);\n                            }\n                            ffdrmModeFreePropertyBlob(blob);\n                        }\n                        break;\n                    }\n                    ffdrmModeFreeProperty(prop);\n                }\n\n                #if __linux__\n                if (name.length == 0)\n                {\n                    uint8_t edidData[512];\n                    ssize_t edidLength = 0;\n                    drmGetEdidByConnId(conn->connector_id, edidData, &edidLength);\n                    if (edidLength > 0 && edidLength % 128 == 0)\n                    {\n                        ffEdidGetName(edidData, &name);\n                        hdrStatus = ffEdidGetHdrCompatible(edidData, (uint32_t) edidLength) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED;\n                        ffEdidGetSerialAndManufactureDate(edidData, &serial, &myear, &mweak);\n                    }\n                }\n                #endif\n\n                if (name.length == 0)\n                {\n                    const char* connectorTypeName = drmType2Name(conn->connector_type);\n                    if (connectorTypeName == NULL)\n                        connectorTypeName = \"Unknown\";\n                    ffStrbufSetF(&name, \"%s-%d\", connectorTypeName, iConn + 1);\n                }\n\n                FFDisplayResult* item = ffdsAppendDisplay(result,\n                    width, height,\n                    refreshRate,\n                    0,\n                    preferredWidth, preferredHeight,\n                    preferredRefreshRate,\n                    0,\n                    &name,\n                    conn->connector_type == DRM_MODE_CONNECTOR_eDP || conn->connector_type == DRM_MODE_CONNECTOR_LVDS\n                        ? FF_DISPLAY_TYPE_BUILTIN\n                        : conn->connector_type == DRM_MODE_CONNECTOR_HDMIA || conn->connector_type == DRM_MODE_CONNECTOR_HDMIB || conn->connector_type == DRM_MODE_CONNECTOR_DisplayPort\n                            ? FF_DISPLAY_TYPE_EXTERNAL : FF_DISPLAY_TYPE_UNKNOWN,\n                    false,\n                    conn->connector_id,\n                    conn->mmWidth,\n                    conn->mmHeight,\n                    \"libdrm\"\n                );\n\n                if (item)\n                {\n                    item->hdrStatus = hdrStatus;\n                    item->serial = serial;\n                    item->manufactureYear = myear;\n                    item->manufactureWeek = mweak;\n                    item->bitDepth = bitDepth;\n                }\n            }\n\n            ffdrmModeFreeConnector(conn);\n        }\n\n        ffdrmModeFreeResources(res);\n    }\n\n    ffdrmFreeDevices(devices, nDevices);\n\n    return NULL;\n}\n\n#endif\n\nconst char* ffdsConnectDrm(FF_MAYBE_UNUSED FFDisplayServerResult* result)\n{\n    #ifdef FF_HAVE_DRM\n    if (instance.config.general.dsForceDrm != FF_DS_FORCE_DRM_TYPE_SYSFS_ONLY)\n    {\n        if (drmConnectLibdrm(result) == NULL)\n            return NULL;\n    }\n    #endif\n\n    #ifdef __linux__\n    return drmParseSysfs(result);\n    #endif\n\n    return \"fastfetch was compiled without drm support\";\n}\n"
  },
  {
    "path": "src/detection/displayserver/linux/wayland/global-output.c",
    "content": "#ifdef FF_HAVE_WAYLAND\n\n#include \"wayland.h\"\n#include \"common/stringUtils.h\"\n#include \"xdg-output-unstable-v1-client-protocol.h\"\n\nstatic void waylandOutputModeListener(void* data, FF_MAYBE_UNUSED struct wl_output* output, uint32_t flags, int32_t width, int32_t height, int32_t refreshRate)\n{\n    WaylandDisplay* display = data;\n\n    if (flags & WL_OUTPUT_MODE_CURRENT)\n    {\n        display->width = width;\n        display->height = height;\n        display->refreshRate = refreshRate;\n    }\n    if (flags & WL_OUTPUT_MODE_PREFERRED)\n    {\n        display->preferredWidth = width;\n        display->preferredHeight = height;\n        display->preferredRefreshRate = refreshRate;\n    }\n}\n\nstatic void waylandOutputScaleListener(void* data, FF_MAYBE_UNUSED struct wl_output* output, int32_t scale)\n{\n    WaylandDisplay* display = data;\n    display->dpi = 96 * (uint32_t) scale;\n}\n\nstatic void waylandOutputGeometryListener(void *data,\n    FF_MAYBE_UNUSED struct wl_output *output,\n    FF_MAYBE_UNUSED int32_t x,\n    FF_MAYBE_UNUSED int32_t y,\n    int32_t physical_width,\n    int32_t physical_height,\n    FF_MAYBE_UNUSED int32_t subpixel,\n    FF_MAYBE_UNUSED const char *make,\n    FF_MAYBE_UNUSED const char *model,\n    int32_t transform)\n{\n    WaylandDisplay* display = data;\n    display->physicalWidth = physical_width;\n    display->physicalHeight = physical_height;\n    display->transform = (enum wl_output_transform) transform;\n}\n\nstatic void handleXdgLogicalSize(void *data, FF_MAYBE_UNUSED struct zxdg_output_v1 *_, int32_t width, FF_MAYBE_UNUSED int32_t height)\n{\n    WaylandDisplay* display = data;\n    // Seems the values are only useful when ractional scale is enabled\n    if (width < display->width)\n    {\n        display->dpi = (uint32_t) (display->width * 96 / width);\n    }\n}\n\n// Dirty hack for #477\n// The order of these callbacks MUST follow `struct wl_output_listener`\nstatic void* outputListener[] = {\n    waylandOutputGeometryListener, // geometry\n    waylandOutputModeListener, // mode\n    stubListener, // done\n    waylandOutputScaleListener, // scale\n    ffWaylandOutputNameListener, // name\n    ffWaylandOutputDescriptionListener, // description\n};\nstatic_assert(\n    sizeof(outputListener) >= sizeof(struct wl_output_listener),\n    \"sizeof(outputListener) is too small. Please report it to fastfetch github issue\"\n);\n\nstatic struct zxdg_output_v1_listener zxdgOutputListener = {\n    .logical_position = (void*) stubListener,\n    .logical_size = handleXdgLogicalSize,\n    .done = (void*) stubListener,\n    .name = (void*) ffWaylandOutputNameListener,\n    .description = (void*) ffWaylandOutputDescriptionListener,\n};\n\nconst char* ffWaylandHandleGlobalOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version)\n{\n    struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, wldata->ffwl_output_interface, version, name, wldata->ffwl_output_interface->name, version, NULL);\n    if(output == NULL)\n        return \"Failed to create wl_output\";\n\n    WaylandDisplay display = {\n        .parent = wldata,\n        .transform = WL_OUTPUT_TRANSFORM_NORMAL,\n        .type = FF_DISPLAY_TYPE_UNKNOWN,\n        .name = ffStrbufCreate(),\n        .description = ffStrbufCreate(),\n        .edidName = ffStrbufCreate(),\n    };\n\n    if (wldata->ffwl_proxy_add_listener(output, (void(**)(void)) &outputListener, &display) < 0)\n    {\n        wldata->ffwl_proxy_destroy(output);\n        return \"Failed to add listener to wl_output\";\n    }\n    if (wldata->ffwl_display_roundtrip(wldata->display) < 0)\n    {\n        wldata->ffwl_proxy_destroy(output);\n        return \"Failed to roundtrip wl_output\";\n    }\n\n    if (wldata->zxdgOutputManager)\n    {\n        struct wl_proxy* zxdgOutput = wldata->ffwl_proxy_marshal_constructor_versioned(wldata->zxdgOutputManager, ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT, &zxdg_output_v1_interface, version, NULL, output);\n\n        if (zxdgOutput)\n        {\n            wldata->ffwl_proxy_add_listener(zxdgOutput, (void(**)(void)) &zxdgOutputListener, &display);\n            wldata->ffwl_display_roundtrip(wldata->display);\n            wldata->ffwl_proxy_destroy(zxdgOutput);\n        }\n    }\n\n    wldata->ffwl_proxy_destroy(output);\n\n    if(display.width <= 0 || display.height <= 0)\n        return \"Failed to get display information from wl_output\";\n\n    uint32_t rotation = ffWaylandHandleRotation(&display);\n\n    FFDisplayResult* item = ffdsAppendDisplay(wldata->result,\n        (uint32_t) display.width,\n        (uint32_t) display.height,\n        display.refreshRate / 1000.0,\n        display.dpi,\n        (uint32_t) display.preferredWidth,\n        (uint32_t) display.preferredHeight,\n        display.preferredRefreshRate / 1000.0,\n        rotation,\n        display.edidName.length\n            ? &display.edidName\n            // Try ignoring `eDP-1-unknown`, where `unknown` is localized\n            : display.description.length && !ffStrbufContain(&display.description, &display.name)\n                ? &display.description\n                : &display.name,\n        display.type,\n        false,\n        display.id,\n        (uint32_t) display.physicalWidth,\n        (uint32_t) display.physicalHeight,\n        \"wayland-global\"\n    );\n    if (item)\n    {\n        if (display.hdrSupported)\n            item->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED;\n        else if (display.hdrInfoAvailable)\n            item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED;\n        else\n            item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN;\n\n        item->manufactureYear = display.myear;\n        item->manufactureWeek = display.mweek;\n        item->serial = display.serial;\n    }\n\n    ffStrbufDestroy(&display.description);\n    ffStrbufDestroy(&display.name);\n    ffStrbufDestroy(&display.edidName);\n\n    return NULL;\n}\n\nconst char* ffWaylandHandleZxdgOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version)\n{\n    struct wl_proxy* manager = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &zxdg_output_manager_v1_interface, version, name, zxdg_output_manager_v1_interface.name, version, NULL);\n    if(manager == NULL)\n        return \"Failed to create zxdg_output_manager_v1\";\n\n    wldata->zxdgOutputManager = manager;\n\n    return NULL;\n}\n\n#endif\n"
  },
  {
    "path": "src/detection/displayserver/linux/wayland/kde-output-device-v2-client-protocol.h",
    "content": "/* Generated by wayland-scanner 1.24.0 */\n\n#ifndef KDE_OUTPUT_DEVICE_V2_CLIENT_PROTOCOL_H\n#define KDE_OUTPUT_DEVICE_V2_CLIENT_PROTOCOL_H\n\n#include <stdint.h>\n#include <stddef.h>\n#include <wayland-client.h>\n\n#ifdef  __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * @page page_kde_output_device_v2 The kde_output_device_v2 protocol\n * @section page_ifaces_kde_output_device_v2 Interfaces\n * - @subpage page_iface_kde_output_device_registry_v2 - output devices\n * - @subpage page_iface_kde_output_device_v2 - output configuration representation\n * - @subpage page_iface_kde_output_device_mode_v2 - output mode\n * @section page_copyright_kde_output_device_v2 Copyright\n * <pre>\n *\n * SPDX-FileCopyrightText: 2008-2011 Kristian Høgsberg\n * SPDX-FileCopyrightText: 2010-2011 Intel Corporation\n * SPDX-FileCopyrightText: 2012-2013 Collabora, Ltd.\n * SPDX-FileCopyrightText: 2015 Sebastian Kügler <sebas@kde.org>\n * SPDX-FileCopyrightText: 2021 Méven Car <meven.car@enioka.com>\n *\n * SPDX-License-Identifier: MIT-CMU\n * </pre>\n */\nstruct kde_output_device_mode_v2;\nstruct kde_output_device_registry_v2;\nstruct kde_output_device_v2;\n\n#ifndef KDE_OUTPUT_DEVICE_REGISTRY_V2_INTERFACE\n#define KDE_OUTPUT_DEVICE_REGISTRY_V2_INTERFACE\n/**\n * @page page_iface_kde_output_device_registry_v2 kde_output_device_registry_v2\n * @section page_iface_kde_output_device_registry_v2_desc Description\n *\n * This interface can be used to list output devices.\n *\n * If this global is bound with a version less than 21, the unsupported_version\n * protocol error will be posted.\n * @section page_iface_kde_output_device_registry_v2_api API\n * See @ref iface_kde_output_device_registry_v2.\n */\n/**\n * @defgroup iface_kde_output_device_registry_v2 The kde_output_device_registry_v2 interface\n *\n * This interface can be used to list output devices.\n *\n * If this global is bound with a version less than 21, the unsupported_version\n * protocol error will be posted.\n */\nextern const struct wl_interface kde_output_device_registry_v2_interface;\n#endif\n#ifndef KDE_OUTPUT_DEVICE_V2_INTERFACE\n#define KDE_OUTPUT_DEVICE_V2_INTERFACE\n/**\n * @page page_iface_kde_output_device_v2 kde_output_device_v2\n * @section page_iface_kde_output_device_v2_desc Description\n *\n * An output device describes a display device available to the compositor.\n * output_device is similar to wl_output, but focuses on output\n * configuration management.\n *\n * A client can query all global output_device objects to enlist all\n * available display devices, even those that may currently not be\n * represented by the compositor as a wl_output.\n *\n * The client sends configuration changes to the server through the\n * outputconfiguration interface, and the server applies the configuration\n * changes to the hardware and signals changes to the output devices\n * accordingly.\n *\n * This object is published as global during start up for every available\n * display devices, or when one later becomes available, for example by\n * being hotplugged via a physical connector.\n *\n * Warning! The protocol described in this file is a desktop environment\n * implementation detail. Regular clients must not use this protocol.\n * Backward incompatible changes may be added without bumping the major\n * version of the extension.\n * @section page_iface_kde_output_device_v2_api API\n * See @ref iface_kde_output_device_v2.\n */\n/**\n * @defgroup iface_kde_output_device_v2 The kde_output_device_v2 interface\n *\n * An output device describes a display device available to the compositor.\n * output_device is similar to wl_output, but focuses on output\n * configuration management.\n *\n * A client can query all global output_device objects to enlist all\n * available display devices, even those that may currently not be\n * represented by the compositor as a wl_output.\n *\n * The client sends configuration changes to the server through the\n * outputconfiguration interface, and the server applies the configuration\n * changes to the hardware and signals changes to the output devices\n * accordingly.\n *\n * This object is published as global during start up for every available\n * display devices, or when one later becomes available, for example by\n * being hotplugged via a physical connector.\n *\n * Warning! The protocol described in this file is a desktop environment\n * implementation detail. Regular clients must not use this protocol.\n * Backward incompatible changes may be added without bumping the major\n * version of the extension.\n */\nextern const struct wl_interface kde_output_device_v2_interface;\n#endif\n#ifndef KDE_OUTPUT_DEVICE_MODE_V2_INTERFACE\n#define KDE_OUTPUT_DEVICE_MODE_V2_INTERFACE\n/**\n * @page page_iface_kde_output_device_mode_v2 kde_output_device_mode_v2\n * @section page_iface_kde_output_device_mode_v2_desc Description\n *\n * This object describes an output mode.\n *\n * Some heads don't support output modes, in which case modes won't be\n * advertised.\n *\n * Properties sent via this interface are applied atomically via the\n * kde_output_device.done event. No guarantees are made regarding the order\n * in which properties are sent.\n * @section page_iface_kde_output_device_mode_v2_api API\n * See @ref iface_kde_output_device_mode_v2.\n */\n/**\n * @defgroup iface_kde_output_device_mode_v2 The kde_output_device_mode_v2 interface\n *\n * This object describes an output mode.\n *\n * Some heads don't support output modes, in which case modes won't be\n * advertised.\n *\n * Properties sent via this interface are applied atomically via the\n * kde_output_device.done event. No guarantees are made regarding the order\n * in which properties are sent.\n */\nextern const struct wl_interface kde_output_device_mode_v2_interface;\n#endif\n\n#ifndef KDE_OUTPUT_DEVICE_REGISTRY_V2_ERROR_ENUM\n#define KDE_OUTPUT_DEVICE_REGISTRY_V2_ERROR_ENUM\n/**\n * @ingroup iface_kde_output_device_registry_v2\n * kde_output_device_registry_v2 error values\n *\n * These errors can be emitted in response to some requests.\n */\nenum kde_output_device_registry_v2_error {\n\t/**\n\t * the registry was bound with an unsupported version\n\t */\n\tKDE_OUTPUT_DEVICE_REGISTRY_V2_ERROR_UNSUPPORTED_VERSION = 0,\n};\n#endif /* KDE_OUTPUT_DEVICE_REGISTRY_V2_ERROR_ENUM */\n\n/**\n * @ingroup iface_kde_output_device_registry_v2\n * @struct kde_output_device_registry_v2_listener\n */\nstruct kde_output_device_registry_v2_listener {\n\t/**\n\t * no new output announcements\n\t *\n\t * This event is sent in response to the stop request. The\n\t * compositor will immediately destroy the object after sending\n\t * this event.\n\t * @since 21\n\t */\n\tvoid (*finished)(void *data,\n\t\t\t struct kde_output_device_registry_v2 *kde_output_device_registry_v2);\n\t/**\n\t * new available output\n\t *\n\t * This event is sent when a new output is connected or after\n\t * binding this global to list all available outputs.\n\t * @since 21\n\t */\n\tvoid (*output)(void *data,\n\t\t       struct kde_output_device_registry_v2 *kde_output_device_registry_v2,\n\t\t       struct kde_output_device_v2 *output);\n};\n\n/**\n * @ingroup iface_kde_output_device_registry_v2\n */\nstatic inline int\nkde_output_device_registry_v2_add_listener(struct kde_output_device_registry_v2 *kde_output_device_registry_v2,\n\t\t\t\t\t   const struct kde_output_device_registry_v2_listener *listener, void *data)\n{\n\treturn wl_proxy_add_listener((struct wl_proxy *) kde_output_device_registry_v2,\n\t\t\t\t     (void (**)(void)) listener, data);\n}\n\n#define KDE_OUTPUT_DEVICE_REGISTRY_V2_STOP 0\n\n/**\n * @ingroup iface_kde_output_device_registry_v2\n */\n#define KDE_OUTPUT_DEVICE_REGISTRY_V2_FINISHED_SINCE_VERSION 21\n/**\n * @ingroup iface_kde_output_device_registry_v2\n */\n#define KDE_OUTPUT_DEVICE_REGISTRY_V2_OUTPUT_SINCE_VERSION 21\n\n/**\n * @ingroup iface_kde_output_device_registry_v2\n */\n#define KDE_OUTPUT_DEVICE_REGISTRY_V2_STOP_SINCE_VERSION 21\n\n/** @ingroup iface_kde_output_device_registry_v2 */\nstatic inline void\nkde_output_device_registry_v2_set_user_data(struct kde_output_device_registry_v2 *kde_output_device_registry_v2, void *user_data)\n{\n\twl_proxy_set_user_data((struct wl_proxy *) kde_output_device_registry_v2, user_data);\n}\n\n/** @ingroup iface_kde_output_device_registry_v2 */\nstatic inline void *\nkde_output_device_registry_v2_get_user_data(struct kde_output_device_registry_v2 *kde_output_device_registry_v2)\n{\n\treturn wl_proxy_get_user_data((struct wl_proxy *) kde_output_device_registry_v2);\n}\n\nstatic inline uint32_t\nkde_output_device_registry_v2_get_version(struct kde_output_device_registry_v2 *kde_output_device_registry_v2)\n{\n\treturn wl_proxy_get_version((struct wl_proxy *) kde_output_device_registry_v2);\n}\n\n/** @ingroup iface_kde_output_device_registry_v2 */\nstatic inline void\nkde_output_device_registry_v2_destroy(struct kde_output_device_registry_v2 *kde_output_device_registry_v2)\n{\n\twl_proxy_destroy((struct wl_proxy *) kde_output_device_registry_v2);\n}\n\n/**\n * @ingroup iface_kde_output_device_registry_v2\n *\n * This request indicates that the client no longer wants to receive new\n * output announcements. The compositor will send the\n * kde_output_device_registry_v2.finished event in response to this request.\n * The compositor may still send new output announcements after calling this\n * request until the kde_output_device_registry_v2.finished event is sent.\n */\nstatic inline void\nkde_output_device_registry_v2_stop(struct kde_output_device_registry_v2 *kde_output_device_registry_v2)\n{\n\twl_proxy_marshal_flags((struct wl_proxy *) kde_output_device_registry_v2,\n\t\t\t KDE_OUTPUT_DEVICE_REGISTRY_V2_STOP, NULL, wl_proxy_get_version((struct wl_proxy *) kde_output_device_registry_v2), 0);\n}\n\n#ifndef KDE_OUTPUT_DEVICE_V2_SUBPIXEL_ENUM\n#define KDE_OUTPUT_DEVICE_V2_SUBPIXEL_ENUM\n/**\n * @ingroup iface_kde_output_device_v2\n * subpixel geometry information\n *\n * This enumeration describes how the physical pixels on an output are\n * laid out.\n */\nenum kde_output_device_v2_subpixel {\n\tKDE_OUTPUT_DEVICE_V2_SUBPIXEL_UNKNOWN = 0,\n\tKDE_OUTPUT_DEVICE_V2_SUBPIXEL_NONE = 1,\n\tKDE_OUTPUT_DEVICE_V2_SUBPIXEL_HORIZONTAL_RGB = 2,\n\tKDE_OUTPUT_DEVICE_V2_SUBPIXEL_HORIZONTAL_BGR = 3,\n\tKDE_OUTPUT_DEVICE_V2_SUBPIXEL_VERTICAL_RGB = 4,\n\tKDE_OUTPUT_DEVICE_V2_SUBPIXEL_VERTICAL_BGR = 5,\n};\n#endif /* KDE_OUTPUT_DEVICE_V2_SUBPIXEL_ENUM */\n\n#ifndef KDE_OUTPUT_DEVICE_V2_TRANSFORM_ENUM\n#define KDE_OUTPUT_DEVICE_V2_TRANSFORM_ENUM\n/**\n * @ingroup iface_kde_output_device_v2\n * transform from framebuffer to output\n *\n * This describes the transform, that a compositor will apply to a\n * surface to compensate for the rotation or mirroring of an\n * output device.\n *\n * The flipped values correspond to an initial flip around a\n * vertical axis followed by rotation.\n *\n * The purpose is mainly to allow clients to render accordingly and\n * tell the compositor, so that for fullscreen surfaces, the\n * compositor is still able to scan out directly client surfaces.\n */\nenum kde_output_device_v2_transform {\n\tKDE_OUTPUT_DEVICE_V2_TRANSFORM_NORMAL = 0,\n\tKDE_OUTPUT_DEVICE_V2_TRANSFORM_90 = 1,\n\tKDE_OUTPUT_DEVICE_V2_TRANSFORM_180 = 2,\n\tKDE_OUTPUT_DEVICE_V2_TRANSFORM_270 = 3,\n\tKDE_OUTPUT_DEVICE_V2_TRANSFORM_FLIPPED = 4,\n\tKDE_OUTPUT_DEVICE_V2_TRANSFORM_FLIPPED_90 = 5,\n\tKDE_OUTPUT_DEVICE_V2_TRANSFORM_FLIPPED_180 = 6,\n\tKDE_OUTPUT_DEVICE_V2_TRANSFORM_FLIPPED_270 = 7,\n};\n#endif /* KDE_OUTPUT_DEVICE_V2_TRANSFORM_ENUM */\n\n#ifndef KDE_OUTPUT_DEVICE_V2_CAPABILITY_ENUM\n#define KDE_OUTPUT_DEVICE_V2_CAPABILITY_ENUM\n/**\n * @ingroup iface_kde_output_device_v2\n * describes capabilities of the outputdevice\n *\n * Describes what capabilities this device has.\n */\nenum kde_output_device_v2_capability {\n\t/**\n\t * if this output_device can use overscan\n\t */\n\tKDE_OUTPUT_DEVICE_V2_CAPABILITY_OVERSCAN = 0x1,\n\t/**\n\t * if this outputdevice supports variable refresh rate\n\t */\n\tKDE_OUTPUT_DEVICE_V2_CAPABILITY_VRR = 0x2,\n\t/**\n\t * if setting the rgb range is possible\n\t */\n\tKDE_OUTPUT_DEVICE_V2_CAPABILITY_RGB_RANGE = 0x4,\n\t/**\n\t * if this outputdevice supports high dynamic range\n\t * @since 3\n\t */\n\tKDE_OUTPUT_DEVICE_V2_CAPABILITY_HIGH_DYNAMIC_RANGE = 0x8,\n\t/**\n\t * if this outputdevice supports a wide color gamut\n\t * @since 3\n\t */\n\tKDE_OUTPUT_DEVICE_V2_CAPABILITY_WIDE_COLOR_GAMUT = 0x10,\n\t/**\n\t * if this outputdevice supports autorotation\n\t * @since 4\n\t */\n\tKDE_OUTPUT_DEVICE_V2_CAPABILITY_AUTO_ROTATE = 0x20,\n\t/**\n\t * if this outputdevice supports icc profiles\n\t * @since 5\n\t */\n\tKDE_OUTPUT_DEVICE_V2_CAPABILITY_ICC_PROFILE = 0x40,\n\t/**\n\t * if this outputdevice supports the brightness setting\n\t * @since 9\n\t */\n\tKDE_OUTPUT_DEVICE_V2_CAPABILITY_BRIGHTNESS = 0x80,\n\t/**\n\t * if this outputdevice supports the built-in color profile\n\t * @since 12\n\t */\n\tKDE_OUTPUT_DEVICE_V2_CAPABILITY_BUILT_IN_COLOR = 0x100,\n\t/**\n\t * if this outputdevice supports DDC/CI\n\t * @since 14\n\t */\n\tKDE_OUTPUT_DEVICE_V2_CAPABILITY_DDC_CI = 0x200,\n\t/**\n\t * if this outputdevice supports setting max bpc\n\t * @since 15\n\t */\n\tKDE_OUTPUT_DEVICE_V2_CAPABILITY_MAX_BITS_PER_COLOR = 0x400,\n\t/**\n\t * if this outputdevice supports EDR\n\t * @since 16\n\t */\n\tKDE_OUTPUT_DEVICE_V2_CAPABILITY_EDR = 0x800,\n\t/**\n\t * if this outputdevice supports the sharpness setting\n\t * @since 17\n\t */\n\tKDE_OUTPUT_DEVICE_V2_CAPABILITY_SHARPNESS = 0x1000,\n\t/**\n\t * if this outputdevice supports custom modes\n\t * @since 18\n\t */\n\tKDE_OUTPUT_DEVICE_V2_CAPABILITY_CUSTOM_MODES = 0x2000,\n\t/**\n\t * @since 19\n\t */\n\tKDE_OUTPUT_DEVICE_V2_CAPABILITY_AUTO_BRIGHTNESS = 0x4000,\n};\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_CAPABILITY_HIGH_DYNAMIC_RANGE_SINCE_VERSION 3\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_CAPABILITY_WIDE_COLOR_GAMUT_SINCE_VERSION 3\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_CAPABILITY_AUTO_ROTATE_SINCE_VERSION 4\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_CAPABILITY_ICC_PROFILE_SINCE_VERSION 5\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_CAPABILITY_BRIGHTNESS_SINCE_VERSION 9\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_CAPABILITY_BUILT_IN_COLOR_SINCE_VERSION 12\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_CAPABILITY_DDC_CI_SINCE_VERSION 14\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_CAPABILITY_MAX_BITS_PER_COLOR_SINCE_VERSION 15\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_CAPABILITY_EDR_SINCE_VERSION 16\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_CAPABILITY_SHARPNESS_SINCE_VERSION 17\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_CAPABILITY_CUSTOM_MODES_SINCE_VERSION 18\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_CAPABILITY_AUTO_BRIGHTNESS_SINCE_VERSION 19\n#endif /* KDE_OUTPUT_DEVICE_V2_CAPABILITY_ENUM */\n\n#ifndef KDE_OUTPUT_DEVICE_V2_VRR_POLICY_ENUM\n#define KDE_OUTPUT_DEVICE_V2_VRR_POLICY_ENUM\n/**\n * @ingroup iface_kde_output_device_v2\n * describes vrr policy\n *\n * Describes when the compositor may employ variable refresh rate\n */\nenum kde_output_device_v2_vrr_policy {\n\tKDE_OUTPUT_DEVICE_V2_VRR_POLICY_NEVER = 0,\n\tKDE_OUTPUT_DEVICE_V2_VRR_POLICY_ALWAYS = 1,\n\tKDE_OUTPUT_DEVICE_V2_VRR_POLICY_AUTOMATIC = 2,\n};\n#endif /* KDE_OUTPUT_DEVICE_V2_VRR_POLICY_ENUM */\n\n#ifndef KDE_OUTPUT_DEVICE_V2_RGB_RANGE_ENUM\n#define KDE_OUTPUT_DEVICE_V2_RGB_RANGE_ENUM\n/**\n * @ingroup iface_kde_output_device_v2\n * describes RGB range policy\n *\n * Whether full or limited color range should be used\n */\nenum kde_output_device_v2_rgb_range {\n\tKDE_OUTPUT_DEVICE_V2_RGB_RANGE_AUTOMATIC = 0,\n\tKDE_OUTPUT_DEVICE_V2_RGB_RANGE_FULL = 1,\n\tKDE_OUTPUT_DEVICE_V2_RGB_RANGE_LIMITED = 2,\n};\n#endif /* KDE_OUTPUT_DEVICE_V2_RGB_RANGE_ENUM */\n\n#ifndef KDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_ENUM\n#define KDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_ENUM\n/**\n * @ingroup iface_kde_output_device_v2\n * describes when auto rotate should be used\n */\nenum kde_output_device_v2_auto_rotate_policy {\n\tKDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_NEVER = 0,\n\tKDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_IN_TABLET_MODE = 1,\n\tKDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_ALWAYS = 2,\n};\n#endif /* KDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_ENUM */\n\n#ifndef KDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_ENUM\n#define KDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_ENUM\n/**\n * @ingroup iface_kde_output_device_v2\n * which source the compositor should use for the color profile on an output\n */\nenum kde_output_device_v2_color_profile_source {\n\tKDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_SRGB = 0,\n\tKDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_ICC = 1,\n\tKDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_EDID = 2,\n};\n#endif /* KDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_ENUM */\n\n#ifndef KDE_OUTPUT_DEVICE_V2_COLOR_POWER_TRADEOFF_ENUM\n#define KDE_OUTPUT_DEVICE_V2_COLOR_POWER_TRADEOFF_ENUM\n/**\n * @ingroup iface_kde_output_device_v2\n * tradeoff between power and accuracy\n *\n * The compositor can do a lot of things that trade between\n * performance, power and color accuracy. This setting describes\n * a high level preference from the user about in which direction\n * that tradeoff should be made.\n */\nenum kde_output_device_v2_color_power_tradeoff {\n\t/**\n\t * prefer efficiency and performance\n\t */\n\tKDE_OUTPUT_DEVICE_V2_COLOR_POWER_TRADEOFF_EFFICIENCY = 0,\n\t/**\n\t * prefer accuracy\n\t */\n\tKDE_OUTPUT_DEVICE_V2_COLOR_POWER_TRADEOFF_ACCURACY = 1,\n};\n#endif /* KDE_OUTPUT_DEVICE_V2_COLOR_POWER_TRADEOFF_ENUM */\n\n#ifndef KDE_OUTPUT_DEVICE_V2_EDR_POLICY_ENUM\n#define KDE_OUTPUT_DEVICE_V2_EDR_POLICY_ENUM\n/**\n * @ingroup iface_kde_output_device_v2\n * when the compositor may make use of EDR\n */\nenum kde_output_device_v2_edr_policy {\n\tKDE_OUTPUT_DEVICE_V2_EDR_POLICY_NEVER = 0,\n\tKDE_OUTPUT_DEVICE_V2_EDR_POLICY_ALWAYS = 1,\n};\n#endif /* KDE_OUTPUT_DEVICE_V2_EDR_POLICY_ENUM */\n\n/**\n * @ingroup iface_kde_output_device_v2\n * @struct kde_output_device_v2_listener\n */\nstruct kde_output_device_v2_listener {\n\t/**\n\t * geometric properties of the output\n\t *\n\t * The geometry event describes geometric properties of the\n\t * output. The event is sent when binding to the output object and\n\t * whenever any of the properties change.\n\t * @param x x position within the global compositor space\n\t * @param y y position within the global compositor space\n\t * @param physical_width width in millimeters of the output\n\t * @param physical_height height in millimeters of the output\n\t * @param subpixel subpixel orientation of the output\n\t * @param make textual description of the manufacturer\n\t * @param model textual description of the model\n\t * @param transform transform that maps framebuffer to output\n\t */\n\tvoid (*geometry)(void *data,\n\t\t\t struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t int32_t x,\n\t\t\t int32_t y,\n\t\t\t int32_t physical_width,\n\t\t\t int32_t physical_height,\n\t\t\t int32_t subpixel,\n\t\t\t const char *make,\n\t\t\t const char *model,\n\t\t\t int32_t transform);\n\t/**\n\t * current mode\n\t *\n\t * This event describes the mode currently in use for this head.\n\t * It is only sent if the output is enabled.\n\t */\n\tvoid (*current_mode)(void *data,\n\t\t\t     struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t     struct kde_output_device_mode_v2 *mode);\n\t/**\n\t * advertise available output modes and current one\n\t *\n\t * The mode event describes an available mode for the output.\n\t *\n\t * When the client binds to the output_device object, the server\n\t * sends this event once for every available mode the output_device\n\t * can be operated by.\n\t *\n\t * There will always be at least one event sent out on initial\n\t * binding, which represents the current mode.\n\t *\n\t * Later if an output changes, its mode event is sent again for the\n\t * eventual added modes and lastly the current mode. In other\n\t * words, the current mode is always represented by the latest\n\t * event sent with the current flag set.\n\t *\n\t * The size of a mode is given in physical hardware units of the\n\t * output device. This is not necessarily the same as the output\n\t * size in the global compositor space. For instance, the output\n\t * may be scaled, as described in kde_output_device_v2.scale, or\n\t * transformed, as described in kde_output_device_v2.transform.\n\t */\n\tvoid (*mode)(void *data,\n\t\t     struct kde_output_device_v2 *kde_output_device_v2,\n\t\t     struct kde_output_device_mode_v2 *mode);\n\t/**\n\t * sent all information about output\n\t *\n\t * This event is sent after all other properties have been sent\n\t * on binding to the output object as well as after any other\n\t * output property change have been applied later on. This allows\n\t * to see changes to the output properties as atomic, even if\n\t * multiple events successively announce them.\n\t */\n\tvoid (*done)(void *data,\n\t\t     struct kde_output_device_v2 *kde_output_device_v2);\n\t/**\n\t * output scaling properties\n\t *\n\t * This event contains scaling geometry information that is not\n\t * in the geometry event. It may be sent after binding the output\n\t * object or if the output scale changes later. If it is not sent,\n\t * the client should assume a scale of 1.\n\t *\n\t * A scale larger than 1 means that the compositor will\n\t * automatically scale surface buffers by this amount when\n\t * rendering. This is used for high resolution displays where\n\t * applications rendering at the native resolution would be too\n\t * small to be legible.\n\t *\n\t * It is intended that scaling aware clients track the current\n\t * output of a surface, and if it is on a scaled output it should\n\t * use wl_surface.set_buffer_scale with the scale of the output.\n\t * That way the compositor can avoid scaling the surface, and the\n\t * client can supply a higher detail image.\n\t * @param factor scaling factor of output\n\t */\n\tvoid (*scale)(void *data,\n\t\t      struct kde_output_device_v2 *kde_output_device_v2,\n\t\t      wl_fixed_t factor);\n\t/**\n\t * advertise EDID data for the output\n\t *\n\t * The edid event encapsulates the EDID data for the\n\t * outputdevice.\n\t *\n\t * The event is sent when binding to the output object. The EDID\n\t * data may be empty, in which case this event is sent anyway. If\n\t * the EDID information is empty, you can fall back to the name et\n\t * al. properties of the outputdevice.\n\t * @param raw base64-encoded EDID string\n\t */\n\tvoid (*edid)(void *data,\n\t\t     struct kde_output_device_v2 *kde_output_device_v2,\n\t\t     const char *raw);\n\t/**\n\t * output is enabled or disabled\n\t *\n\t * The enabled event notifies whether this output is currently\n\t * enabled and used for displaying content by the server. The event\n\t * is sent when binding to the output object and whenever later on\n\t * an output changes its state by becoming enabled or disabled.\n\t * @param enabled output enabled state\n\t */\n\tvoid (*enabled)(void *data,\n\t\t\tstruct kde_output_device_v2 *kde_output_device_v2,\n\t\t\tint32_t enabled);\n\t/**\n\t * A unique id for this outputdevice\n\t *\n\t * The uuid can be used to identify the output. It's controlled\n\t * by the server entirely. The server should make sure the uuid is\n\t * persistent across restarts. An empty uuid is considered invalid.\n\t * @param uuid output devices ID\n\t */\n\tvoid (*uuid)(void *data,\n\t\t     struct kde_output_device_v2 *kde_output_device_v2,\n\t\t     const char *uuid);\n\t/**\n\t * Serial Number\n\t *\n\t * Serial ID of the monitor, sent on startup before the first\n\t * done event.\n\t * @param serialNumber textual representation of serial number\n\t */\n\tvoid (*serial_number)(void *data,\n\t\t\t      struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t      const char *serialNumber);\n\t/**\n\t * EISA ID\n\t *\n\t * EISA ID of the monitor, sent on startup before the first done\n\t * event.\n\t * @param eisaId textual representation of EISA identifier\n\t */\n\tvoid (*eisa_id)(void *data,\n\t\t\tstruct kde_output_device_v2 *kde_output_device_v2,\n\t\t\tconst char *eisaId);\n\t/**\n\t * capability flags\n\t *\n\t * What capabilities this device has, sent on startup before the\n\t * first done event.\n\t */\n\tvoid (*capabilities)(void *data,\n\t\t\t     struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t     uint32_t flags);\n\t/**\n\t * overscan\n\t *\n\t * Overscan value of the monitor in percent, sent on startup\n\t * before the first done event.\n\t * @param overscan amount of overscan of the monitor\n\t */\n\tvoid (*overscan)(void *data,\n\t\t\t struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t uint32_t overscan);\n\t/**\n\t * Variable Refresh Rate Policy\n\t *\n\t * What policy the compositor will employ regarding its use of\n\t * variable refresh rate.\n\t */\n\tvoid (*vrr_policy)(void *data,\n\t\t\t   struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t   uint32_t vrr_policy);\n\t/**\n\t * RGB range\n\t *\n\t * What rgb range the compositor is using for this output\n\t */\n\tvoid (*rgb_range)(void *data,\n\t\t\t  struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t  uint32_t rgb_range);\n\t/**\n\t * Output's name\n\t *\n\t * Name of the output, it's useful to cross-reference to an\n\t * zxdg_output_v1 and ultimately QScreen\n\t * @since 2\n\t */\n\tvoid (*name)(void *data,\n\t\t     struct kde_output_device_v2 *kde_output_device_v2,\n\t\t     const char *name);\n\t/**\n\t * if HDR is enabled\n\t *\n\t * Whether or not high dynamic range is enabled for this output\n\t * @param hdr_enabled 1 if enabled, 0 if disabled\n\t * @since 3\n\t */\n\tvoid (*high_dynamic_range)(void *data,\n\t\t\t\t   struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t\t   uint32_t hdr_enabled);\n\t/**\n\t * the brightness of sdr if hdr is enabled\n\t *\n\t * If high dynamic range is used, this value defines the\n\t * brightness in nits for content that's in standard dynamic range\n\t * format. Note that while the value is in nits, that doesn't\n\t * necessarily translate to the same brightness on the screen.\n\t * @since 3\n\t */\n\tvoid (*sdr_brightness)(void *data,\n\t\t\t       struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t       uint32_t sdr_brightness);\n\t/**\n\t * if WCG is enabled\n\t *\n\t * Whether or not the use of a wide color gamut is enabled for\n\t * this output\n\t * @param wcg_enabled 1 if enabled, 0 if disabled\n\t * @since 3\n\t */\n\tvoid (*wide_color_gamut)(void *data,\n\t\t\t\t struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t\t uint32_t wcg_enabled);\n\t/**\n\t * describes when auto rotate is used\n\t *\n\t *\n\t * @since 4\n\t */\n\tvoid (*auto_rotate_policy)(void *data,\n\t\t\t\t   struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t\t   uint32_t policy);\n\t/**\n\t * describes when auto rotate is used\n\t *\n\t *\n\t * @since 5\n\t */\n\tvoid (*icc_profile_path)(void *data,\n\t\t\t\t struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t\t const char *profile_path);\n\t/**\n\t * metadata about the screen's brightness limits\n\t *\n\t *\n\t * @param max_peak_brightness in nits\n\t * @param max_frame_average_brightness in nits\n\t * @param min_brightness in 0.0001 nits\n\t * @since 6\n\t */\n\tvoid (*brightness_metadata)(void *data,\n\t\t\t\t    struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t\t    uint32_t max_peak_brightness,\n\t\t\t\t    uint32_t max_frame_average_brightness,\n\t\t\t\t    uint32_t min_brightness);\n\t/**\n\t * overrides for the screen's brightness limits\n\t *\n\t *\n\t * @param max_peak_brightness -1 for no override, positive values are the brightness in nits\n\t * @param max_average_brightness -1 for no override, positive values are the brightness in nits\n\t * @param min_brightness -1 for no override, positive values are the brightness in 0.0001 nits\n\t * @since 6\n\t */\n\tvoid (*brightness_overrides)(void *data,\n\t\t\t\t     struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t\t     int32_t max_peak_brightness,\n\t\t\t\t     int32_t max_average_brightness,\n\t\t\t\t     int32_t min_brightness);\n\t/**\n\t * describes which gamut is assumed for sRGB applications\n\t *\n\t * This can be used to provide the colors users assume sRGB\n\t * applications should have based on the default experience on many\n\t * modern sRGB screens.\n\t * @param gamut_wideness 0 means rec.709 primaries, 10000 means native primaries\n\t * @since 6\n\t */\n\tvoid (*sdr_gamut_wideness)(void *data,\n\t\t\t\t   struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t\t   uint32_t gamut_wideness);\n\t/**\n\t * describes which source the compositor uses for the color profile on an output\n\t *\n\t *\n\t * @since 7\n\t */\n\tvoid (*color_profile_source)(void *data,\n\t\t\t\t     struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t\t     uint32_t source);\n\t/**\n\t * brightness multiplier\n\t *\n\t * This is the brightness modifier of the output. It doesn't\n\t * specify any absolute values, but is merely a multiplier on top\n\t * of other brightness values, like sdr_brightness and\n\t * brightness_metadata. 0 is the minimum brightness (not completely\n\t * dark) and 10000 is the maximum brightness. This is currently\n\t * only supported / meaningful while HDR is active.\n\t * @param brightness brightness in 0-10000\n\t * @since 8\n\t */\n\tvoid (*brightness)(void *data,\n\t\t\t   struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t   uint32_t brightness);\n\t/**\n\t * the preferred color/power tradeoff\n\t *\n\t *\n\t * @since 10\n\t */\n\tvoid (*color_power_tradeoff)(void *data,\n\t\t\t\t     struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t\t     uint32_t preference);\n\t/**\n\t * dimming multiplier\n\t *\n\t * This is the dimming multiplier of the output. This is similar\n\t * to the brightness setting, except it's meant to be a temporary\n\t * setting only, not persistent and may be implemented differently\n\t * depending on the display. 0 is the minimum dimming factor (not\n\t * completely dark) and 10000 means the output is not dimmed.\n\t * @param multiplier multiplier in 0-10000\n\t * @since 11\n\t */\n\tvoid (*dimming)(void *data,\n\t\t\tstruct kde_output_device_v2 *kde_output_device_v2,\n\t\t\tuint32_t multiplier);\n\t/**\n\t * source output for mirroring\n\t *\n\t *\n\t * @param source uuid of the source output\n\t * @since 13\n\t */\n\tvoid (*replication_source)(void *data,\n\t\t\t\t   struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t\t   const char *source);\n\t/**\n\t * if DDC/CI should be used to control brightness etc.\n\t *\n\t * If the ddc_ci capability is present, this determines if\n\t * settings such as brightness, contrast or others should be set\n\t * using DDC/CI.\n\t * @param allowed 1 if allowed, 0 if disabled\n\t * @since 14\n\t */\n\tvoid (*ddc_ci_allowed)(void *data,\n\t\t\t       struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t       uint32_t allowed);\n\t/**\n\t * override max bpc\n\t *\n\t * This limits the amount of bits per color that are sent to the\n\t * display.\n\t * @param max_bpc 0 for the default / automatic\n\t * @since 15\n\t */\n\tvoid (*max_bits_per_color)(void *data,\n\t\t\t\t   struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t\t   uint32_t max_bpc);\n\t/**\n\t * range of max bits per color value\n\t *\n\t *\n\t * @param min_value the minimum supported by the driver\n\t * @param max_value the maximum supported by the driver\n\t * @since 15\n\t */\n\tvoid (*max_bits_per_color_range)(void *data,\n\t\t\t\t\t struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t\t\t uint32_t min_value,\n\t\t\t\t\t uint32_t max_value);\n\t/**\n\t * if and to what value automatic max bpc is limited\n\t *\n\t *\n\t * @param max_bpc_limit which value automatic bpc gets limited to. 0 if not limited\n\t * @since 15\n\t */\n\tvoid (*automatic_max_bits_per_color_limit)(void *data,\n\t\t\t\t\t\t   struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t\t\t\t   uint32_t max_bpc_limit);\n\t/**\n\t * when the compositor may apply EDR\n\t *\n\t * When EDR is enabled, the compositor may increase the backlight\n\t * beyond the user-specified setting, in order to present HDR\n\t * content on displays without native HDR support. This will\n\t * usually result in better visuals, but also increases battery\n\t * usage.\n\t * @since 16\n\t */\n\tvoid (*edr_policy)(void *data,\n\t\t\t   struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t   uint32_t policy);\n\t/**\n\t * sharpness strength\n\t *\n\t * This is the sharpness modifier of the output. 0 is sharpness\n\t * disabled and 10000 is the maximum sharpness\n\t * @param sharpness sharpness in 0-10000\n\t * @since 17\n\t */\n\tvoid (*sharpness)(void *data,\n\t\t\t  struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t  uint32_t sharpness);\n\t/**\n\t * output priority\n\t *\n\t * Describes the position of the output in the output order list,\n\t * with lower values being earlier in the list. There's no specific\n\t * value the list has to start at, this value is only used in\n\t * sorting outputs.\n\t *\n\t * Note that the output order protocol is not sufficient for this,\n\t * as an output may not be in the output order if it's disabled or\n\t * mirroring another screen.\n\t * @param priority priority\n\t * @since 18\n\t */\n\tvoid (*priority)(void *data,\n\t\t\t struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t uint32_t priority);\n\t/**\n\t * whether or not automatic brightness is enabled\n\t *\n\t *\n\t * @param enabled 1 for enabled, 0 for disabled\n\t * @since 20\n\t */\n\tvoid (*auto_brightness)(void *data,\n\t\t\t\tstruct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t\tuint32_t enabled);\n\t/**\n\t * the output has been removed\n\t *\n\t * This event is sent when the output device is disconnected and\n\t * no new updates will be sent. The client should call the\n\t * kde_output_device_v2.release request after receiving this event.\n\t * @since 21\n\t */\n\tvoid (*removed)(void *data,\n\t\t\tstruct kde_output_device_v2 *kde_output_device_v2);\n};\n\n/**\n * @ingroup iface_kde_output_device_v2\n */\nstatic inline int\nkde_output_device_v2_add_listener(struct kde_output_device_v2 *kde_output_device_v2,\n\t\t\t\t  const struct kde_output_device_v2_listener *listener, void *data)\n{\n\treturn wl_proxy_add_listener((struct wl_proxy *) kde_output_device_v2,\n\t\t\t\t     (void (**)(void)) listener, data);\n}\n\n#define KDE_OUTPUT_DEVICE_V2_RELEASE 0\n\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_GEOMETRY_SINCE_VERSION 1\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_CURRENT_MODE_SINCE_VERSION 1\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_MODE_SINCE_VERSION 1\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_DONE_SINCE_VERSION 1\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_SCALE_SINCE_VERSION 1\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_EDID_SINCE_VERSION 1\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_ENABLED_SINCE_VERSION 1\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_UUID_SINCE_VERSION 1\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_SERIAL_NUMBER_SINCE_VERSION 1\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_EISA_ID_SINCE_VERSION 1\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_CAPABILITIES_SINCE_VERSION 1\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_OVERSCAN_SINCE_VERSION 1\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_VRR_POLICY_SINCE_VERSION 1\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_RGB_RANGE_SINCE_VERSION 1\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_NAME_SINCE_VERSION 2\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_HIGH_DYNAMIC_RANGE_SINCE_VERSION 3\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_SDR_BRIGHTNESS_SINCE_VERSION 3\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_WIDE_COLOR_GAMUT_SINCE_VERSION 3\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_SINCE_VERSION 4\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_ICC_PROFILE_PATH_SINCE_VERSION 5\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_BRIGHTNESS_METADATA_SINCE_VERSION 6\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_BRIGHTNESS_OVERRIDES_SINCE_VERSION 6\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_SDR_GAMUT_WIDENESS_SINCE_VERSION 6\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_SINCE_VERSION 7\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_BRIGHTNESS_SINCE_VERSION 8\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_COLOR_POWER_TRADEOFF_SINCE_VERSION 10\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_DIMMING_SINCE_VERSION 11\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_REPLICATION_SOURCE_SINCE_VERSION 13\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_DDC_CI_ALLOWED_SINCE_VERSION 14\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_MAX_BITS_PER_COLOR_SINCE_VERSION 15\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_MAX_BITS_PER_COLOR_RANGE_SINCE_VERSION 15\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_AUTOMATIC_MAX_BITS_PER_COLOR_LIMIT_SINCE_VERSION 15\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_EDR_POLICY_SINCE_VERSION 16\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_SHARPNESS_SINCE_VERSION 17\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_PRIORITY_SINCE_VERSION 18\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_AUTO_BRIGHTNESS_SINCE_VERSION 20\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_REMOVED_SINCE_VERSION 21\n\n/**\n * @ingroup iface_kde_output_device_v2\n */\n#define KDE_OUTPUT_DEVICE_V2_RELEASE_SINCE_VERSION 21\n\n/** @ingroup iface_kde_output_device_v2 */\nstatic inline void\nkde_output_device_v2_set_user_data(struct kde_output_device_v2 *kde_output_device_v2, void *user_data)\n{\n\twl_proxy_set_user_data((struct wl_proxy *) kde_output_device_v2, user_data);\n}\n\n/** @ingroup iface_kde_output_device_v2 */\nstatic inline void *\nkde_output_device_v2_get_user_data(struct kde_output_device_v2 *kde_output_device_v2)\n{\n\treturn wl_proxy_get_user_data((struct wl_proxy *) kde_output_device_v2);\n}\n\nstatic inline uint32_t\nkde_output_device_v2_get_version(struct kde_output_device_v2 *kde_output_device_v2)\n{\n\treturn wl_proxy_get_version((struct wl_proxy *) kde_output_device_v2);\n}\n\n/** @ingroup iface_kde_output_device_v2 */\nstatic inline void\nkde_output_device_v2_destroy(struct kde_output_device_v2 *kde_output_device_v2)\n{\n\twl_proxy_destroy((struct wl_proxy *) kde_output_device_v2);\n}\n\n/**\n * @ingroup iface_kde_output_device_v2\n *\n * This notifies the compositor that the client no longer wishes to use\n * the kde_output_device_v2 object.\n */\nstatic inline void\nkde_output_device_v2_release(struct kde_output_device_v2 *kde_output_device_v2)\n{\n\twl_proxy_marshal_flags((struct wl_proxy *) kde_output_device_v2,\n\t\t\t KDE_OUTPUT_DEVICE_V2_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) kde_output_device_v2), WL_MARSHAL_FLAG_DESTROY);\n}\n\n#ifndef KDE_OUTPUT_DEVICE_MODE_V2_FLAGS_ENUM\n#define KDE_OUTPUT_DEVICE_MODE_V2_FLAGS_ENUM\n/**\n * @ingroup iface_kde_output_device_mode_v2\n * mode flags\n */\nenum kde_output_device_mode_v2_flags {\n\tKDE_OUTPUT_DEVICE_MODE_V2_FLAGS_CUSTOM = 0x1,\n\tKDE_OUTPUT_DEVICE_MODE_V2_FLAGS_REDUCED_BLANKING = 0x2,\n};\n#endif /* KDE_OUTPUT_DEVICE_MODE_V2_FLAGS_ENUM */\n\n/**\n * @ingroup iface_kde_output_device_mode_v2\n * @struct kde_output_device_mode_v2_listener\n */\nstruct kde_output_device_mode_v2_listener {\n\t/**\n\t * mode size\n\t *\n\t * This event describes the mode size. The size is given in\n\t * physical hardware units of the output device. This is not\n\t * necessarily the same as the output size in the global compositor\n\t * space. For instance, the output may be scaled or transformed.\n\t * @param width width of the mode in hardware units\n\t * @param height height of the mode in hardware units\n\t */\n\tvoid (*size)(void *data,\n\t\t     struct kde_output_device_mode_v2 *kde_output_device_mode_v2,\n\t\t     int32_t width,\n\t\t     int32_t height);\n\t/**\n\t * mode refresh rate\n\t *\n\t * This event describes the mode's fixed vertical refresh rate.\n\t * It is only sent if the mode has a fixed refresh rate.\n\t * @param refresh vertical refresh rate in mHz\n\t */\n\tvoid (*refresh)(void *data,\n\t\t\tstruct kde_output_device_mode_v2 *kde_output_device_mode_v2,\n\t\t\tint32_t refresh);\n\t/**\n\t * mode is preferred\n\t *\n\t * This event advertises this mode as preferred.\n\t */\n\tvoid (*preferred)(void *data,\n\t\t\t  struct kde_output_device_mode_v2 *kde_output_device_mode_v2);\n\t/**\n\t * the mode has been destroyed\n\t *\n\t * The compositor will destroy the object immediately after\n\t * sending this event, so it will become invalid and the client\n\t * should release any resources associated with it.\n\t */\n\tvoid (*removed)(void *data,\n\t\t\tstruct kde_output_device_mode_v2 *kde_output_device_mode_v2);\n\t/**\n\t * mode flags\n\t *\n\t * This event describes the mode's flags.\n\t * @since 19\n\t */\n\tvoid (*flags)(void *data,\n\t\t      struct kde_output_device_mode_v2 *kde_output_device_mode_v2,\n\t\t      uint32_t flags);\n};\n\n/**\n * @ingroup iface_kde_output_device_mode_v2\n */\nstatic inline int\nkde_output_device_mode_v2_add_listener(struct kde_output_device_mode_v2 *kde_output_device_mode_v2,\n\t\t\t\t       const struct kde_output_device_mode_v2_listener *listener, void *data)\n{\n\treturn wl_proxy_add_listener((struct wl_proxy *) kde_output_device_mode_v2,\n\t\t\t\t     (void (**)(void)) listener, data);\n}\n\n/**\n * @ingroup iface_kde_output_device_mode_v2\n */\n#define KDE_OUTPUT_DEVICE_MODE_V2_SIZE_SINCE_VERSION 1\n/**\n * @ingroup iface_kde_output_device_mode_v2\n */\n#define KDE_OUTPUT_DEVICE_MODE_V2_REFRESH_SINCE_VERSION 1\n/**\n * @ingroup iface_kde_output_device_mode_v2\n */\n#define KDE_OUTPUT_DEVICE_MODE_V2_PREFERRED_SINCE_VERSION 1\n/**\n * @ingroup iface_kde_output_device_mode_v2\n */\n#define KDE_OUTPUT_DEVICE_MODE_V2_REMOVED_SINCE_VERSION 1\n/**\n * @ingroup iface_kde_output_device_mode_v2\n */\n#define KDE_OUTPUT_DEVICE_MODE_V2_FLAGS_SINCE_VERSION 19\n\n\n/** @ingroup iface_kde_output_device_mode_v2 */\nstatic inline void\nkde_output_device_mode_v2_set_user_data(struct kde_output_device_mode_v2 *kde_output_device_mode_v2, void *user_data)\n{\n\twl_proxy_set_user_data((struct wl_proxy *) kde_output_device_mode_v2, user_data);\n}\n\n/** @ingroup iface_kde_output_device_mode_v2 */\nstatic inline void *\nkde_output_device_mode_v2_get_user_data(struct kde_output_device_mode_v2 *kde_output_device_mode_v2)\n{\n\treturn wl_proxy_get_user_data((struct wl_proxy *) kde_output_device_mode_v2);\n}\n\nstatic inline uint32_t\nkde_output_device_mode_v2_get_version(struct kde_output_device_mode_v2 *kde_output_device_mode_v2)\n{\n\treturn wl_proxy_get_version((struct wl_proxy *) kde_output_device_mode_v2);\n}\n\n/** @ingroup iface_kde_output_device_mode_v2 */\nstatic inline void\nkde_output_device_mode_v2_destroy(struct kde_output_device_mode_v2 *kde_output_device_mode_v2)\n{\n\twl_proxy_destroy((struct wl_proxy *) kde_output_device_mode_v2);\n}\n\n#ifdef  __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c",
    "content": "#ifdef FF_HAVE_WAYLAND\n\n/* Generated by wayland-scanner 1.24.0 */\n\n/*\n * SPDX-FileCopyrightText: 2008-2011 Kristian Høgsberg\n * SPDX-FileCopyrightText: 2010-2011 Intel Corporation\n * SPDX-FileCopyrightText: 2012-2013 Collabora, Ltd.\n * SPDX-FileCopyrightText: 2015 Sebastian Kügler <sebas@kde.org>\n * SPDX-FileCopyrightText: 2021 Méven Car <meven.car@enioka.com>\n *\n * SPDX-License-Identifier: MIT-CMU\n */\n\n#include <stdbool.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <wayland-util.h>\n\nextern const struct wl_interface kde_output_device_mode_v2_interface;\nextern const struct wl_interface kde_output_device_v2_interface;\n\nstatic const struct wl_interface *kde_output_device_v2_types[] = {\n\tNULL,\n\tNULL,\n\tNULL,\n\tNULL,\n\tNULL,\n\tNULL,\n\tNULL,\n\tNULL,\n\t&kde_output_device_v2_interface,\n\t&kde_output_device_mode_v2_interface,\n\t&kde_output_device_mode_v2_interface,\n};\n\nstatic const struct wl_message kde_output_device_registry_v2_requests[] = {\n\t{ \"stop\", \"21\", kde_output_device_v2_types + 0 },\n};\n\nstatic const struct wl_message kde_output_device_registry_v2_events[] = {\n\t{ \"finished\", \"21\", kde_output_device_v2_types + 0 },\n\t{ \"output\", \"21n\", kde_output_device_v2_types + 8 },\n};\n\nWL_EXPORT const struct wl_interface kde_output_device_registry_v2_interface = {\n\t\"kde_output_device_registry_v2\", 21,\n\t1, kde_output_device_registry_v2_requests,\n\t2, kde_output_device_registry_v2_events,\n};\n\nstatic const struct wl_message kde_output_device_v2_requests[] = {\n\t{ \"release\", \"21\", kde_output_device_v2_types + 0 },\n};\n\nstatic const struct wl_message kde_output_device_v2_events[] = {\n\t{ \"geometry\", \"iiiiissi\", kde_output_device_v2_types + 0 },\n\t{ \"current_mode\", \"o\", kde_output_device_v2_types + 9 },\n\t{ \"mode\", \"n\", kde_output_device_v2_types + 10 },\n\t{ \"done\", \"\", kde_output_device_v2_types + 0 },\n\t{ \"scale\", \"f\", kde_output_device_v2_types + 0 },\n\t{ \"edid\", \"s\", kde_output_device_v2_types + 0 },\n\t{ \"enabled\", \"i\", kde_output_device_v2_types + 0 },\n\t{ \"uuid\", \"s\", kde_output_device_v2_types + 0 },\n\t{ \"serial_number\", \"s\", kde_output_device_v2_types + 0 },\n\t{ \"eisa_id\", \"s\", kde_output_device_v2_types + 0 },\n\t{ \"capabilities\", \"u\", kde_output_device_v2_types + 0 },\n\t{ \"overscan\", \"u\", kde_output_device_v2_types + 0 },\n\t{ \"vrr_policy\", \"u\", kde_output_device_v2_types + 0 },\n\t{ \"rgb_range\", \"u\", kde_output_device_v2_types + 0 },\n\t{ \"name\", \"2s\", kde_output_device_v2_types + 0 },\n\t{ \"high_dynamic_range\", \"3u\", kde_output_device_v2_types + 0 },\n\t{ \"sdr_brightness\", \"3u\", kde_output_device_v2_types + 0 },\n\t{ \"wide_color_gamut\", \"3u\", kde_output_device_v2_types + 0 },\n\t{ \"auto_rotate_policy\", \"4u\", kde_output_device_v2_types + 0 },\n\t{ \"icc_profile_path\", \"5s\", kde_output_device_v2_types + 0 },\n\t{ \"brightness_metadata\", \"6uuu\", kde_output_device_v2_types + 0 },\n\t{ \"brightness_overrides\", \"6iii\", kde_output_device_v2_types + 0 },\n\t{ \"sdr_gamut_wideness\", \"6u\", kde_output_device_v2_types + 0 },\n\t{ \"color_profile_source\", \"7u\", kde_output_device_v2_types + 0 },\n\t{ \"brightness\", \"8u\", kde_output_device_v2_types + 0 },\n\t{ \"color_power_tradeoff\", \"10u\", kde_output_device_v2_types + 0 },\n\t{ \"dimming\", \"11u\", kde_output_device_v2_types + 0 },\n\t{ \"replication_source\", \"13s\", kde_output_device_v2_types + 0 },\n\t{ \"ddc_ci_allowed\", \"14u\", kde_output_device_v2_types + 0 },\n\t{ \"max_bits_per_color\", \"15u\", kde_output_device_v2_types + 0 },\n\t{ \"max_bits_per_color_range\", \"15uu\", kde_output_device_v2_types + 0 },\n\t{ \"automatic_max_bits_per_color_limit\", \"15u\", kde_output_device_v2_types + 0 },\n\t{ \"edr_policy\", \"16u\", kde_output_device_v2_types + 0 },\n\t{ \"sharpness\", \"17u\", kde_output_device_v2_types + 0 },\n\t{ \"priority\", \"18u\", kde_output_device_v2_types + 0 },\n\t{ \"auto_brightness\", \"20u\", kde_output_device_v2_types + 0 },\n\t{ \"removed\", \"21\", kde_output_device_v2_types + 0 },\n};\n\nWL_EXPORT const struct wl_interface kde_output_device_v2_interface = {\n\t\"kde_output_device_v2\", 21,\n\t1, kde_output_device_v2_requests,\n\t37, kde_output_device_v2_events,\n};\n\nstatic const struct wl_message kde_output_device_mode_v2_events[] = {\n\t{ \"size\", \"ii\", kde_output_device_v2_types + 0 },\n\t{ \"refresh\", \"i\", kde_output_device_v2_types + 0 },\n\t{ \"preferred\", \"\", kde_output_device_v2_types + 0 },\n\t{ \"removed\", \"\", kde_output_device_v2_types + 0 },\n\t{ \"flags\", \"19u\", kde_output_device_v2_types + 0 },\n};\n\nWL_EXPORT const struct wl_interface kde_output_device_mode_v2_interface = {\n\t\"kde_output_device_mode_v2\", 21,\n\t0, NULL,\n\t5, kde_output_device_mode_v2_events,\n};\n\n#endif\n"
  },
  {
    "path": "src/detection/displayserver/linux/wayland/kde-output-order-v1-client-protocol.h",
    "content": "/* Generated by wayland-scanner 1.22.0 */\n\n#ifndef KDE_OUTPUT_ORDER_V1_CLIENT_PROTOCOL_H\n#define KDE_OUTPUT_ORDER_V1_CLIENT_PROTOCOL_H\n\n#include <stdint.h>\n#include <stddef.h>\n#include \"wayland-client.h\"\n\n#ifdef  __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * @page page_kde_output_order_v1 The kde_output_order_v1 protocol\n * @section page_ifaces_kde_output_order_v1 Interfaces\n * - @subpage page_iface_kde_output_order_v1 - announce order of outputs\n * @section page_copyright_kde_output_order_v1 Copyright\n * <pre>\n *\n * SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>\n *\n * SPDX-License-Identifier: MIT-CMU\n * </pre>\n */\nstruct kde_output_order_v1;\n\n#ifndef KDE_OUTPUT_ORDER_V1_INTERFACE\n#define KDE_OUTPUT_ORDER_V1_INTERFACE\n/**\n * @page page_iface_kde_output_order_v1 kde_output_order_v1\n * @section page_iface_kde_output_order_v1_desc Description\n *\n * Announce the order in which desktop environment components should be placed on outputs.\n * The compositor will send the list of outputs when the global is bound and whenever there is a change.\n * @section page_iface_kde_output_order_v1_api API\n * See @ref iface_kde_output_order_v1.\n */\n/**\n * @defgroup iface_kde_output_order_v1 The kde_output_order_v1 interface\n *\n * Announce the order in which desktop environment components should be placed on outputs.\n * The compositor will send the list of outputs when the global is bound and whenever there is a change.\n */\nextern const struct wl_interface kde_output_order_v1_interface;\n#endif\n\n/**\n * @ingroup iface_kde_output_order_v1\n * @struct kde_output_order_v1_listener\n */\nstruct kde_output_order_v1_listener {\n\t/**\n\t * output name\n\t *\n\t * Specifies the output identified by their wl_output.name.\n\t * @param output_name the name of the output\n\t */\n\tvoid (*output)(void *data,\n\t\t       struct kde_output_order_v1 *kde_output_order_v1,\n\t\t       const char *output_name);\n\t/**\n\t * done\n\t *\n\t * Specifies that the output list is complete. On the next output\n\t * event, a new list begins.\n\t */\n\tvoid (*done)(void *data,\n\t\t     struct kde_output_order_v1 *kde_output_order_v1);\n};\n\n/**\n * @ingroup iface_kde_output_order_v1\n */\nstatic inline int\nkde_output_order_v1_add_listener(struct kde_output_order_v1 *kde_output_order_v1,\n\t\t\t\t const struct kde_output_order_v1_listener *listener, void *data)\n{\n\treturn wl_proxy_add_listener((struct wl_proxy *) kde_output_order_v1,\n\t\t\t\t     (void (**)(void)) listener, data);\n}\n\n#define KDE_OUTPUT_ORDER_V1_DESTROY 0\n\n/**\n * @ingroup iface_kde_output_order_v1\n */\n#define KDE_OUTPUT_ORDER_V1_OUTPUT_SINCE_VERSION 1\n/**\n * @ingroup iface_kde_output_order_v1\n */\n#define KDE_OUTPUT_ORDER_V1_DONE_SINCE_VERSION 1\n\n/**\n * @ingroup iface_kde_output_order_v1\n */\n#define KDE_OUTPUT_ORDER_V1_DESTROY_SINCE_VERSION 1\n\n// /** @ingroup iface_kde_output_order_v1 */\n// static inline void\n// kde_output_order_v1_set_user_data(struct kde_output_order_v1 *kde_output_order_v1, void *user_data)\n// {\n// \twl_proxy_set_user_data((struct wl_proxy *) kde_output_order_v1, user_data);\n// }\n\n// /** @ingroup iface_kde_output_order_v1 */\n// static inline void *\n// kde_output_order_v1_get_user_data(struct kde_output_order_v1 *kde_output_order_v1)\n// {\n// \treturn wl_proxy_get_user_data((struct wl_proxy *) kde_output_order_v1);\n// }\n\n// static inline uint32_t\n// kde_output_order_v1_get_version(struct kde_output_order_v1 *kde_output_order_v1)\n// {\n// \treturn wl_proxy_get_version((struct wl_proxy *) kde_output_order_v1);\n// }\n\n// /**\n//  * @ingroup iface_kde_output_order_v1\n//  */\n// static inline void\n// kde_output_order_v1_destroy(struct kde_output_order_v1 *kde_output_order_v1)\n// {\n// \twl_proxy_marshal_flags((struct wl_proxy *) kde_output_order_v1,\n// \t\t\t KDE_OUTPUT_ORDER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) kde_output_order_v1), WL_MARSHAL_FLAG_DESTROY);\n// }\n\n#ifdef  __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "src/detection/displayserver/linux/wayland/kde-output-order-v1-protocol.c",
    "content": "#ifdef FF_HAVE_WAYLAND\n\n/* Generated by wayland-scanner 1.22.0 */\n\n/*\n * SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>\n *\n * SPDX-License-Identifier: MIT-CMU\n */\n\n#include <stdlib.h>\n#include <stdint.h>\n#include \"wayland-util.h\"\n\n\nstatic const struct wl_interface *kde_output_order_v1_types[] = {\n\tNULL,\n};\n\nstatic const struct wl_message kde_output_order_v1_requests[] = {\n\t{ \"destroy\", \"\", kde_output_order_v1_types + 0 },\n};\n\nstatic const struct wl_message kde_output_order_v1_events[] = {\n\t{ \"output\", \"s\", kde_output_order_v1_types + 0 },\n\t{ \"done\", \"\", kde_output_order_v1_types + 0 },\n};\n\nWL_EXPORT const struct wl_interface kde_output_order_v1_interface = {\n\t\"kde_output_order_v1\", 1,\n\t1, kde_output_order_v1_requests,\n\t2, kde_output_order_v1_events,\n};\n\n#endif\n"
  },
  {
    "path": "src/detection/displayserver/linux/wayland/kde-output.c",
    "content": "#ifdef FF_HAVE_WAYLAND\n\n#include \"wayland.h\"\n#include \"kde-output-device-v2-client-protocol.h\"\n#include \"kde-output-order-v1-client-protocol.h\"\n#include \"common/edidHelper.h\"\n#include \"common/base64.h\"\n\ntypedef struct WaylandKdeMode\n{\n    int32_t width;\n    int32_t height;\n    int32_t refreshRate;\n    bool preferred;\n    struct kde_output_device_mode_v2* pMode;\n} WaylandKdeMode;\n\nstatic void waylandKdeModeSizeListener(void* data, FF_MAYBE_UNUSED struct kde_output_device_mode_v2 *_, int32_t width, int32_t height)\n{\n    WaylandKdeMode* mode = (WaylandKdeMode*) data;\n    mode->width = width;\n    mode->height = height;\n}\n\nstatic void waylandKdeModeRefreshListener(void* data, FF_MAYBE_UNUSED struct kde_output_device_mode_v2 *_, int32_t rate)\n{\n    WaylandKdeMode* mode = (WaylandKdeMode*) data;\n    mode->refreshRate = rate;\n}\n\nstatic void waylandKdeModePreferredListener(void* data, FF_MAYBE_UNUSED struct kde_output_device_mode_v2 *_)\n{\n    WaylandKdeMode* mode = (WaylandKdeMode*) data;\n    mode->preferred = true;\n}\n\nstatic const struct kde_output_device_mode_v2_listener modeListener = {\n    .size = waylandKdeModeSizeListener,\n    .refresh = waylandKdeModeRefreshListener,\n    .preferred = waylandKdeModePreferredListener,\n    .removed = (void*) stubListener,\n    .flags = (void*) stubListener,\n};\n\nstatic void waylandKdeModeListener(void* data, FF_MAYBE_UNUSED struct kde_output_device_v2* _, struct kde_output_device_mode_v2 *mode)\n{\n    WaylandDisplay* wldata = (WaylandDisplay*) data;\n    if (!wldata->internal) return;\n\n    WaylandKdeMode* newMode = ffListAdd((FFlist*) wldata->internal);\n    *newMode = (WaylandKdeMode) { .pMode = mode };\n\n    // Strangely, the listener is called only in this function, but not in `waylandKdeCurrentModeListener`\n    wldata->parent->ffwl_proxy_add_listener((struct wl_proxy *) mode, (void (**)(void)) &modeListener, newMode);\n}\n\nstatic void waylandKdeCurrentModeListener(void* data, FF_MAYBE_UNUSED struct kde_output_device_v2 *_, struct kde_output_device_mode_v2 *mode)\n{\n    // waylandKdeModeListener is always run before this\n    WaylandDisplay* wldata = (WaylandDisplay*) data;\n    if (!wldata->internal) return;\n\n    int set = 0;\n    FF_LIST_FOR_EACH(WaylandKdeMode, m, *(FFlist*) wldata->internal)\n    {\n        if (m->pMode == mode)\n        {\n            wldata->width = m->width;\n            wldata->height = m->height;\n            wldata->refreshRate = m->refreshRate;\n            if (++set == 2) break;\n        }\n        if (m->preferred)\n        {\n            wldata->preferredWidth = m->width;\n            wldata->preferredHeight = m->height;\n            wldata->preferredRefreshRate = m->refreshRate;\n            if (++set == 2) break;\n        }\n    }\n}\n\nstatic void waylandKdeScaleListener(void* data, FF_MAYBE_UNUSED struct kde_output_device_v2* _, wl_fixed_t scale)\n{\n    WaylandDisplay* wldata = (WaylandDisplay*) data;\n    wldata->dpi = (uint32_t) scale * 3 / 8; // wl_fixed_to_double(scale) * 96;\n}\n\nstatic void waylandKdeEdidListener(void* data, FF_MAYBE_UNUSED struct kde_output_device_v2* _, const char* raw)\n{\n    if (!*raw) return;\n    WaylandDisplay* wldata = (WaylandDisplay*) data;\n    FF_STRBUF_AUTO_DESTROY b64 = ffStrbufCreateStatic(raw);\n    FF_STRBUF_AUTO_DESTROY edid = ffBase64DecodeStrbuf(&b64);\n    if (edid.length < 128) return;\n    ffEdidGetName((const uint8_t*) edid.chars, &wldata->edidName);\n    wldata->hdrSupported = ffEdidGetHdrCompatible((const uint8_t*) edid.chars, edid.length);\n    ffEdidGetSerialAndManufactureDate((const uint8_t*) edid.chars, &wldata->serial, &wldata->myear, &wldata->mweek);\n    wldata->hdrInfoAvailable = true;\n}\n\nstatic void waylandKdeEnabledListener(void* data, FF_MAYBE_UNUSED struct kde_output_device_v2* _, int32_t enabled)\n{\n    WaylandDisplay* wldata = (WaylandDisplay*) data;\n    if (!enabled) wldata->internal = NULL;\n}\n\nstatic void waylandKdeGeometryListener(void *data,\n    FF_MAYBE_UNUSED struct kde_output_device_v2 *kde_output_device_v2,\n    FF_MAYBE_UNUSED int32_t x,\n    FF_MAYBE_UNUSED int32_t y,\n    int32_t physical_width,\n    int32_t physical_height,\n    FF_MAYBE_UNUSED int32_t subpixel,\n    FF_MAYBE_UNUSED const char *make,\n    FF_MAYBE_UNUSED const char *model,\n    int32_t transform)\n{\n    WaylandDisplay* display = data;\n    display->physicalWidth = physical_width;\n    display->physicalHeight = physical_height;\n    display->transform = (enum wl_output_transform) transform;\n}\n\nstatic void waylandKdeNameListener(void* data, FF_MAYBE_UNUSED struct kde_output_device_v2* kde_output_device_v2, const char *name)\n{\n    WaylandDisplay* display = data;\n    display->type = ffdsGetDisplayType(name);\n    // As display->id is used as an internal identifier, we don't need it to be NUL terminated\n    strncpy((char*) &display->id, name, sizeof(display->id));\n\n    ffStrbufAppendS(&display->name, name);\n}\n\nstatic void waylandKdeHdrListener(void *data, FF_MAYBE_UNUSED struct kde_output_device_v2 *kde_output_device_v2, uint32_t hdr_enabled)\n{\n    WaylandDisplay* display = data;\n    display->hdrEnabled = !!hdr_enabled;\n}\n\nstatic void waylandKdeMaxBitsPerColorListener(void *data, FF_MAYBE_UNUSED struct kde_output_device_v2 *kde_output_device_v2, uint32_t max_bpc)\n{\n    WaylandDisplay* display = data;\n    display->bitDepth = (uint8_t) max_bpc;\n}\n\nstatic struct kde_output_device_v2_listener outputListener = {\n    .geometry = waylandKdeGeometryListener,\n    .current_mode = waylandKdeCurrentModeListener,\n    .mode = waylandKdeModeListener,\n    .done = (void*) stubListener,\n    .scale = waylandKdeScaleListener,\n    .edid = waylandKdeEdidListener,\n    .enabled = waylandKdeEnabledListener,\n    .uuid = (void*) stubListener,\n    .serial_number = (void*) stubListener,\n    .eisa_id = (void*) stubListener,\n    .capabilities = (void*) stubListener,\n    .overscan = (void*) stubListener,\n    .vrr_policy = (void*) stubListener,\n    .rgb_range = (void*) stubListener,\n    .name = waylandKdeNameListener,\n    .high_dynamic_range = waylandKdeHdrListener,\n    .sdr_brightness = (void*) stubListener,\n    .wide_color_gamut = (void*) stubListener,\n    .auto_rotate_policy = (void*) stubListener,\n    .icc_profile_path = (void*) stubListener,\n    .brightness_metadata = (void*) stubListener,\n    .brightness_overrides = (void*) stubListener,\n    .sdr_gamut_wideness = (void*) stubListener,\n    .color_profile_source = (void*) stubListener,\n    .brightness = (void*) stubListener,\n    .color_power_tradeoff = (void*) stubListener,\n    .dimming = (void*) stubListener,\n    .replication_source = (void*) stubListener,\n    .ddc_ci_allowed = (void*) stubListener,\n    .max_bits_per_color = (void*) waylandKdeMaxBitsPerColorListener,\n    .max_bits_per_color_range = (void*) stubListener,\n    .automatic_max_bits_per_color_limit = (void*) stubListener,\n    .edr_policy = (void*) stubListener,\n    .sharpness = (void*) stubListener,\n    .priority = (void*) stubListener,\n    .auto_brightness = (void*) stubListener,\n    .removed = (void*) stubListener,\n};\n\nconst char* ffWaylandHandleKdeOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version)\n{\n    struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &kde_output_device_v2_interface, version, name, kde_output_device_v2_interface.name, version, NULL);\n    if(output == NULL)\n        return \"Failed to create kde_output_device_v2\";\n\n    FF_LIST_AUTO_DESTROY modes = ffListCreate(sizeof(WaylandKdeMode));\n    WaylandDisplay display = {\n        .parent = wldata,\n        .transform = WL_OUTPUT_TRANSFORM_NORMAL,\n        .type = FF_DISPLAY_TYPE_UNKNOWN,\n        .name = ffStrbufCreate(),\n        .description = ffStrbufCreate(),\n        .edidName = ffStrbufCreate(),\n        .internal = &modes,\n    };\n\n    if (wldata->ffwl_proxy_add_listener(output, (void(**)(void)) &outputListener, &display) < 0)\n    {\n        wldata->ffwl_proxy_destroy(output);\n        return \"Failed to add listener to kde_output_device_v2\";\n    }\n\n    if (wldata->ffwl_display_roundtrip(wldata->display) < 0)\n    {\n        wldata->ffwl_proxy_destroy(output);\n        return \"Failed to roundtrip kde_output_device_v2\";\n    }\n    wldata->ffwl_proxy_destroy(output);\n\n    if(display.width <= 0 || display.height <= 0 || !display.internal)\n        return \"Failed to get display information from kde_output_device_v2\";\n\n    uint32_t rotation = ffWaylandHandleRotation(&display);\n\n    FFDisplayResult* item = ffdsAppendDisplay(wldata->result,\n        (uint32_t) display.width,\n        (uint32_t) display.height,\n        display.refreshRate / 1000.0,\n        display.dpi,\n        (uint32_t) display.preferredWidth,\n        (uint32_t) display.preferredHeight,\n        display.preferredRefreshRate / 1000.0,\n        rotation,\n        display.edidName.length\n            ? &display.edidName\n            : &display.name,\n        display.type,\n        false,\n        display.id,\n        (uint32_t) display.physicalWidth,\n        (uint32_t) display.physicalHeight,\n        \"wayland-kde\"\n    );\n    if (item)\n    {\n        if (display.hdrEnabled)\n            item->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED;\n        else if (display.hdrSupported)\n            item->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED;\n        else if (display.hdrInfoAvailable)\n            item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED;\n        else\n            item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN;\n\n        item->manufactureYear = display.myear;\n        item->manufactureWeek = display.mweek;\n        item->serial = display.serial;\n        item->bitDepth = display.bitDepth;\n    }\n\n    ffStrbufDestroy(&display.description);\n    ffStrbufDestroy(&display.name);\n    ffStrbufDestroy(&display.edidName);\n\n    return NULL;\n}\n\n\nstatic void waylandKdeOutputOrderListener(void *data, FF_MAYBE_UNUSED struct kde_output_order_v1 *_, const char *output_name)\n{\n    uint64_t* id = (uint64_t*) data;\n    if (*id == 0)\n        *id = ffWaylandGenerateIdFromName(output_name);\n}\n\nconst char* ffWaylandHandleKdeOutputOrder(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version)\n{\n    struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &kde_output_order_v1_interface, version, name, kde_output_order_v1_interface.name, version, NULL);\n    if(output == NULL)\n        return \"Failed to create kde_output_order_v1\";\n\n    struct kde_output_order_v1_listener orderListener = {\n        .output = waylandKdeOutputOrderListener,\n        .done = (void*) stubListener,\n    };\n\n    if (wldata->ffwl_proxy_add_listener(output, (void(**)(void)) &orderListener, &wldata->primaryDisplayId) < 0)\n    {\n        wldata->ffwl_proxy_destroy(output);\n        return \"Failed to add listener to kde_output_order_v1\";\n    }\n    if (wldata->ffwl_display_roundtrip(wldata->display) < 0)\n    {\n        wldata->ffwl_proxy_destroy(output);\n        return \"Failed to roundtrip kde_output_order_v1\";\n    }\n    wldata->ffwl_proxy_destroy(output);\n\n    return NULL;\n}\n\n#endif\n"
  },
  {
    "path": "src/detection/displayserver/linux/wayland/wayland.c",
    "content": "#include \"../displayserver_linux.h\"\n#include \"common/io.h\"\n#include \"common/edidHelper.h\"\n#include \"common/stringUtils.h\"\n\n#include <stdlib.h>\n#include <string.h>\n\n#ifdef FF_HAVE_WAYLAND\n\n#include <sys/socket.h>\n\n#include \"common/properties.h\"\n\n#include \"wayland.h\"\n#include \"wlr-output-management-unstable-v1-client-protocol.h\"\n#include \"kde-output-device-v2-client-protocol.h\"\n#include \"kde-output-order-v1-client-protocol.h\"\n#include \"xdg-output-unstable-v1-client-protocol.h\"\n\n#if __FreeBSD__\n#include <sys/un.h>\n#include <sys/ucred.h>\n#include <sys/sysctl.h>\n#endif\n\nstatic bool waylandDetectWM(int fd, FFDisplayServerResult* result)\n{\n#if __linux__ || __GNU__ || (__FreeBSD__ && !__DragonFly__)\n\n#if __linux__ || __GNU__\n    struct ucred ucred = {};\n    socklen_t len = sizeof(ucred);\n    if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1 || ucred.pid <= 0)\n        return false;\n\n    FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreate();\n    ffStrbufAppendF(&procPath, \"/proc/%d/cmdline\", ucred.pid); //We check the cmdline for the process name, because it is not trimmed.\n    if (!ffReadFileBuffer(procPath.chars, &result->wmProcessName))\n        return false;\n#else\n    struct xucred ucred = {};\n    socklen_t len = sizeof(ucred);\n    if (getsockopt(fd, AF_UNSPEC, LOCAL_PEERCRED, &ucred, &len) == -1 || ucred.cr_pid <= 0)\n        return false;\n\n    size_t size = 4096;\n    ffStrbufEnsureFixedLengthFree(&result->wmProcessName, (uint32_t) size);\n\n    if(sysctl((int[]){CTL_KERN, KERN_PROC, KERN_PROC_ARGS, ucred.cr_pid}, 4, result->wmProcessName.chars, &size, NULL, 0 ) != 0)\n        return false;\n    result->wmProcessName.length = (uint32_t) size - 1;\n#endif\n\n    // #1135: wl-restart is a special case\n    const char* filename = strrchr(result->wmProcessName.chars, '/');\n    if (filename)\n        filename++;\n    else\n        filename = result->wmProcessName.chars;\n\n    if (ffStrEquals(filename, \"wl-restart\"))\n        ffStrbufSubstrAfterLastC(&result->wmProcessName, '\\0');\n\n    ffStrbufSubstrBeforeFirstC(&result->wmProcessName, '\\0'); //Trim the arguments\n    ffStrbufSubstrAfterLastC(&result->wmProcessName, '/'); //Trim the path\n\n    return true;\n\n#else\n    FF_UNUSED(fd, result);\n    return false;\n#endif\n}\n\nstatic void waylandGlobalAddListener(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version)\n{\n    WaylandData* wldata = data;\n\n    if((wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_NONE || wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_GLOBAL) && ffStrEquals(interface, wldata->ffwl_output_interface->name))\n    {\n        wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_GLOBAL;\n        if (ffWaylandHandleGlobalOutput(wldata, registry, name, version) != NULL)\n            wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_NONE;\n    }\n    else if((wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_NONE || wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_ZWLR) && ffStrEquals(interface, zwlr_output_manager_v1_interface.name))\n    {\n        wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_ZWLR;\n        if (ffWaylandHandleZwlrOutput(wldata, registry, name, version) != NULL)\n            wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_NONE;\n    }\n    else if((wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_NONE || wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_KDE) && ffStrEquals(interface, kde_output_device_v2_interface.name))\n    {\n        wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_KDE;\n        if (ffWaylandHandleKdeOutput(wldata, registry, name, version) != NULL)\n            wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_NONE;\n    }\n    else if(ffStrEquals(interface, kde_output_order_v1_interface.name))\n    {\n        ffWaylandHandleKdeOutputOrder(wldata, registry, name, version);\n    }\n    else if((wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_GLOBAL || wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_NONE) && ffStrEquals(interface, zxdg_output_manager_v1_interface.name))\n    {\n        ffWaylandHandleZxdgOutput(wldata, registry, name, version);\n    }\n}\n\nstatic FF_MAYBE_UNUSED bool matchDrmConnector(const char* connName, WaylandDisplay* wldata)\n{\n    // https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_output-event-name\n    // The doc says that \"do not assume that the name is a reflection of an underlying DRM connector, X11 connection, etc.\"\n    // However I can't find a better method to get the edid data\n    const char* drmDirPath = \"/sys/class/drm/\";\n\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(drmDirPath);\n    if(dirp == NULL)\n        return false;\n\n    struct dirent* entry;\n    while((entry = readdir(dirp)) != NULL)\n    {\n        const char* plainName = entry->d_name;\n        if (ffStrStartsWith(plainName, \"card\"))\n        {\n            const char* tmp = strchr(plainName + strlen(\"card\"), '-');\n            if (tmp) plainName = tmp + 1;\n        }\n        if (ffStrEquals(plainName, connName))\n        {\n            FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateF(\"%s%s/edid\", drmDirPath, entry->d_name);\n\n            uint8_t edidData[512];\n            ssize_t edidLength = ffReadFileData(path.chars, ARRAY_SIZE(edidData), edidData);\n            if (edidLength > 0 && edidLength % 128 == 0)\n            {\n                ffEdidGetName(edidData, &wldata->edidName);\n                ffEdidGetHdrCompatible(edidData, (uint32_t) edidLength);\n                ffEdidGetSerialAndManufactureDate(edidData, &wldata->serial, &wldata->myear, &wldata->mweek);\n                wldata->hdrInfoAvailable = true;\n                return true;\n            }\n            break;\n        }\n    }\n    return false;\n}\n\nvoid ffWaylandOutputNameListener(void* data, FF_MAYBE_UNUSED void* output, const char *name)\n{\n    WaylandDisplay* display = data;\n    if (display->id) return;\n\n    display->type = ffdsGetDisplayType(name);\n    #if __linux__\n    if (!display->edidName.length)\n        matchDrmConnector(name, display);\n    #endif\n    display->id = ffWaylandGenerateIdFromName(name);\n    ffStrbufAppendS(&display->name, name);\n}\n\nvoid ffWaylandOutputDescriptionListener(void* data, FF_MAYBE_UNUSED void* output, const char* description)\n{\n    WaylandDisplay* display = data;\n    if (display->description.length) return;\n\n    while (*description == ' ') ++description;\n    if (!ffStrEquals(description, \"Unknown Display\") && !ffStrContains(description, \"(null)\"))\n        ffStrbufAppendS(&display->description, description);\n}\n\nuint32_t ffWaylandHandleRotation(WaylandDisplay* display)\n{\n    uint32_t rotation;\n    switch(display->transform)\n    {\n        case WL_OUTPUT_TRANSFORM_FLIPPED_90:\n        case WL_OUTPUT_TRANSFORM_90:\n            rotation = 90;\n            break;\n        case WL_OUTPUT_TRANSFORM_FLIPPED_180:\n        case WL_OUTPUT_TRANSFORM_180:\n            rotation = 180;\n            break;\n        case WL_OUTPUT_TRANSFORM_FLIPPED_270:\n        case WL_OUTPUT_TRANSFORM_270:\n            rotation = 270;\n            break;\n        default:\n            rotation = 0;\n            break;\n    }\n\n    switch(rotation)\n    {\n        case 90:\n        case 270: {\n            int32_t temp = display->width;\n            display->width = display->height;\n            display->height = temp;\n\n            temp = display->physicalWidth;\n            display->physicalWidth = display->physicalHeight;\n            display->physicalHeight = temp;\n            break;\n        }\n        default:\n            break;\n    }\n    return rotation;\n}\n\nconst char* ffdsConnectWayland(FFDisplayServerResult* result)\n{\n    if (getenv(\"XDG_RUNTIME_DIR\") == NULL)\n        return \"Wayland requires $XDG_RUNTIME_DIR being set\";\n\n    FF_LIBRARY_LOAD_MESSAGE(wayland, \"libwayland-client\" FF_LIBRARY_EXTENSION, 1)\n\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wayland, wl_display_connect)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wayland, wl_display_get_fd)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wayland, wl_proxy_marshal_constructor)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wayland, wl_display_disconnect)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wayland, wl_registry_interface)\n\n    WaylandData data = {};\n\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_proxy_marshal_constructor_versioned)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_proxy_add_listener)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_proxy_destroy)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_display_roundtrip)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_output_interface)\n\n    data.display = ffwl_display_connect(NULL);\n    if(data.display == NULL)\n        return \"wl_display_connect returned NULL\";\n\n    waylandDetectWM(ffwl_display_get_fd(data.display), result);\n\n    struct wl_proxy* registry = ffwl_proxy_marshal_constructor((struct wl_proxy*) data.display, WL_DISPLAY_GET_REGISTRY, ffwl_registry_interface, NULL);\n    if(registry == NULL)\n    {\n        ffwl_display_disconnect(data.display);\n        return \"wl_display_get_registry returned NULL\";\n    }\n\n    data.result = result;\n\n    struct wl_registry_listener registry_listener = {\n        .global = waylandGlobalAddListener,\n        .global_remove = (void*) stubListener\n    };\n\n    data.ffwl_proxy_add_listener(registry, (void(**)(void)) &registry_listener, &data);\n    data.ffwl_display_roundtrip(data.display);\n\n    if (data.zxdgOutputManager)\n        data.ffwl_proxy_destroy(data.zxdgOutputManager);\n\n    data.ffwl_proxy_destroy(registry);\n    ffwl_display_disconnect(data.display);\n\n    if(data.primaryDisplayId == 0 && result->wmProcessName.length > 0)\n    {\n        const char* fileName = ffStrbufEqualS(&result->wmProcessName, \"gnome-shell\")\n            ? \"monitors.xml\"\n            : ffStrbufEqualS(&result->wmProcessName, \"cinnamon\")\n                ? \"cinnamon-monitors.xml\"\n                : NULL;\n        if (fileName)\n        {\n            FF_STRBUF_AUTO_DESTROY monitorsXml = ffStrbufCreate();\n            FF_LIST_FOR_EACH(FFstrbuf, basePath, instance.state.platform.configDirs)\n            {\n                char path[1024];\n                snprintf(path, ARRAY_SIZE(path), \"%s%s\", basePath->chars, fileName);\n                if (ffReadFileBuffer(path, &monitorsXml))\n                    break;\n            }\n            if (monitorsXml.length)\n            {\n                // <monitors version=\"2\">\n                // <configuration>\n                //     <logicalmonitor>\n                //     <x>0</x>\n                //     <y>0</y>\n                //     <scale>1.7489879131317139</scale>\n                //     <primary>yes</primary>\n                //     <monitor>\n                //         <monitorspec>\n                //         <connector>Virtual-1</connector>\n                //         <vendor>unknown</vendor>\n                //         <product>unknown</product>\n                //         <serial>unknown</serial>\n                //         </monitorspec>\n                //         <mode>\n                //         <width>3456</width>\n                //         <height>2160</height>\n                //         <rate>60.000068664550781</rate>\n                //         </mode>\n                //     </monitor>\n                //     </logicalmonitor>\n                // </configuration>\n                // </monitors>\n                uint32_t start = ffStrbufFirstIndexS(&monitorsXml, \"<primary>yes</primary>\");\n                if (start < monitorsXml.length)\n                {\n                    start = ffStrbufNextIndexS(&monitorsXml, start, \"<connector>\");\n                    if (start < monitorsXml.length)\n                    {\n                        uint32_t end = ffStrbufNextIndexS(&monitorsXml, start, \"</connector>\");\n                        if (end < monitorsXml.length)\n                        {\n                            ffStrbufSubstrBefore(&monitorsXml, end);\n                            const char* name = monitorsXml.chars + start + strlen(\"<connector>\");\n                            data.primaryDisplayId = ffWaylandGenerateIdFromName(name);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    if(data.primaryDisplayId)\n    {\n        FF_LIST_FOR_EACH(FFDisplayResult, d, data.result->displays)\n        {\n            if(d->id == data.primaryDisplayId)\n            {\n                d->primary = true;\n                break;\n            }\n        }\n    }\n\n    //We successfully connected to wayland and detected the display.\n    //So we can set set the session type to wayland.\n    //This is used as an indicator that we are running wayland by the x11 backends.\n    ffStrbufSetStatic(&result->wmProtocolName, FF_WM_PROTOCOL_WAYLAND);\n    return NULL;\n}\n\n#else\n\nconst char* ffdsConnectWayland(FF_MAYBE_UNUSED FFDisplayServerResult* result)\n{\n    return \"Fastfetch was compiled without Wayland support\";\n}\n\n#endif\n"
  },
  {
    "path": "src/detection/displayserver/linux/wayland/wayland.h",
    "content": "#pragma once\n\n#ifdef FF_HAVE_WAYLAND\n\n#include \"common/library.h\"\n#include \"common/stringUtils.h\"\n\n#include <wayland-client.h>\n\n#include \"../displayserver_linux.h\"\n\ntypedef enum __attribute__((__packed__)) WaylandProtocolType\n{\n    FF_WAYLAND_PROTOCOL_TYPE_NONE,\n    FF_WAYLAND_PROTOCOL_TYPE_GLOBAL,\n    FF_WAYLAND_PROTOCOL_TYPE_ZWLR,\n    FF_WAYLAND_PROTOCOL_TYPE_KDE,\n} WaylandProtocolType;\n\ntypedef struct WaylandData\n{\n    FFDisplayServerResult* result;\n    FF_LIBRARY_SYMBOL(wl_proxy_marshal_constructor_versioned)\n    FF_LIBRARY_SYMBOL(wl_proxy_add_listener)\n    FF_LIBRARY_SYMBOL(wl_proxy_destroy)\n    FF_LIBRARY_SYMBOL(wl_display_roundtrip)\n    struct wl_display* display;\n    const struct wl_interface* ffwl_output_interface;\n    WaylandProtocolType protocolType;\n    uint64_t primaryDisplayId;\n    struct wl_proxy* zxdgOutputManager;\n} WaylandData;\n\ntypedef struct WaylandDisplay\n{\n    WaylandData* parent;\n    void* internal;\n    int32_t width;\n    int32_t height;\n    int32_t refreshRate;\n    int32_t preferredWidth;\n    int32_t preferredHeight;\n    int32_t preferredRefreshRate;\n    int32_t physicalWidth;\n    int32_t physicalHeight;\n    uint32_t dpi;\n    enum wl_output_transform transform;\n    FFDisplayType type;\n    FFstrbuf name;\n    FFstrbuf description;\n    FFstrbuf edidName;\n    uint64_t id;\n    bool hdrInfoAvailable;\n    bool hdrSupported;\n    bool hdrEnabled;\n    uint16_t myear;\n    uint16_t mweek;\n    uint32_t serial;\n    uint8_t bitDepth;\n} WaylandDisplay;\n\ninline static void stubListener(void* data, ...)\n{\n    (void) data;\n}\n\ninline static uint64_t ffWaylandGenerateIdFromName(const char* name)\n{\n    uint64_t id = 0;\n    size_t len = strlen(name);\n    if (len > sizeof(id))\n        memcpy(&id, name + (len - sizeof(id)), sizeof(id)); // copy the last 8 bytes\n    else if (len > 0)\n        memcpy(&id, name, len);\n    return id;\n}\n\nvoid ffWaylandOutputNameListener(void* data, FF_MAYBE_UNUSED void* output, const char *name);\nvoid ffWaylandOutputDescriptionListener(void* data, FF_MAYBE_UNUSED void* output, const char* description);\n// Modifies content of display. Don't call this function when calling ffdsAppendDisplay\nuint32_t ffWaylandHandleRotation(WaylandDisplay* display);\n\nconst char* ffWaylandHandleGlobalOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version);\nconst char* ffWaylandHandleZwlrOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version);\nconst char* ffWaylandHandleKdeOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version);\nconst char* ffWaylandHandleKdeOutputOrder(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version);\nconst char* ffWaylandHandleZxdgOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version);\n\n#endif\n"
  },
  {
    "path": "src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-client-protocol.h",
    "content": "/* Generated by wayland-scanner 1.24.0 */\n\n#ifndef WLR_OUTPUT_MANAGEMENT_UNSTABLE_V1_CLIENT_PROTOCOL_H\n#define WLR_OUTPUT_MANAGEMENT_UNSTABLE_V1_CLIENT_PROTOCOL_H\n\n#include <stdint.h>\n#include <stddef.h>\n#include <wayland-client.h>\n\n#ifdef  __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * @page page_wlr_output_management_unstable_v1 The wlr_output_management_unstable_v1 protocol\n * protocol to configure output devices\n *\n * @section page_desc_wlr_output_management_unstable_v1 Description\n *\n * This protocol exposes interfaces to obtain and modify output device\n * configuration.\n *\n * Warning! The protocol described in this file is experimental and\n * backward incompatible changes may be made. Backward compatible changes\n * may be added together with the corresponding interface version bump.\n * Backward incompatible changes are done by bumping the version number in\n * the protocol and interface names and resetting the interface version.\n * Once the protocol is to be declared stable, the 'z' prefix and the\n * version number in the protocol and interface names are removed and the\n * interface version number is reset.\n *\n * @section page_ifaces_wlr_output_management_unstable_v1 Interfaces\n * - @subpage page_iface_zwlr_output_manager_v1 - output device configuration manager\n * - @subpage page_iface_zwlr_output_head_v1 - output device\n * - @subpage page_iface_zwlr_output_mode_v1 - output mode\n * - @subpage page_iface_zwlr_output_configuration_v1 - output configuration\n * - @subpage page_iface_zwlr_output_configuration_head_v1 - head configuration\n * @section page_copyright_wlr_output_management_unstable_v1 Copyright\n * <pre>\n *\n * Copyright © 2019 Purism SPC\n *\n * Permission to use, copy, modify, distribute, and sell this\n * software and its documentation for any purpose is hereby granted\n * without fee, provided that the above copyright notice appear in\n * all copies and that both that copyright notice and this permission\n * notice appear in supporting documentation, and that the name of\n * the copyright holders not be used in advertising or publicity\n * pertaining to distribution of the software without specific,\n * written prior permission.  The copyright holders make no\n * representations about the suitability of this software for any\n * purpose.  It is provided \"as is\" without express or implied\n * warranty.\n *\n * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS\n * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY\n * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN\n * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\n * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n * THIS SOFTWARE.\n * </pre>\n */\nstruct zwlr_output_configuration_head_v1;\nstruct zwlr_output_configuration_v1;\nstruct zwlr_output_head_v1;\nstruct zwlr_output_manager_v1;\nstruct zwlr_output_mode_v1;\n\n#ifndef ZWLR_OUTPUT_MANAGER_V1_INTERFACE\n#define ZWLR_OUTPUT_MANAGER_V1_INTERFACE\n/**\n * @page page_iface_zwlr_output_manager_v1 zwlr_output_manager_v1\n * @section page_iface_zwlr_output_manager_v1_desc Description\n *\n * This interface is a manager that allows reading and writing the current\n * output device configuration.\n *\n * Output devices that display pixels (e.g. a physical monitor or a virtual\n * output in a window) are represented as heads. Heads cannot be created nor\n * destroyed by the client, but they can be enabled or disabled and their\n * properties can be changed. Each head may have one or more available modes.\n *\n * Whenever a head appears (e.g. a monitor is plugged in), it will be\n * advertised via the head event. Immediately after the output manager is\n * bound, all current heads are advertised.\n *\n * Whenever a head's properties change, the relevant wlr_output_head events\n * will be sent. Not all head properties will be sent: only properties that\n * have changed need to.\n *\n * Whenever a head disappears (e.g. a monitor is unplugged), a\n * wlr_output_head.finished event will be sent.\n *\n * After one or more heads appear, change or disappear, the done event will\n * be sent. It carries a serial which can be used in a create_configuration\n * request to update heads properties.\n *\n * The information obtained from this protocol should only be used for output\n * configuration purposes. This protocol is not designed to be a generic\n * output property advertisement protocol for regular clients. Instead,\n * protocols such as xdg-output should be used.\n * @section page_iface_zwlr_output_manager_v1_api API\n * See @ref iface_zwlr_output_manager_v1.\n */\n/**\n * @defgroup iface_zwlr_output_manager_v1 The zwlr_output_manager_v1 interface\n *\n * This interface is a manager that allows reading and writing the current\n * output device configuration.\n *\n * Output devices that display pixels (e.g. a physical monitor or a virtual\n * output in a window) are represented as heads. Heads cannot be created nor\n * destroyed by the client, but they can be enabled or disabled and their\n * properties can be changed. Each head may have one or more available modes.\n *\n * Whenever a head appears (e.g. a monitor is plugged in), it will be\n * advertised via the head event. Immediately after the output manager is\n * bound, all current heads are advertised.\n *\n * Whenever a head's properties change, the relevant wlr_output_head events\n * will be sent. Not all head properties will be sent: only properties that\n * have changed need to.\n *\n * Whenever a head disappears (e.g. a monitor is unplugged), a\n * wlr_output_head.finished event will be sent.\n *\n * After one or more heads appear, change or disappear, the done event will\n * be sent. It carries a serial which can be used in a create_configuration\n * request to update heads properties.\n *\n * The information obtained from this protocol should only be used for output\n * configuration purposes. This protocol is not designed to be a generic\n * output property advertisement protocol for regular clients. Instead,\n * protocols such as xdg-output should be used.\n */\nextern const struct wl_interface zwlr_output_manager_v1_interface;\n#endif\n#ifndef ZWLR_OUTPUT_HEAD_V1_INTERFACE\n#define ZWLR_OUTPUT_HEAD_V1_INTERFACE\n/**\n * @page page_iface_zwlr_output_head_v1 zwlr_output_head_v1\n * @section page_iface_zwlr_output_head_v1_desc Description\n *\n * A head is an output device. The difference between a wl_output object and\n * a head is that heads are advertised even if they are turned off. A head\n * object only advertises properties and cannot be used directly to change\n * them.\n *\n * A head has some read-only properties: modes, name, description and\n * physical_size. These cannot be changed by clients.\n *\n * Other properties can be updated via a wlr_output_configuration object.\n *\n * Properties sent via this interface are applied atomically via the\n * wlr_output_manager.done event. No guarantees are made regarding the order\n * in which properties are sent.\n * @section page_iface_zwlr_output_head_v1_api API\n * See @ref iface_zwlr_output_head_v1.\n */\n/**\n * @defgroup iface_zwlr_output_head_v1 The zwlr_output_head_v1 interface\n *\n * A head is an output device. The difference between a wl_output object and\n * a head is that heads are advertised even if they are turned off. A head\n * object only advertises properties and cannot be used directly to change\n * them.\n *\n * A head has some read-only properties: modes, name, description and\n * physical_size. These cannot be changed by clients.\n *\n * Other properties can be updated via a wlr_output_configuration object.\n *\n * Properties sent via this interface are applied atomically via the\n * wlr_output_manager.done event. No guarantees are made regarding the order\n * in which properties are sent.\n */\nextern const struct wl_interface zwlr_output_head_v1_interface;\n#endif\n#ifndef ZWLR_OUTPUT_MODE_V1_INTERFACE\n#define ZWLR_OUTPUT_MODE_V1_INTERFACE\n/**\n * @page page_iface_zwlr_output_mode_v1 zwlr_output_mode_v1\n * @section page_iface_zwlr_output_mode_v1_desc Description\n *\n * This object describes an output mode.\n *\n * Some heads don't support output modes, in which case modes won't be\n * advertised.\n *\n * Properties sent via this interface are applied atomically via the\n * wlr_output_manager.done event. No guarantees are made regarding the order\n * in which properties are sent.\n * @section page_iface_zwlr_output_mode_v1_api API\n * See @ref iface_zwlr_output_mode_v1.\n */\n/**\n * @defgroup iface_zwlr_output_mode_v1 The zwlr_output_mode_v1 interface\n *\n * This object describes an output mode.\n *\n * Some heads don't support output modes, in which case modes won't be\n * advertised.\n *\n * Properties sent via this interface are applied atomically via the\n * wlr_output_manager.done event. No guarantees are made regarding the order\n * in which properties are sent.\n */\nextern const struct wl_interface zwlr_output_mode_v1_interface;\n#endif\n#ifndef ZWLR_OUTPUT_CONFIGURATION_V1_INTERFACE\n#define ZWLR_OUTPUT_CONFIGURATION_V1_INTERFACE\n/**\n * @page page_iface_zwlr_output_configuration_v1 zwlr_output_configuration_v1\n * @section page_iface_zwlr_output_configuration_v1_desc Description\n *\n * This object is used by the client to describe a full output configuration.\n *\n * First, the client needs to setup the output configuration. Each head can\n * be either enabled (and configured) or disabled. It is a protocol error to\n * send two enable_head or disable_head requests with the same head. It is a\n * protocol error to omit a head in a configuration.\n *\n * Then, the client can apply or test the configuration. The compositor will\n * then reply with a succeeded, failed or cancelled event. Finally the client\n * should destroy the configuration object.\n * @section page_iface_zwlr_output_configuration_v1_api API\n * See @ref iface_zwlr_output_configuration_v1.\n */\n/**\n * @defgroup iface_zwlr_output_configuration_v1 The zwlr_output_configuration_v1 interface\n *\n * This object is used by the client to describe a full output configuration.\n *\n * First, the client needs to setup the output configuration. Each head can\n * be either enabled (and configured) or disabled. It is a protocol error to\n * send two enable_head or disable_head requests with the same head. It is a\n * protocol error to omit a head in a configuration.\n *\n * Then, the client can apply or test the configuration. The compositor will\n * then reply with a succeeded, failed or cancelled event. Finally the client\n * should destroy the configuration object.\n */\nextern const struct wl_interface zwlr_output_configuration_v1_interface;\n#endif\n#ifndef ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_INTERFACE\n#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_INTERFACE\n/**\n * @page page_iface_zwlr_output_configuration_head_v1 zwlr_output_configuration_head_v1\n * @section page_iface_zwlr_output_configuration_head_v1_desc Description\n *\n * This object is used by the client to update a single head's configuration.\n *\n * It is a protocol error to set the same property twice.\n * @section page_iface_zwlr_output_configuration_head_v1_api API\n * See @ref iface_zwlr_output_configuration_head_v1.\n */\n/**\n * @defgroup iface_zwlr_output_configuration_head_v1 The zwlr_output_configuration_head_v1 interface\n *\n * This object is used by the client to update a single head's configuration.\n *\n * It is a protocol error to set the same property twice.\n */\nextern const struct wl_interface zwlr_output_configuration_head_v1_interface;\n#endif\n\n/**\n * @ingroup iface_zwlr_output_manager_v1\n * @struct zwlr_output_manager_v1_listener\n */\nstruct zwlr_output_manager_v1_listener {\n\t/**\n\t * introduce a new head\n\t *\n\t * This event introduces a new head. This happens whenever a new\n\t * head appears (e.g. a monitor is plugged in) or after the output\n\t * manager is bound.\n\t */\n\tvoid (*head)(void *data,\n\t\t     struct zwlr_output_manager_v1 *zwlr_output_manager_v1,\n\t\t     struct zwlr_output_head_v1 *head);\n\t/**\n\t * sent all information about current configuration\n\t *\n\t * This event is sent after all information has been sent after\n\t * binding to the output manager object and after any subsequent\n\t * changes. This applies to child head and mode objects as well. In\n\t * other words, this event is sent whenever a head or mode is\n\t * created or destroyed and whenever one of their properties has\n\t * been changed. Not all state is re-sent each time the current\n\t * configuration changes: only the actual changes are sent.\n\t *\n\t * This allows changes to the output configuration to be seen as\n\t * atomic, even if they happen via multiple events.\n\t *\n\t * A serial is sent to be used in a future create_configuration\n\t * request.\n\t * @param serial current configuration serial\n\t */\n\tvoid (*done)(void *data,\n\t\t     struct zwlr_output_manager_v1 *zwlr_output_manager_v1,\n\t\t     uint32_t serial);\n\t/**\n\t * the compositor has finished with the manager\n\t *\n\t * This event indicates that the compositor is done sending\n\t * manager events. The compositor will destroy the object\n\t * immediately after sending this event, so it will become invalid\n\t * and the client should release any resources associated with it.\n\t */\n\tvoid (*finished)(void *data,\n\t\t\t struct zwlr_output_manager_v1 *zwlr_output_manager_v1);\n};\n\n/**\n * @ingroup iface_zwlr_output_manager_v1\n */\nstatic inline int\nzwlr_output_manager_v1_add_listener(struct zwlr_output_manager_v1 *zwlr_output_manager_v1,\n\t\t\t\t    const struct zwlr_output_manager_v1_listener *listener, void *data)\n{\n\treturn wl_proxy_add_listener((struct wl_proxy *) zwlr_output_manager_v1,\n\t\t\t\t     (void (**)(void)) listener, data);\n}\n\n#define ZWLR_OUTPUT_MANAGER_V1_CREATE_CONFIGURATION 0\n#define ZWLR_OUTPUT_MANAGER_V1_STOP 1\n\n/**\n * @ingroup iface_zwlr_output_manager_v1\n */\n#define ZWLR_OUTPUT_MANAGER_V1_HEAD_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_manager_v1\n */\n#define ZWLR_OUTPUT_MANAGER_V1_DONE_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_manager_v1\n */\n#define ZWLR_OUTPUT_MANAGER_V1_FINISHED_SINCE_VERSION 1\n\n/**\n * @ingroup iface_zwlr_output_manager_v1\n */\n#define ZWLR_OUTPUT_MANAGER_V1_CREATE_CONFIGURATION_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_manager_v1\n */\n#define ZWLR_OUTPUT_MANAGER_V1_STOP_SINCE_VERSION 1\n\n/** @ingroup iface_zwlr_output_manager_v1 */\nstatic inline void\nzwlr_output_manager_v1_set_user_data(struct zwlr_output_manager_v1 *zwlr_output_manager_v1, void *user_data)\n{\n\twl_proxy_set_user_data((struct wl_proxy *) zwlr_output_manager_v1, user_data);\n}\n\n/** @ingroup iface_zwlr_output_manager_v1 */\nstatic inline void *\nzwlr_output_manager_v1_get_user_data(struct zwlr_output_manager_v1 *zwlr_output_manager_v1)\n{\n\treturn wl_proxy_get_user_data((struct wl_proxy *) zwlr_output_manager_v1);\n}\n\nstatic inline uint32_t\nzwlr_output_manager_v1_get_version(struct zwlr_output_manager_v1 *zwlr_output_manager_v1)\n{\n\treturn wl_proxy_get_version((struct wl_proxy *) zwlr_output_manager_v1);\n}\n\n/** @ingroup iface_zwlr_output_manager_v1 */\nstatic inline void\nzwlr_output_manager_v1_destroy(struct zwlr_output_manager_v1 *zwlr_output_manager_v1)\n{\n\twl_proxy_destroy((struct wl_proxy *) zwlr_output_manager_v1);\n}\n\n// /**\n//  * @ingroup iface_zwlr_output_manager_v1\n//  *\n//  * Create a new output configuration object. This allows to update head\n//  * properties.\n//  */\n// static inline struct zwlr_output_configuration_v1 *\n// zwlr_output_manager_v1_create_configuration(struct zwlr_output_manager_v1 *zwlr_output_manager_v1, uint32_t serial)\n// {\n// \tstruct wl_proxy *id;\n\n// \tid = wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_manager_v1,\n// \t\t\t ZWLR_OUTPUT_MANAGER_V1_CREATE_CONFIGURATION, &zwlr_output_configuration_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwlr_output_manager_v1), 0, NULL, serial);\n\n// \treturn (struct zwlr_output_configuration_v1 *) id;\n// }\n\n// /**\n//  * @ingroup iface_zwlr_output_manager_v1\n//  *\n//  * Indicates the client no longer wishes to receive events for output\n//  * configuration changes. However the compositor may emit further events,\n//  * until the finished event is emitted.\n//  *\n//  * The client must not send any more requests after this one.\n//  */\n// static inline void\n// zwlr_output_manager_v1_stop(struct zwlr_output_manager_v1 *zwlr_output_manager_v1)\n// {\n// \twl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_manager_v1,\n// \t\t\t ZWLR_OUTPUT_MANAGER_V1_STOP, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_manager_v1), 0);\n// }\n\n#ifndef ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_ENUM\n#define ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_ENUM\nenum zwlr_output_head_v1_adaptive_sync_state {\n\t/**\n\t * adaptive sync is disabled\n\t */\n\tZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_DISABLED = 0,\n\t/**\n\t * adaptive sync is enabled\n\t */\n\tZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_ENABLED = 1,\n};\n#endif /* ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_ENUM */\n\n/**\n * @ingroup iface_zwlr_output_head_v1\n * @struct zwlr_output_head_v1_listener\n */\nstruct zwlr_output_head_v1_listener {\n\t/**\n\t * head name\n\t *\n\t * This event describes the head name.\n\t *\n\t * The naming convention is compositor defined, but limited to\n\t * alphanumeric characters and dashes (-). Each name is unique\n\t * among all wlr_output_head objects, but if a wlr_output_head\n\t * object is destroyed the same name may be reused later. The names\n\t * will also remain consistent across sessions with the same\n\t * hardware and software configuration.\n\t *\n\t * Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc.\n\t * However, do not assume that the name is a reflection of an\n\t * underlying DRM connector, X11 connection, etc.\n\t *\n\t * If this head matches a wl_output, the wl_output.name event must\n\t * report the same name.\n\t *\n\t * The name event is sent after a wlr_output_head object is\n\t * created. This event is only sent once per object, and the name\n\t * does not change over the lifetime of the wlr_output_head object.\n\t */\n\tvoid (*name)(void *data,\n\t\t     struct zwlr_output_head_v1 *zwlr_output_head_v1,\n\t\t     const char *name);\n\t/**\n\t * head description\n\t *\n\t * This event describes a human-readable description of the head.\n\t *\n\t * The description is a UTF-8 string with no convention defined for\n\t * its contents. Examples might include 'Foocorp 11\" Display' or\n\t * 'Virtual X11 output via :1'. However, do not assume that the\n\t * name is a reflection of the make, model, serial of the\n\t * underlying DRM connector or the display name of the underlying\n\t * X11 connection, etc.\n\t *\n\t * If this head matches a wl_output, the wl_output.description\n\t * event must report the same name.\n\t *\n\t * The description event is sent after a wlr_output_head object is\n\t * created. This event is only sent once per object, and the\n\t * description does not change over the lifetime of the\n\t * wlr_output_head object.\n\t */\n\tvoid (*description)(void *data,\n\t\t\t    struct zwlr_output_head_v1 *zwlr_output_head_v1,\n\t\t\t    const char *description);\n\t/**\n\t * head physical size\n\t *\n\t * This event describes the physical size of the head. This event\n\t * is only sent if the head has a physical size (e.g. is not a\n\t * projector or a virtual device).\n\t *\n\t * The physical size event is sent after a wlr_output_head object\n\t * is created. This event is only sent once per object, and the\n\t * physical size does not change over the lifetime of the\n\t * wlr_output_head object.\n\t * @param width width in millimeters of the output\n\t * @param height height in millimeters of the output\n\t */\n\tvoid (*physical_size)(void *data,\n\t\t\t      struct zwlr_output_head_v1 *zwlr_output_head_v1,\n\t\t\t      int32_t width,\n\t\t\t      int32_t height);\n\t/**\n\t * introduce a mode\n\t *\n\t * This event introduces a mode for this head. It is sent once\n\t * per supported mode.\n\t */\n\tvoid (*mode)(void *data,\n\t\t     struct zwlr_output_head_v1 *zwlr_output_head_v1,\n\t\t     struct zwlr_output_mode_v1 *mode);\n\t/**\n\t * head is enabled or disabled\n\t *\n\t * This event describes whether the head is enabled. A disabled\n\t * head is not mapped to a region of the global compositor space.\n\t *\n\t * When a head is disabled, some properties (current_mode,\n\t * position, transform and scale) are irrelevant.\n\t * @param enabled zero if disabled, non-zero if enabled\n\t */\n\tvoid (*enabled)(void *data,\n\t\t\tstruct zwlr_output_head_v1 *zwlr_output_head_v1,\n\t\t\tint32_t enabled);\n\t/**\n\t * current mode\n\t *\n\t * This event describes the mode currently in use for this head.\n\t * It is only sent if the output is enabled.\n\t */\n\tvoid (*current_mode)(void *data,\n\t\t\t     struct zwlr_output_head_v1 *zwlr_output_head_v1,\n\t\t\t     struct zwlr_output_mode_v1 *mode);\n\t/**\n\t * current position\n\t *\n\t * This events describes the position of the head in the global\n\t * compositor space. It is only sent if the output is enabled.\n\t * @param x x position within the global compositor space\n\t * @param y y position within the global compositor space\n\t */\n\tvoid (*position)(void *data,\n\t\t\t struct zwlr_output_head_v1 *zwlr_output_head_v1,\n\t\t\t int32_t x,\n\t\t\t int32_t y);\n\t/**\n\t * current transformation\n\t *\n\t * This event describes the transformation currently applied to\n\t * the head. It is only sent if the output is enabled.\n\t */\n\tvoid (*transform)(void *data,\n\t\t\t  struct zwlr_output_head_v1 *zwlr_output_head_v1,\n\t\t\t  int32_t transform);\n\t/**\n\t * current scale\n\t *\n\t * This events describes the scale of the head in the global\n\t * compositor space. It is only sent if the output is enabled.\n\t */\n\tvoid (*scale)(void *data,\n\t\t      struct zwlr_output_head_v1 *zwlr_output_head_v1,\n\t\t      wl_fixed_t scale);\n\t/**\n\t * the head has disappeared\n\t *\n\t * This event indicates that the head is no longer available. The\n\t * head object becomes inert. Clients should send a destroy request\n\t * and release any resources associated with it.\n\t */\n\tvoid (*finished)(void *data,\n\t\t\t struct zwlr_output_head_v1 *zwlr_output_head_v1);\n\t/**\n\t * head manufacturer\n\t *\n\t * This event describes the manufacturer of the head.\n\t *\n\t * Together with the model and serial_number events the purpose is\n\t * to allow clients to recognize heads from previous sessions and\n\t * for example load head-specific configurations back.\n\t *\n\t * It is not guaranteed this event will be ever sent. A reason for\n\t * that can be that the compositor does not have information about\n\t * the make of the head or the definition of a make is not sensible\n\t * in the current setup, for example in a virtual session. Clients\n\t * can still try to identify the head by available information from\n\t * other events but should be aware that there is an increased risk\n\t * of false positives.\n\t *\n\t * If sent, the make event is sent after a wlr_output_head object\n\t * is created and only sent once per object. The make does not\n\t * change over the lifetime of the wlr_output_head object.\n\t *\n\t * It is not recommended to display the make string in UI to users.\n\t * For that the string provided by the description event should be\n\t * preferred.\n\t * @since 2\n\t */\n\tvoid (*make)(void *data,\n\t\t     struct zwlr_output_head_v1 *zwlr_output_head_v1,\n\t\t     const char *make);\n\t/**\n\t * head model\n\t *\n\t * This event describes the model of the head.\n\t *\n\t * Together with the make and serial_number events the purpose is\n\t * to allow clients to recognize heads from previous sessions and\n\t * for example load head-specific configurations back.\n\t *\n\t * It is not guaranteed this event will be ever sent. A reason for\n\t * that can be that the compositor does not have information about\n\t * the model of the head or the definition of a model is not\n\t * sensible in the current setup, for example in a virtual session.\n\t * Clients can still try to identify the head by available\n\t * information from other events but should be aware that there is\n\t * an increased risk of false positives.\n\t *\n\t * If sent, the model event is sent after a wlr_output_head object\n\t * is created and only sent once per object. The model does not\n\t * change over the lifetime of the wlr_output_head object.\n\t *\n\t * It is not recommended to display the model string in UI to\n\t * users. For that the string provided by the description event\n\t * should be preferred.\n\t * @since 2\n\t */\n\tvoid (*model)(void *data,\n\t\t      struct zwlr_output_head_v1 *zwlr_output_head_v1,\n\t\t      const char *model);\n\t/**\n\t * head serial number\n\t *\n\t * This event describes the serial number of the head.\n\t *\n\t * Together with the make and model events the purpose is to allow\n\t * clients to recognize heads from previous sessions and for\n\t * example load head- specific configurations back.\n\t *\n\t * It is not guaranteed this event will be ever sent. A reason for\n\t * that can be that the compositor does not have information about\n\t * the serial number of the head or the definition of a serial\n\t * number is not sensible in the current setup. Clients can still\n\t * try to identify the head by available information from other\n\t * events but should be aware that there is an increased risk of\n\t * false positives.\n\t *\n\t * If sent, the serial number event is sent after a wlr_output_head\n\t * object is created and only sent once per object. The serial\n\t * number does not change over the lifetime of the wlr_output_head\n\t * object.\n\t *\n\t * It is not recommended to display the serial_number string in UI\n\t * to users. For that the string provided by the description event\n\t * should be preferred.\n\t * @since 2\n\t */\n\tvoid (*serial_number)(void *data,\n\t\t\t      struct zwlr_output_head_v1 *zwlr_output_head_v1,\n\t\t\t      const char *serial_number);\n\t/**\n\t * current adaptive sync state\n\t *\n\t * This event describes whether adaptive sync is currently\n\t * enabled for the head or not. Adaptive sync is also known as\n\t * Variable Refresh Rate or VRR.\n\t * @since 4\n\t */\n\tvoid (*adaptive_sync)(void *data,\n\t\t\t      struct zwlr_output_head_v1 *zwlr_output_head_v1,\n\t\t\t      uint32_t state);\n};\n\n/**\n * @ingroup iface_zwlr_output_head_v1\n */\nstatic inline int\nzwlr_output_head_v1_add_listener(struct zwlr_output_head_v1 *zwlr_output_head_v1,\n\t\t\t\t const struct zwlr_output_head_v1_listener *listener, void *data)\n{\n\treturn wl_proxy_add_listener((struct wl_proxy *) zwlr_output_head_v1,\n\t\t\t\t     (void (**)(void)) listener, data);\n}\n\n#define ZWLR_OUTPUT_HEAD_V1_RELEASE 0\n\n/**\n * @ingroup iface_zwlr_output_head_v1\n */\n#define ZWLR_OUTPUT_HEAD_V1_NAME_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_head_v1\n */\n#define ZWLR_OUTPUT_HEAD_V1_DESCRIPTION_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_head_v1\n */\n#define ZWLR_OUTPUT_HEAD_V1_PHYSICAL_SIZE_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_head_v1\n */\n#define ZWLR_OUTPUT_HEAD_V1_MODE_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_head_v1\n */\n#define ZWLR_OUTPUT_HEAD_V1_ENABLED_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_head_v1\n */\n#define ZWLR_OUTPUT_HEAD_V1_CURRENT_MODE_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_head_v1\n */\n#define ZWLR_OUTPUT_HEAD_V1_POSITION_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_head_v1\n */\n#define ZWLR_OUTPUT_HEAD_V1_TRANSFORM_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_head_v1\n */\n#define ZWLR_OUTPUT_HEAD_V1_SCALE_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_head_v1\n */\n#define ZWLR_OUTPUT_HEAD_V1_FINISHED_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_head_v1\n */\n#define ZWLR_OUTPUT_HEAD_V1_MAKE_SINCE_VERSION 2\n/**\n * @ingroup iface_zwlr_output_head_v1\n */\n#define ZWLR_OUTPUT_HEAD_V1_MODEL_SINCE_VERSION 2\n/**\n * @ingroup iface_zwlr_output_head_v1\n */\n#define ZWLR_OUTPUT_HEAD_V1_SERIAL_NUMBER_SINCE_VERSION 2\n/**\n * @ingroup iface_zwlr_output_head_v1\n */\n#define ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_SINCE_VERSION 4\n\n/**\n * @ingroup iface_zwlr_output_head_v1\n */\n#define ZWLR_OUTPUT_HEAD_V1_RELEASE_SINCE_VERSION 3\n\n/** @ingroup iface_zwlr_output_head_v1 */\nstatic inline void\nzwlr_output_head_v1_set_user_data(struct zwlr_output_head_v1 *zwlr_output_head_v1, void *user_data)\n{\n\twl_proxy_set_user_data((struct wl_proxy *) zwlr_output_head_v1, user_data);\n}\n\n/** @ingroup iface_zwlr_output_head_v1 */\nstatic inline void *\nzwlr_output_head_v1_get_user_data(struct zwlr_output_head_v1 *zwlr_output_head_v1)\n{\n\treturn wl_proxy_get_user_data((struct wl_proxy *) zwlr_output_head_v1);\n}\n\nstatic inline uint32_t\nzwlr_output_head_v1_get_version(struct zwlr_output_head_v1 *zwlr_output_head_v1)\n{\n\treturn wl_proxy_get_version((struct wl_proxy *) zwlr_output_head_v1);\n}\n\n/** @ingroup iface_zwlr_output_head_v1 */\nstatic inline void\nzwlr_output_head_v1_destroy(struct zwlr_output_head_v1 *zwlr_output_head_v1)\n{\n\twl_proxy_destroy((struct wl_proxy *) zwlr_output_head_v1);\n}\n\n// /**\n//  * @ingroup iface_zwlr_output_head_v1\n//  *\n//  * This request indicates that the client will no longer use this head\n//  * object.\n//  */\n// static inline void\n// zwlr_output_head_v1_release(struct zwlr_output_head_v1 *zwlr_output_head_v1)\n// {\n// \twl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_head_v1,\n// \t\t\t ZWLR_OUTPUT_HEAD_V1_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_head_v1), WL_MARSHAL_FLAG_DESTROY);\n// }\n\n/**\n * @ingroup iface_zwlr_output_mode_v1\n * @struct zwlr_output_mode_v1_listener\n */\nstruct zwlr_output_mode_v1_listener {\n\t/**\n\t * mode size\n\t *\n\t * This event describes the mode size. The size is given in\n\t * physical hardware units of the output device. This is not\n\t * necessarily the same as the output size in the global compositor\n\t * space. For instance, the output may be scaled or transformed.\n\t * @param width width of the mode in hardware units\n\t * @param height height of the mode in hardware units\n\t */\n\tvoid (*size)(void *data,\n\t\t     struct zwlr_output_mode_v1 *zwlr_output_mode_v1,\n\t\t     int32_t width,\n\t\t     int32_t height);\n\t/**\n\t * mode refresh rate\n\t *\n\t * This event describes the mode's fixed vertical refresh rate.\n\t * It is only sent if the mode has a fixed refresh rate.\n\t * @param refresh vertical refresh rate in mHz\n\t */\n\tvoid (*refresh)(void *data,\n\t\t\tstruct zwlr_output_mode_v1 *zwlr_output_mode_v1,\n\t\t\tint32_t refresh);\n\t/**\n\t * mode is preferred\n\t *\n\t * This event advertises this mode as preferred.\n\t */\n\tvoid (*preferred)(void *data,\n\t\t\t  struct zwlr_output_mode_v1 *zwlr_output_mode_v1);\n\t/**\n\t * the mode has disappeared\n\t *\n\t * This event indicates that the mode is no longer available. The\n\t * mode object becomes inert. Clients should send a destroy request\n\t * and release any resources associated with it.\n\t */\n\tvoid (*finished)(void *data,\n\t\t\t struct zwlr_output_mode_v1 *zwlr_output_mode_v1);\n};\n\n/**\n * @ingroup iface_zwlr_output_mode_v1\n */\nstatic inline int\nzwlr_output_mode_v1_add_listener(struct zwlr_output_mode_v1 *zwlr_output_mode_v1,\n\t\t\t\t const struct zwlr_output_mode_v1_listener *listener, void *data)\n{\n\treturn wl_proxy_add_listener((struct wl_proxy *) zwlr_output_mode_v1,\n\t\t\t\t     (void (**)(void)) listener, data);\n}\n\n#define ZWLR_OUTPUT_MODE_V1_RELEASE 0\n\n/**\n * @ingroup iface_zwlr_output_mode_v1\n */\n#define ZWLR_OUTPUT_MODE_V1_SIZE_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_mode_v1\n */\n#define ZWLR_OUTPUT_MODE_V1_REFRESH_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_mode_v1\n */\n#define ZWLR_OUTPUT_MODE_V1_PREFERRED_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_mode_v1\n */\n#define ZWLR_OUTPUT_MODE_V1_FINISHED_SINCE_VERSION 1\n\n/**\n * @ingroup iface_zwlr_output_mode_v1\n */\n#define ZWLR_OUTPUT_MODE_V1_RELEASE_SINCE_VERSION 3\n\n/** @ingroup iface_zwlr_output_mode_v1 */\nstatic inline void\nzwlr_output_mode_v1_set_user_data(struct zwlr_output_mode_v1 *zwlr_output_mode_v1, void *user_data)\n{\n\twl_proxy_set_user_data((struct wl_proxy *) zwlr_output_mode_v1, user_data);\n}\n\n/** @ingroup iface_zwlr_output_mode_v1 */\nstatic inline void *\nzwlr_output_mode_v1_get_user_data(struct zwlr_output_mode_v1 *zwlr_output_mode_v1)\n{\n\treturn wl_proxy_get_user_data((struct wl_proxy *) zwlr_output_mode_v1);\n}\n\nstatic inline uint32_t\nzwlr_output_mode_v1_get_version(struct zwlr_output_mode_v1 *zwlr_output_mode_v1)\n{\n\treturn wl_proxy_get_version((struct wl_proxy *) zwlr_output_mode_v1);\n}\n\n/** @ingroup iface_zwlr_output_mode_v1 */\nstatic inline void\nzwlr_output_mode_v1_destroy(struct zwlr_output_mode_v1 *zwlr_output_mode_v1)\n{\n\twl_proxy_destroy((struct wl_proxy *) zwlr_output_mode_v1);\n}\n\n// /**\n//  * @ingroup iface_zwlr_output_mode_v1\n//  *\n//  * This request indicates that the client will no longer use this mode\n//  * object.\n//  */\n// static inline void\n// zwlr_output_mode_v1_release(struct zwlr_output_mode_v1 *zwlr_output_mode_v1)\n// {\n// \twl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_mode_v1,\n// \t\t\t ZWLR_OUTPUT_MODE_V1_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_mode_v1), WL_MARSHAL_FLAG_DESTROY);\n// }\n\n#ifndef ZWLR_OUTPUT_CONFIGURATION_V1_ERROR_ENUM\n#define ZWLR_OUTPUT_CONFIGURATION_V1_ERROR_ENUM\nenum zwlr_output_configuration_v1_error {\n\t/**\n\t * head has been configured twice\n\t */\n\tZWLR_OUTPUT_CONFIGURATION_V1_ERROR_ALREADY_CONFIGURED_HEAD = 1,\n\t/**\n\t * head has not been configured\n\t */\n\tZWLR_OUTPUT_CONFIGURATION_V1_ERROR_UNCONFIGURED_HEAD = 2,\n\t/**\n\t * request sent after configuration has been applied or tested\n\t */\n\tZWLR_OUTPUT_CONFIGURATION_V1_ERROR_ALREADY_USED = 3,\n};\n#endif /* ZWLR_OUTPUT_CONFIGURATION_V1_ERROR_ENUM */\n\n/**\n * @ingroup iface_zwlr_output_configuration_v1\n * @struct zwlr_output_configuration_v1_listener\n */\nstruct zwlr_output_configuration_v1_listener {\n\t/**\n\t * configuration changes succeeded\n\t *\n\t * Sent after the compositor has successfully applied the changes\n\t * or tested them.\n\t *\n\t * Upon receiving this event, the client should destroy this\n\t * object.\n\t *\n\t * If the current configuration has changed, events to describe the\n\t * changes will be sent followed by a wlr_output_manager.done\n\t * event.\n\t */\n\tvoid (*succeeded)(void *data,\n\t\t\t  struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1);\n\t/**\n\t * configuration changes failed\n\t *\n\t * Sent if the compositor rejects the changes or failed to apply\n\t * them. The compositor should revert any changes made by the apply\n\t * request that triggered this event.\n\t *\n\t * Upon receiving this event, the client should destroy this\n\t * object.\n\t */\n\tvoid (*failed)(void *data,\n\t\t       struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1);\n\t/**\n\t * configuration has been cancelled\n\t *\n\t * Sent if the compositor cancels the configuration because the\n\t * state of an output changed and the client has outdated\n\t * information (e.g. after an output has been hotplugged).\n\t *\n\t * The client can create a new configuration with a newer serial\n\t * and try again.\n\t *\n\t * Upon receiving this event, the client should destroy this\n\t * object.\n\t */\n\tvoid (*cancelled)(void *data,\n\t\t\t  struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1);\n};\n\n/**\n * @ingroup iface_zwlr_output_configuration_v1\n */\nstatic inline int\nzwlr_output_configuration_v1_add_listener(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1,\n\t\t\t\t\t  const struct zwlr_output_configuration_v1_listener *listener, void *data)\n{\n\treturn wl_proxy_add_listener((struct wl_proxy *) zwlr_output_configuration_v1,\n\t\t\t\t     (void (**)(void)) listener, data);\n}\n\n#define ZWLR_OUTPUT_CONFIGURATION_V1_ENABLE_HEAD 0\n#define ZWLR_OUTPUT_CONFIGURATION_V1_DISABLE_HEAD 1\n#define ZWLR_OUTPUT_CONFIGURATION_V1_APPLY 2\n#define ZWLR_OUTPUT_CONFIGURATION_V1_TEST 3\n#define ZWLR_OUTPUT_CONFIGURATION_V1_DESTROY 4\n\n/**\n * @ingroup iface_zwlr_output_configuration_v1\n */\n#define ZWLR_OUTPUT_CONFIGURATION_V1_SUCCEEDED_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_configuration_v1\n */\n#define ZWLR_OUTPUT_CONFIGURATION_V1_FAILED_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_configuration_v1\n */\n#define ZWLR_OUTPUT_CONFIGURATION_V1_CANCELLED_SINCE_VERSION 1\n\n/**\n * @ingroup iface_zwlr_output_configuration_v1\n */\n#define ZWLR_OUTPUT_CONFIGURATION_V1_ENABLE_HEAD_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_configuration_v1\n */\n#define ZWLR_OUTPUT_CONFIGURATION_V1_DISABLE_HEAD_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_configuration_v1\n */\n#define ZWLR_OUTPUT_CONFIGURATION_V1_APPLY_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_configuration_v1\n */\n#define ZWLR_OUTPUT_CONFIGURATION_V1_TEST_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_configuration_v1\n */\n#define ZWLR_OUTPUT_CONFIGURATION_V1_DESTROY_SINCE_VERSION 1\n\n/** @ingroup iface_zwlr_output_configuration_v1 */\nstatic inline void\nzwlr_output_configuration_v1_set_user_data(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1, void *user_data)\n{\n\twl_proxy_set_user_data((struct wl_proxy *) zwlr_output_configuration_v1, user_data);\n}\n\n/** @ingroup iface_zwlr_output_configuration_v1 */\nstatic inline void *\nzwlr_output_configuration_v1_get_user_data(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1)\n{\n\treturn wl_proxy_get_user_data((struct wl_proxy *) zwlr_output_configuration_v1);\n}\n\nstatic inline uint32_t\nzwlr_output_configuration_v1_get_version(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1)\n{\n\treturn wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_v1);\n}\n\n// /**\n//  * @ingroup iface_zwlr_output_configuration_v1\n//  *\n//  * Enable a head. This request creates a head configuration object that can\n//  * be used to change the head's properties.\n//  */\n// static inline struct zwlr_output_configuration_head_v1 *\n// zwlr_output_configuration_v1_enable_head(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1, struct zwlr_output_head_v1 *head)\n// {\n// \tstruct wl_proxy *id;\n\n// \tid = wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_v1,\n// \t\t\t ZWLR_OUTPUT_CONFIGURATION_V1_ENABLE_HEAD, &zwlr_output_configuration_head_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_v1), 0, NULL, head);\n\n// \treturn (struct zwlr_output_configuration_head_v1 *) id;\n// }\n\n// /**\n//  * @ingroup iface_zwlr_output_configuration_v1\n//  *\n//  * Disable a head.\n//  */\n// static inline void\n// zwlr_output_configuration_v1_disable_head(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1, struct zwlr_output_head_v1 *head)\n// {\n// \twl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_v1,\n// \t\t\t ZWLR_OUTPUT_CONFIGURATION_V1_DISABLE_HEAD, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_v1), 0, head);\n// }\n\n// /**\n//  * @ingroup iface_zwlr_output_configuration_v1\n//  *\n//  * Apply the new output configuration.\n//  *\n//  * In case the configuration is successfully applied, there is no guarantee\n//  * that the new output state matches completely the requested\n//  * configuration. For instance, a compositor might round the scale if it\n//  * doesn't support fractional scaling.\n//  *\n//  * After this request has been sent, the compositor must respond with an\n//  * succeeded, failed or cancelled event. Sending a request that isn't the\n//  * destructor is a protocol error.\n//  */\n// static inline void\n// zwlr_output_configuration_v1_apply(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1)\n// {\n// \twl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_v1,\n// \t\t\t ZWLR_OUTPUT_CONFIGURATION_V1_APPLY, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_v1), 0);\n// }\n\n// /**\n//  * @ingroup iface_zwlr_output_configuration_v1\n//  *\n//  * Test the new output configuration. The configuration won't be applied,\n//  * but will only be validated.\n//  *\n//  * Even if the compositor succeeds to test a configuration, applying it may\n//  * fail.\n//  *\n//  * After this request has been sent, the compositor must respond with an\n//  * succeeded, failed or cancelled event. Sending a request that isn't the\n//  * destructor is a protocol error.\n//  */\n// static inline void\n// zwlr_output_configuration_v1_test(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1)\n// {\n// \twl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_v1,\n// \t\t\t ZWLR_OUTPUT_CONFIGURATION_V1_TEST, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_v1), 0);\n// }\n\n// /**\n//  * @ingroup iface_zwlr_output_configuration_v1\n//  *\n//  * Using this request a client can tell the compositor that it is not going\n//  * to use the configuration object anymore. Any changes to the outputs\n//  * that have not been applied will be discarded.\n//  *\n//  * This request also destroys wlr_output_configuration_head objects created\n//  * via this object.\n//  */\n// static inline void\n// zwlr_output_configuration_v1_destroy(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1)\n// {\n// \twl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_v1,\n// \t\t\t ZWLR_OUTPUT_CONFIGURATION_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_v1), WL_MARSHAL_FLAG_DESTROY);\n// }\n\n#ifndef ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ENUM\n#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ENUM\nenum zwlr_output_configuration_head_v1_error {\n\t/**\n\t * property has already been set\n\t */\n\tZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET = 1,\n\t/**\n\t * mode doesn't belong to head\n\t */\n\tZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_MODE = 2,\n\t/**\n\t * mode is invalid\n\t */\n\tZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_CUSTOM_MODE = 3,\n\t/**\n\t * transform value outside enum\n\t */\n\tZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_TRANSFORM = 4,\n\t/**\n\t * scale negative or zero\n\t */\n\tZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_SCALE = 5,\n\t/**\n\t * invalid enum value used in the set_adaptive_sync request\n\t * @since 4\n\t */\n\tZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_ADAPTIVE_SYNC_STATE = 6,\n};\n/**\n * @ingroup iface_zwlr_output_configuration_head_v1\n */\n#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_ADAPTIVE_SYNC_STATE_SINCE_VERSION 4\n#endif /* ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ENUM */\n\n#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_MODE 0\n#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_CUSTOM_MODE 1\n#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_POSITION 2\n#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_TRANSFORM 3\n#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_SCALE 4\n#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_ADAPTIVE_SYNC 5\n\n\n/**\n * @ingroup iface_zwlr_output_configuration_head_v1\n */\n#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_MODE_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_configuration_head_v1\n */\n#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_CUSTOM_MODE_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_configuration_head_v1\n */\n#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_POSITION_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_configuration_head_v1\n */\n#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_TRANSFORM_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_configuration_head_v1\n */\n#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_SCALE_SINCE_VERSION 1\n/**\n * @ingroup iface_zwlr_output_configuration_head_v1\n */\n#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_ADAPTIVE_SYNC_SINCE_VERSION 4\n\n/** @ingroup iface_zwlr_output_configuration_head_v1 */\nstatic inline void\nzwlr_output_configuration_head_v1_set_user_data(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1, void *user_data)\n{\n\twl_proxy_set_user_data((struct wl_proxy *) zwlr_output_configuration_head_v1, user_data);\n}\n\n/** @ingroup iface_zwlr_output_configuration_head_v1 */\nstatic inline void *\nzwlr_output_configuration_head_v1_get_user_data(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1)\n{\n\treturn wl_proxy_get_user_data((struct wl_proxy *) zwlr_output_configuration_head_v1);\n}\n\nstatic inline uint32_t\nzwlr_output_configuration_head_v1_get_version(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1)\n{\n\treturn wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_head_v1);\n}\n\n/** @ingroup iface_zwlr_output_configuration_head_v1 */\nstatic inline void\nzwlr_output_configuration_head_v1_destroy(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1)\n{\n\twl_proxy_destroy((struct wl_proxy *) zwlr_output_configuration_head_v1);\n}\n\n// /**\n//  * @ingroup iface_zwlr_output_configuration_head_v1\n//  *\n//  * This request sets the head's mode.\n//  */\n// static inline void\n// zwlr_output_configuration_head_v1_set_mode(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1, struct zwlr_output_mode_v1 *mode)\n// {\n// \twl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_head_v1,\n// \t\t\t ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_MODE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_head_v1), 0, mode);\n// }\n\n// /**\n//  * @ingroup iface_zwlr_output_configuration_head_v1\n//  *\n//  * This request assigns a custom mode to the head. The size is given in\n//  * physical hardware units of the output device. If set to zero, the\n//  * refresh rate is unspecified.\n//  *\n//  * It is a protocol error to set both a mode and a custom mode.\n//  */\n// static inline void\n// zwlr_output_configuration_head_v1_set_custom_mode(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1, int32_t width, int32_t height, int32_t refresh)\n// {\n// \twl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_head_v1,\n// \t\t\t ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_CUSTOM_MODE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_head_v1), 0, width, height, refresh);\n// }\n\n// /**\n//  * @ingroup iface_zwlr_output_configuration_head_v1\n//  *\n//  * This request sets the head's position in the global compositor space.\n//  */\n// static inline void\n// zwlr_output_configuration_head_v1_set_position(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1, int32_t x, int32_t y)\n// {\n// \twl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_head_v1,\n// \t\t\t ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_POSITION, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_head_v1), 0, x, y);\n// }\n\n// /**\n//  * @ingroup iface_zwlr_output_configuration_head_v1\n//  *\n//  * This request sets the head's transform.\n//  */\n// static inline void\n// zwlr_output_configuration_head_v1_set_transform(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1, int32_t transform)\n// {\n// \twl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_head_v1,\n// \t\t\t ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_TRANSFORM, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_head_v1), 0, transform);\n// }\n\n// /**\n//  * @ingroup iface_zwlr_output_configuration_head_v1\n//  *\n//  * This request sets the head's scale.\n//  */\n// static inline void\n// zwlr_output_configuration_head_v1_set_scale(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1, wl_fixed_t scale)\n// {\n// \twl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_head_v1,\n// \t\t\t ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_SCALE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_head_v1), 0, scale);\n// }\n\n// /**\n//  * @ingroup iface_zwlr_output_configuration_head_v1\n//  *\n//  * This request enables/disables adaptive sync. Adaptive sync is also\n//  * known as Variable Refresh Rate or VRR.\n//  */\n// static inline void\n// zwlr_output_configuration_head_v1_set_adaptive_sync(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1, uint32_t state)\n// {\n// \twl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_head_v1,\n// \t\t\t ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_ADAPTIVE_SYNC, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_head_v1), 0, state);\n// }\n\n#ifdef  __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c",
    "content": "#ifdef FF_HAVE_WAYLAND\n\n/* Generated by wayland-scanner 1.24.0 */\n\n/*\n * Copyright © 2019 Purism SPC\n *\n * Permission to use, copy, modify, distribute, and sell this\n * software and its documentation for any purpose is hereby granted\n * without fee, provided that the above copyright notice appear in\n * all copies and that both that copyright notice and this permission\n * notice appear in supporting documentation, and that the name of\n * the copyright holders not be used in advertising or publicity\n * pertaining to distribution of the software without specific,\n * written prior permission.  The copyright holders make no\n * representations about the suitability of this software for any\n * purpose.  It is provided \"as is\" without express or implied\n * warranty.\n *\n * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS\n * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY\n * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN\n * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\n * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n * THIS SOFTWARE.\n */\n\n#include <stdbool.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <wayland-util.h>\n\n#ifndef __has_attribute\n# define __has_attribute(x) 0  /* Compatibility with non-clang compilers. */\n#endif\n\n#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)\n#define WL_PRIVATE __attribute__ ((visibility(\"hidden\")))\n#else\n#define WL_PRIVATE\n#endif\n\nextern const struct wl_interface zwlr_output_configuration_head_v1_interface;\nextern const struct wl_interface zwlr_output_configuration_v1_interface;\nextern const struct wl_interface zwlr_output_head_v1_interface;\nextern const struct wl_interface zwlr_output_mode_v1_interface;\n\nstatic const struct wl_interface *wlr_output_management_unstable_v1_types[] = {\n\tNULL,\n\tNULL,\n\tNULL,\n\t&zwlr_output_configuration_v1_interface,\n\tNULL,\n\t&zwlr_output_head_v1_interface,\n\t&zwlr_output_mode_v1_interface,\n\t&zwlr_output_mode_v1_interface,\n\t&zwlr_output_configuration_head_v1_interface,\n\t&zwlr_output_head_v1_interface,\n\t&zwlr_output_head_v1_interface,\n\t&zwlr_output_mode_v1_interface,\n};\n\nstatic const struct wl_message zwlr_output_manager_v1_requests[] = {\n\t{ \"create_configuration\", \"nu\", wlr_output_management_unstable_v1_types + 3 },\n\t{ \"stop\", \"\", wlr_output_management_unstable_v1_types + 0 },\n};\n\nstatic const struct wl_message zwlr_output_manager_v1_events[] = {\n\t{ \"head\", \"n\", wlr_output_management_unstable_v1_types + 5 },\n\t{ \"done\", \"u\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"finished\", \"\", wlr_output_management_unstable_v1_types + 0 },\n};\n\nWL_PRIVATE const struct wl_interface zwlr_output_manager_v1_interface = {\n\t\"zwlr_output_manager_v1\", 4,\n\t2, zwlr_output_manager_v1_requests,\n\t3, zwlr_output_manager_v1_events,\n};\n\nstatic const struct wl_message zwlr_output_head_v1_requests[] = {\n\t{ \"release\", \"3\", wlr_output_management_unstable_v1_types + 0 },\n};\n\nstatic const struct wl_message zwlr_output_head_v1_events[] = {\n\t{ \"name\", \"s\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"description\", \"s\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"physical_size\", \"ii\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"mode\", \"n\", wlr_output_management_unstable_v1_types + 6 },\n\t{ \"enabled\", \"i\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"current_mode\", \"o\", wlr_output_management_unstable_v1_types + 7 },\n\t{ \"position\", \"ii\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"transform\", \"i\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"scale\", \"f\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"finished\", \"\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"make\", \"2s\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"model\", \"2s\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"serial_number\", \"2s\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"adaptive_sync\", \"4u\", wlr_output_management_unstable_v1_types + 0 },\n};\n\nWL_PRIVATE const struct wl_interface zwlr_output_head_v1_interface = {\n\t\"zwlr_output_head_v1\", 4,\n\t1, zwlr_output_head_v1_requests,\n\t14, zwlr_output_head_v1_events,\n};\n\nstatic const struct wl_message zwlr_output_mode_v1_requests[] = {\n\t{ \"release\", \"3\", wlr_output_management_unstable_v1_types + 0 },\n};\n\nstatic const struct wl_message zwlr_output_mode_v1_events[] = {\n\t{ \"size\", \"ii\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"refresh\", \"i\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"preferred\", \"\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"finished\", \"\", wlr_output_management_unstable_v1_types + 0 },\n};\n\nWL_PRIVATE const struct wl_interface zwlr_output_mode_v1_interface = {\n\t\"zwlr_output_mode_v1\", 3,\n\t1, zwlr_output_mode_v1_requests,\n\t4, zwlr_output_mode_v1_events,\n};\n\nstatic const struct wl_message zwlr_output_configuration_v1_requests[] = {\n\t{ \"enable_head\", \"no\", wlr_output_management_unstable_v1_types + 8 },\n\t{ \"disable_head\", \"o\", wlr_output_management_unstable_v1_types + 10 },\n\t{ \"apply\", \"\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"test\", \"\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"destroy\", \"\", wlr_output_management_unstable_v1_types + 0 },\n};\n\nstatic const struct wl_message zwlr_output_configuration_v1_events[] = {\n\t{ \"succeeded\", \"\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"failed\", \"\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"cancelled\", \"\", wlr_output_management_unstable_v1_types + 0 },\n};\n\nWL_PRIVATE const struct wl_interface zwlr_output_configuration_v1_interface = {\n\t\"zwlr_output_configuration_v1\", 4,\n\t5, zwlr_output_configuration_v1_requests,\n\t3, zwlr_output_configuration_v1_events,\n};\n\nstatic const struct wl_message zwlr_output_configuration_head_v1_requests[] = {\n\t{ \"set_mode\", \"o\", wlr_output_management_unstable_v1_types + 11 },\n\t{ \"set_custom_mode\", \"iii\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"set_position\", \"ii\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"set_transform\", \"i\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"set_scale\", \"f\", wlr_output_management_unstable_v1_types + 0 },\n\t{ \"set_adaptive_sync\", \"4u\", wlr_output_management_unstable_v1_types + 0 },\n};\n\nWL_PRIVATE const struct wl_interface zwlr_output_configuration_head_v1_interface = {\n\t\"zwlr_output_configuration_head_v1\", 4,\n\t6, zwlr_output_configuration_head_v1_requests,\n\t0, NULL,\n};\n\n#endif\n"
  },
  {
    "path": "src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-client-protocol.h",
    "content": "/* Generated by wayland-scanner 1.22.0 */\n\n#ifndef XDG_OUTPUT_UNSTABLE_V1_CLIENT_PROTOCOL_H\n#define XDG_OUTPUT_UNSTABLE_V1_CLIENT_PROTOCOL_H\n\n#include <stdint.h>\n#include <stddef.h>\n#include <wayland-client.h>\n\n#ifdef  __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * @page page_xdg_output_unstable_v1 The xdg_output_unstable_v1 protocol\n * Protocol to describe output regions\n *\n * @section page_desc_xdg_output_unstable_v1 Description\n *\n * This protocol aims at describing outputs in a way which is more in line\n * with the concept of an output on desktop oriented systems.\n *\n * Some information are more specific to the concept of an output for\n * a desktop oriented system and may not make sense in other applications,\n * such as IVI systems for example.\n *\n * Typically, the global compositor space on a desktop system is made of\n * a contiguous or overlapping set of rectangular regions.\n *\n * The logical_position and logical_size events defined in this protocol\n * might provide information identical to their counterparts already\n * available from wl_output, in which case the information provided by this\n * protocol should be preferred to their equivalent in wl_output. The goal is\n * to move the desktop specific concepts (such as output location within the\n * global compositor space, etc.) out of the core wl_output protocol.\n *\n * Warning! The protocol described in this file is experimental and\n * backward incompatible changes may be made. Backward compatible\n * changes may be added together with the corresponding interface\n * version bump.\n * Backward incompatible changes are done by bumping the version\n * number in the protocol and interface names and resetting the\n * interface version. Once the protocol is to be declared stable,\n * the 'z' prefix and the version number in the protocol and\n * interface names are removed and the interface version number is\n * reset.\n *\n * @section page_ifaces_xdg_output_unstable_v1 Interfaces\n * - @subpage page_iface_zxdg_output_manager_v1 - manage xdg_output objects\n * - @subpage page_iface_zxdg_output_v1 - compositor logical output region\n * @section page_copyright_xdg_output_unstable_v1 Copyright\n * <pre>\n *\n * Copyright © 2017 Red Hat Inc.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice (including the next\n * paragraph) shall be included in all copies or substantial portions of the\n * Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n * </pre>\n */\nstruct wl_output;\nstruct zxdg_output_manager_v1;\nstruct zxdg_output_v1;\n\n#ifndef ZXDG_OUTPUT_MANAGER_V1_INTERFACE\n#define ZXDG_OUTPUT_MANAGER_V1_INTERFACE\n/**\n * @page page_iface_zxdg_output_manager_v1 zxdg_output_manager_v1\n * @section page_iface_zxdg_output_manager_v1_desc Description\n *\n * A global factory interface for xdg_output objects.\n * @section page_iface_zxdg_output_manager_v1_api API\n * See @ref iface_zxdg_output_manager_v1.\n */\n/**\n * @defgroup iface_zxdg_output_manager_v1 The zxdg_output_manager_v1 interface\n *\n * A global factory interface for xdg_output objects.\n */\nextern const struct wl_interface zxdg_output_manager_v1_interface;\n#endif\n#ifndef ZXDG_OUTPUT_V1_INTERFACE\n#define ZXDG_OUTPUT_V1_INTERFACE\n/**\n * @page page_iface_zxdg_output_v1 zxdg_output_v1\n * @section page_iface_zxdg_output_v1_desc Description\n *\n * An xdg_output describes part of the compositor geometry.\n *\n * This typically corresponds to a monitor that displays part of the\n * compositor space.\n *\n * For objects version 3 onwards, after all xdg_output properties have been\n * sent (when the object is created and when properties are updated), a\n * wl_output.done event is sent. This allows changes to the output\n * properties to be seen as atomic, even if they happen via multiple events.\n * @section page_iface_zxdg_output_v1_api API\n * See @ref iface_zxdg_output_v1.\n */\n/**\n * @defgroup iface_zxdg_output_v1 The zxdg_output_v1 interface\n *\n * An xdg_output describes part of the compositor geometry.\n *\n * This typically corresponds to a monitor that displays part of the\n * compositor space.\n *\n * For objects version 3 onwards, after all xdg_output properties have been\n * sent (when the object is created and when properties are updated), a\n * wl_output.done event is sent. This allows changes to the output\n * properties to be seen as atomic, even if they happen via multiple events.\n */\nextern const struct wl_interface zxdg_output_v1_interface;\n#endif\n\n#define ZXDG_OUTPUT_MANAGER_V1_DESTROY 0\n#define ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT 1\n\n\n/**\n * @ingroup iface_zxdg_output_manager_v1\n */\n#define ZXDG_OUTPUT_MANAGER_V1_DESTROY_SINCE_VERSION 1\n/**\n * @ingroup iface_zxdg_output_manager_v1\n */\n#define ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT_SINCE_VERSION 1\n\n// /** @ingroup iface_zxdg_output_manager_v1 */\n// static inline void\n// zxdg_output_manager_v1_set_user_data(struct zxdg_output_manager_v1 *zxdg_output_manager_v1, void *user_data)\n// {\n// \twl_proxy_set_user_data((struct wl_proxy *) zxdg_output_manager_v1, user_data);\n// }\n\n// /** @ingroup iface_zxdg_output_manager_v1 */\n// static inline void *\n// zxdg_output_manager_v1_get_user_data(struct zxdg_output_manager_v1 *zxdg_output_manager_v1)\n// {\n// \treturn wl_proxy_get_user_data((struct wl_proxy *) zxdg_output_manager_v1);\n// }\n\n// static inline uint32_t\n// zxdg_output_manager_v1_get_version(struct zxdg_output_manager_v1 *zxdg_output_manager_v1)\n// {\n// \treturn wl_proxy_get_version((struct wl_proxy *) zxdg_output_manager_v1);\n// }\n\n// /**\n//  * @ingroup iface_zxdg_output_manager_v1\n//  *\n//  * Using this request a client can tell the server that it is not\n//  * going to use the xdg_output_manager object anymore.\n//  *\n//  * Any objects already created through this instance are not affected.\n//  */\n// static inline void\n// zxdg_output_manager_v1_destroy(struct zxdg_output_manager_v1 *zxdg_output_manager_v1)\n// {\n// \twl_proxy_marshal_flags((struct wl_proxy *) zxdg_output_manager_v1,\n// \t\t\t ZXDG_OUTPUT_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_output_manager_v1), WL_MARSHAL_FLAG_DESTROY);\n// }\n\n// /**\n//  * @ingroup iface_zxdg_output_manager_v1\n//  *\n//  * This creates a new xdg_output object for the given wl_output.\n//  */\n// static inline struct zxdg_output_v1 *\n// zxdg_output_manager_v1_get_xdg_output(struct zxdg_output_manager_v1 *zxdg_output_manager_v1, struct wl_output *output)\n// {\n// \tstruct wl_proxy *id;\n\n// \tid = wl_proxy_marshal_flags((struct wl_proxy *) zxdg_output_manager_v1,\n// \t\t\t ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT, &zxdg_output_v1_interface, wl_proxy_get_version((struct wl_proxy *) zxdg_output_manager_v1), 0, NULL, output);\n\n// \treturn (struct zxdg_output_v1 *) id;\n// }\n\n/**\n * @ingroup iface_zxdg_output_v1\n * @struct zxdg_output_v1_listener\n */\nstruct zxdg_output_v1_listener {\n\t/**\n\t * position of the output within the global compositor space\n\t *\n\t * The position event describes the location of the wl_output\n\t * within the global compositor space.\n\t *\n\t * The logical_position event is sent after creating an xdg_output\n\t * (see xdg_output_manager.get_xdg_output) and whenever the\n\t * location of the output changes within the global compositor\n\t * space.\n\t * @param x x position within the global compositor space\n\t * @param y y position within the global compositor space\n\t */\n\tvoid (*logical_position)(void *data,\n\t\t\t\t struct zxdg_output_v1 *zxdg_output_v1,\n\t\t\t\t int32_t x,\n\t\t\t\t int32_t y);\n\t/**\n\t * size of the output in the global compositor space\n\t *\n\t * The logical_size event describes the size of the output in the\n\t * global compositor space.\n\t *\n\t * Most regular Wayland clients should not pay attention to the\n\t * logical size and would rather rely on xdg_shell interfaces.\n\t *\n\t * Some clients such as Xwayland, however, need this to configure\n\t * their surfaces in the global compositor space as the compositor\n\t * may apply a different scale from what is advertised by the\n\t * output scaling property (to achieve fractional scaling, for\n\t * example).\n\t *\n\t * For example, for a wl_output mode 3840×2160 and a scale factor\n\t * 2:\n\t *\n\t * - A compositor not scaling the monitor viewport in its\n\t * compositing space will advertise a logical size of 3840×2160,\n\t *\n\t * - A compositor scaling the monitor viewport with scale factor 2\n\t * will advertise a logical size of 1920×1080,\n\t *\n\t * - A compositor scaling the monitor viewport using a fractional\n\t * scale of 1.5 will advertise a logical size of 2560×1440.\n\t *\n\t * For example, for a wl_output mode 1920×1080 and a 90 degree\n\t * rotation, the compositor will advertise a logical size of\n\t * 1080x1920.\n\t *\n\t * The logical_size event is sent after creating an xdg_output (see\n\t * xdg_output_manager.get_xdg_output) and whenever the logical size\n\t * of the output changes, either as a result of a change in the\n\t * applied scale or because of a change in the corresponding output\n\t * mode(see wl_output.mode) or transform (see wl_output.transform).\n\t * @param width width in global compositor space\n\t * @param height height in global compositor space\n\t */\n\tvoid (*logical_size)(void *data,\n\t\t\t     struct zxdg_output_v1 *zxdg_output_v1,\n\t\t\t     int32_t width,\n\t\t\t     int32_t height);\n\t/**\n\t * all information about the output have been sent\n\t *\n\t * This event is sent after all other properties of an xdg_output\n\t * have been sent.\n\t *\n\t * This allows changes to the xdg_output properties to be seen as\n\t * atomic, even if they happen via multiple events.\n\t *\n\t * For objects version 3 onwards, this event is deprecated.\n\t * Compositors are not required to send it anymore and must send\n\t * wl_output.done instead.\n\t */\n\tvoid (*done)(void *data,\n\t\t     struct zxdg_output_v1 *zxdg_output_v1);\n\t/**\n\t * name of this output\n\t *\n\t * Many compositors will assign names to their outputs, show them\n\t * to the user, allow them to be configured by name, etc. The\n\t * client may wish to know this name as well to offer the user\n\t * similar behaviors.\n\t *\n\t * The naming convention is compositor defined, but limited to\n\t * alphanumeric characters and dashes (-). Each name is unique\n\t * among all wl_output globals, but if a wl_output global is\n\t * destroyed the same name may be reused later. The names will also\n\t * remain consistent across sessions with the same hardware and\n\t * software configuration.\n\t *\n\t * Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc.\n\t * However, do not assume that the name is a reflection of an\n\t * underlying DRM connector, X11 connection, etc.\n\t *\n\t * The name event is sent after creating an xdg_output (see\n\t * xdg_output_manager.get_xdg_output). This event is only sent once\n\t * per xdg_output, and the name does not change over the lifetime\n\t * of the wl_output global.\n\t *\n\t * This event is deprecated, instead clients should use\n\t * wl_output.name. Compositors must still support this event.\n\t * @param name output name\n\t * @since 2\n\t */\n\tvoid (*name)(void *data,\n\t\t     struct zxdg_output_v1 *zxdg_output_v1,\n\t\t     const char *name);\n\t/**\n\t * human-readable description of this output\n\t *\n\t * Many compositors can produce human-readable descriptions of\n\t * their outputs. The client may wish to know this description as\n\t * well, to communicate the user for various purposes.\n\t *\n\t * The description is a UTF-8 string with no convention defined for\n\t * its contents. Examples might include 'Foocorp 11\" Display' or\n\t * 'Virtual X11 output via :1'.\n\t *\n\t * The description event is sent after creating an xdg_output (see\n\t * xdg_output_manager.get_xdg_output) and whenever the description\n\t * changes. The description is optional, and may not be sent at\n\t * all.\n\t *\n\t * For objects of version 2 and lower, this event is only sent once\n\t * per xdg_output, and the description does not change over the\n\t * lifetime of the wl_output global.\n\t *\n\t * This event is deprecated, instead clients should use\n\t * wl_output.description. Compositors must still support this\n\t * event.\n\t * @param description output description\n\t * @since 2\n\t */\n\tvoid (*description)(void *data,\n\t\t\t    struct zxdg_output_v1 *zxdg_output_v1,\n\t\t\t    const char *description);\n};\n\n/**\n * @ingroup iface_zxdg_output_v1\n */\nstatic inline int\nzxdg_output_v1_add_listener(struct zxdg_output_v1 *zxdg_output_v1,\n\t\t\t    const struct zxdg_output_v1_listener *listener, void *data)\n{\n\treturn wl_proxy_add_listener((struct wl_proxy *) zxdg_output_v1,\n\t\t\t\t     (void (**)(void)) listener, data);\n}\n\n#define ZXDG_OUTPUT_V1_DESTROY 0\n\n/**\n * @ingroup iface_zxdg_output_v1\n */\n#define ZXDG_OUTPUT_V1_LOGICAL_POSITION_SINCE_VERSION 1\n/**\n * @ingroup iface_zxdg_output_v1\n */\n#define ZXDG_OUTPUT_V1_LOGICAL_SIZE_SINCE_VERSION 1\n/**\n * @ingroup iface_zxdg_output_v1\n */\n#define ZXDG_OUTPUT_V1_DONE_SINCE_VERSION 1\n/**\n * @ingroup iface_zxdg_output_v1\n */\n#define ZXDG_OUTPUT_V1_NAME_SINCE_VERSION 2\n/**\n * @ingroup iface_zxdg_output_v1\n */\n#define ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION 2\n\n/**\n * @ingroup iface_zxdg_output_v1\n */\n#define ZXDG_OUTPUT_V1_DESTROY_SINCE_VERSION 1\n\n// /** @ingroup iface_zxdg_output_v1 */\n// static inline void\n// zxdg_output_v1_set_user_data(struct zxdg_output_v1 *zxdg_output_v1, void *user_data)\n// {\n// \twl_proxy_set_user_data((struct wl_proxy *) zxdg_output_v1, user_data);\n// }\n\n// /** @ingroup iface_zxdg_output_v1 */\n// static inline void *\n// zxdg_output_v1_get_user_data(struct zxdg_output_v1 *zxdg_output_v1)\n// {\n// \treturn wl_proxy_get_user_data((struct wl_proxy *) zxdg_output_v1);\n// }\n\n// static inline uint32_t\n// zxdg_output_v1_get_version(struct zxdg_output_v1 *zxdg_output_v1)\n// {\n// \treturn wl_proxy_get_version((struct wl_proxy *) zxdg_output_v1);\n// }\n\n// /**\n//  * @ingroup iface_zxdg_output_v1\n//  *\n//  * Using this request a client can tell the server that it is not\n//  * going to use the xdg_output object anymore.\n//  */\n// static inline void\n// zxdg_output_v1_destroy(struct zxdg_output_v1 *zxdg_output_v1)\n// {\n// \twl_proxy_marshal_flags((struct wl_proxy *) zxdg_output_v1,\n// \t\t\t ZXDG_OUTPUT_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_output_v1), WL_MARSHAL_FLAG_DESTROY);\n// }\n\n#ifdef  __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c",
    "content": "#ifdef FF_HAVE_WAYLAND\n/* Generated by wayland-scanner 1.22.0 */\n\n/*\n * Copyright © 2017 Red Hat Inc.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice (including the next\n * paragraph) shall be included in all copies or substantial portions of the\n * Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#include <stdlib.h>\n#include <stdint.h>\n#include <wayland-util.h>\n\nextern const struct wl_interface wl_output_interface;\nextern const struct wl_interface zxdg_output_v1_interface;\n\nstatic const struct wl_interface *xdg_output_unstable_v1_types[] = {\n\tNULL,\n\tNULL,\n\t&zxdg_output_v1_interface,\n\tNULL, // &wl_output_interface,\n};\n\nstatic const struct wl_message zxdg_output_manager_v1_requests[] = {\n\t{ \"destroy\", \"\", xdg_output_unstable_v1_types + 0 },\n\t{ \"get_xdg_output\", \"no\", xdg_output_unstable_v1_types + 2 },\n};\n\nWL_EXPORT const struct wl_interface zxdg_output_manager_v1_interface = {\n\t\"zxdg_output_manager_v1\", 3,\n\t2, zxdg_output_manager_v1_requests,\n\t0, NULL,\n};\n\nstatic const struct wl_message zxdg_output_v1_requests[] = {\n\t{ \"destroy\", \"\", xdg_output_unstable_v1_types + 0 },\n};\n\nstatic const struct wl_message zxdg_output_v1_events[] = {\n\t{ \"logical_position\", \"ii\", xdg_output_unstable_v1_types + 0 },\n\t{ \"logical_size\", \"ii\", xdg_output_unstable_v1_types + 0 },\n\t{ \"done\", \"\", xdg_output_unstable_v1_types + 0 },\n\t{ \"name\", \"2s\", xdg_output_unstable_v1_types + 0 },\n\t{ \"description\", \"2s\", xdg_output_unstable_v1_types + 0 },\n};\n\nWL_EXPORT const struct wl_interface zxdg_output_v1_interface = {\n\t\"zxdg_output_v1\", 3,\n\t1, zxdg_output_v1_requests,\n\t5, zxdg_output_v1_events,\n};\n\n#endif\n"
  },
  {
    "path": "src/detection/displayserver/linux/wayland/zwlr-output.c",
    "content": "#ifdef FF_HAVE_WAYLAND\n\n#include \"wayland.h\"\n#include \"wlr-output-management-unstable-v1-client-protocol.h\"\n\nstatic void waylandZwlrTransformListener(void* data, FF_MAYBE_UNUSED struct zwlr_output_head_v1 *zwlr_output_head_v1, int32_t transform)\n{\n    WaylandDisplay* wldata = (WaylandDisplay*) data;\n    wldata->transform = (enum wl_output_transform) transform;\n}\n\nstatic void waylandZwlrScaleListener(void* data, FF_MAYBE_UNUSED struct zwlr_output_head_v1 *zwlr_output_head_v1, wl_fixed_t scale)\n{\n    WaylandDisplay* wldata = (WaylandDisplay*) data;\n    wldata->dpi = (uint32_t) scale * 3 / 8; // wl_fixed_to_double(scale) * 96;\n}\n\ntypedef struct WaylandZwlrMode\n{\n    int32_t width;\n    int32_t height;\n    int32_t refreshRate;\n    bool preferred;\n    struct zwlr_output_mode_v1* pMode;\n} WaylandZwlrMode;\n\nstatic void waylandZwlrModeSizeListener(void* data, FF_MAYBE_UNUSED struct zwlr_output_mode_v1 *zwlr_output_mode_v1, int32_t width, int32_t height)\n{\n    WaylandZwlrMode* mode = (WaylandZwlrMode*) data;\n    mode->width = width;\n    mode->height = height;\n}\n\nstatic void waylandZwlrModeRefreshListener(void* data, FF_MAYBE_UNUSED struct zwlr_output_mode_v1 *zwlr_output_mode_v1, int32_t rate)\n{\n    WaylandZwlrMode* mode = (WaylandZwlrMode*) data;\n    mode->refreshRate = rate;\n}\n\nstatic void waylandZwlrModePreferredListener(void* data, FF_MAYBE_UNUSED struct zwlr_output_mode_v1 *zwlr_output_mode_v1)\n{\n    WaylandZwlrMode* mode = (WaylandZwlrMode*) data;\n    mode->preferred = true;\n}\n\nstatic const struct zwlr_output_mode_v1_listener modeListener = {\n    .size = waylandZwlrModeSizeListener,\n    .refresh = waylandZwlrModeRefreshListener,\n    .preferred = waylandZwlrModePreferredListener,\n    .finished = (void*) stubListener,\n};\n\nstatic void waylandZwlrModeListener(void* data, FF_MAYBE_UNUSED struct zwlr_output_head_v1 *zwlr_output_head_v1, struct zwlr_output_mode_v1 *mode)\n{\n    WaylandDisplay* wldata = (WaylandDisplay*) data;\n    if (!wldata->internal) return;\n\n    WaylandZwlrMode* newMode = ffListAdd((FFlist*) wldata->internal);\n    *newMode = (WaylandZwlrMode) { .pMode = mode };\n\n    // Strangely, the listener is called only in this function, but not in `waylandZwlrCurrentModeListener`\n    wldata->parent->ffwl_proxy_add_listener((struct wl_proxy *) mode, (void (**)(void)) &modeListener, newMode);\n}\n\nstatic void waylandZwlrCurrentModeListener(void* data, FF_MAYBE_UNUSED struct zwlr_output_head_v1 *zwlr_output_head_v1, struct zwlr_output_mode_v1 *mode)\n{\n    // waylandZwlrModeListener is always run before this\n    WaylandDisplay* wldata = (WaylandDisplay*) data;\n    if (!wldata->internal) return;\n\n    int set = 0;\n    FF_LIST_FOR_EACH(WaylandZwlrMode, m, *(FFlist*) wldata->internal)\n    {\n        if (m->pMode == mode)\n        {\n            wldata->width = m->width;\n            wldata->height = m->height;\n            wldata->refreshRate = m->refreshRate;\n            if (++set == 2) break;\n        }\n        if (m->preferred)\n        {\n            wldata->preferredWidth = m->width;\n            wldata->preferredHeight = m->height;\n            wldata->preferredRefreshRate = m->refreshRate;\n            if (++set == 2) break;\n        }\n    }\n}\n\nstatic void waylandZwlrPhysicalSizeListener(void* data, FF_MAYBE_UNUSED struct zwlr_output_head_v1 *zwlr_output_head_v1, int32_t width, int32_t height)\n{\n    WaylandDisplay* wldata = (WaylandDisplay*) data;\n    wldata->physicalWidth = width;\n    wldata->physicalHeight = height;\n}\n\nstatic void waylandZwlrEnabledListener(void* data, FF_MAYBE_UNUSED struct zwlr_output_head_v1 *zwlr_output_head_v1, bool enabled)\n{\n    WaylandDisplay* wldata = (WaylandDisplay*) data;\n    if (!enabled) wldata->internal = NULL;\n}\n\nstatic const struct zwlr_output_head_v1_listener headListener = {\n    .name = (void*) ffWaylandOutputNameListener,\n    .description = (void*) ffWaylandOutputDescriptionListener,\n    .physical_size = waylandZwlrPhysicalSizeListener,\n    .mode = waylandZwlrModeListener,\n    .enabled = (void*) waylandZwlrEnabledListener,\n    .current_mode = waylandZwlrCurrentModeListener,\n    .position = (void*) stubListener,\n    .transform = waylandZwlrTransformListener,\n    .scale = waylandZwlrScaleListener,\n    .finished = (void*) stubListener,\n    .make = (void*) stubListener,\n    .model = (void*) stubListener,\n    .serial_number = (void*) stubListener,\n    .adaptive_sync = (void*) stubListener,\n};\n\nstatic void waylandHandleZwlrHead(void *data, FF_MAYBE_UNUSED struct zwlr_output_manager_v1 *zwlr_output_manager_v1, struct zwlr_output_head_v1 *head)\n{\n    WaylandData* wldata = data;\n\n    FF_LIST_AUTO_DESTROY modes = ffListCreate(sizeof(WaylandZwlrMode));\n    WaylandDisplay display = {\n        .parent = wldata,\n        .transform = WL_OUTPUT_TRANSFORM_NORMAL,\n        .type = FF_DISPLAY_TYPE_UNKNOWN,\n        .name = ffStrbufCreate(),\n        .description = ffStrbufCreate(),\n        .edidName = ffStrbufCreate(),\n        .internal = &modes,\n    };\n\n    wldata->ffwl_proxy_add_listener((struct wl_proxy*) head, (void(**)(void)) &headListener, &display);\n    wldata->ffwl_display_roundtrip(wldata->display);\n\n    if(display.width <= 0 || display.height <= 0 || !display.internal)\n        return;\n\n    uint32_t rotation = ffWaylandHandleRotation(&display);\n\n    FFDisplayResult* item = ffdsAppendDisplay(wldata->result,\n        (uint32_t) display.width,\n        (uint32_t) display.height,\n        display.refreshRate / 1000.0,\n        (uint32_t) display.dpi,\n        (uint32_t) display.preferredWidth,\n        (uint32_t) display.preferredHeight,\n        display.preferredRefreshRate / 1000.0,\n        rotation,\n        display.edidName.length\n            ? &display.edidName\n            : display.description.length && !ffStrbufContain(&display.description, &display.name)\n                ? &display.description\n                : &display.name,\n        display.type,\n        false,\n        display.id,\n        (uint32_t) display.physicalWidth,\n        (uint32_t) display.physicalHeight,\n        \"wayland-zwlr\"\n    );\n    if (item)\n    {\n        if (display.hdrSupported)\n            item->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED;\n        else if (display.hdrInfoAvailable)\n            item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED;\n        else\n            item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN;\n\n        item->manufactureYear = display.myear;\n        item->manufactureWeek = display.mweek;\n        item->serial = display.serial;\n    }\n\n    ffStrbufDestroy(&display.description);\n    ffStrbufDestroy(&display.name);\n    ffStrbufDestroy(&display.edidName);\n\n    // These must be released manually\n    FF_LIST_FOR_EACH(WaylandZwlrMode, m, modes)\n        wldata->ffwl_proxy_destroy((void*) m->pMode);\n    wldata->ffwl_proxy_destroy((void*) head);\n}\n\nconst char* ffWaylandHandleZwlrOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version)\n{\n    struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &zwlr_output_manager_v1_interface, version, name, zwlr_output_manager_v1_interface.name, version, NULL);\n    if(output == NULL)\n        return \"Failed to bind zwlr_output_manager_v1\";\n\n    const struct zwlr_output_manager_v1_listener outputListener = {\n        .head = waylandHandleZwlrHead,\n        .done = (void*) stubListener,\n        .finished = (void*) stubListener,\n    };\n\n    if (wldata->ffwl_proxy_add_listener(output, (void(**)(void)) &outputListener, wldata) < 0)\n    {\n        wldata->ffwl_proxy_destroy(output);\n        return \"Failed to add listener to zwlr_output_manager_v1\";\n    }\n    if (wldata->ffwl_display_roundtrip(wldata->display) < 0)\n    {\n        wldata->ffwl_proxy_destroy(output);\n        return \"Failed to roundtrip display\";\n    }\n    wldata->ffwl_proxy_destroy(output);\n\n    return NULL;\n}\n\n#endif\n"
  },
  {
    "path": "src/detection/displayserver/linux/wmde.c",
    "content": "#include \"displayserver_linux.h\"\n#include \"common/io.h\"\n#include \"common/properties.h\"\n#include \"common/stringUtils.h\"\n#include \"common/mallocHelper.h\"\n\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n\n#if __FreeBSD__\n    #include <sys/sysctl.h>\n    #include <sys/types.h>\n    #include <sys/user.h>\n#elif __OpenBSD__\n    #include <sys/param.h>\n    #include <sys/sysctl.h>\n    #include <kvm.h>\n#elif __sun\n    #include <procfs.h>\n#elif __NetBSD__\n    #include <sys/types.h>\n    #include <sys/sysctl.h>\n#endif\n\nstatic const char* parseEnv(void)\n{\n    const char* env;\n\n    env = getenv(\"XDG_CURRENT_DESKTOP\");\n    if(ffStrSet(env))\n        return env;\n\n    env = getenv(\"XDG_SESSION_DESKTOP\");\n    if(ffStrSet(env))\n        return env;\n\n    env = getenv(\"CURRENT_DESKTOP\");\n    if(ffStrSet(env))\n        return env;\n\n    env = getenv(\"SESSION_DESKTOP\");\n    if(ffStrSet(env))\n        return env;\n\n    env = getenv(\"DESKTOP_SESSION\");\n    if(ffStrSet(env))\n        return env;\n\n    if(getenv(\"KDE_FULL_SESSION\") != NULL || getenv(\"KDE_SESSION_UID\") != NULL || getenv(\"KDE_SESSION_VERSION\") != NULL)\n        return \"KDE\";\n\n    if(getenv(\"GNOME_DESKTOP_SESSION_ID\") != NULL)\n        return \"GNOME\";\n\n    if(getenv(\"MATE_DESKTOP_SESSION_ID\") != NULL)\n        return \"Mate\";\n\n    if(getenv(\"TDE_FULL_SESSION\") != NULL)\n        return \"Trinity\";\n\n    if(getenv(\"HYPRLAND_CMD\") != NULL)\n        return \"Hyprland\";\n\n    if(getenv(\"SWAYSOCK\") != NULL)\n        return \"Sway\";\n\n    #if __linux__ && !__ANDROID__\n    if(\n        getenv(\"WAYLAND_DISPLAY\") != NULL &&\n        ffPathExists(\"/mnt/wslg/\", FF_PATHTYPE_DIRECTORY)\n    ) return \"WSLg\";\n    #endif\n\n    return NULL;\n}\n\nstatic void applyPrettyNameIfWM(FFDisplayServerResult* result, const char* name)\n{\n    if(!ffStrSet(name))\n        return;\n\n    if(\n        ffStrEqualsIgnCase(name, \"kwin\") ||\n        ffStrStartsWithIgnCase(name, \"kwin_\") ||\n        ffStrEndsWithIgnCase(name, \"-kwin_wayland\") ||\n        ffStrEndsWithIgnCase(name, \"-kwin_x11\")\n    ) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_KWIN);\n    else if(\n        ffStrEqualsIgnCase(name, \"gnome-shell\") ||\n        ffStrEqualsIgnCase(name, \"gnome shell\") ||\n        ffStrEqualsIgnCase(name, \"gnome-session-binary\") ||\n        ffStrEqualsIgnCase(name, \"Mutter\")\n    ) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_MUTTER);\n    else if(\n        ffStrEqualsIgnCase(name, \"cinnamon\") ||\n        ffStrStartsWithIgnCase(name, \"cinnamon-\") ||\n        ffStrEqualsIgnCase(name, \"Muffin\") ||\n        ffStrEqualsIgnCase(name, \"Mutter (Muffin)\")\n    ) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_MUFFIN);\n    else if(ffStrEqualsIgnCase(name, \"sway\"))\n        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_SWAY);\n    else if(ffStrEqualsIgnCase(name, \"weston\"))\n        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_WESTON);\n    else if(ffStrEqualsIgnCase(name, \"wayfire\"))\n        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_WAYFIRE);\n    else if(ffStrEqualsIgnCase(name, \"openbox\"))\n        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_OPENBOX);\n    else if(ffStrEqualsIgnCase(name, \"xfwm4\"))\n        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_XFWM4);\n    else if(ffStrEqualsIgnCase(name, \"Marco\") ||\n        ffStrEqualsIgnCase(name, \"Metacity (Marco)\"))\n        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_MARCO);\n    else if(ffStrEqualsIgnCase(name, \"xmonad\"))\n        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_XMONAD);\n    else if(ffStrEqualsIgnCase(name, \"WSLg\"))\n        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_WSLG);\n    else if(ffStrEqualsIgnCase(name, \"dwm\"))\n        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_DWM);\n    else if(ffStrEqualsIgnCase(name, \"bspwm\"))\n        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_BSPWM);\n    else if(ffStrEqualsIgnCase(name, \"tinywm\"))\n        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_TINYWM);\n    else if(ffStrEqualsIgnCase(name, \"qtile\"))\n        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_QTILE);\n    else if(ffStrEqualsIgnCase(name, \"herbstluftwm\"))\n        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_HERBSTLUFTWM);\n    else if(ffStrEqualsIgnCase(name, \"icewm\"))\n        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_ICEWM);\n    else if(ffStrEqualsIgnCase(name, \"dtwm\"))\n        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_DTWM);\n    else if(ffStrEqualsIgnCase(name, \"fvwm\"))\n        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_FVWM);\n    else if(ffStrEqualsIgnCase(name, \"ctwm\"))\n        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_CTWM);\n    else if(ffStrEqualsIgnCase(name, \"hyprland\"))\n        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_HYPRLAND);\n    else if(ffStrEqualsIgnCase(name, \"ratpoison\"))\n        ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_RATPOISON);\n}\n\nstatic void applyNameIfWM(FFDisplayServerResult* result, const char* processName)\n{\n    applyPrettyNameIfWM(result, processName);\n    if(result->wmPrettyName.length > 0)\n        ffStrbufSetS(&result->wmProcessName, processName);\n}\n\nstatic void applyBetterWM(FFDisplayServerResult* result, const char* processName)\n{\n    if(!ffStrSet(processName))\n        return;\n\n    ffStrbufSetS(&result->wmProcessName, processName);\n\n    //If it is a known wm, this will set the pretty name\n    applyPrettyNameIfWM(result, processName);\n\n    //If it isn't a known wm, set the pretty name to the process name\n    if(result->wmPrettyName.length == 0)\n        ffStrbufAppend(&result->wmPrettyName, &result->wmProcessName);\n}\n\nstatic void applyPrettyNameIfDE(FFDisplayServerResult* result, const char* name)\n{\n    if(!ffStrSet(name))\n        return;\n\n    else if(\n        ffStrEqualsIgnCase(name, \"KDE\") ||\n        ffStrEqualsIgnCase(name, \"plasma\") ||\n        ffStrEqualsIgnCase(name, \"plasmashell\") ||\n        ffStrEqualsIgnCase(name, \"plasmawayland\")\n    ) {\n        ffStrbufSetStatic(&result->deProcessName, \"plasmashell\");\n        ffStrbufSetStatic(&result->dePrettyName, FF_DE_PRETTY_PLASMA);\n        applyBetterWM(result, getenv(\"KDEWM\"));\n    }\n\n    else if(\n        ffStrEqualsIgnCase(name, \"GNOME\") ||\n        ffStrEqualsIgnCase(name, \"ubuntu:GNOME\") ||\n        ffStrEqualsIgnCase(name, \"ubuntu\") ||\n        ffStrEqualsIgnCase(name, \"gnome-shell\")\n    ) {\n        ffStrbufSetStatic(&result->deProcessName, \"gnome-shell\");\n        const char* sessionMode = getenv(\"GNOME_SHELL_SESSION_MODE\");\n        if (sessionMode && ffStrEquals(sessionMode, \"classic\"))\n            ffStrbufSetStatic(&result->dePrettyName, FF_DE_PRETTY_GNOME_CLASSIC);\n        else\n            ffStrbufSetStatic(&result->dePrettyName, FF_DE_PRETTY_GNOME);\n    }\n\n    else if(\n        ffStrEqualsIgnCase(name, \"X-Cinnamon\") ||\n        ffStrEqualsIgnCase(name, \"Cinnamon\")\n    ) {\n        ffStrbufSetS(&result->deProcessName, \"cinnamon\");\n        ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_CINNAMON);\n    }\n\n    else if(\n        ffStrEqualsIgnCase(name, \"XFCE\") ||\n        ffStrEqualsIgnCase(name, \"X-XFCE\") ||\n        ffStrEqualsIgnCase(name, \"XFCE4\") ||\n        ffStrEqualsIgnCase(name, \"X-XFCE4\") ||\n        ffStrEqualsIgnCase(name, \"xfce4-session\")\n    ) {\n        ffStrbufSetS(&result->deProcessName, \"xfce4-session\");\n        ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_XFCE4);\n    }\n\n    else if(\n        ffStrEqualsIgnCase(name, \"MATE\") ||\n        ffStrEqualsIgnCase(name, \"X-MATE\") ||\n        ffStrEqualsIgnCase(name, \"mate-session\")\n    ) {\n        ffStrbufSetS(&result->deProcessName, \"mate-session\");\n        ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_MATE);\n    }\n\n    else if(\n        ffStrEqualsIgnCase(name, \"LXQt\") ||\n        ffStrEqualsIgnCase(name, \"X-LXQt\") ||\n        ffStrEqualsIgnCase(name, \"lxqt-session\")\n    ) {\n        ffStrbufSetS(&result->deProcessName, \"lxqt-session\");\n        ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_LXQT);\n        if (result->wmProcessName.length == 0)\n        {\n            FF_STRBUF_AUTO_DESTROY wmProcessNameBuffer = ffStrbufCreate();\n            ffParsePropFileConfig(\"lxqt/session.conf\", \"window_manager =\", &wmProcessNameBuffer);\n            applyBetterWM(result, wmProcessNameBuffer.chars);\n        }\n    }\n\n    else if(\n        ffStrEqualsIgnCase(name, \"Budgie\") ||\n        ffStrEqualsIgnCase(name, \"X-Budgie\") ||\n        ffStrEqualsIgnCase(name, \"budgie-desktop\") ||\n        ffStrEqualsIgnCase(name, \"Budgie:GNOME\")\n    ) {\n        ffStrbufSetS(&result->deProcessName, \"budgie-desktop\");\n        ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_BUDGIE);\n    }\n\n    else if(\n        ffStrEqualsIgnCase(name, \"dtsession\")\n    ) {\n        ffStrbufSetS(&result->deProcessName, \"dtsession\");\n        ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_CDE);\n    }\n\n    else if(\n        ffStrEqualsIgnCase(name, \"ukui-session\")\n    ) {\n        ffStrbufSetS(&result->deProcessName, \"ukui-session\");\n        ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_UKUI);\n    }\n\n    else if(\n        ffStrStartsWithIgnCase(name, \"Unity:Unity\")\n    ) {\n        ffStrbufSetS(&result->deProcessName, \"unity-session\");\n        ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_UNITY);\n    }\n}\n\n\nstatic const char* getFromProcesses(FFDisplayServerResult* result)\n{\n    uint32_t userId = instance.state.platform.uid;\n\n#if __FreeBSD__\n    #ifdef __DragonFly__\n        #define ki_comm kp_comm\n    #endif\n\n    int request[] = {CTL_KERN, KERN_PROC, KERN_PROC_UID, (int) userId};\n    size_t length = 0;\n\n    if(sysctl(request, ARRAY_SIZE(request), NULL, &length, NULL, 0) != 0)\n        return \"sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, NULL) failed\";\n\n    FF_AUTO_FREE struct kinfo_proc* procs = (struct kinfo_proc*) malloc(length);\n    if(sysctl(request, ARRAY_SIZE(request), procs, &length, NULL, 0) != 0)\n        return \"sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed\";\n\n    length /= sizeof(*procs);\n\n    for (struct kinfo_proc* proc = procs; proc < procs + length; ++proc)\n    {\n        if(result->dePrettyName.length == 0)\n            applyPrettyNameIfDE(result, proc->ki_comm);\n\n        if(result->wmPrettyName.length == 0)\n            applyNameIfWM(result, proc->ki_comm);\n\n        if(result->dePrettyName.length > 0 && result->wmPrettyName.length > 0)\n            break;\n    }\n#elif __OpenBSD__\n    kvm_t* kd = kvm_open(NULL, NULL, NULL, KVM_NO_FILES, NULL);\n    int count = 0;\n    const struct kinfo_proc* proc = kvm_getprocs(kd, KERN_PROC_UID, userId, sizeof(*proc), &count);\n    if (proc)\n    {\n        for (int i = 0; i < count; ++i)\n        {\n            if(result->dePrettyName.length == 0)\n                applyPrettyNameIfDE(result, proc[i].p_comm);\n\n            if(result->wmPrettyName.length == 0)\n                applyNameIfWM(result, proc[i].p_comm);\n\n            if(result->dePrettyName.length > 0 && result->wmPrettyName.length > 0)\n                break;\n        }\n    }\n    kvm_close(kd);\n#elif __sun\n    FF_AUTO_CLOSE_DIR DIR* procdir = opendir(\"/proc\");\n    if(procdir == NULL)\n        return \"opendir(\\\"/proc\\\") failed\";\n\n    FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);\n    ffStrbufAppendS(&procPath, \"/proc/\");\n\n    uint32_t procPathLength = procPath.length;\n\n    struct dirent* dirent;\n    while((dirent = readdir(procdir)) != NULL)\n    {\n        if (!ffCharIsDigit(dirent->d_name[0]))\n            continue;\n\n        ffStrbufAppendS(&procPath, dirent->d_name);\n        ffStrbufAppendS(&procPath, \"/psinfo\");\n        psinfo_t proc;\n        if (ffReadFileData(procPath.chars, sizeof(proc), &proc) == sizeof(proc))\n        {\n            ffStrbufSubstrBefore(&procPath, procPathLength);\n\n            if (proc.pr_uid != userId)\n                continue;\n\n            if(result->dePrettyName.length == 0)\n                applyPrettyNameIfDE(result, proc.pr_fname);\n\n            if(result->wmPrettyName.length == 0)\n                applyNameIfWM(result, proc.pr_fname);\n\n            if(result->dePrettyName.length > 0 && result->wmPrettyName.length > 0)\n                break;\n        }\n    }\n#elif __linux__ || __GNU__\n    FF_AUTO_CLOSE_DIR DIR* procdir = opendir(\"/proc\");\n    if(procdir == NULL)\n        return \"opendir(\\\"/proc\\\") failed\";\n\n    FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);\n    ffStrbufAppendS(&procPath, \"/proc/\");\n\n    uint32_t procPathLength = procPath.length;\n\n    FF_STRBUF_AUTO_DESTROY loginuid = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY processName = ffStrbufCreateA(256); //Some processes have large command lines (looking at you chrome)\n\n    struct dirent* dirent;\n    while((dirent = readdir(procdir)) != NULL)\n    {\n        //Match only folders starting with a number (the pid folders)\n        if(dirent->d_type != DT_DIR || !ffCharIsDigit(dirent->d_name[0]))\n            continue;\n\n        ffStrbufAppendS(&procPath, dirent->d_name);\n        uint32_t procFolderPathLength = procPath.length;\n\n        //Don't check for processes not owned by the current user.\n        ffStrbufAppendS(&procPath, \"/loginuid\");\n        ffReadFileBuffer(procPath.chars, &loginuid);\n        if(ffStrbufToUInt(&loginuid, (uint64_t) -1) != userId)\n        {\n            ffStrbufSubstrBefore(&procPath, procPathLength);\n            continue;\n        }\n\n        ffStrbufSubstrBefore(&procPath, procFolderPathLength);\n\n        //We check the cmdline for the process name, because it is not trimmed.\n        ffStrbufAppendS(&procPath, \"/cmdline\");\n        ffReadFileBuffer(procPath.chars, &processName);\n        ffStrbufTrimRightSpace(&processName);\n        ffStrbufSubstrBeforeFirstC(&processName, '\\0'); //Trim the arguments\n        ffStrbufSubstrAfterLastC(&processName, '/');\n\n        ffStrbufSubstrBefore(&procPath, procPathLength);\n\n        if(result->dePrettyName.length == 0)\n            applyPrettyNameIfDE(result, processName.chars);\n\n        if(result->wmPrettyName.length == 0)\n            applyNameIfWM(result, processName.chars);\n\n        if(result->dePrettyName.length > 0 && result->wmPrettyName.length > 0)\n            break;\n    }\n#elif __NetBSD__\n    int request[] = {CTL_KERN, KERN_PROC2, KERN_PROC_UID, (int) userId, sizeof(struct kinfo_proc2), INT_MAX};\n\n    size_t size = 0;\n    if(sysctl(request, ARRAY_SIZE(request), NULL, &size, NULL, 0) != 0)\n        return \"sysctl(KERN_PROC_UID, NULL) failed\";\n\n    FF_AUTO_FREE struct kinfo_proc2* procs = malloc(size);\n\n    if(sysctl(request, ARRAY_SIZE(request), procs, &size, NULL, 0) != 0)\n        return \"sysctl(KERN_PROC_UID, procs) failed\";\n\n    for(struct kinfo_proc2* proc = procs; proc < procs + (size / sizeof(struct kinfo_proc2)); proc++)\n    {\n        if(result->dePrettyName.length == 0)\n            applyPrettyNameIfDE(result, proc->p_comm);\n\n        if(result->wmPrettyName.length == 0)\n            applyNameIfWM(result, proc->p_comm);\n\n        if(result->dePrettyName.length > 0 && result->wmPrettyName.length > 0)\n            break;\n    }\n#endif\n\n    return NULL;\n}\n\nvoid ffdsDetectWMDE(FFDisplayServerResult* result)\n{\n    #if __ANDROID__\n    if(ffStrbufIgnCaseEqualS(&result->wmProtocolName, FF_WM_PROTOCOL_SURFACEFLINGER))\n        return; // Only supported when connected to X11\n    #endif\n\n    const char* env = parseEnv();\n\n    if(result->wmProcessName.length > 0)\n    {\n        //If we found the processName via display server, use it.\n        //This will set the pretty name if it is a known WM, otherwise the prettyName to the processName\n        applyPrettyNameIfWM(result, result->wmProcessName.chars);\n        if(result->wmPrettyName.length == 0)\n            ffStrbufSet(&result->wmPrettyName, &result->wmProcessName);\n    }\n    else\n    {\n        //if env is a known WM, use it\n        applyNameIfWM(result, env);\n    }\n\n    //Connecting to a display server only gives WM results, not DE results.\n    //If we find it in the environment, use that.\n    applyPrettyNameIfDE(result, env);\n\n    //If WM was found by connection to the sever, and DE in the environment, we can return\n    //This way we never call getFromProcDir(), which has slow initialization time\n    if(result->dePrettyName.length > 0 && result->wmPrettyName.length > 0)\n        return;\n\n    //Get missing WM / DE from processes.\n    getFromProcesses(result);\n\n    //Return if both wm and de are set, or if env doesn't contain anything\n    if(\n        (result->wmPrettyName.length > 0 && result->dePrettyName.length > 0) ||\n        !ffStrSet(env)\n    ) return;\n\n    //If nothing is set, use env as WM\n    else if(result->wmPrettyName.length == 0 && result->dePrettyName.length == 0)\n    {\n        ffStrbufSetS(&result->wmProcessName, env);\n        ffStrbufSetS(&result->wmPrettyName, env);\n    }\n\n    //If only WM is not set, and DE doesn't equal env, use env as WM\n    else if(\n        result->wmPrettyName.length == 0 &&\n        ffStrbufIgnCaseCompS(&result->deProcessName, env) != 0 &&\n        ffStrbufIgnCaseCompS(&result->dePrettyName, env) != 0\n    ) {\n        ffStrbufSetS(&result->wmProcessName, env);\n        ffStrbufSetS(&result->wmPrettyName, env);\n    }\n\n    //If only DE is not set, and WM doesn't equal env, use env as DE\n    else if(\n        result->dePrettyName.length == 0 &&\n        ffStrbufIgnCaseCompS(&result->wmProcessName, env) != 0 &&\n        ffStrbufIgnCaseCompS(&result->wmPrettyName, env) != 0\n    ) {\n        ffStrbufSetS(&result->deProcessName, env);\n        ffStrbufSetS(&result->dePrettyName, env);\n    }\n}\n"
  },
  {
    "path": "src/detection/displayserver/linux/xcb.c",
    "content": "#include \"displayserver_linux.h\"\n\n#ifdef FF_HAVE_XCB_RANDR\n\n#include \"common/library.h\"\n#include \"common/properties.h\"\n#include \"common/edidHelper.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/stringUtils.h\"\n\n#include <stdlib.h>\n#include <string.h>\n#include <xcb/randr.h>\n#include <xcb/xcb.h>\n\ntypedef struct XcbRandrData\n{\n    FF_LIBRARY_SYMBOL(xcb_randr_get_screen_resources_current)\n    FF_LIBRARY_SYMBOL(xcb_randr_get_screen_resources_current_reply)\n    FF_LIBRARY_SYMBOL(xcb_randr_get_screen_resources_current_modes_iterator)\n    FF_LIBRARY_SYMBOL(xcb_randr_mode_info_next)\n    FF_LIBRARY_SYMBOL(xcb_randr_get_monitors)\n    FF_LIBRARY_SYMBOL(xcb_randr_get_monitors_reply)\n    FF_LIBRARY_SYMBOL(xcb_randr_get_monitors_monitors_iterator)\n    FF_LIBRARY_SYMBOL(xcb_randr_monitor_info_next)\n    FF_LIBRARY_SYMBOL(xcb_randr_monitor_info_outputs_length)\n    FF_LIBRARY_SYMBOL(xcb_randr_monitor_info_outputs)\n    FF_LIBRARY_SYMBOL(xcb_randr_output_next)\n    FF_LIBRARY_SYMBOL(xcb_randr_get_output_info)\n    FF_LIBRARY_SYMBOL(xcb_randr_get_output_info_reply)\n    FF_LIBRARY_SYMBOL(xcb_randr_get_crtc_info)\n    FF_LIBRARY_SYMBOL(xcb_randr_get_crtc_info_reply)\n    FF_LIBRARY_SYMBOL(xcb_randr_get_output_property)\n    FF_LIBRARY_SYMBOL(xcb_randr_get_output_property_reply)\n    FF_LIBRARY_SYMBOL(xcb_randr_get_output_property_data)\n    FF_LIBRARY_SYMBOL(xcb_randr_get_output_property_data_length)\n\n    FF_LIBRARY_SYMBOL(xcb_intern_atom)\n    FF_LIBRARY_SYMBOL(xcb_intern_atom_reply)\n    FF_LIBRARY_SYMBOL(xcb_get_property)\n    FF_LIBRARY_SYMBOL(xcb_get_property_reply)\n    FF_LIBRARY_SYMBOL(xcb_get_property_value)\n    FF_LIBRARY_SYMBOL(xcb_get_property_value_length)\n    FF_LIBRARY_SYMBOL(xcb_get_atom_name)\n    FF_LIBRARY_SYMBOL(xcb_get_atom_name_name)\n    FF_LIBRARY_SYMBOL(xcb_get_atom_name_name_length)\n    FF_LIBRARY_SYMBOL(xcb_get_atom_name_reply)\n    FF_LIBRARY_SYMBOL(xcb_get_setup)\n    FF_LIBRARY_SYMBOL(xcb_setup_vendor)\n    FF_LIBRARY_SYMBOL(xcb_setup_vendor_length)\n\n    //init once\n    xcb_connection_t* connection;\n    FFDisplayServerResult* result;\n} XcbRandrData;\n\nstatic void* xcbGetProperty(XcbRandrData* data, xcb_window_t window, const char* request)\n{\n    xcb_intern_atom_cookie_t requestAtomCookie = data->ffxcb_intern_atom(data->connection, true, (uint16_t) strlen(request), request);\n    FF_AUTO_FREE xcb_intern_atom_reply_t* requestAtomReply = data->ffxcb_intern_atom_reply(data->connection, requestAtomCookie, NULL);\n    if(requestAtomReply == NULL)\n        return NULL;\n\n    xcb_get_property_cookie_t propertyCookie = data->ffxcb_get_property(data->connection, false, window, requestAtomReply->atom, XCB_ATOM_ANY, 0, 8 * 1024);\n    FF_AUTO_FREE xcb_get_property_reply_t* propertyReply = data->ffxcb_get_property_reply(data->connection, propertyCookie, NULL);\n    if(propertyReply == NULL)\n        return NULL;\n\n    int length = data->ffxcb_get_property_value_length(propertyReply);\n    if(length <= 0)\n        return NULL;\n\n    //Why are xcb property strings not null terminated???\n    void* replyValue = malloc((size_t)length + 1);\n    memcpy(replyValue, data->ffxcb_get_property_value(propertyReply), (size_t) length);\n    ((char*) replyValue)[length] = '\\0';\n\n    return replyValue;\n}\n\nstatic xcb_randr_get_output_property_reply_t* xcbRandrGetProperty(XcbRandrData* data, xcb_randr_output_t output, const char* name)\n{\n    xcb_intern_atom_cookie_t requestAtomCookie = data->ffxcb_intern_atom(data->connection, true, (uint16_t) strlen(name), name);\n    FF_AUTO_FREE xcb_intern_atom_reply_t* requestAtomReply = data->ffxcb_intern_atom_reply(data->connection, requestAtomCookie, NULL);\n\n    if(requestAtomReply)\n    {\n        xcb_randr_get_output_property_cookie_t outputPropertyCookie = data->ffxcb_randr_get_output_property(data->connection, output, requestAtomReply->atom, XCB_GET_PROPERTY_TYPE_ANY, 0, 100, false, false);\n        return data->ffxcb_randr_get_output_property_reply(data->connection, outputPropertyCookie, NULL);\n    }\n    return NULL;\n}\n\nstatic void xcbDetectWMfromEWMH(XcbRandrData* data, xcb_window_t rootWindow, FFDisplayServerResult* result)\n{\n    if(result->wmProcessName.length > 0 || ffStrbufEqualS(&result->wmProtocolName, FF_WM_PROTOCOL_WAYLAND))\n        return;\n\n    FF_AUTO_FREE xcb_window_t* wmWindow = (xcb_window_t*) xcbGetProperty(data, rootWindow, \"_NET_SUPPORTING_WM_CHECK\");\n    if(wmWindow == NULL)\n        return;\n\n    FF_AUTO_FREE char* wmName = (char*) xcbGetProperty(data, *wmWindow, \"WM_NAME\");\n    if(!ffStrSet(wmName))\n        wmName = (char*) xcbGetProperty(data, *wmWindow, \"_NET_WM_NAME\");\n\n    if(!ffStrSet(wmName))\n        return;\n\n    ffStrbufSetS(&result->wmProcessName, wmName);\n}\n\nstatic void xcbFetchServerVendor(XcbRandrData* data, FFDisplayServerResult* result)\n{\n    const xcb_setup_t* setup = data->ffxcb_get_setup(data->connection);\n\n    int length = data->ffxcb_setup_vendor_length(setup);\n    if(length <= 0)\n        return;\n\n    FF_STRBUF_AUTO_DESTROY serverVendor = ffStrbufCreateNS((uint32_t) length, data->ffxcb_setup_vendor(setup));\n\n    if (!ffStrbufEqualS(&serverVendor, \"The X.Org Foundation\")) // Original\n    {\n        ffStrbufDestroy(&result->wmProtocolName);\n        ffStrbufInitMove(&result->wmProtocolName, &serverVendor);\n    }\n}\n\nstatic bool xcbRandrHandleOutput(XcbRandrData* data, xcb_randr_output_t output, FFstrbuf* name, bool primary, FFDisplayType displayType, struct xcb_randr_get_screen_resources_current_reply_t* screenResources, uint8_t bitDepth, uint32_t dpi)\n{\n    xcb_randr_get_output_info_cookie_t outputInfoCookie = data->ffxcb_randr_get_output_info(data->connection, output, XCB_CURRENT_TIME);\n    FF_AUTO_FREE xcb_randr_get_output_info_reply_t* outputInfoReply = data->ffxcb_randr_get_output_info_reply(data->connection, outputInfoCookie, NULL);\n    if(outputInfoReply == NULL)\n        return false;\n\n    FF_AUTO_FREE xcb_randr_get_output_property_reply_t* edidReply = xcbRandrGetProperty(data, output, \"EDID\");\n    uint8_t* edidData = NULL;\n    uint32_t edidLength = 0;\n    if(edidReply)\n    {\n        int len = data->ffxcb_randr_get_output_property_data_length(edidReply);\n        if(len >= 128)\n        {\n            edidData = data->ffxcb_randr_get_output_property_data(edidReply);\n            edidLength = (uint32_t) len;\n        }\n    }\n\n    if(edidData)\n    {\n        ffStrbufClear(name);\n        ffEdidGetName(edidData, name);\n    }\n\n    bool randrEmulation = false;\n    FF_AUTO_FREE xcb_randr_get_output_property_reply_t* randrEmulationReply = xcbRandrGetProperty(data, output, \"RANDR Emulation\");\n    if(randrEmulationReply)\n    {\n        int len = data->ffxcb_randr_get_output_property_data_length(randrEmulationReply);\n        if(len >= 1)\n            randrEmulation = !!data->ffxcb_randr_get_output_property_data(randrEmulationReply)[0];\n    }\n\n    xcb_randr_get_crtc_info_cookie_t crtcInfoCookie = data->ffxcb_randr_get_crtc_info(data->connection, outputInfoReply->crtc, XCB_CURRENT_TIME);\n    FF_AUTO_FREE xcb_randr_get_crtc_info_reply_t* crtcInfoReply = data->ffxcb_randr_get_crtc_info_reply(data->connection, crtcInfoCookie, NULL);\n    if(crtcInfoReply == NULL)\n        return false;\n\n    uint32_t rotation;\n    switch (crtcInfoReply->rotation)\n    {\n        case XCB_RANDR_ROTATION_ROTATE_90:\n            rotation = 90;\n            break;\n        case XCB_RANDR_ROTATION_ROTATE_180:\n            rotation = 180;\n            break;\n        case XCB_RANDR_ROTATION_ROTATE_270:\n            rotation = 270;\n            break;\n        default:\n            rotation = 0;\n            break;\n    }\n\n    xcb_randr_mode_info_t* currentMode = NULL;\n    xcb_randr_mode_info_t* preferredMode = NULL;\n\n    if(screenResources)\n    {\n        xcb_randr_mode_info_iterator_t modesIterator = data->ffxcb_randr_get_screen_resources_current_modes_iterator(screenResources);\n\n        if (outputInfoReply->num_preferred > 0)\n            preferredMode = modesIterator.data;\n\n        while (modesIterator.rem > 0)\n        {\n            if (modesIterator.data->id == crtcInfoReply->mode)\n            {\n                currentMode = modesIterator.data;\n                break;\n            }\n\n            data->ffxcb_randr_mode_info_next(&modesIterator);\n        }\n    }\n\n    FFDisplayResult* item = ffdsAppendDisplay(\n        data->result,\n        (uint32_t) (currentMode ? currentMode->width : crtcInfoReply->width),\n        (uint32_t) (currentMode ? currentMode->height : crtcInfoReply->height),\n        currentMode ? (double) currentMode->dot_clock / (double) ((uint32_t) currentMode->htotal * currentMode->vtotal) : 0,\n        dpi,\n        preferredMode ? (uint32_t) preferredMode->width : 0,\n        preferredMode ? (uint32_t) preferredMode->height : 0,\n        preferredMode ? (double) preferredMode->dot_clock / (double) ((uint32_t) preferredMode->htotal * preferredMode->vtotal) : 0,\n        rotation,\n        name,\n        displayType,\n        primary,\n        0,\n        (uint32_t) outputInfoReply->mm_width,\n        (uint32_t) outputInfoReply->mm_height,\n        randrEmulation\n            ? (currentMode ? \"xcb-randr-emu-mode\" : \"xcb-randr-emu-crtc\")\n            : (currentMode ? \"xcb-randr-mode\" : \"xcb-randr-crtc\")\n\n    );\n    if (item)\n    {\n        if (edidData && edidLength >= 128)\n        {\n            item->hdrStatus = ffEdidGetHdrCompatible(edidData, (uint32_t) edidLength) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED;\n            ffEdidGetSerialAndManufactureDate(edidData, &item->serial, &item->manufactureYear, &item->manufactureWeek);\n        }\n        item->bitDepth = bitDepth;\n        if ((rotation == 90 || rotation == 180) && !randrEmulation)\n        {\n            // In XWayland mode, width / height has been swapped out of box\n            uint32_t tmp = item->width;\n            item->width = item->height;\n            item->height = tmp;\n        }\n    }\n\n    return !!item;\n}\n\nstatic bool xcbRandrHandleMonitor(XcbRandrData* data, xcb_randr_monitor_info_t* monitor, struct xcb_randr_get_screen_resources_current_reply_t* screenResources, uint8_t bitDepth, uint32_t dpi)\n{\n    //for some reasons, we have to construct this our self\n    xcb_randr_output_iterator_t outputIterator = {\n        .index = 0,\n        .data = data->ffxcb_randr_monitor_info_outputs(monitor),\n        .rem = data->ffxcb_randr_monitor_info_outputs_length(monitor)\n    };\n\n    FF_AUTO_FREE xcb_get_atom_name_reply_t* nameReply = data->ffxcb_get_atom_name_reply(\n        data->connection,\n        data->ffxcb_get_atom_name(data->connection, monitor->name),\n        NULL\n    );\n    FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateNS(\n        (uint32_t) data->ffxcb_get_atom_name_name_length(nameReply),\n        data->ffxcb_get_atom_name_name(nameReply)\n    );\n    const FFDisplayType displayType = ffdsGetDisplayType(name.chars);\n\n    bool foundOutput = false;\n\n    while(outputIterator.rem > 0)\n    {\n        if(xcbRandrHandleOutput(data, *outputIterator.data, &name, monitor->primary, displayType, screenResources, bitDepth, dpi))\n            foundOutput = true;\n        data->ffxcb_randr_output_next(&outputIterator);\n    }\n\n    if (foundOutput) return true;\n\n    FFDisplayResult* display = ffdsAppendDisplay(\n        data->result,\n        (uint32_t) monitor->width,\n        (uint32_t) monitor->height,\n        0,\n        dpi,\n        0, 0, 0,\n        0,\n        &name,\n        displayType,\n        !!monitor->primary,\n        0,\n        (uint32_t) monitor->width_in_millimeters,\n        (uint32_t) monitor->height_in_millimeters,\n        \"xcb-randr-monitor\"\n    );\n    if (display) display->bitDepth = bitDepth;\n    return !!display;\n}\n\nstatic bool xcbRandrHandleMonitors(XcbRandrData* data, xcb_screen_t* screen)\n{\n    xcb_randr_get_monitors_cookie_t monitorsCookie = data->ffxcb_randr_get_monitors(data->connection, screen->root, true);\n    FF_AUTO_FREE xcb_randr_get_monitors_reply_t* monitorsReply = data->ffxcb_randr_get_monitors_reply(data->connection, monitorsCookie, NULL);\n    if(monitorsReply == NULL)\n        return false;\n\n    //Init screen resources. They are used to iterate over all modes. xcbRandrHandleMode checks for \" == NULL\", to fail as late as possible.\n    xcb_randr_get_screen_resources_current_cookie_t screenResourcesCookie = data->ffxcb_randr_get_screen_resources_current(data->connection, screen->root);\n    FF_AUTO_FREE struct xcb_randr_get_screen_resources_current_reply_t* screenResources = data->ffxcb_randr_get_screen_resources_current_reply(data->connection, screenResourcesCookie, NULL);\n\n    uint32_t dpi = 0;\n    FF_AUTO_FREE const char* resourceManager = xcbGetProperty(data, screen->root, \"RESOURCE_MANAGER\");\n    if (resourceManager)\n    {\n        FF_STRBUF_AUTO_DESTROY dpiStr = ffStrbufCreate();\n        if (ffParsePropLines(resourceManager, \"Xft.dpi:\", &dpiStr))\n            dpi = (uint32_t) ffStrbufToUInt(&dpiStr, 96);\n    }\n    uint8_t bitDepth = (uint8_t) (screen->root_depth / 3);\n\n    xcb_randr_monitor_info_iterator_t monitorInfoIterator = data->ffxcb_randr_get_monitors_monitors_iterator(monitorsReply);\n\n    bool foundMonitor = false;\n\n    while(monitorInfoIterator.rem > 0)\n    {\n        if(xcbRandrHandleMonitor(data, monitorInfoIterator.data, screenResources, bitDepth, dpi))\n            foundMonitor = true;\n        data->ffxcb_randr_monitor_info_next(&monitorInfoIterator);\n    }\n\n    return foundMonitor;\n}\n\nstatic void xcbRandrHandleScreen(XcbRandrData* data, xcb_screen_t* screen)\n{\n    //With all the initialisation done, start the detection\n    if(xcbRandrHandleMonitors(data, screen))\n        return;\n\n    //If detetction failed, fallback to screen = monitor, like in the libxcb.so implementation\n    ffdsAppendDisplay(\n        data->result,\n        (uint32_t) screen->width_in_pixels,\n        (uint32_t) screen->height_in_pixels,\n        0,\n        0,\n        0, 0, 0,\n        0,\n        NULL,\n        FF_DISPLAY_TYPE_UNKNOWN,\n        false,\n        (uint64_t) screen->root,\n        (uint32_t) screen->width_in_millimeters,\n        (uint32_t) screen->height_in_millimeters,\n        \"xcb-randr-screen\"\n    );\n}\n\nconst char* ffdsConnectXcbRandr(FFDisplayServerResult* result)\n{\n    FF_LIBRARY_LOAD_MESSAGE(xcbRandr, \"libxcb-randr\" FF_LIBRARY_EXTENSION, 1)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_connect)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_connection_has_error)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_get_setup)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_setup_roots_iterator)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_screen_next)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_disconnect)\n\n    XcbRandrData data;\n\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_intern_atom)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_intern_atom_reply)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_property)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_property_reply)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_property_value)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_property_value_length)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_atom_name)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_atom_name_name)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_atom_name_name_length)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_atom_name_reply)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_setup)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_setup_vendor)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_setup_vendor_length)\n\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_screen_resources_current)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_screen_resources_current_reply)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_screen_resources_current_modes_iterator)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_mode_info_next)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_monitors)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_monitors_reply)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_monitors_monitors_iterator)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_monitor_info_next)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_monitor_info_outputs_length)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_monitor_info_outputs)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_output_next)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_info)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_info_reply)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_property)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_property_reply)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_property_data)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_property_data_length)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_crtc_info)\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_crtc_info_reply)\n\n\n    data.connection = ffxcb_connect(NULL, NULL);\n    if(ffxcb_connection_has_error(data.connection) > 0)\n    {\n        ffxcb_disconnect(data.connection);\n        return \"xcb_connect() failed\";\n    }\n\n\n    data.result = result;\n\n    xcb_screen_iterator_t iterator = ffxcb_setup_roots_iterator(ffxcb_get_setup(data.connection));\n\n\n    if(iterator.rem > 0) {\n        xcbDetectWMfromEWMH(&data, iterator.data->root, result);\n        xcbFetchServerVendor(&data, result);\n    }\n\n\n    while(iterator.rem > 0)\n    {\n        xcbRandrHandleScreen(&data, iterator.data);\n        ffxcb_screen_next(&iterator);\n    }\n\n    ffxcb_disconnect(data.connection);\n\n\n    //If wayland hasn't set this, connection failed for it. So we are running only a X Server, not XWayland.\n    if(result->wmProtocolName.length == 0)\n        ffStrbufSetS(&result->wmProtocolName, FF_WM_PROTOCOL_X11);\n\n    return NULL;\n}\n\n#else\n\nconst char* ffdsConnectXcbRandr(FFDisplayServerResult* result)\n{\n    //Do nothing. There are other implementations coming\n    FF_UNUSED(result)\n    return \"Fastfetch was compiled without libxcb-randr support\";\n}\n\n#endif\n"
  },
  {
    "path": "src/detection/displayserver/linux/xlib.c",
    "content": "#include \"displayserver_linux.h\"\n\n#ifdef FF_HAVE_XRANDR\n\n#include \"common/library.h\"\n#include \"common/properties.h\"\n#include \"common/edidHelper.h\"\n#include \"common/stringUtils.h\"\n\n#include <X11/extensions/Xrandr.h>\n#include <X11/Xlib.h>\n\ntypedef struct XrandrData\n{\n    FF_LIBRARY_SYMBOL(XInternAtom)\n    FF_LIBRARY_SYMBOL(XGetAtomName)\n    FF_LIBRARY_SYMBOL(XGetWindowProperty)\n    FF_LIBRARY_SYMBOL(XServerVendor)\n    FF_LIBRARY_SYMBOL(XFree)\n    FF_LIBRARY_SYMBOL(XRRGetMonitors)\n    FF_LIBRARY_SYMBOL(XRRGetScreenResourcesCurrent)\n    FF_LIBRARY_SYMBOL(XRRGetOutputInfo)\n    FF_LIBRARY_SYMBOL(XRRGetOutputProperty)\n    FF_LIBRARY_SYMBOL(XRRGetCrtcInfo)\n    FF_LIBRARY_SYMBOL(XRRFreeCrtcInfo)\n    FF_LIBRARY_SYMBOL(XRRFreeOutputInfo)\n    FF_LIBRARY_SYMBOL(XRRFreeScreenResources)\n    FF_LIBRARY_SYMBOL(XRRFreeMonitors)\n\n    //Init once\n    Display* display;\n    FFDisplayServerResult* result;\n} XrandrData;\n\nstatic unsigned char* x11GetProperty(XrandrData* data, Display* display, Window window, const char* request)\n{\n    Atom requestAtom = data->ffXInternAtom(display, request, False);\n    if(requestAtom == None)\n        return NULL;\n\n    Atom actualType;\n    unsigned long unused;\n    unsigned char* result = NULL;\n\n    if(data->ffXGetWindowProperty(display, window, requestAtom, 0, 64, False, AnyPropertyType, &actualType, (int*) &unused, &unused, &unused, &result) != Success)\n        return NULL;\n\n    return result;\n}\n\nstatic uint8_t* xrandrGetProperty(XrandrData* data, RROutput output, const char* name, uint32_t* bufSize)\n{\n    unsigned long size = 0;\n    uint8_t* result = NULL;\n    Atom atomEdid = data->ffXInternAtom(data->display, name, true);\n    if (atomEdid != None)\n    {\n        int actual_format = 0;\n        unsigned long bytes_after = 0;\n        Atom actual_type = None;\n        if (data->ffXRRGetOutputProperty(data->display, output, atomEdid, 0, 100, false, false, AnyPropertyType, &actual_type, &actual_format, &size, &bytes_after, &result) == Success)\n        {\n            if (size == 0)\n                data->ffXFree(result);\n            else\n            {\n                if (bufSize)\n                    *bufSize = (uint32_t) size;\n                return result;\n            }\n        }\n    }\n\n    return NULL;\n}\n\nstatic void x11DetectWMFromEWMH(XrandrData* data, FFDisplayServerResult* result)\n{\n    if(result->wmProcessName.length > 0 || ffStrbufEqualS(&result->wmProtocolName, FF_WM_PROTOCOL_WAYLAND))\n        return;\n\n    Window* wmWindow = (Window*) x11GetProperty(data, data->display, DefaultRootWindow(data->display), \"_NET_SUPPORTING_WM_CHECK\");\n    if(wmWindow == NULL)\n        return;\n\n    char* wmName = (char*) x11GetProperty(data, data->display, *wmWindow, \"WM_NAME\");\n    if(!ffStrSet(wmName))\n        wmName = (char*) x11GetProperty(data, data->display, *wmWindow, \"_NET_WM_NAME\");\n\n    if(ffStrSet(wmName))\n        ffStrbufSetS(&result->wmProcessName, wmName);\n\n    data->ffXFree(wmName);\n    data->ffXFree(wmWindow);\n}\n\nstatic void x11FetchServerVendor(XrandrData* data, FFDisplayServerResult* result)\n{\n    const char* serverVendor = data->ffXServerVendor(data->display);\n    if (serverVendor && !ffStrEquals(serverVendor, \"The X.Org Foundation\"))\n        ffStrbufSetS(&result->wmProtocolName, serverVendor);\n}\n\nstatic bool xrandrHandleCrtc(XrandrData* data, XRROutputInfo* output, FFstrbuf* name, bool primary, FFDisplayType displayType, uint8_t* edidData, uint32_t edidLength, XRRScreenResources* screenResources, uint8_t bitDepth, uint32_t dpi, bool randrEmulation)\n{\n    //We do the check here, because we want the best fallback display if this call failed\n    if(screenResources == NULL)\n        return false;\n\n    XRRCrtcInfo* crtcInfo = data->ffXRRGetCrtcInfo(data->display, screenResources, output->crtc);\n    if(crtcInfo == NULL)\n        return false;\n\n    uint32_t rotation;\n    switch (crtcInfo->rotation)\n    {\n        case RR_Rotate_90:\n            rotation = 90;\n            break;\n        case RR_Rotate_180:\n            rotation = 180;\n            break;\n        case RR_Rotate_270:\n            rotation = 270;\n            break;\n        default:\n            rotation = 0;\n            break;\n    }\n\n    XRRModeInfo* currentMode = NULL;\n    for(int i = 0; i < screenResources->nmode; i++)\n    {\n        if(screenResources->modes[i].id == crtcInfo->mode)\n        {\n            currentMode = &screenResources->modes[i];\n            break;\n        }\n    }\n\n    XRRModeInfo* preferredMode = output->npreferred > 0 ? &screenResources->modes[0] : NULL;\n\n    FFDisplayResult* item = ffdsAppendDisplay(\n        data->result,\n        (uint32_t) (currentMode ? currentMode->width : crtcInfo->width),\n        (uint32_t) (currentMode ? currentMode->height : crtcInfo->height),\n        currentMode ? (double) currentMode->dotClock / (double) ((uint32_t) currentMode->hTotal * currentMode->vTotal) : 0,\n        dpi,\n        preferredMode ? (uint32_t) preferredMode->width : 0,\n        preferredMode ? (uint32_t) preferredMode->height : 0,\n        preferredMode ? (double) preferredMode->dotClock / (double) ((uint32_t) preferredMode->hTotal * preferredMode->vTotal) : 0,\n        rotation,\n        name,\n        displayType,\n        primary,\n        0,\n        (uint32_t) output->mm_width,\n        (uint32_t) output->mm_height,\n        randrEmulation\n            ? (currentMode ? \"xlib-randr-emu-mode\" : \"xlib-randr-emu-crtc\")\n            : (currentMode ? \"xlib-randr-mode\" : \"xlib-randr-crtc\")\n    );\n\n    if (item)\n    {\n        if (edidLength)\n        {\n            item->hdrStatus = ffEdidGetHdrCompatible(edidData, edidLength) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED;\n            ffEdidGetSerialAndManufactureDate(edidData, &item->serial, &item->manufactureYear, &item->manufactureWeek);\n        }\n        item->bitDepth = bitDepth;\n        if ((rotation == 90 || rotation == 180) && !randrEmulation)\n        {\n            // In XWayland mode, width / height has been swapped out of box\n            uint32_t tmp = item->width;\n            item->width = item->height;\n            item->height = tmp;\n        }\n    }\n\n    data->ffXRRFreeCrtcInfo(crtcInfo);\n    return !!item;\n}\n\nstatic bool xrandrHandleOutput(XrandrData* data, RROutput output, FFstrbuf* name, bool primary, FFDisplayType displayType, XRRScreenResources* screenResources, uint8_t bitDepth, uint32_t dpi)\n{\n    XRROutputInfo* outputInfo = data->ffXRRGetOutputInfo(data->display, screenResources, output);\n    if(outputInfo == NULL)\n        return false;\n\n    uint32_t edidLength = 0;\n    uint8_t* edidData = xrandrGetProperty(data, output, RR_PROPERTY_RANDR_EDID, &edidLength);\n\n    if (edidLength >= 128)\n    {\n        ffStrbufClear(name);\n        ffEdidGetName(edidData, name);\n    }\n    else\n        edidLength = 0;\n\n    uint8_t* randrEmulation = xrandrGetProperty(data, output, \"RANDR Emulation\", NULL);\n\n    bool res = xrandrHandleCrtc(data, outputInfo, name, primary, displayType, edidData, edidLength, screenResources, bitDepth, dpi, randrEmulation ? !!randrEmulation[0] : false);\n\n    if (edidData)\n        data->ffXFree(edidData);\n    if (randrEmulation)\n        data->ffXFree(randrEmulation);\n    data->ffXRRFreeOutputInfo(outputInfo);\n\n    return res;\n}\n\nstatic bool xrandrHandleMonitor(XrandrData* data, XRRMonitorInfo* monitorInfo, XRRScreenResources* screenResources, uint8_t bitDepth, uint32_t dpi)\n{\n    bool foundOutput = false;\n    char* xname = data->ffXGetAtomName(data->display, monitorInfo->name);\n    FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateS(xname);\n    data->ffXFree(xname);\n    FFDisplayType displayType = ffdsGetDisplayType(name.chars);\n    for(int i = 0; i < monitorInfo->noutput; i++)\n    {\n        if(xrandrHandleOutput(data, monitorInfo->outputs[i], &name, monitorInfo->primary, displayType, screenResources, bitDepth, dpi))\n            foundOutput = true;\n    }\n\n    if (foundOutput) return true;\n\n    FFDisplayResult* display = ffdsAppendDisplay(\n        data->result,\n        (uint32_t) monitorInfo->width,\n        (uint32_t) monitorInfo->height,\n        0,\n        dpi,\n        0, 0, 0,\n        0,\n        &name,\n        displayType,\n        !!monitorInfo->primary,\n        0,\n        (uint32_t) monitorInfo->mwidth,\n        (uint32_t) monitorInfo->mheight,\n        \"xlib-randr-monitor\"\n    );\n    if (display) display->bitDepth = bitDepth;\n    return !!display;\n}\n\nstatic bool xrandrHandleMonitors(XrandrData* data, Screen* screen)\n{\n    int numberOfMonitors;\n    XRRMonitorInfo* monitorInfos = data->ffXRRGetMonitors(data->display, RootWindowOfScreen(screen), True, &numberOfMonitors);\n    if(monitorInfos == NULL)\n        return false;\n\n    XRRScreenResources* screenResources = data->ffXRRGetScreenResourcesCurrent(data->display, RootWindowOfScreen(screen));\n\n    uint32_t dpi = 1;\n    char* resourceManager = (char*) x11GetProperty(data, data->display, screen->root, \"RESOURCE_MANAGER\");\n    if (resourceManager)\n    {\n        FF_STRBUF_AUTO_DESTROY dpiStr = ffStrbufCreate();\n        if (ffParsePropLines(resourceManager, \"Xft.dpi:\", &dpiStr))\n            dpi = (uint32_t) ffStrbufToUInt(&dpiStr, 96);\n        data->ffXFree(resourceManager);\n    }\n    uint8_t bitDepth = (uint8_t) (screen->root_depth / 3);\n\n    bool foundAMonitor = false;\n\n    for(int i = 0; i < numberOfMonitors; i++)\n    {\n        if(xrandrHandleMonitor(data, &monitorInfos[i], screenResources, bitDepth, dpi))\n            foundAMonitor = true;\n    }\n\n    data->ffXRRFreeMonitors(monitorInfos);\n    data->ffXRRFreeScreenResources(screenResources);\n\n    return foundAMonitor;\n}\n\nstatic void xrandrHandleScreen(XrandrData* data, Screen* screen)\n{\n    if(xrandrHandleMonitors(data, screen))\n        return;\n\n    //Fallback to screen\n    ffdsAppendDisplay(\n        data->result,\n        (uint32_t) WidthOfScreen(screen),\n        (uint32_t) HeightOfScreen(screen),\n        0,\n        0,\n        0, 0, 0,\n        0,\n        NULL,\n        FF_DISPLAY_TYPE_UNKNOWN,\n        false,\n        RootWindowOfScreen(screen),\n        (uint32_t) WidthMMOfScreen(screen),\n        (uint32_t) HeightMMOfScreen(screen),\n        \"xlib-randr-screen\"\n    );\n}\n\nconst char* ffdsConnectXrandr(FFDisplayServerResult* result)\n{\n    FF_LIBRARY_LOAD_MESSAGE(xrandr, \"libXrandr\" FF_LIBRARY_EXTENSION, 3)\n\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xrandr, XOpenDisplay)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xrandr, XCloseDisplay)\n\n    XrandrData data;\n\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XInternAtom);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XGetAtomName);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XGetWindowProperty);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XServerVendor);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XFree);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRGetMonitors);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRGetScreenResourcesCurrent);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRGetOutputInfo);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRGetOutputProperty);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRGetCrtcInfo);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRFreeCrtcInfo);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRFreeOutputInfo);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRFreeScreenResources);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRFreeMonitors);\n\n    data.display = ffXOpenDisplay(NULL);\n    if(data.display == NULL)\n        return \"XOpenDisplay() failed\";\n\n    if(ScreenCount(data.display) > 0) {\n        x11DetectWMFromEWMH(&data, result);\n        x11FetchServerVendor(&data, result);\n    }\n\n    data.result = result;\n\n    for(int i = 0; i < ScreenCount(data.display); i++)\n        xrandrHandleScreen(&data, ScreenOfDisplay(data.display, i));\n\n    ffXCloseDisplay(data.display);\n\n    //If wayland hasn't set this, connection failed for it. So we are running only a X Server, not XWayland.\n    if(result->wmProtocolName.length == 0)\n        ffStrbufSetS(&result->wmProtocolName, FF_WM_PROTOCOL_X11);\n\n    return NULL;\n}\n\n#else\n\nconst char* ffdsConnectXrandr(FFDisplayServerResult* result)\n{\n    //Do nothing here. There are more x11 implementations to come.\n    FF_UNUSED(result);\n    return \"Fastfetch was compiled without libXrandr support\";\n}\n\n#endif // FF_HAVE_XRANDR\n"
  },
  {
    "path": "src/detection/dns/dns.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/dns/option.h\"\n\nconst char* ffDetectDNS(FFDNSOptions* options, FFlist* results /* list of FFstrbuf */);\n"
  },
  {
    "path": "src/detection/dns/dns_apple.c",
    "content": "#include \"detection/dns/dns.h\"\n\n#include \"common/io.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/stringUtils.h\"\n#include \"common/apple/cf_helpers.h\"\n#include \"common/debug.h\"\n\n#include <SystemConfiguration/SystemConfiguration.h>\n\nstatic const char* detectDnsFromConf(const char* path, FFDNSOptions* options, FFlist* results)\n{\n    FF_DEBUG(\"Attempting to read DNS config from %s\", path);\n\n    FF_AUTO_CLOSE_FILE FILE* file = fopen(path, \"r\");\n    if (!file)\n    {\n        FF_DEBUG(\"Failed to open %s: %m\", path);\n        return \"fopen(path, r) failed\";\n    }\n\n    if (results->length > 0)\n    {\n        FF_DEBUG(\"Clearing existing DNS entries (%u entries)\", results->length);\n        FF_LIST_FOR_EACH(FFstrbuf, item, *results)\n            ffStrbufDestroy(item);\n        ffListClear(results);\n    }\n\n    FF_AUTO_FREE char* line = NULL;\n    size_t len = 0;\n\n    while (getline(&line, &len, file) != -1)\n    {\n        if (ffStrStartsWith(line, \"nameserver\"))\n        {\n            char* nameserver = line + strlen(\"nameserver\");\n            while (*nameserver == ' ' || *nameserver == '\\t')\n                nameserver++;\n            if (*nameserver == '\\0') continue;\n\n            char* comment = strchr(nameserver, '#');\n            if (comment) *comment = '\\0';\n\n            if ((ffStrContainsC(nameserver, ':') && !(options->showType & FF_DNS_TYPE_IPV6_BIT)) ||\n                (ffStrContainsC(nameserver, '.') && !(options->showType & FF_DNS_TYPE_IPV4_BIT)))\n                continue;\n\n            FFstrbuf* item = (FFstrbuf*) ffListAdd(results);\n            ffStrbufInitS(item, nameserver);\n            ffStrbufTrimRightSpace(item);\n            FF_DEBUG(\"Found DNS server: %s\", item->chars);\n        }\n    }\n\n    FF_DEBUG(\"Found %u DNS servers in %s\", results->length, path);\n    return NULL;\n}\n\nconst char* ffDetectDNS(FFDNSOptions* options, FFlist* results)\n{\n    // Handle macOS-specific DNS configurations\n    FF_DEBUG(\"Using SystemConfiguration framework for macOS\");\n\n    // Create a reference to the dynamic store\n    FF_CFTYPE_AUTO_RELEASE SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR(\"fastfetch\"), NULL, NULL);\n    if (store)\n    {\n        // Get the network global IPv4 and IPv6 configuration\n        FF_CFTYPE_AUTO_RELEASE CFStringRef key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetDNS);\n        if (key)\n        {\n            FF_CFTYPE_AUTO_RELEASE CFDictionaryRef dict = SCDynamicStoreCopyValue(store, key);\n            if (dict)\n            {\n                // Get the DNS server addresses array\n                CFArrayRef dnsServers = CFDictionaryGetValue(dict, kSCPropNetDNSServerAddresses);\n\n                if (dnsServers)\n                {\n                    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n                    for (CFIndex i = 0; i < CFArrayGetCount(dnsServers); i++)\n                    {\n                        if (ffCfStrGetString(CFArrayGetValueAtIndex(dnsServers, i), &buffer) == NULL)\n                        {\n                            // Check if the address matches our filter\n                            if ((ffStrbufContainC(&buffer, ':') && !(options->showType & FF_DNS_TYPE_IPV6_BIT)) ||\n                                (ffStrbufContainC(&buffer, '.') && !(options->showType & FF_DNS_TYPE_IPV4_BIT)))\n                                continue;\n\n                            // Add to results\n                            FFstrbuf* item = (FFstrbuf*) ffListAdd(results);\n                            ffStrbufInitMove(item, &buffer);\n                            FF_DEBUG(\"Found DNS server on macOS: %s\", item->chars);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    // If we didn't find any servers, try resolv.conf as fallback\n    if (results->length > 0)\n        return NULL;\n\n    FF_DEBUG(\"No DNS servers found via SystemConfiguration, trying resolv.conf\");\n    // Try standard resolv.conf location on macOS as a fallback\n    return detectDnsFromConf(\"/var/run/resolv.conf\", options, results);\n}\n"
  },
  {
    "path": "src/detection/dns/dns_linux.c",
    "content": "#include \"detection/dns/dns.h\"\n\n#include \"common/io.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/stringUtils.h\"\n#include \"common/debug.h\"\n\n#ifdef __HAIKU__\n#define RESOLV_CONF \"/system/settings/network/resolv.conf\"\n#else\n#define RESOLV_CONF \"/etc/resolv.conf\"\n#endif\n\nstatic const char* detectDnsFromConf(const char* path, FFDNSOptions* options, FFlist* results)\n{\n    FF_DEBUG(\"Attempting to read DNS config from %s\", path);\n\n    FF_AUTO_CLOSE_FILE FILE* file = fopen(path, \"r\");\n    if (!file)\n    {\n        FF_DEBUG(\"Failed to open %s: %m\", path);\n        return \"fopen(path, r) failed\";\n    }\n\n    if (results->length > 0)\n    {\n        FF_DEBUG(\"Clearing existing DNS entries (%u entries)\", results->length);\n        FF_LIST_FOR_EACH(FFstrbuf, item, *results)\n            ffStrbufDestroy(item);\n        ffListClear(results);\n    }\n\n    FF_AUTO_FREE char* line = NULL;\n    size_t len = 0;\n\n    while (getline(&line, &len, file) != -1)\n    {\n        if (ffStrStartsWith(line, \"nameserver\"))\n        {\n            char* nameserver = line + strlen(\"nameserver\");\n            while (*nameserver == ' ' || *nameserver == '\\t')\n                nameserver++;\n            if (*nameserver == '\\0') continue;\n\n            char* comment = strchr(nameserver, '#');\n            if (comment) *comment = '\\0';\n\n            if ((ffStrContainsC(nameserver, ':') && !(options->showType & FF_DNS_TYPE_IPV6_BIT)) ||\n                (ffStrContainsC(nameserver, '.') && !(options->showType & FF_DNS_TYPE_IPV4_BIT)))\n                continue;\n\n            FFstrbuf* item = (FFstrbuf*) ffListAdd(results);\n            ffStrbufInitS(item, nameserver);\n            ffStrbufTrimRightSpace(item);\n            FF_DEBUG(\"Found DNS server: %s\", item->chars);\n        }\n    }\n\n    FF_DEBUG(\"Found %u DNS servers in %s\", results->length, path);\n    return NULL;\n}\n\nconst char* ffDetectDNS(FFDNSOptions* options, FFlist* results)\n{\n    FF_DEBUG(\"Starting DNS detection\");\n\n    const char* error = detectDnsFromConf(FASTFETCH_TARGET_DIR_ROOT RESOLV_CONF, options, results);\n    if (error != NULL)\n    {\n        FF_DEBUG(\"Error detecting DNS: %s\", error);\n        return error;\n    }\n\n    #if __linux__ && !__ANDROID__\n    // Handle different DNS management services\n    if (results->length == 1)\n    {\n        const FFstrbuf* firstEntry = FF_LIST_FIRST(FFstrbuf, *results);\n\n        if (ffStrbufEqualS(firstEntry, \"127.0.0.53\"))\n        {\n            FF_DEBUG(\"Detected systemd-resolved (127.0.0.53), checking actual DNS servers\");\n            // Managed by systemd-resolved\n            if (detectDnsFromConf(\"/run/systemd/resolve/resolv.conf\", options, results) == NULL)\n                return NULL;\n        }\n        else if (ffStrbufEqualS(firstEntry, \"127.0.0.1\"))\n        {\n            FF_DEBUG(\"Detected possible NetworkManager (127.0.0.1), checking actual DNS servers\");\n            // Managed by NetworkManager\n            if (detectDnsFromConf(\"/var/run/NetworkManager/resolv.conf\", options, results) == NULL)\n                return NULL;\n        }\n    }\n\n    // Check other possible DNS configuration files\n    if (results->length == 0)\n    {\n        FF_DEBUG(\"No DNS servers found, trying alternative config files\");\n\n        // Try resolvconf\n        FF_DEBUG(\"Trying resolvconf configuration\");\n        if (detectDnsFromConf(FASTFETCH_TARGET_DIR_ROOT \"/run/resolvconf/resolv.conf\", options, results) == NULL && results->length > 0)\n            return NULL;\n\n        // Try dnsmasq\n        FF_DEBUG(\"Trying dnsmasq configuration\");\n        if (detectDnsFromConf(FASTFETCH_TARGET_DIR_ROOT \"/var/run/dnsmasq/resolv.conf\", options, results) == NULL && results->length > 0)\n            return NULL;\n\n        // Try openresolv\n        FF_DEBUG(\"Trying openresolv configuration\");\n        if (detectDnsFromConf(FASTFETCH_TARGET_DIR_ROOT \"/etc/resolv.conf.openresolv\", options, results) == NULL && results->length > 0)\n            return NULL;\n    }\n    #elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) || defined(__OpenBSD__)\n    // Handle BSD-specific DNS configurations\n    if (results->length == 0)\n    {\n        FF_DEBUG(\"No DNS servers found, trying BSD-specific config files\");\n\n        // FreeBSD and other BSDs may use resolvconf service\n        FF_DEBUG(\"Trying BSD resolvconf configuration\");\n        if (detectDnsFromConf(FASTFETCH_TARGET_DIR_ROOT \"/var/run/resolvconf/resolv.conf\", options, results) == NULL && results->length > 0)\n            return NULL;\n\n        // Some BSDs store DNS configuration here\n        FF_DEBUG(\"Trying BSD nameserver configuration\");\n        if (detectDnsFromConf(FASTFETCH_TARGET_DIR_ROOT \"/var/run/nameserver\", options, results) == NULL && results->length > 0)\n            return NULL;\n\n        // Try common BSD paths\n        FF_DEBUG(\"Trying BSD common paths\");\n        if (detectDnsFromConf(FASTFETCH_TARGET_DIR_ROOT \"/etc/nameserver\", options, results) == NULL && results->length > 0)\n            return NULL;\n    }\n    #endif\n\n    FF_DEBUG(\"DNS detection completed with %u servers found\", results->length);\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/dns/dns_windows.c",
    "content": "#include \"detection/dns/dns.h\"\n#include \"common/netif.h\"\n#include \"common/mallocHelper.h\"\n\n#include <ws2tcpip.h>\n#include <iphlpapi.h>\n\nconst char* ffDetectDNS(FFDNSOptions* options, FFlist* results)\n{\n    IP_ADAPTER_ADDRESSES* FF_AUTO_FREE adapter_addresses = NULL;\n\n    // Multiple attempts in case interfaces change while\n    // we are in the middle of querying them.\n    DWORD adapter_addresses_buffer_size = 0;\n    for (int attempts = 0;; ++attempts)\n    {\n        if (adapter_addresses_buffer_size)\n        {\n            adapter_addresses = (IP_ADAPTER_ADDRESSES*)realloc(adapter_addresses, adapter_addresses_buffer_size);\n            assert(adapter_addresses);\n        }\n\n        DWORD error = GetAdaptersAddresses(\n            options->showType & FF_DNS_TYPE_IPV4_BIT\n                ? options->showType & FF_DNS_TYPE_IPV6_BIT ? AF_UNSPEC : AF_INET\n                : AF_INET6,\n            GAA_FLAG_SKIP_UNICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_FRIENDLY_NAME,\n            NULL,\n            adapter_addresses,\n            &adapter_addresses_buffer_size);\n\n        if (error == ERROR_SUCCESS)\n            break;\n        else if (ERROR_BUFFER_OVERFLOW == error && attempts < 4)\n            continue;\n        else\n            return \"GetAdaptersAddresses() failed\";\n    }\n\n    uint32_t defaultRouteIfIndex = ffNetifGetDefaultRouteV4()->ifIndex;\n    // Iterate through all of the adapters\n    for (IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; adapter; adapter = adapter->Next)\n    {\n        if (adapter->IfIndex != defaultRouteIfIndex) continue;\n        if (adapter->OperStatus != IfOperStatusUp) continue;\n\n        for (IP_ADAPTER_DNS_SERVER_ADDRESS_XP * ifa = adapter->FirstDnsServerAddress; ifa; ifa = ifa->Next)\n        {\n            FFstrbuf* item = (FFstrbuf*) ffListAdd(results);\n            if (ifa->Address.lpSockaddr->sa_family == AF_INET)\n            {\n                SOCKADDR_IN* ipv4 = (SOCKADDR_IN*) ifa->Address.lpSockaddr;\n                ffStrbufInitA(item, INET_ADDRSTRLEN);\n                item->length = (uint32_t) (RtlIpv4AddressToStringA(&ipv4->sin_addr, item->chars) - item->chars);\n            }\n            else if (ifa->Address.lpSockaddr->sa_family == AF_INET6)\n            {\n                SOCKADDR_IN6* ipv6 = (SOCKADDR_IN6*) ifa->Address.lpSockaddr;\n                ffStrbufInitA(item, INET6_ADDRSTRLEN);\n                item->length = (uint32_t) (RtlIpv6AddressToStringA(&ipv6->sin6_addr, item->chars) - item->chars);\n            }\n        }\n        break;\n    }\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/editor/editor.c",
    "content": "#include \"editor.h\"\n#include \"common/processing.h\"\n#include \"common/library.h\"\n#include \"common/stringUtils.h\"\n#include \"common/path.h\"\n#include \"common/binary.h\"\n\n#include <stdlib.h>\n\nstatic bool extractNvimVersionFromBinary(const char* str, FF_MAYBE_UNUSED uint32_t len, void* userdata)\n{\n    if (!ffStrStartsWith(str, \"NVIM v\")) return true;\n    ffStrbufSetS((FFstrbuf*) userdata, str + strlen(\"NVIM v\"));\n    return false;\n}\n\nstatic bool extractVimVersionFromBinary(const char* str, FF_MAYBE_UNUSED uint32_t len, void* userdata)\n{\n    if (!ffStrStartsWith(str, \"VIM - Vi IMproved \")) return true;\n    ffStrbufSetS((FFstrbuf*) userdata, str + strlen(\"VIM - Vi IMproved \"));\n    ffStrbufSubstrBeforeFirstC(userdata, ' ');\n    return false;\n}\n\nstatic bool extractNanoVersionFromBinary(const char* str, FF_MAYBE_UNUSED uint32_t len, void* userdata)\n{\n    if (!ffStrStartsWith(str, \"GNU nano \")) return true;\n    ffStrbufSetS((FFstrbuf*) userdata, str + strlen(\"GNU nano \"));\n    return false;\n}\n\nconst char* ffDetectEditor(FFEditorResult* result)\n{\n    ffStrbufSetS(&result->name, getenv(\"VISUAL\"));\n    if (result->name.length)\n        result->type = \"Visual\";\n    else\n    {\n        ffStrbufSetS(&result->name, getenv(\"EDITOR\"));\n        if (result->name.length)\n            result->type = \"Editor\";\n        else\n            return \"$VISUAL or $EDITOR not set\";\n    }\n\n    if (ffIsAbsolutePath(result->name.chars))\n        ffStrbufSet(&result->path, &result->name);\n    else\n    {\n        const char* error = ffFindExecutableInPath(result->name.chars, &result->path);\n        if (error) return NULL;\n    }\n\n    {\n        char buf[PATH_MAX + 1];\n        if (!realpath(result->path.chars, buf))\n            return NULL;\n\n        // WIN32: Should we handle scoop shim exe here?\n\n        #ifdef __linux__\n        if (!ffStrEndsWith(buf, \"/snap\"))\n        #endif\n            ffStrbufSetS(&result->path, buf);\n    }\n\n    {\n        uint32_t index = ffStrbufLastIndexC(&result->path,\n            #ifndef _WIN32\n            '/'\n            #else\n            '\\\\'\n            #endif\n        );\n        if (index == result->path.length)\n            return NULL;\n        ffStrbufSetS(&result->exe, &result->path.chars[index + 1]);\n        if (!result->exe.length)\n            return NULL;\n\n        #ifdef _WIN32\n        if (ffStrbufEndsWithS(&result->exe, \".exe\"))\n            ffStrbufSubstrBefore(&result->exe, result->exe.length - 4);\n        #endif\n    }\n\n    if (!instance.config.general.detectVersion) return NULL;\n\n    if (ffStrbufEqualS(&result->exe, \"nvim\"))\n        ffBinaryExtractStrings(result->path.chars, extractNvimVersionFromBinary, &result->version, (uint32_t) strlen(\"NVIM v0.0.0\"));\n    else if (ffStrbufEqualS(&result->exe, \"vim\") || ffStrbufStartsWithS(&result->exe, \"vim.\"))\n        ffBinaryExtractStrings(result->path.chars, extractVimVersionFromBinary, &result->version, (uint32_t) strlen(\"VIM - Vi IMproved 0.0\"));\n    else if (ffStrbufEqualS(&result->exe, \"nano\"))\n        ffBinaryExtractStrings(result->path.chars, extractNanoVersionFromBinary, &result->version, (uint32_t) strlen(\"GNU nano 0.0\"));\n\n    if (result->version.length > 0) return NULL;\n\n    const char* param = NULL;\n    if (\n        ffStrbufEqualS(&result->exe, \"nano\") ||\n        ffStrbufEqualS(&result->exe, \"vim\") ||\n        ffStrbufStartsWithS(&result->exe, \"vim.\") || // vim.basic/vim.tiny\n        ffStrbufEqualS(&result->exe, \"nvim\") ||\n        ffStrbufEqualS(&result->exe, \"micro\") ||\n        ffStrbufEqualS(&result->exe, \"emacs\") ||\n        ffStrbufStartsWithS(&result->exe, \"emacs-\") || // emacs-29.3\n        ffStrbufEqualS(&result->exe, \"hx\") ||\n        ffStrbufEqualS(&result->exe, \"code\") ||\n        ffStrbufEqualS(&result->exe, \"pluma\") ||\n        ffStrbufEqualS(&result->exe, \"sublime_text\") ||\n        ffStrbufEqualS(&result->exe, \"zeditor\")\n    ) param = \"--version\";\n    else if (\n        ffStrbufEqualS(&result->exe, \"kak\") ||\n        ffStrbufEqualS(&result->exe, \"pico\")\n    ) param = \"-version\";\n    else if (\n        ffStrbufEqualS(&result->exe, \"ne\")\n    ) param = \"-h\";\n    else return NULL;\n\n    ffProcessAppendStdOut(&result->version, (char* const[]){\n        result->path.chars,\n        (char*) param,\n        NULL,\n    });\n\n    if (result->version.length == 0)\n        return NULL;\n\n    ffStrbufSubstrBeforeFirstC(&result->version, '\\n');\n    const char* versionStart = strpbrk(result->version.chars, \"0123456789\");\n    if (versionStart != NULL) {\n        const char* versionEnd = strpbrk(versionStart, \" \\t\\v\\f\\r\");\n        if (versionEnd != NULL)\n            ffStrbufSubstrBefore(&result->version, (uint32_t)(versionEnd - result->version.chars));\n\n        if (versionStart != result->version.chars)\n            ffStrbufSubstrAfter(&result->version, (uint32_t)(versionStart - result->version.chars - 1));\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/editor/editor.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/editor/option.h\"\n\ntypedef struct FFEditorResult\n{\n    const char* type;\n    FFstrbuf name;\n    FFstrbuf exe;\n    FFstrbuf path;\n    FFstrbuf version;\n} FFEditorResult;\n\nconst char* ffDetectEditor(FFEditorResult* result);\n"
  },
  {
    "path": "src/detection/font/font.c",
    "content": "#include \"font.h\"\n\nconst char* ffDetectFontImpl(FFFontResult* font);\n\nconst char* ffDetectFont(FFFontResult* font)\n{\n    const char* error = ffDetectFontImpl(font);\n\n    if(error) return error;\n\n    for(uint32_t i = 0; i < FF_DETECT_FONT_NUM_FONTS; ++i)\n    {\n        if(font->fonts[i].length > 0)\n            return NULL;\n    }\n\n    return \"No fonts found\";\n}\n"
  },
  {
    "path": "src/detection/font/font.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/font/option.h\"\n\nenum { FF_DETECT_FONT_NUM_FONTS = 4 };\n\ntypedef struct FFFontResult\n{\n    /**\n     * Linux / BSD: Qt,      GTK2,  GTK3,        GTK4\n     * MacOS:       System,  User,  System Mono, User Mono\n     * Windows:     Caption, Menu,  Message,     Status\n     * Haiku:       Plain,   Menu,  Bold,        Mono\n     * Other:       Unset,   Unset, Unset,       Unset\n     */\n    FFstrbuf fonts[FF_DETECT_FONT_NUM_FONTS];\n    FFstrbuf display;\n} FFFontResult;\n\nconst char* ffDetectFont(FFFontResult* font);\n"
  },
  {
    "path": "src/detection/font/font_apple.m",
    "content": "#include \"common/font.h\"\n#include \"common/io.h\"\n#include \"font.h\"\n\n#import <AppKit/NSFont.h>\n\nstatic void generateString(FFFontResult* font)\n{\n    if(font->fonts[0].length > 0)\n    {\n        ffStrbufAppend(&font->display, &font->fonts[0]);\n        ffStrbufAppendS(&font->display, \" [System]\");\n        if(font->fonts[1].length > 0)\n            ffStrbufAppendS(&font->display, \", \");\n    }\n\n    if(font->fonts[1].length > 0)\n    {\n        ffStrbufAppend(&font->display, &font->fonts[1]);\n        ffStrbufAppendS(&font->display, \" [User]\");\n    }\n}\n\nconst char* ffDetectFontImpl(FFFontResult* result)\n{\n    ffStrbufAppendS(&result->fonts[0], [NSFont systemFontOfSize:12].familyName.UTF8String);\n    ffStrbufAppendS(&result->fonts[1], [NSFont userFontOfSize:12].familyName.UTF8String);\n    #ifdef MAC_OS_X_VERSION_10_15\n    ffStrbufAppendS(&result->fonts[2], [NSFont monospacedSystemFontOfSize:12 weight:400].familyName.UTF8String);\n    #else\n    ffStrbufAppendS(&result->fonts[2], \"\");\n    #endif\n    ffStrbufAppendS(&result->fonts[3], [NSFont userFixedPitchFontOfSize:12].familyName.UTF8String);\n    generateString(result);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/font/font_haiku.cpp",
    "content": "extern \"C\" {\n#include \"font.h\"\n}\n\n#include <Application.h>\n#include <Font.h>\n#include <Menu.h>\n\nextern \"C\" {\n    const char* ffDetectFontImpl(FFFontResult* result);\n}\n\nstatic void generateString(FFFontResult* font)\n{\n    const char* types[] = { \"Plain\", \"Menu\", \"Bold\", \"Mono\" };\n    for(uint32_t i = 0; i < ARRAY_SIZE(types); ++i)\n    {\n        if(i == 0 || !ffStrbufEqual(&font->fonts[i - 1], &font->fonts[i]))\n        {\n            if(i > 0)\n                ffStrbufAppendS(&font->display, \"], \");\n            ffStrbufAppendF(&font->display, \"%s [%s\", font->fonts[i].chars, types[i]);\n        }\n        else\n        {\n            ffStrbufAppendS(&font->display, \" / \");\n            ffStrbufAppendS(&font->display, types[i]);\n        }\n    }\n    ffStrbufAppendC(&font->display, ']');\n}\n\nconst char* ffDetectFontImpl(FFFontResult* result)\n{\n    struct menu_info menuInfo;\n    const BFont *f;\n    // We need a valid be_app to query the app_server here.\n    BApplication app(\"application/x-vnd.fastfetch-cli-fastfetch\");\n\n    if ((f = be_plain_font) != NULL)\n    {\n        f->GetFamilyAndStyle(&menuInfo.f_family, &menuInfo.f_style);\n        ffStrbufAppendF(&result->fonts[0], \"%s %s (%dpt)\", menuInfo.f_family, menuInfo.f_style, (int)f->Size());\n    }\n    if (get_menu_info(&menuInfo) == B_OK)\n    {\n        ffStrbufAppendF(&result->fonts[1], \"%s %s (%dpt)\", menuInfo.f_family, menuInfo.f_style, (int)menuInfo.font_size);\n    }\n    if ((f = be_bold_font) != NULL)\n    {\n        f->GetFamilyAndStyle(&menuInfo.f_family, &menuInfo.f_style);\n        ffStrbufAppendF(&result->fonts[2], \"%s %s (%dpt)\", menuInfo.f_family, menuInfo.f_style, (int)f->Size());\n    }\n    if ((f = be_fixed_font) != NULL)\n    {\n        f->GetFamilyAndStyle(&menuInfo.f_family, &menuInfo.f_style);\n        ffStrbufAppendF(&result->fonts[3], \"%s %s (%dpt)\", menuInfo.f_family, menuInfo.f_style, (int)f->Size());\n    }\n\n    generateString(result);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/font/font_linux.c",
    "content": "#include \"common/font.h\"\n#include \"common/parsing.h\"\n#include \"detection/displayserver/displayserver.h\"\n#include \"detection/gtk_qt/gtk_qt.h\"\n#include \"font.h\"\n\nstatic void generateString(FFFontResult* font)\n{\n    if(font->fonts[0].length > 0)\n    {\n        ffStrbufAppend(&font->display, &font->fonts[0]);\n        ffStrbufAppendS(&font->display, \" [Qt]\");\n\n        for(uint8_t i = 1; i < ARRAY_SIZE(font->fonts); i++)\n        {\n            if(font->fonts[i].length > 0)\n            {\n                ffStrbufAppendS(&font->display, \", \");\n                break;\n            }\n        }\n    }\n\n    ffParseGTK(&font->display, &font->fonts[1], &font->fonts[2], &font->fonts[3]);\n}\n\nconst char* ffDetectFontImpl(FFFontResult* result)\n{\n    const FFDisplayServerResult* wmde = ffConnectDisplayServer();\n\n    if(ffStrbufIgnCaseEqualS(&wmde->wmProtocolName, FF_WM_PROTOCOL_TTY))\n        return \"Font isn't supported in TTY\";\n\n    FFfont qt;\n    ffFontInitQt(&qt, ffDetectQt()->font.chars);\n    ffStrbufAppend(&result->fonts[0], &qt.pretty);\n    ffFontDestroy(&qt);\n\n    FFfont gtk2;\n    ffFontInitPango(&gtk2, ffDetectGTK2()->font.chars);\n    ffStrbufAppend(&result->fonts[1], &gtk2.pretty);\n    ffFontDestroy(&gtk2);\n\n    FFfont gtk3;\n    ffFontInitPango(&gtk3, ffDetectGTK3()->font.chars);\n    ffStrbufAppend(&result->fonts[2], &gtk3.pretty);\n    ffFontDestroy(&gtk3);\n\n    FFfont gtk4;\n    ffFontInitPango(&gtk4, ffDetectGTK4()->font.chars);\n    ffStrbufAppend(&result->fonts[3], &gtk4.pretty);\n    ffFontDestroy(&gtk4);\n\n    generateString(result);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/font/font_nosupport.c",
    "content": "#include \"fastfetch.h\"\n#include \"font.h\"\n\nconst char* ffDetectFontImpl(FF_MAYBE_UNUSED FFFontResult* result)\n{\n    FF_UNUSED(result);\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/font/font_windows.c",
    "content": "#include \"font.h\"\n#include \"common/windows/unicode.h\"\n\n#include <windows.h>\n\nstatic void generateString(FFFontResult* font)\n{\n    const char* types[] = { \"Caption\", \"Menu\", \"Message\", \"Status\" };\n    for(uint32_t i = 0; i < ARRAY_SIZE(types); ++i)\n    {\n        if(i == 0 || !ffStrbufEqual(&font->fonts[i - 1], &font->fonts[i]))\n        {\n            if(i > 0)\n                ffStrbufAppendS(&font->display, \"], \");\n            ffStrbufAppendF(&font->display, \"%s [%s\", font->fonts[i].chars, types[i]);\n        }\n        else\n        {\n            ffStrbufAppendS(&font->display, \" / \");\n            ffStrbufAppendS(&font->display, types[i]);\n        }\n    }\n    ffStrbufAppendC(&font->display, ']');\n}\n\nconst char* ffDetectFontImpl(FFFontResult* result)\n{\n    NONCLIENTMETRICSW info = { .cbSize = sizeof(info) };\n    if(!SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(info), &info, 0))\n        return \"SystemParametersInfoW(SPI_GETNONCLIENTMETRICS) failed\";\n\n    LOGFONTW* fonts[4] = { &info.lfCaptionFont, &info.lfMenuFont, &info.lfMessageFont, &info.lfStatusFont };\n\n    for(uint32_t i = 0; i < ARRAY_SIZE(fonts); ++i)\n    {\n        ffStrbufSetWS(&result->fonts[i], fonts[i]->lfFaceName);\n        if(fonts[i]->lfHeight < 0)\n            ffStrbufAppendF(&result->fonts[i], \" (%dpt)\", (int)-fonts[i]->lfHeight);\n    }\n\n    generateString(result);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/gamepad/gamepad.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\ntypedef struct FFGamepadDevice\n{\n    FFstrbuf serial;\n    FFstrbuf name;\n    uint8_t battery; // 0-100%\n} FFGamepadDevice;\n\nconst char* ffDetectGamepad(FFlist* devices /* List of FFGamepadDevice */);\n"
  },
  {
    "path": "src/detection/gamepad/gamepad_apple.c",
    "content": "#include \"gamepad.h\"\n#include \"common/apple/cf_helpers.h\"\n#include \"common/mallocHelper.h\"\n\n#include <IOKit/IOKitLib.h>\n#include <IOKit/hid/IOHIDLib.h>\n\nstatic void enumSet(IOHIDDeviceRef value, FFlist* results)\n{\n    FFGamepadDevice* device = (FFGamepadDevice*) ffListAdd(results);\n    ffStrbufInit(&device->serial);\n    ffStrbufInit(&device->name);\n    device->battery = 0;\n\n    CFStringRef manufacturer = IOHIDDeviceGetProperty(value, CFSTR(kIOHIDManufacturerKey));\n    ffCfStrGetString(manufacturer, &device->name);\n\n    CFStringRef product = IOHIDDeviceGetProperty(value, CFSTR(kIOHIDProductKey));\n    if (device->name.length)\n    {\n        ffCfStrGetString(product, &device->serial);\n        ffStrbufAppendC(&device->name, ' ');\n        ffStrbufAppend(&device->name, &device->serial);\n    }\n    else\n    {\n        ffCfStrGetString(product, &device->name);\n    }\n\n    CFStringRef serialNumber = IOHIDDeviceGetProperty(value, CFSTR(kIOHIDSerialNumberKey));\n    ffCfStrGetString(serialNumber, &device->serial);\n}\n\nconst char* ffDetectGamepad(FFlist* devices /* List of FFGamepadDevice */)\n{\n    IOHIDManagerRef FF_CFTYPE_AUTO_RELEASE manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);\n    if (IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone) != kIOReturnSuccess)\n        return \"IOHIDManagerOpen() failed\";\n\n    CFDictionaryRef FF_CFTYPE_AUTO_RELEASE matching1 = CFDictionaryCreate(kCFAllocatorDefault, (const void **)(CFStringRef[]){\n        CFSTR(kIOHIDDeviceUsagePageKey),\n        CFSTR(kIOHIDDeviceUsageKey)\n    }, (const void **)(CFNumberRef[]){\n        ffCfCreateInt(kHIDPage_GenericDesktop),\n        ffCfCreateInt(kHIDUsage_GD_Joystick)\n    }, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);\n    CFDictionaryRef FF_CFTYPE_AUTO_RELEASE matching2 = CFDictionaryCreate(kCFAllocatorDefault, (const void **)(CFStringRef[]){\n        CFSTR(kIOHIDDeviceUsagePageKey),\n        CFSTR(kIOHIDDeviceUsageKey)\n    }, (const void **)(CFNumberRef[]){\n        ffCfCreateInt(kHIDPage_GenericDesktop),\n        ffCfCreateInt(kHIDUsage_GD_GamePad)\n    }, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);\n    CFArrayRef FF_CFTYPE_AUTO_RELEASE matchings = CFArrayCreate(kCFAllocatorDefault, (const void **)(CFTypeRef[]){\n        matching1, matching2\n    }, 2, &kCFTypeArrayCallBacks);\n    IOHIDManagerSetDeviceMatchingMultiple(manager, matchings);\n\n    CFSetRef FF_CFTYPE_AUTO_RELEASE set = IOHIDManagerCopyDevices(manager);\n    if (set)\n        CFSetApplyFunction(set, (CFSetApplierFunction) &enumSet, devices);\n    IOHIDManagerClose(manager, kIOHIDOptionsTypeNone);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/gamepad/gamepad_bsd.c",
    "content": "#include \"gamepad.h\"\n#include \"common/io.h\"\n\n#include <stdio.h>\n#include <fcntl.h>\n#include <usbhid.h>\n\n#if __has_include(<dev/usb/usb_ioctl.h>)\n#include <dev/usb/usb_ioctl.h> // FreeBSD\n#else\n#include <bus/u4b/usb_ioctl.h> // DragonFly\n#endif\n\n#define MAX_UHID_JOYS 64\n\nconst char* ffDetectGamepad(FFlist* devices /* List of FFGamepadDevice */)\n{\n    char path[16];\n    for (int i = 0; i < MAX_UHID_JOYS; i++)\n    {\n        snprintf(path, ARRAY_SIZE(path), \"/dev/uhid%d\", i);\n        FF_AUTO_CLOSE_FD int fd = open(path, O_RDONLY | O_CLOEXEC);\n        if (fd < 0)\n        {\n            if (errno == ENOENT)\n                break; // No more devices\n            continue; // Device not found\n        }\n        report_desc_t repDesc = hid_get_report_desc(fd);\n        if (!repDesc) continue;\n\n        int reportId = hid_get_report_id(fd);\n\n        struct hid_data* hData = hid_start_parse(repDesc, 0, reportId);\n        if (hData)\n        {\n            struct hid_item hItem;\n            while (hid_get_item(hData, &hItem) > 0)\n            {\n                if (HID_PAGE(hItem.usage) != 1) continue;\n                switch (HID_USAGE(hItem.usage))\n                {\n                    case 1: // Pointer. FreeBSD returns 1 for my Pro Controller for some reason\n                    case 4: // Joystick\n                    case 5: // Gamepad\n                        break;\n                    default:\n                        continue;\n                }\n\n                struct usb_device_info di;\n                if (ioctl(fd, USB_GET_DEVICEINFO, &di) != -1)\n                {\n                    FFGamepadDevice* device = (FFGamepadDevice*) ffListAdd(devices);\n                    ffStrbufInitS(&device->serial, di.udi_serial);\n                    ffStrbufInitF(&device->name, \"%s %s\", di.udi_vendor, di.udi_product);\n                    device->battery = 0;\n                }\n            }\n            hid_end_parse(hData);\n        }\n\n        hid_dispose_report_desc(repDesc);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/gamepad/gamepad_haiku.cpp",
    "content": "extern \"C\" {\n#include \"gamepad.h\"\n}\n#include <Joystick.h>\n\nconst char* ffDetectGamepad(FFlist* devices /* List of FFGamepadDevice */)\n{\n    BJoystick js;\n    for (int32 i = 0, n = js.CountDevices(); i < n; ++i)\n    {\n        char name[B_OS_NAME_LENGTH];\n        if (js.GetDeviceName(i, name) == B_OK)\n        {\n            FFGamepadDevice* device = (FFGamepadDevice*) ffListAdd(devices);\n            ffStrbufInit(&device->serial);\n            ffStrbufInitS(&device->name, name);\n            device->battery = 0;\n        }\n    }\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/gamepad/gamepad_linux.c",
    "content": "#include \"gamepad.h\"\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n\nstatic void detectGamepad(FFlist* devices, FFstrbuf* name, FFstrbuf* path)\n{\n    uint32_t baseLen = path->length;\n    FFGamepadDevice* device = (FFGamepadDevice*) ffListAdd(devices);\n    ffStrbufInit(&device->serial);\n    ffStrbufInitMove(&device->name, name);\n    device->battery = 0;\n\n    ffStrbufAppendS(path, \"uniq\");\n    if (ffAppendFileBuffer(path->chars, &device->serial))\n        ffStrbufTrimRightSpace(&device->serial);\n\n    ffStrbufSubstrBefore(path, baseLen);\n    ffStrbufAppendS(path, \"device/power_supply/\"); // /sys/class/input/jsX/device/device/power_supply\n\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(path->chars);\n    if (dirp)\n    {\n        struct dirent* entry;\n        while ((entry = readdir(dirp)) != NULL)\n        {\n            if (entry->d_name[0] == '.' || (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN))\n                continue;\n            ffStrbufAppendS(path, entry->d_name);\n            ffStrbufAppendS(path, \"/capacity\"); // /sys/class/input/jsX/device/device/power_supply/XXX/capacity\n            char capacity[32];\n            ssize_t nRead = ffReadFileData(path->chars, ARRAY_SIZE(capacity) - 1, capacity);\n            if (nRead > 0) // Tested with a PS4 controller\n            {\n                capacity[nRead] = '\\0';\n                device->battery = (uint8_t) strtoul(capacity, NULL, 10);\n                break;\n            }\n\n            ffStrbufAppendS(path, \"_level\");\n            nRead = ffReadFileData(path->chars, ARRAY_SIZE(capacity) - 1, capacity);\n            if (nRead > 0) // Tested with a NS Pro controller\n            {\n                // https://github.com/torvalds/linux/blob/52b1853b080a082ec3749c3a9577f6c71b1d4a90/drivers/power/supply/power_supply_sysfs.c#L124\n                switch (capacity[0])\n                {\n                    case 'C': device->battery = 1; break; // Critical\n                    case 'L': device->battery = 25; break; // Low\n                    case 'N': device->battery = 50; break; // Normal\n                    case 'H': device->battery = 75; break; // High\n                    case 'F': device->battery = 100; break; // Full\n                }\n            }\n        }\n    }\n}\n\nconst char* ffDetectGamepad(FFlist* devices /* List of FFGamepadDevice */)\n{\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(\"/sys/class/input/\");\n    if (dirp == NULL)\n        return \"opendir(\\\"/sys/class/input/\\\") == NULL\";\n\n    FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateS(\"/sys/class/input/\");\n    uint32_t baseLen = path.length;\n\n    struct dirent* entry;\n    while ((entry = readdir(dirp)) != NULL)\n    {\n        if (!ffStrStartsWith(entry->d_name, \"js\"))\n            continue;\n        if (!ffCharIsDigit(entry->d_name[strlen(\"js\")]))\n            continue;\n\n        ffStrbufAppendS(&path, entry->d_name);\n        ffStrbufAppendS(&path, \"/device/name\");\n\n        FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate();\n        if (ffAppendFileBuffer(path.chars, &name))\n        {\n            ffStrbufTrimRightSpace(&name);\n            ffStrbufSubstrBefore(&path, path.length - 4);\n            detectGamepad(devices, &name, &path);\n        }\n\n        ffStrbufSubstrBefore(&path, baseLen);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/gamepad/gamepad_nosupport.c",
    "content": "#include \"gamepad.h\"\n\nconst char* ffDetectGamepad(FF_MAYBE_UNUSED FFlist* devices /* List of FFGamepadDevice */)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/gamepad/gamepad_windows.c",
    "content": "#include \"gamepad.h\"\n#include \"common/io.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/windows/unicode.h\"\n\n#include <windows.h>\n#include <hidsdi.h>\n\nstatic const char* detectKnownDeviceName(uint32_t vendorId, uint32_t productId)\n{\n    switch (vendorId)\n    {\n        // Nintendo\n        case 0x057E:\n        {\n            switch (productId)\n            {\n                case 0x2006: return \"Nintendo Switch Joycon L\";\n                case 0x2007: return \"Nintendo Switch Joycon R\";\n                case 0x2009: return \"Nintendo Switch Pro Controller\";\n                case 0x200E: return \"Nintendo Switch Charging Grip\";\n                case 0x2017: return \"Nintendo Switch SNES Controller\";\n\n                default: return NULL;\n            }\n        }\n\n        // Sony\n        case 0x054C:\n        {\n            switch (productId)\n            {\n                case 0x0268: return \"Sony DualShock 3 / Six Axis\";\n\n                case 0x05C4: return \"Sony DualShock 4 Gen1\";\n                case 0x09CC: return \"Sony DualShock 4 Gen2\";\n                case 0x0BA0: return \"Sony DualShock 4 USB receiver\";\n\n                case 0x0CE6: return \"Sony DualSense\";\n                case 0x0DF2: return \"Sony DualSense Edge\";\n\n                default: return NULL;\n            }\n        }\n\n        // Logitech\n        case 0x046D:\n        {\n            switch (productId)\n            {\n                case 0xC216: return \"Logitech F310, DirectInput\";\n                case 0xC218: return \"Logitech F510, DirectInput\";\n                case 0xC219: return \"Logitech F710, DirectInput\";\n                case 0xC21D: return \"Logitech F310\";\n                case 0xC21E: return \"Logitech F510\";\n                case 0xC21F: return \"Logitech F710\";\n\n                default: return NULL;\n            }\n        }\n\n        case 0x045E: // Microsoft Xbox compatible controllers should be handled by Windows without problems\n        default: return NULL;\n    }\n}\n\nconst char* ffDetectGamepad(FFlist* devices /* List of FFGamepadDevice */)\n{\n    UINT nDevices = 0;\n    if (GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)))\n        return \"GetRawInputDeviceList(NULL) failed\";\n    if (nDevices == 0)\n        return \"No HID devices found\";\n    RAWINPUTDEVICELIST* FF_AUTO_FREE pRawInputDeviceList = (RAWINPUTDEVICELIST*) malloc(sizeof(RAWINPUTDEVICELIST) * nDevices);\n    if ((nDevices = GetRawInputDeviceList(pRawInputDeviceList, &nDevices, sizeof(RAWINPUTDEVICELIST))) == (UINT) -1)\n        return \"GetRawInputDeviceList(pRawInputDeviceList) failed\";\n\n    for (UINT i = 0; i < nDevices; ++i)\n    {\n        if (pRawInputDeviceList[i].dwType != RIM_TYPEHID) continue;\n\n        HANDLE hDevice = pRawInputDeviceList[i].hDevice;\n\n        RID_DEVICE_INFO rdi;\n        UINT rdiSize = sizeof(rdi);\n        if (GetRawInputDeviceInfoW(hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) == (UINT) -1)\n            continue;\n\n        if (rdi.hid.usUsagePage != 1 || (rdi.hid.usUsage != 4/*Joystick*/ && rdi.hid.usUsage != 5/*Gamepad*/))\n            continue;\n\n        WCHAR devName[MAX_PATH] = L\"\";\n        UINT nameSize = MAX_PATH;\n        if (GetRawInputDeviceInfoW(hDevice, RIDI_DEVICENAME, devName, &nameSize) == (UINT) -1)\n            continue;\n\n        FFGamepadDevice* device = (FFGamepadDevice*) ffListAdd(devices);\n        ffStrbufInit(&device->serial);\n        ffStrbufInit(&device->name);\n        device->battery = 0;\n\n        const char* knownGamepad = detectKnownDeviceName(rdi.hid.dwVendorId, rdi.hid.dwProductId);\n        if (knownGamepad)\n            ffStrbufSetS(&device->name, knownGamepad);\n        HANDLE FF_AUTO_CLOSE_FD hHidFile = CreateFileW(devName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);\n        if (hHidFile == INVALID_HANDLE_VALUE)\n        {\n            if (!knownGamepad)\n                ffStrbufSetF(&device->name, \"Unknown gamepad %04X-%04X\", (unsigned) rdi.hid.dwVendorId, (unsigned) rdi.hid.dwProductId);\n            continue;\n        }\n\n        if (!knownGamepad)\n        {\n            wchar_t displayName[126];\n            if (HidD_GetProductString(hHidFile, displayName, sizeof(displayName) /*in bytes*/))\n            {\n                wchar_t manufacturer[126];\n                if (HidD_GetManufacturerString(hHidFile, manufacturer, sizeof(manufacturer) /*in bytes*/))\n                {\n                    ffStrbufSetWS(&device->name, manufacturer);\n                    FF_STRBUF_AUTO_DESTROY displayNameStr = ffStrbufCreateWS(displayName);\n                    ffStrbufAppendC(&device->name, ' ');\n                    ffStrbufAppend(&device->name, &displayNameStr);\n                }\n                else\n                {\n                    ffStrbufSetWS(&device->name, displayName);\n                }\n            }\n        }\n\n        wchar_t serialNumber[127] = L\"\";\n        if (HidD_GetSerialNumberString(hHidFile, serialNumber, sizeof(serialNumber) /*in bytes*/))\n            ffStrbufSetWS(&device->serial, serialNumber);\n\n        PHIDP_PREPARSED_DATA preparsedData = NULL;\n        if (HidD_GetPreparsedData(hHidFile, &preparsedData))\n        {\n            HIDP_CAPS caps;\n            NTSTATUS capsResult = HidP_GetCaps(preparsedData, &caps);\n            HidD_FreePreparsedData(preparsedData);\n            if (!NT_SUCCESS(capsResult))\n                continue;\n\n            if (\n                (rdi.hid.dwVendorId == 0x054C && (\n                    rdi.hid.dwProductId == 0x05C4 || // PS4 Gen1\n                    rdi.hid.dwProductId == 0x09CC // PS4 Gen2\n                )) ||\n                (rdi.hid.dwVendorId == 0x057E && (\n                    rdi.hid.dwProductId == 0x2009 // NS Pro\n                ))\n            )\n            {\n                // Controller must be connected by other programs\n                FF_AUTO_FREE uint8_t* reportBuffer = malloc(caps.InputReportByteLength);\n                OVERLAPPED overlapped = { };\n                DWORD nBytes;\n                if (ReadFile(hHidFile, reportBuffer, caps.InputReportByteLength, &nBytes, &overlapped) ||\n                    GetOverlappedResultEx(hHidFile, &overlapped, &nBytes, FF_IO_TERM_RESP_WAIT_MS, TRUE))\n                {\n                    if (rdi.hid.dwVendorId == 0x054C)\n                    {\n                        if (nBytes > 31)\n                        {\n                            uint8_t batteryInfo = reportBuffer[caps.InputReportByteLength == 64 /*USB?*/ ? 30 : 32];\n                            device->battery = (uint8_t) ((batteryInfo & 0x0f) * 100 / (batteryInfo & 0x10 /*charging?*/ ? 11 /*BATTERY_MAX_USB*/ : 8 /*BATTERY_MAX*/));\n                            if (device->battery > 100) device->battery = 100;\n                        }\n                    }\n                    else\n                    {\n                        if (nBytes > 3 && reportBuffer[0] == 0x30)\n                        {\n                            uint8_t batteryInfo = reportBuffer[2];\n                            device->battery = (uint8_t) (((batteryInfo & 0xE0) >> 4) * 100 / 8);\n                            if (device->battery == 0) device->battery = 1;\n                            else if (device->battery > 100) device->battery = 100;\n                        }\n                    }\n                }\n                else\n                    CancelIo(hHidFile);\n            }\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/gpu/adl.h",
    "content": "#pragma once\n\n#include \"3rdparty/display-library/adl_sdk.h\"\n\n// https://gpuopen-librariesandsdks.github.io/adl/modules.html\n\n// Function to initialize the ADL2 interface and to obtain client's context handle.\nextern int ADL2_Main_Control_Create(ADL_MAIN_MALLOC_CALLBACK callback, int iEnumConnectedAdapters, ADL_CONTEXT_HANDLE* context);\n\n// Destroy client's ADL context.\nextern int ADL2_Main_Control_Destroy(ADL_CONTEXT_HANDLE context);\n\n// Retrieves adapter information for given adapter or all OS-known adapters.\n// Return ADL_OK on success, DESPITE THE OFFICIAL DOCUMENT SAYS IT RETURNS 1 FOR SUCCESS!\nextern int ADL2_Adapter_AdapterInfoX3_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int* numAdapters, AdapterInfo** lppAdapterInfo);\n\n// Function to retrieve Graphic Core Info.\nextern int ADL2_Adapter_Graphic_Core_Info_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLGraphicCoreInfo* pGraphicCoreInfo);\n\n// Function to retrieve memory information from the adapter. Version 2\nextern int ADL2_Adapter_MemoryInfo2_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLMemoryInfo2* lpMemoryInfo2);\n\n// This function retrieves the Dedicated VRAM usage of given adapter.\nextern int ADL2_Adapter_DedicatedVRAMUsage_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int* iVRAMUsageInMB);\n\n// Function to get the ASICFamilyType from the adapter.\nextern int ADL2_Adapter_ASICFamilyType_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int* lpAsicTypes, int* lpValids);\n\n\n// Function to retrieve current power management capabilities.\nextern int ADL2_Overdrive_Caps(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int* iSupported, int* iEnabled, int* iVersion);\n\n\n/////////// Overdrive 6 functions\n\n// Function to retrieve current Overdrive and performance-related activity.\nextern int ADL2_Overdrive6_CurrentStatus_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLOD6CurrentStatus* lpCurrentStatus);\n\n// Function to retrieve GPU temperature from the thermal controller.\nextern int ADL2_Overdrive6_Temperature_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int* lpTemperature);\n\n// Function to retrieve the current or default Overdrive clock ranges.\nextern int ADL2_Overdrive6_StateInfo_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int iStateType, ADLOD6StateInfo* lpStateInfo);\n\n\n/// Overdrive N functions\n\n// Despite the name (N means Next), this is actually Overdrive7 API\n// https://github.com/GPUOpen-LibrariesAndSDKs/display-library/blob/master/Sample/OverdriveN/OverdriveN.cpp#L209\n\n// Function to retrieve the OverdriveN capabilities.\nextern int ADL2_OverdriveN_CapabilitiesX2_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLODNCapabilitiesX2* lpODCapabilities);\n\n// Function to retrieve the current OD performance status.\nextern int ADL2_OverdriveN_PerformanceStatus_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLODNPerformanceStatus *lpODPerformanceStatus);\n\n// Function to retrieve the current temperature.\nextern int ADL2_OverdriveN_Temperature_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int iTemperatureType, int *iTemperature);\n\n// Function to retrieve the current GPU clocks settings.\nextern int ADL2_OverdriveN_SystemClocksX2_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLODNPerformanceLevelsX2 *lpODPerformanceLevels);\n\n\n/// Overdrive 8 functions\n\n// Function to retrieve the Overdrive8 current settings.\nextern int ADL2_Overdrive8_Current_Setting_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLOD8CurrentSetting *lpCurrentSetting);\n\n// Function to retrieve the Overdrive8 current settings.\nextern int ADL2_New_QueryPMLogData_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLPMLogDataOutput *lpDataOutput);\n"
  },
  {
    "path": "src/detection/gpu/asahi_drm.h",
    "content": "/* SPDX-License-Identifier: MIT */\n/*\n * Copyright (C) The Asahi Linux Contributors\n * Copyright (C) 2018-2023 Collabora Ltd.\n * Copyright (C) 2014-2018 Broadcom\n */\n#ifndef _ASAHI_DRM_H_\n#define _ASAHI_DRM_H_\n\n#include <drm.h>\n\n#if defined(__cplusplus)\nextern \"C\" {\n#endif\n\n/**\n * DOC: Introduction to the Asahi UAPI\n *\n * This documentation describes the Asahi IOCTLs.\n *\n * Just a few generic rules about the data passed to the Asahi IOCTLs (cribbed\n * from Panthor):\n *\n * - Structures must be aligned on 64-bit/8-byte. If the object is not\n *   naturally aligned, a padding field must be added.\n * - Fields must be explicitly aligned to their natural type alignment with\n *   pad[0..N] fields.\n * - All padding fields will be checked by the driver to make sure they are\n *   zeroed.\n * - Flags can be added, but not removed/replaced.\n * - New fields can be added to the main structures (the structures\n *   directly passed to the ioctl). Those fields can be added at the end of\n *   the structure, or replace existing padding fields. Any new field being\n *   added must preserve the behavior that existed before those fields were\n *   added when a value of zero is passed.\n * - New fields can be added to indirect objects (objects pointed by the\n *   main structure), iff those objects are passed a size to reflect the\n *   size known by the userspace driver (see\n *   drm_asahi_cmd_header::size).\n * - If the kernel driver is too old to know some fields, those will be\n *   ignored if zero, and otherwise rejected (and so will be zero on output).\n * - If userspace is too old to know some fields, those will be zeroed\n *   (input) before the structure is parsed by the kernel driver.\n * - Each new flag/field addition must come with a driver version update so\n *   the userspace driver doesn't have to guess which flags are supported.\n * - Structures should not contain unions, as this would defeat the\n *   extensibility of such structures.\n * - IOCTLs can't be removed or replaced. New IOCTL IDs should be placed\n *   at the end of the drm_asahi_ioctl_id enum.\n */\n\n/**\n * enum drm_asahi_ioctl_id - IOCTL IDs\n *\n * Place new ioctls at the end, don't re-order, don't replace or remove entries.\n *\n * These IDs are not meant to be used directly. Use the DRM_IOCTL_ASAHI_xxx\n * definitions instead.\n */\nenum drm_asahi_ioctl_id {\n\t/** @DRM_ASAHI_GET_PARAMS: Query device properties. */\n\tDRM_ASAHI_GET_PARAMS = 0,\n\n\t/** @DRM_ASAHI_GET_TIME: Query device time. */\n\tDRM_ASAHI_GET_TIME,\n\n\t/** @DRM_ASAHI_VM_CREATE: Create a GPU VM address space. */\n\tDRM_ASAHI_VM_CREATE,\n\n\t/** @DRM_ASAHI_VM_DESTROY: Destroy a VM. */\n\tDRM_ASAHI_VM_DESTROY,\n\n\t/** @DRM_ASAHI_VM_BIND: Bind/unbind memory to a VM. */\n\tDRM_ASAHI_VM_BIND,\n\n\t/** @DRM_ASAHI_GEM_CREATE: Create a buffer object. */\n\tDRM_ASAHI_GEM_CREATE,\n\n\t/**\n\t * @DRM_ASAHI_GEM_MMAP_OFFSET: Get offset to pass to mmap() to map a\n\t * given GEM handle.\n\t */\n\tDRM_ASAHI_GEM_MMAP_OFFSET,\n\n\t/** @DRM_ASAHI_GEM_BIND_OBJECT: Bind memory as a special object */\n\tDRM_ASAHI_GEM_BIND_OBJECT,\n\n\t/** @DRM_ASAHI_QUEUE_CREATE: Create a scheduling queue. */\n\tDRM_ASAHI_QUEUE_CREATE,\n\n\t/** @DRM_ASAHI_QUEUE_DESTROY: Destroy a scheduling queue. */\n\tDRM_ASAHI_QUEUE_DESTROY,\n\n\t/** @DRM_ASAHI_SUBMIT: Submit commands to a queue. */\n\tDRM_ASAHI_SUBMIT,\n};\n\n#define DRM_ASAHI_MAX_CLUSTERS\t64\n\n/**\n * struct drm_asahi_params_global - Global parameters.\n *\n * This struct may be queried by drm_asahi_get_params.\n */\nstruct drm_asahi_params_global {\n\t/** @features: Feature bits from drm_asahi_feature */\n\t__u64 features;\n\n\t/** @gpu_generation: GPU generation, e.g. 13 for G13G */\n\t__u32 gpu_generation;\n\n\t/** @gpu_variant: GPU variant as a character, e.g. 'C' for G13C */\n\t__u32 gpu_variant;\n\n\t/**\n\t * @gpu_revision: GPU revision in BCD, e.g. 0x00 for 'A0' or\n\t * 0x21 for 'C1'\n\t */\n\t__u32 gpu_revision;\n\n\t/** @chip_id: Chip ID in BCD, e.g. 0x8103 for T8103 */\n\t__u32 chip_id;\n\n\t/** @num_dies: Number of dies in the SoC */\n\t__u32 num_dies;\n\n\t/** @num_clusters_total: Number of GPU clusters (across all dies) */\n\t__u32 num_clusters_total;\n\n\t/**\n\t * @num_cores_per_cluster: Number of logical cores per cluster\n\t * (including inactive/nonexistent)\n\t */\n\t__u32 num_cores_per_cluster;\n\n\t/** @max_frequency_khz: Maximum GPU core clock frequency */\n\t__u32 max_frequency_khz;\n\n\t/** @core_masks: Bitmask of present/enabled cores per cluster */\n\t__u64 core_masks[DRM_ASAHI_MAX_CLUSTERS];\n\n\t/**\n\t * @vm_start: VM range start VMA. Together with @vm_end, this defines\n\t * the window of valid GPU VAs. Userspace is expected to subdivide VAs\n\t * out of this window.\n\t *\n\t * This window contains all virtual addresses that userspace needs to\n\t * know about. There may be kernel-internal GPU VAs outside this range,\n\t * but that detail is not relevant here.\n\t */\n\t__u64 vm_start;\n\n\t/** @vm_end: VM range end VMA */\n\t__u64 vm_end;\n\n\t/**\n\t * @vm_kernel_min_size: Minimum kernel VMA window size.\n\t *\n\t * When creating a VM, userspace is required to carve out a section of\n\t * virtual addresses (within the range given by @vm_start and\n\t * @vm_end). The kernel will allocate various internal structures\n\t * within the specified VA range.\n\t *\n\t * Allowing userspace to choose the VA range for the kernel, rather than\n\t * the kernel reserving VAs and requiring userspace to cope, can assist\n\t * in implementing SVM.\n\t */\n\t__u64 vm_kernel_min_size;\n\n\t/**\n\t * @max_commands_per_submission: Maximum number of supported commands\n\t * per submission. This mirrors firmware limits. Userspace must split up\n\t * larger command buffers, which may require inserting additional\n\t * synchronization.\n\t */\n\t__u32 max_commands_per_submission;\n\n\t/**\n\t * @max_attachments: Maximum number of drm_asahi_attachment's per\n\t * command\n\t */\n\t__u32 max_attachments;\n\n\t/**\n\t * @command_timestamp_frequency_hz: Timebase frequency for timestamps\n\t * written during command exeuction, specified via drm_asahi_timestamp\n\t * structures. As this rate is controlled by the firmware, it is a\n\t * queryable parameter.\n\t *\n\t * Userspace must divide by this frequency to convert timestamps to\n\t * seconds, rather than hardcoding a particular firmware's rate.\n\t */\n\t__u64 command_timestamp_frequency_hz;\n};\n\n/**\n * enum drm_asahi_feature - Feature bits\n *\n * This covers only features that userspace cannot infer from the architecture\n * version. Most features don't need to be here.\n */\nenum drm_asahi_feature {\n\t/**\n\t * @DRM_ASAHI_FEATURE_SOFT_FAULTS: GPU has \"soft fault\" enabled. Shader\n\t * loads of unmapped memory will return zero. Shader stores to unmapped\n\t * memory will be silently discarded. Note that only shader load/store\n\t * is affected. Other hardware units are not affected, notably including\n\t * texture sampling.\n\t *\n\t * Soft fault is set when initializing the GPU and cannot be runtime\n\t * toggled. Therefore, it is exposed as a feature bit and not a\n\t * userspace-settable flag on the VM. When soft fault is enabled,\n\t * userspace can speculate memory accesses more aggressively.\n\t */\n\tDRM_ASAHI_FEATURE_SOFT_FAULTS = (1UL) << 0,\n};\n\n/**\n * struct drm_asahi_get_params - Arguments passed to DRM_IOCTL_ASAHI_GET_PARAMS\n */\nstruct drm_asahi_get_params {\n\t/** @param_group: Parameter group to fetch (MBZ) */\n\t__u32 param_group;\n\n\t/** @pad: MBZ */\n\t__u32 pad;\n\n\t/** @pointer: User pointer to write parameter struct */\n\t__u64 pointer;\n\n\t/**\n\t * @size: Size of the user buffer. In case of older userspace, this may\n\t * be less than sizeof(struct drm_asahi_params_global). The kernel will\n\t * not write past the length specified here, allowing extensibility.\n\t */\n\t__u64 size;\n};\n\n/**\n * struct drm_asahi_vm_create - Arguments passed to DRM_IOCTL_ASAHI_VM_CREATE\n */\nstruct drm_asahi_vm_create {\n\t/**\n\t * @kernel_start: Start of the kernel-reserved address range. See\n\t * drm_asahi_params_global::vm_kernel_min_size.\n\t *\n\t * Both @kernel_start and @kernel_end must be within the range of\n\t * valid VAs given by drm_asahi_params_global::vm_start and\n\t * drm_asahi_params_global::vm_end. The size of the kernel range\n\t * (@kernel_end - @kernel_start) must be at least\n\t * drm_asahi_params_global::vm_kernel_min_size.\n\t *\n\t * Userspace must not bind any memory on this VM into this reserved\n\t * range, it is for kernel use only.\n\t */\n\t__u64 kernel_start;\n\n\t/**\n\t * @kernel_end: End of the kernel-reserved address range. See\n\t * @kernel_start.\n\t */\n\t__u64 kernel_end;\n\n\t/** @vm_id: Returned VM ID */\n\t__u32 vm_id;\n\n\t/** @pad: MBZ */\n\t__u32 pad;\n};\n\n/**\n * struct drm_asahi_vm_destroy - Arguments passed to DRM_IOCTL_ASAHI_VM_DESTROY\n */\nstruct drm_asahi_vm_destroy {\n\t/** @vm_id: VM ID to be destroyed */\n\t__u32 vm_id;\n\n\t/** @pad: MBZ */\n\t__u32 pad;\n};\n\n/**\n * enum drm_asahi_gem_flags - Flags for GEM creation\n */\nenum drm_asahi_gem_flags {\n\t/**\n\t * @DRM_ASAHI_GEM_WRITEBACK: BO should be CPU-mapped as writeback.\n\t *\n\t * Map as writeback instead of write-combine. This optimizes for CPU\n\t * reads.\n\t */\n\tDRM_ASAHI_GEM_WRITEBACK = (1L << 0),\n\n\t/**\n\t * @DRM_ASAHI_GEM_VM_PRIVATE: BO is private to this GPU VM (no exports).\n\t */\n\tDRM_ASAHI_GEM_VM_PRIVATE = (1L << 1),\n};\n\n/**\n * struct drm_asahi_gem_create - Arguments passed to DRM_IOCTL_ASAHI_GEM_CREATE\n */\nstruct drm_asahi_gem_create {\n\t/** @size: Size of the BO */\n\t__u64 size;\n\n\t/** @flags: Combination of drm_asahi_gem_flags flags. */\n\t__u32 flags;\n\n\t/**\n\t * @vm_id: VM ID to assign to the BO, if DRM_ASAHI_GEM_VM_PRIVATE is set\n\t */\n\t__u32 vm_id;\n\n\t/** @handle: Returned GEM handle for the BO */\n\t__u32 handle;\n\n\t/** @pad: MBZ */\n\t__u32 pad;\n};\n\n/**\n * struct drm_asahi_gem_mmap_offset - Arguments passed to\n * DRM_IOCTL_ASAHI_GEM_MMAP_OFFSET\n */\nstruct drm_asahi_gem_mmap_offset {\n\t/** @handle: Handle for the object being mapped. */\n\t__u32 handle;\n\n\t/** @flags: Must be zero */\n\t__u32 flags;\n\n\t/** @offset: The fake offset to use for subsequent mmap call */\n\t__u64 offset;\n};\n\n/**\n * enum drm_asahi_bind_flags - Flags for GEM binding\n */\nenum drm_asahi_bind_flags {\n\t/**\n\t * @DRM_ASAHI_BIND_UNBIND: Instead of binding a GEM object to the range,\n\t * simply unbind the GPU VMA range.\n\t */\n\tDRM_ASAHI_BIND_UNBIND = (1L << 0),\n\n\t/** @DRM_ASAHI_BIND_READ: Map BO with GPU read permission */\n\tDRM_ASAHI_BIND_READ = (1L << 1),\n\n\t/** @DRM_ASAHI_BIND_WRITE: Map BO with GPU write permission */\n\tDRM_ASAHI_BIND_WRITE = (1L << 2),\n\n\t/**\n\t * @DRM_ASAHI_BIND_SINGLE_PAGE: Map a single page of the BO repeatedly\n\t * across the VA range.\n\t *\n\t * This is useful to fill a VA range with scratch pages or zero pages.\n\t * It is intended as a mechanism to accelerate sparse.\n\t */\n\tDRM_ASAHI_BIND_SINGLE_PAGE = (1L << 3),\n};\n\n/**\n * struct drm_asahi_gem_bind_op - Description of a single GEM bind operation.\n */\nstruct drm_asahi_gem_bind_op {\n\t/** @flags: Combination of drm_asahi_bind_flags flags. */\n\t__u32 flags;\n\n\t/** @handle: GEM object to bind (except for UNBIND) */\n\t__u32 handle;\n\n\t/**\n\t * @offset: Offset into the object (except for UNBIND).\n\t *\n\t * For a regular bind, this is the beginning of the region of the GEM\n\t * object to bind.\n\t *\n\t * For a single-page bind, this is the offset to the single page that\n\t * will be repeatedly bound.\n\t *\n\t * Must be page-size aligned.\n\t */\n\t__u64 offset;\n\n\t/**\n\t * @range: Number of bytes to bind/unbind to @addr.\n\t *\n\t * Must be page-size aligned.\n\t */\n\t__u64 range;\n\n\t/**\n\t * @addr: Address to bind to.\n\t *\n\t * Must be page-size aligned.\n\t */\n\t__u64 addr;\n};\n\n/**\n * struct drm_asahi_vm_bind - Arguments passed to\n * DRM_IOCTL_ASAHI_VM_BIND\n */\nstruct drm_asahi_vm_bind {\n\t/** @vm_id: The ID of the VM to bind to */\n\t__u32 vm_id;\n\n\t/** @num_binds: number of binds in this IOCTL. */\n\t__u32 num_binds;\n\n\t/**\n\t * @stride: Stride in bytes between consecutive binds. This allows\n\t * extensibility of drm_asahi_gem_bind_op.\n\t */\n\t__u32 stride;\n\n\t/** @pad: MBZ */\n\t__u32 pad;\n\n\t/**\n\t * @userptr: User pointer to an array of @num_binds structures of type\n\t * @drm_asahi_gem_bind_op and size @stride bytes.\n\t */\n\t__u64 userptr;\n};\n\n/**\n * enum drm_asahi_bind_object_op - Special object bind operation\n */\nenum drm_asahi_bind_object_op {\n\t/** @DRM_ASAHI_BIND_OBJECT_OP_BIND: Bind a BO as a special GPU object */\n\tDRM_ASAHI_BIND_OBJECT_OP_BIND = 0,\n\n\t/** @DRM_ASAHI_BIND_OBJECT_OP_UNBIND: Unbind a special GPU object */\n\tDRM_ASAHI_BIND_OBJECT_OP_UNBIND = 1,\n};\n\n/**\n * enum drm_asahi_bind_object_flags - Special object bind flags\n */\nenum drm_asahi_bind_object_flags {\n\t/**\n\t * @DRM_ASAHI_BIND_OBJECT_USAGE_TIMESTAMPS: Map a BO as a timestamp\n\t * buffer.\n\t */\n\tDRM_ASAHI_BIND_OBJECT_USAGE_TIMESTAMPS = (1L << 0),\n};\n\n/**\n * struct drm_asahi_gem_bind_object - Arguments passed to\n * DRM_IOCTL_ASAHI_GEM_BIND_OBJECT\n */\nstruct drm_asahi_gem_bind_object {\n\t/** @op: Bind operation (enum drm_asahi_bind_object_op) */\n\t__u32 op;\n\n\t/** @flags: Combination of drm_asahi_bind_object_flags flags. */\n\t__u32 flags;\n\n\t/** @handle: GEM object to bind/unbind (BIND) */\n\t__u32 handle;\n\n\t/** @vm_id: The ID of the VM to operate on (MBZ currently) */\n\t__u32 vm_id;\n\n\t/** @offset: Offset into the object (BIND only) */\n\t__u64 offset;\n\n\t/** @range: Number of bytes to bind/unbind (BIND only) */\n\t__u64 range;\n\n\t/** @object_handle: Object handle (out for BIND, in for UNBIND) */\n\t__u32 object_handle;\n\n\t/** @pad: MBZ */\n\t__u32 pad;\n};\n\n/**\n * enum drm_asahi_cmd_type - Command type\n */\nenum drm_asahi_cmd_type {\n\t/**\n\t * @DRM_ASAHI_CMD_RENDER: Render command, executing on the render\n\t * subqueue. Combined vertex and fragment operation.\n\t *\n\t * Followed by a @drm_asahi_cmd_render payload.\n\t */\n\tDRM_ASAHI_CMD_RENDER = 0,\n\n\t/**\n\t * @DRM_ASAHI_CMD_COMPUTE: Compute command on the compute subqueue.\n\t *\n\t * Followed by a @drm_asahi_cmd_compute payload.\n\t */\n\tDRM_ASAHI_CMD_COMPUTE = 1,\n\n\t/**\n\t * @DRM_ASAHI_SET_VERTEX_ATTACHMENTS: Software command to set\n\t * attachments for subsequent vertex shaders in the same submit.\n\t *\n\t * Followed by (possibly multiple) @drm_asahi_attachment payloads.\n\t */\n\tDRM_ASAHI_SET_VERTEX_ATTACHMENTS = 2,\n\n\t/**\n\t * @DRM_ASAHI_SET_FRAGMENT_ATTACHMENTS: Software command to set\n\t * attachments for subsequent fragment shaders in the same submit.\n\t *\n\t * Followed by (possibly multiple) @drm_asahi_attachment payloads.\n\t */\n\tDRM_ASAHI_SET_FRAGMENT_ATTACHMENTS = 3,\n\n\t/**\n\t * @DRM_ASAHI_SET_COMPUTE_ATTACHMENTS: Software command to set\n\t * attachments for subsequent compute shaders in the same submit.\n\t *\n\t * Followed by (possibly multiple) @drm_asahi_attachment payloads.\n\t */\n\tDRM_ASAHI_SET_COMPUTE_ATTACHMENTS = 4,\n};\n\n/**\n * enum drm_asahi_priority - Scheduling queue priority.\n *\n * These priorities are forwarded to the firmware to influence firmware\n * scheduling. The exact policy is ultimately decided by firmware, but\n * these enums allow userspace to communicate the intentions.\n */\nenum drm_asahi_priority {\n\t/** @DRM_ASAHI_PRIORITY_LOW: Low priority queue. */\n\tDRM_ASAHI_PRIORITY_LOW = 0,\n\n\t/** @DRM_ASAHI_PRIORITY_MEDIUM: Medium priority queue. */\n\tDRM_ASAHI_PRIORITY_MEDIUM = 1,\n\n\t/**\n\t * @DRM_ASAHI_PRIORITY_HIGH: High priority queue.\n\t *\n\t * Reserved for future extension.\n\t */\n\tDRM_ASAHI_PRIORITY_HIGH = 2,\n\n\t/**\n\t * @DRM_ASAHI_PRIORITY_REALTIME: Real-time priority queue.\n\t *\n\t * Reserved for future extension.\n\t */\n\tDRM_ASAHI_PRIORITY_REALTIME = 3,\n};\n\n/**\n * struct drm_asahi_queue_create - Arguments passed to\n * DRM_IOCTL_ASAHI_QUEUE_CREATE\n */\nstruct drm_asahi_queue_create {\n\t/** @flags: MBZ */\n\t__u32 flags;\n\n\t/** @vm_id: The ID of the VM this queue is bound to */\n\t__u32 vm_id;\n\n\t/** @priority: One of drm_asahi_priority */\n\t__u32 priority;\n\n\t/** @queue_id: The returned queue ID */\n\t__u32 queue_id;\n\n\t/**\n\t * @usc_exec_base: GPU base address for all USC binaries (shaders) on\n\t * this queue. USC addresses are 32-bit relative to this 64-bit base.\n\t *\n\t * This sets the following registers on all queue commands:\n\t *\n\t *\tUSC_EXEC_BASE_TA  (vertex)\n\t *\tUSC_EXEC_BASE_ISP (fragment)\n\t *\tUSC_EXEC_BASE_CP  (compute)\n\t *\n\t * While the hardware lets us configure these independently per command,\n\t * we do not have a use case for this. Instead, we expect userspace to\n\t * fix a 4GiB VA carveout for USC memory and pass its base address here.\n\t */\n\t__u64 usc_exec_base;\n};\n\n/**\n * struct drm_asahi_queue_destroy - Arguments passed to\n * DRM_IOCTL_ASAHI_QUEUE_DESTROY\n */\nstruct drm_asahi_queue_destroy {\n\t/** @queue_id: The queue ID to be destroyed */\n\t__u32 queue_id;\n\n\t/** @pad: MBZ */\n\t__u32 pad;\n};\n\n/**\n * enum drm_asahi_sync_type - Sync item type\n */\nenum drm_asahi_sync_type {\n\t/** @DRM_ASAHI_SYNC_SYNCOBJ: Binary sync object */\n\tDRM_ASAHI_SYNC_SYNCOBJ = 0,\n\n\t/** @DRM_ASAHI_SYNC_TIMELINE_SYNCOBJ: Timeline sync object */\n\tDRM_ASAHI_SYNC_TIMELINE_SYNCOBJ = 1,\n};\n\n/**\n * struct drm_asahi_sync - Sync item\n */\nstruct drm_asahi_sync {\n\t/** @sync_type: One of drm_asahi_sync_type */\n\t__u32 sync_type;\n\n\t/** @handle: The sync object handle */\n\t__u32 handle;\n\n\t/** @timeline_value: Timeline value for timeline sync objects */\n\t__u64 timeline_value;\n};\n\n/**\n * define DRM_ASAHI_BARRIER_NONE - Command index for no barrier\n *\n * This special value may be passed in to drm_asahi_command::vdm_barrier or\n * drm_asahi_command::cdm_barrier to indicate that the respective subqueue\n * should not wait on any previous work.\n */\n#define DRM_ASAHI_BARRIER_NONE (0xFFFFu)\n\n/**\n * struct drm_asahi_cmd_header - Top level command structure\n *\n * This struct is core to the command buffer definition and therefore is not\n * extensible.\n */\nstruct drm_asahi_cmd_header {\n\t/** @cmd_type: One of drm_asahi_cmd_type */\n\t__u16 cmd_type;\n\n\t/**\n\t * @size: Size of this command, not including this header.\n\t *\n\t * For hardware commands, this enables extensibility of commands without\n\t * requiring extra command types. Passing a command that is shorter\n\t * than expected is explicitly allowed for backwards-compatibility.\n\t * Truncated fields will be zeroed.\n\t *\n\t * For the synthetic attachment setting commands, this implicitly\n\t * encodes the number of attachments. These commands take multiple\n\t * fixed-size @drm_asahi_attachment structures as their payload, so size\n\t * equals number of attachments * sizeof(struct drm_asahi_attachment).\n\t */\n\t__u16 size;\n\n\t/**\n\t * @vdm_barrier: VDM (render) command index to wait on.\n\t *\n\t * Barriers are indices relative to the beginning of a given submit. A\n\t * barrier of 0 waits on commands submitted to the respective subqueue\n\t * in previous submit ioctls. A barrier of N waits on N previous\n\t * commands on the subqueue within the current submit ioctl. As a\n\t * special case, passing @DRM_ASAHI_BARRIER_NONE avoids waiting on any\n\t * commands in the subqueue.\n\t *\n\t * Examples:\n\t *\n\t *   0: This waits on all previous work.\n\t *\n\t *   NONE: This does not wait for anything on this subqueue.\n\t *\n\t *   1: This waits on the first render command in the submit.\n\t *   This is valid only if there are multiple render commands in the\n\t *   same submit.\n\t *\n\t * Barriers are valid only for hardware commands. Synthetic software\n\t * commands to set attachments must pass NONE here.\n\t */\n\t__u16 vdm_barrier;\n\n\t/**\n\t * @cdm_barrier: CDM (compute) command index to wait on.\n\t *\n\t * See @vdm_barrier, and replace VDM/render with CDM/compute.\n\t */\n\t__u16 cdm_barrier;\n};\n\n/**\n * struct drm_asahi_submit - Arguments passed to DRM_IOCTL_ASAHI_SUBMIT\n */\nstruct drm_asahi_submit {\n\t/**\n\t * @syncs: An optional pointer to an array of drm_asahi_sync. The first\n\t * @in_sync_count elements are in-syncs, then the remaining\n\t * @out_sync_count elements are out-syncs. Using a single array with\n\t * explicit partitioning simplifies handling.\n\t */\n\t__u64 syncs;\n\n\t/**\n\t * @cmdbuf: Pointer to the command buffer to submit.\n\t *\n\t * This is a flat command buffer. By design, it contains no CPU\n\t * pointers, which makes it suitable for a virtgpu wire protocol without\n\t * requiring any serializing/deserializing step.\n\t *\n\t * It consists of a series of commands. Each command begins with a\n\t * fixed-size @drm_asahi_cmd_header header and is followed by a\n\t * variable-length payload according to the type and size in the header.\n\t *\n\t * The combined count of \"real\" hardware commands must be nonzero and at\n\t * most drm_asahi_params_global::max_commands_per_submission.\n\t */\n\t__u64 cmdbuf;\n\n\t/** @flags: Flags for command submission (MBZ) */\n\t__u32 flags;\n\n\t/** @queue_id: The queue ID to be submitted to */\n\t__u32 queue_id;\n\n\t/**\n\t * @in_sync_count: Number of sync objects to wait on before starting\n\t * this job.\n\t */\n\t__u32 in_sync_count;\n\n\t/**\n\t * @out_sync_count: Number of sync objects to signal upon completion of\n\t * this job.\n\t */\n\t__u32 out_sync_count;\n\n\t/** @cmdbuf_size: Command buffer size in bytes */\n\t__u32 cmdbuf_size;\n\n\t/** @pad: MBZ */\n\t__u32 pad;\n};\n\n/**\n * struct drm_asahi_attachment - Describe an \"attachment\".\n *\n * Attachments are any memory written by shaders, notably including render\n * target attachments written by the end-of-tile program. This is purely a hint\n * about the accessed memory regions. It is optional to specify, which is\n * fortunate as it cannot be specified precisely with bindless access anyway.\n * But where possible, it's probably a good idea for userspace to include these\n * hints, forwarded to the firmware.\n *\n * This struct is implicitly sized and therefore is not extensible.\n */\nstruct drm_asahi_attachment {\n\t/** @pointer: Base address of the attachment */\n\t__u64 pointer;\n\n\t/** @size: Size of the attachment in bytes */\n\t__u64 size;\n\n\t/** @pad: MBZ */\n\t__u32 pad;\n\n\t/** @flags: MBZ */\n\t__u32 flags;\n};\n\nenum drm_asahi_render_flags {\n\t/**\n\t * @DRM_ASAHI_RENDER_VERTEX_SCRATCH: A vertex stage shader uses scratch\n\t * memory.\n\t */\n\tDRM_ASAHI_RENDER_VERTEX_SCRATCH = (1U << 0),\n\n\t/**\n\t * @DRM_ASAHI_RENDER_PROCESS_EMPTY_TILES: Process even empty tiles.\n\t * This must be set when clearing render targets.\n\t */\n\tDRM_ASAHI_RENDER_PROCESS_EMPTY_TILES = (1U << 1),\n\n\t/**\n\t * @DRM_ASAHI_RENDER_NO_VERTEX_CLUSTERING: Run vertex stage on a single\n\t * cluster (on multi-cluster GPUs)\n\t *\n\t * This harms performance but can workaround certain sync/coherency\n\t * bugs, and therefore is useful for debugging.\n\t */\n\tDRM_ASAHI_RENDER_NO_VERTEX_CLUSTERING = (1U << 2),\n\n\t/**\n\t * @DRM_ASAHI_RENDER_DBIAS_IS_INT: Use integer depth bias formula.\n\t *\n\t * Graphics specifications contain two alternate formulas for depth\n\t * bias, a float formula used with floating-point depth buffers and an\n\t * integer formula using with unorm depth buffers. This flag specifies\n\t * that the integer formula should be used. If omitted, the float\n\t * formula is used instead.\n\t *\n\t * This corresponds to bit 18 of the relevant hardware control register,\n\t * so we match that here for efficiency.\n\t */\n\tDRM_ASAHI_RENDER_DBIAS_IS_INT = (1U << 18),\n};\n\n/**\n * struct drm_asahi_zls_buffer - Describe a depth or stencil buffer.\n *\n * These fields correspond to hardware registers in the ZLS (Z Load/Store) unit.\n * There are three hardware registers for each field respectively for loads,\n * stores, and partial renders. In practice, it makes sense to set all to the\n * same values, except in exceptional cases not yet implemented in userspace, so\n * we do not duplicate here for simplicity/efficiency.\n *\n * This struct is embedded in other structs and therefore is not extensible.\n */\nstruct drm_asahi_zls_buffer {\n\t/** @base: Base address of the buffer */\n\t__u64 base;\n\n\t/**\n\t * @comp_base: If the load buffer is compressed, address of the\n\t * compression metadata section.\n\t */\n\t__u64 comp_base;\n\n\t/**\n\t * @stride: If layered rendering is enabled, the number of bytes\n\t * between each layer of the buffer.\n\t */\n\t__u32 stride;\n\n\t/**\n\t * @comp_stride: If layered rendering is enabled, the number of bytes\n\t * between each layer of the compression metadata.\n\t */\n\t__u32 comp_stride;\n};\n\n/**\n * struct drm_asahi_timestamp - Describe a timestamp write.\n *\n * The firmware can optionally write the GPU timestamp at render pass\n * granularities, but it needs to be mapped specially via\n * DRM_IOCTL_ASAHI_GEM_BIND_OBJECT. This structure therefore describes where to\n * write as a handle-offset pair, rather than a GPU address like normal.\n *\n * This struct is embedded in other structs and therefore is not extensible.\n */\nstruct drm_asahi_timestamp {\n\t/**\n\t * @handle: Handle of the timestamp buffer, or 0 to skip this\n\t * timestamp. If nonzero, this must equal the value returned in\n\t * drm_asahi_gem_bind_object::object_handle.\n\t */\n\t__u32 handle;\n\n\t/** @offset: Offset to write into the timestamp buffer */\n\t__u32 offset;\n};\n\n/**\n * struct drm_asahi_timestamps - Describe timestamp writes.\n *\n * Each operation that can be timestamped, can be timestamped at the start and\n * end. Therefore, drm_asahi_timestamp structs always come in pairs, bundled\n * together into drm_asahi_timestamps.\n *\n * This struct is embedded in other structs and therefore is not extensible.\n */\nstruct drm_asahi_timestamps {\n\t/** @start: Timestamp recorded at the start of the operation */\n\tstruct drm_asahi_timestamp start;\n\n\t/** @end: Timestamp recorded at the end of the operation */\n\tstruct drm_asahi_timestamp end;\n};\n\n/**\n * struct drm_asahi_helper_program - Describe helper program configuration.\n *\n * The helper program is a compute-like kernel required for various hardware\n * functionality. Its most important role is dynamically allocating\n * scratch/stack memory for individual subgroups, by partitioning a static\n * allocation shared for the whole device. It is supplied by userspace via\n * drm_asahi_helper_program and internally dispatched by the hardware as needed.\n *\n * This struct is embedded in other structs and therefore is not extensible.\n */\nstruct drm_asahi_helper_program {\n\t/**\n\t * @binary: USC address to the helper program binary. This is a tagged\n\t * pointer with configuration in the bottom bits.\n\t */\n\t__u32 binary;\n\n\t/** @cfg: Additional configuration bits for the helper program. */\n\t__u32 cfg;\n\n\t/**\n\t * @data: Data passed to the helper program. This value is not\n\t * interpreted by the kernel, firmware, or hardware in any way. It is\n\t * simply a sideband for userspace, set with the submit ioctl and read\n\t * via special registers inside the helper program.\n\t *\n\t * In practice, userspace will pass a 64-bit GPU VA here pointing to the\n\t * actual arguments, which presumably don't fit in 64-bits.\n\t */\n\t__u64 data;\n};\n\n/**\n * struct drm_asahi_bg_eot - Describe a background or end-of-tile program.\n *\n * The background and end-of-tile programs are dispatched by the hardware at the\n * beginning and end of rendering. As the hardware \"tilebuffer\" is simply local\n * memory, these programs are necessary to implement API-level render targets.\n * The fragment-like background program is responsible for loading either the\n * clear colour or the existing render target contents, while the compute-like\n * end-of-tile program stores the tilebuffer contents to memory.\n *\n * This struct is embedded in other structs and therefore is not extensible.\n */\nstruct drm_asahi_bg_eot {\n\t/**\n\t * @usc: USC address of the hardware USC words binding resources\n\t * (including images and uniforms) and the program itself. Note this is\n\t * an additional layer of indirection compared to the helper program,\n\t * avoiding the need for a sideband for data. This is a tagged pointer\n\t * with additional configuration in the bottom bits.\n\t */\n\t__u32 usc;\n\n\t/**\n\t * @rsrc_spec: Resource specifier for the program. This is a packed\n\t * hardware data structure describing the required number of registers,\n\t * uniforms, bound textures, and bound samplers.\n\t */\n\t__u32 rsrc_spec;\n};\n\n/**\n * struct drm_asahi_cmd_render - Command to submit 3D\n *\n * This command submits a single render pass. The hardware control stream may\n * include many draws and subpasses, but within the command, the framebuffer\n * dimensions and attachments are fixed.\n *\n * The hardware requires the firmware to set a large number of Control Registers\n * setting up state at render pass granularity before each command rendering 3D.\n * The firmware bundles this state into data structures. Unfortunately, we\n * cannot expose either any of that directly to userspace, because the\n * kernel-firmware ABI is not stable. Although we can guarantee the firmware\n * updates in tandem with the kernel, we cannot break old userspace when\n * upgrading the firmware and kernel. Therefore, we need to abstract well the\n * data structures to avoid tying our hands with future firmwares.\n *\n * The bulk of drm_asahi_cmd_render therefore consists of values of hardware\n * control registers, marshalled via the firmware interface.\n *\n * The framebuffer/tilebuffer dimensions are also specified here. In addition to\n * being passed to the firmware/hardware, the kernel requires these dimensions\n * to calculate various essential tiling-related data structures. It is\n * unfortunate that our submits are heavier than on vendors with saner\n * hardware-software interfaces. The upshot is all of this information is\n * readily available to userspace with all current APIs.\n *\n * It looks odd - but it's not overly burdensome and it ensures we can remain\n * compatible with old userspace.\n */\nstruct drm_asahi_cmd_render {\n\t/** @flags: Combination of drm_asahi_render_flags flags. */\n\t__u32 flags;\n\n\t/**\n\t * @isp_zls_pixels: ISP_ZLS_PIXELS register value. This contains the\n\t * depth/stencil width/height, which may differ from the framebuffer\n\t * width/height.\n\t */\n\t__u32 isp_zls_pixels;\n\n\t/**\n\t * @vdm_ctrl_stream_base: VDM_CTRL_STREAM_BASE register value. GPU\n\t * address to the beginning of the VDM control stream.\n\t */\n\t__u64 vdm_ctrl_stream_base;\n\n\t/** @vertex_helper: Helper program used for the vertex shader */\n\tstruct drm_asahi_helper_program vertex_helper;\n\n\t/** @fragment_helper: Helper program used for the fragment shader */\n\tstruct drm_asahi_helper_program fragment_helper;\n\n\t/**\n\t * @isp_scissor_base: ISP_SCISSOR_BASE register value. GPU address of an\n\t * array of scissor descriptors indexed in the render pass.\n\t */\n\t__u64 isp_scissor_base;\n\n\t/**\n\t * @isp_dbias_base: ISP_DBIAS_BASE register value. GPU address of an\n\t * array of depth bias values indexed in the render pass.\n\t */\n\t__u64 isp_dbias_base;\n\n\t/**\n\t * @isp_oclqry_base: ISP_OCLQRY_BASE register value. GPU address of an\n\t * array of occlusion query results written by the render pass.\n\t */\n\t__u64 isp_oclqry_base;\n\n\t/** @depth: Depth buffer */\n\tstruct drm_asahi_zls_buffer depth;\n\n\t/** @stencil: Stencil buffer */\n\tstruct drm_asahi_zls_buffer stencil;\n\n\t/** @zls_ctrl: ZLS_CTRL register value */\n\t__u64 zls_ctrl;\n\n\t/** @ppp_multisamplectl: PPP_MULTISAMPLECTL register value */\n\t__u64 ppp_multisamplectl;\n\n\t/**\n\t * @sampler_heap: Base address of the sampler heap. This heap is used\n\t * for both vertex shaders and fragment shaders. The registers are\n\t * per-stage, but there is no known use case for separate heaps.\n\t */\n\t__u64 sampler_heap;\n\n\t/** @ppp_ctrl: PPP_CTRL register value */\n\t__u32 ppp_ctrl;\n\n\t/** @width_px: Framebuffer width in pixels */\n\t__u16 width_px;\n\n\t/** @height_px: Framebuffer height in pixels */\n\t__u16 height_px;\n\n\t/** @layers: Number of layers in the framebuffer */\n\t__u16 layers;\n\n\t/** @sampler_count: Number of samplers in the sampler heap. */\n\t__u16 sampler_count;\n\n\t/** @utile_width_px: Width of a logical tilebuffer tile in pixels */\n\t__u8 utile_width_px;\n\n\t/** @utile_height_px: Height of a logical tilebuffer tile in pixels */\n\t__u8 utile_height_px;\n\n\t/** @samples: # of samples in the framebuffer. Must be 1, 2, or 4. */\n\t__u8 samples;\n\n\t/** @sample_size_B: # of bytes in the tilebuffer required per sample. */\n\t__u8 sample_size_B;\n\n\t/**\n\t * @isp_merge_upper_x: 32-bit float used in the hardware triangle\n\t * merging. Calculate as: tan(60 deg) * width.\n\t *\n\t * Making these values UAPI avoids requiring floating-point calculations\n\t * in the kernel in the hot path.\n\t */\n\t__u32 isp_merge_upper_x;\n\n\t/**\n\t * @isp_merge_upper_y: 32-bit float. Calculate as: tan(60 deg) * height.\n\t * See @isp_merge_upper_x.\n\t */\n\t__u32 isp_merge_upper_y;\n\n\t/** @bg: Background program run for each tile at the start */\n\tstruct drm_asahi_bg_eot bg;\n\n\t/** @eot: End-of-tile program ran for each tile at the end */\n\tstruct drm_asahi_bg_eot eot;\n\n\t/**\n\t * @partial_bg: Background program ran at the start of each tile when\n\t * resuming the render pass during a partial render.\n\t */\n\tstruct drm_asahi_bg_eot partial_bg;\n\n\t/**\n\t * @partial_eot: End-of-tile program ran at the end of each tile when\n\t * pausing the render pass during a partial render.\n\t */\n\tstruct drm_asahi_bg_eot partial_eot;\n\n\t/**\n\t * @isp_bgobjdepth: ISP_BGOBJDEPTH register value. This is the depth\n\t * buffer clear value, encoded in the depth buffer's format: either a\n\t * 32-bit float or a 16-bit unorm (with upper bits zeroed).\n\t */\n\t__u32 isp_bgobjdepth;\n\n\t/**\n\t * @isp_bgobjvals: ISP_BGOBJVALS register value. The bottom 8-bits\n\t * contain the stencil buffer clear value.\n\t */\n\t__u32 isp_bgobjvals;\n\n\t/** @ts_vtx: Timestamps for the vertex portion of the render */\n\tstruct drm_asahi_timestamps ts_vtx;\n\n\t/** @ts_frag: Timestamps for the fragment portion of the render */\n\tstruct drm_asahi_timestamps ts_frag;\n};\n\n/**\n * struct drm_asahi_cmd_compute - Command to submit compute\n *\n * This command submits a control stream consisting of compute dispatches. There\n * is essentially no limit on how many compute dispatches may be included in a\n * single compute command, although timestamps are at command granularity.\n */\nstruct drm_asahi_cmd_compute {\n\t/** @flags: MBZ */\n\t__u32 flags;\n\n\t/** @sampler_count: Number of samplers in the sampler heap. */\n\t__u32 sampler_count;\n\n\t/**\n\t * @cdm_ctrl_stream_base: CDM_CTRL_STREAM_BASE register value. GPU\n\t * address to the beginning of the CDM control stream.\n\t */\n\t__u64 cdm_ctrl_stream_base;\n\n\t/**\n\t * @cdm_ctrl_stream_end: GPU base address to the end of the hardware\n\t * control stream. Note this only considers the first contiguous segment\n\t * of the control stream, as the stream might jump elsewhere.\n\t */\n\t__u64 cdm_ctrl_stream_end;\n\n\t/** @sampler_heap: Base address of the sampler heap. */\n\t__u64 sampler_heap;\n\n\t/** @helper: Helper program used for this compute command */\n\tstruct drm_asahi_helper_program helper;\n\n\t/** @ts: Timestamps for the compute command */\n\tstruct drm_asahi_timestamps ts;\n};\n\n/**\n * struct drm_asahi_get_time - Arguments passed to DRM_IOCTL_ASAHI_GET_TIME\n */\nstruct drm_asahi_get_time {\n\t/** @flags: MBZ. */\n\t__u64 flags;\n\n\t/** @gpu_timestamp: On return, the GPU timestamp in nanoseconds. */\n\t__u64 gpu_timestamp;\n};\n\n/**\n * DRM_IOCTL_ASAHI() - Build an Asahi IOCTL number\n * @__access: Access type. Must be R, W or RW.\n * @__id: One of the DRM_ASAHI_xxx id.\n * @__type: Suffix of the type being passed to the IOCTL.\n *\n * Don't use this macro directly, use the DRM_IOCTL_ASAHI_xxx\n * values instead.\n *\n * Return: An IOCTL number to be passed to ioctl() from userspace.\n */\n#define DRM_IOCTL_ASAHI(__access, __id, __type) \\\n\tDRM_IO ## __access(DRM_COMMAND_BASE + DRM_ASAHI_ ## __id, \\\n\t\t\t   struct drm_asahi_ ## __type)\n\n/* Note: this is an enum so that it can be resolved by Rust bindgen. */\nenum {\n\tDRM_IOCTL_ASAHI_GET_PARAMS       = DRM_IOCTL_ASAHI(W, GET_PARAMS, get_params),\n\tDRM_IOCTL_ASAHI_GET_TIME         = DRM_IOCTL_ASAHI(WR, GET_TIME, get_time),\n\tDRM_IOCTL_ASAHI_VM_CREATE        = DRM_IOCTL_ASAHI(WR, VM_CREATE, vm_create),\n\tDRM_IOCTL_ASAHI_VM_DESTROY       = DRM_IOCTL_ASAHI(W, VM_DESTROY, vm_destroy),\n\tDRM_IOCTL_ASAHI_VM_BIND          = DRM_IOCTL_ASAHI(W, VM_BIND, vm_bind),\n\tDRM_IOCTL_ASAHI_GEM_CREATE       = DRM_IOCTL_ASAHI(WR, GEM_CREATE, gem_create),\n\tDRM_IOCTL_ASAHI_GEM_MMAP_OFFSET  = DRM_IOCTL_ASAHI(WR, GEM_MMAP_OFFSET, gem_mmap_offset),\n\tDRM_IOCTL_ASAHI_GEM_BIND_OBJECT  = DRM_IOCTL_ASAHI(WR, GEM_BIND_OBJECT, gem_bind_object),\n\tDRM_IOCTL_ASAHI_QUEUE_CREATE     = DRM_IOCTL_ASAHI(WR, QUEUE_CREATE, queue_create),\n\tDRM_IOCTL_ASAHI_QUEUE_DESTROY    = DRM_IOCTL_ASAHI(W, QUEUE_DESTROY, queue_destroy),\n\tDRM_IOCTL_ASAHI_SUBMIT           = DRM_IOCTL_ASAHI(W, SUBMIT, submit),\n};\n\n#if defined(__cplusplus)\n}\n#endif\n\n#endif /* _ASAHI_DRM_H_ */\n"
  },
  {
    "path": "src/detection/gpu/gpu.c",
    "content": "#include \"gpu.h\"\n#include \"common/debug.h\"\n#include \"detection/vulkan/vulkan.h\"\n#include \"detection/opencl/opencl.h\"\n#include \"detection/opengl/opengl.h\"\n#include \"modules/opengl/opengl.h\"\n\nconst char* FF_GPU_VENDOR_NAME_APPLE = \"Apple\";\nconst char* FF_GPU_VENDOR_NAME_AMD = \"AMD\";\nconst char* FF_GPU_VENDOR_NAME_INTEL = \"Intel\";\nconst char* FF_GPU_VENDOR_NAME_NVIDIA = \"NVIDIA\";\nconst char* FF_GPU_VENDOR_NAME_MTHREADS = \"Moore Threads\";\nconst char* FF_GPU_VENDOR_NAME_QUALCOMM = \"Qualcomm\";\nconst char* FF_GPU_VENDOR_NAME_MTK = \"MTK\";\nconst char* FF_GPU_VENDOR_NAME_VMWARE = \"VMware\";\nconst char* FF_GPU_VENDOR_NAME_PARALLEL = \"Parallel\";\nconst char* FF_GPU_VENDOR_NAME_MICROSOFT = \"Microsoft\";\nconst char* FF_GPU_VENDOR_NAME_REDHAT = \"RedHat\";\nconst char* FF_GPU_VENDOR_NAME_ORACLE = \"Oracle\";\nconst char* FF_GPU_VENDOR_NAME_BROADCOM = \"Broadcom\";\nconst char* FF_GPU_VENDOR_NAME_LOONGSON = \"Loongson\";\nconst char* FF_GPU_VENDOR_NAME_JINGJIA_MICRO = \"Jingjia Micro\";\nconst char* FF_GPU_VENDOR_NAME_HUAWEI = \"Huawei\";\nconst char* FF_GPU_VENDOR_NAME_ZHAOXIN = \"Zhaoxin\";\n\nconst char* ffGPUGetVendorString(unsigned vendorId)\n{\n    // https://devicehunt.com/all-pci-vendors\n    switch (vendorId)\n    {\n        case 0x106b: return FF_GPU_VENDOR_NAME_APPLE;\n        case 0x1002: case 0x1022: case 0x1dd8: return FF_GPU_VENDOR_NAME_AMD;\n        case 0x8086: case 0x8087: case 0x03e7: return FF_GPU_VENDOR_NAME_INTEL;\n        case 0x0955: case 0x10de: case 0x12d2: return FF_GPU_VENDOR_NAME_NVIDIA;\n        case 0x1ed5: return FF_GPU_VENDOR_NAME_MTHREADS;\n        case 0x17cb: case 0x5143: return FF_GPU_VENDOR_NAME_QUALCOMM;\n        case 0x14c3: return FF_GPU_VENDOR_NAME_MTK;\n        case 0x15ad: return FF_GPU_VENDOR_NAME_VMWARE;\n        case 0x1af4: return FF_GPU_VENDOR_NAME_REDHAT;\n        case 0x1ab8: return FF_GPU_VENDOR_NAME_PARALLEL;\n        case 0x1414: return FF_GPU_VENDOR_NAME_MICROSOFT;\n        case 0x108e: return FF_GPU_VENDOR_NAME_ORACLE;\n        case 0x182f: case 0x14e4: return FF_GPU_VENDOR_NAME_BROADCOM;\n        case 0x0014: return FF_GPU_VENDOR_NAME_LOONGSON;\n        case 0x0731: return FF_GPU_VENDOR_NAME_JINGJIA_MICRO;\n        case 0x19e5: return FF_GPU_VENDOR_NAME_HUAWEI;\n        case 0x1d17: return FF_GPU_VENDOR_NAME_ZHAOXIN;\n        default: return NULL;\n    }\n}\n\nconst char* detectByOpenGL(FFlist* gpus)\n{\n    FF_DEBUG(\"Starting OpenGL GPU detection fallback\");\n\n    FFOpenGLResult result;\n    ffStrbufInit(&result.version);\n    ffStrbufInit(&result.renderer);\n    ffStrbufInit(&result.vendor);\n    ffStrbufInit(&result.slv);\n    ffStrbufInit(&result.library);\n\n    __attribute__((__cleanup__(ffDestroyOpenGLOptions))) FFOpenGLOptions options;\n    ffInitOpenGLOptions(&options);\n    const char* error = ffDetectOpenGL(&options, &result);\n    FF_DEBUG(\"OpenGL detection returns: %s\", error ?: \"success\");\n\n    if (!error)\n    {\n        FFGPUResult* gpu = (FFGPUResult*) ffListAdd(gpus);\n        gpu->type = FF_GPU_TYPE_UNKNOWN;\n        ffStrbufInitMove(&gpu->vendor, &result.vendor);\n        ffStrbufInitMove(&gpu->name, &result.renderer);\n        ffStrbufInit(&gpu->driver);\n        ffStrbufInitF(&gpu->platformApi, \"OpenGL %s\", result.version.chars);\n        ffStrbufInit(&gpu->memoryType);\n        gpu->index = FF_GPU_INDEX_UNSET;\n        gpu->temperature = FF_GPU_TEMP_UNSET;\n        gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;\n        gpu->frequency = FF_GPU_FREQUENCY_UNSET;\n        gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;\n        gpu->dedicated = gpu->shared = (FFGPUMemory){0, 0};\n        gpu->deviceId = 0;\n\n        FF_DEBUG(\"OpenGL reported renderer='%s', vendor='%s', version='%s'\",\n            gpu->name.chars,\n            gpu->vendor.chars,\n            result.version.chars);\n\n        if (ffStrbufContainS(&gpu->name, \"Apple\"))\n        {\n            ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_APPLE);\n            gpu->type = FF_GPU_TYPE_INTEGRATED;\n        }\n        else if (ffStrbufContainS(&gpu->name, \"Intel\"))\n            ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_INTEL);\n        else if (ffStrbufContainS(&gpu->name, \"AMD\") || ffStrbufContainS(&gpu->name, \"ATI\"))\n            ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_AMD);\n        else if (ffStrbufContainS(&gpu->name, \"NVIDIA\"))\n            ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_NVIDIA);\n        else if (ffStrbufContainS(&gpu->name, \"MTT\"))\n            ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_MTHREADS);\n\n        FF_DEBUG(\"OpenGL fallback produced GPU: name='%s', vendor='%s', type=%u\",\n            gpu->name.chars,\n            gpu->vendor.chars,\n            gpu->type);\n\n    }\n\n    ffStrbufDestroy(&result.version);\n    ffStrbufDestroy(&result.renderer);\n    ffStrbufDestroy(&result.vendor);\n    ffStrbufDestroy(&result.slv);\n    ffStrbufDestroy(&result.library);\n    return error;\n}\n\nconst char* ffDetectGPU(const FFGPUOptions* options, FFlist* result)\n{\n    FF_DEBUG(\"Starting GPU detection with method=%d\", (int) options->detectionMethod);\n\n    if (options->detectionMethod <= FF_GPU_DETECTION_METHOD_PCI)\n    {\n        FF_DEBUG(\"Trying PCI/native GPU detection\");\n        const char* error = ffDetectGPUImpl(options, result);\n        if (!error && result->length > 0)\n        {\n            FF_DEBUG(\"PCI/native GPU detection succeeded with %u GPU(s)\", result->length);\n            return NULL;\n        }\n\n        FF_DEBUG(\"PCI/native GPU detection did not produce results (error=%s, gpuCount=%u)\",\n            error ?: \"none\",\n            result->length);\n    }\n    if (options->detectionMethod <= FF_GPU_DETECTION_METHOD_VULKAN)\n    {\n        FF_DEBUG(\"Trying Vulkan GPU detection fallback\");\n        FFVulkanResult* vulkan = ffDetectVulkan();\n        if (!vulkan->error && vulkan->gpus.length > 0)\n        {\n            FF_DEBUG(\"Vulkan detection succeeded with %u GPU(s)\", vulkan->gpus.length);\n            ffListDestroy(result);\n            ffListInitMove(result, &vulkan->gpus);\n\n            #ifdef __ANDROID__\n            double ffGPUDetectTempFromTZ(void);\n            if (options->temp && result->length == 1)\n            {\n                FF_DEBUG(\"Applying Android thermal-zone temperature to single Vulkan GPU\");\n                FF_LIST_GET(FFGPUResult, *result, 0)->temperature = ffGPUDetectTempFromTZ();\n            }\n            #endif\n\n            return NULL;\n        }\n\n        FF_DEBUG(\"Vulkan detection did not produce results (error=%s, gpuCount=%u)\",\n            vulkan->error ?: \"none\",\n            vulkan->gpus.length);\n    }\n    if (options->detectionMethod <= FF_GPU_DETECTION_METHOD_OPENCL)\n    {\n        FF_DEBUG(\"Trying OpenCL GPU detection fallback\");\n        FFOpenCLResult* opencl = ffDetectOpenCL();\n        if (!opencl->error && opencl->gpus.length > 0)\n        {\n            FF_DEBUG(\"OpenCL detection succeeded with %u GPU(s)\", opencl->gpus.length);\n            ffListDestroy(result);\n            ffListInitMove(result, &opencl->gpus);\n            return NULL;\n        }\n\n        FF_DEBUG(\"OpenCL detection did not produce results (error=%s, gpuCount=%u)\",\n            opencl->error ?: \"none\",\n            opencl->gpus.length);\n    }\n    if (options->detectionMethod <= FF_GPU_DETECTION_METHOD_OPENGL)\n    {\n        FF_DEBUG(\"Trying OpenGL GPU detection fallback\");\n        const char* error = detectByOpenGL(result);\n        if (error == NULL)\n        {\n            FF_DEBUG(\"OpenGL fallback succeeded with %u GPU(s)\", result->length);\n            return NULL;\n        }\n\n        FF_DEBUG(\"OpenGL fallback failed: %s\", error);\n    }\n\n    FF_DEBUG(\"GPU detection failed in all enabled backends\");\n    return \"GPU detection failed\";\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/gpu/option.h\"\n\n#define FF_GPU_TEMP_UNSET (-DBL_MAX)\n#define FF_GPU_CORE_COUNT_UNSET -1\n#define FF_GPU_VMEM_SIZE_UNSET ((uint64_t)-1)\n#define FF_GPU_FREQUENCY_UNSET 0\n#define FF_GPU_CORE_USAGE_UNSET (-DBL_MAX)\n#define FF_GPU_INDEX_UNSET ((uint32_t)-1)\n\nextern const char* FF_GPU_VENDOR_NAME_APPLE;\nextern const char* FF_GPU_VENDOR_NAME_AMD;\nextern const char* FF_GPU_VENDOR_NAME_INTEL;\nextern const char* FF_GPU_VENDOR_NAME_NVIDIA;\nextern const char* FF_GPU_VENDOR_NAME_MTHREADS;\nextern const char* FF_GPU_VENDOR_NAME_QUALCOMM;\nextern const char* FF_GPU_VENDOR_NAME_MTK;\nextern const char* FF_GPU_VENDOR_NAME_VMWARE;\nextern const char* FF_GPU_VENDOR_NAME_PARALLEL;\nextern const char* FF_GPU_VENDOR_NAME_MICROSOFT;\nextern const char* FF_GPU_VENDOR_NAME_REDHAT;\nextern const char* FF_GPU_VENDOR_NAME_ORACLE;\nextern const char* FF_GPU_VENDOR_NAME_BROADCOM;\nextern const char* FF_GPU_VENDOR_NAME_LOONGSON;\nextern const char* FF_GPU_VENDOR_NAME_JINGJIA_MICRO;\nextern const char* FF_GPU_VENDOR_NAME_HUAWEI;\nextern const char* FF_GPU_VENDOR_NAME_ZHAOXIN;\n\ntypedef struct FFGPUMemory\n{\n    uint64_t total;\n    uint64_t used;\n} FFGPUMemory;\n\ntypedef struct FFGPUResult\n{\n    uint32_t index;\n    FFGPUType type;\n    FFstrbuf vendor;\n    FFstrbuf name;\n    FFstrbuf driver;\n    FFstrbuf platformApi;\n    FFstrbuf memoryType;\n    double temperature;\n    double coreUsage;\n    int32_t coreCount;\n    uint32_t frequency; // Maximum time clock frequency in MHz\n    FFGPUMemory dedicated;\n    FFGPUMemory shared;\n    uint64_t deviceId;\n} FFGPUResult;\n\nconst char* ffDetectGPU(const FFGPUOptions* options, FFlist* result);\nconst char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus);\n\nconst char* ffGPUGetVendorString(unsigned vendorId);\n\ntypedef struct FFGpuDriverPciBusId\n{\n    uint32_t domain;\n    uint32_t bus;\n    uint32_t device;\n    uint32_t func;\n} FFGpuDriverPciBusId;\n\n#if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__HAIKU__) || defined(__GNU__)\nvoid ffGPUFillVendorAndName(uint8_t subclass, uint16_t vendor, uint16_t device, FFGPUResult* gpu);\nvoid ffGPUQueryAmdGpuName(uint16_t deviceId, uint8_t revisionId, FFGPUResult* gpu);\n\n#if FF_HAVE_DRM\nconst char* ffDrmDetectRadeon(const FFGPUOptions* options, FFGPUResult* gpu, const char* renderPath);\nconst char* ffDrmDetectAmdgpu(const FFGPUOptions* options, FFGPUResult* gpu, const char* renderPath);\nconst char* ffDrmDetectI915(FFGPUResult* gpu, int fd);\nconst char* ffDrmDetectXe(FFGPUResult* gpu, int fd);\nconst char* ffDrmDetectAsahi(FFGPUResult* gpu, int fd);\nconst char* ffDrmDetectNouveau(FFGPUResult* gpu, int fd);\n#endif // FF_HAVE_DRM\n\nconst char* ffGPUDetectDriverSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFGpuDriverPciBusId pciBusId);\n#endif // defined(XXX)\n\nstatic inline uint64_t ffGPUPciAddr2Id(uint64_t domain, uint64_t bus, uint64_t device, uint64_t function)\n{\n    return (domain << 16) | (bus << 8) | (device << 3) | function;\n}\n\nstatic inline uint64_t ffGPUGeneral2Id(uint64_t originalId)\n{\n    // Note: originalId may already have the MSB set\n    return (1ULL << 63) | originalId;\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu_amd.c",
    "content": "#include \"gpu_driver_specific.h\"\n\n#include \"adl.h\"\n#include \"common/library.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/debug.h\"\n\n// Helper function to convert ADL status code to string\nFF_MAYBE_UNUSED static const char* ffAdlStatusToString(int status) {\n    switch (status) {\n        #define FF_ADL_STATUS_CASE(name) case name: return #name;\n        FF_ADL_STATUS_CASE(ADL_OK)\n        FF_ADL_STATUS_CASE(ADL_OK_WARNING)\n        FF_ADL_STATUS_CASE(ADL_OK_MODE_CHANGE)\n        FF_ADL_STATUS_CASE(ADL_OK_RESTART)\n        FF_ADL_STATUS_CASE(ADL_OK_WAIT)\n        FF_ADL_STATUS_CASE(ADL_ERR)\n        FF_ADL_STATUS_CASE(ADL_ERR_NOT_INIT)\n        FF_ADL_STATUS_CASE(ADL_ERR_INVALID_PARAM)\n        FF_ADL_STATUS_CASE(ADL_ERR_INVALID_PARAM_SIZE)\n        FF_ADL_STATUS_CASE(ADL_ERR_INVALID_ADL_IDX)\n        FF_ADL_STATUS_CASE(ADL_ERR_INVALID_CONTROLLER_IDX)\n        FF_ADL_STATUS_CASE(ADL_ERR_INVALID_DIPLAY_IDX)\n        FF_ADL_STATUS_CASE(ADL_ERR_NOT_SUPPORTED)\n        FF_ADL_STATUS_CASE(ADL_ERR_NULL_POINTER)\n        FF_ADL_STATUS_CASE(ADL_ERR_DISABLED_ADAPTER)\n        FF_ADL_STATUS_CASE(ADL_ERR_INVALID_CALLBACK)\n        FF_ADL_STATUS_CASE(ADL_ERR_RESOURCE_CONFLICT)\n        FF_ADL_STATUS_CASE(ADL_ERR_SET_INCOMPLETE)\n        FF_ADL_STATUS_CASE(ADL_ERR_NO_XDISPLAY)\n        FF_ADL_STATUS_CASE(ADL_ERR_CALL_TO_INCOMPATIABLE_DRIVER)\n        FF_ADL_STATUS_CASE(ADL_ERR_NO_ADMINISTRATOR_PRIVILEGES)\n        FF_ADL_STATUS_CASE(ADL_ERR_FEATURESYNC_NOT_STARTED)\n        FF_ADL_STATUS_CASE(ADL_ERR_INVALID_POWER_STATE)\n        #undef FF_ADL_STATUS_CASE\n        default: return \"Unknown ADL error\";\n    }\n}\n\n// Memory allocation function\nstatic void* __attribute__((__stdcall__)) ffAdlMainMemoryAlloc(int iSize)\n{\n    return malloc((size_t) iSize);\n}\n\nstruct FFAdlData {\n    FF_LIBRARY_SYMBOL(ADL2_Main_Control_Destroy)\n    FF_LIBRARY_SYMBOL(ADL2_Adapter_AdapterInfoX3_Get)\n    FF_LIBRARY_SYMBOL(ADL2_Adapter_Graphic_Core_Info_Get)\n    FF_LIBRARY_SYMBOL(ADL2_Adapter_MemoryInfo2_Get)\n    FF_LIBRARY_SYMBOL(ADL2_Adapter_DedicatedVRAMUsage_Get)\n    FF_LIBRARY_SYMBOL(ADL2_Adapter_ASICFamilyType_Get)\n    FF_LIBRARY_SYMBOL(ADL2_Overdrive_Caps)\n    FF_LIBRARY_SYMBOL(ADL2_OverdriveN_CapabilitiesX2_Get)\n    FF_LIBRARY_SYMBOL(ADL2_OverdriveN_SystemClocksX2_Get)\n    FF_LIBRARY_SYMBOL(ADL2_OverdriveN_PerformanceStatus_Get)\n    FF_LIBRARY_SYMBOL(ADL2_OverdriveN_Temperature_Get)\n    FF_LIBRARY_SYMBOL(ADL2_Overdrive8_Current_Setting_Get)\n    FF_LIBRARY_SYMBOL(ADL2_New_QueryPMLogData_Get)\n    FF_LIBRARY_SYMBOL(ADL2_Overdrive6_CurrentStatus_Get)\n    FF_LIBRARY_SYMBOL(ADL2_Overdrive6_Temperature_Get)\n    FF_LIBRARY_SYMBOL(ADL2_Overdrive6_StateInfo_Get)\n\n    bool inited;\n    ADL_CONTEXT_HANDLE apiHandle;\n} adlData;\n\nstatic void shutdownAdl()\n{\n    if (adlData.apiHandle)\n    {\n        FF_DEBUG(\"Destroying ADL context\");\n        adlData.ffADL2_Main_Control_Destroy(adlData.apiHandle);\n        adlData.apiHandle = NULL;\n    }\n}\n\nconst char* ffDetectAmdGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName)\n{\n    FF_DEBUG(\"Attempting to detect AMD GPU info using '%s'\", soName);\n\n    if (!adlData.inited)\n    {\n        adlData.inited = true;\n        FF_DEBUG(\"Initializing ADL library\");\n        FF_LIBRARY_LOAD(atiadl, \"dlopen atiadlxx failed\", soName , 1);\n        FF_LIBRARY_LOAD_SYMBOL_MESSAGE(atiadl, ADL2_Main_Control_Create)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Main_Control_Destroy)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_AdapterInfoX3_Get)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_Graphic_Core_Info_Get)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_MemoryInfo2_Get)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_DedicatedVRAMUsage_Get)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_ASICFamilyType_Get)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Overdrive_Caps)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_OverdriveN_CapabilitiesX2_Get)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_OverdriveN_SystemClocksX2_Get)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_OverdriveN_PerformanceStatus_Get)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Overdrive8_Current_Setting_Get)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_New_QueryPMLogData_Get)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_OverdriveN_Temperature_Get)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Overdrive6_CurrentStatus_Get)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Overdrive6_Temperature_Get)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Overdrive6_StateInfo_Get)\n        FF_DEBUG(\"ADL library loaded\");\n\n        int result = ffADL2_Main_Control_Create(ffAdlMainMemoryAlloc, 1 /*iEnumConnectedAdapters*/, &adlData.apiHandle);\n        FF_DEBUG(\"ADL2_Main_Control_Create returned %s (%d)\", ffAdlStatusToString(result), result);\n        if (result != ADL_OK)\n            return \"ffADL2_Main_Control_Create() failed\";\n\n        atexit(shutdownAdl);\n        atiadl = NULL; // don't close atiadl\n        FF_DEBUG(\"ADL initialization complete\");\n    }\n\n    if (!adlData.apiHandle)\n    {\n        FF_DEBUG(\"ADL context not initialized\");\n        return \"ffADL2_Main_Control_Create() failed\";\n    }\n\n    FF_AUTO_FREE AdapterInfo* devices = NULL;\n    int numDevices = 0;\n    int adapterResult = adlData.ffADL2_Adapter_AdapterInfoX3_Get(adlData.apiHandle, -1, &numDevices, &devices);\n    FF_DEBUG(\"ADL2_Adapter_AdapterInfoX3_Get returned %s (%d)\", ffAdlStatusToString(adapterResult), adapterResult);\n\n    if (adapterResult == ADL_OK)\n    {\n        FF_DEBUG(\"found %d adapters\", numDevices);\n    }\n    else\n    {\n        FF_DEBUG(\"ffADL2_Adapter_AdapterInfoX3_Get() failed\");\n        return \"ffADL2_Adapter_AdapterInfoX3_Get() failed\";\n    }\n\n    const AdapterInfo* device = NULL;\n    for (int iDev = 0; iDev < numDevices; iDev++)\n    {\n        if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID)\n        {\n            FF_DEBUG(\"Checking device %d: bus=%d, device=%d, func=%d against requested bus=%u, device=%u, func=%u\",\n                iDev, devices[iDev].iBusNumber, devices[iDev].iDeviceNumber, devices[iDev].iFunctionNumber,\n                cond->pciBusId.bus, cond->pciBusId.device, cond->pciBusId.func);\n\n            if (\n                cond->pciBusId.bus == (uint32_t) devices[iDev].iBusNumber &&\n                cond->pciBusId.device == (uint32_t) devices[iDev].iDeviceNumber &&\n                cond->pciBusId.func == (uint32_t) devices[iDev].iFunctionNumber)\n            {\n                device = &devices[iDev];\n                FF_DEBUG(\"Found matching device: %s (index: %d)\", device->strAdapterName, device->iAdapterIndex);\n                break;\n            }\n        }\n    }\n\n    if (!device)\n    {\n        FF_DEBUG(\"Device not found\");\n        return \"Device not found\";\n    }\n\n    if (result.coreCount)\n    {\n        ADLGraphicCoreInfo coreInfo;\n        int status = adlData.ffADL2_Adapter_Graphic_Core_Info_Get(adlData.apiHandle, device->iAdapterIndex, &coreInfo);\n        FF_DEBUG(\"ADL2_Adapter_Graphic_Core_Info_Get returned %s (%d)\", ffAdlStatusToString(status), status);\n\n        if (status == ADL_OK)\n        {\n            FF_DEBUG(\"Core info - NumCUs: %d, NumPEsPerCU: %d\", coreInfo.iNumCUs, coreInfo.iNumPEsPerCU);\n            *result.coreCount = (uint32_t) coreInfo.iNumCUs * (uint32_t) coreInfo.iNumPEsPerCU;\n            FF_DEBUG(\"Got core count: %u\", *result.coreCount);\n        }\n        else\n        {\n            FF_DEBUG(\"Failed to get core count\");\n        }\n    }\n\n    if (result.memory)\n    {\n        int vramUsage = 0;\n        int status = adlData.ffADL2_Adapter_DedicatedVRAMUsage_Get(adlData.apiHandle, device->iAdapterIndex, &vramUsage);\n        FF_DEBUG(\"ADL2_Adapter_DedicatedVRAMUsage_Get returned %s (%d), usage: %d MB\",\n            ffAdlStatusToString(status), status, vramUsage);\n\n        if (status == ADL_OK && vramUsage >= 0) {\n            result.memory->used = (uint64_t) vramUsage * 1024 * 1024;\n            FF_DEBUG(\"Dedicated VRAM usage: %llu bytes (%d MB)\", result.memory->used, vramUsage);\n        } else {\n            FF_DEBUG(\"Failed to get dedicated VRAM usage\");\n        }\n    }\n\n    if (result.memoryType)\n    {\n        ADLMemoryInfo2 memoryInfo;\n        int status = adlData.ffADL2_Adapter_MemoryInfo2_Get(adlData.apiHandle, device->iAdapterIndex, &memoryInfo);\n        FF_DEBUG(\"ADL2_Adapter_MemoryInfo2_Get returned %s (%d)\", ffAdlStatusToString(status), status);\n\n        if (status == ADL_OK)\n        {\n            FF_DEBUG(\"Memory info - Type: %s, Size: %lld MB\", memoryInfo.strMemoryType, memoryInfo.iMemorySize / 1024 / 1024);\n            ffStrbufSetS(result.memoryType, memoryInfo.strMemoryType);\n            FF_DEBUG(\"Got memory type: %s\", memoryInfo.strMemoryType);\n        }\n        else\n        {\n            FF_DEBUG(\"Failed to get memory type\");\n        }\n    }\n\n    if (result.type)\n    {\n        int asicTypes = 0;\n        int valids = 0;\n        int status = adlData.ffADL2_Adapter_ASICFamilyType_Get(adlData.apiHandle, device->iAdapterIndex, &asicTypes, &valids);\n        FF_DEBUG(\"ADL2_Adapter_ASICFamilyType_Get returned %s (%d), asicTypes: 0x%x, valids: 0x%x\",\n            ffAdlStatusToString(status), status, asicTypes, valids);\n\n        if (status == ADL_OK)\n        {\n            asicTypes &= valids; // This design is strange\n            *result.type = asicTypes & ADL_ASIC_INTEGRATED ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;\n            FF_DEBUG(\"GPU type: %s (asicTypes: 0x%x, valids: 0x%x)\",\n                    *result.type == FF_GPU_TYPE_INTEGRATED ? \"Integrated\" : \"Discrete\", asicTypes, valids);\n        }\n        else\n        {\n            FF_DEBUG(\"Failed to get GPU type\");\n        }\n    }\n\n    if (result.index)\n    {\n        *result.index = (uint32_t) device->iAdapterIndex;\n        FF_DEBUG(\"Setting adapter index: %u\", *result.index);\n    }\n\n    if (result.name)\n    {\n        ffStrbufSetS(result.name, device->strAdapterName);\n        FF_DEBUG(\"Setting adapter name: %s; UDID: %s, Present: %d, Exist: %d\", device->strAdapterName, device->strUDID, device->iPresent, device->iExist);\n    }\n\n    int odVersion = 0;\n\n    {\n        int odSupported = 0;\n        int odEnabled = 0;\n        int status = adlData.ffADL2_Overdrive_Caps(adlData.apiHandle, device->iAdapterIndex, &odSupported, &odEnabled, &odVersion);\n        FF_DEBUG(\"ADL2_Overdrive_Caps returned %s (%d); supported %d, enabled %d; version %d\",\n                ffAdlStatusToString(status), status, odSupported, odEnabled, odVersion);\n        if (status != ADL_OK)\n        {\n            FF_DEBUG(\"Overdrive not supported, results may be inaccurate\");\n            // Note even if Overdrive is not supported, we can still get the OD version\n        }\n    }\n\n\n    if (odVersion == 8)\n    {\n        FF_DEBUG(\"Using Overdrive8 API (odVersion=%d)\", odVersion);\n\n        if (result.frequency)\n        {\n            ADLOD8CurrentSetting currentSetting = { .count = OD8_COUNT };\n            int status = adlData.ffADL2_Overdrive8_Current_Setting_Get(adlData.apiHandle, device->iAdapterIndex, &currentSetting);\n            FF_DEBUG(\"ADL2_Overdrive8_Current_Setting_Get returned %s (%d)\", ffAdlStatusToString(status), status);\n            if (status == ADL_OK)\n            {\n                FF_DEBUG(\"OD8 Settings count: %d\", currentSetting.count);\n\n                *result.frequency = (uint32_t) currentSetting.Od8SettingTable[OD8_GFXCLK_FMAX];\n                FF_DEBUG(\"Got max engine clock (OD8_GFXCLK_FMAX): %u MHz\", *result.frequency);\n            }\n            else\n            {\n                FF_DEBUG(\"Failed to get max frequency information\");\n            }\n        }\n\n        if (result.temp || result.coreUsage)\n        {\n            ADLPMLogDataOutput pmLogDataOutput = {};\n            int status = adlData.ffADL2_New_QueryPMLogData_Get(adlData.apiHandle, device->iAdapterIndex, &pmLogDataOutput);\n            FF_DEBUG(\"ADL2_New_QueryPMLogData_Get returned %s (%d)\", ffAdlStatusToString(status), status);\n            if (status == ADL_OK)\n            {\n                if (result.temp)\n                {\n                    ADLSingleSensorData* sensor = &pmLogDataOutput.sensors[ADL_PMLOG_TEMPERATURE_HOTSPOT];\n                    FF_DEBUG(\"Sensor %d: %s, supported: %d, value: %d\", ADL_PMLOG_TEMPERATURE_HOTSPOT, \"ADL_PMLOG_TEMPERATURE_HOTSPOT\", sensor->supported, sensor->value);\n                    if (sensor->supported)\n                    {\n                        *result.temp = sensor->value;\n                        FF_DEBUG(\"Temperature: %.1f°C (HOTSPOT)\", *result.temp);\n                    }\n                    else\n                    {\n                        sensor = &pmLogDataOutput.sensors[ADL_PMLOG_TEMPERATURE_GFX];\n                        FF_DEBUG(\"Sensor %d: %s, supported: %d, value: %d\", ADL_PMLOG_TEMPERATURE_GFX, \"ADL_PMLOG_TEMPERATURE_GFX\", sensor->supported, sensor->value);\n                        if (sensor->supported)\n                        {\n                            *result.temp = sensor->value;\n                            FF_DEBUG(\"Temperature: %.1f°C (GFX)\", *result.temp);\n                        }\n                        else\n                        {\n                            sensor = &pmLogDataOutput.sensors[ADL_PMLOG_TEMPERATURE_SOC];\n                            FF_DEBUG(\"Sensor %d: %s, supported: %d, value: %d\", ADL_PMLOG_TEMPERATURE_SOC, \"ADL_PMLOG_TEMPERATURE_SOC\", sensor->supported, sensor->value);\n                            if (sensor->supported)\n                            {\n                                *result.temp = sensor->value;\n                                FF_DEBUG(\"Temperature: %.1f°C (SOC)\", *result.temp);\n                            }\n                            else\n                            {\n                                FF_DEBUG(\"No supported temp sensor found, temp detection failed\");\n                            }\n                        }\n                    }\n                }\n                if (result.coreUsage)\n                {\n                    ADLSingleSensorData* activity = &pmLogDataOutput.sensors[ADL_PMLOG_INFO_ACTIVITY_GFX];\n                    FF_DEBUG(\"Sensor %d: %s, supported: %d, value: %d\", ADL_PMLOG_INFO_ACTIVITY_GFX, \"ADL_PMLOG_INFO_ACTIVITY_GFX\", activity->supported, activity->value);\n                    if (activity->supported)\n                    {\n                        *result.coreUsage = activity->value;\n                        FF_DEBUG(\"Core usage: %.1f%%\", *result.coreUsage);\n                    }\n                    else\n                    {\n                        FF_DEBUG(\"Sensor %d not supported, GPU usage detection failed\", ADL_PMLOG_INFO_ACTIVITY_GFX);\n                    }\n                }\n            }\n            else\n            {\n                FF_DEBUG(\"Failed to get temperature / GPU activity\");\n            }\n        }\n    }\n    else if (odVersion == 7)\n    {\n        FF_DEBUG(\"Using OverdriveN API (odVersion=%d)\", odVersion);\n\n        if (result.frequency)\n        {\n            // https://github.com/MaynardMiner/odvii/blob/master/OverdriveN.cpp#L176\n            ADLODNCapabilitiesX2 odCapabilities = {};\n            int status = adlData.ffADL2_OverdriveN_CapabilitiesX2_Get(adlData.apiHandle, device->iAdapterIndex, &odCapabilities);\n            FF_DEBUG(\"ADL2_OverdriveN_CapabilitiesX2_Get returned %s (%d)\", ffAdlStatusToString(status), status);\n\n            if (status == ADL_OK)\n            {\n                if (odCapabilities.iMaximumNumberOfPerformanceLevels == 0)\n                {\n                    FF_DEBUG(\"ADL2_OverdriveN_CapabilitiesX2_Get: no performance levels available\");\n                }\n                else\n                {\n                    FF_DEBUG(\"ODN Capabilities - MaxPerformanceLevels: %d, GPU Clock Range: [%d - %d]\",\n                            odCapabilities.iMaximumNumberOfPerformanceLevels,\n                            odCapabilities.sEngineClockRange.iMin, odCapabilities.sEngineClockRange.iMax);\n\n                    size_t size = sizeof(ADLODNPerformanceLevelsX2) + sizeof(ADLODNPerformanceLevelX2) * ((unsigned) odCapabilities.iMaximumNumberOfPerformanceLevels - 1);\n                    FF_AUTO_FREE ADLODNPerformanceLevelsX2* odPerfLevels = calloc(size, 1);\n                    odPerfLevels->iSize = (int) size;\n                    odPerfLevels->iNumberOfPerformanceLevels = odCapabilities.iMaximumNumberOfPerformanceLevels;\n                    odPerfLevels->iMode = ODNControlType_Current;\n\n                    int status = adlData.ffADL2_OverdriveN_SystemClocksX2_Get(adlData.apiHandle, device->iAdapterIndex, odPerfLevels);\n                    FF_DEBUG(\"ADL2_OverdriveN_SystemClocksX2_Get returned %s (%d), levels: %d\",\n                            ffAdlStatusToString(status), status, odPerfLevels->iNumberOfPerformanceLevels);\n\n                    if (status != ADL_OK)\n                    {\n                        FF_DEBUG(\"Failed to get frequency information\");\n                    }\n                    else\n                    {\n                        // lowest to highest\n                        for (int i = odPerfLevels->iNumberOfPerformanceLevels - 1; i >= 0 ; i--)\n                        {\n                            ADLODNPerformanceLevelX2* level = &odPerfLevels->aLevels[i];\n                            FF_DEBUG(\"Performance level %d: enabled: %d, engine clock = %d\", i, level->iEnabled, level->iClock);\n                            if (level->iEnabled)\n                            {\n                                *result.frequency = (uint32_t) level->iClock / 100; // in 10 kHz\n                                FF_DEBUG(\"Got max engine clock: %u MHz\", *result.frequency);\n                                break;\n                            }\n                        }\n                    }\n                }\n            }\n            else\n            {\n                FF_DEBUG(\"Failed to get frequency information\");\n            }\n        }\n\n        if (result.coreUsage)\n        {\n            ADLODNPerformanceStatus performanceStatus = {};\n            int status = adlData.ffADL2_OverdriveN_PerformanceStatus_Get(adlData.apiHandle, device->iAdapterIndex, &performanceStatus);\n            FF_DEBUG(\"ADL2_OverdriveN_PerformanceStatus_Get returned %s (%d)\", ffAdlStatusToString(status), status);\n\n            if (status == ADL_OK)\n            {\n                FF_DEBUG(\"Performance Status - Activity: %d%%, CoreClock: %dMHz, MemoryClock: %dMHz\",\n                        performanceStatus.iGPUActivityPercent,\n                        performanceStatus.iCoreClock,\n                        performanceStatus.iMemoryClock);\n\n                *result.coreUsage = performanceStatus.iGPUActivityPercent;\n                FF_DEBUG(\"Got GPU activity: %d%%\", performanceStatus.iGPUActivityPercent);\n            }\n            else\n            {\n                FF_DEBUG(\"Failed to get GPU activity\");\n            }\n        }\n\n        if (result.temp)\n        {\n            int milliDegrees = 0;\n            int status = adlData.ffADL2_OverdriveN_Temperature_Get(adlData.apiHandle, device->iAdapterIndex, 1, &milliDegrees);\n            FF_DEBUG(\"ADL2_OverdriveN_Temperature_Get returned %s (%d)\", ffAdlStatusToString(status), status);\n\n            if (status == ADL_OK)\n            {\n                *result.temp = milliDegrees / 1000.0;\n                FF_DEBUG(\"Temperature: %.1f°C (raw: %d milliC)\", *result.temp, milliDegrees);\n            }\n            else\n            {\n                FF_DEBUG(\"Failed to get temperature\");\n            }\n        }\n    }\n    else if (odVersion == 6)\n    {\n        FF_DEBUG(\"Using Overdrive6 API (odVersion=%d)\", odVersion);\n\n        if (result.frequency)\n        {\n            FF_AUTO_FREE ADLOD6StateInfo* stateInfo = calloc(sizeof(ADLOD6StateInfo) + sizeof(ADLOD6PerformanceLevel), 1);\n            stateInfo->iNumberOfPerformanceLevels = 2;\n\n            int status = adlData.ffADL2_Overdrive6_StateInfo_Get(adlData.apiHandle, device->iAdapterIndex, ADL_OD6_GETSTATEINFO_CUSTOM_PERFORMANCE, stateInfo);\n            FF_DEBUG(\"ADL2_Overdrive6_StateInfo_Get returned %s (%d), performance levels: %d\",\n                ffAdlStatusToString(status), status, stateInfo->iNumberOfPerformanceLevels);\n\n            if (status == ADL_OK)\n            {\n                // OD6 uses clock ranges instead of discrete performance levels.\n                // iNumberOfPerformanceLevels is always 2.\n                // The 1st level indicates the minimum clocks in the range.\n                // The 2nd level indicates the maximum clocks in the range.\n                if (stateInfo->iNumberOfPerformanceLevels != 2)\n                {\n                    FF_DEBUG(\"ADL2_Overdrive6_StateInfo_Get: unexpected number of performance levels: %d\", stateInfo->iNumberOfPerformanceLevels);\n                }\n                else\n                {\n                    FF_DEBUG(\"OD6 Settings - MinPerformanceLevels: %d, MaxPerformanceLevels: %d\",\n                            stateInfo->aLevels[0].iEngineClock, stateInfo->aLevels[1].iEngineClock);\n                    *result.frequency = (uint32_t) stateInfo->aLevels[1].iEngineClock / 100; // in 10 kHz\n                    FF_DEBUG(\"Got max engine clock: %u MHz\", *result.frequency);\n                }\n            }\n            else\n            {\n                FF_DEBUG(\"Failed to get frequency information\");\n            }\n        }\n\n        if (result.coreUsage)\n        {\n            ADLOD6CurrentStatus status = {};\n            int apiStatus = adlData.ffADL2_Overdrive6_CurrentStatus_Get(adlData.apiHandle, device->iAdapterIndex, &status);\n            FF_DEBUG(\"ADL2_Overdrive6_CurrentStatus_Get returned %s (%d)\", ffAdlStatusToString(apiStatus), apiStatus);\n\n            if (apiStatus == ADL_OK)\n            {\n                *result.coreUsage = status.iActivityPercent;\n                FF_DEBUG(\"Got GPU activity: %d%%\", status.iActivityPercent);\n            }\n            else\n            {\n                FF_DEBUG(\"Failed to get GPU activity\");\n            }\n        }\n\n        if (result.temp)\n        {\n            int milliDegrees = 0;\n            int status = adlData.ffADL2_Overdrive6_Temperature_Get(adlData.apiHandle, device->iAdapterIndex, &milliDegrees);\n            FF_DEBUG(\"ADL2_Overdrive6_Temperature_Get returned %s (%d), temperature: %d milliC\",\n                ffAdlStatusToString(status), status, milliDegrees);\n\n            if (status == ADL_OK)\n            {\n                *result.temp = milliDegrees / 1000.0;\n                FF_DEBUG(\"Temperature: %.1f°C\", *result.temp);\n            }\n            else\n            {\n                FF_DEBUG(\"Failed to get temperature\");\n            }\n        }\n    }\n    else\n    {\n        FF_DEBUG(\"Unknown Overdrive version: %d\", odVersion);\n        return \"Unknown Overdrive version\";\n    }\n    FF_DEBUG(\"AMD GPU detection complete - returning success\");\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu_android.c",
    "content": "#include \"gpu.h\"\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n\n#include <fcntl.h>\n\nstatic double parseTZDir(int dfd, FFstrbuf* buffer)\n{\n    if (!ffReadFileBufferRelative(dfd, \"type\", buffer) || !ffStrbufStartsWithS(buffer, \"gpu\"))\n        return FF_GPU_TEMP_UNSET;\n\n    if (!ffReadFileBufferRelative(dfd, \"temp\", buffer))\n        return FF_GPU_TEMP_UNSET;\n\n    double value = ffStrbufToDouble(buffer, FF_GPU_TEMP_UNSET);// millidegree Celsius\n    if (value == FF_GPU_TEMP_UNSET)\n        return FF_GPU_TEMP_UNSET;\n\n    return value / 1000.;\n}\n\ndouble ffGPUDetectTempFromTZ(void)\n{\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(\"/sys/class/thermal/\");\n    if(dirp)\n    {\n        FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n        int dfd = dirfd(dirp);\n        struct dirent* entry;\n        while((entry = readdir(dirp)) != NULL)\n        {\n            if(entry->d_name[0] == '.')\n                continue;\n            if(!ffStrStartsWith(entry->d_name, \"thermal_zone\"))\n                continue;\n\n            FF_AUTO_CLOSE_FD int subfd = openat(dfd, entry->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);\n            if(subfd < 0)\n                continue;\n\n            double result = parseTZDir(subfd, &buffer);\n            if (result != FF_GPU_TEMP_UNSET)\n                return result;\n        }\n    }\n    return FF_GPU_TEMP_UNSET;\n}\n\nconst char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus)\n{\n    FF_UNUSED(options, gpus);\n    return \"No permission. Fallbacks to Vulkan, OpenCL or OpenGL instead\";\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu_apple.c",
    "content": "#include \"gpu.h\"\n#include \"common/apple/cf_helpers.h\"\n#include \"common/apple/smc_temps.h\"\n\n#include <IOKit/graphics/IOGraphicsLib.h>\n\nconst char* ffGpuDetectMetal(FFlist* gpus);\nconst char* ffGpuDetectDriverVersion(FFlist* gpus);\n\nstatic double detectGpuTemp(const FFstrbuf* gpuName)\n{\n    double result = 0;\n    const char* error = NULL;\n\n    if (ffStrbufStartsWithS(gpuName, \"Apple M\"))\n    {\n        switch (strtol(gpuName->chars + strlen(\"Apple M\"), NULL, 10))\n        {\n            case 0: error = \"Invalid Apple Silicon GPU\"; break;\n            case 1: error = ffDetectSmcTemps(FF_TEMP_GPU_M1X, &result); break;\n            case 2: error = ffDetectSmcTemps(FF_TEMP_GPU_M2X, &result); break;\n            case 3: error = ffDetectSmcTemps(FF_TEMP_GPU_M3X, &result); break;\n            case 4: error = ffDetectSmcTemps(FF_TEMP_GPU_M4X, &result); break;\n            default: error = \"Unsupported Apple Silicon GPU\"; break;\n        }\n    }\n    else if (ffStrbufStartsWithS(gpuName, \"Intel\"))\n        error = ffDetectSmcTemps(FF_TEMP_GPU_INTEL, &result);\n    else if (ffStrbufStartsWithS(gpuName, \"Radeon\") || ffStrbufStartsWithS(gpuName, \"AMD\"))\n        error = ffDetectSmcTemps(FF_TEMP_GPU_AMD, &result);\n    else\n        error = ffDetectSmcTemps(FF_TEMP_GPU_UNKNOWN, &result);\n\n    if (error)\n        return FF_GPU_TEMP_UNSET;\n\n    return result;\n}\n\n#ifdef __aarch64__\n#include \"common/apple/cf_helpers.h\"\n\n#include <IOKit/IOKitLib.h>\n\nstatic const char* detectFrequency(FFGPUResult* gpu)\n{\n    // https://github.com/giampaolo/psutil/pull/2222/files\n\n    FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDevice = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceNameMatching(\"pmgr\"));\n    if (!entryDevice)\n        return \"IOServiceGetMatchingServices() failed\";\n\n    if (!IOObjectConformsTo(entryDevice, \"AppleARMIODevice\"))\n        return \"\\\"pmgr\\\" should conform to \\\"AppleARMIODevice\\\"\";\n\n    FF_CFTYPE_AUTO_RELEASE CFDataRef freqProperty = (CFDataRef) IORegistryEntryCreateCFProperty(entryDevice, CFSTR(\"voltage-states9-sram\"), kCFAllocatorDefault, kNilOptions);\n    if (!freqProperty || CFGetTypeID(freqProperty) != CFDataGetTypeID())\n        return \"\\\"voltage-states9-sram\\\" in \\\"pmgr\\\" is not found\";\n\n    // voltage-states9-sram stores supported <frequency / voltage> pairs of gpu from the lowest to the highest\n    CFIndex propLength = CFDataGetLength(freqProperty);\n    if (propLength == 0 || propLength % (CFIndex) sizeof(uint32_t) * 2 != 0)\n        return \"Invalid \\\"voltage-states9-sram\\\" length\";\n\n    uint32_t* pStart = (uint32_t*) CFDataGetBytePtr(freqProperty);\n    uint32_t pMax = *pStart;\n    for (CFIndex i = 2; i < propLength / (CFIndex) sizeof(uint32_t) && pStart[i] > 0; i += 2 /* skip voltage */)\n        pMax = pMax > pStart[i] ? pMax : pStart[i];\n\n    if (pMax > 0)\n    {\n        // While this is not necessary for now (seems), we add this logic just in case. See cpu_apple.c\n        if (pMax > 100000000) // Assume that pMax is in Hz\n            gpu->frequency = pMax / 1000 / 1000;\n        else // Assume that pMax is in kHz\n            gpu->frequency = pMax / 1000;\n    }\n\n    return NULL;\n}\n#endif\n\nconst char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus)\n{\n    FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL;\n    {\n        CFMutableDictionaryRef matches = IOServiceMatching(kIOAcceleratorClassName);\n        CFDictionaryAddValue(matches, CFSTR(\"IOMatchCategory\"), CFSTR(kIOAcceleratorClassName));\n        if (IOServiceGetMatchingServices(MACH_PORT_NULL, matches, &iterator) != kIOReturnSuccess)\n            return \"IOServiceGetMatchingServices() failed\";\n    }\n\n    io_registry_entry_t registryEntry;\n    while ((registryEntry = IOIteratorNext(iterator)) != IO_OBJECT_NULL)\n    {\n        CFMutableDictionaryRef properties;\n        if(IORegistryEntryCreateCFProperties(registryEntry, &properties, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess)\n        {\n            IOObjectRelease(registryEntry);\n            continue;\n        }\n\n        FFGPUResult* gpu = ffListAdd(gpus);\n        gpu->index = FF_GPU_INDEX_UNSET;\n        ffStrbufInit(&gpu->memoryType);\n        gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;\n        gpu->type = FF_GPU_TYPE_UNKNOWN;\n        gpu->frequency = FF_GPU_FREQUENCY_UNSET;\n        IORegistryEntryGetRegistryEntryID(registryEntry, &gpu->deviceId);\n        ffStrbufInitStatic(&gpu->platformApi, \"IOKit\");\n\n        ffStrbufInit(&gpu->driver); // Ok for both Apple and Intel\n        ffCfDictGetString(properties, CFSTR(\"CFBundleIdentifier\"), &gpu->driver);\n\n        if(ffCfDictGetInt(properties, CFSTR(\"gpu-core-count\"), &gpu->coreCount) != NULL) // For Apple\n            gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;\n\n        gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;\n        CFDictionaryRef perfStatistics = NULL;\n        uint64_t vramUsed = 0, vramTotal = 0;\n        if (ffCfDictGetDict(properties, CFSTR(\"PerformanceStatistics\"), &perfStatistics) == NULL)\n        {\n            int64_t utilization;\n            if (ffCfDictGetInt64(perfStatistics, CFSTR(\"Device Utilization %\"), &utilization) == NULL)\n                gpu->coreUsage = (double) utilization;\n            else if (ffCfDictGetInt64(perfStatistics, CFSTR(\"GPU Core Utilization\"), &utilization) == NULL)\n                gpu->coreUsage = (double) utilization / 10000000.; // Nvidia?\n\n            if (ffCfDictGetInt64(perfStatistics, CFSTR(\"Alloc system memory\"), (int64_t*) &vramTotal) == NULL)\n            {\n                if (ffCfDictGetInt64(perfStatistics, CFSTR(\"In use system memory\"), (int64_t*) &vramUsed) != NULL)\n                    vramTotal = 0;\n            }\n            else if (ffCfDictGetInt64(perfStatistics, CFSTR(\"vramFreeBytes\"), (int64_t*) &vramTotal) == NULL)\n            {\n                if (ffCfDictGetInt64(perfStatistics, CFSTR(\"vramUsedBytes\"), (int64_t*) &vramUsed) == NULL)\n                    vramTotal += vramUsed;\n                else\n                    vramTotal = 0;\n            }\n        }\n\n        ffStrbufInit(&gpu->name);\n        //IOAccelerator returns model / vendor-id properties for Apple Silicon, but not for Intel Iris GPUs.\n        //Still needs testing for AMD's\n        if(ffCfDictGetString(properties, CFSTR(\"model\"), &gpu->name) != NULL)\n        {\n            CFRelease(properties);\n            properties = NULL;\n\n            FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t parentEntry = 0;\n            if(IORegistryEntryGetParentEntry(registryEntry, kIOServicePlane, &parentEntry) != kIOReturnSuccess ||\n                IORegistryEntryCreateCFProperties(parentEntry, &properties, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess)\n            {\n                IOObjectRelease(registryEntry);\n                continue;\n            }\n            ffCfDictGetString(properties, CFSTR(\"model\"), &gpu->name);\n        }\n\n        ffStrbufInit(&gpu->vendor);\n        int vendorId;\n        if(ffCfDictGetInt(properties, CFSTR(\"vendor-id\"), &vendorId) == NULL)\n        {\n            const char* vendorStr = ffGPUGetVendorString((unsigned) vendorId);\n            ffStrbufAppendS(&gpu->vendor, vendorStr);\n            if (vendorStr == FF_GPU_VENDOR_NAME_APPLE || vendorStr == FF_GPU_VENDOR_NAME_INTEL)\n                gpu->type = FF_GPU_TYPE_INTEGRATED;\n            else if (vendorStr == FF_GPU_VENDOR_NAME_NVIDIA || vendorStr == FF_GPU_VENDOR_NAME_AMD)\n                gpu->type = FF_GPU_TYPE_DISCRETE;\n\n            #ifdef __aarch64__\n            if (vendorStr == FF_GPU_VENDOR_NAME_APPLE)\n                detectFrequency(gpu);\n            #endif\n\n            if (gpu->type == FF_GPU_TYPE_INTEGRATED)\n            {\n                gpu->shared.total = vramTotal;\n                gpu->shared.used = vramUsed;\n            }\n            else if (gpu->type == FF_GPU_TYPE_DISCRETE)\n            {\n                gpu->dedicated.total = vramTotal;\n                gpu->dedicated.used = vramUsed;\n            }\n        }\n\n        gpu->temperature = options->temp ? detectGpuTemp(&gpu->name) : FF_GPU_TEMP_UNSET;\n\n        CFRelease(properties);\n        IOObjectRelease(registryEntry);\n    }\n\n    ffGpuDetectMetal(gpus);\n    if (instance.config.general.detectVersion)\n        ffGpuDetectDriverVersion(gpus);\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu_apple.m",
    "content": "#include \"gpu.h\"\n\n#import <Metal/MTLDevice.h>\n#import <IOKit/kext/KextManager.h>\n\n#ifndef MAC_OS_VERSION_26_0\n    #define MTLGPUFamilyMetal4 ((MTLGPUFamily) 5002)\n#endif\n#ifndef MAC_OS_VERSION_13_0\n    #define MTLGPUFamilyMetal3 ((MTLGPUFamily) 5001)\n#endif\n#ifndef MAC_OS_X_VERSION_10_15\n    #define MTLFeatureSet_macOS_GPUFamily1_v4 ((MTLFeatureSet) 10004)\n    #define MTLFeatureSet_macOS_GPUFamily2_v1 ((MTLFeatureSet) 10005)\n#endif\n\nconst char* ffGpuDetectDriverVersion(FFlist* gpus)\n{\n    if (@available(macOS 10.7, *))\n    {\n        NSMutableArray* arr = NSMutableArray.new;\n        FF_LIST_FOR_EACH(FFGPUResult, x, *gpus)\n            [arr addObject:@(x->driver.chars)];\n\n        NSDictionary* dict = CFBridgingRelease(KextManagerCopyLoadedKextInfo((__bridge CFArrayRef)arr, (__bridge CFArrayRef)@[@\"CFBundleVersion\"]));\n        FF_LIST_FOR_EACH(FFGPUResult, x, *gpus)\n        {\n            NSString* version = dict[@(x->driver.chars)][@\"CFBundleVersion\"];\n            if (version)\n            {\n                ffStrbufAppendC(&x->driver, ' ');\n                ffStrbufAppendS(&x->driver, version.UTF8String);\n            }\n        }\n        return NULL;\n    }\n    return \"Unsupported macOS version\";\n}\n\nconst char* ffGpuDetectMetal(FFlist* gpus)\n{\n    if (@available(macOS 10.13, *))\n    {\n        for (id<MTLDevice> device in MTLCopyAllDevices())\n        {\n            FFGPUResult* gpu = NULL;\n            FF_LIST_FOR_EACH(FFGPUResult, x, *gpus)\n            {\n                if (x->deviceId == device.registryID)\n                {\n                    gpu = x;\n                    break;\n                }\n            }\n            if (!gpu) continue;\n\n            #ifndef MAC_OS_X_VERSION_10_15\n            if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily2_v1])\n                ffStrbufSetStatic(&gpu->platformApi, \"Metal Feature Set 2\");\n            else if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v1])\n                ffStrbufSetStatic(&gpu->platformApi, \"Metal Feature Set 1\");\n            #else // MAC_OS_X_VERSION_10_15\n            #pragma clang diagnostic push\n            #pragma clang diagnostic ignored \"-Wunguarded-availability-new\"\n            if ([device supportsFamily:MTLGPUFamilyMetal4])\n                ffStrbufSetStatic(&gpu->platformApi, \"Metal 4\");\n            else if ([device supportsFamily:MTLGPUFamilyMetal3])\n                ffStrbufSetStatic(&gpu->platformApi, \"Metal 3\");\n            #pragma clang diagnostic pop\n            else if ([device supportsFamily:MTLGPUFamilyCommon3])\n                ffStrbufSetStatic(&gpu->platformApi, \"Metal Common 3\");\n            else if ([device supportsFamily:MTLGPUFamilyCommon2])\n                ffStrbufSetStatic(&gpu->platformApi, \"Metal Common 2\");\n            else if ([device supportsFamily:MTLGPUFamilyCommon1])\n                ffStrbufSetStatic(&gpu->platformApi, \"Metal Common 1\");\n\n            gpu->type = device.hasUnifiedMemory ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;\n            gpu->index = (uint32_t) device.locationNumber;\n\n            if (device.hasUnifiedMemory && device.recommendedMaxWorkingSetSize > 0)\n                gpu->shared.total = device.recommendedMaxWorkingSetSize;\n            #endif\n        }\n        return NULL;\n    }\n    return \"Metal API is not supported by this macOS version\";\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu_bsd.c",
    "content": "#include \"gpu_driver_specific.h\"\n\n#include \"common/io.h\"\n#include \"common/mallocHelper.h\"\n\n#include <sys/pciio.h>\n#include <fcntl.h>\n#if __has_include(<dev/pci/pcireg.h>)\n    #include <dev/pci/pcireg.h> // FreeBSD\n#else\n    #include <bus/pci/pcireg.h> // DragonFly\n#endif\n\nstatic void fillGPUTypeGeneric(FFGPUResult* gpu)\n{\n    if (gpu->type == FF_GPU_TYPE_UNKNOWN)\n    {\n        if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_NVIDIA)\n        {\n            if (ffStrbufStartsWithIgnCaseS(&gpu->name, \"GeForce\") ||\n                ffStrbufStartsWithIgnCaseS(&gpu->name, \"Quadro\") ||\n                ffStrbufStartsWithIgnCaseS(&gpu->name, \"Tesla\"))\n                gpu->type = FF_GPU_TYPE_DISCRETE;\n        }\n        else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_MTHREADS)\n        {\n            if (ffStrbufStartsWithIgnCaseS(&gpu->name, \"MTT \"))\n                gpu->type = FF_GPU_TYPE_DISCRETE;\n        }\n        else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_INTEL)\n        {\n            // 0000:00:02.0 is reserved for Intel integrated graphics\n            gpu->type = gpu->deviceId == ffGPUPciAddr2Id(0, 0, 2, 0) ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;\n        }\n    }\n}\n\n#if FF_HAVE_DRM\n#include \"common/library.h\"\n#include \"common/stringUtils.h\"\n\n#include <xf86drm.h>\n\nstatic const char* detectByDrm(const FFGPUOptions* options, FFlist* gpus)\n{\n    FF_LIBRARY_LOAD_MESSAGE(libdrm, \"libdrm\" FF_LIBRARY_EXTENSION, 2)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmGetDevices)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmFreeDevices)\n\n    drmDevicePtr devices[64];\n    int nDevices = ffdrmGetDevices(devices, ARRAY_SIZE(devices));\n    if (nDevices < 0)\n        return \"drmGetDevices() failed\";\n\n    for (int iDev = 0; iDev < nDevices; ++iDev)\n    {\n        drmDevice* dev = devices[iDev];\n\n        if (!(dev->available_nodes & (1 << DRM_NODE_PRIMARY)))\n            continue;\n\n        const char* path = dev->nodes[DRM_NODE_PRIMARY];\n\n        FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus);\n        ffStrbufInit(&gpu->vendor);\n        ffStrbufInit(&gpu->name);\n        ffStrbufInit(&gpu->driver);\n        ffStrbufInitS(&gpu->platformApi, path);\n        ffStrbufInit(&gpu->memoryType);\n        gpu->index = FF_GPU_INDEX_UNSET;\n        gpu->temperature = FF_GPU_TEMP_UNSET;\n        gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;\n        gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;\n        gpu->type = FF_GPU_TYPE_UNKNOWN;\n        gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;\n        gpu->deviceId = 0;\n        gpu->frequency = FF_GPU_FREQUENCY_UNSET;\n\n        switch (dev->bustype)\n        {\n        case DRM_BUS_PCI:\n            ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(dev->deviceinfo.pci->vendor_id));\n            gpu->deviceId = ffGPUPciAddr2Id(dev->businfo.pci->domain, dev->businfo.pci->bus, dev->businfo.pci->dev, dev->businfo.pci->func);\n            break;\n        case DRM_BUS_HOST1X:\n            ffStrbufSetS(&gpu->name, dev->deviceinfo.host1x->compatible[0]);\n            gpu->type = FF_GPU_TYPE_INTEGRATED;\n            break;\n        case DRM_BUS_PLATFORM:\n            ffStrbufSetS(&gpu->name, dev->deviceinfo.platform->compatible[0]);\n            gpu->type = FF_GPU_TYPE_INTEGRATED;\n            break;\n        case DRM_BUS_USB:\n            ffStrbufSetF(&gpu->name, \"USB Device (%u-%u)\", dev->deviceinfo.usb->vendor, dev->deviceinfo.usb->product);\n            gpu->type = FF_GPU_TYPE_DISCRETE;\n            break;\n        }\n\n        FF_AUTO_CLOSE_FD int fd = open(path, O_RDONLY | O_CLOEXEC);\n        if (fd < 0) continue;\n\n        char driverName[64];\n        driverName[0] = '\\0';\n        struct drm_version ver = {\n            .name = driverName,\n            .name_len = ARRAY_SIZE(driverName),\n        };\n        if (ioctl(fd, DRM_IOCTL_VERSION, &ver) == 0)\n        {\n            driverName[ver.name_len] = '\\0';\n            ffStrbufSetF(&gpu->driver, \"%s %d.%d.%d\", ver.name, ver.version_major, ver.version_minor, ver.version_patchlevel);\n        }\n\n        if (ffStrStartsWith(driverName, \"i915\"))\n            ffDrmDetectI915(gpu, fd);\n        else if (ffStrStartsWith(driverName, \"amdgpu\"))\n            ffDrmDetectAmdgpu(options, gpu, dev->nodes[DRM_NODE_RENDER]);\n        else if (ffStrStartsWith(driverName, \"radeon\"))\n            ffDrmDetectRadeon(options, gpu, dev->nodes[DRM_NODE_RENDER]);\n        else if (ffStrStartsWith(driverName, \"xe\"))\n            ffDrmDetectXe(gpu, fd);\n        else if (ffStrStartsWith(driverName, \"asahi\"))\n            ffDrmDetectAsahi(gpu, fd);\n        else if (ffStrStartsWith(driverName, \"nouveau\"))\n            ffDrmDetectNouveau(gpu, fd);\n        else if (dev->bustype == DRM_BUS_PCI)\n        {\n            ffGPUDetectDriverSpecific(options, gpu, (FFGpuDriverPciBusId) {\n                .domain = (uint32_t) dev->businfo.pci->domain,\n                .bus = dev->businfo.pci->bus,\n                .device = dev->businfo.pci->dev,\n                .func = dev->businfo.pci->func,\n            });\n        }\n\n        if (gpu->name.length == 0)\n        {\n            if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD)\n                ffGPUQueryAmdGpuName(dev->deviceinfo.pci->device_id, dev->deviceinfo.pci->revision_id, gpu);\n            if (gpu->name.length == 0)\n                ffGPUFillVendorAndName(0, dev->deviceinfo.pci->vendor_id, dev->deviceinfo.pci->device_id, gpu);\n        }\n\n        fillGPUTypeGeneric(gpu);\n    }\n\n    ffdrmFreeDevices(devices, nDevices);\n\n    return NULL;\n}\n#endif\n\nstatic const char* detectByPci(const FFGPUOptions* options, FFlist* gpus)\n{\n    FF_AUTO_CLOSE_FD int fd = open(\"/dev/pci\", O_RDONLY | O_CLOEXEC);\n    if (fd < 0)\n        return \"open(\\\"/dev/pci\\\", O_RDONLY | O_CLOEXEC, 0) failed\";\n\n    struct pci_conf confs[128];\n    struct pci_match_conf match = {\n        .pc_class = PCIC_DISPLAY,\n        .flags = PCI_GETCONF_MATCH_CLASS,\n    };\n    struct pci_conf_io pcio = {\n        .pat_buf_len = sizeof(match),\n        .num_patterns = 1,\n        .patterns = &match,\n        .match_buf_len = sizeof(confs),\n        .matches = confs,\n    };\n\n    if (ioctl(fd, PCIOCGETCONF, &pcio) < 0)\n        return \"ioctl(fd, PCIOCGETCONF, &pc) failed\";\n\n    if (pcio.status == PCI_GETCONF_ERROR)\n        return \"ioctl(fd, PCIOCGETCONF, &pc) returned error\";\n\n    for (uint32_t i = 0; i < pcio.num_matches; ++i)\n    {\n        struct pci_conf* pc = &confs[i];\n\n        if (pc->pc_sel.pc_func > 0 && pc->pc_subclass == 0x80 /*PCI_CLASS_DISPLAY_OTHER*/)\n            continue; // Likely an auxiliary display controller (#2034)\n\n        FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus);\n        ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(pc->pc_vendor));\n        ffStrbufInit(&gpu->name);\n        ffStrbufInitS(&gpu->driver, pc->pd_name);\n        ffStrbufInitStatic(&gpu->platformApi, \"/dev/pci\");\n        ffStrbufInit(&gpu->memoryType);\n        gpu->index = FF_GPU_INDEX_UNSET;\n        gpu->temperature = FF_GPU_TEMP_UNSET;\n        gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;\n        gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;\n        gpu->type = FF_GPU_TYPE_UNKNOWN;\n        gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;\n        gpu->deviceId = ffGPUPciAddr2Id(pc->pc_sel.pc_domain, pc->pc_sel.pc_bus, pc->pc_sel.pc_dev, pc->pc_sel.pc_func);\n        gpu->frequency = FF_GPU_FREQUENCY_UNSET;\n\n        ffGPUDetectDriverSpecific(options, gpu, (FFGpuDriverPciBusId) {\n            .domain = (uint32_t) pc->pc_sel.pc_domain,\n            .bus = pc->pc_sel.pc_bus,\n            .device = pc->pc_sel.pc_dev,\n            .func = pc->pc_sel.pc_func,\n        });\n\n        if (gpu->name.length == 0)\n        {\n            if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD)\n                ffGPUQueryAmdGpuName(pc->pc_device, pc->pc_revid, gpu);\n            if (gpu->name.length == 0)\n                ffGPUFillVendorAndName(pc->pc_subclass, pc->pc_vendor, pc->pc_device, gpu);\n        }\n\n        fillGPUTypeGeneric(gpu);\n    }\n\n    return NULL;\n}\n\nconst char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus)\n{\n    #if FF_HAVE_DRM\n    if (options->detectionMethod == FF_GPU_DETECTION_METHOD_AUTO)\n    {\n        detectByDrm(options, gpus);\n        if (gpus->length > 0) return NULL;\n    }\n    #endif\n\n    return detectByPci(options, gpus);\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu_driver_specific.h",
    "content": "#pragma once\n\n#include \"gpu.h\"\n\ntypedef enum __attribute__((__packed__)) FFGpuDriverConditionType\n{\n    FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID = 1 << 0,\n    FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID = 1 << 1,\n    FF_GPU_DRIVER_CONDITION_TYPE_LUID = 1 << 2,\n    FF_GPU_DRIVER_CONDITION_TYPE_FORCE_UNSIGNED = UINT8_MAX,\n} FFGpuDriverConditionType;\n\ntypedef struct FFGpuDriverPciDeviceId\n{\n    uint32_t deviceId;\n    uint32_t vendorId;\n    uint32_t subSystemId;\n    uint32_t revId;\n} FFGpuDriverPciDeviceId;\n\n// Use pciBusId if not NULL; use pciDeviceId otherwise\ntypedef struct FFGpuDriverCondition\n{\n    FFGpuDriverConditionType type;\n    FFGpuDriverPciBusId pciBusId;\n    FFGpuDriverPciDeviceId pciDeviceId;\n    uint64_t luid;\n} FFGpuDriverCondition;\n\n// detect x if not NULL\ntypedef struct FFGpuDriverResult\n{\n    uint32_t* index;\n    double* temp;\n    FFGPUMemory* memory;\n    FFstrbuf* memoryType;\n    FFGPUMemory* sharedMemory;\n    uint32_t* coreCount;\n    double* coreUsage;\n    FFGPUType* type;\n    uint32_t* frequency;\n    FFstrbuf* name;\n} FFGpuDriverResult;\n\nconst char* ffDetectNvidiaGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName);\nconst char* ffDetectIntelGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName);\nconst char* ffDetectAmdGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName);\nconst char* ffDetectMthreadsGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName);\n\nFF_MAYBE_UNUSED static inline bool getDriverSpecificDetectionFn(const char* vendor, __typeof__(&ffDetectNvidiaGpuInfo)* pDetectFn, const char** pDllName)\n{\n    if (vendor == FF_GPU_VENDOR_NAME_NVIDIA)\n    {\n        *pDetectFn = ffDetectNvidiaGpuInfo;\n        #ifdef _WIN32\n        *pDllName = \"nvml.dll\";\n        #else\n        *pDllName = \"libnvidia-ml.so\";\n        #endif\n    }\n    else if (vendor == FF_GPU_VENDOR_NAME_MTHREADS)\n    {\n        *pDetectFn = ffDetectMthreadsGpuInfo;\n        #ifdef _WIN32\n        *pDllName = \"mtml.dll\";\n        #else\n        *pDllName = \"libmtml.so\";\n        #endif\n    }\n    #ifdef _WIN32\n    else if (vendor == FF_GPU_VENDOR_NAME_INTEL)\n    {\n        *pDetectFn = ffDetectIntelGpuInfo;\n        #ifdef _WIN64\n            *pDllName = \"ControlLib.dll\";\n        #else\n            *pDllName = \"ControlLib32.dll\";\n        #endif\n    }\n    else if (vendor == FF_GPU_VENDOR_NAME_AMD)\n    {\n        *pDetectFn = ffDetectAmdGpuInfo;\n        #ifdef _WIN64\n            *pDllName = \"atiadlxx.dll\";\n        #else\n            *pDllName = \"atiadlxy.dll\";\n        #endif\n    }\n    #endif\n    else\n    {\n        *pDetectFn = NULL;\n        *pDllName = NULL;\n        return false;\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu_drm.c",
    "content": "#include \"gpu.h\"\n\n#if FF_HAVE_DRM\n#include <drm.h>\n#include <fcntl.h>\n#include <sys/ioctl.h>\n\n#include \"common/io.h\"\n#include \"common/library.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/stringUtils.h\"\n\n#include \"intel_drm.h\"\n#include \"asahi_drm.h\"\n#include <radeon_drm.h>\n#include <nouveau_drm.h>\n\nconst char* ffDrmDetectRadeon(const FFGPUOptions* options, FFGPUResult* gpu, const char* renderPath)\n{\n    FF_AUTO_CLOSE_FD int fd = open(renderPath, O_RDONLY | O_CLOEXEC);\n    if (fd < 0) return \"Failed to open DRM render device\";\n\n    uint32_t value;\n\n    // https://github.com/torvalds/linux/blob/fb4d33ab452ea254e2c319bac5703d1b56d895bf/drivers/gpu/drm/radeon/radeon_kms.c#L231\n\n    if (ioctl(fd, DRM_IOCTL_RADEON_INFO, &(struct drm_radeon_info) {\n        .request = RADEON_INFO_ACTIVE_CU_COUNT,\n        .value = (uintptr_t) &value,\n    }) >= 0)\n        gpu->coreCount = (int32_t) value;\n\n    if (options->temp)\n    {\n        if (ioctl(fd, DRM_IOCTL_RADEON_INFO, &(struct drm_radeon_info) {\n            .request = RADEON_INFO_CURRENT_GPU_TEMP, // millidegrees C\n            .value = (uintptr_t) &value,\n        }) >= 0 && value != 0) // 0 means unavailable\n            gpu->temperature = (double) value / 1000.0;\n    }\n\n    if (ioctl(fd, DRM_IOCTL_RADEON_INFO, &(struct drm_radeon_info) {\n        .request = RADEON_INFO_MAX_SCLK, // MHz\n        .value = (uintptr_t) &value,\n    }) >= 0)\n        gpu->frequency = (uint32_t) (value / 1000u);\n\n    if (options->driverSpecific)\n    {\n        struct drm_radeon_gem_info gemInfo;\n        if (ioctl(fd, DRM_IOCTL_RADEON_GEM_INFO, &gemInfo) >= 0)\n        {\n            // vram_usage can be bigger than vram_usage, so we use vram_size here\n            gpu->dedicated.total = gemInfo.vram_size;\n            gpu->shared.total = gemInfo.gart_size;\n\n            uint64_t memSize;\n            if (ioctl(fd, DRM_IOCTL_RADEON_INFO, &(struct drm_radeon_info) {\n                .request = RADEON_INFO_VRAM_USAGE, // uint64_t\n                .value = (uintptr_t) &memSize,\n            }) >= 0)\n                gpu->dedicated.used = memSize;\n\n            if (ioctl(fd, DRM_IOCTL_RADEON_INFO, &(struct drm_radeon_info) {\n                .request = RADEON_INFO_GTT_USAGE, // uint64_t\n                .value = (uintptr_t) &memSize,\n            }) >= 0)\n                gpu->shared.used = memSize;\n        }\n    }\n\n    return NULL;\n}\n\n#ifdef FF_HAVE_DRM_AMDGPU\n#include <amdgpu.h>\n#include <amdgpu_drm.h>\n\nconst char* ffDrmDetectAmdgpu(const FFGPUOptions* options, FFGPUResult* gpu, const char* renderPath)\n{\n#if FF_HAVE_DRM_AMDGPU\n    FF_LIBRARY_LOAD_MESSAGE(libdrm, \"libdrm_amdgpu\" FF_LIBRARY_EXTENSION, 1)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, amdgpu_device_initialize)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, amdgpu_get_marketing_name)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, amdgpu_query_gpu_info)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, amdgpu_query_sensor_info)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, amdgpu_query_heap_info)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, amdgpu_device_deinitialize)\n\n    FF_AUTO_CLOSE_FD int fd = open(renderPath, O_RDONLY | O_CLOEXEC);\n    if (fd < 0) return \"Failed to open DRM render device\";\n\n    amdgpu_device_handle handle;\n    uint32_t majorVersion, minorVersion;\n    if (ffamdgpu_device_initialize(fd, &majorVersion, &minorVersion, &handle) < 0)\n        return \"Failed to initialize AMDGPU device\";\n\n    uint32_t value;\n\n    if (options->temp)\n    {\n        if (ffamdgpu_query_sensor_info(handle, AMDGPU_INFO_SENSOR_GPU_TEMP, sizeof(value), &value) >= 0)\n            gpu->temperature = value / 1000.;\n    }\n\n    ffStrbufSetS(&gpu->name, ffamdgpu_get_marketing_name(handle));\n\n    struct amdgpu_gpu_info gpuInfo;\n    if (ffamdgpu_query_gpu_info(handle, &gpuInfo) >= 0)\n    {\n        gpu->coreCount = (int32_t) gpuInfo.cu_active_number;\n        gpu->frequency = (uint32_t) (gpuInfo.max_engine_clk / 1000u);\n        gpu->index = FF_GPU_INDEX_UNSET;\n        gpu->type = gpuInfo.ids_flags & AMDGPU_IDS_FLAGS_FUSION ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;\n#define FF_VRAM_CASE(name, value) case value /* AMDGPU_VRAM_TYPE_ ## name */: ffStrbufSetStatic(&gpu->memoryType, #name); break\n        switch (gpuInfo.vram_type)\n        {\n            FF_VRAM_CASE(UNKNOWN, 0);\n            FF_VRAM_CASE(GDDR1, 1);\n            FF_VRAM_CASE(DDR2, 2);\n            FF_VRAM_CASE(GDDR3, 3);\n            FF_VRAM_CASE(GDDR4, 4);\n            FF_VRAM_CASE(GDDR5, 5);\n            FF_VRAM_CASE(HBM, 6);\n            FF_VRAM_CASE(DDR3, 7);\n            FF_VRAM_CASE(DDR4, 8);\n            FF_VRAM_CASE(GDDR6, 9);\n            FF_VRAM_CASE(DDR5, 10);\n            FF_VRAM_CASE(LPDDR4, 11);\n            FF_VRAM_CASE(LPDDR5, 12);\n        default:\n            ffStrbufAppendF(&gpu->memoryType, \"Unknown (%u)\", gpuInfo.vram_type);\n            break;\n        }\n\n        struct amdgpu_heap_info heapInfo;\n        if (ffamdgpu_query_heap_info(handle, AMDGPU_GEM_DOMAIN_VRAM, 0, &heapInfo) >= 0)\n        {\n            gpu->dedicated.total = heapInfo.heap_size;\n            gpu->dedicated.used = heapInfo.heap_usage;\n        }\n        if (ffamdgpu_query_heap_info(handle, AMDGPU_GEM_DOMAIN_GTT, 0, &heapInfo) >= 0)\n        {\n            gpu->shared.total = heapInfo.heap_size;\n            gpu->shared.used = heapInfo.heap_usage;\n        }\n    }\n\n    if (ffamdgpu_query_sensor_info(handle, AMDGPU_INFO_SENSOR_GPU_LOAD, sizeof(value), &value) >= 0)\n        gpu->coreUsage = value;\n\n    ffamdgpu_device_deinitialize(handle);\n\n    return NULL;\n#else\n    FF_UNUSED(options, gpu, renderPath);\n    return \"Fastfetch is compiled without libdrm support\";\n#endif\n}\n#endif\n\nconst char* ffDrmDetectI915(FFGPUResult* gpu, int fd)\n{\n    {\n        int value;\n        drm_i915_getparam_t getparam = { .param = I915_PARAM_EU_TOTAL, .value = &value };\n        if (ioctl(fd, DRM_IOCTL_I915_GETPARAM, &getparam) >= 0)\n            gpu->coreCount = value;\n    }\n    {\n        struct drm_i915_query_item queryItem = {\n            .query_id = DRM_I915_QUERY_MEMORY_REGIONS,\n        };\n        struct drm_i915_query query = {\n            .items_ptr = (uintptr_t) &queryItem,\n            .num_items = 1,\n        };\n        if (ioctl(fd, DRM_IOCTL_I915_QUERY, &query) >= 0 )\n        {\n            FF_AUTO_FREE uint8_t* buffer = calloc(1, (size_t) queryItem.length);\n            queryItem.data_ptr = (uintptr_t) buffer;\n            if (ioctl(fd, DRM_IOCTL_I915_QUERY, &query) >= 0)\n            {\n                gpu->dedicated.total = gpu->shared.total = gpu->dedicated.used = gpu->shared.used = 0;\n                struct drm_i915_query_memory_regions* regionInfo = (void*) buffer;\n                for (uint32_t i = 0; i < regionInfo->num_regions; i++)\n                {\n                    struct drm_i915_memory_region_info* region = regionInfo->regions + i;\n                    switch (region->region.memory_class)\n                    {\n                    case I915_MEMORY_CLASS_SYSTEM:\n                        gpu->shared.total += region->probed_size;\n                        gpu->shared.used += region->probed_size - region->unallocated_size;\n                        break;\n                    case I915_MEMORY_CLASS_DEVICE:\n                        gpu->dedicated.total += region->probed_size;\n                        gpu->dedicated.used += region->probed_size - region->unallocated_size;\n                        break;\n                    }\n                }\n            }\n        }\n    }\n    return NULL;\n}\n\nstatic inline int popcountBytes(uint8_t* bytes, uint32_t length)\n{\n    int count = 0;\n    while (length >= 8)\n    {\n        count += __builtin_popcountll(*(uint64_t*) bytes);\n        bytes += 8;\n        length -= 8;\n    }\n    if (length >= 4)\n    {\n        count += __builtin_popcountl(*(uint32_t*) bytes);\n        bytes += 4;\n        length -= 4;\n    }\n    if (length >= 2)\n    {\n        count += __builtin_popcountl(*(uint16_t*) bytes);\n        bytes += 2;\n        length -= 2;\n    }\n    if (length)\n    {\n        count += __builtin_popcountl(*(uint8_t*) bytes);\n    }\n    return count;\n}\n\nconst char* ffDrmDetectXe(FFGPUResult* gpu, int fd)\n{\n    bool flag = false;\n    {\n        struct drm_xe_device_query query = {\n            .query = DRM_XE_DEVICE_QUERY_GT_TOPOLOGY,\n        };\n        if (ioctl(fd, DRM_IOCTL_XE_DEVICE_QUERY, &query) >= 0)\n        {\n            FF_AUTO_FREE uint8_t* buffer = malloc(query.size);\n            query.data = (uintptr_t) buffer;\n            if (ioctl(fd, DRM_IOCTL_XE_DEVICE_QUERY, &query) >= 0)\n            {\n                int dssCount = 0, euPerDssCount = 0;\n                for (struct drm_xe_query_topology_mask* topo = (void*) buffer;\n                    (uint8_t*) topo < buffer + query.size;\n                    topo = (void*) (topo->mask + topo->num_bytes)\n                ) {\n                    switch (topo->type)\n                    {\n                        case DRM_XE_TOPO_DSS_COMPUTE:\n                        case DRM_XE_TOPO_DSS_GEOMETRY:\n                            dssCount += popcountBytes(topo->mask, topo->num_bytes);\n                            break;\n                        case DRM_XE_TOPO_EU_PER_DSS:\n                            euPerDssCount += popcountBytes(topo->mask, topo->num_bytes);\n                            break;\n                    }\n                }\n                gpu->coreCount = dssCount * euPerDssCount;\n                flag = true;\n            }\n        }\n    }\n\n    {\n        struct drm_xe_device_query query = {\n            .query = DRM_XE_DEVICE_QUERY_MEM_REGIONS,\n        };\n        if (ioctl(fd, DRM_IOCTL_XE_DEVICE_QUERY, &query) >= 0)\n        {\n            FF_AUTO_FREE uint8_t* buffer = malloc(query.size);\n            query.data = (uintptr_t) buffer;\n            if (ioctl(fd, DRM_IOCTL_XE_DEVICE_QUERY, &query) >= 0)\n            {\n                gpu->dedicated.total = gpu->shared.total = gpu->dedicated.used = gpu->shared.used = 0;\n                struct drm_xe_query_mem_regions* regionInfo = (void*) buffer;\n                for (uint32_t i = 0; i < regionInfo->num_mem_regions; i++)\n                {\n                    struct drm_xe_mem_region* region = regionInfo->mem_regions + i;\n                    switch (region->mem_class)\n                    {\n                        case DRM_XE_MEM_REGION_CLASS_SYSMEM:\n                            gpu->shared.total += region->total_size;\n                            gpu->shared.used += region->used;\n                            break;\n                        case DRM_XE_MEM_REGION_CLASS_VRAM:\n                            gpu->dedicated.total += region->total_size;\n                            gpu->dedicated.used += region->used;\n                            break;\n                    }\n                }\n                flag = true;\n            }\n        }\n    }\n    return flag ? NULL : \"Failed to query Xe GPU information\";\n}\n\nconst char* ffDrmDetectAsahi(FFGPUResult* gpu, int fd)\n{\n    struct drm_asahi_params_global paramsGlobal = {};\n    if (ioctl(fd, DRM_IOCTL_ASAHI_GET_PARAMS, &(struct drm_asahi_get_params) {\n        .param_group = DRM_ASAHI_GET_PARAMS,\n        .pointer = (uintptr_t) &paramsGlobal,\n        .size = sizeof(paramsGlobal),\n    }) >= 0)\n    {\n        // They removed `unstable_uabi_version` from the struct. Hopefully they won't introduce new ABI changes.\n        gpu->coreCount = (int32_t) (paramsGlobal.num_clusters_total * paramsGlobal.num_cores_per_cluster);\n        gpu->frequency = paramsGlobal.max_frequency_khz / 1000;\n        gpu->deviceId = ffGPUGeneral2Id(paramsGlobal.chip_id);\n\n        if (!gpu->name.length)\n        {\n            const char* variant = \" Unknown\";\n            switch (paramsGlobal.gpu_variant) {\n            case 'G':\n                variant = \"\";\n                break;\n            case 'S':\n                variant = \" Pro\";\n                break;\n            case 'C':\n                variant = \" Max\";\n                break;\n            case 'D':\n                variant = \" Ultra\";\n                break;\n            }\n            ffStrbufSetF(&gpu->name, \"Apple M%d%s (G%d%c %02X)\",\n                paramsGlobal.gpu_generation - 12, variant,\n                paramsGlobal.gpu_generation, paramsGlobal.gpu_variant,\n                paramsGlobal.gpu_revision + 0xA0);\n        }\n\n        return NULL;\n    }\n\n    return \"Failed to query Asahi GPU information\";\n}\n\n#ifndef DRM_IOCTL_NOUVEAU_GETPARAM\n#define DRM_IOCTL_NOUVEAU_GETPARAM DRM_IOWR(DRM_COMMAND_BASE + DRM_NOUVEAU_GETPARAM, struct drm_nouveau_getparam)\n#endif\n\nconst char* ffDrmDetectNouveau(FFGPUResult* gpu, int fd)\n{\n    struct drm_nouveau_getparam getparam = { };\n\n    getparam.param = NOUVEAU_GETPARAM_FB_SIZE;\n    if (ioctl(fd, DRM_IOCTL_NOUVEAU_GETPARAM, &getparam) == 0)\n        gpu->dedicated.total = getparam.value;\n\n    getparam.param = NOUVEAU_GETPARAM_AGP_SIZE;\n    if (ioctl(fd, DRM_IOCTL_NOUVEAU_GETPARAM, &getparam) == 0)\n        gpu->shared.total = getparam.value;\n\n    getparam.param = NOUVEAU_GETPARAM_GRAPH_UNITS;\n    if (ioctl(fd, DRM_IOCTL_NOUVEAU_GETPARAM, &getparam) == 0 && getparam.value < INT32_MAX)\n        gpu->coreCount = (int32_t) getparam.value;\n\n    return NULL;\n}\n\n#endif // FF_HAVE_DRM\n\n#include \"gpu_driver_specific.h\"\n\nconst char* ffGPUDetectDriverSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFGpuDriverPciBusId pciBusId)\n{\n    __typeof__(&ffDetectNvidiaGpuInfo) detectFn;\n    const char* soName;\n    if (getDriverSpecificDetectionFn(gpu->vendor.chars, &detectFn, &soName) && (options->temp || options->driverSpecific))\n    {\n        return detectFn(&(FFGpuDriverCondition) {\n            .type = FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID,\n            .pciBusId = pciBusId,\n        }, (FFGpuDriverResult) {\n            .index = &gpu->index,\n            .temp = options->temp ? &gpu->temperature : NULL,\n            .memory = options->driverSpecific ? &gpu->dedicated : NULL,\n            .coreCount = options->driverSpecific ? (uint32_t*) &gpu->coreCount : NULL,\n            .coreUsage = options->driverSpecific ? &gpu->coreUsage : NULL,\n            .type = &gpu->type,\n            .frequency = options->driverSpecific ? &gpu->frequency : NULL,\n            .name = &gpu->name,\n        }, soName);\n    }\n\n    return \"No driver-specific detection function found for the GPU vendor\";\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu_gnu.c",
    "content": "#include \"gpu.h\"\n#include \"common/io.h\"\n\n#include <hurd.h>\n#include <hurd/pci.h>\n#include <hurd/paths.h>\n\nenum {\n    PCI_VENDOR_ID    = 0x00,\n    PCI_DEVICE_ID    = 0x02,\n    PCI_REVISION_ID  = 0x08,\n    PCI_CLASS_PROG   = 0x09,\n    PCI_SUBCLASS     = 0x0a,\n    PCI_CLASS_DEVICE = 0x0b,\n    PCI_CONF_SIZE    = 0x40,\n};\n\nconst char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* gpus)\n{\n    int dDomainFd = open(_SERVERS_BUS \"/pci/0000\", O_RDONLY | O_CLOEXEC);\n    if (dDomainFd < 0) return \"open(_SERVERS_BUS \\\"/pci/0000\\\") failed\";\n\n    FF_AUTO_CLOSE_DIR DIR* dirDomain = fdopendir(dDomainFd);\n    if (dirDomain == NULL) return \"fdopendir(domain) failed\";\n\n    struct dirent* busEntry;\n    while ((busEntry = readdir(dirDomain)) != NULL)\n    {\n        if (busEntry->d_type != DT_DIR || busEntry->d_name[0] == '.')\n            continue;\n\n        char* endptr;\n        uint16_t pciBus = (uint16_t) strtoul(busEntry->d_name, &endptr, 16);\n        if (*endptr != '\\0') continue;\n\n        int dBusFd = openat(dDomainFd, busEntry->d_name, O_RDONLY | O_CLOEXEC);\n        if (dBusFd < 0) continue;\n\n        FF_AUTO_CLOSE_DIR DIR* dirBus = fdopendir(dBusFd);\n        if (dirBus == NULL) continue;\n\n        struct dirent* devEntry;\n        while ((devEntry = readdir(dirBus)) != NULL)\n        {\n            if (devEntry->d_type != DT_DIR || devEntry->d_name[0] == '.')\n                continue;\n\n            uint8_t pciDev = (uint8_t) strtoul(devEntry->d_name, &endptr, 16);\n            if (*endptr != '\\0') continue;\n\n            int dDevFd = openat(dBusFd, devEntry->d_name, O_RDONLY | O_CLOEXEC);\n            if (dDevFd < 0) continue;\n\n            FF_AUTO_CLOSE_DIR DIR* dirDev = fdopendir(dDevFd);\n            if (dirDev == NULL) continue;\n\n            struct dirent* funcEntry;\n            while ((funcEntry = readdir(dirDev)) != NULL)\n            {\n                if (funcEntry->d_type != DT_DIR || funcEntry->d_name[0] == '.')\n                    continue;\n\n                uint8_t pciFunc = (uint8_t) strtoul(funcEntry->d_name, &endptr, 16);\n                if (*endptr != '\\0') continue;\n\n                char subpath[PATH_MAX];\n                snprintf(subpath, ARRAY_SIZE(subpath), \"%s/%s/%s/%s/config\", _SERVERS_BUS \"/pci/0000\", busEntry->d_name, devEntry->d_name, funcEntry->d_name);\n\n                mach_port_t devicePort = file_name_lookup(subpath, 0, 0);\n                if (devicePort == MACH_PORT_NULL)\n                    continue;\n\n                mach_msg_type_number_t nread = 0;\n\n                uint8_t data[PCI_CONF_SIZE];\n                data_t pData = (data_t) data;\n                kern_return_t kr = pci_conf_read(devicePort, 0, &pData, &nread, PCI_CONF_SIZE);\n                mach_port_deallocate(mach_task_self(), devicePort);\n                if (kr != KERN_SUCCESS || nread < PCI_CONF_SIZE) continue;\n\n                if (pData != (data_t) data)\n                {\n                    memcpy(data, pData, PCI_CONF_SIZE);\n                    vm_deallocate(mach_task_self(), (vm_address_t)pData, nread);\n                }\n\n                uint8_t classBase = data[PCI_CLASS_DEVICE];\n                if (classBase != 0x03 /*PCI_BASE_CLASS_DISPLAY*/)\n                    continue;\n\n                uint8_t classSub = data[PCI_SUBCLASS];\n                if (pciFunc > 0 && classSub == 0x80 /*PCI_CLASS_DISPLAY_OTHER*/) // Likely an auxiliary display controller (#2034)\n                    continue;\n\n                uint8_t revision = data[PCI_REVISION_ID];\n                uint16_t vendorId = data[PCI_VENDOR_ID] | (data[PCI_VENDOR_ID + 1] << 8);\n                uint16_t deviceId = data[PCI_DEVICE_ID] | (data[PCI_DEVICE_ID + 1] << 8);\n\n                FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus);\n                ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(vendorId));\n                ffStrbufInit(&gpu->name);\n                ffStrbufInit(&gpu->driver);\n                ffStrbufInitStatic(&gpu->platformApi, \"/servers/bus/pci\");\n                ffStrbufInit(&gpu->memoryType);\n                gpu->temperature = FF_GPU_TEMP_UNSET;\n                gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;\n                gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;\n                gpu->type = FF_GPU_TYPE_UNKNOWN;\n                gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;\n                gpu->deviceId = ffGPUPciAddr2Id(0, pciBus, pciDev, pciFunc);\n                gpu->frequency = FF_GPU_FREQUENCY_UNSET;\n\n                if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD)\n                    ffGPUQueryAmdGpuName(deviceId, revision, gpu);\n\n                if (gpu->name.length == 0)\n                    ffGPUFillVendorAndName(classSub, vendorId, deviceId, gpu);\n            }\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu_haiku.c",
    "content": "#include \"gpu.h\"\n#include \"common/io.h\"\n\n#include <private/drivers/poke.h>\n\nconst char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* gpus)\n{\n    FF_AUTO_CLOSE_FD int pokefd = open(POKE_DEVICE_FULLNAME, O_RDWR | O_CLOEXEC);\n    if (pokefd < 0) return \"open(POKE_DEVICE_FULLNAME) failed\";\n\n    pci_info dev;\n    pci_info_args cmd = {\n        .signature = POKE_SIGNATURE,\n        .info = &dev,\n    };\n\n    for (cmd.index = 0; ioctl(pokefd, POKE_GET_NTH_PCI_INFO, &cmd, sizeof(cmd)) == B_OK && cmd.status == B_OK; ++cmd.index)\n    {\n        if (dev.class_base != 0x03 /*PCI_BASE_CLASS_DISPLAY*/)\n            continue;\n\n        if (dev.function > 0 && dev.class_sub == 0x80 /*PCI_CLASS_DISPLAY_OTHER*/)\n            continue; // Likely an auxiliary display controller (#2034)\n\n        FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus);\n        ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(dev.vendor_id));\n        ffStrbufInit(&gpu->name);\n        ffStrbufInit(&gpu->driver);\n        ffStrbufInitStatic(&gpu->platformApi, POKE_DEVICE_FULLNAME);\n        ffStrbufInit(&gpu->memoryType);\n        gpu->temperature = FF_GPU_TEMP_UNSET;\n        gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;\n        gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;\n        gpu->type = FF_GPU_TYPE_UNKNOWN;\n        gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;\n        gpu->deviceId = ffGPUPciAddr2Id(0, dev.bus, dev.device, dev.function);\n        gpu->frequency = FF_GPU_FREQUENCY_UNSET;\n\n        if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD)\n            ffGPUQueryAmdGpuName(dev.device_id, dev.revision, gpu);\n\n        if (gpu->name.length == 0)\n            ffGPUFillVendorAndName(dev.class_sub, dev.vendor_id, dev.device_id, gpu);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu_intel.c",
    "content": "#include \"gpu_driver_specific.h\"\n\n#include \"common/library.h\"\n#include \"common/mallocHelper.h\"\n#include \"igcl.h\"\n\nstruct FFIgclData {\n    FF_LIBRARY_SYMBOL(ctlClose)\n\n    FF_LIBRARY_SYMBOL(ctlEnumerateDevices)\n    FF_LIBRARY_SYMBOL(ctlGetDeviceProperties)\n    FF_LIBRARY_SYMBOL(ctlEnumTemperatureSensors)\n    FF_LIBRARY_SYMBOL(ctlTemperatureGetProperties)\n    FF_LIBRARY_SYMBOL(ctlEnumMemoryModules)\n    FF_LIBRARY_SYMBOL(ctlMemoryGetProperties)\n    FF_LIBRARY_SYMBOL(ctlMemoryGetState)\n    FF_LIBRARY_SYMBOL(ctlEnumFrequencyDomains)\n    FF_LIBRARY_SYMBOL(ctlFrequencyGetProperties)\n\n    bool inited;\n    ctl_api_handle_t apiHandle;\n} igclData;\n\nstatic void shutdownIgcl()\n{\n    if (igclData.apiHandle)\n    {\n        igclData.ffctlClose(igclData.apiHandle);\n        igclData.apiHandle = NULL;\n    }\n}\n\nconst char* ffDetectIntelGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName)\n{\n    if (!igclData.inited)\n    {\n        igclData.inited = true;\n        FF_LIBRARY_LOAD(libigcl, \"dlopen igcl (ControlLib) failed\", soName , 1);\n        FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libigcl, ctlInit)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlClose)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlEnumerateDevices)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlGetDeviceProperties)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlEnumTemperatureSensors)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlTemperatureGetProperties)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlEnumMemoryModules)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlMemoryGetProperties)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlMemoryGetState)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlEnumFrequencyDomains)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlFrequencyGetProperties)\n\n        if (ffctlInit(&(ctl_init_args_t) {\n            .AppVersion = CTL_IMPL_VERSION,\n            .flags = CTL_INIT_FLAG_USE_LEVEL_ZERO,\n            .Size = sizeof(ctl_init_args_t),\n            .Version = 0,\n        }, &igclData.apiHandle) != CTL_RESULT_SUCCESS)\n            return \"loading igcl library failed\";\n        atexit(shutdownIgcl);\n        libigcl = NULL; // don't close igcl\n    }\n\n    if (!igclData.apiHandle)\n        return \"loading igcl library failed\";\n\n    uint32_t deviceCount = 0;\n    if (igclData.ffctlEnumerateDevices(igclData.apiHandle, &deviceCount, NULL))\n        return \"ctlEnumerateDevices(NULL) failed\";\n    if (deviceCount == 0)\n        return \"No Intel graphics adapter found\";\n\n    FF_AUTO_FREE ctl_device_adapter_handle_t* devices = malloc(deviceCount * sizeof(*devices));\n    if (igclData.ffctlEnumerateDevices(igclData.apiHandle, &deviceCount, devices))\n        return \"ctlEnumerateDevices(devices) failed\";\n\n    ctl_device_adapter_handle_t device = NULL;\n\n    uint64_t /* LUID */ deviceId = 0;\n    ctl_device_adapter_properties_t properties = {\n        .Size = sizeof(properties),\n        .pDeviceID = &deviceId,\n        .device_id_size = sizeof(deviceId),\n        .Version = 2,\n    };\n    for (uint32_t iDev = 0; iDev < deviceCount; iDev++)\n    {\n        if (igclData.ffctlGetDeviceProperties(devices[iDev], &properties) != CTL_RESULT_SUCCESS)\n            continue;\n\n        if (properties.device_type != CTL_DEVICE_TYPE_GRAPHICS)\n            continue;\n\n        if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID)\n        {\n            if (cond->pciBusId.bus == properties.adapter_bdf.bus &&\n                cond->pciBusId.device == properties.adapter_bdf.device &&\n                cond->pciBusId.func == properties.adapter_bdf.function)\n            {\n                device = devices[iDev];\n                break;\n            }\n        }\n        else if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_LUID)\n        {\n            if (cond->luid == deviceId)\n            {\n                device = devices[iDev];\n                break;\n            }\n        }\n        else if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID)\n        {\n            if (\n                cond->pciDeviceId.deviceId == properties.pci_device_id &&\n                cond->pciDeviceId.vendorId == properties.pci_vendor_id &&\n                cond->pciDeviceId.subSystemId == (uint32_t) ((properties.pci_subsys_id << 16u) | properties.pci_subsys_vendor_id) &&\n                cond->pciDeviceId.revId == properties.rev_id)\n            {\n                device = devices[iDev];\n                break;\n            }\n        }\n    }\n\n    if (!device)\n        return \"Device not found\";\n\n    if (result.coreCount)\n        *result.coreCount = properties.num_slices * properties.num_sub_slices_per_slice * properties.num_eus_per_sub_slice;\n\n    if (result.memory)\n    {\n        ctl_mem_handle_t memoryModules[16];\n        uint32_t memoryCount = ARRAY_SIZE(memoryModules);\n        if (igclData.ffctlEnumMemoryModules(device, &memoryCount, memoryModules) == CTL_RESULT_SUCCESS && memoryCount > 0)\n        {\n            result.memory->used = 0;\n            result.memory->total = 0;\n            for (uint32_t iMem = 0; iMem < memoryCount; iMem++)\n            {\n                ctl_mem_properties_t memoryProperties = {\n                    .Size = sizeof(memoryProperties),\n                    .Version = 0,\n                };\n                if (igclData.ffctlMemoryGetProperties(memoryModules[iMem], &memoryProperties) == CTL_RESULT_SUCCESS)\n                {\n                    if (memoryProperties.location == CTL_MEM_LOC_DEVICE && result.memoryType)\n                    {\n                        switch (memoryProperties.type)\n                        {\n                            #define FF_ICTL_MEM_TYPE_CASE(type) case CTL_MEM_TYPE_##type: ffStrbufSetStatic(result.memoryType, #type); break\n                            FF_ICTL_MEM_TYPE_CASE(HBM);\n                            FF_ICTL_MEM_TYPE_CASE(DDR);\n                            FF_ICTL_MEM_TYPE_CASE(DDR3);\n                            FF_ICTL_MEM_TYPE_CASE(DDR4);\n                            FF_ICTL_MEM_TYPE_CASE(DDR5);\n                            FF_ICTL_MEM_TYPE_CASE(LPDDR);\n                            FF_ICTL_MEM_TYPE_CASE(LPDDR3);\n                            FF_ICTL_MEM_TYPE_CASE(LPDDR4);\n                            FF_ICTL_MEM_TYPE_CASE(LPDDR5);\n                            FF_ICTL_MEM_TYPE_CASE(GDDR4);\n                            FF_ICTL_MEM_TYPE_CASE(GDDR5);\n                            FF_ICTL_MEM_TYPE_CASE(GDDR5X);\n                            FF_ICTL_MEM_TYPE_CASE(GDDR6);\n                            FF_ICTL_MEM_TYPE_CASE(GDDR6X);\n                            FF_ICTL_MEM_TYPE_CASE(GDDR7);\n                            #undef FF_ICTL_MEM_TYPE_CASE\n                            default:\n                                ffStrbufSetF(result.memoryType, \"Unknown (%u)\", memoryProperties.type);\n                                break;\n                        }\n                    }\n\n                    ctl_mem_state_t memoryState = {\n                        .Size = sizeof(ctl_mem_state_t),\n                        .Version = 0,\n                    };\n                    if (igclData.ffctlMemoryGetState(memoryModules[iMem], &memoryState) == CTL_RESULT_SUCCESS)\n                    {\n                        if (memoryProperties.location == CTL_MEM_LOC_DEVICE)\n                        {\n                            result.memory->total += memoryState.size;\n                            result.memory->used += memoryState.size - memoryState.free;\n                        }\n                        else if (result.sharedMemory && memoryProperties.location == CTL_MEM_LOC_SYSTEM)\n                        {\n                            result.sharedMemory->total += memoryState.size;\n                            result.sharedMemory->used += memoryState.size - memoryState.free;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    if (result.type)\n    {\n        *result.type = properties.graphics_adapter_properties & CTL_ADAPTER_PROPERTIES_FLAG_INTEGRATED\n            ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;\n    }\n\n    if (result.temp)\n    {\n        ctl_temp_handle_t sensors[16];\n        uint32_t sensorCount = ARRAY_SIZE(sensors);\n        if (igclData.ffctlEnumTemperatureSensors(device, &sensorCount, sensors) == CTL_RESULT_SUCCESS && sensorCount > 0)\n        {\n            for (uint32_t iSensor = 0; iSensor < sensorCount; iSensor++)\n            {\n                ctl_temp_properties_t props = { .Size = sizeof(props) };\n                // The official sample code does not set Version\n                // https://github.com/intel/drivers.gpu.control-library/blob/1bbacbf3814f2fd0d2b930cdf42fad83f3628db9/Samples/Telemetry_Samples/Sample_TelemetryAPP.cpp#L256\n                if (igclData.ffctlTemperatureGetProperties(sensors[iSensor], &props) == CTL_RESULT_SUCCESS)\n                {\n                    if (props.type == CTL_TEMP_SENSORS_GPU)\n                    {\n                        *result.temp = props.maxTemperature;\n                        break;\n                    }\n                }\n            }\n        }\n    }\n\n    if (result.frequency)\n    {\n        ctl_freq_handle_t domains[16];\n        uint32_t domainCount = ARRAY_SIZE(domains);\n        if (igclData.ffctlEnumFrequencyDomains(device, &domainCount, domains) == CTL_RESULT_SUCCESS && domainCount > 0)\n        {\n            double maxValue = 0;\n            ctl_freq_properties_t props = { .Size = sizeof(props), .Version = 0 };\n            for (uint32_t iDomain = 0; iDomain < domainCount; iDomain++)\n            {\n                if (igclData.ffctlFrequencyGetProperties(domains[iDomain], &props) == CTL_RESULT_SUCCESS)\n                {\n                    if (props.type == CTL_FREQ_DOMAIN_GPU && props.max > maxValue)\n                        maxValue = props.max;\n                }\n            }\n            *result.frequency = (uint32_t) (maxValue + 0.5);\n        }\n    }\n\n    if (result.name)\n        ffStrbufSetS(result.name, properties.name);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu_linux.c",
    "content": "#include \"detection/gpu/gpu.h\"\n#include \"detection/vulkan/vulkan.h\"\n#include \"detection/cpu/cpu.h\"\n#include \"detection/gpu/gpu_driver_specific.h\"\n#include \"common/io.h\"\n#include \"common/library.h\"\n#include \"common/FFstrbuf.h\"\n#include \"common/stringUtils.h\"\n#include \"common/mallocHelper.h\"\n#include \"modules/gpu/option.h\"\n\n#include <inttypes.h>\n#include <stdint.h>\n\n#ifdef FF_HAVE_DRM_AMDGPU\n    #include <amdgpu.h>\n    #include <amdgpu_drm.h>\n    #include <fcntl.h>\n#endif\n\n#ifdef FF_HAVE_DRM\n    #include \"intel_drm.h\"\n    #include <fcntl.h>\n    #include <sys/ioctl.h>\n#endif\n\n#if defined(FF_HAVE_DRM) && defined(__aarch64__)\n    // https://github.com/alyssarosenzweig/linux/blob/agx-uapi-v7/include/uapi/drm/asahi_drm.h\n    // Found in kernel-headers-6.14.4-400.asahi.fc42.aarch64\n    #if __has_include(<drm/asahi_drm.h>)\n        #include <drm/asahi_drm.h>\n    #else\n        #include \"asahi_drm.h\"\n    #endif\n    #define FF_HAVE_DRM_ASAHI 1\n#endif\n\nstatic bool pciDetectDriver(FFstrbuf* result, FFstrbuf* pciDir, FFstrbuf* buffer, FF_MAYBE_UNUSED const char* drmKey)\n{\n    uint32_t pciDirLength = pciDir->length;\n    ffStrbufAppendS(pciDir, \"/driver\");\n    char pathBuf[PATH_MAX];\n    ssize_t resultLength = readlink(pciDir->chars, pathBuf, ARRAY_SIZE(pathBuf));\n    if(resultLength <= 0) return false;\n\n    const char* slash = memrchr(pathBuf, '/', (size_t) resultLength);\n    if (slash)\n    {\n        slash++;\n        ffStrbufSetNS(result, (uint32_t) (resultLength - (slash - pathBuf)), slash);\n    }\n\n    if (ffStrbufEqualS(result, \"nvidia\"))\n    {\n        if (ffReadFileBuffer(\"/proc/driver/nvidia/version\", buffer))\n        {\n            if (ffStrbufContainS(buffer, \" Open \"))\n                ffStrbufAppendS(result, \" (open source)\");\n            else\n                ffStrbufAppendS(result, \" (proprietary)\");\n        }\n    }\n\n    if (instance.config.general.detectVersion)\n    {\n        ffStrbufAppendS(pciDir, \"/module/version\");\n        if (ffReadFileBuffer(pciDir->chars, buffer))\n        {\n            ffStrbufTrimRightSpace(buffer);\n            ffStrbufAppendC(result, ' ');\n            ffStrbufAppend(result, buffer);\n        }\n        else if (ffStrbufEqualS(result, \"zx\"))\n        {\n            ffStrbufSubstrBefore(pciDir, pciDirLength);\n            ffStrbufAppendS(pciDir, \"/zx_info/driver_version\");\n            if (ffReadFileBuffer(pciDir->chars, buffer))\n            {\n                ffStrbufTrimRightSpace(buffer);\n                ffStrbufAppendC(result, ' ');\n                ffStrbufAppend(result, buffer);\n            }\n        }\n    }\n\n    return true;\n}\n\nFF_MAYBE_UNUSED static const char* drmFindRenderFromCard(const char* drmCardKey, FFstrbuf* result)\n{\n    char path[PATH_MAX];\n    sprintf(path, \"/sys/class/drm/%s/device/drm\", drmCardKey);\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(path);\n    if (!dirp) return \"Failed to open `/sys/class/drm/{drmCardKey}/device/drm`\";\n\n    struct dirent* entry;\n    while ((entry = readdir(dirp)) != NULL)\n    {\n        if (ffStrStartsWith(entry->d_name, \"render\"))\n        {\n            ffStrbufSetS(result, \"/dev/dri/\");\n            ffStrbufAppendS(result, entry->d_name);\n            return NULL;\n        }\n    }\n    return \"Failed to find render device\";\n}\n\nstatic const char* drmDetectAmdSpecific(const FFGPUOptions* options, FFGPUResult* gpu, const char* drmKey, FFstrbuf* buffer)\n{\n    #if FF_HAVE_DRM\n    const char* error = drmFindRenderFromCard(drmKey, buffer);\n    if (error) return error;\n    if (ffStrbufEqualS(&gpu->driver, \"radeon\"))\n        return ffDrmDetectRadeon(options, gpu, buffer->chars);\n    else\n    {\n        #if FF_HAVE_DRM_AMDGPU\n        return ffDrmDetectAmdgpu(options, gpu, buffer->chars);\n        #else\n        FF_UNUSED(options, gpu, drmKey, buffer);\n        return \"Fastfetch is not compiled with libdrm_amdgpu support\";\n        #endif\n    }\n    #else\n    FF_UNUSED(options, gpu, drmKey, buffer);\n    return \"Fastfetch is not compiled with drm support\";\n    #endif\n}\n\nstatic void pciDetectAmdSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFstrbuf* pciDir, FFstrbuf* buffer)\n{\n    // https://www.kernel.org/doc/html/v5.10/gpu/amdgpu.html#mem-info-vis-vram-total\n    const uint32_t pciDirLen = pciDir->length;\n\n    ffStrbufAppendS(pciDir, \"/hwmon/\");\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pciDir->chars);\n    if (!dirp) return;\n\n    struct dirent* entry;\n    while ((entry = readdir(dirp)) != NULL)\n    {\n        if (entry->d_name[0] == '.') continue;\n        break;\n    }\n    if (!entry) return;\n    ffStrbufAppendS(pciDir, entry->d_name);\n    ffStrbufAppendC(pciDir, '/');\n\n    const uint32_t hwmonLen = pciDir->length;\n    uint64_t value = 0;\n    if (options->temp)\n    {\n        ffStrbufAppendS(pciDir, \"temp1_input\"); // The on die GPU temperature in millidegrees Celsius\n        if (ffReadFileBuffer(pciDir->chars, buffer) && (value = ffStrbufToUInt(buffer, 0)))\n            gpu->temperature = (double) value / 1000;\n    }\n\n    if (ffStrbufEqualS(&gpu->driver, \"amdgpu\")) // Ancient radeon drivers don't have these files\n    {\n        ffStrbufSubstrBefore(pciDir, hwmonLen);\n        ffStrbufAppendS(pciDir, \"in1_input\"); // Northbridge voltage in millivolts (APUs only)\n        if (ffPathExists(pciDir->chars, FF_PATHTYPE_ANY))\n            gpu->type = FF_GPU_TYPE_INTEGRATED;\n        else\n            gpu->type = FF_GPU_TYPE_DISCRETE;\n\n        if (options->driverSpecific)\n        {\n            ffStrbufSubstrBefore(pciDir, pciDirLen);\n            ffStrbufAppendS(pciDir, \"/mem_info_vis_vram_total\");\n            if (ffReadFileBuffer(pciDir->chars, buffer) && (value = ffStrbufToUInt(buffer, 0)))\n            {\n                if (gpu->type == FF_GPU_TYPE_DISCRETE)\n                    gpu->dedicated.total = value;\n                else\n                    gpu->shared.total = value;\n\n                ffStrbufSubstrBefore(pciDir, pciDir->length - (uint32_t) strlen(\"/mem_info_vis_vram_total\"));\n                ffStrbufAppendS(pciDir, \"/mem_info_vis_vram_used\");\n                if (ffReadFileBuffer(pciDir->chars, buffer) && (value = ffStrbufToUInt(buffer, 0)))\n                {\n                    if (gpu->type == FF_GPU_TYPE_DISCRETE)\n                        gpu->dedicated.used = value;\n                    else\n                        gpu->shared.used = value;\n                }\n            }\n\n            ffStrbufSubstrBefore(pciDir, pciDirLen);\n            ffStrbufAppendS(pciDir, \"/gpu_busy_percent\");\n            if (ffReadFileBuffer(pciDir->chars, buffer) && (value = ffStrbufToUInt(buffer, 0)))\n                gpu->coreUsage = (double) value;\n        }\n    }\n}\n\nstatic void pciDetectIntelSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFstrbuf* pciDir, FFstrbuf* buffer, const char* drmKey)\n{\n    // Works for Intel GPUs\n    // https://patchwork.kernel.org/project/intel-gfx/patch/1422039866-11572-3-git-send-email-ville.syrjala@linux.intel.com/\n\n    // 0000:00:02.0 is reserved for Intel integrated graphics\n    gpu->type = gpu->deviceId == ffGPUPciAddr2Id(0, 0, 2, 0) ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;\n\n    if (!drmKey) return;\n\n    const uint32_t pciDirLen = pciDir->length;\n\n    bool isXE = ffStrbufEqualS(&gpu->driver, \"xe\");\n    if (isXE)\n        ffStrbufAppendS(pciDir, \"/tile0/gt0/freq0/max_freq\");\n    else\n        ffStrbufAppendF(pciDir, \"/drm/%s/gt_max_freq_mhz\", drmKey);\n    if (ffReadFileBuffer(pciDir->chars, buffer))\n        gpu->frequency = (uint32_t) ffStrbufToUInt(buffer, 0);\n    ffStrbufSubstrBefore(pciDir, pciDirLen);\n\n    if (options->temp)\n    {\n        ffStrbufAppendS(pciDir, \"/hwmon/\");\n        FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pciDir->chars);\n        if (dirp)\n        {\n            struct dirent* entry;\n            while ((entry = readdir(dirp)) != NULL)\n            {\n                if (entry->d_name[0] == '.') continue;\n\n                ffStrbufSubstrBefore(pciDir, pciDirLen + strlen(\"/hwmon/\"));\n                ffStrbufAppendS(pciDir, entry->d_name);\n                // https://github.com/Syllo/nvtop/blob/73291884d926445e499d6b9b71cb7a9bdbc7c393/src/extract_gpuinfo_intel.c#L279-L281\n                ffStrbufAppendS(pciDir, isXE ? \"/temp2_input\" : \"/temp1_input\");\n\n                if (ffReadFileBuffer(pciDir->chars, buffer))\n                {\n                    uint64_t value = ffStrbufToUInt(buffer, 0);\n                    if (value > 0)\n                    {\n                        gpu->temperature = (double) value / 1000;\n                        break;\n                    }\n                }\n            }\n        }\n        ffStrbufSubstrBefore(pciDir, pciDirLen);\n    }\n}\n\nstatic const char* drmDetectIntelSpecific(FFGPUResult* gpu, const char* drmKey, FFstrbuf* buffer)\n{\n    #if FF_HAVE_DRM\n    ffStrbufSetS(buffer, \"/dev/dri/\");\n    ffStrbufAppendS(buffer, drmKey);\n    FF_AUTO_CLOSE_FD int fd = open(buffer->chars, O_RDONLY | O_CLOEXEC);\n    if (fd < 0) return \"Failed to open drm device\";\n\n    if (ffStrbufEqualS(&gpu->driver, \"xe\"))\n        return ffDrmDetectXe(gpu, fd);\n    else if (ffStrbufEqualS(&gpu->driver, \"i915\"))\n        return ffDrmDetectI915(gpu, fd);\n    return \"Unknown Intel GPU driver\";\n    #else\n    FF_UNUSED(gpu, drmKey, buffer);\n    return \"Fastfetch is not compiled with drm support\";\n    #endif\n}\n\nstatic const char* pciDetectTempGeneral(const FFGPUOptions* options, FFGPUResult* gpu, FFstrbuf* pciDir, FFstrbuf* buffer)\n{\n    if (options->temp)\n    {\n        const uint32_t pciDirLen = pciDir->length;\n        ffStrbufAppendS(pciDir, \"/hwmon/\");\n        FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pciDir->chars);\n        if (dirp)\n        {\n            struct dirent* entry;\n            while ((entry = readdir(dirp)))\n            {\n                if (entry->d_name[0] == '.') continue;\n                ffStrbufAppendS(pciDir, entry->d_name);\n                ffStrbufAppendS(pciDir, \"/temp1_input\");\n                if (ffReadFileBuffer(pciDir->chars, buffer))\n                {\n                    uint64_t value = ffStrbufToUInt(buffer, 0);\n                    if (value > 0) gpu->temperature = (double) value / 1000.0;\n                }\n                break;\n            }\n        }\n        ffStrbufSubstrBefore(pciDir, pciDirLen);\n    }\n    return NULL;\n}\n\nstatic const char* drmDetectNouveauSpecific(FFGPUResult* gpu, const char* drmKey, FFstrbuf* buffer)\n{\n    #if FF_HAVE_DRM\n    ffStrbufSetS(buffer, \"/dev/dri/\");\n    ffStrbufAppendS(buffer, drmKey);\n    FF_AUTO_CLOSE_FD int fd = open(buffer->chars, O_RDONLY | O_CLOEXEC);\n    if (fd < 0) return \"Failed to open drm device\";\n\n    return ffDrmDetectNouveau(gpu, fd);\n    #else\n    FF_UNUSED(gpu, drmKey, buffer);\n    return \"Fastfetch is not compiled with drm support\";\n    #endif\n}\n\nstatic const char* pciDetectZxSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFstrbuf* pciDir, FFstrbuf* buffer)\n{\n    gpu->type = FF_GPU_TYPE_INTEGRATED;\n\n    const uint32_t pciDirLen = pciDir->length;\n    ffStrbufAppendS(pciDir, \"/zx_info/eclk\");\n    if (ffReadFileBuffer(pciDir->chars, buffer))\n        gpu->frequency = (uint32_t) ffStrbufToUInt(buffer, FF_GPU_FREQUENCY_UNSET);\n    ffStrbufSubstrBefore(pciDir, pciDirLen);\n\n    if (options->driverSpecific)\n    {\n        ffStrbufAppendS(pciDir, \"/zx_info/engine_3d_usage\");\n        if (ffReadFileBuffer(pciDir->chars, buffer))\n            gpu->coreUsage = ffStrbufToDouble(buffer, FF_GPU_CORE_USAGE_UNSET);\n        ffStrbufSubstrBefore(pciDir, pciDirLen);\n\n        ffStrbufAppendS(pciDir, \"/zx_info/fb_size\");\n        if (ffReadFileBuffer(pciDir->chars, buffer))\n            gpu->shared.total = ffStrbufToUInt(buffer, FF_GPU_VMEM_SIZE_UNSET);\n        ffStrbufSubstrBefore(pciDir, pciDirLen);\n\n        if (gpu->shared.total != FF_GPU_VMEM_SIZE_UNSET)\n        {\n            gpu->shared.total *= 1024 * 1024;\n\n            ffStrbufAppendS(pciDir, \"/zx_info/free_fb_mem\");\n            if (ffReadFileBuffer(pciDir->chars, buffer))\n                gpu->shared.used = ffStrbufToUInt(buffer, FF_GPU_VMEM_SIZE_UNSET);\n            ffStrbufSubstrBefore(pciDir, pciDirLen);\n\n            if (gpu->shared.used != FF_GPU_VMEM_SIZE_UNSET)\n            {\n                gpu->shared.used *= 1024 * 1024;\n                gpu->shared.used = gpu->shared.total - gpu->shared.used;\n            }\n        }\n    }\n\n    return NULL;\n}\n\nstatic const char* detectPci(const FFGPUOptions* options, FFlist* gpus, FFstrbuf* buffer, FFstrbuf* deviceDir, const char* drmKey)\n{\n    const uint32_t drmDirPathLength = deviceDir->length;\n    uint32_t vendorId, deviceId, subVendorId, subDeviceId;\n    uint8_t classId, subclassId;\n    if (sscanf(buffer->chars + strlen(\"pci:\"), \"v%8\" SCNx32 \"d%8\" SCNx32 \"sv%8\" SCNx32 \"sd%8\" SCNx32 \"bc%2\" SCNx8 \"sc%2\" SCNx8, &vendorId, &deviceId, &subVendorId, &subDeviceId, &classId, &subclassId) != 6)\n        return \"Failed to parse pci modalias\";\n\n    if (classId != 0x03 /*PCI_BASE_CLASS_DISPLAY*/)\n        return \"Not a GPU device\";\n\n    char pciPath[PATH_MAX];\n    const char* pPciPath = NULL;\n    if (drmKey)\n    {\n        ssize_t pathLength = readlink(deviceDir->chars, pciPath, ARRAY_SIZE(pciPath) - 1);\n        if (pathLength <= 0)\n            return \"Unable to get PCI device path\";\n        pciPath[pathLength] = '\\0';\n        pPciPath = strrchr(pciPath, '/');\n        if (__builtin_expect(pPciPath != NULL, true))\n            pPciPath++;\n        else\n            pPciPath = pciPath;\n    }\n    else\n    {\n        pPciPath = memrchr(deviceDir->chars, '/', deviceDir->length);\n        assert(pPciPath);\n        pPciPath++;\n    }\n\n    uint32_t pciDomain, pciBus, pciDevice, pciFunc;\n    if (sscanf(pPciPath, \"%\" SCNx32 \":%\" SCNx32 \":%\" SCNx32 \".%\" SCNx32, &pciDomain, &pciBus, &pciDevice, &pciFunc) != 4)\n        return \"Invalid PCI device path\";\n\n    if (pciFunc > 0 && subclassId == 0x80 /*PCI_CLASS_DISPLAY_OTHER*/)\n        return \"Likely an auxiliary display controller\"; // #2034\n\n    FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus);\n    ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString((uint16_t) vendorId));\n    ffStrbufInit(&gpu->name);\n    ffStrbufInit(&gpu->driver);\n    ffStrbufInit(&gpu->platformApi);\n    ffStrbufInit(&gpu->memoryType);\n    gpu->index = FF_GPU_INDEX_UNSET;\n    gpu->temperature = FF_GPU_TEMP_UNSET;\n    gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;\n    gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;\n    gpu->type = FF_GPU_TYPE_UNKNOWN;\n    gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;\n    gpu->deviceId = ffGPUPciAddr2Id(pciDomain, pciBus, pciDevice, pciFunc);\n    gpu->frequency = FF_GPU_FREQUENCY_UNSET;\n\n    char drmKeyBuffer[8];\n    if (!drmKey)\n    {\n        ffStrbufAppendS(deviceDir, \"/drm\");\n        FF_AUTO_CLOSE_DIR DIR* dirp = opendir(deviceDir->chars);\n        if (dirp)\n        {\n            struct dirent* entry;\n            while ((entry = readdir(dirp)) != NULL)\n            {\n                if (ffStrStartsWith(entry->d_name, \"card\"))\n                {\n                    ffStrCopy(drmKeyBuffer, entry->d_name, ARRAY_SIZE(drmKeyBuffer));\n                    drmKey = drmKeyBuffer;\n                    break;\n                }\n            }\n        }\n        ffStrbufSubstrBefore(deviceDir, drmDirPathLength);\n    }\n\n    if (drmKey) ffStrbufSetF(&gpu->platformApi, \"DRM (%s)\", drmKey);\n\n    pciDetectDriver(&gpu->driver, deviceDir, buffer, drmKey);\n    ffStrbufSubstrBefore(deviceDir, drmDirPathLength);\n\n    if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD)\n    {\n        bool ok = false;\n        if (drmKey && options->driverSpecific)\n            ok = drmDetectAmdSpecific(options, gpu, drmKey, buffer) == NULL;\n\n        if (!ok)\n        {\n            pciDetectAmdSpecific(options, gpu, deviceDir, buffer);\n            ffStrbufSubstrBefore(deviceDir, drmDirPathLength);\n\n            ffStrbufAppendS(deviceDir, \"/revision\");\n            if (ffReadFileBuffer(deviceDir->chars, buffer))\n            {\n                char* pend;\n                uint64_t revision = strtoul(buffer->chars, &pend, 16);\n                if (pend != buffer->chars)\n                    ffGPUQueryAmdGpuName((uint16_t) deviceId, (uint8_t) revision, gpu);\n            }\n            ffStrbufSubstrBefore(deviceDir, drmDirPathLength);\n        }\n    }\n    else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_INTEL)\n    {\n        pciDetectIntelSpecific(options, gpu, deviceDir, buffer, drmKey);\n        ffStrbufSubstrBefore(deviceDir, drmDirPathLength);\n        if (options->driverSpecific && drmKey)\n            drmDetectIntelSpecific(gpu, drmKey, buffer);\n    }\n    else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_NVIDIA && ffStrbufEqualS(&gpu->driver, \"nouveau\"))\n    {\n        pciDetectTempGeneral(options, gpu, deviceDir, buffer);\n        if (options->driverSpecific && drmKey)\n            drmDetectNouveauSpecific(gpu, drmKey, buffer);\n    }\n    else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_ZHAOXIN && ffStrbufStartsWithS(&gpu->driver, \"zx\"))\n    {\n        pciDetectTempGeneral(options, gpu, deviceDir, buffer);\n        pciDetectZxSpecific(options, gpu, deviceDir, buffer);\n    }\n    else\n    {\n        ffGPUDetectDriverSpecific(options, gpu, (FFGpuDriverPciBusId) {\n            .domain = pciDomain,\n            .bus = pciBus,\n            .device = pciDevice,\n            .func = pciFunc,\n        });\n    }\n\n    if (gpu->name.length == 0)\n        ffGPUFillVendorAndName(subclassId, (uint16_t) vendorId, (uint16_t) deviceId, gpu);\n\n    if (gpu->type == FF_GPU_TYPE_UNKNOWN)\n    {\n        if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_NVIDIA)\n        {\n            if (ffStrbufStartsWithIgnCaseS(&gpu->name, \"GeForce\") ||\n                ffStrbufStartsWithIgnCaseS(&gpu->name, \"Quadro\") ||\n                ffStrbufStartsWithIgnCaseS(&gpu->name, \"Tesla\"))\n                gpu->type = FF_GPU_TYPE_DISCRETE;\n        }\n        else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_MTHREADS)\n        {\n            if (ffStrbufStartsWithIgnCaseS(&gpu->name, \"MTT \"))\n                gpu->type = FF_GPU_TYPE_DISCRETE;\n        }\n    }\n\n    return NULL;\n}\n\n#if __aarch64__\n\nFF_MAYBE_UNUSED static const char* drmDetectAsahiSpecific(FFGPUResult* gpu, const char* name, FF_MAYBE_UNUSED FFstrbuf* buffer, FF_MAYBE_UNUSED const char* drmKey)\n{\n    if (sscanf(name, \"agx-t%lu\", &gpu->deviceId) == 1)\n        ffStrbufSetStatic(&gpu->name, ffCPUAppleCodeToName((uint32_t) gpu->deviceId));\n    ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_APPLE);\n\n    #if FF_HAVE_DRM_ASAHI\n    ffStrbufSetS(buffer, \"/dev/dri/\");\n    ffStrbufAppendS(buffer, drmKey);\n    FF_AUTO_CLOSE_FD int fd = open(buffer->chars, O_RDONLY | O_CLOEXEC);\n    if (fd >= 0)\n        return ffDrmDetectAsahi(gpu, fd);\n    #endif\n\n    return NULL;\n}\n#endif\n\nstatic const char* detectOf(FFlist* gpus, FFstrbuf* buffer, FFstrbuf* drmDir, const char* drmKey)\n{\n    char compatible[256]; // vendor,model-name\n    if (sscanf(buffer->chars + strlen(\"of:\"), \"NgpuT%*[^C]C%255[^C]\", compatible) != 1)\n        return \"Failed to parse of modalias or not a GPU device\";\n\n    char* name = strchr(compatible, ',');\n    if (name)\n    {\n        *name = '\\0';\n        ++name;\n    }\n\n    FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus);\n    gpu->index = FF_GPU_INDEX_UNSET;\n    gpu->deviceId = 0;\n    ffStrbufInit(&gpu->name);\n    ffStrbufInit(&gpu->vendor);\n    ffStrbufInit(&gpu->driver);\n    ffStrbufInit(&gpu->memoryType);\n    ffStrbufInitF(&gpu->platformApi, \"DRM (%s)\", drmKey);\n    gpu->temperature = FF_GPU_TEMP_UNSET;\n    gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;\n    gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;\n    gpu->type = FF_GPU_TYPE_INTEGRATED;\n    gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;\n    gpu->frequency = FF_GPU_FREQUENCY_UNSET;\n\n    pciDetectDriver(&gpu->driver, drmDir, buffer, drmKey);\n\n    #ifdef __aarch64__\n    if (ffStrbufEqualS(&gpu->driver, \"asahi\"))\n        drmDetectAsahiSpecific(gpu, name, buffer, drmKey);\n    #endif\n\n    if (!gpu->name.length)\n    {\n        ffStrbufSetS(&gpu->name, name ?: compatible);\n        ffStrbufTrimRightSpace(&gpu->name);\n    }\n    if (!gpu->vendor.length && name)\n    {\n        if (ffStrEquals(compatible, \"brcm\"))\n            ffStrbufSetStatic(&gpu->vendor, \"Broadcom\"); // Raspberry Pi\n        else\n        {\n            ffStrbufSetS(&gpu->vendor, compatible);\n            gpu->vendor.chars[0] = (char) toupper(compatible[0]);\n        }\n    }\n\n    return NULL;\n}\n\nstatic const char* drmDetectGPUs(const FFGPUOptions* options, FFlist* gpus)\n{\n    FF_STRBUF_AUTO_DESTROY drmDir = ffStrbufCreateA(64);\n    ffStrbufAppendS(&drmDir, \"/sys/class/drm/\");\n    const uint32_t drmDirLength = drmDir.length;\n\n    FF_AUTO_CLOSE_DIR DIR* dir = opendir(drmDir.chars);\n    if(dir == NULL)\n        return \"Failed to open `/sys/class/drm/`\";\n\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n\n    struct dirent* entry;\n    while ((entry = readdir(dir)) != NULL)\n    {\n        if (!ffStrStartsWith(entry->d_name, \"card\") ||\n            strchr(entry->d_name + 4, '-') != NULL)\n            continue;\n\n        ffStrbufAppendS(&drmDir, entry->d_name);\n\n        ffStrbufAppendS(&drmDir, \"/device/modalias\");\n        if (!ffReadFileBuffer(drmDir.chars, &buffer))\n            continue;\n        ffStrbufSubstrBefore(&drmDir, drmDir.length - (uint32_t) strlen(\"/modalias\"));\n\n        if (ffStrbufStartsWithS(&buffer, \"pci:\"))\n            detectPci(options, gpus, &buffer, &drmDir, entry->d_name);\n        else if (ffStrbufStartsWithS(&buffer, \"of:\")) // Open Firmware\n            detectOf(gpus, &buffer, &drmDir, entry->d_name);\n\n        ffStrbufSubstrBefore(&drmDir, drmDirLength);\n    }\n\n    return NULL;\n}\n\n\nstatic const char* pciDetectGPUs(const FFGPUOptions* options, FFlist* gpus)\n{\n    //https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-pci\n    const char* pciDirPath = \"/sys/bus/pci/devices/\";\n\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pciDirPath);\n    if(dirp == NULL)\n        return \"Failed to open `/sys/bus/pci/devices/`\";\n\n    FF_STRBUF_AUTO_DESTROY pciDir = ffStrbufCreateA(64);\n    ffStrbufAppendS(&pciDir, pciDirPath);\n\n    const uint32_t pciBaseDirLength = pciDir.length;\n\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n\n    struct dirent* entry;\n    while((entry = readdir(dirp)) != NULL)\n    {\n        if(entry->d_name[0] == '.')\n            continue;\n\n        ffStrbufSubstrBefore(&pciDir, pciBaseDirLength);\n        ffStrbufAppendS(&pciDir, entry->d_name);\n        const uint32_t pciDevDirLength = pciDir.length;\n\n        ffStrbufAppendS(&pciDir, \"/modalias\");\n        if (!ffReadFileBuffer(pciDir.chars, &buffer))\n            continue;\n        ffStrbufSubstrBefore(&pciDir, pciDevDirLength);\n        assert(ffStrbufStartsWithS(&buffer, \"pci:\"));\n\n        detectPci(options, gpus, &buffer, &pciDir, NULL);\n        ffStrbufSubstrBefore(&pciDir, pciBaseDirLength);\n    }\n\n    return NULL;\n}\n\nconst char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus)\n{\n    #ifdef FF_HAVE_DIRECTX_HEADERS\n        const char* ffGPUDetectByDirectX(const FFGPUOptions* options, FFlist* gpus);\n        if (ffGPUDetectByDirectX(options, gpus) == NULL)\n            return NULL;\n    #endif\n\n    if (options->detectionMethod == FF_GPU_DETECTION_METHOD_AUTO)\n    {\n        if (drmDetectGPUs(options, gpus) == NULL && gpus->length > 0)\n            return NULL;\n    }\n    return pciDetectGPUs(options, gpus);\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu_mthreads.c",
    "content": "#include \"gpu_driver_specific.h\"\n\n#include \"common/library.h\"\n#include \"mtml.h\"\n\nstruct FFMtmlData\n{\n    FF_LIBRARY_SYMBOL(mtmlDeviceCountGpuCores)\n    FF_LIBRARY_SYMBOL(mtmlDeviceGetBrand)\n    FF_LIBRARY_SYMBOL(mtmlDeviceGetIndex)\n    FF_LIBRARY_SYMBOL(mtmlDeviceGetName)\n    FF_LIBRARY_SYMBOL(mtmlDeviceGetPciInfo)\n    FF_LIBRARY_SYMBOL(mtmlDeviceGetUUID)\n    FF_LIBRARY_SYMBOL(mtmlDeviceInitGpu)\n    FF_LIBRARY_SYMBOL(mtmlDeviceInitMemory)\n    FF_LIBRARY_SYMBOL(mtmlGpuGetMaxClock)\n    FF_LIBRARY_SYMBOL(mtmlGpuGetTemperature)\n    FF_LIBRARY_SYMBOL(mtmlGpuGetUtilization)\n    FF_LIBRARY_SYMBOL(mtmlLibraryCountDevice)\n    FF_LIBRARY_SYMBOL(mtmlLibraryInitDeviceByIndex)\n    FF_LIBRARY_SYMBOL(mtmlLibraryInitDeviceByPciSbdf)\n    FF_LIBRARY_SYMBOL(mtmlLibraryInitSystem)\n    FF_LIBRARY_SYMBOL(mtmlMemoryGetTotal)\n    FF_LIBRARY_SYMBOL(mtmlMemoryGetUsed)\n    FF_LIBRARY_SYMBOL(mtmlMemoryGetUtilization)\n    FF_LIBRARY_SYMBOL(mtmlLibraryShutDown)\n\n    bool inited;\n    MtmlLibrary *lib;\n    MtmlSystem *sys;\n} mtmlData;\n\nFF_MAYBE_UNUSED static void shutdownMtml(void)\n{\n    mtmlData.ffmtmlLibraryShutDown(mtmlData.lib);\n}\n\nconst char *ffDetectMthreadsGpuInfo(const FFGpuDriverCondition *cond, FFGpuDriverResult result, const char *soName)\n{\n#ifndef FF_DISABLE_DLOPEN\n\n    if (!mtmlData.inited)\n    {\n        mtmlData.inited = true;\n        FF_LIBRARY_LOAD(libmtml, \"dlopen mtml failed\", soName, 1);\n        FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libmtml, mtmlLibraryInit)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceCountGpuCores)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceGetBrand)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceGetIndex)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceGetName)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceGetPciInfo)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceGetUUID)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceInitGpu)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceInitMemory)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlGpuGetMaxClock)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlGpuGetTemperature)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlGpuGetUtilization)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlLibraryCountDevice)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlLibraryInitDeviceByIndex)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlLibraryInitDeviceByPciSbdf)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlLibraryInitSystem)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlMemoryGetTotal)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlMemoryGetUsed)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlMemoryGetUtilization)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlLibraryShutDown)\n\n        if (ffmtmlLibraryInit(&mtmlData.lib) != MTML_SUCCESS)\n        {\n            mtmlData.ffmtmlLibraryInitSystem = NULL;\n            return \"mtmlLibraryInit failed\";\n        }\n        if (mtmlData.ffmtmlLibraryInitSystem(mtmlData.lib, &mtmlData.sys) != MTML_SUCCESS)\n        {\n            mtmlData.ffmtmlLibraryShutDown(mtmlData.lib);\n            mtmlData.ffmtmlLibraryInitSystem = NULL;\n            return \"mtmlLibraryInitSystem failed\";\n        }\n        atexit(shutdownMtml);\n        libmtml = NULL; // don't close mtml\n    }\n\n    if (mtmlData.ffmtmlLibraryInitSystem == NULL)\n        return \"loading mtml library failed\";\n\n    MtmlDevice *device = NULL;\n    if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID)\n    {\n        char pciBusIdStr[32];\n        snprintf(pciBusIdStr, ARRAY_SIZE(pciBusIdStr) - 1, \"%04x:%02x:%02x.%d\", cond->pciBusId.domain, cond->pciBusId.bus, cond->pciBusId.device, cond->pciBusId.func);\n\n        MtmlReturn ret = mtmlData.ffmtmlLibraryInitDeviceByPciSbdf(mtmlData.lib, pciBusIdStr, &device);\n        if (ret != MTML_SUCCESS)\n            return \"mtmlLibraryInitDeviceByPciSbdf() failed\";\n    }\n    else if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID)\n    {\n        uint32_t count;\n        if (mtmlData.ffmtmlLibraryCountDevice(mtmlData.lib, &count) != MTML_SUCCESS)\n            return \"mtmlLibraryCountDevice() failed\";\n\n        for (uint32_t i = 0; i < count; i++, device = NULL)\n        {\n            if (mtmlData.ffmtmlLibraryInitDeviceByIndex(mtmlData.lib, i, &device) != MTML_SUCCESS)\n                continue;\n\n            MtmlPciInfo pciInfo;\n            if (mtmlData.ffmtmlDeviceGetPciInfo(device, &pciInfo) != MTML_SUCCESS)\n                continue;\n\n            if (pciInfo.pciDeviceId != ((cond->pciDeviceId.deviceId << 16u) | cond->pciDeviceId.vendorId) ||\n                pciInfo.pciSubsystemId != cond->pciDeviceId.subSystemId)\n                continue;\n\n            break;\n        }\n        if (!device)\n            return \"Device not found\";\n    }\n    else\n    {\n        return \"Unknown condition type\";\n    }\n\n    MtmlBrandType brand;\n    if (mtmlData.ffmtmlDeviceGetBrand(device, &brand) == MTML_SUCCESS)\n    {\n        switch (brand)\n        {\n        case MTML_BRAND_MTT:\n            *result.type = FF_GPU_TYPE_DISCRETE;\n            break;\n        default:\n            break;\n        }\n    }\n\n    if (result.index)\n    {\n        unsigned int value;\n        if (mtmlData.ffmtmlDeviceGetIndex(device, &value) == MTML_SUCCESS)\n            *result.index = value;\n    }\n\n    if (result.temp)\n    {\n        MtmlGpu *gpu = NULL;\n        if (mtmlData.ffmtmlDeviceInitGpu(device, &gpu) == MTML_SUCCESS)\n        {\n            uint32_t value;\n            if (mtmlData.ffmtmlGpuGetTemperature(gpu, &value) == MTML_SUCCESS)\n                *result.temp = value;\n        }\n    }\n\n    if (result.memory)\n    {\n        MtmlMemory *mem = NULL;\n        if (mtmlData.ffmtmlDeviceInitMemory(device, &mem) == MTML_SUCCESS)\n        {\n            unsigned long long total;\n            if (mtmlData.ffmtmlMemoryGetTotal(mem, &total) == MTML_SUCCESS)\n                result.memory->total = total;\n\n            unsigned long long used;\n            if (mtmlData.ffmtmlMemoryGetUsed(mem, &used) == MTML_SUCCESS)\n                result.memory->used = used;\n        }\n    }\n\n    if (result.coreCount)\n        mtmlData.ffmtmlDeviceCountGpuCores(device, result.coreCount);\n\n    if (result.frequency)\n    {\n        MtmlGpu *gpu = NULL;\n        if (mtmlData.ffmtmlDeviceInitGpu(device, &gpu) == MTML_SUCCESS)\n        {\n            uint32_t clockMHz;\n            if (mtmlData.ffmtmlGpuGetMaxClock(gpu, &clockMHz) == MTML_SUCCESS)\n                *result.frequency = clockMHz;\n        }\n    }\n\n    if (result.coreUsage)\n    {\n        MtmlGpu *gpu = NULL;\n        if (mtmlData.ffmtmlDeviceInitGpu(device, &gpu) == MTML_SUCCESS)\n        {\n            unsigned int utilization;\n            if (mtmlData.ffmtmlGpuGetUtilization(gpu, &utilization) == MTML_SUCCESS)\n                *result.coreUsage = utilization;\n        }\n    }\n\n    if (result.name)\n    {\n        char name[MTML_DEVICE_NAME_BUFFER_SIZE];\n        if (mtmlData.ffmtmlDeviceGetName(device, name, ARRAY_SIZE(name)) == MTML_SUCCESS)\n            ffStrbufSetS(result.name, name);\n    }\n\n    return NULL;\n\n#else\n\n    FF_UNUSED(cond, result, soName);\n    return \"dlopen is disabled\";\n\n#endif\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu_nbsd.c",
    "content": "#include \"gpu.h\"\n#include \"common/io.h\"\n\n#include <sys/param.h>\n#include <sys/ioctl.h>\n#include <fcntl.h>\n#include <dev/pci/pcireg.h>\n#include <dev/pci/pcidevs.h>\n#include <dev/pci/pciio.h>\n\nstatic inline int pciReadConf(int fd, uint32_t bus, uint32_t device, uint32_t func, uint32_t reg, uint32_t* result)\n{\n    struct pciio_bdf_cfgreg bdfr = {\n        .bus = bus,\n        .device = device,\n        .function = func,\n        .cfgreg = {\n            .reg = reg,\n        },\n    };\n\n    if (ioctl(fd, PCI_IOC_BDF_CFGREAD, &bdfr) == -1)\n        return -1;\n\n    *result = bdfr.cfgreg.val;\n    return 0;\n}\n\nconst char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* gpus)\n{\n    char pciDevPath[] = \"/dev/pciXXX\";\n\n    for (uint32_t idev = 0; idev <= 255; idev++)\n    {\n        snprintf(pciDevPath + strlen(\"/dev/pci\"), 4, \"%u\", idev);\n\n        FF_AUTO_CLOSE_FD int pcifd = open(pciDevPath, O_RDONLY | O_CLOEXEC);\n        if (pcifd < 0)\n        {\n            if (errno == ENOENT)\n                break; // No more /dev/pciN devices\n            return \"open(\\\"/dev/pciN\\\", O_RDONLY | O_CLOEXEC) failed\";\n        }\n\n        struct pciio_businfo businfo;\n        if (ioctl(pcifd, PCI_IOC_BUSINFO, &businfo) != 0)\n            continue;\n\n        uint32_t bus = businfo.busno;\n        for (uint32_t dev = 0; dev < businfo.maxdevs; dev++)\n        {\n            uint32_t maxfuncs = 0;\n            for (uint32_t func = 0; func <= maxfuncs; func++)\n            {\n                uint32_t pciid, pciclass;\n                if (pciReadConf(pcifd, bus, dev, func, PCI_ID_REG, &pciid) != 0)\n                    continue;\n\n                if (PCI_VENDOR(pciid) == PCI_VENDOR_INVALID || PCI_VENDOR(pciid) == 0)\n                    continue;\n\n                if (pciReadConf(pcifd, bus, dev, func, PCI_CLASS_REG, &pciclass) != 0)\n                    continue;\n\n                if (func == 0)\n                {\n                    // For some reason, pciReadConf returns success even for non-existing devices.\n                    // So we need to check for `PCI_VENDOR(pciid) == PCI_VENDOR_INVALID` above to filter them out.\n                    uint32_t bhlcr;\n                    if (pciReadConf(pcifd, bus, dev, 0, PCI_BHLC_REG, &bhlcr) != 0)\n                        continue;\n\n                    if (PCI_HDRTYPE_MULTIFN(bhlcr)) maxfuncs = 7;\n                }\n\n                if (PCI_CLASS(pciclass) != PCI_CLASS_DISPLAY)\n                    continue;\n\n                if (func > 0 && PCI_SUBCLASS(pciclass) == PCI_SUBCLASS_DISPLAY_MISC)\n                    continue; // Likely an auxiliary display controller (#2034)\n\n                FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus);\n                ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(PCI_VENDOR(pciid)));\n                ffStrbufInit(&gpu->name);\n                ffStrbufInit(&gpu->driver);\n                ffStrbufInitS(&gpu->platformApi, pciDevPath);\n                ffStrbufInit(&gpu->memoryType);\n                gpu->index = FF_GPU_INDEX_UNSET;\n                gpu->temperature = FF_GPU_TEMP_UNSET;\n                gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;\n                gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;\n                gpu->type = FF_GPU_TYPE_UNKNOWN;\n                gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;\n                gpu->deviceId = ffGPUPciAddr2Id(0, bus, dev, func);\n                gpu->frequency = FF_GPU_FREQUENCY_UNSET;\n\n                if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD)\n                    ffGPUQueryAmdGpuName(PCI_PRODUCT(pciid), PCI_REVISION(pciid), gpu);\n                if (gpu->name.length == 0)\n                    ffGPUFillVendorAndName(PCI_SUBCLASS(pciclass), PCI_VENDOR(pciid), PCI_PRODUCT(pciid), gpu);\n\n                struct pciio_drvname drvname = {\n                    .device = dev,\n                    .function = func,\n                };\n                if (ioctl(pcifd, PCI_IOC_DRVNAME, &drvname) == 0)\n                    ffStrbufInitS(&gpu->driver, drvname.name);\n            }\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu_nosupport.c",
    "content": "#include \"gpu.h\"\n\nconst char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus)\n{\n    FF_UNUSED(options, gpus);\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu_nvidia.c",
    "content": "#include \"gpu_driver_specific.h\"\n\n#include \"common/library.h\"\n#include \"nvml.h\"\n\nstruct FFNvmlData {\n    FF_LIBRARY_SYMBOL(nvmlDeviceGetCount_v2)\n    FF_LIBRARY_SYMBOL(nvmlDeviceGetHandleByIndex_v2)\n    FF_LIBRARY_SYMBOL(nvmlDeviceGetHandleByPciBusId_v2)\n    FF_LIBRARY_SYMBOL(nvmlDeviceGetPciInfo_v3)\n    FF_LIBRARY_SYMBOL(nvmlDeviceGetTemperature)\n    FF_LIBRARY_SYMBOL(nvmlDeviceGetMemoryInfo_v2)\n    FF_LIBRARY_SYMBOL(nvmlDeviceGetMemoryInfo)\n    FF_LIBRARY_SYMBOL(nvmlDeviceGetNumGpuCores)\n    FF_LIBRARY_SYMBOL(nvmlDeviceGetMaxClockInfo)\n    FF_LIBRARY_SYMBOL(nvmlDeviceGetUtilizationRates)\n    FF_LIBRARY_SYMBOL(nvmlDeviceGetBrand)\n    FF_LIBRARY_SYMBOL(nvmlDeviceGetIndex)\n    FF_LIBRARY_SYMBOL(nvmlDeviceGetName)\n\n    bool inited;\n} nvmlData;\n\n#if defined(_WIN32) && !defined(FF_DISABLE_DLOPEN)\n\n#include \"nvapi.h\"\n\nstruct FFNvapiData {\n    FF_LIBRARY_SYMBOL(nvapi_Unload)\n    FF_LIBRARY_SYMBOL(nvapi_EnumPhysicalGPUs)\n    FF_LIBRARY_SYMBOL(nvapi_GPU_GetRamType)\n    FF_LIBRARY_SYMBOL(nvapi_GPU_GetGPUType)\n\n    bool inited;\n} nvapiData;\n\nstatic const char* detectMoreByNvapi(FFGpuDriverResult* result)\n{\n    if (!nvapiData.inited)\n    {\n        nvapiData.inited = true;\n\n        FF_LIBRARY_LOAD_MESSAGE(libnvapi,\n            #ifdef _WIN64\n            \"nvapi64.dll\"\n            #else\n            \"nvapi.dll\"\n            #endif\n        , 1);\n        FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libnvapi, nvapi_QueryInterface)\n        #define FF_NVAPI_INTERFACE(iName, iOffset) \\\n            __typeof__(&iName) ff ## iName = ffnvapi_QueryInterface(iOffset); \\\n            if (ff ## iName == NULL) return \"nvapi_QueryInterface \" #iName \" failed\";\n\n        FF_NVAPI_INTERFACE(nvapi_Initialize, NVAPI_INTERFACE_OFFSET_INITIALIZE)\n        FF_NVAPI_INTERFACE(nvapi_Unload, NVAPI_INTERFACE_OFFSET_UNLOAD)\n        FF_NVAPI_INTERFACE(nvapi_EnumPhysicalGPUs, NVAPI_INTERFACE_OFFSET_ENUM_PHYSICAL_GPUS)\n        FF_NVAPI_INTERFACE(nvapi_GPU_GetRamType, NVAPI_INTERFACE_OFFSET_GPU_GET_RAM_TYPE)\n        FF_NVAPI_INTERFACE(nvapi_GPU_GetGPUType, NVAPI_INTERFACE_OFFSET_GPU_GET_GPU_TYPE)\n        #undef FF_NVAPI_INTERFACE\n\n        if (ffnvapi_Initialize() < 0)\n            return \"NvAPI_Initialize() failed\";\n\n        nvapiData.ffnvapi_EnumPhysicalGPUs = ffnvapi_EnumPhysicalGPUs;\n        nvapiData.ffnvapi_GPU_GetRamType = ffnvapi_GPU_GetRamType;\n        nvapiData.ffnvapi_GPU_GetGPUType = ffnvapi_GPU_GetGPUType;\n        nvapiData.ffnvapi_Unload = ffnvapi_Unload;\n\n        atexit((void*) ffnvapi_Unload);\n        libnvapi = NULL; // don't close nvapi\n    }\n\n    if (nvapiData.ffnvapi_EnumPhysicalGPUs == NULL)\n        return \"loading nvapi library failed\";\n\n    NvPhysicalGpuHandle handles[32];\n    int gpuCount = 0;\n\n    if (nvapiData.ffnvapi_EnumPhysicalGPUs(handles, &gpuCount) < 0)\n        return \"NvAPI_EnumPhysicalGPUs() failed\";\n\n    uint32_t gpuIndex = *result->index;\n\n    if (gpuIndex >= (uint32_t) gpuCount)\n        return \"GPU index out of range\";\n\n    // Not very sure. Need to check in multi-GPU system\n    NvPhysicalGpuHandle gpuHandle = handles[gpuIndex];\n\n    NvApiGPUMemoryType memType;\n    if (result->memoryType && nvapiData.ffnvapi_GPU_GetRamType(gpuHandle, &memType) == 0)\n    {\n        switch (memType)\n        {\n            #define FF_NVAPI_MEMORY_TYPE(type) \\\n                case NVAPI_GPU_MEMORY_TYPE_##type: \\\n                    ffStrbufSetStatic(result->memoryType, #type); \\\n                    break;\n            FF_NVAPI_MEMORY_TYPE(UNKNOWN)\n            FF_NVAPI_MEMORY_TYPE(SDRAM)\n            FF_NVAPI_MEMORY_TYPE(DDR1)\n            FF_NVAPI_MEMORY_TYPE(DDR2)\n            FF_NVAPI_MEMORY_TYPE(GDDR2)\n            FF_NVAPI_MEMORY_TYPE(GDDR3)\n            FF_NVAPI_MEMORY_TYPE(GDDR4)\n            FF_NVAPI_MEMORY_TYPE(DDR3)\n            FF_NVAPI_MEMORY_TYPE(GDDR5)\n            FF_NVAPI_MEMORY_TYPE(LPDDR2)\n            FF_NVAPI_MEMORY_TYPE(GDDR5X)\n            FF_NVAPI_MEMORY_TYPE(LPDDR3)\n            FF_NVAPI_MEMORY_TYPE(LPDDR4)\n            FF_NVAPI_MEMORY_TYPE(LPDDR5)\n            FF_NVAPI_MEMORY_TYPE(GDDR6)\n            FF_NVAPI_MEMORY_TYPE(GDDR6X)\n            FF_NVAPI_MEMORY_TYPE(GDDR7)\n            #undef FF_NVAPI_MEMORY_TYPE\n            default:\n                ffStrbufSetF(result->memoryType, \"Unknown (%d)\", memType);\n                break;\n        }\n    }\n\n    NvApiGPUType gpuType;\n    if (result->type && nvapiData.ffnvapi_GPU_GetGPUType(gpuHandle, &gpuType) == 0)\n    {\n        switch (gpuType)\n        {\n            case NV_SYSTEM_TYPE_IGPU:\n                *result->type = FF_GPU_TYPE_INTEGRATED;\n                break;\n            case NV_SYSTEM_TYPE_DGPU:\n                *result->type = FF_GPU_TYPE_DISCRETE;\n                break;\n            default:\n                *result->type = FF_GPU_TYPE_UNKNOWN;\n                break;\n        }\n    }\n\n    return NULL;\n}\n\n#endif\n\nconst char* ffDetectNvidiaGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName)\n{\n#ifndef FF_DISABLE_DLOPEN\n\n    if (!nvmlData.inited)\n    {\n        nvmlData.inited = true;\n        FF_LIBRARY_LOAD(libnvml, \"dlopen nvml failed\", soName , 1);\n        FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libnvml, nvmlInit_v2)\n        FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libnvml, nvmlShutdown)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetCount_v2)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetHandleByIndex_v2)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetHandleByPciBusId_v2)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetPciInfo_v3)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetTemperature)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetMemoryInfo_v2)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetMemoryInfo)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetNumGpuCores)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetMaxClockInfo)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetUtilizationRates)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetBrand)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetIndex)\n        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetName)\n\n        if (ffnvmlInit_v2() != NVML_SUCCESS)\n        {\n            nvmlData.ffnvmlDeviceGetNumGpuCores = NULL;\n            return \"nvmlInit_v2() failed\";\n        }\n        atexit((void*) ffnvmlShutdown);\n        libnvml = NULL; // don't close nvml\n    }\n\n    if (nvmlData.ffnvmlDeviceGetNumGpuCores == NULL)\n        return \"loading nvml library failed\";\n\n    nvmlDevice_t device = NULL;\n    if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID)\n    {\n        char pciBusIdStr[32];\n        snprintf(pciBusIdStr, ARRAY_SIZE(pciBusIdStr), \"%04x:%02x:%02x.%d\", cond->pciBusId.domain, cond->pciBusId.bus, cond->pciBusId.device, cond->pciBusId.func);\n\n        nvmlReturn_t ret = nvmlData.ffnvmlDeviceGetHandleByPciBusId_v2(pciBusIdStr, &device);\n        if (ret != NVML_SUCCESS)\n            return \"nvmlDeviceGetHandleByPciBusId_v2() failed\";\n    }\n    else if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID)\n    {\n        uint32_t count;\n        if (nvmlData.ffnvmlDeviceGetCount_v2(&count) != NVML_SUCCESS)\n            return \"nvmlDeviceGetCount_v2() failed\";\n\n        for (uint32_t i = 0; i < count; i++, device = NULL)\n        {\n            if (nvmlData.ffnvmlDeviceGetHandleByIndex_v2(i, &device) != NVML_SUCCESS)\n                continue;\n\n            nvmlPciInfo_t pciInfo;\n            if (nvmlData.ffnvmlDeviceGetPciInfo_v3(device, &pciInfo) != NVML_SUCCESS)\n                continue;\n\n            if (pciInfo.pciDeviceId != ((cond->pciDeviceId.deviceId << 16u) | cond->pciDeviceId.vendorId) ||\n                pciInfo.pciSubSystemId != cond->pciDeviceId.subSystemId)\n                continue;\n\n            break;\n        }\n    }\n\n    if (!device) return \"Device not found\";\n\n    if (result.type)\n    {\n        nvmlBrandType_t brand;\n        if (nvmlData.ffnvmlDeviceGetBrand(device, &brand) == NVML_SUCCESS)\n        {\n            switch (brand)\n            {\n                case NVML_BRAND_NVIDIA_RTX:\n                case NVML_BRAND_QUADRO_RTX:\n                case NVML_BRAND_GEFORCE:\n                case NVML_BRAND_TITAN:\n                case NVML_BRAND_TESLA:\n                case NVML_BRAND_QUADRO:\n                    *result.type = FF_GPU_TYPE_DISCRETE;\n                    break;\n                default:\n                    break;\n            }\n        }\n    }\n\n    if (result.index)\n    {\n        unsigned int value;\n        if (nvmlData.ffnvmlDeviceGetIndex(device, &value) == NVML_SUCCESS)\n        {\n            *result.index = value;\n            #ifdef _WIN32\n            // Don't bother loading nvapi for GPU type detection only\n            if (result.memoryType)\n                detectMoreByNvapi(&result);\n            #endif\n        }\n    }\n\n    if (result.temp)\n    {\n        uint32_t value;\n        if (nvmlData.ffnvmlDeviceGetTemperature(device, NVML_TEMPERATURE_GPU, &value) == NVML_SUCCESS)\n            *result.temp = value;\n    }\n\n    if (result.memory)\n    {\n        nvmlMemory_v2_t memory = { .version = nvmlMemory_v2 };\n        if (nvmlData.ffnvmlDeviceGetMemoryInfo_v2(device, &memory) == NVML_SUCCESS)\n        {\n            result.memory->total = memory.used + memory.free;\n            result.memory->used = memory.used;\n        }\n        else\n        {\n            nvmlMemory_t memory_v1;\n            if (nvmlData.ffnvmlDeviceGetMemoryInfo(device, &memory_v1) == NVML_SUCCESS)\n            {\n                result.memory->total = memory_v1.total;\n                result.memory->used = memory_v1.used;\n            }\n        }\n    }\n\n    if (result.coreCount)\n        nvmlData.ffnvmlDeviceGetNumGpuCores(device, result.coreCount);\n\n    if (result.frequency)\n        nvmlData.ffnvmlDeviceGetMaxClockInfo(device, NVML_CLOCK_GRAPHICS, result.frequency);\n\n    if (result.coreUsage)\n    {\n        nvmlUtilization_t utilization;\n        if (nvmlData.ffnvmlDeviceGetUtilizationRates(device, &utilization) == NVML_SUCCESS)\n            *result.coreUsage = utilization.gpu;\n    }\n\n    if (result.name)\n    {\n        char name[NVML_DEVICE_NAME_V2_BUFFER_SIZE];\n        if (nvmlData.ffnvmlDeviceGetName(device, name, ARRAY_SIZE(name)) == NVML_SUCCESS)\n            ffStrbufSetS(result.name, name);\n    }\n\n    return NULL;\n\n#else\n\n    FF_UNUSED(cond, result, soName);\n    return \"dlopen is disabled\";\n\n#endif\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu_obsd.c",
    "content": "#include \"gpu.h\"\n#include \"common/io.h\"\n\n#include <sys/param.h>\n#include <sys/ioctl.h>\n#include <fcntl.h>\n#include <dev/pci/pcireg.h>\n#include <dev/pci/pcidevs.h>\n#include <sys/pciio.h>\n\nstatic inline int pciReadConf(int fd, uint32_t bus, uint32_t device, uint32_t func, uint32_t reg, uint32_t* result)\n{\n    struct pci_io bdfr = {\n        .pi_sel = {\n            .pc_bus = bus,\n            .pc_dev = device,\n            .pc_func = func,\n        },\n        .pi_reg = reg,\n        .pi_width = 4,\n    };\n\n    if (ioctl(fd, PCIOCREAD, &bdfr) == -1)\n        return -1;\n\n    *result = bdfr.pi_data;\n    return 0;\n}\n\nconst char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* gpus)\n{\n    char pciDevPath[] = \"/dev/pci0\";\n    FF_AUTO_CLOSE_FD int pcifd = open(pciDevPath, O_RDONLY | O_CLOEXEC);\n    if (pcifd < 0)\n        return \"open(\\\"/dev/pci0\\\", O_RDONLY | O_CLOEXEC) failed\";\n\n    for (uint32_t bus = 0; bus <= 255; bus++)\n    {\n        for (uint32_t dev = 0; dev < 32; dev++)\n        {\n            uint32_t maxfuncs = 0;\n            for (uint32_t func = 0; func <= maxfuncs; func++)\n            {\n                uint32_t pciid, pciclass;\n                if (pciReadConf(pcifd, bus, dev, func, PCI_ID_REG, &pciid) != 0)\n                    continue;\n\n                if (PCI_VENDOR(pciid) == PCI_VENDOR_INVALID || PCI_VENDOR(pciid) == 0)\n                    continue;\n\n                if (pciReadConf(pcifd, bus, dev, func, PCI_CLASS_REG, &pciclass) != 0)\n                    continue;\n\n                if (func == 0)\n                {\n                    // For some reason, pciReadConf returns success even for non-existing devices.\n                    // So we need to check for `PCI_VENDOR(pciid) == PCI_VENDOR_INVALID` above to filter them out.\n                    uint32_t bhlcr;\n                    if (pciReadConf(pcifd, bus, dev, 0, PCI_BHLC_REG, &bhlcr) != 0)\n                        continue;\n\n                    if (PCI_HDRTYPE_MULTIFN(bhlcr)) maxfuncs = 7;\n                }\n\n                if (PCI_CLASS(pciclass) != PCI_CLASS_DISPLAY)\n                    continue;\n\n                if (func > 0 && PCI_SUBCLASS(pciclass) == PCI_SUBCLASS_DISPLAY_MISC)\n                    continue; // Likely an auxiliary display controller (#2034)\n\n                FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus);\n                ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(PCI_VENDOR(pciid)));\n                ffStrbufInit(&gpu->name);\n                ffStrbufInit(&gpu->driver);\n                ffStrbufInitS(&gpu->platformApi, \"/dev/pci0\");\n                ffStrbufInit(&gpu->memoryType);\n                gpu->index = FF_GPU_INDEX_UNSET;\n                gpu->temperature = FF_GPU_TEMP_UNSET;\n                gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;\n                gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;\n                gpu->type = FF_GPU_TYPE_UNKNOWN;\n                gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;\n                gpu->deviceId = ffGPUPciAddr2Id(0, bus, dev, func);\n                gpu->frequency = FF_GPU_FREQUENCY_UNSET;\n\n                if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD)\n                    ffGPUQueryAmdGpuName(PCI_PRODUCT(pciid), PCI_REVISION(pciid), gpu);\n                if (gpu->name.length == 0)\n                    ffGPUFillVendorAndName(PCI_SUBCLASS(pciclass), PCI_VENDOR(pciid), PCI_PRODUCT(pciid), gpu);\n            }\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu_pci.c",
    "content": "#include \"gpu.h\"\n#include \"common/io.h\"\n#include \"common/properties.h\"\n#include \"common/memrchr.h\"\n\n#include <stdlib.h>\n#ifdef __FreeBSD__\n    #include <paths.h>\n    #ifndef _PATH_LOCALBASE\n        #define _PATH_LOCALBASE \"/usr/local\"\n    #endif\n#elif __OpenBSD__\n    #define _PATH_LOCALBASE \"/usr/local\"\n#elif __NetBSD__\n    #define _PATH_LOCALBASE \"/usr/pkg\"\n#endif\n\n#if FF_HAVE_EMBEDDED_PCIIDS\n#include \"fastfetch_pciids.c.inc\"\n#endif\n#if FF_HAVE_EMBEDDED_AMDGPUIDS\n#include \"fastfetch_amdgpuids.c.inc\"\n#endif\n\n#define FF_STR_INDIR(x) #x\n#define FF_STR(x) FF_STR_INDIR(x)\n\nstatic const FFstrbuf* loadPciIds()\n{\n    static FFstrbuf pciids;\n\n    if (pciids.chars) return &pciids;\n    ffStrbufInit(&pciids);\n\n    #ifdef FF_CUSTOM_PCI_IDS_PATH\n\n        ffReadFileBuffer(FF_STR(FF_CUSTOM_PCI_IDS_PATH), &pciids);\n\n    #else // FF_CUSTOM_PCI_IDS_PATH\n\n        #if __linux__\n        ffReadFileBuffer(FASTFETCH_TARGET_DIR_USR \"/share/hwdata/pci.ids\", &pciids);\n        if (pciids.length == 0)\n        {\n            ffReadFileBuffer(FASTFETCH_TARGET_DIR_USR \"/share/misc/pci.ids\", &pciids); // debian?\n            if (pciids.length == 0)\n                ffReadFileBuffer(FASTFETCH_TARGET_DIR_USR \"/local/share/hwdata/pci.ids\", &pciids);\n        }\n        #elif __OpenBSD__ || __FreeBSD__ || __NetBSD__\n        ffReadFileBuffer(_PATH_LOCALBASE \"/share/hwdata/pci.ids\", &pciids);\n        if (pciids.length == 0)\n            ffReadFileBuffer(_PATH_LOCALBASE \"/share/pciids/pci.ids\", &pciids);\n        #elif __sun\n        ffReadFileBuffer(FASTFETCH_TARGET_DIR_ROOT \"/usr/share/hwdata/pci.ids\", &pciids);\n        #elif __HAIKU__\n        ffReadFileBuffer(FASTFETCH_TARGET_DIR_ROOT \"/system/data/hwdata/pci.ids\", &pciids);\n        #endif\n\n    #endif // FF_CUSTOM_PCI_IDS_PATH\n\n    return &pciids;\n}\n\nstatic void parsePciIdsFile(const FFstrbuf* content, uint8_t subclass, uint16_t vendor, uint16_t device, FFGPUResult* gpu)\n{\n    if (content->length)\n    {\n        char buffer[32];\n\n        // Search for vendor\n        uint32_t len = (uint32_t) snprintf(buffer, ARRAY_SIZE(buffer), \"\\n%04x  \", vendor);\n        char* start = (char*) memmem(content->chars, content->length, buffer, len);\n        char* end = content->chars + content->length;\n        if (start)\n        {\n            start += len;\n            end = memchr(start, '\\n', (uint32_t) (end - start));\n            if (!end)\n                end = content->chars + content->length;\n            if (!gpu->vendor.length)\n                ffStrbufSetNS(&gpu->vendor, (uint32_t) (end - start), start);\n\n            start = end; // point to '\\n' of vendor\n            end = start + 1; // point to start of devices\n            // find the start of next vendor\n            while (end[0] == '\\t' || end[0] == '#')\n            {\n                end = strchr(end, '\\n');\n                if (!end)\n                {\n                    end = content->chars + content->length;\n                    break;\n                }\n                else\n                    end++;\n            }\n\n            // Search for device\n            len = (uint32_t) snprintf(buffer, ARRAY_SIZE(buffer), \"\\n\\t%04x  \", device);\n            start = memmem(start, (size_t) (end - start), buffer, len);\n            if (start)\n            {\n                start += len;\n                end = memchr(start, '\\n', (uint32_t) (end - start));\n                if (!end)\n                    end = content->chars + content->length;\n\n                char* closingBracket = end - 1;\n                if (*closingBracket == ']')\n                {\n                    char* openingBracket = memrchr(start, '[', (size_t) (closingBracket - start));\n                    if (openingBracket)\n                    {\n                        openingBracket++;\n                        ffStrbufSetNS(&gpu->name, (uint32_t) (closingBracket - openingBracket), openingBracket);\n                    }\n                }\n                if (!gpu->name.length)\n                    ffStrbufSetNS(&gpu->name, (uint32_t) (end - start), start);\n            }\n        }\n    }\n\n    if (!gpu->name.length)\n    {\n        const char* subclassStr;\n        switch (subclass)\n        {\n        case 0 /*PCI_CLASS_DISPLAY_VGA*/: subclassStr = \" (VGA compatible)\"; break;\n        case 1 /*PCI_CLASS_DISPLAY_XGA*/: subclassStr = \" (XGA compatible)\"; break;\n        case 2 /*PCI_CLASS_DISPLAY_3D*/: subclassStr = \" (3D)\"; break;\n        default: subclassStr = \"\"; break;\n        }\n\n        ffStrbufSetF(&gpu->name, \"%s Device %04X%s\", gpu->vendor.length ? gpu->vendor.chars : \"Unknown\", device, subclassStr);\n    }\n}\n\n#if FF_HAVE_EMBEDDED_PCIIDS\nstatic inline int pciDeviceCmp(const uint16_t* key, const FFPciDevice* element)\n{\n    return (int) *key - (int) element->id;\n}\n\nstatic bool loadPciidsInc(uint8_t subclass, uint16_t vendor, uint16_t device, FFGPUResult* gpu)\n{\n    for (const FFPciVendor* pvendor = ffPciVendors; pvendor->name; pvendor++)\n    {\n        if (pvendor->id != vendor) continue;\n\n        if (!gpu->vendor.length)\n            ffStrbufSetS(&gpu->vendor, pvendor->name);\n\n        const FFPciDevice* pdevice = (const FFPciDevice*) bsearch(&device, pvendor->devices, pvendor->nDevices, sizeof(*pdevice), (void*) pciDeviceCmp);\n\n        if (pdevice)\n        {\n            uint32_t nameLen = (uint32_t) strlen(pdevice->name);\n            const char* closingBracket = pdevice->name + nameLen - 1;\n            if (*closingBracket == ']')\n            {\n                const char* openingBracket = memrchr(pdevice->name, '[', nameLen - 1);\n                if (openingBracket)\n                {\n                    openingBracket++;\n                    ffStrbufSetNS(&gpu->name, (uint32_t) (closingBracket - openingBracket), openingBracket);\n                }\n            }\n            if (!gpu->name.length)\n                ffStrbufSetNS(&gpu->name, nameLen, pdevice->name);\n            return true;\n        }\n\n        if (!gpu->name.length)\n        {\n            const char* subclassStr;\n            switch (subclass)\n            {\n            case 0 /*PCI_CLASS_DISPLAY_VGA*/: subclassStr = \" (VGA compatible)\"; break;\n            case 1 /*PCI_CLASS_DISPLAY_XGA*/: subclassStr = \" (XGA compatible)\"; break;\n            case 2 /*PCI_CLASS_DISPLAY_3D*/: subclassStr = \" (3D)\"; break;\n            default: subclassStr = \"\"; break;\n            }\n\n            ffStrbufSetF(&gpu->name, \"%s Device %04X%s\", gpu->vendor.length ? gpu->vendor.chars : \"Unknown\", device, subclassStr);\n        }\n        return true;\n    }\n    return false;\n}\n#endif\n\nvoid ffGPUFillVendorAndName(uint8_t subclass, uint16_t vendor, uint16_t device, FFGPUResult* gpu)\n{\n    #if FF_HAVE_EMBEDDED_PCIIDS\n    bool ok = loadPciidsInc(subclass, vendor, device, gpu);\n    if (ok) return;\n    #endif\n    return parsePciIdsFile(loadPciIds(), subclass, vendor, device, gpu);\n}\n\n#if FF_HAVE_EMBEDDED_AMDGPUIDS\nstatic inline int amdGpuCmp(const uint32_t* key, const FFArmGpuProduct* element)\n{\n    // Maximum value of *key is 0x00FFFFFF. `(int) *key` should never overflow\n    return (int) *key - (int) element->id;\n}\n\nstatic bool loadAmdGpuIdsInc(uint16_t deviceId, uint8_t revision, FFGPUResult* gpu)\n{\n    uint32_t key = (deviceId << 8u) | revision;\n    FFArmGpuProduct* product = bsearch(&key, ffAmdGpuProducts, ARRAY_SIZE(ffAmdGpuProducts), sizeof(*ffAmdGpuProducts), (void*) amdGpuCmp);\n    if (product)\n    {\n        ffStrbufSetS(&gpu->name, product->name);\n        return true;\n    }\n    return false;\n}\n#endif\n\nstatic void parseAmdGpuIdsFile(uint16_t deviceId, uint8_t revision, FFGPUResult* gpu)\n{\n    char query[32];\n    snprintf(query, ARRAY_SIZE(query), \"%X,\\t%X,\", (unsigned) deviceId, (unsigned) revision);\n    #ifdef FF_CUSTOM_AMDGPU_IDS_PATH\n    ffParsePropFile(FF_STR(FF_CUSTOM_AMDGPU_IDS_PATH), query, &gpu->name);\n    #else\n    ffParsePropFileData(\"libdrm/amdgpu.ids\", query, &gpu->name);\n    #endif\n}\n\nvoid ffGPUQueryAmdGpuName(uint16_t deviceId, uint8_t revisionId, FFGPUResult* gpu)\n{\n    #if FF_HAVE_EMBEDDED_AMDGPUIDS\n    bool ok = loadAmdGpuIdsInc(deviceId, revisionId, gpu);\n    if (ok) return;\n    #endif\n    return parseAmdGpuIdsFile(deviceId, revisionId, gpu);\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu_sunos.c",
    "content": "#include \"gpu.h\"\n#include \"common/stringUtils.h\"\n\n#include <libdevinfo.h>\n\nstatic int walkDevTree(di_node_t node, FF_MAYBE_UNUSED di_minor_t minor, FFlist* gpus)\n{\n    int* vendorId;\n    int* deviceId;\n    if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, \"vendor-id\", &vendorId) > 0\n        && di_prop_lookup_ints(DDI_DEV_T_ANY, node, \"device-id\", &deviceId) > 0)\n    {\n        FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus);\n        ffStrbufInitS(&gpu->vendor, ffGPUGetVendorString((uint16_t) *vendorId));\n        ffStrbufInit(&gpu->name);\n        ffStrbufInitS(&gpu->driver, di_driver_name(node));\n        ffStrbufInitStatic(&gpu->platformApi, \"libdevinfo\");\n        ffStrbufInit(&gpu->memoryType);\n        gpu->index = FF_GPU_INDEX_UNSET;\n        gpu->temperature = FF_GPU_TEMP_UNSET;\n        gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;\n        gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;\n        gpu->type = FF_GPU_TYPE_UNKNOWN;\n        gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;\n        gpu->deviceId = strtoul(di_bus_addr(node), NULL, 16);\n        gpu->frequency = FF_GPU_FREQUENCY_UNSET;\n\n        if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD)\n        {\n            int* revId;\n            if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, \"revision-id\", &revId) > 0)\n                ffGPUQueryAmdGpuName((uint16_t) *deviceId, (uint8_t) *revId, gpu);\n        }\n\n        if (gpu->name.length == 0)\n        {\n            uint8_t subclass = 0; // assume VGA\n            int* classCode;\n            if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, \"class-code\", &classCode) > 0)\n                subclass = (uint8_t) (*classCode & 0xFFFF);\n            ffGPUFillVendorAndName(subclass, (uint16_t) *vendorId, (uint16_t) *deviceId, gpu);\n        }\n    }\n\n    return DI_WALK_CONTINUE;\n}\n\nconst char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* gpus)\n{\n    di_node_t rootNode = di_init(\"/\", DINFOCPYALL);\n    if (rootNode == DI_NODE_NIL)\n        return \"di_init() failed\";\n    di_walk_minor(rootNode, DDI_NT_DISPLAY, DI_WALK_CLDFIRST, gpus, (void*) walkDevTree);\n    di_fini(rootNode);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu_windows.c",
    "content": "#include \"gpu.h\"\n#include \"detection/gpu/gpu_driver_specific.h\"\n#include \"common/windows/unicode.h\"\n#include \"common/windows/registry.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/debug.h\"\n#include \"common/windows/nt.h\"\n\n#include <windows.h>\n#include <cfgmgr32.h>\n\n#define FF_EMPTY_GUID_STR L\"{00000000-0000-0000-0000-000000000000}\"\nenum { FF_GUID_STRLEN = sizeof(FF_EMPTY_GUID_STR) / sizeof(wchar_t) - 1 };\n\nwchar_t regDirectxKey[] = L\"SOFTWARE\\\\Microsoft\\\\DirectX\\\\\" FF_EMPTY_GUID_STR;\nconst uint32_t regDirectxKeyPrefixLength = (uint32_t) __builtin_strlen(\"SOFTWARE\\\\Microsoft\\\\DirectX\\\\\");\nwchar_t regDriverKey[] = L\"SYSTEM\\\\CurrentControlSet\\\\Control\\\\Class\\\\\" FF_EMPTY_GUID_STR L\"\\\\0000\";\nconst uint32_t regDriverKeyPrefixLength = (uint32_t) __builtin_strlen(\"SYSTEM\\\\CurrentControlSet\\\\Control\\\\Class\\\\\");\n\n#define GUID_DEVCLASS_DISPLAY_STRING L\"{4d36e968-e325-11ce-bfc1-08002be10318}\" // Found in <devguid.h>\n\nstatic inline void wrapRegCloseKey(HKEY* phKey)\n{\n    if(*phKey)\n        RegCloseKey(*phKey);\n}\n#define FF_HKEY_AUTO_DESTROY __attribute__((__cleanup__(wrapRegCloseKey)))\n\nconst char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* gpus)\n{\n    FF_DEBUG(\"Starting GPU detection\");\n\n    ULONG devIdListSize = 0;\n    if (CM_Get_Device_ID_List_SizeW(&devIdListSize, GUID_DEVCLASS_DISPLAY_STRING, CM_GETIDLIST_FILTER_CLASS | CM_GETIDLIST_FILTER_PRESENT) != CR_SUCCESS || devIdListSize <= 1)\n    {\n        FF_DEBUG(\"No display devices found, list size: %lu\", devIdListSize);\n        return \"No display devices found\";\n    }\n\n    FF_DEBUG(\"Found device ID list size: %lu\", devIdListSize);\n\n    FF_AUTO_FREE DEVINSTID_W devIdList = malloc(devIdListSize * sizeof(*devIdList));\n\n    if (CM_Get_Device_ID_ListW(GUID_DEVCLASS_DISPLAY_STRING, devIdList, devIdListSize, CM_GETIDLIST_FILTER_CLASS | CM_GETIDLIST_FILTER_PRESENT) != CR_SUCCESS)\n    {\n        FF_DEBUG(\"CM_Get_Device_ID_ListW failed\");\n        return \"CM_Get_Device_ID_ListW failed\";\n    }\n\n    FF_MAYBE_UNUSED int deviceCount = 0;\n    for (wchar_t* devId = devIdList; *devId; devId += wcslen(devId) + 1)\n    {\n        FF_DEBUG(\"Processing device ID: %ls\", devId);\n\n        DEVINST devInst = 0;\n\n        if (CM_Locate_DevNodeW(&devInst, devId, CM_LOCATE_DEVNODE_NORMAL) != CR_SUCCESS)\n        {\n            FF_DEBUG(\"Failed to get device instance ID or locate device node\");\n            continue;\n        }\n        FF_DEBUG(\"Device instance ID: %lu\", devInst);\n\n        for (wchar_t* p = devId; *p; p++)\n        {\n            if (*p >= L'a' && *p <= L'z')\n                *p -= L'a' - L'A';\n        }\n\n        FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus);\n        deviceCount++;\n        FF_DEBUG(\"Added GPU #%d to list\", deviceCount);\n\n        ffStrbufInit(&gpu->vendor);\n        ffStrbufInit(&gpu->name);\n        ffStrbufInit(&gpu->driver);\n        ffStrbufInit(&gpu->memoryType);\n        ffStrbufInitStatic(&gpu->platformApi, \"CM API\");\n        gpu->index = FF_GPU_INDEX_UNSET;\n        gpu->temperature = FF_GPU_TEMP_UNSET;\n        gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;\n        gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;\n        gpu->type = FF_GPU_TYPE_UNKNOWN;\n        gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;\n        gpu->deviceId = 0;\n        gpu->frequency = FF_GPU_FREQUENCY_UNSET;\n\n        unsigned vendorId = 0, deviceId = 0, subSystemId = 0, revId = 0;\n        if (swscanf(devId, L\"PCI\\\\VEN_%x&DEV_%x&SUBSYS_%x&REV_%x\", &vendorId, &deviceId, &subSystemId, &revId) == 4)\n        {\n            FF_DEBUG(\"Parsed PCI IDs - Vendor: 0x%x, Device: 0x%x, SubSystem: 0x%x, Rev: 0x%x\", vendorId, deviceId, subSystemId, revId);\n            ffStrbufSetStatic(&gpu->vendor, ffGPUGetVendorString(vendorId));\n        }\n        else\n        {\n            FF_DEBUG(\"Failed to parse PCI device information from instance ID\");\n        }\n\n        uint32_t pciBus = 0, pciAddr = 0, pciDev = 0, pciFunc = 0;\n\n        ULONG pciBufLen = sizeof(pciBus);\n        if (CM_Get_DevNode_Registry_PropertyW(devInst, CM_DRP_BUSNUMBER, NULL, &pciBus, &pciBufLen, 0) == CR_SUCCESS)\n        {\n            pciBufLen = sizeof(pciAddr);\n            if (CM_Get_DevNode_Registry_PropertyW(devInst, CM_DRP_ADDRESS, NULL, &pciAddr, &pciBufLen, 0) == CR_SUCCESS)\n            {\n                pciDev = (pciAddr >> 16) & 0xFFFF;\n                pciFunc = pciAddr & 0xFFFF;\n                gpu->deviceId = ffGPUPciAddr2Id(0, pciBus, pciDev, pciFunc);\n                pciAddr = 1; // Set to 1 to indicate that the device is a PCI device\n                FF_DEBUG(\"PCI location - Bus: %u, Device: %u, Function: %u, DeviceID: %llu\", pciBus, pciDev, pciFunc, gpu->deviceId);\n            }\n            else\n            {\n                FF_DEBUG(\"Failed to get PCI address\");\n            }\n        }\n        else\n        {\n            FF_DEBUG(\"Failed to get PCI bus number\");\n        }\n\n        uint64_t adapterLuid = 0;\n\n        FF_HKEY_AUTO_DESTROY HKEY hVideoIdKey = NULL;\n\n        wchar_t buffer[256];\n        ULONG bufferLen = 0;\n\n        FF_DEBUG(\"Get device description as device name\");\n        bufferLen = sizeof(buffer);\n        if (CM_Get_DevNode_Registry_PropertyW(devInst, CM_DRP_DEVICEDESC, NULL, buffer, &bufferLen, 0) == CR_SUCCESS)\n        {\n            ffStrbufSetWS(&gpu->name, buffer);\n            FF_DEBUG(\"Found device description: %s\", gpu->name.chars);\n        }\n        else\n        {\n            FF_DEBUG(\"Failed to get device description\");\n        }\n\n        if (wcsncmp(devId, L\"SWD\\\\\", 4) == 0 || wcsncmp(devId, L\"ROOT\\\\DISPLAY\\\\\", 13) == 0)\n        {\n            FF_DEBUG(\"Skipping virtual devices to avoid duplicates\");\n            continue;\n        }\n\n        if (CM_Open_DevNode_Key(devInst, KEY_QUERY_VALUE, 0, RegDisposition_OpenExisting, &hVideoIdKey, CM_REGISTRY_HARDWARE) == CR_SUCCESS)\n        {\n            FF_DEBUG(\"Opened device node registry key\");\n            bufferLen = sizeof(buffer);\n            if (RegGetValueW(hVideoIdKey, NULL, L\"VideoID\", RRF_RT_REG_SZ, NULL, buffer, &bufferLen) == ERROR_SUCCESS &&\n                bufferLen == (FF_GUID_STRLEN + 1) * sizeof(wchar_t))\n            {\n                FF_DEBUG(\"Found VideoID: %ls\", buffer);\n                wmemcpy(regDirectxKey + regDirectxKeyPrefixLength, buffer, FF_GUID_STRLEN);\n                FF_AUTO_CLOSE_FD HANDLE hDirectxKey = NULL;\n                if (ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, regDirectxKey, &hDirectxKey, NULL))\n                {\n                    FF_DEBUG(\"Opened DirectX registry key\");\n\n                    if (gpu->vendor.length == 0)\n                    {\n                        uint32_t vendorId = 0;\n                        if(ffRegReadUint(hDirectxKey, L\"VendorId\", &vendorId, NULL) && vendorId)\n                        {\n                            FF_DEBUG(\"Found vendor ID from DirectX registry: 0x%x\", vendorId);\n                            ffStrbufSetStatic(&gpu->vendor, ffGPUGetVendorString(vendorId));\n                        }\n                    }\n\n                    if (gpu->name.length == 0)\n                    {\n                        FF_DEBUG(\"Trying to get GPU name from DirectX registry\");\n                        if (ffRegReadStrbuf(hDirectxKey, L\"Description\", &gpu->name, NULL))\n                            FF_DEBUG(\"Found GPU description: %s\", gpu->name.chars);\n                    }\n\n                    if (ffRegReadUint64(hDirectxKey, L\"DedicatedVideoMemory\", &gpu->dedicated.total, NULL))\n                        FF_DEBUG(\"Found dedicated video memory: %llu bytes\", gpu->dedicated.total);\n\n                    if (ffRegReadUint64(hDirectxKey, L\"DedicatedSystemMemory\", &gpu->shared.total, NULL))\n                    {\n                        FF_DEBUG(\"Found dedicated system memory: %llu bytes\", gpu->shared.total);\n                        uint64_t sharedSystemMemory = 0;\n                        if (ffRegReadUint64(hDirectxKey, L\"SharedSystemMemory\", &sharedSystemMemory, NULL))\n                        {\n                            gpu->shared.total += sharedSystemMemory;\n                            FF_DEBUG(\"Added shared system memory: %llu bytes, total shared: %llu bytes\", sharedSystemMemory, gpu->shared.total);\n                        }\n                    }\n\n                    if (ffRegReadUint64(hDirectxKey, L\"AdapterLuid\", &adapterLuid, NULL))\n                    {\n                        FF_DEBUG(\"Found adapter LUID: %llu\", adapterLuid);\n                        if (!gpu->deviceId) gpu->deviceId = ffGPUGeneral2Id(adapterLuid);\n                    }\n\n                    uint32_t featureLevel = 0;\n                    if(ffRegReadUint(hDirectxKey, L\"MaxD3D12FeatureLevel\", &featureLevel, NULL) && featureLevel)\n                    {\n                        FF_DEBUG(\"Found D3D12 feature level: 0x%x\", featureLevel);\n                        ffStrbufSetF(&gpu->platformApi, \"Direct3D 12.%u\", (featureLevel & 0x0F00) >> 8);\n                    }\n                    else if(ffRegReadUint(hDirectxKey, L\"MaxD3D11FeatureLevel\", &featureLevel, NULL) && featureLevel)\n                    {\n                        FF_DEBUG(\"Found D3D11 feature level: 0x%x\", featureLevel);\n                        ffStrbufSetF(&gpu->platformApi, \"Direct3D 11.%u\", (featureLevel & 0x0F00) >> 8);\n                    }\n\n                    uint64_t driverVersion = 0;\n                    if(ffRegReadUint64(hDirectxKey, L\"DriverVersion\", &driverVersion, NULL) && driverVersion)\n                    {\n                        FF_DEBUG(\"Found driver version: %llu\", driverVersion);\n                        ffStrbufSetF(&gpu->driver, \"%u.%u.%u.%u\",\n                            (unsigned) (driverVersion >> 48) & 0xFFFF,\n                            (unsigned) (driverVersion >> 32) & 0xFFFF,\n                            (unsigned) (driverVersion >> 16) & 0xFFFF,\n                            (unsigned) (driverVersion >> 0) & 0xFFFF\n                        );\n                    }\n                }\n                else\n                {\n                    FF_DEBUG(\"Failed to open DirectX registry key\");\n                }\n            }\n            else\n            {\n                FF_DEBUG(\"Failed to get VideoID or invalid buffer length\");\n            }\n        }\n        else\n        {\n            FF_DEBUG(\"Failed to open device node registry key\");\n        }\n\n        if (gpu->vendor.length == 0 || gpu->name.length == 0 || gpu->driver.length == 0 || gpu->dedicated.total == FF_GPU_VMEM_SIZE_UNSET)\n        {\n            FF_DEBUG(\"Trying fallback registry method for vendor/name etc.\");\n            bufferLen = sizeof(buffer);\n            if (CM_Get_DevNode_Registry_PropertyW(devInst, CM_DRP_DRIVER, NULL, buffer, &bufferLen, 0) == CR_SUCCESS &&\n                bufferLen == (FF_GUID_STRLEN + strlen(\"\\\\0000\") + 1) * 2)\n            {\n                FF_DEBUG(\"Found driver GUID: %ls\", buffer);\n                wmemcpy(regDriverKey + regDriverKeyPrefixLength, buffer, FF_GUID_STRLEN + strlen(\"\\\\0000\"));\n                FF_AUTO_CLOSE_FD HANDLE hRegDriverKey = NULL;\n                if (ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, regDriverKey, &hRegDriverKey, NULL))\n                {\n                    FF_DEBUG(\"Opened driver registry key\");\n\n                    if (gpu->vendor.length == 0 && ffRegReadStrbuf(hRegDriverKey, L\"ProviderName\", &gpu->vendor, NULL))\n                    {\n                        FF_DEBUG(\"Found provider name: %s\", gpu->vendor.chars);\n                        if (ffStrbufContainS(&gpu->vendor, \"Intel\"))\n                            ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_INTEL);\n                        else if (ffStrbufContainS(&gpu->vendor, \"NVIDIA\"))\n                            ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_NVIDIA);\n                        else if (ffStrbufContainS(&gpu->vendor, \"AMD\") || ffStrbufContainS(&gpu->vendor, \"ATI\"))\n                            ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_AMD);\n                    }\n                    if (gpu->name.length == 0 && ffRegReadStrbuf(hRegDriverKey, L\"DriverDesc\", &gpu->name, NULL))\n                        FF_DEBUG(\"Found driver description: %s\", gpu->name.chars);\n                    if (gpu->driver.length == 0 && ffRegReadStrbuf(hRegDriverKey, L\"DriverVersion\", &gpu->driver, NULL))\n                        FF_DEBUG(\"Found driver version: %s\", gpu->driver.chars);\n                    if (gpu->dedicated.total == FF_GPU_VMEM_SIZE_UNSET)\n                    {\n                        if (!ffRegReadUint64(hRegDriverKey, L\"HardwareInformation.qwMemorySize\", &gpu->dedicated.total, NULL))\n                        {\n                            uint32_t memorySize = 0;\n                            if (ffRegReadUint(hRegDriverKey, L\"HardwareInformation.MemorySize\", &memorySize, NULL))\n                            {\n                                gpu->dedicated.total = memorySize;\n                                FF_DEBUG(\"Found memory size from hardware info: %u bytes\", memorySize);\n                            }\n                        }\n                        else\n                        {\n                            FF_DEBUG(\"Found qwMemorySize from hardware info: %llu bytes\", gpu->dedicated.total);\n                        }\n                    }\n                }\n                else\n                {\n                    FF_DEBUG(\"Failed to open driver registry key\");\n                }\n            }\n            else\n            {\n                FF_DEBUG(\"Failed to get driver GUID or invalid buffer length\");\n            }\n        }\n\n        __typeof__(&ffDetectNvidiaGpuInfo) detectFn;\n        const char* dllName;\n\n        if (options->driverSpecific && getDriverSpecificDetectionFn(gpu->vendor.chars, &detectFn, &dllName))\n        {\n            FF_DEBUG(\"Calling driver-specific detection function for vendor: %s, DLL: %s\", gpu->vendor.chars, dllName);\n            detectFn(\n                &(FFGpuDriverCondition) {\n                    .type = (deviceId > 0 ? FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID : 0)\n                            | (adapterLuid > 0 ? FF_GPU_DRIVER_CONDITION_TYPE_LUID : 0)\n                            | (pciAddr > 0 ? FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID : 0),\n                    .pciDeviceId = {\n                        .deviceId = deviceId,\n                        .vendorId = vendorId,\n                        .subSystemId = subSystemId,\n                        .revId = revId,\n                    },\n                    .pciBusId = {\n                        .domain = 0,\n                        .bus = pciBus,\n                        .device = pciDev,\n                        .func = pciFunc,\n                    },\n                    .luid = adapterLuid,\n                },\n                (FFGpuDriverResult){\n                    .index = &gpu->index,\n                    .temp = options->temp ? &gpu->temperature : NULL,\n                    .memory = options->driverSpecific ? &gpu->dedicated : NULL,\n                    .sharedMemory = options->driverSpecific ? &gpu->shared : NULL,\n                    .memoryType = options->driverSpecific ? &gpu->memoryType : NULL,\n                    .coreCount = options->driverSpecific ? (uint32_t*) &gpu->coreCount : NULL,\n                    .coreUsage = options->driverSpecific ? &gpu->coreUsage : NULL,\n                    .type = &gpu->type,\n                    .name = &gpu->name,\n                    .frequency = options->driverSpecific ? &gpu->frequency : NULL,\n                },\n                dllName\n            );\n            FF_DEBUG(\"Driver-specific detection completed\");\n        }\n        else if (options->driverSpecific)\n        {\n            FF_DEBUG(\"No driver-specific detection function found for vendor: %s\", gpu->vendor.chars);\n        }\n\n        if (gpu->type == FF_GPU_TYPE_UNKNOWN && adapterLuid > 0)\n        {\n            FF_DEBUG(\"Trying to determine GPU type using D3DKMT APIs\");\n            D3DKMT_OPENADAPTERFROMLUID openAdapterFromLuid = { .AdapterLuid = *(LUID*)&adapterLuid };\n            if (NT_SUCCESS(D3DKMTOpenAdapterFromLuid(&openAdapterFromLuid)))\n            {\n                FF_DEBUG(\"Successfully opened adapter from LUID\");\n\n                D3DKMT_ADAPTERTYPE adapterType = {};\n                D3DKMT_QUERYADAPTERINFO queryAdapterInfo = {\n                    .hAdapter = openAdapterFromLuid.hAdapter,\n                    .Type = KMTQAITYPE_ADAPTERTYPE, // Windows 8 and later\n                    .pPrivateDriverData = &adapterType,\n                    .PrivateDriverDataSize = sizeof(adapterType),\n                };\n                if (NT_SUCCESS(D3DKMTQueryAdapterInfo(&queryAdapterInfo)))\n                {\n                    FF_DEBUG(\"Queried adapter type - HybridDiscrete: %d, HybridIntegrated: %d\", adapterType.HybridDiscrete, adapterType.HybridIntegrated);\n                    if (adapterType.HybridDiscrete)\n                        gpu->type = FF_GPU_TYPE_DISCRETE;\n                    else if (adapterType.HybridIntegrated)\n                        gpu->type = FF_GPU_TYPE_INTEGRATED;\n                }\n                else\n                {\n                    FF_DEBUG(\"Failed to query adapter type\");\n                }\n\n                if (gpu->frequency == FF_GPU_FREQUENCY_UNSET && ffIsWindows11OrGreater())\n                {\n                    FF_DEBUG(\"Trying to get GPU frequency information\");\n                    for (ULONG nodeIdx = 0; ; nodeIdx++)\n                    {\n                        D3DKMT_NODEMETADATA nodeMetadata = {\n                            .NodeOrdinalAndAdapterIndex = (0 << 16) | nodeIdx,\n                        };\n                        queryAdapterInfo = (D3DKMT_QUERYADAPTERINFO) {\n                            .hAdapter = openAdapterFromLuid.hAdapter,\n                            .Type = KMTQAITYPE_NODEMETADATA, // Windows 10 and later\n                            .pPrivateDriverData = &nodeMetadata,\n                            .PrivateDriverDataSize = sizeof(nodeMetadata),\n                        };\n                        if (!NT_SUCCESS(D3DKMTQueryAdapterInfo(&queryAdapterInfo)))\n                        {\n                            FF_DEBUG(\"No more nodes to query (index %lu)\", nodeIdx);\n                            break;\n                        }\n                        if (nodeMetadata.NodeData.EngineType != DXGK_ENGINE_TYPE_3D)\n                        {\n                            FF_DEBUG(\"Skipping node %lu (not 3D engine)\", nodeIdx);\n                            continue;\n                        }\n\n                        D3DKMT_QUERYSTATISTICS queryStatistics = {\n                            .Type = D3DKMT_QUERYSTATISTICS_NODE2, // Windows 11 (22H2) and later\n                            .AdapterLuid = *(LUID*)&adapterLuid,\n                            .QueryNode2 = { .PhysicalAdapterIndex = 0, .NodeOrdinal = (UINT16) nodeIdx },\n                        };\n                        if (NT_SUCCESS(D3DKMTQueryStatistics(&queryStatistics)))\n                        {\n                            gpu->frequency = (uint32_t) (queryStatistics.QueryResult.NodeInformation.NodePerfData.MaxFrequency / 1000 / 1000);\n                            FF_DEBUG(\"Found GPU frequency: %u MHz\", gpu->frequency);\n                            break;\n                        }\n                        else\n                        {\n                            FF_DEBUG(\"Failed to query node statistics for node %lu\", nodeIdx);\n                        }\n                    }\n                }\n\n                D3DKMT_CLOSEADAPTER closeAdapter = { .hAdapter = openAdapterFromLuid.hAdapter };\n                (void) D3DKMTCloseAdapter(&closeAdapter);\n                openAdapterFromLuid.hAdapter = 0;\n                FF_DEBUG(\"Closed adapter handle\");\n            }\n            else\n            {\n                FF_DEBUG(\"Failed to open adapter from LUID\");\n            }\n\n            if (options->temp && gpu->temperature == FF_GPU_TEMP_UNSET && ffIsWindows10OrGreater())\n            {\n                FF_DEBUG(\"Trying to get GPU temperature\");\n                D3DKMT_QUERYSTATISTICS queryStatistics = {\n                    .Type = D3DKMT_QUERYSTATISTICS_PHYSICAL_ADAPTER, // Windows 10 (1803) and later\n                    .AdapterLuid = *(LUID*)&adapterLuid,\n                    .QueryPhysAdapter = { .PhysicalAdapterIndex = 0 },\n                };\n                if (NT_SUCCESS(D3DKMTQueryStatistics(&queryStatistics)) &&\n                    queryStatistics.QueryResult.PhysAdapterInformation.AdapterPerfData.Temperature != 0)\n                {\n                    gpu->temperature = queryStatistics.QueryResult.PhysAdapterInformation.AdapterPerfData.Temperature / 10.0;\n                    FF_DEBUG(\"Found GPU temperature: %.1f°C\", gpu->temperature);\n                }\n                else\n                {\n                    FF_DEBUG(\"Failed to get GPU temperature or temperature is 0\");\n                }\n            }\n        }\n\n        if (gpu->type == FF_GPU_TYPE_UNKNOWN)\n        {\n            FF_DEBUG(\"Using fallback GPU type detection\");\n            if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_INTEL)\n            {\n                gpu->type = gpu->deviceId == ffGPUPciAddr2Id(0, 0, 2, 0) ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;\n                FF_DEBUG(\"Intel GPU type determined: %s\", gpu->type == FF_GPU_TYPE_INTEGRATED ? \"Integrated\" : \"Discrete\");\n            }\n            else if (gpu->dedicated.total != FF_GPU_VMEM_SIZE_UNSET)\n            {\n                gpu->type = gpu->dedicated.total >= 1024 * 1024 * 1024 ? FF_GPU_TYPE_DISCRETE : FF_GPU_TYPE_INTEGRATED;\n                FF_DEBUG(\"GPU type determined by memory size (%llu bytes): %s\", gpu->dedicated.total, gpu->type == FF_GPU_TYPE_DISCRETE ? \"Discrete\" : \"Integrated\");\n            }\n            else\n            {\n                FF_DEBUG(\"Unable to determine GPU type\");\n            }\n        }\n\n        FF_DEBUG(\"Completed processing GPU #%d - Vendor: %s, Name: %s, Type: %d\", deviceCount, gpu->vendor.chars, gpu->name.chars, gpu->type);\n    }\n\n    FF_DEBUG(\"GPU detection completed, found %d devices\", deviceCount);\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/gpu/gpu_wsl.cpp",
    "content": "#ifdef FF_HAVE_DIRECTX_HEADERS\n\n#define INITGUID\nextern \"C\" {\n#include \"common/library.h\"\n#include \"detection/gpu/gpu.h\"\n#include \"detection/gpu/gpu_driver_specific.h\"\n}\n#include \"common/windows/util.hpp\"\n\n#include <wsl/winadapter.h>\n#include <directx/dxcore.h>\n#include <directx/d3d12.h>\n#include <dxguids/dxguids.h>\n#include <utility>\n#include <cinttypes>\n\n#pragma GCC diagnostic ignored \"-Wmissing-field-initializers\"\n\nextern \"C\"\nconst char* ffGPUDetectByDirectX(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* gpus)\n{\n    FF_LIBRARY_LOAD_MESSAGE(libdxcore, \"/usr/lib/wsl/lib/libdxcore\" FF_LIBRARY_EXTENSION, 4)\n    // DXCoreCreateAdapterFactory is a reloaded function, so we can't use FF_LIBRARY_LOAD_SYMBOL_MESSAGE here\n    typedef HRESULT (*DXCoreCreateAdapterFactory_t)(REFIID riid, void** ppvFactory);\n\n#ifndef FF_DISABLE_DLOPEN\n    auto ffDXCoreCreateAdapterFactory = (DXCoreCreateAdapterFactory_t) dlsym(libdxcore, \"DXCoreCreateAdapterFactory\");\n    if(ffDXCoreCreateAdapterFactory == nullptr) return \"dlsym DXCoreCreateAdapterFactory failed\";\n#else\n    auto ffDXCoreCreateAdapterFactory = (DXCoreCreateAdapterFactory_t) DXCoreCreateAdapterFactory;\n#endif\n\n    IDXCoreAdapterFactory *factory = nullptr;\n\n    if (FAILED(ffDXCoreCreateAdapterFactory(IID_PPV_ARGS(&factory))))\n        return \"DXCoreCreateAdapterFactory(IID_PPV_ARGS(&factory)) failed\";\n    on_scope_exit destroyFactory([&] { factory->Release(); });\n\n    IDXCoreAdapterList *list = NULL;\n    if (FAILED(factory->CreateAdapterList(1, &DXCORE_ADAPTER_ATTRIBUTE_D3D11_GRAPHICS, &list)))\n        return \"factory->CreateAdapterList(1, &DXCORE_ADAPTER_ATTRIBUTE_D3D11_GRAPHICS, &list) failed\";\n    on_scope_exit destroyList([&] { list->Release(); });\n\n    for (uint32_t i = 0, count = list->GetAdapterCount(); i < count; i++)\n    {\n        IDXCoreAdapter *adapter = nullptr;\n        if (FAILED(list->GetAdapter(i, &adapter)))\n            continue;\n        on_scope_exit destroyAdapter([&] { adapter->Release(); });\n\n        // https://learn.microsoft.com/en-us/windows/win32/api/dxcore_interface/ne-dxcore_interface-dxcoreadapterproperty\n        {\n            bool value = 0;\n            if (SUCCEEDED(adapter->GetProperty(DXCoreAdapterProperty::IsHardware, sizeof(value), &value)) && !value)\n                continue;\n        }\n\n        char desc[256];\n        if (FAILED(adapter->GetProperty(DXCoreAdapterProperty::DriverDescription, sizeof(desc), desc)))\n            continue;\n\n        FFGPUResult* gpu = (FFGPUResult*) ffListAdd(gpus);\n        ffStrbufInitS(&gpu->name, desc);\n        ffStrbufInit(&gpu->memoryType);\n        gpu->index = FF_GPU_INDEX_UNSET;\n        gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;\n        gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;\n        gpu->temperature = FF_GPU_TEMP_UNSET;\n        gpu->frequency = FF_GPU_FREQUENCY_UNSET;\n        gpu->deviceId = 0;\n        ffStrbufInitStatic(&gpu->platformApi, \"DXCore\");\n\n        LUID luid;\n        if (SUCCEEDED(adapter->GetProperty(DXCoreAdapterProperty::InstanceLuid, sizeof(luid), &luid)))\n        {\n            static_assert(sizeof(luid) == sizeof(uint64_t), \"LUID size mismatch\");\n            gpu->deviceId = ffGPUGeneral2Id((uint64_t) luid.HighPart << 32 | (uint64_t) luid.LowPart);\n        }\n\n        ffStrbufInit(&gpu->driver);\n        uint64_t value = 0;\n        if (SUCCEEDED(adapter->GetProperty(DXCoreAdapterProperty::DriverVersion, sizeof(value), &value)))\n        {\n            ffStrbufSetF(&gpu->driver, \"%\" PRIu64 \".%\" PRIu64 \".%\" PRIu64 \".%\" PRIu64,\n                (value >> 48) & 0xFFFF,\n                (value >> 32) & 0xFFFF,\n                (value >> 16) & 0xFFFF,\n                (value >>  0) & 0xFFFF);\n        }\n\n        gpu->dedicated.used = gpu->shared.used = gpu->dedicated.total = gpu->shared.total = FF_GPU_VMEM_SIZE_UNSET;\n        if (SUCCEEDED(adapter->GetProperty(DXCoreAdapterProperty::DedicatedAdapterMemory, sizeof(value), &value)))\n            gpu->dedicated.total = value;\n        if (SUCCEEDED(adapter->GetProperty(DXCoreAdapterProperty::SharedSystemMemory, sizeof(value), &value)))\n            gpu->shared.total = value;\n\n        gpu->type = FF_GPU_TYPE_UNKNOWN;\n        bool integrated;\n        if (SUCCEEDED(adapter->GetProperty(DXCoreAdapterProperty::IsIntegrated, sizeof(integrated), &integrated)))\n            gpu->type = integrated ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;\n\n        ffStrbufInit(&gpu->vendor);\n        DXCoreHardwareID hardwareId;\n        if (SUCCEEDED(adapter->GetProperty(DXCoreAdapterProperty::HardwareID, sizeof(hardwareId), &hardwareId)))\n        {\n            const char* vendorStr = ffGPUGetVendorString((unsigned) hardwareId.vendorID);\n            ffStrbufSetStatic(&gpu->vendor, vendorStr);\n\n            if (vendorStr == FF_GPU_VENDOR_NAME_NVIDIA && (options->driverSpecific || options->temp))\n            {\n                FFGpuDriverCondition cond = {\n                    .type = FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID,\n                    .pciDeviceId = {\n                        .deviceId = hardwareId.deviceID,\n                        .vendorId = hardwareId.vendorID,\n                        .subSystemId = hardwareId.subSysID,\n                        .revId = hardwareId.revision,\n                    },\n                };\n                ffDetectNvidiaGpuInfo(&cond, (FFGpuDriverResult){\n                    .index = &gpu->index,\n                    .temp = options->temp ? &gpu->temperature : NULL,\n                    .memory = options->driverSpecific ? &gpu->dedicated : NULL,\n                    .coreCount = options->driverSpecific ? (uint32_t*) &gpu->coreCount : NULL,\n                    .coreUsage = options->driverSpecific ? &gpu->coreUsage : NULL,\n                    .type = &gpu->type,\n                    .frequency = options->driverSpecific ? &gpu->frequency : NULL,\n                    .name = options->driverSpecific ? &gpu->name : NULL,\n                }, \"/usr/lib/wsl/lib/libnvidia-ml.so\");\n            }\n        }\n    }\n\n    return NULL;\n}\n\n#endif\n"
  },
  {
    "path": "src/detection/gpu/igcl.h",
    "content": "#pragma once\n\n// DISCLAIMER:\n// THIS FILE IS CREATED FROM SCRATCH, BY READING THE OFFICIAL IGCL API\n// DOCUMENTATION REFERENCED BELOW, IN ORDER TO MAKE FASTFETCH MIT COMPLIANT.\n\n#include <stdint.h>\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv412ctl_result_t\ntypedef enum ctl_result_t\n{\n    CTL_RESULT_SUCCESS = 0,\n} ctl_result_t;\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv420ctl_application_id_t\ntypedef struct ctl_application_id_t\n{\n    uint32_t Data1;\n    uint16_t Data2;\n    uint16_t Data3;\n    uint8_t Data4[8];\n} ctl_application_id_t;\n\n#define CTL_IMPL_VERSION (( 1 /*major*/ << 16 )|( 1 /*minor*/ & 0x0000ffff))\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv415ctl_init_flag_t\ntypedef enum ctl_init_flag_t\n{\n    CTL_INIT_FLAG_USE_LEVEL_ZERO = 1,\n    CTL_INIT_FLAG_MAX\n} ctl_init_flag_t;\n\ntypedef uint32_t ctl_version_info_t;\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv415ctl_init_args_t\ntypedef struct ctl_init_args_t\n{\n    uint32_t Size;\n    uint8_t Version;\n    ctl_version_info_t AppVersion;\n    ctl_init_flag_t flags;\n    ctl_version_info_t SupportedVersion;\n    ctl_application_id_t ApplicationUID;\n} ctl_init_args_t;\n\ntypedef struct ctl_api_handle_t* ctl_api_handle_t;\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv47ctlInitP15ctl_init_args_tP16ctl_api_handle_t\nextern ctl_result_t ctlInit(ctl_init_args_t *pInitDesc, ctl_api_handle_t *phAPIHandle);\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#ctlclose\nextern ctl_result_t ctlClose(ctl_api_handle_t hAPIHandle);\n\ntypedef struct ctl_device_adapter_handle_t* ctl_device_adapter_handle_t;\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv419ctlEnumerateDevices16ctl_api_handle_tP8uint32_tP27ctl_device_adapter_handle_t\nextern ctl_result_t ctlEnumerateDevices(ctl_api_handle_t hAPIHandle, uint32_t *pCount, ctl_device_adapter_handle_t* phDevices);\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv417ctl_device_type_t\ntypedef enum ctl_device_type_t\n{\n    CTL_DEVICE_TYPE_GRAPHICS = 1,\n    CTL_DEVICE_TYPE_SYSTEM = 2,\n    CTL_DEVICE_TYPE_MAX\n} ctl_device_type_t;\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv422ctl_firmware_version_t\ntypedef struct ctl_firmware_version_t\n{\n    uint64_t major_version;\n    uint64_t minor_version;\n    uint64_t build_number;\n} ctl_firmware_version_t;\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv417ctl_adapter_bdf_t\ntypedef struct ctl_adapter_bdf_t\n{\n    uint8_t bus;\n    uint8_t device;\n    uint8_t function;\n} ctl_adapter_bdf_t;\n\n#define IGCL_CTL_MAX_DEVICE_NAME_LEN 100\n#define IGCL_CTL_MAX_RESERVED_SIZE 112\n\ntypedef enum ctl_adapter_properties_flag_t\n{\n    CTL_ADAPTER_PROPERTIES_FLAG_INTEGRATED = 1,\n} ctl_adapter_properties_flag_t;\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv431ctl_device_adapter_properties_t\ntypedef struct ctl_device_adapter_properties_t\n{\n    uint32_t Size;\n    uint8_t Version;\n    void* pDeviceID;\n    uint32_t device_id_size;\n    ctl_device_type_t device_type;\n    uint32_t /*ctl_supported_functions_flags_t*/ supported_subfunction_flags;\n    uint64_t driver_version;\n    ctl_firmware_version_t firmware_version;\n    uint32_t pci_vendor_id;\n    uint32_t pci_device_id;\n    uint32_t rev_id;\n    uint32_t num_eus_per_sub_slice;\n    uint32_t num_sub_slices_per_slice;\n    uint32_t num_slices;\n    char name[IGCL_CTL_MAX_DEVICE_NAME_LEN];\n    ctl_adapter_properties_flag_t graphics_adapter_properties;\n    uint32_t Frequency;\n    uint16_t pci_subsys_id;\n    uint16_t pci_subsys_vendor_id;\n    ctl_adapter_bdf_t adapter_bdf;\n    char reserved[IGCL_CTL_MAX_RESERVED_SIZE];\n} ctl_device_adapter_properties_t;\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv422ctlGetDeviceProperties27ctl_device_adapter_handle_tP31ctl_device_adapter_properties_t\nextern ctl_result_t ctlGetDeviceProperties(ctl_device_adapter_handle_t hDAhandle, ctl_device_adapter_properties_t* pProperties);\n\ntypedef struct ctl_temp_handle_t* ctl_temp_handle_t;\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#ctlenumtemperaturesensors\nextern ctl_result_t ctlEnumTemperatureSensors(ctl_device_adapter_handle_t hDAhandle, uint32_t* pCount, ctl_temp_handle_t* phTemperature);\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv427ctlTemperatureGetProperties17ctl_temp_handle_tP21ctl_temp_properties_t\n\ntypedef enum ctl_temp_sensors_t\n{\n    CTL_TEMP_SENSORS_GLOBAL = 0,\n    CTL_TEMP_SENSORS_GPU = 1,\n    CTL_TEMP_SENSORS_MEMORY = 2,\n    CTL_TEMP_SENSORS_GLOBAL_MIN = 3,\n    CTL_TEMP_SENSORS_GPU_MIN = 4,\n    CTL_TEMP_SENSORS_MEMORY_MIN = 5,\n    CTL_TEMP_SENSORS_MAX\n} ctl_temp_sensors_t;\n\ntypedef struct _ctl_temp_properties_t\n{\n    uint32_t Size;\n    uint8_t Version;\n    ctl_temp_sensors_t type;\n    double maxTemperature;\n} ctl_temp_properties_t;\n\nextern ctl_result_t ctlTemperatureGetProperties(ctl_temp_handle_t hTemperature, ctl_temp_properties_t* pTemperature);\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv420ctlEnumMemoryModules27ctl_device_adapter_handle_tP8uint32_tP16ctl_mem_handle_t\n\ntypedef struct ctl_mem_handle_t* ctl_mem_handle_t;\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv420ctlEnumMemoryModules27ctl_device_adapter_handle_tP8uint32_tP16ctl_mem_handle_t\nextern ctl_result_t ctlEnumMemoryModules(ctl_device_adapter_handle_t hDAhandle, uint32_t *pCount, ctl_mem_handle_t* phMemory);\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv415ctl_mem_state_t\ntypedef struct ctl_mem_state_t\n{\n    uint32_t Size;\n    uint8_t Version;\n    uint64_t free;\n    uint64_t size;\n} ctl_mem_state_t;\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv417ctlMemoryGetState16ctl_mem_handle_tP15ctl_mem_state_t\nextern ctl_result_t ctlMemoryGetState(ctl_mem_handle_t hMemory, ctl_mem_state_t *pState);\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv414ctl_mem_type_t\ntypedef enum ctl_mem_type_t\n{\n    CTL_MEM_TYPE_HBM = 0,\n    CTL_MEM_TYPE_DDR = 1,\n    CTL_MEM_TYPE_DDR3 = 2,\n    CTL_MEM_TYPE_DDR4 = 3,\n    CTL_MEM_TYPE_DDR5 = 4,\n    CTL_MEM_TYPE_LPDDR = 5,\n    CTL_MEM_TYPE_LPDDR3 = 6,\n    CTL_MEM_TYPE_LPDDR4 = 7,\n    CTL_MEM_TYPE_LPDDR5 = 8,\n    CTL_MEM_TYPE_GDDR4 = 9,\n    CTL_MEM_TYPE_GDDR5 = 10,\n    CTL_MEM_TYPE_GDDR5X = 11,\n    CTL_MEM_TYPE_GDDR6 = 12,\n    CTL_MEM_TYPE_GDDR6X = 13,\n    CTL_MEM_TYPE_GDDR7 = 14,\n    CTL_MEM_TYPE_MAX\n} ctl_mem_type_t;\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv413ctl_mem_loc_t\ntypedef enum ctl_mem_loc_t\n{\n    CTL_MEM_LOC_SYSTEM = 0,\n    CTL_MEM_LOC_DEVICE = 1,\n    CTL_MEM_LOC_MAX\n} ctl_mem_loc_t;\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv420ctl_mem_properties_t\ntypedef struct ctl_mem_properties_t\n{\n    uint32_t Size;\n    uint8_t Version;\n    ctl_mem_type_t type;\n    ctl_mem_loc_t location;\n    uint64_t physicalSize;\n    int32_t busWidth;\n    int32_t numChannels;\n} ctl_mem_properties_t;\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv422ctlMemoryGetProperties16ctl_mem_handle_tP20ctl_mem_properties_t\nextern ctl_result_t ctlMemoryGetProperties(ctl_mem_handle_t hMemory, ctl_mem_properties_t *pProperties);\n\ntypedef struct ctl_freq_handle_t* ctl_freq_handle_t;\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#ctlenumfrequencydomains\nextern ctl_result_t ctlEnumFrequencyDomains(ctl_device_adapter_handle_t hDAhandle, uint32_t* pCount, ctl_freq_handle_t* phFrequency);\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv417ctl_freq_domain_t\ntypedef enum ctl_freq_domain_t\n{\n    CTL_FREQ_DOMAIN_GPU = 0,\n    CTL_FREQ_DOMAIN_MEMORY = 1,\n    CTL_FREQ_DOMAIN_MAX\n} ctl_freq_domain_t;\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv421ctl_freq_properties_t\ntypedef struct ctl_freq_properties_t\n{\n    uint32_t Size;\n    uint8_t Version;\n    ctl_freq_domain_t type;\n    bool canControl;\n    double min;\n    double max;\n} ctl_freq_properties_t;\n\n// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv425ctlFrequencyGetProperties17ctl_freq_handle_tP21ctl_freq_properties_t\nextern ctl_result_t ctlFrequencyGetProperties(ctl_freq_handle_t hFrequency, ctl_freq_properties_t* pProperties);\n"
  },
  {
    "path": "src/detection/gpu/intel_drm.h",
    "content": "#pragma once\n\n/* SPDX-License-Identifier: MIT */\n#include <drm.h>\n\n// xe_drm.h\n\n/*\n * Copyright © 2023 Intel Corporation\n */\n\n#define DRM_XE_DEVICE_QUERY\t\t0x00\n\n#define DRM_IOCTL_XE_DEVICE_QUERY\t\tDRM_IOWR(DRM_COMMAND_BASE + DRM_XE_DEVICE_QUERY, struct drm_xe_device_query)\n\nenum drm_xe_memory_class {\n\tDRM_XE_MEM_REGION_CLASS_SYSMEM = 0,\n\tDRM_XE_MEM_REGION_CLASS_VRAM\n};\n\nstruct drm_xe_mem_region {\n\t__u16 mem_class;\n\t__u16 instance;\n\t__u32 min_page_size;\n\t__u64 total_size;\n\t__u64 used;\n\t__u64 cpu_visible_size;\n\t__u64 cpu_visible_used;\n\t__u64 reserved[6];\n};\n\nstruct drm_xe_query_mem_regions {\n\t__u32 num_mem_regions;\n\t__u32 pad;\n\tstruct drm_xe_mem_region mem_regions[];\n};\n\nstruct drm_xe_query_topology_mask {\n\t__u16 gt_id;\n\n#define DRM_XE_TOPO_DSS_GEOMETRY\t1\n#define DRM_XE_TOPO_DSS_COMPUTE\t\t2\n#define DRM_XE_TOPO_EU_PER_DSS\t\t4\n\t__u16 type;\n\t__u32 num_bytes;\n\t__u8 mask[];\n};\n\nstruct drm_xe_device_query {\n\t__u64 extensions;\n\n#define DRM_XE_DEVICE_QUERY_MEM_REGIONS\t\t1\n#define DRM_XE_DEVICE_QUERY_GT_TOPOLOGY\t\t5\n\t__u32 query;\n\t__u32 size;\n\t__u64 data;\n\t__u64 reserved[2];\n};\n\n// i915_drm.h\n\n/*\n * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas.\n * All Rights Reserved.\n */\n\n#define DRM_IOCTL_I915_GETPARAM         DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_GETPARAM, drm_i915_getparam_t)\n\nstruct drm_i915_getparam {\n\t__s32 param;\n\tint *value;\n};\ntypedef struct drm_i915_getparam drm_i915_getparam_t;\n\n#define DRM_I915_GETPARAM\t0x06\n#define DRM_I915_QUERY\t\t\t0x39\n#define DRM_I915_QUERY_MEMORY_REGIONS\t\t4\n#define DRM_IOCTL_I915_QUERY\t\t\tDRM_IOWR(DRM_COMMAND_BASE + DRM_I915_QUERY, struct drm_i915_query)\n#define I915_PARAM_EU_TOTAL\t\t 34\n\nstruct drm_i915_query_item {\n\t__u64 query_id;\n#define DRM_I915_QUERY_MEMORY_REGIONS\t\t4\n\n\t__s32 length;\n\t__u32 flags;\n\t__u64 data_ptr;\n};\n\nstruct drm_i915_query {\n\t__u32 num_items;\n\t__u32 flags;\n\t__u64 items_ptr;\n};\n\nenum drm_i915_gem_memory_class {\n\tI915_MEMORY_CLASS_SYSTEM = 0,\n\tI915_MEMORY_CLASS_DEVICE,\n};\n\nstruct drm_i915_gem_memory_class_instance {\n\t__u16 memory_class;\n\t__u16 memory_instance;\n};\n\nstruct drm_i915_memory_region_info {\n\tstruct drm_i915_gem_memory_class_instance region;\n\t__u32 rsvd0;\n\t__u64 probed_size;\n\t__u64 unallocated_size;\n\n\tunion {\n\t\t__u64 rsvd1[8];\n\t\tstruct {\n\t\t\t__u64 probed_cpu_visible_size;\n\t\t\t__u64 unallocated_cpu_visible_size;\n\t\t};\n\t};\n};\n\nstruct drm_i915_query_memory_regions {\n\t__u32 num_regions;\n\t__u32 rsvd[3];\n\tstruct drm_i915_memory_region_info regions[];\n};\n"
  },
  {
    "path": "src/detection/gpu/mtml.h",
    "content": "#pragma once\n\n// DISCLAIMER:\n// THIS FILE IS CREATED FROM SCRATCH, BY READING THE OFFICIAL MTML API\n// DOCUMENTATION REFERENCED BELOW, IN ORDER TO MAKE FASTFETCH MIT COMPLIANT.\n\n#define MTML_DEVICE_PCI_SBDF_BUFFER_SIZE 32\n#define MTML_DEVICE_NAME_BUFFER_SIZE 32\n\n/**\n * Return values for MTML API calls.\n */\ntypedef enum\n{\n    MTML_SUCCESS = 0,\n} MtmlReturn;\n\n/**\n * The brand of the device.\n */\ntypedef enum\n{\n    MTML_BRAND_MTT = 0, //!< MTT series.\n} MtmlBrandType;\n\ntypedef struct MtmlLibrary MtmlLibrary;\ntypedef struct MtmlSystem MtmlSystem;\ntypedef struct MtmlDevice MtmlDevice;\ntypedef struct MtmlGpu MtmlGpu;\ntypedef struct MtmlMemory MtmlMemory;\n\n/**\n * PCI information about a device.\n */\ntypedef struct\n{\n    char sbdf[MTML_DEVICE_PCI_SBDF_BUFFER_SIZE]; //!< The tuple segment:bus:device.function PCI identifier (&amp; NULL terminator).\n    unsigned int segment;                        //!< The PCI segment group(domain) on which the device's bus resides, 0 to 0xffffffff.\n    unsigned int bus;                            //!< The bus on which the device resides, 0 to 0xff.\n    unsigned int device;                         //!< The device ID on the bus, 0 to 31.\n    unsigned int pciDeviceId;                    //!< The combined 16-bit device ID and 16-bit vendor ID.\n    unsigned int pciSubsystemId;                 //!< The 32-bit sub system device ID.\n    unsigned int busWidth;                       //!< @deprecated This value set to zero.\n    float pciMaxSpeed;                           //!< The maximum link speed (transfer rate per lane) of the device. The unit is GT/s.\n    float pciCurSpeed;                           //!< The current link speed (transfer rate per lane) of the device. The unit is GT/s.\n    unsigned int pciMaxWidth;                    //!< The maximum link width of the device.\n    unsigned int pciCurWidth;                    //!< The current link width of the device.\n    unsigned int pciMaxGen;                      //!< The maximum supported generation of the device.\n    unsigned int pciCurGen;                      //!< The current generation of the device.\n    int rsvd[6];                                 //!< Reserved for future extension.\n} MtmlPciInfo;\n\n// Retrieves the number of cores of a device.\nMtmlReturn mtmlDeviceCountGpuCores(const MtmlDevice* device, unsigned int* numCores);\n// Retrieves the brand of a device.\nMtmlReturn mtmlDeviceGetBrand(const MtmlDevice *dev, MtmlBrandType *type);\n// Retrieves the index associated with the specified device.\nMtmlReturn mtmlDeviceGetIndex(const MtmlDevice *dev, unsigned int *index);\n// Retrieves the name of a device.\nMtmlReturn mtmlDeviceGetName(const MtmlDevice *dev, char *name, unsigned int length);\n// Retrieves the PCI attributes of a device.\nMtmlReturn mtmlDeviceGetPciInfo(const MtmlDevice *dev, MtmlPciInfo *pci);\n/**\n * Retrieves the UUID of a specified device. The UUID is a hexadecimal string in the\n * form of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, where each 'x' is an ASCII character that represents a hexadecimal\n * digit. The UUID is globally unique for every single device thus can be used to identify different devices\n * physically.\n */\nMtmlReturn mtmlDeviceGetUUID(const MtmlDevice *dev, char *uuid, unsigned int length);\n// Initializes a GPU opaque object to represent a specific graphic core on the target device that is designated by its index.\nMtmlReturn mtmlDeviceInitGpu(const MtmlDevice *dev, MtmlGpu **gpu);\n// Initializes a memory opaque object to represent the memory on the target device.\nMtmlReturn mtmlDeviceInitMemory(const MtmlDevice *dev, MtmlMemory **mem);\n\n// Retrieves the maximum supported clock speed for the device's graphic core.\nMtmlReturn mtmlGpuGetMaxClock(const MtmlGpu *gpu, unsigned int *clockMhz);\n// Retrieves the current temperature readings for the device's graphic core, in degrees Celsius.\nMtmlReturn mtmlGpuGetTemperature(const MtmlGpu *gpu, unsigned int *temp);\n// Retrieves the current utilization rate for the device's graphic core.\nMtmlReturn mtmlGpuGetUtilization(const MtmlGpu *gpu, unsigned int *utilization);\n\n// Retrieves the number of devices that can be accessed by the library opaque object.\nMtmlReturn mtmlLibraryCountDevice(const MtmlLibrary *lib, unsigned int *count);\n/**\n * Initializes a device opaque object to represent a device that is designated by its index.\n * The index ranges from (0) to (deviceCount - 1), where deviceCount is retrieved from \\ref mtmlLibraryCountDevice().\n */\nMtmlReturn mtmlLibraryInit(MtmlLibrary **lib);\n/**\n * Initializes a device opaque object to represent a device that is designated by its index.\n * The index ranges from (0) to (deviceCount - 1), where deviceCount is retrieved from \\ref mtmlLibraryCountDevice().\n */\nMtmlReturn mtmlLibraryInitDeviceByIndex(const MtmlLibrary *lib, unsigned int index, MtmlDevice **dev);\n/**\n * Initializes a device opaque object to represent a device that is designated by its PCI Sbdf.\n * The PCI Sbdf format like 00000000:3a:00.0 refer to \\ref MtmlPciInfo::sbdf.\n */\nMtmlReturn mtmlLibraryInitDeviceByPciSbdf(const MtmlLibrary *lib, const char *pciSbdf, MtmlDevice **dev);\n// Initializes a MtmlSystem opaque pointer that is bound to a library opaque object.\nMtmlReturn mtmlLibraryInitSystem(const MtmlLibrary *lib, MtmlSystem **sys);\n/**\n * Shuts down the library opaque object that is previously initialized by \\ref mtmlLibraryInit() and releases its resources.\n * The \\a lib pointer cannot be used anymore after this function returns.\n */\nMtmlReturn mtmlLibraryShutDown(MtmlLibrary *lib);\n\n// Retrieves the amount of total memory available on the device, in bytes.\nMtmlReturn mtmlMemoryGetTotal(const MtmlMemory *mem, unsigned long long *total);\n// Retrieves the amount of used memory on the device, in bytes.\nMtmlReturn mtmlMemoryGetUsed(const MtmlMemory *mem, unsigned long long *used);\n// Retrieves the current memory utilization rate for the device.\nMtmlReturn mtmlMemoryGetUtilization(const MtmlMemory *mem, unsigned int *utilization);\n"
  },
  {
    "path": "src/detection/gpu/nvapi.h",
    "content": "// References:\n// https://github.com/NVIDIA/nvapi (MIT License)\n// https://github.com/deathcamp/NVOC/blob/master/nvoc.c (Public Domain)\n\ntypedef enum NvApiGPUMemoryType\n{\n    NVAPI_GPU_MEMORY_TYPE_UNKNOWN = 0,\n    NVAPI_GPU_MEMORY_TYPE_SDRAM,\n    NVAPI_GPU_MEMORY_TYPE_DDR1,\n    NVAPI_GPU_MEMORY_TYPE_DDR2,\n    NVAPI_GPU_MEMORY_TYPE_GDDR2,\n    NVAPI_GPU_MEMORY_TYPE_GDDR3,\n    NVAPI_GPU_MEMORY_TYPE_GDDR4,\n    NVAPI_GPU_MEMORY_TYPE_DDR3,\n    NVAPI_GPU_MEMORY_TYPE_GDDR5,\n    NVAPI_GPU_MEMORY_TYPE_LPDDR2,\n    NVAPI_GPU_MEMORY_TYPE_GDDR5X,\n    NVAPI_GPU_MEMORY_TYPE_LPDDR3,\n    NVAPI_GPU_MEMORY_TYPE_LPDDR4,\n    NVAPI_GPU_MEMORY_TYPE_LPDDR5,\n    NVAPI_GPU_MEMORY_TYPE_GDDR6,\n    NVAPI_GPU_MEMORY_TYPE_GDDR6X,\n    NVAPI_GPU_MEMORY_TYPE_GDDR7,\n} NvApiGPUMemoryType;\n\ntypedef enum\n{\n    NV_SYSTEM_TYPE_GPU_UNKNOWN     = 0,\n    NV_SYSTEM_TYPE_IGPU            = 1, // Integrated\n    NV_SYSTEM_TYPE_DGPU            = 2, // Discrete\n} NvApiGPUType;\n\ntypedef int NvAPI_Status; // 0 = success; < 0 = error\ntypedef struct NvPhysicalGpuHandle* NvPhysicalGpuHandle;\n\ntypedef enum\n{\n    NVAPI_INTERFACE_OFFSET_INITIALIZE = 0x0150E828,\n    NVAPI_INTERFACE_OFFSET_UNLOAD = 0xD22BDD7E,\n    NVAPI_INTERFACE_OFFSET_ENUM_PHYSICAL_GPUS = 0xE5AC921F,\n    NVAPI_INTERFACE_OFFSET_GPU_GET_RAM_TYPE = 0x57F7CAAC,\n    NVAPI_INTERFACE_OFFSET_GPU_GET_GPU_TYPE = 0xC33BAEB1,\n\n    NVAPI_INTERFACE_OFFSET_FORCE_UINT32 = 0xFFFFFFFF\n} NvApiInterfaceOffsets;\n\nextern void* nvapi_QueryInterface(NvApiInterfaceOffsets offset);\n\nextern NvAPI_Status nvapi_Initialize(void);\nextern NvAPI_Status nvapi_Unload(void);\nextern NvAPI_Status nvapi_EnumPhysicalGPUs(NvPhysicalGpuHandle* handles, int* count);\nextern NvAPI_Status nvapi_GPU_GetRamType(NvPhysicalGpuHandle handle, NvApiGPUMemoryType* memtype);\nextern NvAPI_Status nvapi_GPU_GetGPUType(NvPhysicalGpuHandle handle, NvApiGPUType* gpuType);\n"
  },
  {
    "path": "src/detection/gpu/nvml.h",
    "content": "#pragma once\n\n// DISCLAIMER:\n// THIS FILE IS CREATED FROM SCRATCH, BY READING THE OFFICIAL NVML API\n// DOCUMENTATION REFERENCED BELOW, IN ORDER TO MAKE FASTFETCH MIT COMPLIANT.\n\n// https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceStructs.html\n#define NVML_DEVICE_PCI_BUS_ID_BUFFER_SIZE 32\n#define NVML_DEVICE_PCI_BUS_ID_BUFFER_V2_SIZE 16\n#define NVML_DEVICE_NAME_V2_BUFFER_SIZE 96\n\ntypedef enum { NVML_SUCCESS = 0 } nvmlReturn_t;\ntypedef struct nvmlDevice_t* nvmlDevice_t;\n\n// https://docs.nvidia.com/deploy/nvml-api/structnvmlPciInfo__t.html\n// PCI information about a GPU device\ntypedef struct {\n    // The legacy tuple domain:bus:device.function PCI identifier (& NULL terminator)\n    char busIdLegacy[NVML_DEVICE_PCI_BUS_ID_BUFFER_V2_SIZE];\n    // The PCI domain on which the device's bus resides, 0 to 0xffffffff\n    unsigned int domain;\n    // The bus on which the device resides, 0 to 0xff\n    unsigned int bus;\n    // The device's id on the bus, 0 to 31\n    unsigned int device;\n    // The combined 16-bit device id and 16-bit vendor id\n    unsigned int pciDeviceId;\n    // The 32-bit Sub System Device ID\n    unsigned int pciSubSystemId;\n    // The tuple domain:bus:device.function PCI identifier (& NULL terminator)\n    char busId[NVML_DEVICE_PCI_BUS_ID_BUFFER_SIZE];\n} nvmlPciInfo_t;\n\n// https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceEnumvs.html#group__nvmlDeviceEnumvs_1g2650b526841fa38b8f293c2d509a1de0\n// Temperature sensors\ntypedef enum {\n    // Temperature sensor for the GPU die\n    NVML_TEMPERATURE_GPU = 0,\n    NVML_TEMPERATURE_COUNT,\n} nvmlTemperatureSensors_t;\n\n// https://docs.nvidia.com/deploy/nvml-api/structnvmlMemory__v2__t.html#structnvmlMemory__v2__t\n// Memory allocation information for a device (v2)\ntypedef struct {\n    // Structure format version (must be 2)\n    unsigned int version;\n    // Total physical device memory (in bytes)\n    unsigned long long total;\n    // Device memory (in bytes) reserved for system use (driver or firmware)\n    unsigned long long reserved;\n    // Unallocated device memory (in bytes)\n    unsigned long long free;\n    // Allocated device memory (in bytes)\n    unsigned long long used;\n} nvmlMemory_v2_t;\n// https://github.com/NVIDIA/nvidia-settings/issues/78#issuecomment-1012837988\nenum { nvmlMemory_v2 = (unsigned int)(sizeof(nvmlMemory_v2_t) | (2 << 24U)) };\n\n// https://docs.nvidia.com/deploy/nvml-api/structnvmlMemory__t.html#structnvmlMemory__t\n// Memory allocation information for a device (v1)\ntypedef struct\n{\n    // Total physical device memory (in bytes)\n    unsigned long long total;\n    // Unallocated device memory (in bytes)\n    unsigned long long free;\n    // Sum of Reserved and Allocated device memory (in bytes)\n    unsigned long long used;\n} nvmlMemory_t;\n\n// https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceEnumvs.html#group__nvmlDeviceEnumvs_1g805c0647be9996589fc5e3f6ff680c64\n// Clock types\ntypedef enum {\n    // Graphics clock domain\n    NVML_CLOCK_GRAPHICS = 0,\n    // SM clock domain\n    NVML_CLOCK_SM = 1,\n    // Memory clock domain\n    NVML_CLOCK_MEM = 2,\n    // Video encoder/decoder clock domain\n    NVML_CLOCK_VIDEO = 3,\n    // Count of clock types\n    NVML_CLOCK_COUNT,\n} nvmlClockType_t;\n\n// https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceEnumvs.html#group__nvmlDeviceEnumvs_1gfa6b01990b212f7b49089b7158eafd2b\n// The Brand of the GPU\ntypedef enum {\n    NVML_BRAND_UNKNOWN = 0,\n    NVML_BRAND_QUADRO = 1,\n    NVML_BRAND_TESLA = 2,\n    NVML_BRAND_NVS = 3,\n    NVML_BRAND_GRID = 4,\n    NVML_BRAND_GEFORCE = 5,\n    NVML_BRAND_TITAN = 6,\n    NVML_BRAND_NVIDIA_VAPPS = 7,\n    NVML_BRAND_NVIDIA_VPC = 8,\n    NVML_BRAND_NVIDIA_VCS = 9,\n    NVML_BRAND_NVIDIA_VWS = 10,\n    NVML_BRAND_NVIDIA_CLOUD_GAMING = 11,\n    NVML_BRAND_NVIDIA_VGAMING = NVML_BRAND_NVIDIA_CLOUD_GAMING,\n    NVML_BRAND_QUADRO_RTX = 12,\n    NVML_BRAND_NVIDIA_RTX = 13,\n    NVML_BRAND_NVIDIA = 14,\n    NVML_BRAND_GEFORCE_RTX = 15,\n    NVML_BRAND_TITAN_RTX = 16,\n    NVML_BRAND_COUNT,\n} nvmlBrandType_t;\n\n// https://docs.nvidia.com/deploy/nvml-api/structnvmlUtilization__t.html#structnvmlUtilization__t\n// Utilization information for a device.\ntypedef struct\n{\n    // Percent of time over the past second during which one or more kernels was executing on the GPU\n    unsigned int gpu;\n    // Percent of time over the past second during which global (device) memory was being read or written\n    unsigned int memory;\n} nvmlUtilization_t;\n\n// https://docs.nvidia.com/deploy/nvml-api/group__nvmlInitializationAndCleanup.html#group__nvmlInitializationAndCleanup\n// Initialize NVML, but don't initialize any GPUs yet\nnvmlReturn_t nvmlInit_v2(void);\n// Shut down NVML by releasing all GPU resources previously allocated with nvmlInit_v2()\nnvmlReturn_t nvmlShutdown(void);\n\n// https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceQueries.html\n// Retrieves the number of compute devices in the system. A compute device is a single GPU\nextern nvmlReturn_t nvmlDeviceGetCount_v2(unsigned int* deviceCount);\n// Acquire the handle for a particular device, based on its index\nextern nvmlReturn_t nvmlDeviceGetHandleByIndex_v2(unsigned int index, nvmlDevice_t* device);\n// Acquire the handle for a particular device, based on its PCI bus id\nextern nvmlReturn_t nvmlDeviceGetHandleByPciBusId_v2(const char* pciBusId, nvmlDevice_t* device);\n// Retrieves the PCI attributes of this device\nextern nvmlReturn_t nvmlDeviceGetPciInfo_v3(nvmlDevice_t device, nvmlPciInfo_t* pci);\n// Retrieves the current temperature readings for the device, in degrees C\nextern nvmlReturn_t nvmlDeviceGetTemperature(nvmlDevice_t device, nvmlTemperatureSensors_t sensorType, unsigned int* temp);\n// Retrieves the amount of used, free, reserved and total memory available on the device, in bytes. The reserved amount is supported on version 2 only\nextern nvmlReturn_t nvmlDeviceGetMemoryInfo_v2(nvmlDevice_t device, nvmlMemory_v2_t* memory);\n// Retrieves the amount of used, free, total memory available on the device, in bytes.\nextern nvmlReturn_t nvmlDeviceGetMemoryInfo(nvmlDevice_t device, nvmlMemory_t *memory);\n// Gets the device's core count\nextern nvmlReturn_t nvmlDeviceGetNumGpuCores(nvmlDevice_t device, unsigned int* numCores);\n// Retrieves the maximum clock speeds for the device\nextern nvmlReturn_t nvmlDeviceGetMaxClockInfo(nvmlDevice_t device, nvmlClockType_t type, unsigned int* clock);\n// Retrieves the brand of this device\nextern nvmlReturn_t nvmlDeviceGetBrand(nvmlDevice_t device, nvmlBrandType_t* type);\n// Retrieves the current utilization rates for the device\nextern nvmlReturn_t nvmlDeviceGetUtilizationRates(nvmlDevice_t device, nvmlUtilization_t *utilization);\n// Retrieves the globally unique immutable UUID associated with this device, as a 5 part hexadecimal string, that augments the immutable, board serial identifier.\nextern nvmlReturn_t nvmlDeviceGetIndex(nvmlDevice_t device, unsigned int *index);\n// Retrieves the name of this device.\nextern nvmlReturn_t nvmlDeviceGetName(nvmlDevice_t device, char *name, unsigned int length);\n"
  },
  {
    "path": "src/detection/gtk_qt/gtk.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/properties.h\"\n#include \"common/thread.h\"\n#include \"common/settings.h\"\n#include \"detection/gtk_qt/gtk_qt.h\"\n#include \"detection/displayserver/displayserver.h\"\n\nstatic inline bool allPropertiesSet(FFGTKResult* result)\n{\n    return\n        result->theme.length > 0 &&\n        result->icons.length > 0 &&\n        result->font.length > 0;\n}\n\nstatic inline void applyGTKSettings(FFGTKResult* result, const char* themeName, const char* iconsName, const char* fontName, const char* cursorTheme, int cursorSize, const char* wallpaper)\n{\n    if(result->theme.length == 0)\n        ffStrbufAppendS(&result->theme, themeName);\n\n    if(result->icons.length == 0)\n        ffStrbufAppendS(&result->icons, iconsName);\n\n    if(result->font.length == 0)\n        ffStrbufAppendS(&result->font, fontName);\n\n    if(result->cursor.length == 0)\n        ffStrbufAppendS(&result->cursor, cursorTheme);\n\n    if(result->cursorSize.length == 0 && cursorSize > 0)\n        ffStrbufAppendF(&result->cursorSize, \"%i\", cursorSize);\n\n    if(result->wallpaper.length == 0)\n        ffStrbufAppendS(&result->wallpaper, wallpaper);\n}\n\nstatic bool testXfconfWallpaperPropKey(FF_MAYBE_UNUSED void* data, const char* key)\n{\n    int count = 0;\n    sscanf(key, \"/backdrop/screen0/monitor%*[^/]/workspace0/last-image%n\", &count);\n    return count == 0;\n}\n\nstatic void detectGTKFromSettings(FFGTKResult* result)\n{\n    static const char* themeName = NULL;\n    static const char* iconsName = NULL;\n    static const char* fontName = NULL;\n    static const char* cursorTheme = NULL;\n    static int cursorSize = 0;\n    static const char* wallpaper = NULL;\n\n    static bool init = false;\n\n    if(init)\n    {\n        applyGTKSettings(result, themeName, iconsName, fontName, cursorTheme, cursorSize, wallpaper);\n        return;\n    }\n\n    init = true;\n\n    const FFDisplayServerResult* wmde = ffConnectDisplayServer();\n\n    if(ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_XFCE4))\n    {\n        themeName = ffSettingsGetXFConf(\"xsettings\", \"/Net/ThemeName\", FF_VARIANT_TYPE_STRING).strValue;\n        iconsName = ffSettingsGetXFConf(\"xsettings\", \"/Net/IconThemeName\", FF_VARIANT_TYPE_STRING).strValue;\n        fontName = ffSettingsGetXFConf(\"xsettings\", \"/Gtk/FontName\", FF_VARIANT_TYPE_STRING).strValue;\n        cursorTheme = ffSettingsGetXFConf(\"xsettings\", \"/Gtk/CursorThemeName\", FF_VARIANT_TYPE_STRING).strValue;\n        cursorSize = ffSettingsGetXFConf(\"xsettings\", \"/Gtk/CursorThemeSize\", FF_VARIANT_TYPE_INT).intValue;\n        wallpaper = ffSettingsGetXFConfFirstMatch(\"xfce4-desktop\", \"/backdrop/screen0\", FF_VARIANT_TYPE_STRING, NULL, testXfconfWallpaperPropKey).strValue;\n    }\n    else if(ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_CINNAMON))\n    {\n        themeName = ffSettingsGetGnome(\"/org/cinnamon/desktop/interface/gtk-theme\", \"org.cinnamon.desktop.interface\", NULL, \"gtk-theme\", FF_VARIANT_TYPE_STRING).strValue;\n        iconsName = ffSettingsGetGnome(\"/org/cinnamon/desktop/interface/icon-theme\", \"org.cinnamon.desktop.interface\", NULL, \"icon-theme\", FF_VARIANT_TYPE_STRING).strValue;\n        fontName = ffSettingsGetGnome(\"/org/cinnamon/desktop/interface/font-name\", \"org.cinnamon.desktop.interface\", NULL, \"font-name\", FF_VARIANT_TYPE_STRING).strValue;\n        cursorTheme = ffSettingsGetGnome(\"/org/cinnamon/desktop/interface/cursor-theme\", \"org.cinnamon.desktop.interface\", NULL, \"cursor-theme\", FF_VARIANT_TYPE_STRING).strValue;\n        cursorSize = ffSettingsGetGnome(\"/org/cinnamon/desktop/interface/cursor-size\", \"org.cinnamon.desktop.interface\", NULL, \"cursor-size\", FF_VARIANT_TYPE_INT).intValue;\n        wallpaper = ffSettingsGetGnome(\"/org/cinnamon/desktop/background/picture-uri\", \"org.cinnamon.desktop.background\", NULL, \"picture-uri\", FF_VARIANT_TYPE_STRING).strValue;\n    }\n    else if(ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_MATE))\n    {\n        themeName = ffSettingsGetGnome(\"/org/mate/interface/gtk-theme\", \"org.mate.interface\", NULL, \"gtk-theme\", FF_VARIANT_TYPE_STRING).strValue;\n        iconsName = ffSettingsGetGnome(\"/org/mate/interface/icon-theme\", \"org.mate.interface\", NULL, \"icon-theme\", FF_VARIANT_TYPE_STRING).strValue;\n        fontName = ffSettingsGetGnome(\"/org/mate/interface/font-name\", \"org.mate.interface\", NULL, \"font-name\", FF_VARIANT_TYPE_STRING).strValue;\n        cursorTheme = ffSettingsGetGnome(\"/org/mate/peripherals-mouse/cursor-theme\", \"org.mate.peripherals-mouse\", NULL, \"cursor-theme\", FF_VARIANT_TYPE_STRING).strValue;\n        cursorSize = ffSettingsGetGnome(\"/org/mate/peripherals-mouse/cursor-size\", \"org.mate.peripherals-mouse\", NULL, \"cursor-size\", FF_VARIANT_TYPE_INT).intValue;\n        wallpaper = ffSettingsGetGnome(\"/org/mate/desktop/background\", \"org.mate.background\", NULL, \"picture-filename\", FF_VARIANT_TYPE_STRING).strValue;\n    }\n    else if(\n        ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_GNOME) ||\n        ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_GNOME_CLASSIC) ||\n        ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_UNITY) ||\n        ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_BUDGIE)\n    ) {\n        themeName = ffSettingsGetGnome(\"/org/gnome/desktop/interface/gtk-theme\", \"org.gnome.desktop.interface\", NULL, \"gtk-theme\", FF_VARIANT_TYPE_STRING).strValue;\n        iconsName = ffSettingsGetGnome(\"/org/gnome/desktop/interface/icon-theme\", \"org.gnome.desktop.interface\", NULL, \"icon-theme\", FF_VARIANT_TYPE_STRING).strValue;\n        fontName = ffSettingsGetGnome(\"/org/gnome/desktop/interface/font-name\", \"org.gnome.desktop.interface\", NULL, \"font-name\", FF_VARIANT_TYPE_STRING).strValue;\n        cursorTheme = ffSettingsGetGnome(\"/org/gnome/desktop/interface/cursor-theme\", \"org.gnome.desktop.interface\", NULL, \"cursor-theme\", FF_VARIANT_TYPE_STRING).strValue;\n        cursorSize = ffSettingsGetGnome(\"/org/gnome/desktop/interface/cursor-size\", \"org.gnome.desktop.interface\", NULL, \"cursor-size\", FF_VARIANT_TYPE_INT).intValue;\n        wallpaper = ffSettingsGetGnome(\"/org/gnome/desktop/background/picture-uri\", \"org.gnome.desktop.background\", NULL, \"picture-uri\", FF_VARIANT_TYPE_STRING).strValue;\n    }\n\n    applyGTKSettings(result, themeName, iconsName, fontName, cursorTheme, cursorSize, wallpaper);\n}\n\nstatic void detectGTKFromConfigFile(const char* filename, FFGTKResult* result)\n{\n    ffParsePropFileValues(filename, 5, (FFpropquery[]) {\n        {\"gtk-theme-name =\", &result->theme},\n        {\"gtk-icon-theme-name =\", &result->icons},\n        {\"gtk-font-name =\", &result->font},\n        {\"gtk-cursor-theme-name =\", &result->cursor},\n        {\"gtk-cursor-theme-size =\", &result->cursorSize}\n    });\n}\n\nstatic void detectGTKFromConfigDir(FFstrbuf* configDir, const char* version, FFGTKResult* result)\n{\n    uint32_t configDirLength = configDir->length;\n\n    // <configdir>/gtk-<version>.0/settings.ini\n    ffStrbufAppendS(configDir, \"gtk-\");\n    ffStrbufAppendS(configDir, version);\n    ffStrbufAppendS(configDir, \".0/settings.ini\");\n    detectGTKFromConfigFile(configDir->chars, result);\n    ffStrbufSubstrBefore(configDir, configDirLength);\n    if(allPropertiesSet(result))\n        return;\n\n    // <configdir>/gtk-<version>.0/gtkrc\n    ffStrbufAppendS(configDir, \"gtk-\");\n    ffStrbufAppendS(configDir, version);\n    ffStrbufAppendS(configDir, \".0/gtkrc\");\n    detectGTKFromConfigFile(configDir->chars, result);\n    ffStrbufSubstrBefore(configDir, configDirLength);\n    if(allPropertiesSet(result))\n        return;\n\n    // <configdir>/gtkrc-<version>.0\n    ffStrbufAppendS(configDir, \"gtkrc-\");\n    ffStrbufAppendS(configDir, version);\n    ffStrbufAppendS(configDir, \".0\");\n    detectGTKFromConfigFile(configDir->chars, result);\n    ffStrbufSubstrBefore(configDir, configDirLength);\n    if(allPropertiesSet(result))\n        return;\n\n    // <configdir>/.gtkrc-<version>.0\n    ffStrbufAppendS(configDir, \".gtkrc-\");\n    ffStrbufAppendS(configDir, version);\n    ffStrbufAppendS(configDir, \".0\");\n    detectGTKFromConfigFile(configDir->chars, result);\n    ffStrbufSubstrBefore(configDir, configDirLength);\n}\n\nstatic void detectGTK(const char* version, FFGTKResult* result)\n{\n    //Mate, Cinnamon, GNOME, Unity, Budgie use dconf to save theme config\n    //On other DEs, this will do nothing\n    detectGTKFromSettings(result);\n    if(allPropertiesSet(result))\n        return;\n\n    //We need to do this because we use multiple threads on configDirs\n    FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreateA(64);\n\n    FF_LIST_FOR_EACH(FFstrbuf, configDir, instance.state.platform.configDirs)\n    {\n        ffStrbufSet(&baseDir, configDir);\n        detectGTKFromConfigDir(&baseDir, version, result);\n        if(allPropertiesSet(result))\n            break;\n    }\n}\n\n#define FF_DETECT_GTK_IMPL(version) \\\n    static FFGTKResult result; \\\n    static bool init = false; \\\n    if(init) \\\n        return &result; \\\n    init = true; \\\n    ffStrbufInit(&result.theme); \\\n    ffStrbufInit(&result.icons); \\\n    ffStrbufInit(&result.font); \\\n    ffStrbufInit(&result.cursor); \\\n    ffStrbufInit(&result.cursorSize); \\\n    ffStrbufInit(&result.wallpaper); \\\n    detectGTK(#version, &result); \\\n    return &result;\n\nconst FFGTKResult* ffDetectGTK2(void)\n{\n    FF_DETECT_GTK_IMPL(2)\n}\n\nconst FFGTKResult* ffDetectGTK3(void)\n{\n    FF_DETECT_GTK_IMPL(3)\n}\n\nconst FFGTKResult* ffDetectGTK4(void)\n{\n    FF_DETECT_GTK_IMPL(4)\n}\n\n#undef FF_CALCULATE_GTK_IMPL\n"
  },
  {
    "path": "src/detection/gtk_qt/gtk_qt.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\ntypedef struct FFGTKResult\n{\n    FFstrbuf theme;\n    FFstrbuf icons;\n    FFstrbuf font;\n    FFstrbuf cursor;\n    FFstrbuf cursorSize;\n    FFstrbuf wallpaper;\n} FFGTKResult;\n\ntypedef struct FFQtResult\n{\n    FFstrbuf widgetStyle;\n    FFstrbuf colorScheme;\n    FFstrbuf icons;\n    FFstrbuf font;\n    FFstrbuf wallpaper;\n} FFQtResult;\n\nconst FFGTKResult* ffDetectGTK2(void);\nconst FFGTKResult* ffDetectGTK4(void);\nconst FFGTKResult* ffDetectGTK3(void);\nconst FFQtResult* ffDetectQt(void);\n"
  },
  {
    "path": "src/detection/gtk_qt/qt.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/properties.h\"\n#include \"common/thread.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/gtk_qt/gtk_qt.h\"\n#include \"detection/displayserver/displayserver.h\"\n\n#include <stdlib.h>\n#include <string.h>\n\nstatic inline bool allValuesSet(const FFQtResult* result)\n{\n    return\n        result->widgetStyle.length > 0 &&\n        result->colorScheme.length > 0 &&\n        result->icons.length > 0 &&\n        result->font.length > 0 &&\n        result->wallpaper.length > 0;\n}\n\ntypedef enum __attribute__((__packed__)) PlasmaCategory\n{\n    PLASMA_CATEGORY_GENERAL,\n    PLASMA_CATEGORY_KDE,\n    PLASMA_CATEGORY_ICONS,\n    PLASMA_CATEGORY_OTHER\n} PlasmaCategory;\n\nstatic bool detectPlasmaFromFile(const char* filename, FFQtResult* result)\n{\n    FILE* kdeglobals = fopen(filename, \"r\");\n    if(kdeglobals == NULL)\n        return false;\n\n    char* line = NULL;\n    size_t len = 0;\n\n    PlasmaCategory category = PLASMA_CATEGORY_OTHER;\n\n    while(getline(&line, &len, kdeglobals) != -1)\n    {\n        if(line[0] == '[')\n        {\n            char categoryName[32];\n            sscanf(line, \"[%31[^]]\", categoryName);\n\n            if(ffStrEqualsIgnCase(categoryName, \"General\"))\n                category = PLASMA_CATEGORY_GENERAL;\n            else if(ffStrEqualsIgnCase(categoryName, \"KDE\"))\n                category = PLASMA_CATEGORY_KDE;\n            else if(ffStrEqualsIgnCase(categoryName, \"Icons\"))\n                category = PLASMA_CATEGORY_ICONS;\n            else\n                category = PLASMA_CATEGORY_OTHER;\n\n            continue;\n        }\n\n        if(category == PLASMA_CATEGORY_KDE && result->widgetStyle.length == 0)\n            ffParsePropLine(line, \"widgetStyle =\", &result->widgetStyle);\n        else if(category == PLASMA_CATEGORY_ICONS && result->icons.length == 0)\n            ffParsePropLine(line, \"Theme =\", &result->icons);\n        else if(category == PLASMA_CATEGORY_GENERAL)\n        {\n            if(result->colorScheme.length == 0)\n                ffParsePropLine(line, \"ColorScheme =\", &result->colorScheme);\n\n            if(result->font.length == 0)\n                ffParsePropLine(line, \"font =\", &result->font);\n\n            //Before plasma 5.23, \"Font\" was the key instead of \"font\". Since a lot of distros ship older versions, we test for both.\n            if(result->font.length == 0)\n                ffParsePropLine(line, \"Font =\", &result->font);\n        }\n    }\n\n    free(line);\n\n    fclose(kdeglobals);\n\n    return true;\n}\n\nstatic void detectPlasma(FFQtResult* result)\n{\n    bool foundAFile = false;\n\n    //We need to do this because we use multiple threads on configDirs\n    FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreateA(64);\n\n    FF_LIST_FOR_EACH(FFstrbuf, configDir, instance.state.platform.configDirs)\n    {\n        ffStrbufSet(&baseDir, configDir);\n        ffStrbufAppendS(&baseDir, \"kdeglobals\");\n\n        if(detectPlasmaFromFile(baseDir.chars, result))\n            foundAFile = true;\n\n        ffStrbufSet(&baseDir, configDir);\n        ffStrbufAppendS(&baseDir, \"plasma-org.kde.plasma.desktop-appletsrc\");\n\n        ffParsePropFile(baseDir.chars, \"Image=\", &result->wallpaper);\n\n        if(allValuesSet(result))\n            return;\n    }\n\n    if(!foundAFile)\n        return;\n\n    //In Plasma the default value is never set in the config file, but the whole key-value is discarded.\n    ///We must set these values by our self if the file exists (it always does here)\n    if(result->widgetStyle.length == 0)\n        ffStrbufAppendS(&result->widgetStyle, \"Breeze\");\n\n    if(result->colorScheme.length == 0)\n        ffStrbufAppendS(&result->colorScheme, \"BreezeLight\");\n\n    if(result->icons.length == 0)\n        ffStrbufAppendS(&result->icons, \"Breeze\");\n\n    if(result->font.length == 0)\n        ffStrbufAppendS(&result->font, \"Noto Sans, 10\");\n}\n\nstatic void detectLXQt(FFQtResult* result)\n{\n    ffParsePropFileConfigValues(\"lxqt/lxqt.conf\", 3, (FFpropquery[]) {\n        {\"style = \", &result->widgetStyle},\n        {\"icon_theme = \", &result->icons},\n        {\"font = \", &result->font}\n    });\n\n    ffParsePropFileConfig(\"pcmanfm-qt/lxqt/settings.conf\", \"Wallpaper=\", &result->wallpaper);\n}\n\nstatic void detectQtCt(char qver, FFQtResult* result)\n{\n    // qt5ct and qt6ct are technically separate applications, but they're both\n    // by the same author and qt6ct understands qt5ct in qt6 applications as well.\n    char file[] = \"qtXct/qtXct.conf\";\n    file[2] = file[8] = qver;\n\n    FF_STRBUF_AUTO_DESTROY font = ffStrbufCreate();\n\n    ffParsePropFileConfigValues(file, 3, (FFpropquery[]) {\n        {\"style=\", &result->widgetStyle},\n        {\"icon_theme=\", &result->icons},\n        {\"general=\", &font}\n    });\n\n    if (ffStrbufStartsWithC(&font, '@'))\n    {\n        // See QVariant notes on https://doc.qt.io/qt-5/qsettings.html and\n        // https://github.com/fastfetch-cli/fastfetch/issues/1053#issuecomment-2197254769\n        // Thankfully, newer versions use the more common font encoding.\n        ffStrbufSetNS(&font, 5, file);\n    }\n    else if (qver == '5')\n    {\n        // #1864\n        const char *p = font.chars;\n\n        while (*p)\n        {\n            if (p[0] == '\\\\' && p[1] == 'x' && isxdigit(p[2]) && isxdigit(p[3]) && isxdigit(p[4]) && isxdigit(p[5]))\n            {\n                uint32_t codepoint = (uint32_t)strtoul((char[]) { p[2], p[3], p[4], p[5], '\\0' }, NULL, 16);\n                ffStrbufAppendUtf32CodePoint(&result->font, codepoint);\n                p += 6;\n            }\n            else\n            {\n                ffStrbufAppendC(&result->font, *p++);\n            }\n        }\n    }\n    else\n    {\n        ffStrbufDestroy(&result->font);\n        ffStrbufInitMove(&result->font, &font);\n    }\n}\n\nstatic void detectKvantum(FFQtResult* result)\n{\n    ffParsePropFileConfigValues(\"Kvantum/kvantum.kvconfig\", 1, (FFpropquery[]) {\n        {\"theme=\", &result->widgetStyle},\n    });\n}\n\nconst FFQtResult* ffDetectQt(void)\n{\n    static FFQtResult result;\n\n    static bool init = false;\n    if(init)\n        return &result;\n    init = true;\n\n    ffStrbufInit(&result.widgetStyle);\n    ffStrbufInit(&result.colorScheme);\n    ffStrbufInit(&result.icons);\n    ffStrbufInit(&result.font);\n    ffStrbufInit(&result.wallpaper);\n\n    const FFDisplayServerResult* wmde = ffConnectDisplayServer();\n\n    if(ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_PLASMA))\n        detectPlasma(&result);\n    else if(ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_LXQT))\n        detectLXQt(&result);\n    else\n    {\n        const char *qPlatformTheme = getenv(\"QT_QPA_PLATFORMTHEME\");\n        if(qPlatformTheme && (ffStrEquals(qPlatformTheme, \"qt5ct\") || ffStrEquals(qPlatformTheme, \"qt6ct\")))\n            detectQtCt(qPlatformTheme[2], &result);\n    }\n\n    if(ffStrbufEqualS(&result.widgetStyle, \"kvantum\") || ffStrbufEqualS(&result.widgetStyle, \"kvantum-dark\"))\n    {\n        ffStrbufClear(&result.widgetStyle);\n        detectKvantum(&result);\n    }\n\n    return &result;\n}\n"
  },
  {
    "path": "src/detection/host/host.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/host/option.h\"\n\ntypedef struct FFHostResult\n{\n    FFstrbuf family;\n    FFstrbuf name;\n    FFstrbuf version;\n    FFstrbuf sku;\n    FFstrbuf serial;\n    FFstrbuf uuid;\n    FFstrbuf vendor;\n} FFHostResult;\n\nconst char* ffHostGetMacProductNameWithHwModel(const FFstrbuf* hwModel);\n#if __x86_64__\nbool ffHostDetectMac(FFHostResult* host);\n#endif\nconst char* ffDetectHost(FFHostResult* host);\n"
  },
  {
    "path": "src/detection/host/host_android.c",
    "content": "#include \"host.h\"\n#include \"common/settings.h\"\n\n#include <ctype.h>\n\nconst char* ffDetectHost(FFHostResult* host)\n{\n    // http://newandroidbook.com/ddb/\n    ffSettingsGetAndroidProperty(\"ro.product.device\", &host->family);\n\n    ffSettingsGetAndroidProperty(\"ro.product.marketname\", &host->name)\n        || ffSettingsGetAndroidProperty(\"ro.vendor.product.display\", &host->name)\n        || ffSettingsGetAndroidProperty(\"ro.vivo.market.name\", &host->name)\n        || ffSettingsGetAndroidProperty(\"ro.product.oppo_model\", &host->name)\n        || ffSettingsGetAndroidProperty(\"ro.oppo.market.name\", &host->name)\n        || ffSettingsGetAndroidProperty(\"ro.vendor.oplus.market.enname\", &host->name)\n        || ffSettingsGetAndroidProperty(\"ro.config.devicename\", &host->name)\n        || ffSettingsGetAndroidProperty(\"ro.config.marketing_name\", &host->name)\n        || ffSettingsGetAndroidProperty(\"ro.product.vendor.model\", &host->name)\n        || ffSettingsGetAndroidProperty(\"ro.product.brand\", &host->name);\n\n    if (ffSettingsGetAndroidProperty(\"ro.product.model\", &host->version))\n    {\n        if (ffStrbufStartsWithIgnCase(&host->version, &host->name))\n        {\n            ffStrbufSubstrAfter(&host->version, host->name.length);\n            ffStrbufTrimLeft(&host->version, ' ');\n        }\n    }\n\n    ffSettingsGetAndroidProperty(\"ro.product.manufacturer\", &host->vendor);\n\n    if(host->vendor.length && !ffStrbufStartsWithIgnCase(&host->name, &host->vendor))\n    {\n        ffStrbufPrependS(&host->name, \" \");\n        ffStrbufPrepend(&host->name, &host->vendor);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/host/host_apple.c",
    "content": "#include \"host.h\"\n#include \"common/sysctl.h\"\n#include \"common/apple/cf_helpers.h\"\n\n#include <IOKit/IOKitLib.h>\n\nconst char* getProductNameWithIokit(FFstrbuf* result)\n{\n    FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t registryEntry = IORegistryEntryFromPath(MACH_PORT_NULL, \"IODeviceTree:/product\");\n    if (!registryEntry)\n        return \"IOServiceGetMatchingService() failed\";\n\n    FF_CFTYPE_AUTO_RELEASE CFStringRef productName = IORegistryEntryCreateCFProperty(registryEntry, CFSTR(\"product-name\"), kCFAllocatorDefault, kNilOptions);\n    if (!productName)\n        return \"IORegistryEntryCreateCFProperty() failed\";\n\n    return ffCfStrGetString(productName, result);\n}\n\nconst char* getOthersByIokit(FFHostResult* host)\n{\n    FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t registryEntry = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceMatching(\"IOPlatformExpertDevice\"));\n    if (!registryEntry)\n        return \"IOServiceGetMatchingService() failed\";\n\n    FF_CFTYPE_AUTO_RELEASE CFStringRef serialNumber = IORegistryEntryCreateCFProperty(registryEntry, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, kNilOptions);\n    if (serialNumber)\n        ffCfStrGetString(serialNumber, &host->serial);\n\n    FF_CFTYPE_AUTO_RELEASE CFStringRef uuid = IORegistryEntryCreateCFProperty(registryEntry, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, kNilOptions);\n    if (uuid)\n        ffCfStrGetString(uuid, &host->uuid);\n\n    FF_CFTYPE_AUTO_RELEASE CFStringRef manufacturer = IORegistryEntryCreateCFProperty(registryEntry, CFSTR(\"manufacturer\"), kCFAllocatorDefault, kNilOptions);\n    if (manufacturer)\n        ffCfStrGetString(manufacturer, &host->vendor);\n\n    FF_CFTYPE_AUTO_RELEASE CFStringRef version = IORegistryEntryCreateCFProperty(registryEntry, CFSTR(\"version\"), kCFAllocatorDefault, kNilOptions);\n    if (version)\n        ffCfStrGetString(version, &host->version);\n\n    return NULL;\n}\n\nconst char* ffDetectHost(FFHostResult* host)\n{\n    const char* error = ffSysctlGetString(\"hw.product\", &host->family);\n    if (error) error = ffSysctlGetString(\"hw.model\", &host->family);\n    if (error) return error;\n\n    ffStrbufSetStatic(&host->name, ffHostGetMacProductNameWithHwModel(&host->family));\n    if (host->name.length == 0)\n        getProductNameWithIokit(&host->name);\n    if (host->name.length == 0)\n        ffStrbufSet(&host->name, &host->family);\n    getOthersByIokit(host);\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/host/host_bsd.c",
    "content": "#include \"host.h\"\n#include \"common/settings.h\"\n#include \"common/smbiosHelper.h\"\n\nconst char* ffDetectHost(FFHostResult* host)\n{\n    ffSettingsGetFreeBSDKenv(\"smbios.system.product\", &host->name);\n    ffCleanUpSmbiosValue(&host->name);\n    ffSettingsGetFreeBSDKenv(\"smbios.system.family\", &host->family);\n    ffCleanUpSmbiosValue(&host->family);\n    ffSettingsGetFreeBSDKenv(\"smbios.system.version\", &host->version);\n    ffCleanUpSmbiosValue(&host->version);\n    ffSettingsGetFreeBSDKenv(\"smbios.system.sku\", &host->sku);\n    ffCleanUpSmbiosValue(&host->sku);\n    ffSettingsGetFreeBSDKenv(\"smbios.system.serial\", &host->serial);\n    ffCleanUpSmbiosValue(&host->serial);\n    ffSettingsGetFreeBSDKenv(\"smbios.system.uuid\", &host->uuid);\n    ffCleanUpSmbiosValue(&host->uuid);\n    ffSettingsGetFreeBSDKenv(\"smbios.system.maker\", &host->vendor);\n    ffCleanUpSmbiosValue(&host->vendor);\n\n    #ifdef __x86_64__\n    ffHostDetectMac(host);\n    #endif\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/host/host_linux.c",
    "content": "#include \"host.h\"\n#include \"common/io.h\"\n#include \"common/processing.h\"\n#include \"common/smbiosHelper.h\"\n\n#include <stdlib.h>\n\nstatic bool getHostProductName(FFstrbuf* name)\n{\n    if (ffReadFileBuffer(\"/sys/firmware/devicetree/base/model\", name) ||\n        ffReadFileBuffer(\"/sys/firmware/devicetree/base/banner-name\", name))\n    {\n        ffStrbufTrimRight(name, '\\0');\n        return true;\n    }\n\n    if (ffReadFileBuffer(\"/tmp/sysinfo/model\", name))\n    {\n        ffStrbufTrimRightSpace(name);\n        ffStrbufTrimRight(name, '\\0');\n        if(ffIsSmbiosValueSet(name))\n            return true;\n    }\n\n    return false;\n}\n\nstatic bool getHostSerialNumber(FFstrbuf* serial)\n{\n    if (ffReadFileBuffer(\"/sys/firmware/devicetree/base/smbios/smbios/system/serial\", serial) ||\n        ffReadFileBuffer(\"/sys/firmware/devicetree/base/serial-number\", serial))\n    {\n        ffStrbufTrimRight(serial, '\\0');\n        return true;\n    }\n    return false;\n}\n\nstatic bool getHostProductFamily(FFstrbuf* family)\n{\n    if (ffReadFileBuffer(\"/sys/firmware/devicetree/base/smbios/smbios/system/family\", family) ||\n        ffReadFileBuffer(\"/sys/firmware/devicetree/base/smbios/smbios/system/product\", family))\n    {\n        ffStrbufTrimRight(family, '\\0');\n        return true;\n    }\n    return false;\n}\n\nstatic bool getHostVendor(FFstrbuf* vendor)\n{\n    if (ffReadFileBuffer(\"/sys/firmware/devicetree/base/smbios/smbios/system/manufacturer\", vendor))\n    {\n        ffStrbufTrimRight(vendor, '\\0');\n        return true;\n    }\n    return false;\n}\n\nconst char* ffDetectHost(FFHostResult* host)\n{\n    // This is a hack for Asahi Linux, whose product_family is empty\n    if (ffGetSmbiosValue(\"/sys/devices/virtual/dmi/id/product_family\", \"/sys/class/dmi/id/product_family\", &host->family))\n    {\n        ffGetSmbiosValue(\"/sys/devices/virtual/dmi/id/product_name\", \"/sys/class/dmi/id/product_name\", &host->name);\n        ffGetSmbiosValue(\"/sys/devices/virtual/dmi/id/product_version\", \"/sys/class/dmi/id/product_version\", &host->version);\n        ffGetSmbiosValue(\"/sys/devices/virtual/dmi/id/product_sku\", \"/sys/class/dmi/id/product_sku\", &host->sku);\n        ffGetSmbiosValue(\"/sys/devices/virtual/dmi/id/product_serial\", \"/sys/class/dmi/id/product_serial\", &host->serial);\n        ffGetSmbiosValue(\"/sys/devices/virtual/dmi/id/sys_vendor\", \"/sys/class/dmi/id/sys_vendor\", &host->vendor);\n    }\n    else\n    {\n        getHostProductFamily(&host->family);\n        getHostProductName(&host->name);\n        getHostSerialNumber(&host->serial);\n        getHostVendor(&host->vendor);\n    }\n\n    #ifdef __x86_64__\n    ffHostDetectMac(host);\n    #endif\n\n    //KVM/Qemu virtual machine\n    if(ffStrbufStartsWithS(&host->name, \"Standard PC\"))\n        ffStrbufPrependS(&host->name, \"KVM/QEMU \");\n\n    if(host->family.length == 0 && host->name.length == 0)\n    {\n        const char* wslDistroName = getenv(\"WSL_DISTRO_NAME\");\n        //On WSL, the real host can't be detected. Instead use WSL as host.\n        if(wslDistroName != NULL || getenv(\"WSL_DISTRO\") != NULL || getenv(\"WSL_INTEROP\") != NULL)\n        {\n            ffStrbufSetStatic(&host->name, \"Windows Subsystem for Linux\");\n            if (wslDistroName)\n                ffStrbufAppendF(&host->name, \" - %s\", wslDistroName);\n            ffStrbufSetStatic(&host->family, \"WSL\");\n            ffStrbufSetStatic(&host->vendor, \"Microsoft Corporation\");\n\n            if (instance.config.general.detectVersion)\n            {\n                ffProcessAppendStdOut(&host->version, (char* const[]){\n                    \"wslinfo\",\n                    \"--wsl-version\",\n                    \"-n\",\n                    NULL,\n                }); // supported in 2.2.3 and later\n            }\n        }\n        else if (ffStrbufStartsWithS(&instance.state.platform.sysinfo.version, \"FreeBSD \"))\n        {\n            ffStrbufSetStatic(&host->name, \"Linux Binary Compatibility on FreeBSD\");\n            ffStrbufSetStatic(&host->family, \"FreeBSD\");\n            ffStrbufSetStatic(&host->vendor, \"FreeBSD Foundation\");\n            if (instance.config.general.detectVersion)\n            {\n                ffStrbufSetS(&host->version, instance.state.platform.sysinfo.version.chars + strlen(\"FreeBSD \"));\n                ffStrbufSubstrBeforeFirstC(&host->version, ' ');\n            }\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/host/host_mac.c",
    "content": "#include \"host.h\"\n#include \"common/stringUtils.h\"\n\nconst char* ffHostGetMacProductNameWithHwModel(const FFstrbuf* hwModel)\n{\n    // Macbook Pro: https://support.apple.com/en-us/HT201300\n    // Macbook Air: https://support.apple.com/en-us/HT201862\n    // Mac mini:    https://support.apple.com/en-us/HT201894\n    // iMac:        https://support.apple.com/en-us/HT201634\n    // Mac Pro:     https://support.apple.com/en-us/HT202888\n    // Mac Studio:  https://support.apple.com/en-us/HT213073\n\n    if(ffStrbufStartsWithS(hwModel, \"MacBookPro\"))\n    {\n        const char* version = hwModel->chars + strlen(\"MacBookPro\");\n        if(ffStrEquals(version, \"18,3\") ||\n           ffStrEquals(version, \"18,4\"))        return \"MacBook Pro (14-inch, 2021)\";\n        if(ffStrEquals(version, \"18,1\") ||\n           ffStrEquals(version, \"18,2\"))        return \"MacBook Pro (16-inch, 2021)\";\n        if(ffStrEquals(version, \"17,1\"))        return \"MacBook Pro (13-inch, M1, 2020)\";\n        if(ffStrEquals(version, \"16,3\"))        return \"MacBook Pro (13-inch, 2020, Two Thunderbolt 3 ports)\";\n        if(ffStrEquals(version, \"16,2\"))        return \"MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)\";\n        if(ffStrEquals(version, \"16,4\") ||\n           ffStrEquals(version, \"16,1\"))        return \"MacBook Pro (16-inch, 2019)\";\n        if(ffStrEquals(version, \"15,4\"))        return \"MacBook Pro (13-inch, 2019, Two Thunderbolt 3 ports)\";\n        if(ffStrEquals(version, \"15,3\"))        return \"MacBook Pro (15-inch, 2019)\";\n        if(ffStrEquals(version, \"15,2\"))        return \"MacBook Pro (13-inch, 2018/2019, Four Thunderbolt 3 ports)\";\n        if(ffStrEquals(version, \"15,1\"))        return \"MacBook Pro (15-inch, 2018/2019)\";\n        if(ffStrEquals(version, \"14,3\"))        return \"MacBook Pro (15-inch, 2017)\";\n        if(ffStrEquals(version, \"14,2\"))        return \"MacBook Pro (13-inch, 2017, Four Thunderbolt 3 ports)\";\n        if(ffStrEquals(version, \"14,1\"))        return \"MacBook Pro (13-inch, 2017, Two Thunderbolt 3 ports)\";\n        if(ffStrEquals(version, \"13,3\"))        return \"MacBook Pro (15-inch, 2016)\";\n        if(ffStrEquals(version, \"13,2\"))        return \"MacBook Pro (13-inch, 2016, Four Thunderbolt 3 ports)\";\n        if(ffStrEquals(version, \"13,1\"))        return \"MacBook Pro (13-inch, 2016, Two Thunderbolt 3 ports)\";\n        if(ffStrEquals(version, \"12,1\"))        return \"MacBook Pro (Retina, 13-inch, Early 2015)\";\n        if(ffStrEquals(version, \"11,4\") ||\n           ffStrEquals(version, \"11,5\"))        return \"MacBook Pro (Retina, 15-inch, Mid 2015)\";\n        if(ffStrEquals(version, \"11,2\") ||\n           ffStrEquals(version, \"11,3\"))        return \"MacBook Pro (Retina, 15-inch, Late 2013/Mid 2014)\";\n        if(ffStrEquals(version, \"11,1\"))        return \"MacBook Pro (Retina, 13-inch, Late 2013/Mid 2014)\";\n        if(ffStrEquals(version, \"10,2\"))        return \"MacBook Pro (Retina, 13-inch, Late 2012/Early 2013)\";\n        if(ffStrEquals(version, \"10,1\"))        return \"MacBook Pro (Retina, 15-inch, Mid 2012/Early 2013)\";\n        if(ffStrEquals(version, \"9,2\"))         return \"MacBook Pro (13-inch, Mid 2012)\";\n        if(ffStrEquals(version, \"9,1\"))         return \"MacBook Pro (15-inch, Mid 2012)\";\n        if(ffStrEquals(version, \"8,3\"))         return \"MacBook Pro (17-inch, 2011)\";\n        if(ffStrEquals(version, \"8,2\"))         return \"MacBook Pro (15-inch, 2011)\";\n        if(ffStrEquals(version, \"8,1\"))         return \"MacBook Pro (13-inch, 2011)\";\n        if(ffStrEquals(version, \"7,1\"))         return \"MacBook Pro (13-inch, Mid 2010)\";\n        if(ffStrEquals(version, \"6,2\"))         return \"MacBook Pro (15-inch, Mid 2010)\";\n        if(ffStrEquals(version, \"6,1\"))         return \"MacBook Pro (17-inch, Mid 2010)\";\n        if(ffStrEquals(version, \"5,5\"))         return \"MacBook Pro (13-inch, Mid 2009)\";\n        if(ffStrEquals(version, \"5,3\"))         return \"MacBook Pro (15-inch, Mid 2009)\";\n        if(ffStrEquals(version, \"5,2\"))         return \"MacBook Pro (17-inch, Mid/Early 2009)\";\n        if(ffStrEquals(version, \"5,1\"))         return \"MacBook Pro (15-inch, Late 2008)\";\n        if(ffStrEquals(version, \"4,1\"))         return \"MacBook Pro (17/15-inch, Early 2008)\";\n    }\n    else if(ffStrbufStartsWithS(hwModel, \"MacBookAir\"))\n    {\n        const char* version = hwModel->chars + strlen(\"MacBookAir\");\n        if(ffStrEquals(version, \"10,1\"))        return \"MacBook Air (M1, 2020)\";\n        if(ffStrEquals(version, \"9,1\"))         return \"MacBook Air (Retina, 13-inch, 2020)\";\n        if(ffStrEquals(version, \"8,2\"))         return \"MacBook Air (Retina, 13-inch, 2019)\";\n        if(ffStrEquals(version, \"8,1\"))         return \"MacBook Air (Retina, 13-inch, 2018)\";\n        if(ffStrEquals(version, \"7,2\"))         return \"MacBook Air (13-inch, Early 2015/2017)\";\n        if(ffStrEquals(version, \"7,1\"))         return \"MacBook Air (11-inch, Early 2015)\";\n        if(ffStrEquals(version, \"6,2\"))         return \"MacBook Air (13-inch, Mid 2013/Early 2014)\";\n        if(ffStrEquals(version, \"6,1\"))         return \"MacBook Air (11-inch, Mid 2013/Early 2014)\";\n        if(ffStrEquals(version, \"5,2\"))         return \"MacBook Air (13-inch, Mid 2012)\";\n        if(ffStrEquals(version, \"5,1\"))         return \"MacBook Air (11-inch, Mid 2012)\";\n        if(ffStrEquals(version, \"4,2\"))         return \"MacBook Air (13-inch, Mid 2011)\";\n        if(ffStrEquals(version, \"4,1\"))         return \"MacBook Air (11-inch, Mid 2011)\";\n        if(ffStrEquals(version, \"3,2\"))         return \"MacBook Air (13-inch, Late 2010)\";\n        if(ffStrEquals(version, \"3,1\"))         return \"MacBook Air (11-inch, Late 2010)\";\n        if(ffStrEquals(version, \"2,1\"))         return \"MacBook Air (Mid 2009)\";\n    }\n    else if(ffStrbufStartsWithS(hwModel, \"Macmini\"))\n    {\n        const char* version = hwModel->chars + strlen(\"Macmini\");\n        if(ffStrEquals(version, \"9,1\"))         return \"Mac mini (M1, 2020)\";\n        if(ffStrEquals(version, \"8,1\"))         return \"Mac mini (2018)\";\n        if(ffStrEquals(version, \"7,1\"))         return \"Mac mini (Mid 2014)\";\n        if(ffStrEquals(version, \"6,1\") ||\n           ffStrEquals(version, \"6,2\"))         return \"Mac mini (Late 2012)\";\n        if(ffStrEquals(version, \"5,1\") ||\n           ffStrEquals(version, \"5,2\"))         return \"Mac mini (Mid 2011)\";\n        if(ffStrEquals(version, \"4,1\"))         return \"Mac mini (Mid 2010)\";\n        if(ffStrEquals(version, \"3,1\"))         return \"Mac mini (Early/Late 2009)\";\n    }\n    else if(ffStrbufStartsWithS(hwModel, \"MacBook\"))\n    {\n        const char* version = hwModel->chars + strlen(\"MacBook\");\n        if(ffStrEquals(version, \"10,1\"))        return \"MacBook (Retina, 12-inch, 2017)\";\n        if(ffStrEquals(version, \"9,1\"))         return \"MacBook (Retina, 12-inch, Early 2016)\";\n        if(ffStrEquals(version, \"8,1\"))         return \"MacBook (Retina, 12-inch, Early 2015)\";\n        if(ffStrEquals(version, \"7,1\"))         return \"MacBook (13-inch, Mid 2010)\";\n        if(ffStrEquals(version, \"6,1\"))         return \"MacBook (13-inch, Late 2009)\";\n        if(ffStrEquals(version, \"5,2\"))         return \"MacBook (13-inch, Early/Mid 2009)\";\n    }\n    else if(ffStrbufStartsWithS(hwModel, \"MacPro\"))\n    {\n        const char* version = hwModel->chars + strlen(\"MacPro\");\n        if(ffStrEquals(version, \"7,1\"))         return \"Mac Pro (2019)\";\n        if(ffStrEquals(version, \"6,1\"))         return \"Mac Pro (Late 2013)\";\n        if(ffStrEquals(version, \"5,1\"))         return \"Mac Pro (Mid 2010 - Mid 2012)\";\n        if(ffStrEquals(version, \"4,1\"))         return \"Mac Pro (Early 2009)\";\n    }\n    else if(ffStrbufStartsWithS(hwModel, \"Mac\"))\n    {\n        const char* version = hwModel->chars + strlen(\"Mac\");\n        if(ffStrEquals(version, \"17,2\"))        return \"MacBook Pro (14-inch, M5)\";\n        if(ffStrEquals(version, \"16,13\"))       return \"MacBook Air (15-inch, M4, 2025)\";\n        if(ffStrEquals(version, \"16,12\"))       return \"MacBook Air (13-inch, M4, 2025)\";\n        if(ffStrEquals(version, \"16,11\") ||\n           ffStrEquals(version, \"16,10\"))       return \"Mac Mini (2024)\";\n        if(ffStrEquals(version, \"16,9\"))        return \"Mac Studio (M4 Max, 2025)\";\n        if(ffStrEquals(version, \"16,3\"))        return \"iMac (24-inch, 2024, Four Thunderbolt / USB 4 ports)\";\n        if(ffStrEquals(version, \"16,2\"))        return \"iMac (24-inch, 2024, Two Thunderbolt / USB 4 ports)\";\n        if(ffStrEquals(version, \"16,1\"))        return \"MacBook Pro (14-inch, 2024, Three Thunderbolt 4 ports)\";\n        if(ffStrEquals(version, \"16,6\") ||\n           ffStrEquals(version, \"16,8\"))        return \"MacBook Pro (14-inch, 2024, Three Thunderbolt 5 ports)\";\n        if(ffStrEquals(version, \"16,7\") ||\n           ffStrEquals(version, \"16,5\"))        return \"MacBook Pro (16-inch, 2024, Three Thunderbolt 5 ports)\";\n        if(ffStrEquals(version, \"15,14\"))       return \"Mac Studio (M3 Ultra, 2025)\";\n        if(ffStrEquals(version, \"15,13\"))       return \"MacBook Air (15-inch, M3, 2024)\";\n        if(ffStrEquals(version, \"15,12\"))       return \"MacBook Air (13-inch, M3, 2024)\";\n        if(ffStrEquals(version, \"15,3\"))        return \"MacBook Pro (14-inch, Nov 2023, Two Thunderbolt / USB 4 ports)\";\n        if(ffStrEquals(version, \"15,4\"))        return \"iMac (24-inch, 2023, Two Thunderbolt / USB 4 ports)\";\n        if(ffStrEquals(version, \"15,5\"))        return \"iMac (24-inch, 2023, Two Thunderbolt / USB 4 ports, Two USB 3 ports)\";\n        if(ffStrEquals(version, \"15,6\") ||\n           ffStrEquals(version, \"15,8\") ||\n           ffStrEquals(version, \"15,10\"))       return \"MacBook Pro (14-inch, Nov 2023, Three Thunderbolt 4 ports)\";\n        if(ffStrEquals(version, \"15,7\") ||\n           ffStrEquals(version, \"15,9\") ||\n           ffStrEquals(version, \"15,11\"))       return \"MacBook Pro (16-inch, Nov 2023, Three Thunderbolt 4 ports)\";\n        if(ffStrEquals(version, \"14,15\"))       return \"MacBook Air (15-inch, M2, 2023)\";\n        if(ffStrEquals(version, \"14,14\"))       return \"Mac Studio (M2 Ultra, 2023, Two Thunderbolt 4 front ports)\";\n        if(ffStrEquals(version, \"14,13\"))       return \"Mac Studio (M2 Max, 2023, Two USB-C front ports)\";\n        if(ffStrEquals(version, \"14,8\"))        return \"Mac Pro (2023)\";\n        if(ffStrEquals(version, \"14,6\") ||\n           ffStrEquals(version, \"14,10\"))       return \"MacBook Pro (16-inch, 2023)\";\n        if(ffStrEquals(version, \"14,5\") ||\n           ffStrEquals(version, \"14,9\"))        return \"MacBook Pro (14-inch, 2023)\";\n        if(ffStrEquals(version, \"14,3\"))        return \"Mac mini (M2, 2023, Two Thunderbolt 4 ports)\";\n        if(ffStrEquals(version, \"14,12\"))       return \"Mac mini (M2 Pro, 2023, Four Thunderbolt 4 ports)\";\n        if(ffStrEquals(version, \"14,7\"))        return \"MacBook Pro (13-inch, M2, 2022)\";\n        if(ffStrEquals(version, \"14,2\"))        return \"MacBook Air (M2, 2022)\";\n        if(ffStrEquals(version, \"13,1\"))        return \"Mac Studio (M1 Max, 2022, Two USB-C front ports)\";\n        if(ffStrEquals(version, \"13,2\"))        return \"Mac Studio (M1 Ultra, 2022, Two Thunderbolt 4 front ports)\";\n    }\n    else if(ffStrbufStartsWithS(hwModel, \"iMac\"))\n    {\n        const char* version = hwModel->chars + strlen(\"iMac\");\n        if(ffStrEquals(version, \"21,1\"))        return \"iMac (24-inch, M1, 2021, Two Thunderbolt / USB 4 ports, Two USB 3 ports)\";\n        if(ffStrEquals(version, \"21,2\"))        return \"iMac (24-inch, M1, 2021, Two Thunderbolt / USB 4 ports)\";\n        if(ffStrEquals(version, \"20,1\") ||\n           ffStrEquals(version, \"20,2\"))        return \"iMac (Retina 5K, 27-inch, 2020)\";\n        if(ffStrEquals(version, \"19,1\"))        return \"iMac (Retina 5K, 27-inch, 2019)\";\n        if(ffStrEquals(version, \"19,2\"))        return \"iMac (Retina 4K, 21.5-inch, 2019)\";\n        if(ffStrEquals(version, \"Pro1,1\"))      return \"iMac Pro (2017)\";\n        if(ffStrEquals(version, \"18,3\"))        return \"iMac (Retina 5K, 27-inch, 2017)\";\n        if(ffStrEquals(version, \"18,2\"))        return \"iMac (Retina 4K, 21.5-inch, 2017)\";\n        if(ffStrEquals(version, \"18,1\"))        return \"iMac (21.5-inch, 2017)\";\n        if(ffStrEquals(version, \"17,1\"))        return \"iMac (Retina 5K, 27-inch, Late 2015)\";\n        if(ffStrEquals(version, \"16,2\"))        return \"iMac (Retina 4K, 21.5-inch, Late 2015)\";\n        if(ffStrEquals(version, \"16,1\"))        return \"iMac (21.5-inch, Late 2015)\";\n        if(ffStrEquals(version, \"15,1\"))        return \"iMac (Retina 5K, 27-inch, Late 2014 - Mid 2015)\";\n        if(ffStrEquals(version, \"14,4\"))        return \"iMac (21.5-inch, Mid 2014)\";\n        if(ffStrEquals(version, \"14,2\"))        return \"iMac (27-inch, Late 2013)\";\n        if(ffStrEquals(version, \"14,1\"))        return \"iMac (21.5-inch, Late 2013)\";\n        if(ffStrEquals(version, \"13,2\"))        return \"iMac (27-inch, Late 2012)\";\n        if(ffStrEquals(version, \"13,1\"))        return \"iMac (21.5-inch, Late 2012)\";\n        if(ffStrEquals(version, \"12,2\"))        return \"iMac (27-inch, Mid 2011)\";\n        if(ffStrEquals(version, \"12,1\"))        return \"iMac (21.5-inch, Mid 2011)\";\n        if(ffStrEquals(version, \"11,3\"))        return \"iMac (27-inch, Mid 2010)\";\n        if(ffStrEquals(version, \"11,2\"))        return \"iMac (21.5-inch, Mid 2010)\";\n        if(ffStrEquals(version, \"10,1\"))        return \"iMac (27/21.5-inch, Late 2009)\";\n        if(ffStrEquals(version, \"9,1\"))         return \"iMac (24/20-inch, Early 2009)\";\n    }\n    return NULL;\n}\n\n#ifdef __x86_64__\nbool ffHostDetectMac(FFHostResult* host)\n{\n    if (ffStrbufStartsWithS(&host->family, \"Mac\") && ffStrbufEqualS(&host->vendor, \"Apple Inc.\"))\n    {\n        const char* productName = ffHostGetMacProductNameWithHwModel(&host->name);\n        if (productName)\n        {\n            ffStrbufDestroy(&host->family);\n            ffStrbufInitMove(&host->family, &host->name);\n            ffStrbufSetStatic(&host->name, productName);\n            return true;\n        }\n    }\n    return false;\n}\n#endif\n"
  },
  {
    "path": "src/detection/host/host_nbsd.c",
    "content": "#include \"host.h\"\n#include \"common/sysctl.h\"\n#include \"common/smbiosHelper.h\"\n\nconst char* ffDetectHost(FFHostResult* host)\n{\n    const char* error = NULL;\n    if ((error = ffSysctlGetString(\"machdep.dmi.system-product\", &host->name)))\n        return error;\n    ffCleanUpSmbiosValue(&host->name);\n    if (ffSysctlGetString(\"machdep.dmi.system-vendor\", &host->vendor) == NULL)\n        ffCleanUpSmbiosValue(&host->vendor);\n    if (ffSysctlGetString(\"machdep.dmi.system-version\", &host->version) == NULL)\n        ffCleanUpSmbiosValue(&host->version);\n    if (ffSysctlGetString(\"machdep.dmi.system-serial\", &host->serial) == NULL)\n        ffCleanUpSmbiosValue(&host->serial);\n    if (ffSysctlGetString(\"machdep.dmi.system-uuid\", &host->uuid) == NULL)\n        ffCleanUpSmbiosValue(&host->uuid);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/host/host_nosupport.c",
    "content": "#include \"host.h\"\n\nconst char* ffDetectHost(FF_MAYBE_UNUSED FFHostResult* host)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/host/host_obsd.c",
    "content": "#include \"host.h\"\n#include \"common/sysctl.h\"\n#include \"common/smbiosHelper.h\"\n\nconst char* ffDetectHost(FFHostResult* host)\n{\n    const char* error = NULL;\n    if ((error = ffSysctlGetString(CTL_HW, HW_PRODUCT, &host->name)))\n        return error;\n    ffCleanUpSmbiosValue(&host->name);\n    if (ffSysctlGetString(CTL_HW, HW_VENDOR, &host->vendor) == NULL)\n        ffCleanUpSmbiosValue(&host->vendor);\n    if (ffSysctlGetString(CTL_HW, HW_VERSION, &host->version) == NULL)\n        ffCleanUpSmbiosValue(&host->version);\n    if (ffSysctlGetString(CTL_HW, HW_SERIALNO, &host->serial) == NULL)\n        ffCleanUpSmbiosValue(&host->serial);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/host/host_windows.c",
    "content": "#include \"host.h\"\n#include \"common/smbiosHelper.h\"\n\ntypedef struct FFSmbiosSystemInfo\n{\n    FFSmbiosHeader Header;\n\n    uint8_t Manufacturer; // string\n    uint8_t ProductName; // string\n    uint8_t Version; // string\n    uint8_t SerialNumber; // string\n\n    // 2.1+\n    struct {\n        uint32_t TimeLow;\n        uint16_t TimeMid;\n        uint16_t TimeHighAndVersion;\n        uint8_t ClockSeqHiAndReserved;\n        uint8_t ClockSeqLow;\n        uint8_t Node[6];\n    } __attribute__((__packed__)) UUID; // varies\n    uint8_t WakeUpType; // enum\n\n    // 2.4+\n    uint8_t SKUNumber; // string\n    uint8_t Family; // string\n} __attribute__((__packed__)) FFSmbiosSystemInfo;\n\nstatic_assert(offsetof(FFSmbiosSystemInfo, Family) == 0x1A,\n    \"FFSmbiosSystemInfo: Wrong struct alignment\");\n\nconst char* ffDetectHost(FFHostResult* host)\n{\n    const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();\n    if (!smbiosTable)\n        return \"Failed to get SMBIOS data\";\n\n    const FFSmbiosSystemInfo* data = (const FFSmbiosSystemInfo*) (*smbiosTable)[FF_SMBIOS_TYPE_SYSTEM_INFO];\n    if (!data)\n        return \"System information is not found in SMBIOS data\";\n\n    const char* strings = (const char*) data + data->Header.Length;\n\n    ffStrbufSetStatic(&host->vendor, ffSmbiosLocateString(strings, data->Manufacturer));\n    ffCleanUpSmbiosValue(&host->vendor);\n    ffStrbufSetStatic(&host->name, ffSmbiosLocateString(strings, data->ProductName));\n    ffCleanUpSmbiosValue(&host->name);\n    ffStrbufSetStatic(&host->version, ffSmbiosLocateString(strings, data->Version));\n    ffCleanUpSmbiosValue(&host->version);\n    ffStrbufSetStatic(&host->serial, ffSmbiosLocateString(strings, data->SerialNumber));\n    ffCleanUpSmbiosValue(&host->serial);\n\n    static_assert(offsetof(FFSmbiosSystemInfo, UUID) == 0x08, \"FFSmbiosSystemInfo.UUID offset is wrong\");\n    if (data->Header.Length > offsetof(FFSmbiosSystemInfo, UUID))\n    {\n        ffStrbufSetF(&host->uuid, \"%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X\",\n            data->UUID.TimeLow, data->UUID.TimeMid, data->UUID.TimeHighAndVersion,\n            data->UUID.ClockSeqHiAndReserved, data->UUID.ClockSeqLow,\n            data->UUID.Node[0], data->UUID.Node[1], data->UUID.Node[2], data->UUID.Node[3], data->UUID.Node[4], data->UUID.Node[5]);\n    }\n\n    static_assert(offsetof(FFSmbiosSystemInfo, SKUNumber) == 0x19, \"FFSmbiosSystemInfo.SKUNumber offset is wrong\");\n    if (data->Header.Length > offsetof(FFSmbiosSystemInfo, SKUNumber))\n    {\n        ffStrbufSetStatic(&host->sku, ffSmbiosLocateString(strings, data->SKUNumber));\n        ffCleanUpSmbiosValue(&host->sku);\n    }\n\n    if (data->Header.Length > offsetof(FFSmbiosSystemInfo, Family))\n    {\n        ffStrbufSetStatic(&host->family, ffSmbiosLocateString(strings, data->Family));\n        ffCleanUpSmbiosValue(&host->family);\n    }\n\n    #if _WIN64 && __x86_64__ // aarch64 also defines _WIN64\n    ffHostDetectMac(host);\n    #endif\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/icons/icons.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/icons/option.h\"\n\ntypedef struct FFIconsResult\n{\n    FFstrbuf icons1;\n    FFstrbuf icons2;\n} FFIconsResult;\n\n\nconst char* ffDetectIcons(FFIconsResult* result);\n"
  },
  {
    "path": "src/detection/icons/icons_linux.c",
    "content": "#include \"icons.h\"\n#include \"common/parsing.h\"\n#include \"detection/gtk_qt/gtk_qt.h\"\n#include \"detection/displayserver/displayserver.h\"\n\nconst char* ffDetectIcons(FFIconsResult* result)\n{\n    const FFDisplayServerResult* wmde = ffConnectDisplayServer();\n\n    if(ffStrbufIgnCaseEqualS(&wmde->wmProtocolName, FF_WM_PROTOCOL_TTY))\n        return \"Icons aren't supported in TTY\";\n\n    const FFstrbuf* plasma = &ffDetectQt()->icons;\n    const FFstrbuf* gtk2 = &ffDetectGTK2()->icons;\n    const FFstrbuf* gtk3 = &ffDetectGTK3()->icons;\n    const FFstrbuf* gtk4 = &ffDetectGTK4()->icons;\n\n    if(plasma->length == 0 && gtk2->length == 0 && gtk3->length == 0 && gtk4->length == 0)\n        return \"No icons could be found\";\n\n    ffParseGTK(&result->icons2, gtk2, gtk3, gtk4);\n\n    if(plasma->length > 0)\n    {\n        ffStrbufAppend(&result->icons1, plasma);\n        ffStrbufAppendS(&result->icons1, \" [Qt]\");\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/icons/icons_nosupport.c",
    "content": "#include \"icons.h\"\n\nconst char* ffDetectIcons(FF_MAYBE_UNUSED FFIconsResult* result)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/icons/icons_windows.c",
    "content": "#include \"icons.h\"\n#include \"common/windows/registry.h\"\n\nconst char* ffDetectIcons(FFIconsResult* result)\n{\n    FF_AUTO_CLOSE_FD HANDLE hKey = NULL;\n    if(!ffRegOpenKeyForRead(HKEY_CURRENT_USER, L\"Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\HideDesktopIcons\\\\NewStartPanel\", &hKey, NULL) &&\n       !ffRegOpenKeyForRead(HKEY_CURRENT_USER, L\"Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\HideDesktopIcons\\\\ClassicStartMenu\", &hKey, NULL))\n        return \"ffRegOpenKeyForRead(Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\HideDesktopIcons\\\\{NewStartPanel|ClassicStartMenu}) failed\";\n\n    // Whether these icons are hidden\n    uint32_t ThisPC = 1, UsersFiles = 1, RemoteNetwork = 1, RecycleBin = 0 /* Shown by default */, ControlPanel = 1;\n    ffRegReadUint(hKey, L\"{20D04FE0-3AEA-1069-A2D8-08002B30309D}\", &ThisPC, NULL);\n    ffRegReadUint(hKey, L\"{59031a47-3f72-44a7-89c5-5595fe6b30ee}\", &UsersFiles, NULL);\n    ffRegReadUint(hKey, L\"{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}\", &RemoteNetwork, NULL);\n    ffRegReadUint(hKey, L\"{645FF040-5081-101B-9F08-00AA002F954E}\", &RecycleBin, NULL);\n    ffRegReadUint(hKey, L\"{5399E694-6CE5-4D6C-8FCE-1D8870FDCBA0}\", &ControlPanel, NULL);\n\n    if (ThisPC && UsersFiles && RemoteNetwork && RecycleBin && ControlPanel)\n        return \"All icons are hidden\";\n\n    if (!ThisPC)\n        ffStrbufAppendS(&result->icons1, \"This PC, \");\n    if (!UsersFiles)\n        ffStrbufAppendS(&result->icons1, \"User's Files\");\n    ffStrbufTrimRight(&result->icons1, ' ');\n    ffStrbufTrimRight(&result->icons1, ',');\n\n    if (!RemoteNetwork)\n        ffStrbufAppendS(&result->icons2, \"Remote Network, \");\n    if (!RecycleBin)\n        ffStrbufAppendS(&result->icons2, \"Recycle Bin, \");\n    if (!ControlPanel)\n        ffStrbufAppendS(&result->icons2, \"Control Panel\");\n    ffStrbufTrimRight(&result->icons2, ' ');\n    ffStrbufTrimRight(&result->icons2, ',');\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/initsystem/initsystem.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/initsystem/option.h\"\n\ntypedef struct FFInitSystemResult\n{\n    FFstrbuf name;\n    FFstrbuf exe;\n    FFstrbuf version;\n    uint32_t pid;\n} FFInitSystemResult;\n\nconst char* ffDetectInitSystem(FFInitSystemResult* result);\n"
  },
  {
    "path": "src/detection/initsystem/initsystem_haiku.c",
    "content": "#include \"initsystem.h\"\n#include \"common/stringUtils.h\"\n#include \"common/haiku/version.h\"\n#include \"common/io.h\"\n\n#include <OS.h>\n#include <unistd.h>\n\nconst char* ffDetectInitSystem(FFInitSystemResult* result)\n{\n    // Since it runs first, registrar does not know about it,\n    // so we can't query be_roster for it.\n    const char* path = \"/boot/system/servers/launch_daemon\";\n    if (!ffPathExists(path, FF_PATHTYPE_FILE))\n        return \"launch_daemon is not found\";\n\n    ffStrbufSetStatic(&result->exe, path);\n    ffStrbufSetStatic(&result->name, \"launch_daemon\");\n    result->pid = 0;\n\n    team_info teamInfo;\n    int32 cookie = 0;\n    while (get_next_team_info(&cookie, &teamInfo) == B_OK)\n    {\n        if (ffStrEquals(teamInfo.args, path))\n        {\n            result->pid = (uint32_t) teamInfo.team;\n            break;\n        }\n    }\n\n    if (instance.config.general.detectVersion)\n        ffGetFileVersion(path, &result->version);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/initsystem/initsystem_linux.c",
    "content": "#include \"initsystem.h\"\n#include \"common/processing.h\"\n#include \"common/binary.h\"\n#include \"common/stringUtils.h\"\n\n#include <libgen.h>\n#include <unistd.h>\n\nFF_MAYBE_UNUSED static bool extractSystemdVersion(const char* str, uint32_t len, void* userdata)\n{\n    if (!ffStrStartsWith(str, \"systemd \")) return true;\n    const char* pstart = str + strlen(\"systemd \");\n    const char* pend = memmem(pstart, len - strlen(\"systemd \"), \" running in \", strlen(\" running in \"));\n    if (!pend) return true;\n    ffStrbufSetNS((FFstrbuf*) userdata, (uint32_t) (pend - pstart), pstart);\n    return false;\n}\n\nconst char* ffDetectInitSystem(FFInitSystemResult* result)\n{\n    const char* error = ffProcessGetBasicInfoLinux((int) result->pid, &result->name, NULL, NULL);\n    if (error)\n    {\n        #ifdef __ANDROID__\n        if (access(\"/system/bin/init\", F_OK) == 0)\n        {\n            ffStrbufSetStatic(&result->exe, \"/system/bin/init\");\n            ffStrbufSetStatic(&result->name, \"init\");\n            return NULL;\n        }\n        #endif\n        return error;\n    }\n\n    const char* _;\n    // In linux /proc/1/exe is not readable\n    ffProcessGetInfoLinux((int) result->pid, &result->name, &result->exe, &_, NULL);\n    if (result->exe.chars[0] == '/')\n    {\n        // In some old system, /sbin/init is a symlink\n        char buf[PATH_MAX];\n        if (realpath(result->exe.chars, buf))\n        {\n            ffStrbufSetS(&result->exe, buf);\n            ffStrbufSetS(&result->name, basename(result->exe.chars));\n        }\n    }\n\n    if (instance.config.general.detectVersion)\n    {\n        #if (defined(__linux__) && !defined(__ANDROID__)) || defined(__GNU__)\n        if (ffStrbufEqualS(&result->name, \"systemd\"))\n        {\n            ffBinaryExtractStrings(result->exe.chars, extractSystemdVersion, &result->version, (uint32_t) strlen(\"systemd 0.0 running in x\"));\n            if (result->version.length == 0)\n            {\n                if (ffProcessAppendStdOut(&result->version, (char* const[]) {\n                    ffStrbufEndsWithS(&result->exe, \"/systemd\") ? result->exe.chars : \"systemctl\", // use exe path in case users have another systemd installed\n                    \"--version\",\n                    NULL,\n                }) == NULL && result->version.length)\n                {\n                    uint32_t iStart = ffStrbufFirstIndexC(&result->version, '(');\n                    if (iStart < result->version.length)\n                    {\n                        uint32_t iEnd = ffStrbufNextIndexC(&result->version, iStart + 1, ')');\n                        ffStrbufSubstrBefore(&result->version, iEnd);\n                        ffStrbufSubstrAfter(&result->version, iStart);\n                    }\n                }\n            }\n        }\n        else if (ffStrbufEqualS(&result->name, \"dinit\"))\n        {\n            if (ffProcessAppendStdOut(&result->version, (char* const[]) {\n                ffStrbufEndsWithS(&result->exe, \"/dinit\") ? result->exe.chars : \"dinit\",\n                \"--version\",\n                NULL,\n            }) == NULL && result->version.length)\n            {\n                // Dinit version 0.18.0.\n                ffStrbufSubstrBeforeFirstC(&result->version, '\\n');\n                ffStrbufTrimRight(&result->version, '.');\n                ffStrbufSubstrAfterLastC(&result->version, ' ');\n            }\n        }\n        else if (ffStrbufEqualS(&result->name, \"shepherd\"))\n        {\n           if (ffProcessAppendStdOut(&result->version, (char* const[]) {\n              ffStrbufEndsWithS(&result->exe, \"/shepherd\") ? result->exe.chars : \"shepherd\",\n              \"--version\",\n                NULL,\n            }) == NULL && result->version.length)\n            {\n                // shepherd (GNU Shepherd) 1.0.6\n                // The first line in the output might not contain the version\n                if (!ffStrbufStartsWithS(&result->version, \"shepherd\"))\n                    ffStrbufSubstrAfterFirstC(&result->version, '\\n');\n\n                ffStrbufSubstrBeforeFirstC(&result->version, '\\n');\n                ffStrbufSubstrAfterLastC(&result->version, ' ');\n            }\n        }\n        #elif __APPLE__\n        if (ffStrbufEqualS(&result->name, \"launchd\"))\n        {\n            if (ffProcessAppendStdOut(&result->version, (char* const[]) {\n                \"/bin/launchctl\",\n                \"version\",\n                NULL,\n            }) == NULL && result->version.length)\n            {\n                uint32_t iStart = ffStrbufFirstIndexS(&result->version, \"Version \");\n                if (iStart < result->version.length)\n                {\n                    iStart += (uint32_t) strlen(\"Version\");\n                    uint32_t iEnd = ffStrbufNextIndexC(&result->version, iStart + 1, ':');\n                    ffStrbufSubstrBefore(&result->version, iEnd);\n                    ffStrbufSubstrAfter(&result->version, iStart);\n                }\n            }\n        }\n        #endif\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/initsystem/initsystem_nosupport.c",
    "content": "#include \"initsystem.h\"\n\nconst char* ffDetectInitSystem(FF_MAYBE_UNUSED FFInitSystemResult* result)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/keyboard/keyboard.h",
    "content": "#include \"fastfetch.h\"\n\ntypedef struct FFKeyboardDevice\n{\n    FFstrbuf serial;\n    FFstrbuf name;\n} FFKeyboardDevice;\n\nconst char* ffDetectKeyboard(FFlist* devices /* List of FFKeyboardDevice */);\n"
  },
  {
    "path": "src/detection/keyboard/keyboard_apple.c",
    "content": "#include \"keyboard.h\"\n#include \"common/apple/cf_helpers.h\"\n#include \"common/mallocHelper.h\"\n\n#include <IOKit/IOKitLib.h>\n#include <IOKit/hid/IOHIDLib.h>\n\nstatic void enumSet(IOHIDDeviceRef value, FFlist* results)\n{\n    FFKeyboardDevice* device = (FFKeyboardDevice*) ffListAdd(results);\n    ffStrbufInit(&device->serial);\n    ffStrbufInit(&device->name);\n\n    CFStringRef product = IOHIDDeviceGetProperty(value, CFSTR(kIOHIDProductKey));\n    ffCfStrGetString(product, &device->name);\n\n    CFStringRef serialNumber = IOHIDDeviceGetProperty(value, CFSTR(kIOHIDSerialNumberKey));\n    ffCfStrGetString(serialNumber, &device->serial);\n}\n\nconst char* ffDetectKeyboard(FFlist* devices /* List of FFKeyboardDevice */)\n{\n    IOHIDManagerRef FF_CFTYPE_AUTO_RELEASE manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);\n    if (IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone) != kIOReturnSuccess)\n        return \"IOHIDManagerOpen() failed\";\n\n    CFDictionaryRef FF_CFTYPE_AUTO_RELEASE matching1 = CFDictionaryCreate(kCFAllocatorDefault, (const void **)(CFStringRef[]){\n        CFSTR(kIOHIDDeviceUsagePageKey),\n        CFSTR(kIOHIDDeviceUsageKey)\n    }, (const void **)(CFNumberRef[]){\n        ffCfCreateInt(kHIDPage_GenericDesktop),\n        ffCfCreateInt(kHIDUsage_GD_Keyboard)\n    }, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);\n    IOHIDManagerSetDeviceMatching(manager, matching1);\n\n    CFSetRef FF_CFTYPE_AUTO_RELEASE set = IOHIDManagerCopyDevices(manager);\n    if (set)\n        CFSetApplyFunction(set, (CFSetApplierFunction) &enumSet, devices);\n    IOHIDManagerClose(manager, kIOHIDOptionsTypeNone);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/keyboard/keyboard_bsd.c",
    "content": "#include \"keyboard.h\"\n#include \"common/io.h\"\n\n#include <stdio.h>\n#include <fcntl.h>\n#include <usbhid.h>\n#include <sys/kbio.h>\n\n#if __has_include(<dev/usb/usb_ioctl.h>)\n    #include <dev/usb/usb_ioctl.h> // FreeBSD\n#else\n    #include <bus/u4b/usb_ioctl.h> // DragonFly\n#endif\n\nstatic const char* detectByIoctl(FFlist* devices)\n{\n    keyboard_info_t kbdInfo;\n    if (ioctl(STDIN_FILENO, KDGKBINFO, &kbdInfo) != 0)\n        return \"ioctl(KDGKBINFO) failed\";\n\n    FFKeyboardDevice* device = (FFKeyboardDevice*) ffListAdd(devices);\n\n    switch (kbdInfo.kb_type) {\n        case KB_84:\n            ffStrbufInitS(&device->name, \"AT 84-key keyboard\");\n            break;\n        case KB_101:\n            ffStrbufInitS(&device->name, \"AT 101/102-key keyboard\");\n            break;\n        default:\n            ffStrbufInitS(&device->name, \"Unknown keyboard\");\n            break;\n    }\n\n    ffStrbufAppendF(&device->name, \" (kbd%d)\", kbdInfo.kb_index);\n\n    ffStrbufInit(&device->serial);\n    return NULL;\n}\n\n#define MAX_UHID_KBDS 64\n\nstatic const char* detectByUsbhid(FFlist* devices)\n{\n    char path[16];\n    for (int i = 0; i < MAX_UHID_KBDS; i++)\n    {\n        snprintf(path, ARRAY_SIZE(path), \"/dev/uhid%d\", i);\n        FF_AUTO_CLOSE_FD int fd = open(path, O_RDONLY | O_CLOEXEC);\n        if (fd < 0)\n        {\n            if (errno == ENOENT)\n                break; // No more devices\n            continue; // Device not found\n        }\n\n        report_desc_t repDesc = hid_get_report_desc(fd);\n        if (!repDesc) continue;\n\n        int reportId = hid_get_report_id(fd);\n\n        struct hid_data* hData = hid_start_parse(repDesc, 0, reportId);\n        if (hData)\n        {\n            struct hid_item hItem;\n            while (hid_get_item(hData, &hItem) > 0)\n            {\n                if (HID_PAGE(hItem.usage) != 1 || HID_USAGE(hItem.usage) != 6) continue;\n\n                struct usb_device_info di;\n                if (ioctl(fd, USB_GET_DEVICEINFO, &di) != -1)\n                {\n                    FFKeyboardDevice* device = (FFKeyboardDevice*) ffListAdd(devices);\n                    ffStrbufInitS(&device->serial, di.udi_serial);\n                    ffStrbufInitS(&device->name, di.udi_product);\n                }\n            }\n            hid_end_parse(hData);\n        }\n\n        hid_dispose_report_desc(repDesc);\n    }\n\n    return NULL;\n}\n\nconst char* ffDetectKeyboard(FFlist* devices /* List of FFKeyboardDevice */)\n{\n    detectByUsbhid(devices);\n    if (devices->length > 0)\n        return NULL;\n    return detectByIoctl(devices);\n}\n"
  },
  {
    "path": "src/detection/keyboard/keyboard_haiku.cpp",
    "content": "extern \"C\" {\n#include \"keyboard.h\"\n}\n\n#include <interface/Input.h>\n#include <support/List.h>\n\nconst char* ffDetectKeyboard(FFlist* devices /* List of FFKeyboardDevice */)\n{\n    BList list;\n\n    if (get_input_devices(&list) != B_OK)\n        return \"get_input_devices() failed\";\n\n    for (int32 i = 0, n = list.CountItems(); i < n; i++)\n    {\n        BInputDevice *device = (BInputDevice *) list.ItemAt(i);\n        if (device->Type() != B_KEYBOARD_DEVICE || !device->IsRunning())\n            continue;\n\n        FFKeyboardDevice* item = (FFKeyboardDevice*) ffListAdd(devices);\n        ffStrbufInit(&item->serial);\n        ffStrbufInitS(&item->name, device->Name());\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/keyboard/keyboard_linux.c",
    "content": "#include \"keyboard.h\"\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n\n#include <linux/input-event-codes.h>\n\nconst char* ffDetectKeyboard(FFlist* devices /* List of FFKeyboardDevice */)\n{\n    // Parse /proc/bus/input/devices to find keyboards with a \"kbd\" handler.\n    // This detects both wired and Bluetooth keyboards uniformly.\n    FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate();\n    if (!ffAppendFileBuffer(\"/proc/bus/input/devices\", &content))\n        return \"ffAppendFileBuffer(\\\"/proc/bus/input/devices\\\") == NULL\";\n\n    FFstrbuf kbd = ffStrbufCreateStatic(\"kbd\");\n\n    FFKeyboardDevice device = {\n        .name = ffStrbufCreate(),\n        .serial = ffStrbufCreate()\n    };\n\n    char* line = NULL;\n    size_t len = 0;\n    while (ffStrbufGetline(&line, &len, &content))\n    {\n        switch (line[0])\n        {\n            case 'N': {\n                const uint32_t prefixLen = strlen(\"N: Name=\");\n                if (__builtin_expect(len <= prefixLen, false)) continue;\n                const char* name = line + prefixLen;\n                const uint32_t nameLen = (uint32_t) len - prefixLen;\n                ffStrbufSetNS(&device.name, nameLen, name);\n                ffStrbufTrim(&device.name, '\"');\n                continue;\n            }\n            case 'H': {\n                const uint32_t prefixLen = strlen(\"H: Handlers=\");\n                if (__builtin_expect(len <= prefixLen, false)) continue;\n                const char* handlers = line + prefixLen;\n                const uint32_t handlersLen = (uint32_t) len - prefixLen;\n                if (!ffStrbufMatchSeparatedNS(&kbd, handlersLen, handlers, ' '))\n                    goto skipDevice;\n                continue;\n            }\n            case 'B': {\n                const char* bits = line + strlen(\"B: \");\n                if (ffStrStartsWith(bits, \"EV=\"))\n                {\n                    // Check EV_REP (auto-repeat, bit 20) to filter pseudo-keyboards (Power Button, PC Speaker).\n                    const char* evBits = bits + strlen(\"EV=\");\n                    uint64_t val = strtoull(evBits, NULL, 16);\n                    if (!(val & (1ULL << EV_REP)))\n                        goto skipDevice;\n                }\n                else if (ffStrStartsWith(bits, \"KEY=\"))\n                {\n                    // Check KEY_A (bit 30) to filter media remotes and headset controls.\n                    // The key capability bitmap is space-separated hex longs, MSB first;\n                    // KEY_A falls in the last (least significant) word on all architectures.\n                    const char* keyBits = bits + strlen(\"KEY=\");\n                    const char* lastWord = memrchr(keyBits, ' ', len - (size_t) (keyBits - line));\n                    lastWord = lastWord ? lastWord + 1 : keyBits;\n\n                    uint64_t val = strtoull(lastWord, NULL, 16);\n                    if (!(val & (1ULL << KEY_A)))\n                        goto skipDevice;\n                }\n                continue;\n            }\n            case 'U': {\n                const uint32_t prefixLen = strlen(\"U: Uniq=\");\n                if (__builtin_expect(len <= prefixLen, false)) continue;\n                const char* uniq = line + prefixLen;\n                const uint32_t uniqLen =  (uint32_t) len - prefixLen;\n                ffStrbufSetNS(&device.serial, uniqLen, uniq);\n                continue;\n            }\n            case '\\0':\n                // End of device entry; add to list if it has a name.\n                if (device.name.length > 0)\n                {\n                    FFKeyboardDevice* added = (FFKeyboardDevice*) ffListAdd(devices);\n                    ffStrbufInitMove(&added->name, &device.name);\n                    ffStrbufInitMove(&added->serial, &device.serial);\n                }\n                continue;\n            default:\n                continue;\n        }\n\n        skipDevice:\n            // Skip to the end of the current device entry.\n            while (line[0] != '\\0' && ffStrbufGetline(&line, &len, &content))\n                ;\n            // Despite the fn name, it resets the string buffer to initial state\n            ffStrbufDestroy(&device.name);\n            ffStrbufDestroy(&device.serial);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/keyboard/keyboard_nosupport.c",
    "content": "#include \"keyboard.h\"\n\nconst char* ffDetectKeyboard(FF_MAYBE_UNUSED FFlist* devices /* List of FFKeyboardDevice */)\n{\n    return \"No mouse support on this platform\";\n}\n"
  },
  {
    "path": "src/detection/keyboard/keyboard_windows.c",
    "content": "#define INITGUID\n\n#include \"keyboard.h\"\n#include \"common/io.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/windows/unicode.h\"\n\n#include <windows.h>\n#include <hidsdi.h>\n#include <cfgmgr32.h>\n#include <devpkey.h>\n\nconst char* ffDetectKeyboard(FFlist* devices /* List of FFKeyboardDevice */)\n{\n    UINT nDevices = 0;\n    if (GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)))\n        return \"GetRawInputDeviceList(NULL) failed\";\n    if (nDevices == 0)\n        return \"No HID devices found\";\n\n    RAWINPUTDEVICELIST* FF_AUTO_FREE pRawInputDeviceList = (RAWINPUTDEVICELIST*) malloc(sizeof(RAWINPUTDEVICELIST) * nDevices);\n    if ((nDevices = GetRawInputDeviceList(pRawInputDeviceList, &nDevices, sizeof(RAWINPUTDEVICELIST))) == (UINT) -1)\n        return \"GetRawInputDeviceList(pRawInputDeviceList) failed\";\n\n\n    for (UINT i = 0; i < nDevices; ++i)\n    {\n        if (pRawInputDeviceList[i].dwType != RIM_TYPEKEYBOARD) continue;\n\n        HANDLE hDevice = pRawInputDeviceList[i].hDevice;\n\n        RID_DEVICE_INFO rdi;\n        UINT rdiSize = sizeof(rdi);\n        if (GetRawInputDeviceInfoW(hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) == (UINT) -1)\n            continue;\n\n        WCHAR devName[MAX_PATH];\n        UINT nameSize = MAX_PATH;\n        if (GetRawInputDeviceInfoW(hDevice, RIDI_DEVICENAME, devName, &nameSize) == (UINT) -1)\n            continue;\n\n        FFKeyboardDevice* device = (FFKeyboardDevice*) ffListAdd(devices);\n        ffStrbufInit(&device->serial);\n        ffStrbufInit(&device->name);\n\n        wchar_t buffer[MAX_PATH];\n\n        HANDLE FF_AUTO_CLOSE_FD hHidFile = CreateFileW(devName, 0 /* must be 0 instead of GENERIC_READ */, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);\n        if (hHidFile != INVALID_HANDLE_VALUE)\n        {\n            if (HidD_GetProductString(hHidFile, buffer, (ULONG) sizeof(buffer)))\n                ffStrbufSetWS(&device->name, buffer);\n\n            if (HidD_GetSerialNumberString(hHidFile, buffer, sizeof(buffer)))\n                ffStrbufSetWS(&device->serial, buffer);\n        }\n\n        if (!device->name.length)\n        {\n            // https://stackoverflow.com/a/64321096/9976392\n            DEVPROPTYPE propertyType;\n            ULONG propertySize = sizeof(buffer);\n\n            if (CM_Get_Device_Interface_PropertyW(devName, &DEVPKEY_Device_InstanceId, &propertyType, (PBYTE) buffer, &propertySize, 0) == CR_SUCCESS)\n            {\n                DEVINST devInst;\n                if (CM_Locate_DevNodeW(&devInst, buffer, CM_LOCATE_DEVNODE_NORMAL) == CR_SUCCESS)\n                {\n                    propertySize = sizeof(buffer);\n                    if (CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_NAME, &propertyType, (PBYTE) buffer, &propertySize, 0) == CR_SUCCESS)\n                        ffStrbufSetWS(&device->name, buffer);\n                }\n            }\n        }\n\n        if (!device->name.length)\n            ffStrbufSetF(&device->name, \"Unknown device %04X-%04X\", (unsigned) rdi.hid.dwVendorId, (unsigned) rdi.hid.dwProductId);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/libc/libc.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\ntypedef struct FFLibcResult\n{\n    const char* name;\n    const char* version;\n} FFLibcResult;\n\nconst char* ffDetectLibc(FFLibcResult* result);\n"
  },
  {
    "path": "src/detection/libc/libc_android.c",
    "content": "#include \"libc.h\"\n\n#define FF_STR_INDIR(x) #x\n#define FF_STR(x) FF_STR_INDIR(x)\n\n#include <features.h>\n\nconst char* ffDetectLibc(FFLibcResult* result)\n{\n#if __ANDROID_NDK__\n    result->name = \"ndk-bionic\";\n    result->version = FF_STR(__NDK_MAJOR__) \".\" FF_STR(__NDK_MINOR__) \".\" FF_STR(__NDK_BUILD__)\n\n    #if __NDK_BETA__\n    \"-beta\" FF_STR(__NDK_BETA__)\n    #elif __NDK_CANARY__\n    \"-canary\"\n    #endif\n\n    ;\n    return NULL;\n#else\n    return \"Unknown Android libc\";\n#endif\n}\n"
  },
  {
    "path": "src/detection/libc/libc_apple.c",
    "content": "#include \"libc.h\"\n\nconst char* ffDetectLibc(FFLibcResult* result)\n{\n    result->name = \"libSystem\";\n\n#ifdef FF_LIBSYSTEM_VERSION\n    result->version = FF_LIBSYSTEM_VERSION;\n#else\n    result->version = NULL;\n#endif\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/libc/libc_bsd.c",
    "content": "#include \"libc.h\"\n\nconst char* ffDetectLibc(FFLibcResult* result)\n{\n    result->name = \"Unknown\";\n    result->version = NULL;\n\n#ifdef __DragonFly__ // We define `__FreeBSD__` on DragonFly BSD for simplification\n    result->name = \"DF\";\n    #ifdef FF_DF_VERSION\n    result->version = FF_DF_VERSION;\n    #endif\n#elif __FreeBSD__\n    result->name = \"FBSD\";\n    #ifdef FF_FBSD_VERSION\n    result->version = FF_FBSD_VERSION;\n    #endif\n#endif\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/libc/libc_linux.c",
    "content": "#include \"libc.h\"\n\n#define FF_STR_INDIR(x) #x\n#define FF_STR(x) FF_STR_INDIR(x)\n\n#include <features.h>\n\nconst char* ffDetectLibc(FFLibcResult* result)\n{\n#ifdef __UCLIBC__\n    result->name = \"uClibc\";\n    result->version = FF_STR(__UCLIBC_MAJOR__) \".\" FF_STR(__UCLIBC_MINOR__) \".\" FF_STR(__UCLIBC_SUBLEVEL__);\n#elif defined(__GNU_LIBRARY__)\n    result->name = \"glibc\";\n    result->version = FF_STR(__GLIBC__) \".\" FF_STR(__GLIBC_MINOR__);\n#else\n    result->name = \"musl\";\n    #ifdef FF_MUSL_VERSION\n        result->version = FF_MUSL_VERSION;\n    #else\n        result->version = NULL;\n    #endif\n#endif\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/libc/libc_nosupport.c",
    "content": "#include \"libc.h\"\n\nconst char* ffDetectLibc(FFLibcResult* result)\n{\n    result->name = \"Unknown\";\n    result->version = NULL;\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/libc/libc_windows.cpp",
    "content": "extern \"C\"\n{\n#include \"libc.h\"\n}\n\n#ifdef __MINGW32__\n#include <_mingw.h>\n#endif\n\ntemplate<uint32_t Major, uint32_t Minor>\nclass version_t {\n    constexpr static auto buflen() noexcept {\n        unsigned int len = 2; // \".\"\n        if (Major == 0)\n            len++;\n        else\n            for (auto n = Major; n; len++, n /= 10);\n\n        if (Minor == 0)\n            len++;\n        else\n            for (auto n = Minor; n; len++, n /= 10);\n        return len;\n    }\n\n    char buf[buflen()] = {};\n\npublic:\n    constexpr version_t() noexcept {\n        auto ptr = buf + buflen();\n        *--ptr = '\\0';\n\n        if (Minor == 0) {\n            *--ptr = '0';\n        } else {\n            for (auto n = Minor; n; n /= 10)\n                *--ptr = \"0123456789\"[n % 10];\n        }\n        *--ptr = '.';\n        if (Major == 0) {\n            *--ptr = '0';\n        } else {\n            for (auto n = Major; n; n /= 10)\n                *--ptr = \"0123456789\"[n % 10];\n        }\n    }\n\n    constexpr operator const char *() const { return buf; }\n};\n\ntemplate<uint32_t Major, uint32_t Minor>\nconstexpr version_t<Major, Minor> version;\n\nextern \"C\"\nconst char* ffDetectLibc(FFLibcResult* result)\n{\n#ifdef _UCRT\n    result->name = \"ucrt\";\n#else\n    result->name = \"msvcrt\";\n#endif\n\n    result->version = version<(__MSVCRT_VERSION__ >> 8), (__MSVCRT_VERSION__ & 8)>;\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/lm/lm.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/lm/option.h\"\n\ntypedef struct FFLMResult\n{\n    FFstrbuf service;\n    FFstrbuf type;\n    FFstrbuf version;\n} FFLMResult;\n\nconst char* ffDetectLM(FFLMResult* result);\n"
  },
  {
    "path": "src/detection/lm/lm_linux.c",
    "content": "#include \"lm.h\"\n#include \"common/properties.h\"\n#include \"common/dbus.h\"\n#include \"common/processing.h\"\n#include \"detection/displayserver/displayserver.h\"\n\n#include <unistd.h>\n\n#define FF_SYSTEMD_SESSIONS_PATH \"/run/systemd/sessions/\"\n#define FF_SYSTEMD_USERS_PATH \"/run/systemd/users/\"\n\nstatic const char* getGdmVersion(FFstrbuf* version)\n{\n    const char* error = ffProcessAppendStdOut(version, (char* const[]) {\n        \"gdm\",\n        \"--version\",\n        NULL\n    });\n    if (error || version->length == 0)\n    {\n        error = ffProcessAppendStdOut(version, (char* const[]) {\n            \"gdm3\",\n            \"--version\",\n            NULL\n        });\n        if (error || version->length == 0) return \"Failed to get GDM version\";\n    }\n\n    // GDM 44.1\n    ffStrbufSubstrAfterFirstC(version, ' ');\n    return NULL;\n}\n\nstatic const char* getSshdVersion(FFstrbuf* version)\n{\n    const char* error = ffProcessAppendStdErr(version, (char* const[]) {\n        \"sshd\",\n        \"-V\",\n        NULL\n    });\n    if (error)\n        return error;\n\n    // OpenSSH_9.0p1, OpenSSL 3.0.9 30 May 2023...\n    ffStrbufSubstrBeforeFirstC(version, ',');\n    ffStrbufSubstrAfterFirstC(version, '_');\n    return NULL;\n}\n\n#ifdef FF_HAVE_ZLIB\n#include \"common/library.h\"\n#include <stdlib.h>\n#include <zlib.h>\n\nstatic const char* getSddmVersion(FFstrbuf* version)\n{\n    FF_LIBRARY_LOAD_MESSAGE(zlib, \"libz\" FF_LIBRARY_EXTENSION, 2)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzopen)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzread)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzerror)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gztell)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzrewind)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzclose)\n\n    gzFile file = ffgzopen(FASTFETCH_TARGET_DIR_USR \"/share/man/man1/sddm.1.gz\", \"rb\");\n    if (file == Z_NULL)\n        return \"ffgzopen(\\\"/usr/share/man/man1/sddm.1.gz\\\", \\\"rb\\\") failed\";\n\n    ffStrbufEnsureFree(version, 2047);\n    memset(version->chars, 0, version->allocated);\n    int size = ffgzread(file, version->chars, version->allocated - 1);\n    ffgzclose(file);\n\n    if (size <= 0)\n        return \"ffgzread(file, version->chars, version->length) failed\";\n\n    version->length = (uint32_t) size;\n    uint32_t index = ffStrbufFirstIndexS(version, \".TH \");\n    if (index == version->length)\n    {\n        ffStrbufClear(version);\n        return \".TH is not found\";\n    }\n\n    ffStrbufSubstrBefore(version, ffStrbufNextIndexC(version, index, '\\n'));\n    ffStrbufSubstrAfter(version, index + (uint32_t) strlen(\".TH \"));\n\n    // \"SDDM\" 1 \"May 2014\" \"sddm 0.20.0\" \"sddm\"\n    ffStrbufSubstrBeforeLastC(version, ' ');\n    ffStrbufTrimRight(version, '\"');\n    ffStrbufSubstrAfterLastC(version, ' ');\n\n    return NULL;\n}\n#else\nstatic const char* getSddmVersion(FF_MAYBE_UNUSED FFstrbuf* version)\n{\n    return \"Fastfetch is built without libz support\";\n}\n#endif\n\nstatic const char* getXfwmVersion(FFstrbuf* version)\n{\n    const char* error = ffProcessAppendStdOut(version, (char* const[]) {\n        \"xfwm4\",\n        \"--version\",\n        NULL\n    });\n    if (error)\n        return error;\n\n    //         This is xfwm4 version 4.18.0 (revision 7e7473c5b) for Xfce 4.18...\n    ffStrbufSubstrAfterFirstS(version, \"version \");\n    ffStrbufSubstrBeforeFirstC(version, ' ');\n\n    return NULL;\n}\n\nstatic const char* getLightdmVersion(FFstrbuf* version)\n{\n    const char* error = ffProcessAppendStdErr(version, (char* const[]) {\n        \"lightdm\",\n        \"--version\",\n        NULL\n    });\n    if (error)\n        return error;\n\n    // lightdm 1.30.0\n    ffStrbufSubstrAfterFirstC(version, ' ');\n    ffStrbufTrimRight(version, '\\n');\n\n    return NULL;\n}\n\nconst char* ffDetectLM(FFLMResult* result)\n{\n    FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();\n\n    FF_STRBUF_AUTO_DESTROY sessionId = ffStrbufCreateS(getenv(\"XDG_SESSION_ID\"));\n    if (sessionId.length == 0)\n    {\n        // On some incorrectly configured systems, $XDG_SESSION_ID is not set. Try finding it ourself\n        // WARNING: This is private data. Do not parse\n        ffStrbufSetF(&path, FF_SYSTEMD_USERS_PATH \"%d\", instance.state.platform.uid);\n\n        // This is actually buggy, and assumes current user is using DE\n        // `sd_pid_get_session` can be a better option, but we need to find a pid to use\n        if (!ffParsePropFileValues(path.chars, 1, (FFpropquery[]) {\n            {\"DISPLAY=\", &sessionId},\n        }))\n            return \"Failed to get $XDG_SESSION_ID\";\n    }\n\n    ffStrbufSetS(&path, FF_SYSTEMD_SESSIONS_PATH);\n    ffStrbufAppend(&path, &sessionId);\n\n    // WARNING: This is private data. Do not parse\n    if (!ffParsePropFileValues(path.chars, 2, (FFpropquery[]) {\n        {\"SERVICE=\", &result->service},\n        {\"TYPE=\", &result->type},\n    }))\n        return \"Failed to parse \" FF_SYSTEMD_SESSIONS_PATH \"$XDG_SESSION_ID\";\n\n    if (instance.config.general.detectVersion)\n    {\n        if (ffStrbufStartsWithS(&result->service, \"gdm\"))\n            getGdmVersion(&result->version);\n        else if (ffStrbufStartsWithS(&result->service, \"sddm\"))\n            getSddmVersion(&result->version);\n        else if (ffStrbufStartsWithS(&result->service, \"xfwm\"))\n            getXfwmVersion(&result->version);\n        else if (ffStrbufStartsWithS(&result->service, \"lightdm\"))\n            getLightdmVersion(&result->version);\n        else if (ffStrbufStartsWithS(&result->service, \"sshd\"))\n            getSshdVersion(&result->version);\n    }\n\n    // Correct char cases\n    if (ffStrbufIgnCaseEqualS(&result->type, FF_WM_PROTOCOL_WAYLAND))\n        ffStrbufSetS(&result->type, FF_WM_PROTOCOL_WAYLAND);\n    else if (ffStrbufIgnCaseEqualS(&result->type, FF_WM_PROTOCOL_X11))\n        ffStrbufSetS(&result->type, FF_WM_PROTOCOL_X11);\n    else if (ffStrbufIgnCaseEqualS(&result->type, FF_WM_PROTOCOL_TTY))\n        ffStrbufSetS(&result->type, FF_WM_PROTOCOL_TTY);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/lm/lm_nosupport.c",
    "content": "#include \"lm.h\"\n\nconst char* ffDetectLM(FF_MAYBE_UNUSED FFLMResult* result)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/loadavg/loadavg.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\nconst char* ffDetectLoadavg(double result[3]);\n"
  },
  {
    "path": "src/detection/loadavg/loadavg_bsd.c",
    "content": "#include \"detection/loadavg/loadavg.h\"\n\n#include <sys/sysctl.h>\n\n#if __FreeBSD__ || __OpenBSD__ || __NetBSD__\n    #include <sys/types.h>\n    #include <sys/resource.h>\n    #if __FreeBSD__\n        #include <vm/vm_param.h>\n    #endif\n#endif\n\nconst char* ffDetectLoadavg(double result[3])\n{\n    struct loadavg load;\n    size_t size = sizeof(load);\n    if (sysctl((int []) { CTL_VM, VM_LOADAVG }, 2, &load, &size, NULL, 0) < 0)\n        return \"sysctl({CTL_VM, VM_LOADAVG}) failed\";\n    for (int i = 0; i < 3; i++)\n        result[i] = (double) load.ldavg[i] / (double) load.fscale;\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/loadavg/loadavg_linux.c",
    "content": "#include \"detection/loadavg/loadavg.h\"\n#include \"common/io.h\"\n\n#include <sys/sysinfo.h>\n\nconst char* ffDetectLoadavg(double result[3])\n{\n    #ifndef __ANDROID__ // cat: /proc/loadavg: Permission denied\n\n    // Don't use syscall for container compatibility. #620\n    char buf[64];\n    ssize_t nRead = ffReadFileData(\"/proc/loadavg\", sizeof(buf) - 1, buf);\n    if (nRead > 0)\n    {\n        buf[nRead] = '\\0';\n\n        if (sscanf(buf, \"%lf%lf%lf\", &result[0], &result[1], &result[2]) == 3)\n            return NULL;\n    }\n\n    #endif\n    #ifndef __GNU__\n    // getloadavg requires higher ANDROID_API version\n    struct sysinfo si;\n    if (sysinfo(&si) < 0)\n        return \"sysinfo() failed\";\n\n    for (int i = 0; i < 3; i++)\n        result[i] = (double) si.loads[i] / (1 << SI_LOAD_SHIFT);\n    #endif\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/loadavg/loadavg_nosupport.c",
    "content": "#include \"detection/loadavg/loadavg.h\"\n\nconst char* ffDetectLoadavg(FF_MAYBE_UNUSED double result[3])\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/loadavg/loadavg_sunos.c",
    "content": "#include \"detection/loadavg/loadavg.h\"\n\n#include <sys/loadavg.h>\n\nconst char* ffDetectLoadavg(double result[3])\n{\n    return getloadavg(result, 3) == 3 ? NULL : \"getloadavg() failed\";\n}\n"
  },
  {
    "path": "src/detection/locale/locale.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\nconst char* ffDetectLocale(FFstrbuf* result);\n"
  },
  {
    "path": "src/detection/locale/locale_linux.c",
    "content": "#include \"detection/locale/locale.h\"\n\n#include <locale.h>\n\nconst char* ffDetectLocale(FFstrbuf* result)\n{\n    ffStrbufAppendS(result, getenv(\"LC_ALL\"));\n    if(result->length > 0)\n        return NULL;\n\n    ffStrbufAppendS(result, getenv(\"LANG\"));\n    if(result->length > 0)\n        return NULL;\n\n    ffStrbufAppendS(result, setlocale(LC_TIME, NULL));\n    if(result->length > 0)\n        return NULL;\n\n    return \"Failed to detect locale\";\n}\n"
  },
  {
    "path": "src/detection/locale/locale_windows.c",
    "content": "#include \"detection/locale/locale.h\"\n#include \"common/windows/unicode.h\"\n\n#include <windows.h>\n\nconst char* ffDetectLocale(FFstrbuf* result)\n{\n    wchar_t name[LOCALE_NAME_MAX_LENGTH];\n    int size = GetUserDefaultLocaleName(name, LOCALE_NAME_MAX_LENGTH);\n    if (size <= 1) // including '\\0'\n        return \"GetUserDefaultLocaleName() failed\";\n\n    ffStrbufSetNWS(result, (uint32_t)size - 1, name);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/localip/localip.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/localip/option.h\"\n\n#ifndef IN6_IS_ADDR_GLOBAL\n/* Global Unicast: 2000::/3 (001...) */\n#define IN6_IS_ADDR_GLOBAL(a) (((a)->s6_addr[0] & 0xE0) == 0x20)\n#endif\n#ifndef IN6_IS_ADDR_UNIQUE_LOCAL\n/* Unique Local: fc00::/7 (1111 110...) */\n#define IN6_IS_ADDR_UNIQUE_LOCAL(a) (((a)->s6_addr[0] & 0xFE) == 0xFC)\n#endif\n#ifndef IN6_IS_ADDR_LINKLOCAL\n/* Link-Local: fe80::/10 (1111 1110 10...) */\n#define IN6_IS_ADDR_LINKLOCAL(a) (((a)->s6_addr[0] == 0xFE) && (((a)->s6_addr[1] & 0xC0) == 0x80))\n#endif\n\ntypedef struct FFLocalIpResult\n{\n    FFstrbuf name;\n    FFstrbuf ipv4;\n    FFstrbuf ipv6;\n    FFstrbuf mac;\n    FFstrbuf flags;\n    int32_t mtu;\n    int32_t speed; // in Mbps\n    FFLocalIpType defaultRoute;\n} FFLocalIpResult;\n\ntypedef struct FFLocalIpNIFlag\n{\n    uint32_t flag;\n    const char *name;\n} FFLocalIpNIFlag;\n\nstatic inline void ffLocalIpFillNIFlags(FFstrbuf *buf, uint64_t flag, const FFLocalIpNIFlag names[])\n{\n    for (const FFLocalIpNIFlag *nf = names; flag && nf->name; ++nf)\n    {\n        if (flag & nf->flag)\n        {\n            if (buf->length > 0)\n                ffStrbufAppendC(buf, ',');\n            ffStrbufAppendS(buf, nf->name);\n            flag &= ~nf->flag;\n        }\n    }\n}\n\nconst char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results);\n"
  },
  {
    "path": "src/detection/localip/localip_linux.c",
    "content": "#include \"localip.h\"\n#include \"common/io.h\"\n#include \"common/netif.h\"\n#include \"common/stringUtils.h\"\n#include \"common/debug.h\"\n\n#include <string.h>\n#include <ctype.h>\n#include <net/if.h>\n#include <ifaddrs.h>\n#include <netinet/in.h>\n#include <arpa/inet.h>\n#include <stdio.h>\n#include <sys/ioctl.h>\n#include <inttypes.h>\n#include <fcntl.h>\n\n#ifdef __linux__\n#include <linux/ethtool.h>\n#include <linux/sockios.h>\n#include <linux/if.h>\n#include <linux/if_addr.h>\n#endif\n\n#if __has_include(<netinet6/in6_var.h>)\n    #include <netinet6/in6_var.h>\n#endif\n\n#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__) || defined(__NetBSD__) || defined(__HAIKU__)\n#include <net/if_media.h>\n#include <net/if_dl.h>\n#elif !defined(__GNU__)\n#include <netpacket/packet.h>\n#endif\n#if defined(__sun) || defined(__HAIKU__)\n#include <sys/sockio.h>\n#endif\n#if defined(__sun)\n#include <kstat.h>\n\nstatic inline void kstatFreeWrap(kstat_ctl_t** pkc)\n{\n    assert(pkc);\n    if (*pkc)\n        kstat_close(*pkc);\n}\n#endif\n\n#define FF_LOCALIP_NIFLAG(name) { IFF_##name, #name }\n\nstatic const FFLocalIpNIFlag niFlagOptions[] = {\n    FF_LOCALIP_NIFLAG(UP),\n    FF_LOCALIP_NIFLAG(BROADCAST),\n#ifdef IFF_DEBUG\n    FF_LOCALIP_NIFLAG(DEBUG),\n#endif\n    FF_LOCALIP_NIFLAG(LOOPBACK),\n    FF_LOCALIP_NIFLAG(POINTOPOINT),\n#ifdef IFF_RUNNING\n    FF_LOCALIP_NIFLAG(RUNNING),\n#endif\n    FF_LOCALIP_NIFLAG(NOARP),\n    FF_LOCALIP_NIFLAG(PROMISC),\n    FF_LOCALIP_NIFLAG(ALLMULTI),\n#ifdef IFF_INTELLIGENT\n    FF_LOCALIP_NIFLAG(INTELLIGENT),\n#endif\n    FF_LOCALIP_NIFLAG(MULTICAST),\n#ifdef IFF_NOTRAILERS\n    FF_LOCALIP_NIFLAG(NOTRAILERS),\n#endif\n#if defined( __linux__) || defined (__GNU__)\n    FF_LOCALIP_NIFLAG(MASTER),\n    FF_LOCALIP_NIFLAG(SLAVE),\n    FF_LOCALIP_NIFLAG(PORTSEL),\n    FF_LOCALIP_NIFLAG(AUTOMEDIA),\n    FF_LOCALIP_NIFLAG(DYNAMIC),\n#endif\n#ifdef __linux__\n    FF_LOCALIP_NIFLAG(LOWER_UP),\n    FF_LOCALIP_NIFLAG(DORMANT),\n    FF_LOCALIP_NIFLAG(ECHO),\n#endif\n#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__)\n    FF_LOCALIP_NIFLAG(OACTIVE),\n    FF_LOCALIP_NIFLAG(SIMPLEX),\n    FF_LOCALIP_NIFLAG(LINK0),\n    FF_LOCALIP_NIFLAG(LINK1),\n    FF_LOCALIP_NIFLAG(LINK2),\n#endif\n#ifdef IFF_ALTPHYS\n    FF_LOCALIP_NIFLAG(ALTPHYS),\n#endif\n#ifdef IFF_CANTCONFIG\n    FF_LOCALIP_NIFLAG(CANTCONFIG),\n#endif\n#ifdef __HAIKU__\n    FF_LOCALIP_NIFLAG(AUTOUP),\n    FF_LOCALIP_NIFLAG(SIMPLEX),\n    FF_LOCALIP_NIFLAG(LINK),\n    FF_LOCALIP_NIFLAG(AUTO_CONFIGURED),\n    FF_LOCALIP_NIFLAG(CONFIGURING),\n#endif\n#ifdef __sun\n    FF_LOCALIP_NIFLAG(MULTI_BCAST),\n    FF_LOCALIP_NIFLAG(UNNUMBERED),\n    FF_LOCALIP_NIFLAG(DHCPRUNNING),\n    FF_LOCALIP_NIFLAG(PRIVATE),\n#endif\n    // sentinel\n    {},\n};\n\nstatic FFLocalIpIpv6Type getIpv6Type(struct ifaddrs* ifa)\n{\n    struct sockaddr_in6* addr = (struct sockaddr_in6*) ifa->ifa_addr;\n\n    FF_DEBUG(\"Checking IPv6 type for interface %s\", ifa->ifa_name);\n\n    FFLocalIpIpv6Type result = FF_LOCALIP_IPV6_TYPE_NONE;\n    if (IN6_IS_ADDR_GLOBAL(&addr->sin6_addr))\n    {\n        result = FF_LOCALIP_IPV6_TYPE_GUA_BIT;\n        FF_DEBUG(\"Interface %s has Global Unicast Address\", ifa->ifa_name);\n    }\n    else if (IN6_IS_ADDR_UNIQUE_LOCAL(&addr->sin6_addr))\n    {\n        result = FF_LOCALIP_IPV6_TYPE_ULA_BIT;\n        FF_DEBUG(\"Interface %s has Unique Local Address\", ifa->ifa_name);\n    }\n    else if (IN6_IS_ADDR_LINKLOCAL(&addr->sin6_addr))\n    {\n        result = FF_LOCALIP_IPV6_TYPE_LLA_BIT;\n        FF_DEBUG(\"Interface %s has Link-Local Address\", ifa->ifa_name);\n    }\n    else\n    {\n        FF_DEBUG(\"Interface %s has unknown IPv6 address type\", ifa->ifa_name);\n        return FF_LOCALIP_IPV6_TYPE_UNKNOWN_BIT;\n    }\n\n#ifdef SIOCGIFAFLAG_IN6\n    static int sockfd = 0;\n    if (sockfd == 0)\n    {\n        sockfd = socket(AF_INET6, SOCK_DGRAM\n            #ifdef SOCK_CLOEXEC\n            | SOCK_CLOEXEC\n            #endif\n        , 0);\n        #ifndef SOCK_CLOEXEC\n        if (sockfd > 0) fcntl(sockfd, F_SETFD, FD_CLOEXEC);\n        #endif\n    }\n    if (sockfd < 0) return result;\n\n    struct in6_ifreq ifr6 = {};\n    ffStrCopy(ifr6.ifr_name, ifa->ifa_name, IFNAMSIZ);\n    ifr6.ifr_addr = *addr;\n\n    if (ioctl(sockfd, SIOCGIFAFLAG_IN6, &ifr6) != 0)\n        return result;\n\n    #ifdef IN6_IFF_PREFER_SOURCE\n        if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_PREFER_SOURCE)\n            result |= FF_LOCALIP_IPV6_TYPE_PREFERRED_BIT;\n    #endif\n    if (ifr6.ifr_ifru.ifru_flags6 & (IN6_IFF_DEPRECATED | IN6_IFF_TEMPORARY | IN6_IFF_TENTATIVE | IN6_IFF_DUPLICATED\n        #ifdef IN6_IFF_OPTIMISTIC\n             | IN6_IFF_OPTIMISTIC\n        #endif\n    )) result |= FF_LOCALIP_IPV6_TYPE_SECONDARY_BIT;\n    return result;\n#elif __linux__\n    static FFlist addresses = {};\n    if (addresses.elementSize == 0)\n    {\n        ffListInit(&addresses, sizeof(struct in6_addr));\n        FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n        if (!ffReadFileBuffer(\"/proc/net/if_inet6\", &buffer))\n            return result;\n\n        char* line = NULL;\n        size_t len = 0;\n        while (ffStrbufGetline(&line, &len, &buffer))\n        {\n            struct in6_addr* entry = (struct in6_addr*) ffListAdd(&addresses);\n            uint8_t flags;\n            if (sscanf(line, \"%2\" SCNx8 \"%2\" SCNx8 \"%2\" SCNx8 \"%2\" SCNx8 \"%2\" SCNx8 \"%2\" SCNx8 \"%2\" SCNx8 \"%2\" SCNx8 \"%2\" SCNx8 \"%2\" SCNx8 \"%2\" SCNx8 \"%2\" SCNx8 \"%2\" SCNx8 \"%2\" SCNx8 \"%2\" SCNx8 \"%2\" SCNx8 \" %*s %*s %*s %\" SCNx8 \" %*s\",\n                    &entry->s6_addr[0], &entry->s6_addr[1], &entry->s6_addr[2], &entry->s6_addr[3],\n                    &entry->s6_addr[4], &entry->s6_addr[5], &entry->s6_addr[6], &entry->s6_addr[7],\n                    &entry->s6_addr[8], &entry->s6_addr[9], &entry->s6_addr[10], &entry->s6_addr[11],\n                    &entry->s6_addr[12], &entry->s6_addr[13], &entry->s6_addr[14], &entry->s6_addr[15],\n                    &flags) != 17 ||\n                (!IN6_IS_ADDR_GLOBAL(entry) && !IN6_IS_ADDR_UNIQUE_LOCAL(entry)) ||\n                (flags & (IFA_F_DEPRECATED | IFA_F_TEMPORARY | IFA_F_TENTATIVE | IFA_F_DADFAILED | IFA_F_OPTIMISTIC))\n            )\n                --addresses.length;\n        }\n    }\n    if (addresses.capacity == 0) return result;\n\n    FF_LIST_FOR_EACH(struct in6_addr, entry, addresses)\n    {\n        if (memcmp(&addr->sin6_addr, entry, sizeof(struct in6_addr)) == 0)\n            return result;\n    }\n    result |= FF_LOCALIP_IPV6_TYPE_SECONDARY_BIT;\n    return result;\n#elif __sun\n    if (ifa->ifa_flags & IFF_PREFERRED)\n        result |= FF_LOCALIP_IPV6_TYPE_PREFERRED_BIT;\n    if (ifa->ifa_flags & (IFF_DEPRECATED | IFF_TEMPORARY | IFF_DUPLICATE))\n        result |= FF_LOCALIP_IPV6_TYPE_SECONDARY_BIT;\n    return result;\n#else\n    return result;\n#endif\n}\n\ntypedef struct {\n    struct ifaddrs* mac;\n    FFlist /*<struct ifaddrs*>*/ ipv4;\n    FFlist /*<struct ifaddrs*>*/ ipv6;\n} FFAdapter;\n\nstatic void appendIpv4(const FFLocalIpOptions* options, FFstrbuf* buffer, const struct ifaddrs* ifa)\n{\n    struct sockaddr_in* ipv4 = (struct sockaddr_in*) ifa->ifa_addr;\n\n    char addressBuffer[INET_ADDRSTRLEN + 16];\n    inet_ntop(AF_INET, &ipv4->sin_addr, addressBuffer, INET_ADDRSTRLEN);\n\n    FF_DEBUG(\"Adding IPv4 address %s for interface %s\", addressBuffer, ifa->ifa_name);\n\n    if (options->showType & FF_LOCALIP_TYPE_PREFIX_LEN_BIT)\n    {\n        struct sockaddr_in* netmask = (struct sockaddr_in*) ifa->ifa_netmask;\n        int cidr = __builtin_popcount(netmask->sin_addr.s_addr);\n        if (cidr != 0)\n        {\n            size_t len = strlen(addressBuffer);\n            snprintf(addressBuffer + len, 16, \"/%d\", cidr);\n        }\n    }\n\n    if (buffer->length) ffStrbufAppendC(buffer, ',');\n    ffStrbufAppendS(buffer, addressBuffer);\n}\n\nstatic void appendIpv6(const FFLocalIpOptions* options, FFstrbuf* buffer, const struct ifaddrs* ifa)\n{\n    struct sockaddr_in6* ipv6 = (struct sockaddr_in6*) ifa->ifa_addr;\n\n    char addressBuffer[INET6_ADDRSTRLEN + 16];\n    inet_ntop(AF_INET6, &ipv6->sin6_addr, addressBuffer, INET6_ADDRSTRLEN);\n\n    FF_DEBUG(\"Adding IPv6 address %s for interface %s\", addressBuffer, ifa->ifa_name);\n\n    if (options->showType & FF_LOCALIP_TYPE_PREFIX_LEN_BIT)\n    {\n        struct sockaddr_in6* netmask = (struct sockaddr_in6*) ifa->ifa_netmask;\n        int cidr = 0;\n        static_assert(sizeof(netmask->sin6_addr) % sizeof(uint64_t) == 0, \"\");\n        for (uint32_t i = 0; i < sizeof(netmask->sin6_addr) / sizeof(uint64_t); ++i)\n            cidr += __builtin_popcountll(((uint64_t*) &netmask->sin6_addr)[i]);\n        if (cidr != 0)\n        {\n            size_t len = strlen(addressBuffer);\n            snprintf(addressBuffer + len, 16, \"/%d\", cidr);\n        }\n    }\n\n    if (buffer->length) ffStrbufAppendC(buffer, ',');\n    ffStrbufAppendS(buffer, addressBuffer);\n}\n\nconst char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results)\n{\n    FF_DEBUG(\"Starting local IP detection with showType=0x%x, namePrefix='%s'\",\n             options->showType, options->namePrefix.chars);\n\n    struct ifaddrs* ifAddrStruct = NULL;\n    if(getifaddrs(&ifAddrStruct) < 0)\n    {\n        FF_DEBUG(\"getifaddrs() failed\");\n        return \"getifaddrs(&ifAddrStruct) failed\";\n    }\n\n    FF_DEBUG(\"Successfully retrieved interface addresses\");\n\n    FF_LIST_AUTO_DESTROY adapters = ffListCreate(sizeof(FFAdapter));\n\n    for (struct ifaddrs* ifa = ifAddrStruct; ifa; ifa = ifa->ifa_next)\n    {\n        if (!ifa->ifa_addr)\n        {\n            FF_DEBUG(\"Skipping interface %s (no address)\", ifa->ifa_name);\n            continue;\n        }\n\n        #ifdef IFF_RUNNING\n        if (!(ifa->ifa_flags & IFF_RUNNING))\n        {\n            FF_DEBUG(\"Skipping interface %s (not running)\", ifa->ifa_name);\n            continue;\n        }\n        #endif\n\n        if ((ifa->ifa_flags & IFF_LOOPBACK) && !(options->showType & FF_LOCALIP_TYPE_LOOP_BIT))\n        {\n            FF_DEBUG(\"Skipping loopback interface %s\", ifa->ifa_name);\n            continue;\n        }\n\n        if (options->namePrefix.length && strncmp(ifa->ifa_name, options->namePrefix.chars, options->namePrefix.length) != 0)\n        {\n            FF_DEBUG(\"Skipping interface %s (doesn't match prefix '%s')\",\n                     ifa->ifa_name, options->namePrefix.chars);\n            continue;\n        }\n\n        if (!(options->showType & FF_LOCALIP_TYPE_MAC_BIT) &&\n            ifa->ifa_addr->sa_family != AF_INET && ifa->ifa_addr->sa_family != AF_INET6)\n        {\n            FF_DEBUG(\"Skipping interface %s (unsupported address family %d)\",\n                     ifa->ifa_name, ifa->ifa_addr->sa_family);\n            continue;\n        }\n\n        if (options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT)\n        {\n            // If the interface is not the default route for either IPv4 or IPv6, skip it\n            if (!((options->showType & FF_LOCALIP_TYPE_IPV4_BIT) && ffStrEquals(ffNetifGetDefaultRouteV4()->ifName, ifa->ifa_name)) &&\n                !((options->showType & FF_LOCALIP_TYPE_IPV6_BIT) && ffStrEquals(ffNetifGetDefaultRouteV6()->ifName, ifa->ifa_name)))\n            {\n                FF_DEBUG(\"Skipping interface %s (not default route interface)\", ifa->ifa_name);\n                    continue;\n            }\n        }\n\n        FF_DEBUG(\"Processing interface %s (family=%d, flags=0x%lx)\",\n                 ifa->ifa_name, ifa->ifa_addr->sa_family, (unsigned long) ifa->ifa_flags);\n\n        FFAdapter* adapter = NULL;\n        FF_LIST_FOR_EACH(FFAdapter, x, adapters)\n        {\n            if (ffStrEquals(x->mac->ifa_name, ifa->ifa_name))\n            {\n                adapter = x;\n                break;\n            }\n        }\n        if (!adapter)\n        {\n            adapter = ffListAdd(&adapters);\n            *adapter = (FFAdapter) {\n                .mac = ifa,\n                .ipv4 = ffListCreate(sizeof(struct ifaddrs*)),\n                .ipv6 = ffListCreate(sizeof(struct ifaddrs*)),\n            };\n            FF_DEBUG(\"Created new adapter entry for interface %s\", ifa->ifa_name);\n        }\n\n        switch (ifa->ifa_addr->sa_family)\n        {\n            case AF_INET:\n                if (options->showType & FF_LOCALIP_TYPE_IPV4_BIT)\n                {\n                    *FF_LIST_ADD(struct ifaddrs*, adapter->ipv4) = ifa;\n                    FF_DEBUG(\"Added IPv4 entry for interface %s\", ifa->ifa_name);\n                }\n                break;\n            case AF_INET6:\n                if (options->showType & FF_LOCALIP_TYPE_IPV6_BIT)\n                {\n                    *FF_LIST_ADD(struct ifaddrs*, adapter->ipv6) = ifa;\n                    FF_DEBUG(\"Added IPv6 entry for interface %s\", ifa->ifa_name);\n                }\n                break;\n            #if __FreeBSD__ || __OpenBSD__ || __APPLE__ || __NetBSD__ || __HAIKU__\n            case AF_LINK:\n                adapter->mac = ifa;\n                FF_DEBUG(\"Updated MAC entry for interface %s\", ifa->ifa_name);\n                break;\n            #elif !__sun && !__GNU__\n            case AF_PACKET:\n                adapter->mac = ifa;\n                FF_DEBUG(\"Updated MAC entry for interface %s\", ifa->ifa_name);\n                break;\n            #endif\n        }\n    }\n\n    FF_DEBUG(\"Found %u network adapters\", adapters.length);\n\n    FF_LIST_FOR_EACH(FFAdapter, adapter, adapters)\n    {\n        FF_DEBUG(\"Processing adapter %s (IPv4 entries: %u, IPv6 entries: %u)\",\n                 adapter->mac->ifa_name, adapter->ipv4.length, adapter->ipv6.length);\n\n        if (adapter->ipv4.length == 0 && adapter->ipv6.length == 0 &&\n            !(options->showType & FF_LOCALIP_TYPE_MAC_BIT) )\n        {\n            FF_DEBUG(\"Skipping interface %s (no IP addresses)\", adapter->mac->ifa_name);\n            continue;\n        }\n\n        FFLocalIpResult* item = FF_LIST_ADD(FFLocalIpResult, *results);\n        ffStrbufInitS(&item->name, adapter->mac->ifa_name);\n        ffStrbufInit(&item->ipv4);\n        ffStrbufInit(&item->ipv6);\n        ffStrbufInit(&item->mac);\n        ffStrbufInit(&item->flags);\n        item->defaultRoute = FF_LOCALIP_TYPE_NONE;\n        item->mtu = -1;\n        item->speed = -1;\n\n        if (options->showType & FF_LOCALIP_TYPE_FLAGS_BIT)\n        {\n            ffLocalIpFillNIFlags(&item->flags, adapter->mac->ifa_flags, niFlagOptions);\n            FF_DEBUG(\"Added flags for interface %s: %s\", adapter->mac->ifa_name, item->flags.chars);\n        }\n\n        if ((options->showType & FF_LOCALIP_TYPE_IPV4_BIT))\n        {\n            const FFNetifDefaultRouteResult* defaultRouteV4 = ffNetifGetDefaultRouteV4();\n            bool isDefaultRouteIf = ffStrEquals(defaultRouteV4->ifName, adapter->mac->ifa_name);\n\n            if (isDefaultRouteIf)\n            {\n                item->defaultRoute |= FF_LOCALIP_TYPE_IPV4_BIT;\n                FF_DEBUG(\"Interface %s is IPv4 default route\", adapter->mac->ifa_name);\n            }\n\n            if (options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT)\n            {\n                if (!isDefaultRouteIf)\n                {\n                    FF_DEBUG(\"Skipping IPv4 for interface %s (not default route)\", adapter->mac->ifa_name);\n                    goto v6;\n                }\n            }\n\n            if (!(options->showType & FF_LOCALIP_TYPE_ALL_IPS_BIT))\n            {\n                struct ifaddrs* ifa = NULL;\n                if (isDefaultRouteIf && defaultRouteV4->preferredSourceAddrV4 != 0)\n                {\n                    FF_LIST_FOR_EACH(struct ifaddrs*, pifa, adapter->ipv4)\n                    {\n                        struct sockaddr_in* ipv4 = (struct sockaddr_in*) (*pifa)->ifa_addr;\n                        if (ipv4->sin_addr.s_addr == defaultRouteV4->preferredSourceAddrV4)\n                        {\n                            ifa = *pifa;\n                            FF_DEBUG(\"Found preferred IPv4 source address for interface %s\", adapter->mac->ifa_name);\n                            break;\n                        }\n                    }\n                }\n                if (ifa)\n                    appendIpv4(options, &item->ipv4, ifa);\n                else if (adapter->ipv4.length > 0)\n                {\n                    appendIpv4(options, &item->ipv4, *FF_LIST_FIRST(struct ifaddrs*, adapter->ipv4));\n                    FF_DEBUG(\"Using first IPv4 address for interface %s\", adapter->mac->ifa_name);\n                }\n            }\n            else\n            {\n                FF_DEBUG(\"Adding all IPv4 addresses for interface %s\", adapter->mac->ifa_name);\n                FF_LIST_FOR_EACH(struct ifaddrs*, pifa, adapter->ipv4)\n                    appendIpv4(options, &item->ipv4, *pifa);\n            }\n        }\n    v6:\n        if ((options->showType & FF_LOCALIP_TYPE_IPV6_BIT))\n        {\n            const FFNetifDefaultRouteResult* defaultRouteV6 = ffNetifGetDefaultRouteV6();\n            bool isDefaultRouteIf = ffStrEquals(defaultRouteV6->ifName, adapter->mac->ifa_name);\n\n            if (isDefaultRouteIf)\n            {\n                item->defaultRoute |= FF_LOCALIP_TYPE_IPV6_BIT;\n                FF_DEBUG(\"Interface %s is IPv6 default route\", adapter->mac->ifa_name);\n            }\n\n            if (options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT)\n            {\n                if (!isDefaultRouteIf)\n                {\n                    FF_DEBUG(\"Skipping IPv6 for interface %s (not default route)\", adapter->mac->ifa_name);\n                    goto mac;\n                }\n            }\n\n            if (options->ipv6Type == FF_LOCALIP_IPV6_TYPE_AUTO)\n            {\n                if (!(options->showType & FF_LOCALIP_TYPE_ALL_IPS_BIT))\n                {\n                    struct ifaddrs* selected = NULL;\n                    struct ifaddrs* secondary = NULL;\n\n                    FF_LIST_FOR_EACH(struct ifaddrs*, pifa, adapter->ipv6)\n                    {\n                        FFLocalIpIpv6Type type = getIpv6Type(*pifa);\n                        if (type & FF_LOCALIP_IPV6_TYPE_PREFERRED_BIT)\n                        {\n                            selected = *pifa;\n                            FF_DEBUG(\"Found preferred IPv6 address for interface %s\", adapter->mac->ifa_name);\n                            break;\n                        }\n                        else if ((type & FF_LOCALIP_IPV6_TYPE_GUA_BIT) && !(type & FF_LOCALIP_IPV6_TYPE_SECONDARY_BIT) && !selected)\n                        {\n                            selected = *pifa;\n                            FF_DEBUG(\"Found GUA IPv6 address for interface %s\", adapter->mac->ifa_name);\n                        }\n                        else if ((type & FF_LOCALIP_IPV6_TYPE_ULA_BIT) && !(type & FF_LOCALIP_IPV6_TYPE_SECONDARY_BIT) && !secondary)\n                        {\n                            secondary = *pifa;\n                            FF_DEBUG(\"Found ULA IPv6 address for interface %s\", adapter->mac->ifa_name);\n                        }\n                    }\n                    if (!selected) selected = secondary;\n\n                    if (selected)\n                        appendIpv6(options, &item->ipv6, selected);\n                    else if (adapter->ipv6.length > 0)\n                    {\n                        appendIpv6(options, &item->ipv6, *FF_LIST_FIRST(struct ifaddrs*, adapter->ipv6));\n                        FF_DEBUG(\"Using first IPv6 address for interface %s\", adapter->mac->ifa_name);\n                    }\n                }\n                else\n                {\n                    FF_DEBUG(\"Adding all IPv6 addresses for interface %s\", adapter->mac->ifa_name);\n                    FF_LIST_FOR_EACH(struct ifaddrs*, pifa, adapter->ipv6)\n                        appendIpv6(options, &item->ipv6, *pifa);\n                }\n            }\n            else\n            {\n                FF_LIST_FOR_EACH(struct ifaddrs*, pifa, adapter->ipv6)\n                {\n                    FFLocalIpIpv6Type type = getIpv6Type(*pifa);\n                    if (type & options->ipv6Type)\n                    {\n                        if ((options->showType & FF_LOCALIP_TYPE_ALL_IPS_BIT) || !(type & FF_LOCALIP_IPV6_TYPE_SECONDARY_BIT))\n                        {\n                            appendIpv6(options, &item->ipv6, *pifa);\n                            if (!(options->showType & FF_LOCALIP_TYPE_ALL_IPS_BIT))\n                                break;\n                        }\n                    }\n                }\n            }\n        }\n    mac:\n        #if !defined( __sun)  && !defined(__GNU__)\n        if (options->showType & FF_LOCALIP_TYPE_MAC_BIT)\n        {\n            if (adapter->mac->ifa_addr)\n            {\n                #if __FreeBSD__ || __OpenBSD__ || __APPLE__ || __NetBSD__ || __HAIKU__\n                uint8_t* ptr = (uint8_t*) LLADDR((struct sockaddr_dl *)adapter->mac->ifa_addr);\n                #else\n                uint8_t* ptr = ((struct sockaddr_ll *)adapter->mac->ifa_addr)->sll_addr;\n                #endif\n                ffStrbufSetF(&item->mac, \"%02x:%02x:%02x:%02x:%02x:%02x\",\n                    ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5]);\n                FF_DEBUG(\"Added MAC address %s for interface %s\", item->mac.chars, adapter->mac->ifa_name);\n            }\n            else\n            {\n                FF_DEBUG(\"No MAC address available for interface %s\", adapter->mac->ifa_name);\n            }\n        }\n        #else\n        (void) adapter;\n        #endif\n    }\n\n    FF_LIST_FOR_EACH(FFAdapter, adapter, adapters)\n    {\n        ffListDestroy(&adapter->ipv4);\n        ffListDestroy(&adapter->ipv6);\n    }\n\n    if (ifAddrStruct)\n    {\n        freeifaddrs(ifAddrStruct);\n        ifAddrStruct = NULL;\n        FF_DEBUG(\"Cleaned up interface address structures\");\n    }\n\n    if ((options->showType & FF_LOCALIP_TYPE_MTU_BIT) || (options->showType & FF_LOCALIP_TYPE_SPEED_BIT)\n        #ifdef __sun\n        || (options->showType & FF_LOCALIP_TYPE_MAC_BIT)\n        #endif\n    )\n    {\n        FF_DEBUG(\"Retrieving additional interface properties (MTU/Speed/MAC)\");\n        FF_AUTO_CLOSE_FD int sockfd = socket(AF_INET, SOCK_DGRAM, 0);\n        if (sockfd > 0)\n        {\n            FF_LIST_FOR_EACH(FFLocalIpResult, iface, *results)\n            {\n                struct ifreq ifr = {};\n                ffStrCopy(ifr.ifr_name, iface->name.chars, IFNAMSIZ);\n\n                if (options->showType & FF_LOCALIP_TYPE_MTU_BIT)\n                {\n                    if (ioctl(sockfd, SIOCGIFMTU, &ifr) == 0)\n                    {\n                        iface->mtu = (int32_t) ifr.ifr_mtu;\n                        FF_DEBUG(\"Interface %s MTU: %d\", iface->name.chars, iface->mtu);\n                    }\n                    else\n                    {\n                        FF_DEBUG(\"Failed to get MTU for interface %s\", iface->name.chars);\n                    }\n                }\n\n                if (options->showType & FF_LOCALIP_TYPE_SPEED_BIT)\n                {\n                    #ifdef __linux__\n                    struct ethtool_cmd edata = { .cmd = ETHTOOL_GSET };\n                    ifr.ifr_data = (void*) &edata;\n                    if (ioctl(sockfd, SIOCETHTOOL, &ifr) == 0)\n                    {\n                        iface->speed = (edata.speed_hi << 16) | edata.speed;\n                        FF_DEBUG(\"Interface %s speed: %d Mbps\", iface->name.chars, iface->speed);\n                    }\n                    else\n                    {\n                        // ethtool_cmd_speed is not available on Android\n                        FF_DEBUG(\"Failed to get speed for interface %s via ethtool\", iface->name.chars);\n                    }\n                    #elif __FreeBSD__ || __APPLE__ || __OpenBSD__ || __NetBSD__\n                    struct ifmediareq ifmr = {};\n                    ffStrCopy(ifmr.ifm_name, iface->name.chars, IFNAMSIZ);\n                    if (ioctl(sockfd, SIOCGIFMEDIA, &ifmr) == 0 && (IFM_TYPE(ifmr.ifm_active) & IFM_ETHER))\n                    {\n                        FF_DEBUG(\"Interface %s media type: 0x%x\", iface->name.chars, IFM_SUBTYPE(ifmr.ifm_active));\n                        switch (IFM_SUBTYPE(ifmr.ifm_active))\n                        {\n                        #ifdef IFM_HPNA_1\n                        case IFM_HPNA_1:\n                        #endif\n                            iface->speed = 1; break;\n                        #ifdef IFM_1000_CX\n                        case IFM_1000_CX:\n                        #endif\n                        #ifdef IFM_1000_CX_SGMII\n                        case IFM_1000_CX_SGMII:\n                        #endif\n                        #ifdef IFM_1000_KX\n                        case IFM_1000_KX:\n                        #endif\n                        #ifdef IFM_1000_LX\n                        case IFM_1000_LX:\n                        #endif\n                        #ifdef IFM_1000_SGMII\n                        case IFM_1000_SGMII:\n                        #endif\n                        #ifdef IFM_1000_SX\n                        case IFM_1000_SX:\n                        #endif\n                        #ifdef IFM_1000_T\n                        case IFM_1000_T:\n                        #endif\n                            iface->speed = 1000; break;\n                        #ifdef IFM_100G_AUI2\n                        case IFM_100G_AUI2:\n                        #endif\n                        #ifdef IFM_100G_AUI2_AC\n                        case IFM_100G_AUI2_AC:\n                        #endif\n                        #ifdef IFM_100G_AUI4\n                        case IFM_100G_AUI4:\n                        #endif\n                        #ifdef IFM_100G_AUI4_AC\n                        case IFM_100G_AUI4_AC:\n                        #endif\n                        #ifdef IFM_100G_CAUI2\n                        case IFM_100G_CAUI2:\n                        #endif\n                        #ifdef IFM_100G_CAUI2_AC\n                        case IFM_100G_CAUI2_AC:\n                        #endif\n                        #ifdef IFM_100G_CAUI4\n                        case IFM_100G_CAUI4:\n                        #endif\n                        #ifdef IFM_100G_CAUI4_AC\n                        case IFM_100G_CAUI4_AC:\n                        #endif\n                        #ifdef IFM_100G_CP2\n                        case IFM_100G_CP2:\n                        #endif\n                        #ifdef IFM_100G_CR4\n                        case IFM_100G_CR4:\n                        #endif\n                        #ifdef IFM_100G_CR_PAM4\n                        case IFM_100G_CR_PAM4:\n                        #endif\n                        #ifdef IFM_100G_DR\n                        case IFM_100G_DR:\n                        #endif\n                        #ifdef IFM_100G_KR2_PAM4\n                        case IFM_100G_KR2_PAM4:\n                        #endif\n                        #ifdef IFM_100G_KR4\n                        case IFM_100G_KR4:\n                        #endif\n                        #ifdef IFM_100G_KR_PAM4\n                        case IFM_100G_KR_PAM4:\n                        #endif\n                        #ifdef IFM_100G_LR4\n                        case IFM_100G_LR4:\n                        #endif\n                        #ifdef IFM_100G_SR2\n                        case IFM_100G_SR2:\n                        #endif\n                        #ifdef IFM_100G_SR4\n                        case IFM_100G_SR4:\n                        #endif\n                            iface->speed = 100000; break;\n                        #ifdef IFM_100_FX\n                        case IFM_100_FX:\n                        #endif\n                        #ifdef IFM_100_SGMII\n                        case IFM_100_SGMII:\n                        #endif\n                        #ifdef IFM_100_T\n                        case IFM_100_T:\n                        #endif\n                        #ifdef IFM_100_T2\n                        case IFM_100_T2:\n                        #endif\n                        #ifdef IFM_100_T4\n                        case IFM_100_T4:\n                        #endif\n                        #ifdef IFM_100_TX\n                        case IFM_100_TX:\n                        #endif\n                        #ifdef IFM_100_VG\n                        case IFM_100_VG:\n                        #endif\n                            iface->speed = 100; break;\n                        #ifdef IFM_10G_AOC\n                        case IFM_10G_AOC:\n                        #endif\n                        #ifdef IFM_10G_CR1\n                        case IFM_10G_CR1:\n                        #endif\n                        #ifdef IFM_10G_CX4\n                        case IFM_10G_CX4:\n                        #endif\n                        #ifdef IFM_10G_ER\n                        case IFM_10G_ER:\n                        #endif\n                        #ifdef IFM_10G_KR\n                        case IFM_10G_KR:\n                        #endif\n                        #ifdef IFM_10G_KX4\n                        case IFM_10G_KX4:\n                        #endif\n                        #ifdef IFM_10G_LR\n                        case IFM_10G_LR:\n                        #endif\n                        #ifdef IFM_10G_LRM\n                        case IFM_10G_LRM:\n                        #endif\n                        #ifdef IFM_10G_SFI\n                        case IFM_10G_SFI:\n                        #endif\n                        #ifdef IFM_10G_SR\n                        case IFM_10G_SR:\n                        #endif\n                        #ifdef IFM_10G_T\n                        case IFM_10G_T:\n                        #endif\n                        #ifdef IFM_10G_TWINAX\n                        case IFM_10G_TWINAX:\n                        #endif\n                        #ifdef IFM_10G_TWINAX_LONG\n                        case IFM_10G_TWINAX_LONG:\n                        #endif\n                            iface->speed = 10000; break;\n                        #ifdef IFM_10_2\n                        case IFM_10_2:\n                        #endif\n                        #ifdef IFM_10_5\n                        case IFM_10_5:\n                        #endif\n                        #ifdef IFM_10_FL\n                        case IFM_10_FL:\n                        #endif\n                        #ifdef IFM_10_STP\n                        case IFM_10_STP:\n                        #endif\n                        #ifdef IFM_10_T\n                        case IFM_10_T:\n                        #endif\n                            iface->speed = 10; break;\n                        #ifdef IFM_200G_AUI4\n                        case IFM_200G_AUI4:\n                        #endif\n                        #ifdef IFM_200G_AUI4_AC\n                        case IFM_200G_AUI4_AC:\n                        #endif\n                        #ifdef IFM_200G_AUI8\n                        case IFM_200G_AUI8:\n                        #endif\n                        #ifdef IFM_200G_AUI8_AC\n                        case IFM_200G_AUI8_AC:\n                        #endif\n                        #ifdef IFM_200G_CR4_PAM4\n                        case IFM_200G_CR4_PAM4:\n                        #endif\n                        #ifdef IFM_200G_DR4\n                        case IFM_200G_DR4:\n                        #endif\n                        #ifdef IFM_200G_FR4\n                        case IFM_200G_FR4:\n                        #endif\n                        #ifdef IFM_200G_KR4_PAM4\n                        case IFM_200G_KR4_PAM4:\n                        #endif\n                        #ifdef IFM_200G_LR4\n                        case IFM_200G_LR4:\n                        #endif\n                        #ifdef IFM_200G_SR4\n                        case IFM_200G_SR4:\n                        #endif\n                            iface->speed = 200000; break;\n                        #ifdef IFM_20G_KR2\n                        case IFM_20G_KR2:\n                        #endif\n                            iface->speed = 20000; break;\n                        #ifdef IFM_2500_KX\n                        case IFM_2500_KX:\n                        #endif\n                        #ifdef IFM_2500_SX\n                        case IFM_2500_SX:\n                        #endif\n                        #ifdef IFM_2500_T\n                        case IFM_2500_T:\n                        #endif\n                        #ifdef IFM_2500_X\n                        case IFM_2500_X:\n                        #endif\n                            iface->speed = 2500; break;\n                        #ifdef IFM_25G_ACC\n                        case IFM_25G_ACC:\n                        #endif\n                        #ifdef IFM_25G_AOC\n                        case IFM_25G_AOC:\n                        #endif\n                        #ifdef IFM_25G_AUI\n                        case IFM_25G_AUI:\n                        #endif\n                        #ifdef IFM_25G_CR\n                        case IFM_25G_CR:\n                        #endif\n                        #ifdef IFM_25G_CR1\n                        case IFM_25G_CR1:\n                        #endif\n                        #ifdef IFM_25G_CR_S\n                        case IFM_25G_CR_S:\n                        #endif\n                        #ifdef IFM_25G_KR\n                        case IFM_25G_KR:\n                        #endif\n                        #ifdef IFM_25G_KR1\n                        case IFM_25G_KR1:\n                        #endif\n                        #ifdef IFM_25G_KR_S\n                        case IFM_25G_KR_S:\n                        #endif\n                        #ifdef IFM_25G_LR\n                        case IFM_25G_LR:\n                        #endif\n                        #ifdef IFM_25G_PCIE\n                        case IFM_25G_PCIE:\n                        #endif\n                        #ifdef IFM_25G_SR\n                        case IFM_25G_SR:\n                        #endif\n                        #ifdef IFM_25G_T\n                        case IFM_25G_T:\n                        #endif\n                            iface->speed = 25000; break;\n                        #ifdef IFM_400G_AUI8\n                        case IFM_400G_AUI8:\n                        #endif\n                        #ifdef IFM_400G_AUI8_AC\n                        case IFM_400G_AUI8_AC:\n                        #endif\n                        #ifdef IFM_400G_DR4\n                        case IFM_400G_DR4:\n                        #endif\n                        #ifdef IFM_400G_FR8\n                        case IFM_400G_FR8:\n                        #endif\n                        #ifdef IFM_400G_LR8\n                        case IFM_400G_LR8:\n                        #endif\n                            iface->speed = 400000; break;\n                        #ifdef IFM_40G_CR4\n                        case IFM_40G_CR4:\n                        #endif\n                        #ifdef IFM_40G_ER4\n                        case IFM_40G_ER4:\n                        #endif\n                        #ifdef IFM_40G_KR4\n                        case IFM_40G_KR4:\n                        #endif\n                        #ifdef IFM_40G_LR4\n                        case IFM_40G_LR4:\n                        #endif\n                        #ifdef IFM_40G_SR4\n                        case IFM_40G_SR4:\n                        #endif\n                        #ifdef IFM_40G_XLAUI\n                        case IFM_40G_XLAUI:\n                        #endif\n                        #ifdef IFM_40G_XLAUI_AC\n                        case IFM_40G_XLAUI_AC:\n                        #endif\n                        #ifdef IFM_40G_XLPPI\n                        case IFM_40G_XLPPI:\n                        #endif\n                        #ifdef IFM_40G_LM4\n                        case IFM_40G_LM4:\n                        #endif\n                            iface->speed = 40000; break;\n                        #ifdef IFM_5000_KR\n                        case IFM_5000_KR:\n                        #endif\n                        #ifdef IFM_5000_KR1\n                        case IFM_5000_KR1:\n                        #endif\n                        #ifdef IFM_5000_KR_S\n                        case IFM_5000_KR_S:\n                        #endif\n                        #ifdef IFM_5000_T\n                        case IFM_5000_T:\n                        #endif\n                            iface->speed = 5000; break;\n                        #ifdef IFM_50G_AUI1\n                        case IFM_50G_AUI1:\n                        #endif\n                        #ifdef IFM_50G_AUI1_AC\n                        case IFM_50G_AUI1_AC:\n                        #endif\n                        #ifdef IFM_50G_AUI2\n                        case IFM_50G_AUI2:\n                        #endif\n                        #ifdef IFM_50G_AUI2_AC\n                        case IFM_50G_AUI2_AC:\n                        #endif\n                        #ifdef IFM_50G_CP\n                        case IFM_50G_CP:\n                        #endif\n                        #ifdef IFM_50G_CR2\n                        case IFM_50G_CR2:\n                        #endif\n                        #ifdef IFM_50G_FR\n                        case IFM_50G_FR:\n                        #endif\n                        #ifdef IFM_50G_KR2\n                        case IFM_50G_KR2:\n                        #endif\n                        #ifdef IFM_50G_KR_PAM4\n                        case IFM_50G_KR_PAM4:\n                        #endif\n                        #ifdef IFM_50G_LAUI2\n                        case IFM_50G_LAUI2:\n                        #endif\n                        #ifdef IFM_50G_LAUI2_AC\n                        case IFM_50G_LAUI2_AC:\n                        #endif\n                        #ifdef IFM_50G_LR\n                        case IFM_50G_LR:\n                        #endif\n                        #ifdef IFM_50G_LR2\n                        case IFM_50G_LR2:\n                        #endif\n                        #ifdef IFM_50G_PCIE\n                        case IFM_50G_PCIE:\n                        #endif\n                        #ifdef IFM_50G_SR\n                        case IFM_50G_SR:\n                        #endif\n                        #ifdef IFM_50G_SR2\n                        case IFM_50G_SR2:\n                        #endif\n                        #ifdef IFM_50G_KR4\n                        case IFM_50G_KR4:\n                        #endif\n                            iface->speed = 50000; break;\n                        #ifdef IFM_56G_R4\n                        case IFM_56G_R4:\n                        #endif\n                            iface->speed = 56000; break;\n                        default:\n                            iface->speed = -1;\n                            FF_DEBUG(\"Unknown media subtype for interface %s\", iface->name.chars);\n                            break;\n                        }\n                        if (iface->speed > 0)\n                            FF_DEBUG(\"Interface %s speed: %d Mbps\", iface->name.chars, iface->speed);\n                    }\n                    else\n                    {\n                        FF_DEBUG(\"Failed to get media info for interface %s\", iface->name.chars);\n                    }\n                    #endif\n                }\n\n                #if __sun || __GNU__\n                if ((options->showType & FF_LOCALIP_TYPE_MAC_BIT) && ioctl(sockfd, SIOCGIFHWADDR, &ifr) == 0)\n                {\n                    const uint8_t* ptr = (uint8_t*) ifr.ifr_addr.sa_data; // NOT ifr_enaddr\n                    ffStrbufSetF(&iface->mac, \"%02x:%02x:%02x:%02x:%02x:%02x\",\n                                ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5]);\n                    FF_DEBUG(\"Added MAC address %s for interface %s (Solaris/GNU)\", iface->mac.chars, iface->name.chars);\n                }\n                #endif\n                #if __sun\n                if (options->showType & FF_LOCALIP_TYPE_SPEED_BIT)\n                {\n                    __attribute__((__cleanup__(kstatFreeWrap))) kstat_ctl_t* kc = kstat_open();\n                    for (kstat_t* ks = kc->kc_chain; ks; ks = ks->ks_next)\n                    {\n                        if (!ffStrEquals(ks->ks_class, \"net\") || !ffStrEquals(ks->ks_module, \"link\")) continue;\n                        if (ffStrbufEqualS(&iface->name, ks->ks_name))\n                        {\n                            if (kstat_read(kc, ks, NULL) >= 0)\n                            {\n                                kstat_named_t* ifspeed = (kstat_named_t*) kstat_data_lookup(ks, \"ifspeed\");\n                                if (ifspeed)\n                                {\n                                    iface->speed = (int32_t) (ifspeed->value.ui64 / 1000 / 1000);\n                                    FF_DEBUG(\"Interface %s speed: %d Mbps (kstat)\", iface->name.chars, iface->speed);\n                                }\n                            }\n                            break;\n                        }\n                    }\n                }\n                #endif\n            }\n        }\n        else\n        {\n            FF_DEBUG(\"Failed to create socket for interface property retrieval\");\n        }\n    }\n\n    FF_DEBUG(\"Local IP detection completed, found %u interfaces\", results->length);\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/localip/localip_windows.c",
    "content": "#include <ws2tcpip.h>\n#include <iphlpapi.h>\n\n#include \"common/netif.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/windows/unicode.h\"\n#include \"common/debug.h\"\n#include \"localip.h\"\n\n#define FF_LOCALIP_NIFLAG(name) { IP_ADAPTER_##name, #name }\n\nstatic const FFLocalIpNIFlag niFlagOptions[] = {\n    FF_LOCALIP_NIFLAG(DDNS_ENABLED),\n    FF_LOCALIP_NIFLAG(REGISTER_ADAPTER_SUFFIX),\n    FF_LOCALIP_NIFLAG(DHCP_ENABLED),\n    FF_LOCALIP_NIFLAG(RECEIVE_ONLY),\n    FF_LOCALIP_NIFLAG(NO_MULTICAST),\n    FF_LOCALIP_NIFLAG(IPV6_OTHER_STATEFUL_CONFIG),\n    FF_LOCALIP_NIFLAG(NETBIOS_OVER_TCPIP_ENABLED),\n    FF_LOCALIP_NIFLAG(IPV4_ENABLED),\n    FF_LOCALIP_NIFLAG(IPV6_ENABLED),\n    FF_LOCALIP_NIFLAG(IPV6_MANAGE_ADDRESS_CONFIG),\n    // sentinel\n    {},\n};\n\nconst char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results)\n{\n    FF_DEBUG(\"Starting local IP detection with showType=0x%X, namePrefix='%.*s'\",\n             options->showType, (int)options->namePrefix.length, options->namePrefix.chars);\n\n    IP_ADAPTER_ADDRESSES* FF_AUTO_FREE adapter_addresses = NULL;\n\n    // Multiple attempts in case interfaces change while\n    // we are in the middle of querying them.\n    DWORD adapter_addresses_buffer_size = 0;\n    for (int attempts = 0;; ++attempts)\n    {\n        FF_DEBUG(\"Attempt %d to get adapter addresses, buffer size: %lu\", attempts + 1, adapter_addresses_buffer_size);\n\n        if (adapter_addresses_buffer_size)\n        {\n            adapter_addresses = (IP_ADAPTER_ADDRESSES*)realloc(adapter_addresses, adapter_addresses_buffer_size);\n            assert(adapter_addresses);\n        }\n\n        DWORD family = options->showType & FF_LOCALIP_TYPE_IPV4_BIT\n            ? options->showType & FF_LOCALIP_TYPE_IPV6_BIT ? AF_UNSPEC : AF_INET\n            : AF_INET6;\n        FF_DEBUG(\"Calling GetAdaptersAddresses with family=%u, flags=0x%X\", (unsigned)family,\n                 GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER);\n\n        DWORD error = GetAdaptersAddresses(\n            family,\n            GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER,\n            NULL,\n            adapter_addresses,\n            &adapter_addresses_buffer_size);\n\n        if (error == ERROR_SUCCESS)\n        {\n            FF_DEBUG(\"GetAdaptersAddresses succeeded on attempt %d\", attempts + 1);\n            break;\n        }\n        else if (ERROR_BUFFER_OVERFLOW == error && attempts < 4)\n        {\n            FF_DEBUG(\"Buffer overflow, need %lu bytes, retrying\", adapter_addresses_buffer_size);\n            continue;\n        }\n        else\n        {\n            FF_DEBUG(\"GetAdaptersAddresses failed with error %lu after %d attempts\", error, attempts + 1);\n            return \"GetAdaptersAddresses() failed\";\n        }\n    }\n\n    FF_MAYBE_UNUSED int adapterCount = 0, processedCount = 0;\n\n    // Iterate through all of the adapters\n    for (IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; adapter; adapter = adapter->Next)\n    {\n        adapterCount++;\n\n        FF_DEBUG(\"Processing adapter %d: IfIndex=%u, IfType=%u, OperStatus=%u\",\n                 adapterCount, (unsigned)adapter->IfIndex, (unsigned)adapter->IfType, (unsigned)adapter->OperStatus);\n\n        if (adapter->OperStatus != IfOperStatusUp)\n        {\n            FF_DEBUG(\"Skipping adapter %u (not operational, status=%d)\", (unsigned)adapter->IfIndex, adapter->OperStatus);\n            continue;\n        }\n\n        bool isLoop = adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK;\n        FF_DEBUG(\"Adapter %u: isLoopback=%s\", (unsigned)adapter->IfIndex, isLoop ? \"true\" : \"false\");\n\n        if (isLoop && !(options->showType & FF_LOCALIP_TYPE_LOOP_BIT))\n        {\n            FF_DEBUG(\"Skipping loopback adapter %u (loopback not requested)\", (unsigned)adapter->IfIndex);\n            continue;\n        }\n\n        FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateWS(adapter->FriendlyName);\n        FF_DEBUG(\"Adapter %u name: '%s'\", (unsigned)adapter->IfIndex, name.chars);\n\n        if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix))\n        {\n            FF_DEBUG(\"Skipping adapter %u (name doesn't match prefix '%.*s')\",\n                     (unsigned)adapter->IfIndex, (int)options->namePrefix.length, options->namePrefix.chars);\n            continue;\n        }\n\n        if (options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT)\n        {\n            if (!((options->showType & FF_LOCALIP_TYPE_IPV4_BIT) && ffNetifGetDefaultRouteV4()->ifIndex == adapter->IfIndex) &&\n                !((options->showType & FF_LOCALIP_TYPE_IPV6_BIT) && ffNetifGetDefaultRouteV6()->ifIndex == adapter->IfIndex))\n            {\n                FF_DEBUG(\"Skipping interface %u (not default route interface)\", (unsigned)adapter->IfIndex);\n                continue;\n            }\n        }\n\n        processedCount++;\n        FF_DEBUG(\"Creating result item for adapter %u ('%s')\", (unsigned)adapter->IfIndex, name.chars);\n\n        FFLocalIpResult* item = (FFLocalIpResult*) ffListAdd(results);\n        ffStrbufInitMove(&item->name, &name);\n        ffStrbufInit(&item->ipv4);\n        ffStrbufInit(&item->ipv6);\n        ffStrbufInit(&item->mac);\n        ffStrbufInit(&item->flags);\n        item->defaultRoute = FF_LOCALIP_TYPE_NONE;\n        item->speed = -1;\n        item->mtu = -1;\n\n        uint32_t typesToAdd = options->showType & (FF_LOCALIP_TYPE_IPV4_BIT | FF_LOCALIP_TYPE_IPV6_BIT | FF_LOCALIP_TYPE_ALL_IPS_BIT);\n        FF_DEBUG(\"Types to add for adapter %u: 0x%X\", (unsigned)adapter->IfIndex, typesToAdd);\n\n        FF_MAYBE_UNUSED int ipv4Count = 0, ipv6Count = 0;\n\n        for (IP_ADAPTER_UNICAST_ADDRESS* ifa = adapter->FirstUnicastAddress; ifa; ifa = ifa->Next)\n        {\n            FF_DEBUG(\"Processing unicast address: prefix origin=%d, suffix origin=%d, family=%d, DadState=%d\",\n                     ifa->PrefixOrigin, ifa->SuffixOrigin, ifa->Address.lpSockaddr->sa_family, ifa->DadState);\n\n            if (!(options->showType & FF_LOCALIP_TYPE_ALL_IPS_BIT))\n            {\n                if (ifa->DadState != IpDadStatePreferred)\n                {\n                    FF_DEBUG(\"Skipping address (not preferred)\");\n                    continue;\n                }\n\n                if (ifa->SuffixOrigin == IpSuffixOriginRandom)\n                {\n                    FF_DEBUG(\"Skipping temporary address (random suffix)\");\n                    continue;\n                }\n\n                // MIB_UNICASTIPADDRESS_ROW::SkipAsSource\n            }\n\n            if (ifa->Address.lpSockaddr->sa_family == AF_INET)\n            {\n                if (!(typesToAdd & (FF_LOCALIP_TYPE_IPV4_BIT | FF_LOCALIP_TYPE_ALL_IPS_BIT)))\n                {\n                    FF_DEBUG(\"Skipping IPv4 address (not requested in typesToAdd=0x%X)\", typesToAdd);\n                    continue;\n                }\n\n                bool isDefaultRoute = ((options->showType & FF_LOCALIP_TYPE_IPV4_BIT) && ffNetifGetDefaultRouteV4()->ifIndex == adapter->IfIndex);\n                if ((options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT) && !isDefaultRoute)\n                {\n                    FF_DEBUG(\"Skipping IPv4 address (not on default route interface)\");\n                    continue;\n                }\n\n                SOCKADDR_IN* ipv4 = (SOCKADDR_IN*) ifa->Address.lpSockaddr;\n                char addressBuffer[INET_ADDRSTRLEN + 10];\n                char* end = RtlIpv4AddressToStringA(&ipv4->sin_addr, addressBuffer);\n\n                if ((options->showType & FF_LOCALIP_TYPE_PREFIX_LEN_BIT) && ifa->OnLinkPrefixLength)\n                    end += snprintf(end, 10, \"/%u\", (unsigned) ifa->OnLinkPrefixLength);\n\n                FF_DEBUG(\"Adding IPv4 address: %s (isDefaultRoute=%s)\", addressBuffer, isDefaultRoute ? \"true\" : \"false\");\n\n                if (item->ipv4.length) ffStrbufAppendC(&item->ipv4, ',');\n                ffStrbufAppendNS(&item->ipv4, (uint32_t) (end - addressBuffer), addressBuffer);\n                if (isDefaultRoute) item->defaultRoute |= FF_LOCALIP_TYPE_IPV4_BIT;\n\n                ipv4Count++;\n                typesToAdd &= ~(unsigned) FF_LOCALIP_TYPE_IPV4_BIT;\n                if (typesToAdd == 0) break;\n            }\n            else if (ifa->Address.lpSockaddr->sa_family == AF_INET6)\n            {\n                if (!(typesToAdd & (FF_LOCALIP_TYPE_IPV6_BIT | FF_LOCALIP_TYPE_ALL_IPS_BIT)))\n                {\n                    FF_DEBUG(\"Skipping IPv6 address (not requested in typesToAdd=0x%X)\", typesToAdd);\n                    continue;\n                }\n\n                SOCKADDR_IN6* ipv6 = (SOCKADDR_IN6*) ifa->Address.lpSockaddr;\n\n                FFLocalIpIpv6Type ipv6Type = FF_LOCALIP_IPV6_TYPE_NONE;\n                if (IN6_IS_ADDR_GLOBAL(&ipv6->sin6_addr)) ipv6Type |= FF_LOCALIP_IPV6_TYPE_GUA_BIT;\n                else if (IN6_IS_ADDR_UNIQUE_LOCAL(&ipv6->sin6_addr)) ipv6Type |= FF_LOCALIP_IPV6_TYPE_ULA_BIT;\n                else if (IN6_IS_ADDR_LINKLOCAL(&ipv6->sin6_addr)) ipv6Type |= FF_LOCALIP_IPV6_TYPE_LLA_BIT;\n                else ipv6Type |= FF_LOCALIP_IPV6_TYPE_UNKNOWN_BIT;\n\n                if (!(options->ipv6Type & ipv6Type))\n                {\n                    FF_DEBUG(\"Skipping IPv6 address (doesn't match requested type 0x%X)\", options->ipv6Type);\n                    continue;\n                }\n\n                bool isDefaultRoute = ((options->showType & FF_LOCALIP_TYPE_IPV6_BIT) && ffNetifGetDefaultRouteV6()->ifIndex == adapter->IfIndex);\n                if ((options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT) && !isDefaultRoute)\n                {\n                    FF_DEBUG(\"Skipping IPv6 address (not on default route interface)\");\n                    continue;\n                }\n\n                char addressBuffer[INET6_ADDRSTRLEN + 10];\n                char* end = RtlIpv6AddressToStringA(&ipv6->sin6_addr, addressBuffer);\n\n                if ((options->showType & FF_LOCALIP_TYPE_PREFIX_LEN_BIT) && ifa->OnLinkPrefixLength)\n                    end += snprintf(end, 10, \"/%u\", (unsigned) ifa->OnLinkPrefixLength);\n\n                FF_DEBUG(\"Adding IPv6 address: %s (isDefaultRoute=%s)\", addressBuffer, isDefaultRoute ? \"true\" : \"false\");\n\n                if (item->ipv6.length) ffStrbufAppendC(&item->ipv6, ',');\n                ffStrbufAppendNS(&item->ipv6, (uint32_t) (end - addressBuffer), addressBuffer);\n                if (isDefaultRoute) item->defaultRoute |= FF_LOCALIP_TYPE_IPV6_BIT;\n\n                ipv6Count++;\n                typesToAdd &= ~(unsigned) FF_LOCALIP_TYPE_IPV6_BIT;\n                if (typesToAdd == 0) break;\n            }\n        }\n\n        FF_DEBUG(\"Adapter %u: collected %d IPv4 and %d IPv6 addresses\", (unsigned)adapter->IfIndex, ipv4Count, ipv6Count);\n\n        if (options->showType & FF_LOCALIP_TYPE_SPEED_BIT)\n        {\n            item->speed = (int32_t) (adapter->ReceiveLinkSpeed / 1000000);\n            FF_DEBUG(\"Adapter %u speed: %d Mbps (raw: %llu)\", (unsigned)adapter->IfIndex, item->speed, adapter->ReceiveLinkSpeed);\n        }\n        if (options->showType & FF_LOCALIP_TYPE_MTU_BIT)\n        {\n            item->mtu = (int32_t) adapter->Mtu;\n            FF_DEBUG(\"Adapter %u MTU: %d\", (unsigned)adapter->IfIndex, item->mtu);\n        }\n        if (options->showType & FF_LOCALIP_TYPE_FLAGS_BIT)\n        {\n            ffLocalIpFillNIFlags(&item->flags, adapter->Flags, niFlagOptions);\n            FF_DEBUG(\"Adapter %u flags: 0x%lX -> '%s'\", (unsigned)adapter->IfIndex, adapter->Flags, item->flags.chars);\n        }\n        if (options->showType & FF_LOCALIP_TYPE_MAC_BIT && adapter->PhysicalAddressLength == 6)\n        {\n            uint8_t* ptr = adapter->PhysicalAddress;\n            ffStrbufSetF(&item->mac, \"%02x:%02x:%02x:%02x:%02x:%02x\", ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5]);\n            FF_DEBUG(\"Adapter %u MAC: %s\", (unsigned)adapter->IfIndex, item->mac.chars);\n        }\n    }\n\n    FF_DEBUG(\"Local IP detection completed: scanned %d adapters, processed %d, results count: %u\",\n             adapterCount, processedCount, results->length);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/media/media.c",
    "content": "#include \"media.h\"\n#include \"common/io.h\"\n\nvoid ffDetectMediaImpl(FFMediaResult* media, bool saveCover);\n\nstatic FFMediaResult result;\n\nstatic void removeMediaCoverFile(void)\n{\n    if (result.cover.length > 0)\n    {\n        ffRemoveFile(result.cover.chars);\n        ffStrbufDestroy(&result.cover);\n    }\n}\n\nconst FFMediaResult* ffDetectMedia(bool saveCover)\n{\n    if (result.error.chars == NULL)\n    {\n        ffStrbufInit(&result.error);\n        ffStrbufInit(&result.playerId);\n        ffStrbufInit(&result.player);\n        ffStrbufInit(&result.song);\n        ffStrbufInit(&result.artist);\n        ffStrbufInit(&result.album);\n        ffStrbufInit(&result.url);\n        ffStrbufInit(&result.status);\n        ffStrbufInit(&result.cover);\n        result.removeCoverAfterUse = false;\n        ffDetectMediaImpl(&result, saveCover);\n\n        if(result.song.length == 0 && result.error.length == 0)\n            ffStrbufAppendS(&result.error, \"No media found\");\n        ffStrbufTrimRightSpace(&result.song);\n        ffStrbufTrimRightSpace(&result.artist);\n        ffStrbufTrimRightSpace(&result.album);\n        ffStrbufTrimRightSpace(&result.player);\n\n        if (saveCover && result.removeCoverAfterUse)\n            atexit(removeMediaCoverFile);\n    }\n\n    return &result;\n}\n"
  },
  {
    "path": "src/detection/media/media.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/media/option.h\"\n\ntypedef struct FFMediaResult\n{\n    FFstrbuf error;\n    FFstrbuf playerId; // Bus name on Linux, app bundle name on macOS. e.g. plasma-browser-integration\n    FFstrbuf player; // e.g. Google Chrome\n    FFstrbuf song;\n    FFstrbuf artist;\n    FFstrbuf album;\n    FFstrbuf url;\n    FFstrbuf status;\n    FFstrbuf cover;\n    bool removeCoverAfterUse;\n} FFMediaResult;\n\nconst FFMediaResult* ffDetectMedia(bool saveCover);\n"
  },
  {
    "path": "src/detection/media/media_apple.m",
    "content": "#include \"fastfetch.h\"\n#include \"common/processing.h\"\n#include \"common/apple/cf_helpers.h\"\n#include \"detection/media/media.h\"\n\n#import <Foundation/Foundation.h>\n#import <CoreFoundation/CoreFoundation.h>\n#import <CoreServices/CoreServices.h>\n\n// https://github.com/andrewwiik/iOS-Blocks/blob/master/Widgets/Music/MediaRemote.h\nextern void MRMediaRemoteGetNowPlayingInfo(dispatch_queue_t dispatcher, void(^callback)(_Nullable CFDictionaryRef info)) __attribute__((weak_import));\nextern void MRMediaRemoteGetNowPlayingApplicationIsPlaying(dispatch_queue_t queue, void (^callback)(BOOL playing)) __attribute__((weak_import));\nextern void MRMediaRemoteGetNowPlayingApplicationDisplayID(dispatch_queue_t queue, void (^callback)(_Nullable CFStringRef displayID)) __attribute__((weak_import));\nextern void MRMediaRemoteGetNowPlayingApplicationDisplayName(int unknown, dispatch_queue_t queue, void (^callback)(_Nullable CFStringRef name)) __attribute__((weak_import));\n\nstatic const char* getMediaByMediaRemote(FFMediaResult* result, bool saveCover)\n{\n    #define FF_TEST_FN_EXISTANCE(fn) if (!fn) return \"MediaRemote function \" #fn \" is not available\"\n    FF_TEST_FN_EXISTANCE(MRMediaRemoteGetNowPlayingInfo);\n    FF_TEST_FN_EXISTANCE(MRMediaRemoteGetNowPlayingApplicationIsPlaying);\n    #undef FF_TEST_FN_EXISTANCE\n\n    dispatch_group_t group = dispatch_group_create();\n    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);\n\n    dispatch_group_enter(group);\n    __block const char* error = NULL;\n    MRMediaRemoteGetNowPlayingInfo(queue, ^(_Nullable CFDictionaryRef info) {\n        if(info != nil)\n        {\n            ffCfDictGetString(info, CFSTR(\"kMRMediaRemoteNowPlayingInfoTitle\"), &result->song);\n            ffCfDictGetString(info, CFSTR(\"kMRMediaRemoteNowPlayingInfoArtist\"), &result->artist);\n            ffCfDictGetString(info, CFSTR(\"kMRMediaRemoteNowPlayingInfoAlbum\"), &result->album);\n\n            if (saveCover)\n            {\n                NSData* artworkData = (__bridge NSData*) CFDictionaryGetValue(info, CFSTR(\"kMRMediaRemoteNowPlayingInfoArtworkData\"));\n                if (artworkData)\n                {\n                    CFStringRef mime = (CFStringRef) CFDictionaryGetValue(info, CFSTR(\"kMRMediaRemoteNowPlayingInfoArtworkMIMEType\"));\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n                    FF_CFTYPE_AUTO_RELEASE CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mime, NULL);\n                    FF_CFTYPE_AUTO_RELEASE CFStringRef ext = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension);\n#pragma clang diagnostic pop\n                    NSString *tmpDir = NSTemporaryDirectory();\n                    NSString *uuid = NSUUID.UUID.UUIDString;\n                    NSString *path = [tmpDir stringByAppendingPathComponent:[NSString stringWithFormat:@\"ff_%@.%@\", uuid, ext ? (__bridge NSString *) ext : @\"img\"]];\n                    if ([artworkData writeToFile:path atomically:NO])\n                        ffStrbufSetS(&result->cover, path.UTF8String);\n                }\n            }\n        }\n        else\n            error = \"MRMediaRemoteGetNowPlayingInfo() failed\";\n\n        dispatch_group_leave(group);\n    });\n\n    dispatch_group_enter(group);\n    MRMediaRemoteGetNowPlayingApplicationIsPlaying(queue, ^(BOOL playing) {\n        ffStrbufSetStatic(&result->status, playing ? \"Playing\" : \"Paused\");\n        dispatch_group_leave(group);\n    });\n\n    if (MRMediaRemoteGetNowPlayingApplicationDisplayID)\n    {\n        dispatch_group_enter(group);\n        MRMediaRemoteGetNowPlayingApplicationDisplayID(queue, ^(_Nullable CFStringRef displayID) {\n            ffCfStrGetString(displayID, &result->playerId);\n            dispatch_group_leave(group);\n        });\n    }\n\n    if (MRMediaRemoteGetNowPlayingApplicationDisplayName)\n    {\n        dispatch_group_enter(group);\n        MRMediaRemoteGetNowPlayingApplicationDisplayName(0, queue, ^(_Nullable CFStringRef name) {\n            ffCfStrGetString(name, &result->player);\n            dispatch_group_leave(group);\n        });\n    }\n\n    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);\n    // Don't dispatch_release because we are using ARC\n\n    if(result->song.length > 0)\n        return NULL;\n\n    return error;\n}\n\n__attribute__((visibility(\"default\"), used))\nint ffPrintMediaByMediaRemote(bool saveCover)\n{\n    FFMediaResult media = {\n        .status = ffStrbufCreate(),\n        .song = ffStrbufCreate(),\n        .artist = ffStrbufCreate(),\n        .album = ffStrbufCreate(),\n        .playerId = ffStrbufCreate(),\n        .player = ffStrbufCreate(),\n        .cover = ffStrbufCreate(),\n    };\n    if (getMediaByMediaRemote(&media, saveCover) != NULL)\n        return 1;\n    ffStrbufPutTo(&media.status, stdout);\n    ffStrbufPutTo(&media.song, stdout);\n    ffStrbufPutTo(&media.artist, stdout);\n    ffStrbufPutTo(&media.album, stdout);\n    ffStrbufPutTo(&media.playerId, stdout);\n    ffStrbufPutTo(&media.player, stdout);\n    ffStrbufWriteTo(&media.cover, stdout);\n    ffStrbufDestroy(&media.status);\n    ffStrbufDestroy(&media.song);\n    ffStrbufDestroy(&media.artist);\n    ffStrbufDestroy(&media.album);\n    ffStrbufDestroy(&media.playerId);\n    ffStrbufDestroy(&media.player);\n    ffStrbufDestroy(&media.cover);\n    return 0;\n}\n\nstatic const char* getMediaByAuthorizedProcess(FFMediaResult* result, bool saveCover)\n{\n    // #1737\n    FF_STRBUF_AUTO_DESTROY script = ffStrbufCreateF(\"import ctypes;ctypes.CDLL('%s').ffPrintMediaByMediaRemote(%s)\", instance.state.platform.exePath.chars, saveCover ? \"True\" : \"False\");\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n\n    const char* error = ffProcessAppendStdOut(&buffer, (char* const[]) {\n        \"/usr/bin/python3\", // Must be signed by Apple. Homebrew python doesn't work\n        \"-c\",\n        script.chars,\n        nil\n    });\n    if (error) return error;\n    if (buffer.length == 0) return \"No media found\";\n\n    // status\\ntitle\\nartist\\nalbum\\nbundleName\\nappName\n    FFstrbuf* const varList[] = { &result->status, &result->song, &result->artist, &result->album, &result->playerId, &result->player, &result->cover };\n    char* line = NULL;\n    size_t len = 0;\n    for (uint32_t i = 0; i < ARRAY_SIZE(varList) && ffStrbufGetline(&line, &len, &buffer); ++i)\n        ffStrbufSetS(varList[i], line);\n    return NULL;\n}\n\nvoid ffDetectMediaImpl(FFMediaResult* media, bool saveCover)\n{\n    const char* error;\n    if (@available(macOS 15.4, *))\n        error = getMediaByAuthorizedProcess(media, saveCover);\n    else\n        error = getMediaByMediaRemote(media, saveCover);\n    if (error)\n        ffStrbufAppendS(&media->error, error);\n    else if (media->player.length == 0 && media->playerId.length > 0)\n    {\n        ffStrbufSet(&media->player, &media->playerId);\n        if (ffStrbufStartsWithIgnCaseS(&media->player, \"com.\"))\n            ffStrbufSubstrAfter(&media->player, strlen(\"com.\") - 1);\n        ffStrbufReplaceAllC(&media->player, '.', ' ');\n        if (media->cover.length > 0)\n            media->removeCoverAfterUse = true;\n    }\n}\n"
  },
  {
    "path": "src/detection/media/media_linux.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n#include \"common/memrchr.h\"\n#include \"detection/media/media.h\"\n\n#include <string.h>\n\n#define FF_DBUS_MPRIS_PREFIX \"org.mpris.MediaPlayer2.\"\n\n#ifdef FF_HAVE_DBUS\n#include \"common/dbus.h\"\n\n#define FF_DBUS_ITER_CONTINUE(dbus, iterator) \\\n    { \\\n        if(!(dbus)->lib->ffdbus_message_iter_next(iterator)) \\\n            break; \\\n        continue; \\\n    }\n\nstatic bool parseMprisMetadata(FFDBusData* data, DBusMessageIter* rootIterator, FFMediaResult* result)\n{\n    DBusMessageIter arrayIterator;\n\n    if (data->lib->ffdbus_message_iter_get_arg_type(rootIterator) == DBUS_TYPE_VARIANT)\n    {\n        DBusMessageIter variantIterator;\n        data->lib->ffdbus_message_iter_recurse(rootIterator, &variantIterator);\n        if(data->lib->ffdbus_message_iter_get_arg_type(&variantIterator) != DBUS_TYPE_ARRAY)\n            return false;\n        data->lib->ffdbus_message_iter_recurse(&variantIterator, &arrayIterator);\n    }\n    else\n    {\n        data->lib->ffdbus_message_iter_recurse(rootIterator, &arrayIterator);\n    }\n\n    while(true)\n    {\n        if(data->lib->ffdbus_message_iter_get_arg_type(&arrayIterator) != DBUS_TYPE_DICT_ENTRY)\n            FF_DBUS_ITER_CONTINUE(data, &arrayIterator)\n\n        DBusMessageIter dictIterator;\n        data->lib->ffdbus_message_iter_recurse(&arrayIterator, &dictIterator);\n\n        if(data->lib->ffdbus_message_iter_get_arg_type(&dictIterator) != DBUS_TYPE_STRING)\n            FF_DBUS_ITER_CONTINUE(data, &arrayIterator)\n\n        if(!data->lib->ffdbus_message_iter_has_next(&dictIterator))\n            FF_DBUS_ITER_CONTINUE(data, &arrayIterator)\n\n        const char* key;\n        data->lib->ffdbus_message_iter_get_basic(&dictIterator, &key);\n\n        data->lib->ffdbus_message_iter_next(&dictIterator);\n\n        if(ffStrStartsWith(key, \"xesam:\"))\n        {\n            const char* xesam = key + strlen(\"xesam:\");\n            if(ffStrEquals(xesam, \"title\"))\n                ffDBusGetString(data, &dictIterator, &result->song);\n            else if(ffStrEquals(xesam, \"album\"))\n                ffDBusGetString(data, &dictIterator, &result->album);\n            else if(ffStrEquals(xesam, \"artist\"))\n                ffDBusGetString(data, &dictIterator, &result->artist);\n            else if(ffStrEquals(xesam, \"url\"))\n                ffDBusGetString(data, &dictIterator, &result->url);\n\n            if(result->song.length > 0 && result->artist.length > 0 && result->album.length > 0 && result->url.length > 0)\n                break;\n        }\n        else if (ffStrStartsWith(key, \"mpris:\"))\n        {\n            const char* xesam = key + strlen(\"mpris:\");\n            if(ffStrEquals(xesam, \"artUrl\"))\n            {\n                FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();\n                ffDBusGetString(data, &dictIterator, &path);\n                if (ffStrbufStartsWithS(&path, \"file:///\"))\n                {\n                    ffStrbufEnsureFree(&result->cover, path.length - (uint32_t) strlen(\"file://\"));\n                    for (uint32_t i = (uint32_t) strlen(\"file://\"); i < path.length; ++i)\n                    {\n                        if (path.chars[i] == '%')\n                        {\n                            if (i + 2 >= path.length)\n                                break;\n                            char str[] = { path.chars[i + 1], path.chars[i + 2], 0 };\n                            char* end = NULL;\n                            const char decodedChar = (char) strtoul(str, &end, 16);\n                            if (end == &str[2])\n                            {\n                                i += 2;\n                                ffStrbufAppendC(&result->cover, decodedChar);\n                            }\n                            else\n                                ffStrbufAppendC(&result->cover, '%');\n                        }\n                        else\n                        {\n                            ffStrbufAppendC(&result->cover, path.chars[i]);\n                        }\n                    }\n                }\n            }\n        }\n\n        FF_DBUS_ITER_CONTINUE(data, &arrayIterator)\n    }\n\n    return true;\n}\n\nstatic bool getBusProperties(FFDBusData* data, const char* busName, FFMediaResult* result)\n{\n    // Get all properties at once to reduce the number of IPCs\n    DBusMessage* reply = ffDBusGetAllProperties(data, busName, \"/org/mpris/MediaPlayer2\", \"org.mpris.MediaPlayer2.Player\");\n    if(reply == NULL)\n        return false;\n\n    DBusMessageIter rootIterator;\n    if(!data->lib->ffdbus_message_iter_init(reply, &rootIterator) &&\n        data->lib->ffdbus_message_iter_get_arg_type(&rootIterator) != DBUS_TYPE_ARRAY)\n    {\n        data->lib->ffdbus_message_unref(reply);\n        return false;\n    }\n\n    DBusMessageIter arrayIterator;\n    data->lib->ffdbus_message_iter_recurse(&rootIterator, &arrayIterator);\n\n    while(true)\n    {\n        if(data->lib->ffdbus_message_iter_get_arg_type(&arrayIterator) != DBUS_TYPE_DICT_ENTRY)\n            FF_DBUS_ITER_CONTINUE(data, &arrayIterator)\n\n        DBusMessageIter dictIterator;\n        data->lib->ffdbus_message_iter_recurse(&arrayIterator, &dictIterator);\n\n        const char* key;\n        data->lib->ffdbus_message_iter_get_basic(&dictIterator, &key);\n\n        data->lib->ffdbus_message_iter_next(&dictIterator);\n\n        if(ffStrEquals(key, \"Metadata\"))\n            parseMprisMetadata(data, &dictIterator, result);\n        else if(ffStrEquals(key, \"PlaybackStatus\"))\n            ffDBusGetString(data, &dictIterator, &result->status);\n\n        FF_DBUS_ITER_CONTINUE(data, &arrayIterator)\n    }\n\n    if(result->song.length == 0)\n    {\n        if(result->url.length)\n        {\n            const char* fileName = memrchr(result->url.chars, '/', result->url.length);\n            assert(fileName);\n            ++fileName;\n            ffStrbufEnsureFixedLengthFree(&result->song, result->url.length - (uint32_t) (fileName - result->url.chars));\n            for(; *fileName && *fileName != '?'; ++fileName)\n            {\n                if (*fileName != '%')\n                {\n                    ffStrbufAppendC(&result->song, *fileName);\n                }\n                else\n                {\n                    if (fileName[1] == 0 || fileName[2] == 0)\n                        break;\n                    char str[] = { fileName[1], fileName[2], 0 };\n                    ffStrbufAppendC(&result->song, (char) strtoul(str, NULL, 16));\n                    fileName += 2;\n                }\n            }\n        }\n        else\n        {\n            ffStrbufClear(&result->artist);\n            ffStrbufClear(&result->album);\n            ffStrbufClear(&result->url);\n            ffStrbufClear(&result->status);\n            data->lib->ffdbus_message_unref(reply);\n            return false;\n        }\n    }\n\n    //Set short bus name\n    ffStrbufAppendS(&result->playerId, busName + sizeof(FF_DBUS_MPRIS_PREFIX) - 1);\n\n    //We found a song, get the player name\n    if (ffStrbufStartsWithS(&result->playerId, \"musikcube.instance\"))\n    {\n        // dbus calls are EXTREMELY slow on musikcube, so we set the player name manually\n        ffStrbufSetStatic(&result->player, \"musikcube\");\n    }\n    else\n    {\n        ffDBusGetPropertyString(data, busName, \"/org/mpris/MediaPlayer2\", \"org.mpris.MediaPlayer2\", \"Identity\", &result->player);\n        if(result->player.length == 0)\n            ffDBusGetPropertyString(data, busName, \"/org/mpris/MediaPlayer2\", \"org.mpris.MediaPlayer2\", \"DesktopEntry\", &result->player);\n        if(result->player.length == 0)\n            ffStrbufAppend(&result->player, &result->playerId);\n    }\n\n    data->lib->ffdbus_message_unref(reply);\n\n    return true;\n}\n\nstatic void getCustomBus(FFDBusData* data, const FFstrbuf* playerName, FFMediaResult* result)\n{\n    if(ffStrbufStartsWithS(playerName, FF_DBUS_MPRIS_PREFIX))\n    {\n        getBusProperties(data, playerName->chars, result);\n        return;\n    }\n\n    FF_STRBUF_AUTO_DESTROY busName = ffStrbufCreateS(FF_DBUS_MPRIS_PREFIX);\n    ffStrbufAppend(&busName, playerName);\n    getBusProperties(data, busName.chars, result);\n}\n\nstatic void getBestBus(FFDBusData* data, FFMediaResult* result)\n{\n    if(\n        getBusProperties(data, FF_DBUS_MPRIS_PREFIX \"spotify\", result) ||\n        getBusProperties(data, FF_DBUS_MPRIS_PREFIX \"vlc\", result) ||\n        getBusProperties(data, FF_DBUS_MPRIS_PREFIX \"plasma-browser-integration\", result)\n    ) return;\n\n    DBusMessage* reply = ffDBusGetMethodReply(data, \"org.freedesktop.DBus\", \"/org/freedesktop/DBus\", \"org.freedesktop.DBus\", \"ListNames\", NULL, NULL);\n    if(reply == NULL)\n        return;\n\n    DBusMessageIter rootIterator;\n    if(!data->lib->ffdbus_message_iter_init(reply, &rootIterator) || data->lib->ffdbus_message_iter_get_arg_type(&rootIterator) != DBUS_TYPE_ARRAY)\n        return;\n\n    DBusMessageIter arrayIterator;\n    data->lib->ffdbus_message_iter_recurse(&rootIterator, &arrayIterator);\n\n    while(true)\n    {\n        if(data->lib->ffdbus_message_iter_get_arg_type(&arrayIterator) != DBUS_TYPE_STRING)\n            FF_DBUS_ITER_CONTINUE(data, &arrayIterator)\n\n        const char* busName;\n        data->lib->ffdbus_message_iter_get_basic(&arrayIterator, &busName);\n\n        if(!ffStrStartsWith(busName, FF_DBUS_MPRIS_PREFIX))\n            FF_DBUS_ITER_CONTINUE(data, &arrayIterator)\n\n        if(getBusProperties(data, busName, result))\n            break;\n\n        FF_DBUS_ITER_CONTINUE(data, &arrayIterator)\n    }\n\n    data->lib->ffdbus_message_unref(reply);\n}\n\nstatic const char* getMedia(FFMediaResult* result)\n{\n    FF_DBUS_AUTO_DESTROY_DATA FFDBusData data = {};\n    const char* error = ffDBusLoadData(DBUS_BUS_SESSION, &data);\n    if(error != NULL)\n        return error;\n\n    // FIXME: This is shared for both player and media module.\n    // However it uses an option in one specific module\n    if(instance.config.general.playerName.length > 0)\n        getCustomBus(&data, &instance.config.general.playerName, result);\n    else\n        getBestBus(&data, result);\n\n    return NULL;\n}\n\n#endif\n\nvoid ffDetectMediaImpl(FFMediaResult* media, bool saveCover)\n{\n    FF_UNUSED(saveCover); // We don't save the cover to a file for Mpris implementation\n    #ifdef FF_HAVE_DBUS\n        const char* error = getMedia(media);\n        ffStrbufAppendS(&media->error, error);\n    #else\n        ffStrbufAppendS(&media->error, \"Fastfetch was compiled without DBus support\");\n    #endif\n}\n"
  },
  {
    "path": "src/detection/media/media_nosupport.c",
    "content": "#include \"media.h\"\n\nvoid ffDetectMediaImpl(FFMediaResult* media)\n{\n    ffStrbufAppendS(&media->error, \"Not supported on this platform\");\n}\n"
  },
  {
    "path": "src/detection/media/media_windows.c",
    "content": "#include \"common/library.h\"\n#include \"common/windows/unicode.h\"\n#include \"media.h\"\n#include \"media_windows.dll.h\"\n\nstatic const char* getMedia(FFMediaResult* media, bool saveCover)\n{\n    FF_LIBRARY_LOAD_MESSAGE(libffwinrt, \"libffwinrt\" FF_LIBRARY_EXTENSION, 0)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libffwinrt, ffWinrtDetectMedia)\n    libffwinrt = NULL; // Don't close libffwinrt or it may crash\n\n    FFWinrtMediaResult result = {};\n\n    const char* error = ffffWinrtDetectMedia(&result, saveCover);\n    if (error)\n    {\n        ffStrbufSetStatic(&media->error, error);\n        return NULL;\n    }\n\n    ffStrbufSetWS(&media->playerId, result.playerId);\n    if (result.playerName[0])\n    {\n        ffStrbufSetWS(&media->player, result.playerName);\n    }\n    else\n    {\n        ffStrbufSet(&media->player, &media->playerId);\n        if (ffStrbufEndsWithIgnCaseS(&media->player, \".exe\"))\n            ffStrbufSubstrBefore(&media->player, media->player.length - 4);\n    }\n    ffStrbufSetWS(&media->song, result.song);\n    ffStrbufSetWS(&media->artist, result.artist);\n    ffStrbufSetWS(&media->album, result.album);\n    ffStrbufSetWS(&media->cover, result.cover);\n    ffStrbufSetStatic(&media->status, result.status);\n    if (media->cover.length > 0)\n        media->removeCoverAfterUse = true;\n    return NULL;\n}\n\nvoid ffDetectMediaImpl(FFMediaResult* media, bool saveCover)\n{\n    const char* error = getMedia(media, saveCover);\n    ffStrbufAppendS(&media->error, error);\n}\n"
  },
  {
    "path": "src/detection/media/media_windows.dll.cpp",
    "content": "#include <winrt/Windows.ApplicationModel.h>\n#include <winrt/Windows.Foundation.h>\n#include <winrt/Windows.Media.Control.h>\n#include <winrt/Windows.Storage.Streams.h>\n#include <winrt/Windows.Storage.h>\n#include <wchar.h>\n#include <windows.h>\n\nextern \"C\"\n{\n#include \"media_windows.dll.h\"\n\nconst char* ffWinrtDetectMedia(FFWinrtMediaResult* result, bool saveCover)\n{\n    // C++/WinRT requires Windows 8.1+ and C++ runtime (std::string, exceptions and other stuff)\n    // Make it a separate dll in order not to break Windows 7 support\n    using namespace winrt::Windows::Media::Control;\n    using namespace winrt::Windows::ApplicationModel;\n\n    try\n    {\n        auto manager = GlobalSystemMediaTransportControlsSessionManager::RequestAsync().get();\n        if (!manager)\n            return \"winrt: GlobalSystemMediaTransportControlsSessionManager::RequestAsync() failed\";\n\n        auto session = manager.GetCurrentSession();\n        if (!session)\n            return \"winrt: GetCurrentSession() failed\";\n\n        auto mediaProps = session\n            .TryGetMediaPropertiesAsync()\n            .get();\n        if (!mediaProps)\n            return \"winrt: TryGetMediaPropertiesAsync() failed\";\n\n        if (auto playbackInfo = session.GetPlaybackInfo())\n        {\n            switch (playbackInfo.PlaybackStatus())\n            {\n            #define FF_MEDIA_SET_STATUS(status_code) \\\n    case GlobalSystemMediaTransportControlsSessionPlaybackStatus::status_code: result->status = #status_code; break;\n                FF_MEDIA_SET_STATUS(Closed)\n                FF_MEDIA_SET_STATUS(Opened)\n                FF_MEDIA_SET_STATUS(Changing)\n                FF_MEDIA_SET_STATUS(Stopped)\n                FF_MEDIA_SET_STATUS(Playing)\n                FF_MEDIA_SET_STATUS(Paused)\n            #undef FF_MEDIA_SET_STATUS\n            }\n        }\n\n        ::wcsncpy(result->playerId, session.SourceAppUserModelId().data(), FF_MEDIA_WIN_RESULT_BUFLEN);\n        result->playerId[FF_MEDIA_WIN_RESULT_BUFLEN - 1] = L'\\0';\n        ::wcsncpy(result->song, mediaProps.Title().data(), FF_MEDIA_WIN_RESULT_BUFLEN);\n        result->song[FF_MEDIA_WIN_RESULT_BUFLEN - 1] = L'\\0';\n        ::wcsncpy(result->artist, mediaProps.Artist().data(), FF_MEDIA_WIN_RESULT_BUFLEN);\n        result->artist[FF_MEDIA_WIN_RESULT_BUFLEN - 1] = L'\\0';\n        ::wcsncpy(result->album, mediaProps.AlbumTitle().data(), FF_MEDIA_WIN_RESULT_BUFLEN);\n        result->album[FF_MEDIA_WIN_RESULT_BUFLEN - 1] = L'\\0';\n        try\n        {\n            // Only works for UWP apps\n            ::wcsncpy(result->playerName, AppInfo::GetFromAppUserModelId(session.SourceAppUserModelId()).DisplayInfo().DisplayName().data(), FF_MEDIA_WIN_RESULT_BUFLEN);\n            result->playerName[FF_MEDIA_WIN_RESULT_BUFLEN - 1] = L'\\0';\n        } catch (...) { }\n\n        if (saveCover)\n        {\n            using namespace winrt::Windows::Storage;\n            using namespace winrt::Windows::Storage::Streams;\n            if (auto thumbRef = mediaProps.Thumbnail())\n            {\n                try\n                {\n                    if (auto stream = thumbRef.OpenReadAsync().get())\n                    {\n                        if (stream.Size() > 0)\n                        {\n                            Buffer buffer(static_cast<uint32_t>(stream.Size()));\n                            stream.ReadAsync(buffer, buffer.Capacity(), InputStreamOptions::None).get();\n\n                            wchar_t tempPath[MAX_PATH];\n                            if (GetTempPathW(MAX_PATH, tempPath) > 0)\n                            {\n                                auto tempFolder = StorageFolder::GetFolderFromPathAsync(tempPath).get();\n                                auto tempFile = tempFolder.CreateFileAsync(L\"ff_thumb.img\", CreationCollisionOption::GenerateUniqueName).get();\n                                FileIO::WriteBufferAsync(tempFile, buffer).get();\n\n                                ::wcsncpy(result->cover, tempFile.Path().data(), FF_MEDIA_WIN_RESULT_BUFLEN);\n                                result->cover[FF_MEDIA_WIN_RESULT_BUFLEN - 1] = L'\\0';\n                            }\n                        }\n                    }\n                }\n                catch (...)\n                {\n                    // Ignore thumbnail errors\n                }\n            }\n        }\n\n        return NULL;\n    }\n    catch (...)\n    {\n        return \"A C++ exception is thrown\";\n    }\n}\n\n} // extern \"C\"\n"
  },
  {
    "path": "src/detection/media/media_windows.dll.h",
    "content": "#pragma once\n\n#define FF_MEDIA_WIN_RESULT_BUFLEN 256\n\ntypedef struct FFWinrtMediaResult\n{\n    wchar_t playerId[FF_MEDIA_WIN_RESULT_BUFLEN];\n    wchar_t playerName[FF_MEDIA_WIN_RESULT_BUFLEN];\n    wchar_t song[FF_MEDIA_WIN_RESULT_BUFLEN];\n    wchar_t artist[FF_MEDIA_WIN_RESULT_BUFLEN];\n    wchar_t album[FF_MEDIA_WIN_RESULT_BUFLEN];\n    wchar_t cover[FF_MEDIA_WIN_RESULT_BUFLEN];\n    const char* status;\n} FFWinrtMediaResult;\n\n__attribute__((__dllexport__))\nconst char* ffWinrtDetectMedia(FFWinrtMediaResult* result, bool saveCover);\n"
  },
  {
    "path": "src/detection/memory/memory.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/memory/option.h\"\n\ntypedef struct FFMemoryResult\n{\n    uint64_t bytesUsed;\n    uint64_t bytesTotal;\n} FFMemoryResult;\n\nconst char* ffDetectMemory(FFMemoryResult* ram);\n"
  },
  {
    "path": "src/detection/memory/memory_apple.c",
    "content": "#include \"memory.h\"\n#include \"common/debug.h\"\n\n#include <string.h>\n#include <mach/mach.h>\n#include <sys/sysctl.h>\n#include <unistd.h>\n\nconst char* ffDetectMemory(FFMemoryResult* ram)\n{\n    size_t length = sizeof(ram->bytesTotal);\n\n    #if FF_APPLE_MEMSIZE_USABLE\n    if (sysctlbyname(\"hw.memsize_usable\", &ram->bytesTotal, &length, NULL, 0) != 0)\n        return \"Failed to read hw.memsize_usable\";\n    #else\n    if (sysctl((int[]){ CTL_HW, HW_MEMSIZE }, 2, &ram->bytesTotal, &length, NULL, 0) != 0)\n        return \"Failed to read hw.memsize\";\n    #endif\n\n    mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;\n    vm_statistics64_data_t vmstat;\n    if(host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t) (&vmstat), &count) != KERN_SUCCESS)\n        return \"Failed to read host_statistics64\";\n\n    // https://github.com/st3fan/osx-10.9/blob/34e34a6a539b5a822cda4074e56a7ced9b57da71/system_cmds-597.1.1/vm_stat.tproj/vm_stat.c#L139\n\n    uint64_t pagesFree = vmstat.free_count - vmstat.speculative_count;\n    uint64_t pagesFileBacked = vmstat.external_page_count; // Cached files\n    ram->bytesUsed = ram->bytesTotal - (pagesFree + pagesFileBacked) * instance.state.platform.sysinfo.pageSize;\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/memory/memory_bsd.c",
    "content": "#include \"memory.h\"\n#include \"common/sysctl.h\"\n\nconst char* ffDetectMemory(FFMemoryResult* ram)\n{\n    size_t length = sizeof(ram->bytesTotal);\n    if (sysctl((int[]){ CTL_HW, HW_PHYSMEM }, 2, &ram->bytesTotal, &length, NULL, 0))\n        return \"Failed to read hw.physmem\";\n\n    // vm.stats.vm.* are int values\n    int32_t pagesFree = ffSysctlGetInt(\"vm.stats.vm.v_free_count\", 0)\n        + ffSysctlGetInt(\"vm.stats.vm.v_inactive_count\", 0)\n        + ffSysctlGetInt(\"vm.stats.vm.v_cache_count\", 0);\n\n    ram->bytesUsed = ram->bytesTotal - (uint64_t) pagesFree * instance.state.platform.sysinfo.pageSize;\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/memory/memory_haiku.c",
    "content": "#include \"memory.h\"\n\n#include <OS.h>\n\nconst char* ffDetectMemory(FFMemoryResult* ram)\n{\n    system_info info;\n    if (get_system_info(&info) != B_OK)\n        return \"Error getting system info\";\n\n    uint32_t pageSize = instance.state.platform.sysinfo.pageSize;\n    ram->bytesTotal = pageSize * info.max_pages;\n    ram->bytesUsed = pageSize * info.used_pages;\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/memory/memory_linux.c",
    "content": "#include \"memory.h\"\n#include \"common/io.h\"\n#include \"common/mallocHelper.h\"\n\n#include <inttypes.h>\n\nconst char* ffDetectMemory(FFMemoryResult* ram)\n{\n    char buf[PROC_FILE_BUFFSIZ];\n    ssize_t nRead = ffReadFileData(\"/proc/meminfo\", ARRAY_SIZE(buf) - 1, buf);\n    if(nRead < 0)\n        return \"ffReadFileData(\\\"/proc/meminfo\\\", ARRAY_SIZE(buf)-1, buf)\";\n    buf[nRead] = '\\0';\n\n    uint64_t memTotal = 0,\n             memAvailable = 0,\n             shmem = 0,\n             memFree = 0,\n             buffers = 0,\n             cached = 0,\n             sReclaimable = 0;\n\n    char *token = NULL;\n    if((token = strstr(buf, \"MemTotal:\")) != NULL)\n        memTotal = strtoul(token + strlen(\"MemTotal:\"), NULL, 10);\n    else\n        return \"MemTotal not found in /proc/meminfo\";\n\n    if((token = strstr(buf, \"MemAvailable:\")) != NULL)\n        memAvailable = strtoul(token + strlen(\"MemAvailable:\"), NULL, 10);\n    if (memAvailable == 0 || memAvailable >= memTotal) // MemAvailable can be unreasonable. #1988\n    {\n        if((token = strstr(buf, \"MemFree:\")) != NULL)\n            memFree = strtoul(token + strlen(\"MemFree:\"), NULL, 10);\n\n        if((token = strstr(buf, \"Buffers:\")) != NULL)\n            buffers = strtoul(token + strlen(\"Buffers:\"), NULL, 10);\n\n        if((token = strstr(buf, \"Cached:\")) != NULL)\n            cached = strtoul(token + strlen(\"Cached:\"), NULL, 10);\n\n        if((token = strstr(buf, \"Shmem:\")) != NULL)\n            shmem = strtoul(token + strlen(\"Shmem:\"), NULL, 10);\n\n        if((token = strstr(buf, \"SReclaimable:\")) != NULL)\n            sReclaimable = strtoul(token + strlen(\"SReclaimable:\"), NULL, 10);\n\n        memAvailable = memFree + buffers + cached + sReclaimable - shmem;\n    }\n\n    ram->bytesTotal = memTotal * 1024lu;\n    ram->bytesUsed = (memTotal - memAvailable) * 1024lu;\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/memory/memory_nbsd.c",
    "content": "#include \"memory.h\"\n#include \"common/sysctl.h\"\n\n#include <sys/param.h>\n#include <uvm/uvm_extern.h>\n\nconst char* ffDetectMemory(FFMemoryResult* ram)\n{\n    struct uvmexp_sysctl buf;\n    size_t length = sizeof(buf);\n    if (sysctl((int[]){ CTL_VM, VM_UVMEXP2 }, 2, &buf, &length, NULL, 0) < 0)\n        return \"sysctl(CTL_VM, VM_UVMEXP2) failed\";\n\n    ram->bytesTotal = (uint64_t) buf.npages * instance.state.platform.sysinfo.pageSize;\n    ram->bytesUsed = ((uint64_t) buf.active + (uint64_t) buf.inactive + (uint64_t) buf.wired) * instance.state.platform.sysinfo.pageSize;\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/memory/memory_nosupport.c",
    "content": "#include \"memory.h\"\n\nconst char* ffDetectMemory(FF_MAYBE_UNUSED FFMemoryResult* ram)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/memory/memory_obsd.c",
    "content": "#include \"memory.h\"\n#include \"common/sysctl.h\"\n\n#include <sys/param.h>\n#include <uvm/uvm_extern.h>\n\nconst char* ffDetectMemory(FFMemoryResult* ram)\n{\n    struct uvmexp buf;\n    size_t length = sizeof(buf);\n    if (sysctl((int[]){ CTL_VM, VM_UVMEXP }, 2, &buf, &length, NULL, 0) < 0)\n        return \"sysctl(CTL_VM, VM_UVMEXP) failed\";\n\n    ram->bytesTotal = (uint64_t) buf.npages * instance.state.platform.sysinfo.pageSize;\n    ram->bytesUsed = ((uint64_t) buf.active + (uint64_t) buf.inactive + (uint64_t) buf.wired) * instance.state.platform.sysinfo.pageSize;\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/memory/memory_sunos.c",
    "content": "#include \"memory.h\"\n#include <unistd.h>\n\nconst char* ffDetectMemory(FFMemoryResult* ram)\n{\n    ram->bytesTotal = (uint64_t) sysconf(_SC_PHYS_PAGES) * instance.state.platform.sysinfo.pageSize;\n    ram->bytesUsed = ram->bytesTotal - (uint64_t) sysconf(_SC_AVPHYS_PAGES) * instance.state.platform.sysinfo.pageSize;\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/memory/memory_windows.c",
    "content": "#include \"memory.h\"\n\n#include <windows.h>\n\nconst char* ffDetectMemory(FFMemoryResult* ram)\n{\n    MEMORYSTATUSEX statex = {\n        .dwLength = sizeof(statex),\n    };\n    // GlobalMemoryStatusEx() internally uses\n    // NtQuerySystemInformation(SystemBasicPerformanceInformation) in Win 7, and\n    // NtQuerySystemInformation(SystemMemoryUsageInformation) in Win 10\n    if (!GlobalMemoryStatusEx(&statex))\n        return \"GlobalMemoryStatusEx() failed\";\n\n    ram->bytesTotal = statex.ullTotalPhys;\n    ram->bytesUsed = statex.ullTotalPhys - statex.ullAvailPhys;\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/mouse/mouse.h",
    "content": "#include \"fastfetch.h\"\n\ntypedef struct FFMouseDevice\n{\n    FFstrbuf serial;\n    FFstrbuf name;\n} FFMouseDevice;\n\nconst char* ffDetectMouse(FFlist* devices /* List of FFMouseDevice */);\n"
  },
  {
    "path": "src/detection/mouse/mouse_apple.c",
    "content": "#include \"mouse.h\"\n#include \"common/apple/cf_helpers.h\"\n#include \"common/mallocHelper.h\"\n\n#include <IOKit/IOKitLib.h>\n#include <IOKit/hid/IOHIDLib.h>\n\nstatic void enumSet(IOHIDDeviceRef value, FFlist* results)\n{\n    FFMouseDevice* device = (FFMouseDevice*) ffListAdd(results);\n    ffStrbufInit(&device->serial);\n    ffStrbufInit(&device->name);\n\n    CFStringRef product = IOHIDDeviceGetProperty(value, CFSTR(kIOHIDProductKey));\n    ffCfStrGetString(product, &device->name);\n\n    CFStringRef serialNumber = IOHIDDeviceGetProperty(value, CFSTR(kIOHIDSerialNumberKey));\n    ffCfStrGetString(serialNumber, &device->serial);\n}\n\nconst char* ffDetectMouse(FFlist* devices /* List of FFMouseDevice */)\n{\n    IOHIDManagerRef FF_CFTYPE_AUTO_RELEASE manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);\n    if (IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone) != kIOReturnSuccess)\n        return \"IOHIDManagerOpen() failed\";\n\n    CFDictionaryRef FF_CFTYPE_AUTO_RELEASE matching1 = CFDictionaryCreate(kCFAllocatorDefault, (const void **)(CFStringRef[]){\n        CFSTR(kIOHIDDeviceUsagePageKey),\n        CFSTR(kIOHIDDeviceUsageKey)\n    }, (const void **)(CFNumberRef[]){\n        ffCfCreateInt(kHIDPage_GenericDesktop),\n        ffCfCreateInt(kHIDUsage_GD_Mouse)\n    }, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);\n    IOHIDManagerSetDeviceMatching(manager, matching1);\n\n    CFSetRef FF_CFTYPE_AUTO_RELEASE set = IOHIDManagerCopyDevices(manager);\n    if (set)\n        CFSetApplyFunction(set, (CFSetApplierFunction) &enumSet, devices);\n    IOHIDManagerClose(manager, kIOHIDOptionsTypeNone);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/mouse/mouse_bsd.c",
    "content": "#include \"mouse.h\"\n#include \"common/io.h\"\n\n#include <stdio.h>\n#include <fcntl.h>\n#include <usbhid.h>\n\n#if __has_include(<dev/usb/usb_ioctl.h>)\n#include <dev/usb/usb_ioctl.h> // FreeBSD\n#else\n#include <bus/u4b/usb_ioctl.h> // DragonFly\n#endif\n\n#define MAX_UHID_MICE 64\n\nconst char* ffDetectMouse(FFlist* devices /* List of FFMouseDevice */)\n{\n    char path[16];\n    for (int i = 0; i < MAX_UHID_MICE; i++)\n    {\n        snprintf(path, ARRAY_SIZE(path), \"/dev/uhid%d\", i);\n        FF_AUTO_CLOSE_FD int fd = open(path, O_RDONLY | O_CLOEXEC);\n        if (fd < 0)\n        {\n            if (errno == ENOENT)\n                break; // No more devices\n            continue; // Device not found\n        }\n        report_desc_t repDesc = hid_get_report_desc(fd);\n        if (!repDesc) continue;\n\n        int reportId = hid_get_report_id(fd);\n\n        struct hid_data* hData = hid_start_parse(repDesc, 0, reportId);\n        if (hData)\n        {\n            struct hid_item hItem;\n            while (hid_get_item(hData, &hItem) > 0)\n            {\n                if (HID_PAGE(hItem.usage) != 1 || HID_USAGE(hItem.usage) != 2) continue;\n\n                struct usb_device_info di;\n                if (ioctl(fd, USB_GET_DEVICEINFO, &di) != -1)\n                {\n                    FFMouseDevice* device = (FFMouseDevice*) ffListAdd(devices);\n                    ffStrbufInitS(&device->serial, di.udi_serial);\n                    ffStrbufInitS(&device->name, di.udi_product);\n                }\n            }\n            hid_end_parse(hData);\n        }\n\n        hid_dispose_report_desc(repDesc);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/mouse/mouse_haiku.cpp",
    "content": "extern \"C\" {\n#include \"mouse.h\"\n}\n\n#include <interface/Input.h>\n#include <support/List.h>\n\nconst char* ffDetectMouse(FFlist* devices /* List of FFMouseDevice */)\n{\n    BList list;\n\n    if (get_input_devices(&list) != B_OK)\n        return \"get_input_devices() failed\";\n\n    for (int32 i = 0, n = list.CountItems(); i < n; i++)\n    {\n        BInputDevice *device = (BInputDevice *) list.ItemAt(i);\n        if (device->Type() != B_POINTING_DEVICE || !device->IsRunning())\n            continue;\n\n        FFMouseDevice* item = (FFMouseDevice*) ffListAdd(devices);\n        ffStrbufInit(&item->serial);\n        ffStrbufInitS(&item->name, device->Name());\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/mouse/mouse_linux.c",
    "content": "#include \"mouse.h\"\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n\nconst char* ffDetectMouse(FFlist* devices /* List of FFMouseDevice */)\n{\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(\"/sys/class/input/\");\n    if (dirp == NULL)\n        return \"opendir(\\\"/sys/class/input/\\\") == NULL\";\n\n    FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateS(\"/sys/class/input/\");\n    uint32_t baseLen = path.length;\n\n    struct dirent* entry;\n    while ((entry = readdir(dirp)) != NULL)\n    {\n        if (!ffStrStartsWith(entry->d_name, \"mouse\"))\n            continue;\n        if (!ffCharIsDigit(entry->d_name[strlen(\"mouse\")]))\n            continue;\n\n        ffStrbufAppendS(&path, entry->d_name);\n        ffStrbufAppendS(&path, \"/device/name\");\n\n        FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate();\n        if (ffAppendFileBuffer(path.chars, &name))\n        {\n            ffStrbufTrimRightSpace(&name);\n            ffStrbufSubstrBefore(&path, path.length - 4);\n\n            FFMouseDevice* device = (FFMouseDevice*) ffListAdd(devices);\n            ffStrbufInitMove(&device->name, &name);\n            ffStrbufInit(&device->serial);\n\n            ffStrbufAppendS(&path, \"uniq\");\n            if (ffAppendFileBuffer(path.chars, &device->serial))\n                ffStrbufTrimRightSpace(&device->serial);\n        }\n\n        ffStrbufSubstrBefore(&path, baseLen);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/mouse/mouse_nosupport.c",
    "content": "#include \"mouse.h\"\n\nconst char* ffDetectMouse(FF_MAYBE_UNUSED FFlist* devices /* List of FFMouseDevice */)\n{\n    return \"No mouse support on this platform\";\n}\n"
  },
  {
    "path": "src/detection/mouse/mouse_windows.c",
    "content": "#define INITGUID\n\n#include \"mouse.h\"\n#include \"common/io.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/windows/unicode.h\"\n\n#include <windows.h>\n#include <hidsdi.h>\n#include <cfgmgr32.h>\n#include <devpkey.h>\n\nconst char* ffDetectMouse(FFlist* devices /* List of FFMouseDevice */)\n{\n    UINT nDevices = 0;\n    if (GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)))\n        return \"GetRawInputDeviceList(NULL) failed\";\n    if (nDevices == 0)\n        return \"No HID devices found\";\n    RAWINPUTDEVICELIST* FF_AUTO_FREE pRawInputDeviceList = (RAWINPUTDEVICELIST*) malloc(sizeof(RAWINPUTDEVICELIST) * nDevices);\n    if ((nDevices = GetRawInputDeviceList(pRawInputDeviceList, &nDevices, sizeof(RAWINPUTDEVICELIST))) == (UINT) -1)\n        return \"GetRawInputDeviceList(pRawInputDeviceList) failed\";\n\n    for (UINT i = 0; i < nDevices; ++i)\n    {\n        if (pRawInputDeviceList[i].dwType != RIM_TYPEMOUSE) continue;\n\n        HANDLE hDevice = pRawInputDeviceList[i].hDevice;\n\n        RID_DEVICE_INFO rdi;\n        UINT rdiSize = sizeof(rdi);\n        if (GetRawInputDeviceInfoW(hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) == (UINT) -1)\n            continue;\n\n        WCHAR devName[MAX_PATH];\n        UINT nameSize = MAX_PATH;\n        if (GetRawInputDeviceInfoW(hDevice, RIDI_DEVICENAME, devName, &nameSize) == (UINT) -1)\n            continue;\n\n        FFMouseDevice* device = (FFMouseDevice*) ffListAdd(devices);\n        ffStrbufInit(&device->serial);\n        ffStrbufInit(&device->name);\n\n        wchar_t buffer[MAX_PATH];\n\n        HANDLE FF_AUTO_CLOSE_FD hHidFile = CreateFileW(devName, 0 /* must be 0 instead of GENERIC_READ */, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);\n        if (hHidFile != INVALID_HANDLE_VALUE)\n        {\n            if (HidD_GetProductString(hHidFile, buffer, (ULONG) sizeof(buffer)))\n                ffStrbufSetWS(&device->name, buffer);\n\n            if (HidD_GetSerialNumberString(hHidFile, buffer, sizeof(buffer)))\n                ffStrbufSetWS(&device->serial, buffer);\n        }\n\n        if (!device->name.length)\n        {\n            // https://stackoverflow.com/a/64321096/9976392\n            DEVPROPTYPE propertyType;\n            ULONG propertySize = sizeof(buffer);\n\n            if (CM_Get_Device_Interface_PropertyW(devName, &DEVPKEY_Device_InstanceId, &propertyType, (PBYTE) buffer, &propertySize, 0) == CR_SUCCESS)\n            {\n                DEVINST devInst;\n                if (CM_Locate_DevNodeW(&devInst, buffer, CM_LOCATE_DEVNODE_NORMAL) == CR_SUCCESS)\n                {\n                    propertySize = sizeof(buffer);\n                    if (CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_NAME, &propertyType, (PBYTE) buffer, &propertySize, 0) == CR_SUCCESS)\n                        ffStrbufSetWS(&device->name, buffer);\n                }\n            }\n        }\n\n        if (!device->name.length)\n            ffStrbufSetF(&device->name, \"Unknown device %04X-%04X\", (unsigned) rdi.hid.dwVendorId, (unsigned) rdi.hid.dwProductId);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/netio/netio.c",
    "content": "#include \"netio.h\"\n\n#include \"common/time.h\"\n\nstatic FFlist ioCounters1;\nstatic uint64_t time1;\n\nvoid ffPrepareNetIO(FFNetIOOptions* options)\n{\n    if (options->detectTotal) return;\n\n    if (time1 != 0) return; // Already prepared\n\n    ffListInit(&ioCounters1, sizeof(FFNetIOResult));\n    ffNetIOGetIoCounters(&ioCounters1, options);\n    time1 = ffTimeGetNow();\n}\n\nconst char* ffDetectNetIO(FFlist* result, FFNetIOOptions* options)\n{\n    const char* error = NULL;\n\n    if (options->detectTotal)\n    {\n        error = ffNetIOGetIoCounters(result, options);\n        if (error)\n            return error;\n        return NULL;\n    }\n\n    if (time1 == 0)\n    {\n        ffListInit(&ioCounters1, sizeof(FFNetIOResult));\n        error = ffNetIOGetIoCounters(&ioCounters1, options);\n        if (error)\n            return error;\n        time1 = ffTimeGetNow();\n    }\n\n    if (ioCounters1.length == 0)\n        return \"No network interfaces found\";\n\n    uint64_t time2 = ffTimeGetNow();\n    while (time2 - time1 < options->waitTime)\n    {\n        ffTimeSleep((uint32_t) (options->waitTime - (time2 - time1)));\n        time2 = ffTimeGetNow();\n    }\n\n    error = ffNetIOGetIoCounters(result, options);\n    if (error)\n        return error;\n\n    if (result->length != ioCounters1.length)\n        return \"Different number of network interfaces. Network change?\";\n\n    for (uint32_t i = 0; i < result->length; ++i)\n    {\n        FFNetIOResult* icPrev = FF_LIST_GET(FFNetIOResult, ioCounters1, i);\n        FFNetIOResult* icCurr = FF_LIST_GET(FFNetIOResult, *result, i);\n        if (!ffStrbufEqual(&icPrev->name, &icCurr->name))\n            return \"Network interface name changed\";\n\n        static_assert(sizeof(FFNetIOResult) - offsetof(FFNetIOResult, txBytes) == sizeof(uint64_t) * 8, \"Unexpected struct FFNetIOResult layout\");\n        for (size_t off = offsetof(FFNetIOResult, txBytes); off < sizeof(FFNetIOResult); off += sizeof(uint64_t))\n        {\n            uint64_t* prevValue = (uint64_t*) ((uint8_t*) icPrev + off);\n            uint64_t* currValue = (uint64_t*) ((uint8_t*) icCurr + off);\n            uint64_t temp = *currValue;\n            *currValue -= *prevValue;\n            *currValue /= (time2 - time1) / 1000 /* seconds */;\n            *prevValue = temp;\n        }\n    }\n    time1 = time2;\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/netio/netio.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/netio/option.h\"\n\ntypedef struct FFNetIOResult\n{\n    FFstrbuf name;\n    bool defaultRoute;\n    uint64_t txBytes;\n    uint64_t rxBytes;\n    uint64_t txPackets;\n    uint64_t rxPackets;\n    uint64_t rxErrors;\n    uint64_t txErrors;\n    uint64_t rxDrops;\n    uint64_t txDrops;\n} FFNetIOResult;\n\nconst char* ffDetectNetIO(FFlist* result, FFNetIOOptions* options);\nconst char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options);\n"
  },
  {
    "path": "src/detection/netio/netio_apple.c",
    "content": "#include \"netio.h\"\n\n#include \"common/netif.h\"\n#include \"common/mallocHelper.h\"\n\n#include <net/if.h>\n#include <net/if_mib.h>\n#include <sys/sysctl.h>\n\nconst char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options)\n{\n    int mib[] = {CTL_NET, PF_LINK, NETLINK_GENERIC,\n        options->defaultRouteOnly ? IFMIB_IFDATA : IFMIB_IFALLDATA,\n        options->defaultRouteOnly ? (int) ffNetifGetDefaultRouteV4()->ifIndex : 0,\n        IFDATA_GENERAL};\n\n    size_t bufSize = 0;\n    if (sysctl(mib, ARRAY_SIZE(mib), NULL, &bufSize, 0, 0) < 0)\n        return \"sysctl(mib, ARRAY_SIZE(mib), NULL, &bufSize, 0, 0) failed\";\n\n    assert(bufSize % sizeof(struct ifmibdata) == 0);\n\n    FF_AUTO_FREE struct ifmibdata* buf = (struct ifmibdata*) malloc(bufSize);\n    if (sysctl(mib, ARRAY_SIZE(mib), buf, &bufSize, 0, 0) < 0)\n        return \"sysctl(mib, ARRAY_SIZE(mib), buf, &bufSize, 0, 0) failed\";\n\n    size_t ifCount = bufSize / sizeof(struct ifmibdata);\n\n    const char* defaultRouteIfName = ffNetifGetDefaultRouteV4()->ifName;\n\n    for (size_t i = 0; i < ifCount; i++)\n    {\n        struct ifmibdata* mibdata = &buf[i];\n        if (!(mibdata->ifmd_flags & IFF_RUNNING) || (mibdata->ifmd_flags & IFF_NOARP))\n            continue;\n\n        if (options->namePrefix.length && strncmp(mibdata->ifmd_name, options->namePrefix.chars, options->namePrefix.length) != 0)\n            continue;\n\n        FFNetIOResult* counters = (FFNetIOResult*) ffListAdd(result);\n        *counters = (FFNetIOResult) {\n            .name = ffStrbufCreateS(mibdata->ifmd_name),\n            .txBytes = mibdata->ifmd_data.ifi_obytes,\n            .rxBytes = mibdata->ifmd_data.ifi_ibytes,\n            .txPackets = mibdata->ifmd_data.ifi_opackets,\n            .rxPackets = mibdata->ifmd_data.ifi_ipackets,\n            .txErrors = mibdata->ifmd_data.ifi_oerrors,\n            .rxErrors = mibdata->ifmd_data.ifi_ierrors,\n            .txDrops = mibdata->ifmd_snd_drops,\n            .rxDrops = mibdata->ifmd_data.ifi_iqdrops,\n            .defaultRoute = strncmp(mibdata->ifmd_name, defaultRouteIfName, IFNAMSIZ) == 0,\n        };\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/netio/netio_bsd.c",
    "content": "#include \"netio.h\"\n\n#include \"common/netif.h\"\n#include \"common/mallocHelper.h\"\n\n#include <net/if.h>\n#include <net/if_dl.h>\n#include <net/if_types.h>\n#include <net/route.h>\n#include <sys/sysctl.h>\n#include <sys/socket.h>\n\nconst char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options)\n{\n    uint32_t defaultRouteIfIndex = ffNetifGetDefaultRouteV4()->ifIndex;\n\n    size_t bufSize = 0;\n    if (sysctl((int[]) { CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, (options->defaultRouteOnly ? (int) defaultRouteIfIndex : 0) }, 6, NULL, &bufSize, 0, 0) < 0)\n        return \"sysctl({ CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, ifIndex }, 6, NULL, &bufSize, 0, 0) failed\";\n\n    FF_AUTO_FREE struct if_msghdr* buf = (struct if_msghdr*) malloc(bufSize);\n    if (sysctl((int[]) { CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, (options->defaultRouteOnly ? (int) defaultRouteIfIndex : 0) }, 6, buf, &bufSize, 0, 0) < 0)\n        return \"sysctl({ CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, ifIndex }, 6, buf, &bufSize, 0, 0) failed\";\n\n    for (struct if_msghdr* ifm = buf;\n        ifm < (struct if_msghdr*) ((uint8_t*) buf + bufSize);\n        ifm = (struct if_msghdr*) ((uint8_t*) ifm + ifm->ifm_msglen))\n    {\n        if (ifm->ifm_type != RTM_IFINFO || !(ifm->ifm_flags & IFF_RUNNING) || (ifm->ifm_flags & IFF_NOARP)) continue;\n\n        struct sockaddr_dl* sdl = (struct sockaddr_dl*) (ifm + 1);\n        assert(sdl->sdl_family == AF_LINK);\n\n        sdl->sdl_data[sdl->sdl_nlen] = 0;\n\n        if (options->namePrefix.length && strncmp(sdl->sdl_data, options->namePrefix.chars, options->namePrefix.length) != 0)\n            continue;\n\n        FFNetIOResult* counters = (FFNetIOResult*) ffListAdd(result);\n        *counters = (FFNetIOResult) {\n            .name = ffStrbufCreateNS(sdl->sdl_nlen, sdl->sdl_data),\n            .txBytes = ifm->ifm_data.ifi_obytes,\n            .rxBytes = ifm->ifm_data.ifi_ibytes,\n            .txPackets = ifm->ifm_data.ifi_opackets,\n            .rxPackets = ifm->ifm_data.ifi_ipackets,\n            .txErrors = ifm->ifm_data.ifi_oerrors,\n            .rxErrors = ifm->ifm_data.ifi_ierrors,\n            #ifdef FF_HAVE_IFI_OQDROPS\n            .txDrops = ifm->ifm_data.ifi_oqdrops,\n            #else\n            .txDrops = 0, // unsupported\n            #endif\n            .rxDrops = ifm->ifm_data.ifi_iqdrops,\n            .defaultRoute = sdl->sdl_index == defaultRouteIfIndex,\n        };\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/netio/netio_haiku.cpp",
    "content": "extern \"C\" {\n#include \"netio.h\"\n#include \"common/netif.h\"\n}\n\n#include <NetworkInterface.h>\n#include <NetworkRoster.h>\n\nconst char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options)\n{\n    BNetworkRoster& roster = BNetworkRoster::Default();\n\n    BNetworkInterface interface;\n    uint32 cookie = 0;\n\n    uint32_t defaultRouteIfIndex = ffNetifGetDefaultRouteV4()->ifIndex;\n\n    while (roster.GetNextInterface(&cookie, interface) == B_OK)\n    {\n        if (!interface.Exists())\n            continue;\n\n        bool defaultRoute = interface.Index() == defaultRouteIfIndex;\n        if (options->defaultRouteOnly && !defaultRoute)\n            continue;\n\n        if (options->namePrefix.length && strncmp(interface.Name(), options->namePrefix.chars, options->namePrefix.length) != 0)\n            continue;\n\n        ifreq_stats stats = {};\n        if (interface.GetStats(stats) != B_OK) continue;\n\n        FFNetIOResult* counters = (FFNetIOResult*) ffListAdd(result);\n        *counters = (FFNetIOResult) {\n            .name = ffStrbufCreateS(interface.Name()),\n            .defaultRoute = defaultRoute,\n            .txBytes = stats.send.bytes,\n            .rxBytes = stats.receive.bytes,\n            .txPackets = stats.send.packets,\n            .rxPackets = stats.receive.packets,\n            .rxErrors = stats.receive.errors,\n            .txErrors = stats.send.errors,\n            .rxDrops = stats.receive.dropped,\n            .txDrops = stats.send.dropped\n        };\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/netio/netio_linux.c",
    "content": "#include \"netio.h\"\n\n#include \"common/io.h\"\n#include \"common/netif.h\"\n#include \"common/stringUtils.h\"\n\n#include <fcntl.h>\n#include <net/if.h>\n\nstatic void getData(FFstrbuf* buffer, const char* ifName, bool isDefaultRoute, int basefd, FFlist* result)\n{\n    FF_AUTO_CLOSE_FD int dfd = openat(basefd, ifName, O_RDONLY | O_DIRECTORY);\n    if (dfd < 0)\n        return;\n\n    char operstate;\n    if(!ffReadFileDataRelative(dfd, \"operstate\", 1, &operstate) || operstate != 'u' /* up or unknown */)\n        return;\n\n    FFNetIOResult* counters = (FFNetIOResult*) ffListAdd(result);\n    ffStrbufInitS(&counters->name, ifName);\n    counters->defaultRoute = isDefaultRoute;\n\n    if (ffReadFileBufferRelative(dfd, \"statistics/rx_bytes\", buffer))\n        counters->rxBytes = ffStrbufToUInt(buffer, 0);\n\n    if (ffReadFileBufferRelative(dfd, \"statistics/tx_bytes\", buffer))\n        counters->txBytes = ffStrbufToUInt(buffer, 0);\n\n    if (ffReadFileBufferRelative(dfd, \"statistics/rx_packets\", buffer))\n        counters->rxPackets = ffStrbufToUInt(buffer, 0);\n\n    if (ffReadFileBufferRelative(dfd, \"statistics/tx_packets\", buffer))\n        counters->txPackets = ffStrbufToUInt(buffer, 0);\n\n    if (ffReadFileBufferRelative(dfd, \"statistics/rx_errors\", buffer))\n        counters->rxErrors = ffStrbufToUInt(buffer, 0);\n\n    if (ffReadFileBufferRelative(dfd, \"statistics/tx_errors\", buffer))\n        counters->txErrors = ffStrbufToUInt(buffer, 0);\n\n    if (ffReadFileBufferRelative(dfd, \"statistics/rx_dropped\", buffer))\n        counters->rxDrops = ffStrbufToUInt(buffer, 0);\n\n    if (ffReadFileBufferRelative(dfd, \"statistics/tx_dropped\", buffer))\n        counters->txDrops = ffStrbufToUInt(buffer, 0);\n}\n\nconst char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options)\n{\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(\"/sys/class/net\");\n    if (!dirp) return \"opendir(\\\"/sys/class/net\\\") == NULL\";\n\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n\n    const char* defaultRouteIfName = ffNetifGetDefaultRouteV4()->ifName;\n\n    if (options->defaultRouteOnly)\n    {\n        if (options->namePrefix.length && strncmp(defaultRouteIfName, options->namePrefix.chars, options->namePrefix.length) != 0)\n            return NULL;\n\n       getData(&buffer, defaultRouteIfName, true, dirfd(dirp), result);\n    }\n    else\n    {\n        struct dirent* entry;\n        while((entry = readdir(dirp)) != NULL)\n        {\n            const char* ifName = entry->d_name;\n            if(ifName[0] == '.')\n                continue;\n\n            if (options->namePrefix.length && strncmp(ifName, options->namePrefix.chars, options->namePrefix.length) != 0)\n                continue;\n\n            getData(&buffer, ifName, ffStrEquals(ifName, defaultRouteIfName), dirfd(dirp), result);\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/netio/netio_nosupport.c",
    "content": "#include \"netio.h\"\n\nconst char* ffNetIOGetIoCounters(FF_MAYBE_UNUSED FFlist* result, FF_MAYBE_UNUSED FFNetIOOptions* options)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/netio/netio_sunos.c",
    "content": "#include \"netio.h\"\n#include \"common/netif.h\"\n#include \"common/stringUtils.h\"\n\n#include <kstat.h>\n\nstatic inline void kstatFreeWrap(kstat_ctl_t** pkc)\n{\n    assert(pkc);\n    if (*pkc)\n        kstat_close(*pkc);\n}\n\nconst char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options)\n{\n    __attribute__((__cleanup__(kstatFreeWrap))) kstat_ctl_t* kc = kstat_open();\n    if (!kc)\n        return \"kstat_open() failed\";\n\n    const char* defaultRouteIfName = ffNetifGetDefaultRouteV4()->ifName;\n\n    for (kstat_t* ks = kc->kc_chain; ks; ks = ks->ks_next)\n    {\n        if (!ffStrEquals(ks->ks_class, \"net\") || !ffStrEquals(ks->ks_module, \"link\")) continue;\n\n        if (options->namePrefix.length && strncmp(ks->ks_name, options->namePrefix.chars, options->namePrefix.length) != 0)\n            continue;\n\n        bool isDefaultRoute = ffStrEquals(ks->ks_name, defaultRouteIfName);\n        if (options->defaultRouteOnly && !isDefaultRoute)\n            continue;\n\n        if (kstat_read(kc, ks, NULL) < 0)\n            continue;\n\n        FFNetIOResult* counters = (FFNetIOResult*) ffListAdd(result);\n\n        kstat_named_t* wbytes = (kstat_named_t*) kstat_data_lookup(ks, \"obytes64\");\n        kstat_named_t* rbytes = (kstat_named_t*) kstat_data_lookup(ks, \"rbytes64\");\n        kstat_named_t* wpkts = (kstat_named_t*) kstat_data_lookup(ks, \"opackets64\");\n        kstat_named_t* rpkts = (kstat_named_t*) kstat_data_lookup(ks, \"ipackets64\");\n        kstat_named_t* werrs = (kstat_named_t*) kstat_data_lookup(ks, \"oerrors\");\n        kstat_named_t* rerrs = (kstat_named_t*) kstat_data_lookup(ks, \"ierrors\");\n\n        *counters = (FFNetIOResult) {\n            .name = ffStrbufCreateS(ks->ks_name),\n            .txBytes = wbytes->value.ui64,\n            .rxBytes = rbytes->value.ui64,\n            .txPackets = wpkts->value.ui64,\n            .rxPackets = rpkts->value.ui64,\n            .txErrors = werrs->value.ui64,\n            .rxErrors = rerrs->value.ui64,\n            .txDrops = 0, // unsupported\n            .rxDrops = 0,\n            .defaultRoute = isDefaultRoute,\n        };\n\n        if (options->defaultRouteOnly)\n            break;\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/netio/netio_windows.c",
    "content": "#include \"netio.h\"\n\n#include \"common/netif.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/windows/unicode.h\"\n\n#include <ws2tcpip.h>\n#include <iphlpapi.h>\n\nconst char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options)\n{\n    IP_ADAPTER_ADDRESSES* FF_AUTO_FREE adapter_addresses = NULL;\n\n    // Multiple attempts in case interfaces change while\n    // we are in the middle of querying them.\n    DWORD adapter_addresses_buffer_size = 0;\n    for (int attempts = 0;; ++attempts)\n    {\n        if (adapter_addresses_buffer_size)\n        {\n            adapter_addresses = (IP_ADAPTER_ADDRESSES*)realloc(adapter_addresses, adapter_addresses_buffer_size);\n            assert(adapter_addresses);\n        }\n\n        DWORD error = GetAdaptersAddresses(\n            AF_UNSPEC,\n            GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER,\n            NULL,\n            adapter_addresses,\n            &adapter_addresses_buffer_size);\n\n        if (error == ERROR_SUCCESS)\n            break;\n        else if (ERROR_BUFFER_OVERFLOW == error && attempts < 4)\n            continue;\n        else\n            return \"GetAdaptersAddresses() failed\";\n    }\n\n    uint32_t defaultRouteIfIndex = ffNetifGetDefaultRouteV4()->ifIndex;\n\n    // Iterate through all of the adapters\n    for (IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; adapter; adapter = adapter->Next)\n    {\n        bool isDefaultRoute = adapter->IfIndex == defaultRouteIfIndex;\n        if (options->defaultRouteOnly && !isDefaultRoute)\n            continue;\n\n        FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateWS(adapter->FriendlyName);\n        if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix))\n            continue;\n\n        MIB_IF_ROW2 ifRow = { .InterfaceIndex = adapter->IfIndex };\n        if (GetIfEntry2(&ifRow) == NO_ERROR)\n        {\n            FFNetIOResult* counters = (FFNetIOResult*) ffListAdd(result);\n            *counters = (FFNetIOResult) {\n                .name = ffStrbufCreateMove(&name),\n                .txBytes = ifRow.OutOctets,\n                .rxBytes = ifRow.InOctets,\n                .txPackets = (ifRow.OutUcastPkts + ifRow.OutNUcastPkts),\n                .rxPackets = (ifRow.InUcastPkts + ifRow.InNUcastPkts),\n                .rxErrors = ifRow.InErrors,\n                .txErrors = ifRow.OutErrors,\n                .rxDrops = ifRow.InDiscards,\n                .txDrops = ifRow.OutDiscards,\n                .defaultRoute = isDefaultRoute,\n            };\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/opencl/opencl.c",
    "content": "#include \"detection/opencl/opencl.h\"\n#include \"detection/gpu/gpu.h\"\n\n#if !defined(FF_HAVE_OPENCL) && defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_15)\n    #define FF_HAVE_OPENCL 1\n#endif\n\n#ifdef FF_HAVE_OPENCL\n\n#include \"common/library.h\"\n#include \"common/parsing.h\"\n#include \"common/stringUtils.h\"\n#include <string.h>\n\n#define CL_TARGET_OPENCL_VERSION 110\n#ifndef __APPLE__\n    #include <CL/cl.h>\n    #include <CL/cl_ext.h>\n#else\n    #include <OpenCL/cl.h>\n    #include <OpenCL/cl_ext.h>\n#endif\n\ntypedef struct OpenCLData\n{\n    FF_LIBRARY_SYMBOL(clGetPlatformIDs)\n    FF_LIBRARY_SYMBOL(clGetPlatformInfo)\n    FF_LIBRARY_SYMBOL(clGetDeviceInfo)\n    FF_LIBRARY_SYMBOL(clGetDeviceIDs)\n} OpenCLData;\n\nstatic const char* openCLHandleData(OpenCLData* data, FFOpenCLResult* result)\n{\n    cl_platform_id platforms[32];\n    cl_uint numPlatforms = 0;\n    cl_int ret = data->ffclGetPlatformIDs(ARRAY_SIZE(platforms), platforms, &numPlatforms);\n    if (ret != CL_SUCCESS)\n    {\n        switch (ret)\n        {\n            #ifdef CL_PLATFORM_NOT_FOUND_KHR // not available on macOS\n            case CL_PLATFORM_NOT_FOUND_KHR:\n                return \"clGetPlatformIDs() failed: CL_PLATFORM_NOT_FOUND_KHR\";\n            #endif\n            case CL_INVALID_VALUE:\n                return \"clGetPlatformIDs() failed: CL_INVALID_VALUE\";\n            case CL_OUT_OF_HOST_MEMORY:\n                return \"clGetPlatformIDs() failed: CL_OUT_OF_HOST_MEMORY\";\n            default:\n                return \"clGetPlatformIDs() failed: unknown error\";\n        }\n    }\n\n    if (numPlatforms == 0)\n        return \"clGetPlatformIDs returned 0 platforms\";\n\n    char buffer[1024];\n    for (cl_uint iplat = 0; iplat < numPlatforms; ++iplat)\n    {\n        if (data->ffclGetPlatformInfo(platforms[iplat], CL_PLATFORM_VERSION, sizeof(buffer), buffer, NULL) != CL_SUCCESS)\n            return \"clGetPlatformInfo() failed\";\n\n        // Use the newest supported OpenCL version\n        if (ffStrbufCompS(&result->version, buffer) < 0)\n        {\n            const char* versionPretty = buffer;\n            if(ffStrStartsWithIgnCase(buffer, \"OpenCL \"))\n                versionPretty = buffer + strlen(\"OpenCL \");\n            ffStrbufSetS(&result->version, versionPretty);\n            ffStrbufTrim(&result->version, ' ');\n\n            if (data->ffclGetPlatformInfo(platforms[iplat], CL_PLATFORM_NAME, sizeof(buffer), buffer, NULL) == CL_SUCCESS)\n                ffStrbufSetS(&result->name, buffer);\n\n            if (data->ffclGetPlatformInfo(platforms[iplat], CL_PLATFORM_VENDOR, sizeof(buffer), buffer, NULL) == CL_SUCCESS)\n                ffStrbufSetS(&result->vendor, buffer);\n        }\n\n        cl_device_id deviceIDs[32];\n        cl_uint numDevices = (cl_uint) ARRAY_SIZE(deviceIDs);\n        if (data->ffclGetDeviceIDs(platforms[iplat], CL_DEVICE_TYPE_GPU, numDevices, deviceIDs, &numDevices) != CL_SUCCESS)\n            continue;\n\n        for (cl_uint idev = 0; idev < numDevices; ++idev)\n        {\n            cl_device_id deviceID = deviceIDs[idev];\n            if (data->ffclGetDeviceInfo(deviceID, CL_DEVICE_NAME, sizeof(buffer), buffer, NULL) != CL_SUCCESS)\n                continue;\n\n            FFGPUResult* gpu = ffListAdd(&result->gpus);\n            ffStrbufInitS(&gpu->name, buffer);\n            ffStrbufInit(&gpu->vendor);\n            ffStrbufInit(&gpu->driver);\n            ffStrbufInit(&gpu->platformApi);\n            ffStrbufInit(&gpu->memoryType);\n            gpu->index = FF_GPU_INDEX_UNSET;\n            gpu->temperature = FF_GPU_TEMP_UNSET;\n            gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;\n            gpu->type = FF_GPU_TYPE_UNKNOWN;\n            gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;\n            gpu->deviceId = (size_t) deviceID;\n            gpu->frequency = FF_GPU_FREQUENCY_UNSET;\n            gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;\n\n            if (data->ffclGetDeviceInfo(deviceID, CL_DEVICE_VERSION, sizeof(buffer), buffer, NULL) == CL_SUCCESS)\n            {\n                ffStrbufSetS(&gpu->platformApi, buffer);\n                ffStrbufTrimRight(&gpu->platformApi, ' ');\n            }\n            else\n                ffStrbufSetStatic(&gpu->platformApi, \"OpenCL\");\n\n            {\n                cl_uint vendorId;\n                if (data->ffclGetDeviceInfo(deviceID, CL_DEVICE_VENDOR_ID, sizeof(vendorId), &vendorId, NULL) == CL_SUCCESS)\n                    ffStrbufSetStatic(&gpu->vendor, ffGPUGetVendorString(vendorId));\n                if (gpu->vendor.length == 0 && data->ffclGetDeviceInfo(deviceID, CL_DEVICE_VENDOR, sizeof(buffer), buffer, NULL) == CL_SUCCESS)\n                    ffStrbufSetS(&gpu->vendor, buffer);\n            }\n\n            if (data->ffclGetDeviceInfo(deviceID, CL_DRIVER_VERSION, sizeof(buffer), buffer, NULL) == CL_SUCCESS)\n            {\n                const char* versionPretty = strchr(buffer, ' ');\n                if (versionPretty && *versionPretty)\n                    ffStrbufSetS(&gpu->driver, versionPretty + 1);\n                else\n                    ffStrbufSetS(&gpu->driver, buffer);\n            }\n\n            {\n                cl_uint value;\n                if (data->ffclGetDeviceInfo(deviceID, CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(value), &value, NULL) == CL_SUCCESS)\n                    gpu->coreCount = (int32_t) value;\n            }\n\n            {\n                cl_uint value;\n                if (data->ffclGetDeviceInfo(deviceID, CL_DEVICE_MAX_CLOCK_FREQUENCY, sizeof(value), &value, NULL) == CL_SUCCESS)\n                    gpu->frequency = value;\n            }\n\n            {\n                cl_bool value;\n                if (data->ffclGetDeviceInfo(deviceID, CL_DEVICE_HOST_UNIFIED_MEMORY, sizeof(value), &value, NULL) == CL_SUCCESS)\n                {\n                    gpu->type = value ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;\n\n                    cl_ulong memSize;\n                    if (data->ffclGetDeviceInfo(deviceID, CL_DEVICE_GLOBAL_MEM_SIZE, sizeof(memSize), &memSize, NULL) == CL_SUCCESS)\n                    {\n                        if (gpu->type == FF_GPU_TYPE_INTEGRATED)\n                            gpu->shared.total = memSize;\n                        else\n                            gpu->dedicated.total = memSize;\n                    }\n                }\n            }\n        }\n    }\n\n    return NULL;\n}\n\nstatic const char* detectOpenCL(FFOpenCLResult* result)\n{\n    OpenCLData data;\n\n    #ifndef __APPLE__\n\n    FF_LIBRARY_LOAD_MESSAGE(opencl,\n        #ifdef _WIN32\n            \"OpenCL\" FF_LIBRARY_EXTENSION, -1,\n        #endif\n        \"libOpenCL\" FF_LIBRARY_EXTENSION, 1\n    );\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(opencl, data, clGetPlatformIDs);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(opencl, data, clGetPlatformInfo);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(opencl, data, clGetDeviceIDs);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(opencl, data, clGetDeviceInfo);\n\n    return openCLHandleData(&data, result);\n\n    #else\n\n    data.ffclGetPlatformIDs = clGetPlatformIDs;\n    data.ffclGetPlatformInfo = clGetPlatformInfo;\n    data.ffclGetDeviceIDs = clGetDeviceIDs;\n    data.ffclGetDeviceInfo = clGetDeviceInfo;\n\n    return openCLHandleData(&data, result);\n\n    #endif\n}\n\n#endif // defined(FF_HAVE_OPENCL)\n\nFFOpenCLResult* ffDetectOpenCL(void)\n{\n    static FFOpenCLResult result;\n\n    if (result.gpus.elementSize == 0)\n    {\n        ffStrbufInit(&result.version);\n        ffStrbufInit(&result.name);\n        ffStrbufInit(&result.vendor);\n        ffListInit(&result.gpus, sizeof(FFGPUResult));\n\n        #ifdef FF_HAVE_OPENCL\n            result.error = detectOpenCL(&result);\n        #else\n            result.error = \"fastfetch was compiled without OpenCL support\";\n        #endif\n    }\n\n    return &result;\n}\n"
  },
  {
    "path": "src/detection/opencl/opencl.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/opencl/option.h\"\n\ntypedef struct FFOpenCLResult\n{\n    FFstrbuf version;\n    FFstrbuf name;\n    FFstrbuf vendor;\n    FFlist gpus; //List of FFGPUResult, see detection/gpu/gpu.h\n    const char* error;\n} FFOpenCLResult;\n\nFFOpenCLResult* ffDetectOpenCL();\n"
  },
  {
    "path": "src/detection/opengl/opengl.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/opengl/option.h\"\n\ntypedef struct FFOpenGLResult\n{\n    FFstrbuf version;\n    FFstrbuf renderer;\n    FFstrbuf vendor;\n    FFstrbuf slv;\n    FFstrbuf library;\n} FFOpenGLResult;\n\n#define FF_OPENGL_BUFFER_WIDTH 1\n#define FF_OPENGL_BUFFER_HEIGHT 1\n\nconst char* ffDetectOpenGL(FFOpenGLOptions* options, FFOpenGLResult* result);\n"
  },
  {
    "path": "src/detection/opengl/opengl_apple.c",
    "content": "\n#include \"fastfetch.h\"\n#include \"opengl.h\"\n\n#define GL_SILENCE_DEPRECATION\n#include <OpenGL/gl.h>\n#include <OpenGL/OpenGL.h> // This brings in CGL, not GL\n\nvoid ffOpenGLHandleResult(FFOpenGLResult* result, __typeof__(&glGetString) ffglGetString);\n\nstatic const char* cglHandleContext(FFOpenGLResult* result, CGLContextObj context)\n{\n    if(CGLSetCurrentContext(context) != kCGLNoError)\n        return \"CGLSetCurrentContext() failed\";\n\n    ffOpenGLHandleResult(result, &glGetString);\n\n    GLint major, minor;\n    CGLGetVersion(&major, &minor);\n    ffStrbufSetF(&result->library, \"CGL %d.%d\", major, minor);\n\n    return NULL;\n}\n\nstatic const char* cglHandlePixelFormat(FFOpenGLResult* result, CGLPixelFormatObj pixelFormat)\n{\n    CGLContextObj context;\n\n    if(CGLCreateContext(pixelFormat, NULL, &context) != kCGLNoError)\n        return \"CGLCreateContext() failed\";\n\n    const char* error = cglHandleContext(result, context);\n    CGLDestroyContext(context);\n    return error;\n}\n\nconst char* cglDetectOpenGL(FFOpenGLResult* result)\n{\n    CGLPixelFormatObj pixelFormat;\n    CGLPixelFormatAttribute attrs[] = {\n        kCGLPFAOpenGLProfile, (CGLPixelFormatAttribute) kCGLOGLPVersion_3_2_Core,\n        kCGLPFAAccelerated,\n        0\n    };\n\n    GLint num;\n    if (CGLChoosePixelFormat(attrs, &pixelFormat, &num) != kCGLNoError)\n        return \"CGLChoosePixelFormat() failed\";\n\n    const char* error = cglHandlePixelFormat(result, pixelFormat);\n    CGLDestroyPixelFormat(pixelFormat);\n    return error;\n}\n\nconst char* ffDetectOpenGL(FFOpenGLOptions* options, FFOpenGLResult* result)\n{\n    if (options->library == FF_OPENGL_LIBRARY_AUTO)\n        return cglDetectOpenGL(result);\n    else if (options->library == FF_OPENGL_LIBRARY_EGL)\n    {\n        #if __has_include(<EGL/egl.h>)\n        const char* ffOpenGLDetectByEGL(FFOpenGLResult* result);\n        return ffOpenGLDetectByEGL(result);\n        #else\n        return \"fastfetch was compiled without egl support\";\n        #endif\n    }\n    else\n        return \"Unsupported OpenGL library\";\n}\n"
  },
  {
    "path": "src/detection/opengl/opengl_haiku.cpp",
    "content": "#include <OpenGLKit.h>\n\nextern \"C\" {\n#include \"opengl.h\"\n#include \"common/io.h\"\n#if FF_HAVE_EGL\nconst char* ffOpenGLDetectByEGL(FFOpenGLResult* result);\n#endif\nvoid ffOpenGLHandleResult(FFOpenGLResult* result, __typeof__(&glGetString) ffglGetString);\n}\n\nstatic const char* oglDetectOpenGL(FFOpenGLResult* result)\n{\n    BApplication app(\"application/x-vnd.fastfetch-cli-fastfetch\");\n    FF_SUPPRESS_IO();\n\n    BGLView glView(BRect(), \"ff_ogl_view\", B_FOLLOW_NONE, B_WILL_DRAW, BGL_RGB);\n    auto ffglGetString = (decltype(&glGetString)) glView.GetGLProcAddress(\"glGetString\");\n    if (!ffglGetString) return \"glView.GetGLProcAddress() failed\";\n    ffOpenGLHandleResult(result, ffglGetString);\n    ffStrbufSetStatic(&result->library, \"OpenGLKit\");\n    return NULL;\n}\n\nconst char* ffDetectOpenGL(FFOpenGLOptions* options, FFOpenGLResult* result)\n{\n    if (options->library == FF_OPENGL_LIBRARY_AUTO)\n        return oglDetectOpenGL(result);\n    else if (options->library == FF_OPENGL_LIBRARY_EGL)\n    {\n        #if FF_HAVE_EGL\n        return ffOpenGLDetectByEGL(result);\n        #else\n        return \"fastfetch was compiled without egl support\";\n        #endif\n    }\n    else\n        return \"Unsupported OpenGL library\";\n}\n"
  },
  {
    "path": "src/detection/opengl/opengl_linux.c",
    "content": "#include \"fastfetch.h\"\n#include \"opengl.h\"\n#include \"common/io.h\"\n\n#include <string.h>\n\n#if __ANDROID__ && !defined(FF_HAVE_EGL)\n    // On Android, installing OpenGL headers is enough (mesa-dev)\n    #if __has_include(<EGL/egl.h>)\n        #define FF_HAVE_EGL 1\n    #endif\n#endif\n\n#if defined(FF_HAVE_EGL) || defined(FF_HAVE_GLX)\n#define FF_HAVE_GL 1\n\n#include \"common/library.h\"\n\n#include <GL/gl.h>\n\nvoid ffOpenGLHandleResult(FFOpenGLResult* result, __typeof__(&glGetString) ffglGetString);\n\n#endif // FF_HAVE_GL\n\n#ifdef FF_HAVE_GLX\n#include <GL/glx.h>\n\ntypedef struct GLXData\n{\n    FF_LIBRARY_SYMBOL(glGetString)\n    FF_LIBRARY_SYMBOL(glXGetProcAddress)\n    FF_LIBRARY_SYMBOL(glXQueryVersion)\n    FF_LIBRARY_SYMBOL(XOpenDisplay)\n    FF_LIBRARY_SYMBOL(glXChooseVisual)\n    FF_LIBRARY_SYMBOL(XCreatePixmap)\n    FF_LIBRARY_SYMBOL(glXCreateGLXPixmap)\n    FF_LIBRARY_SYMBOL(glXCreateContext)\n    FF_LIBRARY_SYMBOL(glXMakeCurrent)\n    FF_LIBRARY_SYMBOL(glXDestroyContext)\n    FF_LIBRARY_SYMBOL(glXDestroyGLXPixmap)\n    FF_LIBRARY_SYMBOL(XFreePixmap)\n    FF_LIBRARY_SYMBOL(XCloseDisplay)\n    FF_LIBRARY_SYMBOL(XFree)\n\n    Display* display;\n    XVisualInfo* visualInfo;\n    Pixmap pixmap;\n    GLXPixmap glxPixmap;\n    GLXContext context;\n} GLXData;\n\nstatic const char* glxHandleContext(FFOpenGLResult* result, GLXData* data)\n{\n    if(data->ffglXMakeCurrent(data->display, data->glxPixmap, data->context) != True)\n        return \"glXMakeCurrent returned False\";\n    ffOpenGLHandleResult(result, data->ffglGetString);\n\n    int major, minor;\n    if (data->ffglXQueryVersion(data->display, &major, &minor))\n        ffStrbufSetF(&result->library, \"GLX %d.%d\", major, minor);\n    else\n        ffStrbufSetStatic(&result->library, \"GLX\");\n\n    return NULL;\n}\n\nstatic const char* glxHandleGLXPixmap(FFOpenGLResult* result, GLXData* data)\n{\n    data->context = data->ffglXCreateContext(data->display, data->visualInfo, NULL, True);\n    if(data->context == NULL)\n        return \"glXCreateContext returned NULL\";\n\n    const char* error = glxHandleContext(result, data);\n    data->ffglXDestroyContext(data->display, data->context);\n    return error;\n}\n\nstatic const char* glxHandlePixmap(FFOpenGLResult* result, GLXData* data)\n{\n    data->glxPixmap = data->ffglXCreateGLXPixmap(data->display, data->visualInfo, data->pixmap);\n    if(data->glxPixmap == None)\n        return \"glXCreateGLXPixmap returned None\";\n\n    const char* error = glxHandleGLXPixmap(result, data);\n    data->ffglXDestroyGLXPixmap(data->display, data->glxPixmap);\n    return error;\n}\n\nstatic const char* glxHandleVisualInfo(FFOpenGLResult* result, GLXData* data)\n{\n    data->pixmap = data->ffXCreatePixmap(data->display, DefaultRootWindow(data->display), FF_OPENGL_BUFFER_WIDTH, FF_OPENGL_BUFFER_HEIGHT, (unsigned int) data->visualInfo->depth);\n    if(data->pixmap == None)\n        return \"XCreatePixmap returned None\";\n\n    const char* error = glxHandlePixmap(result, data);\n    data->ffXFreePixmap(data->display, data->pixmap);\n    return error;\n}\n\nstatic const char* glxHandleDisplay(FFOpenGLResult* result, GLXData* data)\n{\n    data->visualInfo = data->ffglXChooseVisual(data->display, DefaultScreen(data->display), (int[]){None});\n    if(data->visualInfo == NULL)\n        return \"glXChooseVisual returned NULL\";\n\n    const char* error = glxHandleVisualInfo(result, data);\n    data->ffXFree(data->visualInfo);\n    return error;\n}\n\nstatic const char* glxHandleData(FFOpenGLResult* result, GLXData* data)\n{\n    data->ffglGetString = (__typeof__(data->ffglGetString)) data->ffglXGetProcAddress((const GLubyte*) \"glGetString\");\n    if(data->ffglGetString == NULL)\n        return \"glXGetProcAddress(glGetString) returned NULL\";\n\n    data->display = data->ffXOpenDisplay(NULL);\n    if(data->display == NULL)\n        return \"XOpenDisplay returned NULL\";\n\n    const char* error = glxHandleDisplay(result, data);\n    data->ffXCloseDisplay(data->display);\n    return error;\n}\n\nstatic const char* detectByGlx(FFOpenGLResult* result)\n{\n    GLXData data;\n\n    FF_LIBRARY_LOAD_MESSAGE(glx,\n        #if !__OpenBSD__ && !__NetBSD__\n            \"libGLX\"\n        #else\n            \"libGL\"\n        #endif\n            FF_LIBRARY_EXTENSION, 1);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXGetProcAddress);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXQueryVersion);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, XOpenDisplay);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXChooseVisual);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, XCreatePixmap);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXCreateGLXPixmap);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXCreateContext);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXMakeCurrent);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXDestroyContext);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXDestroyGLXPixmap);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, XFreePixmap);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, XCloseDisplay);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, XFree);\n\n    FF_SUPPRESS_IO();\n\n    return glxHandleData(result, &data);\n}\n\n#endif //FF_HAVE_GLX\n\nconst char* ffDetectOpenGL(FFOpenGLOptions* options, FFOpenGLResult* result)\n{\n    #if FF_HAVE_GL\n\n    if(options->library == FF_OPENGL_LIBRARY_GLX)\n    {\n        #ifdef FF_HAVE_GLX\n            return detectByGlx(result);\n        #else\n            return \"fastfetch was compiled without glx support\";\n        #endif\n    }\n\n    if(options->library == FF_OPENGL_LIBRARY_EGL)\n    {\n        #ifdef FF_HAVE_EGL\n            const char* ffOpenGLDetectByEGL(FFOpenGLResult* result);\n            return ffOpenGLDetectByEGL(result);\n        #else\n            return \"fastfetch was compiled without egl support\";\n        #endif\n    }\n\n    const char* error = \"\"; // not NULL dummy value\n\n    #ifdef FF_HAVE_EGL\n        const char* ffOpenGLDetectByEGL(FFOpenGLResult* result);\n        error = ffOpenGLDetectByEGL(result);\n    #endif\n\n    #ifdef FF_HAVE_GLX\n        if(error != NULL)\n            error = detectByGlx(result);\n    #endif\n\n    return error;\n\n    #else\n\n        FF_UNUSED(options, result);\n        return \"Fastfetch was built without gl support.\";\n\n    #endif //FF_HAVE_GL\n}\n"
  },
  {
    "path": "src/detection/opengl/opengl_shared.c",
    "content": "#include \"opengl.h\"\n#include \"common/debug.h\"\n#include \"common/library.h\"\n\n#if __has_include(<GL/gl.h>)\n#include <GL/gl.h>\n#elif __APPLE__\n#define GL_SILENCE_DEPRECATION 1\n#include <OpenGL/gl.h>\n#else\n#define FF_HAVE_NO_GL 1\n#endif\n\n#ifndef FF_HAVE_NO_GL\n\n#ifndef GL_SHADING_LANGUAGE_VERSION // For WGL\n    #define GL_SHADING_LANGUAGE_VERSION 0x8B8C\n#endif\n\nvoid ffOpenGLHandleResult(FFOpenGLResult* result, __typeof__(&glGetString) ffglGetString)\n{\n    ffStrbufAppendS(&result->version, (const char*) ffglGetString(GL_VERSION));\n    ffStrbufAppendS(&result->renderer, (const char*) ffglGetString(GL_RENDERER));\n    ffStrbufAppendS(&result->vendor, (const char*) ffglGetString(GL_VENDOR));\n    ffStrbufAppendS(&result->slv, (const char*) ffglGetString(GL_SHADING_LANGUAGE_VERSION));\n}\n\n#if defined(FF_HAVE_EGL) || __has_include(<EGL/egl.h>)\n#include \"common/io.h\"\n\n#define EGL_EGL_PROTOTYPES 1\n#define EGL_EGLEXT_PROTOTYPES 1\n#include <EGL/egl.h>\n#include <EGL/eglext.h>\n\ntypedef struct EGLData\n{\n    FF_LIBRARY_SYMBOL(glGetString)\n    FF_LIBRARY_SYMBOL(eglGetProcAddress)\n    FF_LIBRARY_SYMBOL(eglGetDisplay)\n    FF_LIBRARY_SYMBOL(eglQueryString)\n    FF_LIBRARY_SYMBOL(eglInitialize)\n    FF_LIBRARY_SYMBOL(eglBindAPI)\n    FF_LIBRARY_SYMBOL(eglGetConfigs)\n    FF_LIBRARY_SYMBOL(eglCreatePbufferSurface)\n    FF_LIBRARY_SYMBOL(eglCreateContext)\n    FF_LIBRARY_SYMBOL(eglMakeCurrent)\n    FF_LIBRARY_SYMBOL(eglDestroyContext)\n    FF_LIBRARY_SYMBOL(eglDestroySurface)\n    FF_LIBRARY_SYMBOL(eglTerminate)\n\n    EGLDisplay display;\n    EGLConfig config;\n    EGLSurface surface;\n    EGLContext context;\n} EGLData;\n\nstatic const char* eglHandleContext(FFOpenGLResult* result, EGLData* data)\n{\n    FF_DEBUG(\"Making EGL context current\");\n    if(data->ffeglMakeCurrent(data->display, data->surface, data->surface, data->context) != EGL_TRUE)\n    {\n        FF_DEBUG(\"eglMakeCurrent() returned EGL_FALSE\");\n        return \"eglMakeCurrent returned EGL_FALSE\";\n    }\n\n    ffOpenGLHandleResult(result, data->ffglGetString);\n    ffStrbufSetF(&result->library, \"EGL %s\", data->ffeglQueryString(data->display, EGL_VERSION));\n    FF_DEBUG(\"OpenGL via EGL detected: version='%s', renderer='%s', vendor='%s', slv='%s', library='%s'\",\n        result->version.chars,\n        result->renderer.chars,\n        result->vendor.chars,\n        result->slv.chars,\n        result->library.chars);\n    return NULL;\n}\n\nstatic const char* eglHandleSurface(FFOpenGLResult* result, EGLData* data, bool gles)\n{\n    FF_DEBUG(\"Creating EGL context (preferred API=%s, client version=%d)\", gles ? \"OpenGL ES\" : \"OpenGL\", gles ? 2 : 1);\n    data->context = data->ffeglCreateContext(data->display, data->config, EGL_NO_CONTEXT, (EGLint[]){\n        EGL_CONTEXT_CLIENT_VERSION, gles ? 2 : 1, // Try GLES 2.0+ first\n        EGL_NONE\n    });\n    if(data->context == EGL_NO_CONTEXT && gles) // Some ANGLE builds support GLES 1.1 only\n    {\n        FF_DEBUG(\"EGL context creation with GLES 2.x failed, retrying with default attributes (GLES 1.1 fallback)\");\n        data->context = data->ffeglCreateContext(data->display, data->config, EGL_NO_CONTEXT, (EGLint[]){EGL_NONE});\n    }\n    if(data->context == EGL_NO_CONTEXT)\n    {\n        FF_DEBUG(\"eglCreateContext() returned EGL_NO_CONTEXT\");\n        return \"eglCreateContext returned EGL_NO_CONTEXT\";\n    }\n\n    FF_DEBUG(\"EGL context created successfully\");\n\n    const char* error = eglHandleContext(result, data);\n    FF_DEBUG(\"eglHandleContext() returns: %s\", error ?: \"success\");\n\n    FF_DEBUG(\"Releasing current EGL context\");\n    data->ffeglMakeCurrent(data->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);\n\n    FF_DEBUG(\"Destroying EGL context\");\n    data->ffeglDestroyContext(data->display, data->context);\n    return error;\n}\n\nstatic const char* eglHandleDisplay(FFOpenGLResult* result, EGLData* data)\n{\n    // try use OpenGL API. If failed, use the default API (usually OpenGL ES)\n    bool gles = !data->ffeglBindAPI(EGL_OPENGL_API);\n    FF_DEBUG(\"eglBindAPI(EGL_OPENGL_API) %s, effective API=%s\",\n        gles ? \"failed\" : \"succeeded\",\n        gles ? \"default (usually OpenGL ES)\" : \"OpenGL\");\n\n    EGLint eglConfigCount;\n    data->ffeglGetConfigs(data->display, &data->config, 1, &eglConfigCount);\n    FF_DEBUG(\"eglGetConfigs() returned %d config(s)\", eglConfigCount);\n\n    if(eglConfigCount == 0)\n    {\n        FF_DEBUG(\"No EGL config is available\");\n        return \"eglGetConfigs returned 0 configs\";\n    }\n\n    FF_DEBUG(\"Creating EGL pbuffer surface (%dx%d)\", FF_OPENGL_BUFFER_WIDTH, FF_OPENGL_BUFFER_HEIGHT);\n    data->surface = data->ffeglCreatePbufferSurface(data->display, data->config, (EGLint[]){\n        EGL_WIDTH, FF_OPENGL_BUFFER_WIDTH,\n        EGL_HEIGHT, FF_OPENGL_BUFFER_HEIGHT,\n        EGL_NONE\n    });\n\n    if(data->surface == EGL_NO_SURFACE)\n    {\n        FF_DEBUG(\"eglCreatePbufferSurface() returned EGL_NO_SURFACE\");\n        return \"eglCreatePbufferSurface returned EGL_NO_SURFACE\";\n    }\n\n    FF_DEBUG(\"EGL pbuffer surface created successfully\");\n\n    const char* error = eglHandleSurface(result, data, gles);\n    FF_DEBUG(\"eglHandleSurface() returns: %s\", error ?: \"success\");\n\n    FF_DEBUG(\"Destroying EGL surface\");\n    data->ffeglDestroySurface(data->display, data->surface);\n    return error;\n}\n\nstatic const char* eglHandleData(FFOpenGLResult* result, EGLData* data)\n{\n    FF_DEBUG(\"Resolving glGetString via eglGetProcAddress()\");\n    data->ffglGetString = (__typeof__(&glGetString)) data->ffeglGetProcAddress(\"glGetString\");\n    if(!data->ffglGetString)\n    {\n        FF_DEBUG(\"eglGetProcAddress('glGetString') returned NULL\");\n        return \"eglGetProcAddress(glGetString) returned NULL\";\n    }\n\n    #if EGL_VERSION_1_5\n    PFNEGLGETPLATFORMDISPLAYEXTPROC ffeglGetPlatformDisplay = (PFNEGLGETPLATFORMDISPLAYEXTPROC) data->ffeglGetProcAddress(\"eglGetPlatformDisplay\");\n    if (ffeglGetPlatformDisplay)\n    {\n        FF_DEBUG(\"Trying eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA)\");\n        data->display = ffeglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA, NULL, NULL);\n        FF_DEBUG(\"eglGetPlatformDisplay() %s\", data->display == EGL_NO_DISPLAY ? \"failed\" : \"succeeded\");\n    }\n    else\n        FF_DEBUG(\"eglGetPlatformDisplay is unavailable, falling back to eglGetDisplay\");\n\n    if(!ffeglGetPlatformDisplay || data->display == EGL_NO_DISPLAY)\n    #endif\n\n    {\n        FF_DEBUG(\"Trying eglGetDisplay(EGL_DEFAULT_DISPLAY)\");\n        data->display = data->ffeglGetDisplay(EGL_DEFAULT_DISPLAY);\n        if(data->display == EGL_NO_DISPLAY)\n        {\n            FF_DEBUG(\"eglGetDisplay() returned EGL_NO_DISPLAY\");\n            return \"eglGetDisplay returned EGL_NO_DISPLAY\";\n        }\n\n        FF_DEBUG(\"eglGetDisplay() succeeded\");\n    }\n\n\n    EGLint major, minor;\n    if(data->ffeglInitialize(data->display, &major, &minor) == EGL_FALSE)\n    {\n        FF_DEBUG(\"eglInitialize() returned EGL_FALSE\");\n        return \"eglInitialize returned EGL_FALSE\";\n    }\n\n    FF_DEBUG(\"EGL initialized successfully: %d.%d\", major, minor);\n\n    const char* error = eglHandleDisplay(result, data);\n    FF_DEBUG(\"eglHandleDisplay() returns: %s\", error ?: \"success\");\n\n    FF_DEBUG(\"Terminating EGL display connection\");\n    data->ffeglTerminate(data->display);\n    return error;\n}\n\n\nconst char* ffOpenGLDetectByEGL(FFOpenGLResult* result)\n{\n    FF_DEBUG(\"Starting OpenGL detection via EGL\");\n    EGLData eglData;\n\n    FF_LIBRARY_LOAD_MESSAGE(egl, \"libEGL\" FF_LIBRARY_EXTENSION, 1);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglGetProcAddress);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglGetDisplay);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglQueryString);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglInitialize);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglBindAPI);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglGetConfigs);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglCreatePbufferSurface);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglCreateContext);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglMakeCurrent);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglDestroyContext);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglDestroySurface);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglTerminate);\n\n    FF_DEBUG(\"Loaded EGL library and required symbols\");\n\n    FF_SUPPRESS_IO();\n    FF_DEBUG(\"Suppressed stdout/stderr during EGL probing\");\n\n    const char* error = eglHandleData(result, &eglData);\n    FF_DEBUG(\"OpenGL detection via EGL returns: %s\", error ?: \"success\");\n\n    return error;\n}\n\n#endif //FF_HAVE_EGL\n\n#endif //FF_HAVE_NO_GL\n"
  },
  {
    "path": "src/detection/opengl/opengl_windows.c",
    "content": "#include \"opengl.h\"\n#include \"common/library.h\"\n#include \"common/printing.h\"\n#include \"common/windows/nt.h\"\n\n#include <windows.h>\n#include <GL/gl.h>\n\ntypedef struct WGLData\n{\n    FF_LIBRARY_SYMBOL(glGetString)\n    FF_LIBRARY_SYMBOL(wglMakeCurrent)\n    FF_LIBRARY_SYMBOL(wglCreateContext)\n    FF_LIBRARY_SYMBOL(wglDeleteContext)\n} WGLData;\n\nvoid ffOpenGLHandleResult(FFOpenGLResult* result, __typeof__(&glGetString) ffglGetString);\n\nstatic const char* wglHandleContext(WGLData* wglData, FFOpenGLResult* result, HDC hdc, HGLRC context)\n{\n    if(wglData->ffwglMakeCurrent(hdc, context) == FALSE)\n        return \"wglMakeCurrent() failed\";\n    ffOpenGLHandleResult(result, wglData->ffglGetString);\n    ffStrbufSetStatic(&result->library, \"WGL 1.0\");\n    if(wglData->ffwglMakeCurrent(NULL, NULL) == FALSE)\n        return \"wglMakeCurrent(NULL, NULL) failed\";\n    return NULL;\n}\n\nstatic const char* wglHandlePixelFormat(WGLData* wglData, FFOpenGLResult* result, HWND hWnd)\n{\n    HDC hdc = GetDC(hWnd);\n\n    if(hdc == NULL)\n        return \"GetDC() failed\";\n\n    PIXELFORMATDESCRIPTOR pfd = {\n        .nSize = sizeof(PIXELFORMATDESCRIPTOR),\n        .nVersion = 1,\n        .dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,\n        .iPixelType = PFD_TYPE_RGBA,\n        .cColorBits = 32,\n        .cDepthBits = 24,\n        .iLayerType = PFD_MAIN_PLANE\n    };\n    int pixelFormat = ChoosePixelFormat(hdc, &pfd);\n    if(pixelFormat == 0)\n    {\n        ReleaseDC(hWnd, hdc);\n        return \"ChoosePixelFormat() failed\";\n    }\n\n    if(SetPixelFormat(hdc, pixelFormat, &pfd) == FALSE)\n    {\n        ReleaseDC(hWnd, hdc);\n        return \"SetPixelFormat() failed\";\n    }\n\n    HGLRC context = wglData->ffwglCreateContext(hdc);\n    if(context == NULL)\n    {\n        ReleaseDC(hWnd, hdc);\n        return \"wglCreateContext() failed\";\n    }\n\n    const char* error = wglHandleContext(wglData, result, hdc, context);\n    wglData->ffwglDeleteContext(context);\n\n    ReleaseDC(hWnd, hdc);\n\n    return error;\n}\n\nstatic const char* wglDetectOpenGL(FFOpenGLResult* result)\n{\n    FF_LIBRARY_LOAD_MESSAGE(opengl32, \"opengl32\" FF_LIBRARY_EXTENSION, 1);\n\n    WGLData data = {};\n\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(opengl32, data, wglMakeCurrent);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(opengl32, data, wglCreateContext);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(opengl32, data, wglDeleteContext);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(opengl32, data, glGetString);\n\n    HINSTANCE hInstance = ffGetPeb()->ImageBaseAddress;\n\n    WNDCLASSW wc = {\n        .lpfnWndProc = DefWindowProcW,\n        .hInstance = hInstance,\n        .hbrBackground = (HBRUSH)COLOR_BACKGROUND,\n        .lpszClassName = L\"ogl_version_check\",\n        .style = CS_OWNDC,\n    };\n    if(!RegisterClassW(&wc))\n        return \"RegisterClassW() failed\";\n\n    HWND hWnd = CreateWindowW(wc.lpszClassName, L\"ogl_version_check\", 0, 0, 0, FF_OPENGL_BUFFER_WIDTH, FF_OPENGL_BUFFER_HEIGHT, NULL, NULL, hInstance, NULL);\n    if(!hWnd)\n        return \"CreateWindowW() failed\";\n\n    const char* error = wglHandlePixelFormat(&data, result, hWnd);\n\n    DestroyWindow(hWnd);\n    UnregisterClassW(wc.lpszClassName, hInstance);\n\n    return error;\n}\n\n\nconst char* ffDetectOpenGL(FFOpenGLOptions* options, FFOpenGLResult* result)\n{\n    if (options->library == FF_OPENGL_LIBRARY_AUTO)\n        return wglDetectOpenGL(result);\n    else if (options->library == FF_OPENGL_LIBRARY_EGL)\n    {\n        #if __has_include(<EGL/egl.h>)\n        const char* ffOpenGLDetectByEGL(FFOpenGLResult* result);\n        return ffOpenGLDetectByEGL(result);\n        #else\n        return \"fastfetch was compiled without egl support\";\n        #endif\n    }\n    else\n        return \"Unsupported OpenGL library\";\n}\n"
  },
  {
    "path": "src/detection/os/os.c",
    "content": "#include \"os.h\"\n\nvoid ffDetectOSImpl(FFOSResult* os);\n\nconst FFOSResult* ffDetectOS(void)\n{\n    static FFOSResult result;\n    if (result.name.chars == NULL)\n    {\n        ffStrbufInit(&result.name);\n        ffStrbufInit(&result.prettyName);\n        ffStrbufInit(&result.id);\n        ffStrbufInit(&result.version);\n        ffStrbufInit(&result.versionID);\n        ffStrbufInit(&result.codename);\n        ffStrbufInit(&result.buildID);\n        ffStrbufInit(&result.idLike);\n        ffStrbufInit(&result.variant);\n        ffStrbufInit(&result.variantID);\n        ffDetectOSImpl(&result);\n    }\n    return &result;\n}\n"
  },
  {
    "path": "src/detection/os/os.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/os/option.h\"\n\ntypedef struct FFOSResult\n{\n    FFstrbuf name;\n    FFstrbuf prettyName;\n    FFstrbuf id;\n    FFstrbuf idLike;\n    FFstrbuf variant;\n    FFstrbuf variantID;\n    FFstrbuf version;\n    FFstrbuf versionID;\n    FFstrbuf codename;\n    FFstrbuf buildID;\n} FFOSResult;\n\nconst FFOSResult* ffDetectOS();\n"
  },
  {
    "path": "src/detection/os/os_android.c",
    "content": "#include \"os.h\"\n#include \"common/settings.h\"\n\nvoid ffDetectOSImpl(FFOSResult* os)\n{\n    ffStrbufSetStatic(&os->name, \"Android\");\n\n    ffStrbufSetStatic(&os->id, \"android\");\n\n    ffSettingsGetAndroidProperty(\"ro.build.version.release\", &os->version);\n\n    ffSettingsGetAndroidProperty(\"ro.build.version.release\", &os->versionID);\n\n    ffSettingsGetAndroidProperty(\"ro.build.version.codename\", &os->codename);\n\n    ffSettingsGetAndroidProperty(\"ro.build.id\", &os->buildID);\n}\n"
  },
  {
    "path": "src/detection/os/os_apple.m",
    "content": "#include \"os.h\"\n#include \"common/io.h\"\n#include \"common/sysctl.h\"\n#include \"common/stringUtils.h\"\n#include \"common/mallocHelper.h\"\n\n#include <stdlib.h>\n#include <string.h>\n#import <Foundation/Foundation.h>\n\nstatic bool parseSystemVersion(FFOSResult* os)\n{\n    NSError* error;\n    NSString* fileName = @\"file:///System/Library/CoreServices/SystemVersion.plist\";\n    NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName]\n                                       error:&error];\n    if(error)\n        return false;\n\n    NSString* value;\n\n    if((value = dict[@\"ProductName\"]))\n        ffStrbufInitS(&os->name, value.UTF8String);\n    if((value = dict[@\"ProductUserVisibleVersion\"]))\n        ffStrbufInitS(&os->version, value.UTF8String);\n    if((value = dict[@\"ProductBuildVersion\"]))\n        ffStrbufInitS(&os->buildID, value.UTF8String);\n    if (ffStrbufStartsWithS(&os->version, \"16.\"))\n    {\n        // macOS 26 Tahoe. #1809\n        os->version.chars[0] = '2';\n    }\n\n    return true;\n}\n\nstatic bool detectOSCodeName(FFOSResult* os)\n{\n    // https://en.wikipedia.org/wiki/MacOS_version_history\n    char* str_end;\n    const char* version = os->version.chars;\n    unsigned long num = strtoul(version, &str_end, 10);\n    if (str_end == version) return false;\n\n    switch (num)\n    {\n        case 26:\n        case 16: ffStrbufSetStatic(&os->codename, \"Tahoe\"); return true;\n        case 15: ffStrbufSetStatic(&os->codename, \"Sequoia\"); return true;\n        case 14: ffStrbufSetStatic(&os->codename, \"Sonoma\"); return true;\n        case 13: ffStrbufSetStatic(&os->codename, \"Ventura\"); return true;\n        case 12: ffStrbufSetStatic(&os->codename, \"Monterey\"); return true;\n        case 11: ffStrbufSetStatic(&os->codename, \"Big Sur\"); return true;\n        case 10: {\n            version = str_end + 1;\n            num = strtoul(version, &str_end, 10);\n            if (str_end == version) return false;\n\n            switch (num)\n            {\n                case 16: ffStrbufSetStatic(&os->codename, \"Big Sur\"); return true;\n                case 15: ffStrbufSetStatic(&os->codename, \"Catalina\"); return true;\n                case 14: ffStrbufSetStatic(&os->codename, \"Mojave\"); return true;\n                case 13: ffStrbufSetStatic(&os->codename, \"High Sierra\"); return true;\n                case 12: ffStrbufSetStatic(&os->codename, \"Sierra\"); return true;\n                case 11: ffStrbufSetStatic(&os->codename, \"El Capitan\"); return true;\n                case 10: ffStrbufSetStatic(&os->codename, \"Yosemite\"); return true;\n                case 9: ffStrbufSetStatic(&os->codename, \"Mavericks\"); return true;\n                case 8: ffStrbufSetStatic(&os->codename, \"Mountain Lion\"); return true;\n                case 7: ffStrbufSetStatic(&os->codename, \"Lion\"); return true;\n                case 6: ffStrbufSetStatic(&os->codename, \"Snow Leopard\"); return true;\n                case 5: ffStrbufSetStatic(&os->codename, \"Leopard\"); return true;\n                case 4: ffStrbufSetStatic(&os->codename, \"Tiger\"); return true;\n                case 3: ffStrbufSetStatic(&os->codename, \"Panther\"); return true;\n                case 2: ffStrbufSetStatic(&os->codename, \"Jaguar\"); return true;\n                case 1: ffStrbufSetStatic(&os->codename, \"Puma\"); return true;\n                case 0: ffStrbufSetStatic(&os->codename, \"Cheetah\"); return true;\n            }\n        }\n    }\n\n    return false;\n}\n\nvoid ffDetectOSImpl(FFOSResult* os)\n{\n    parseSystemVersion(os);\n\n    ffStrbufSetStatic(&os->id, \"macos\");\n\n    if(__builtin_expect(os->name.length == 0, 0))\n        ffStrbufSetStatic(&os->name, \"macOS\");\n\n    if(__builtin_expect(os->version.length == 0, 0))\n        ffSysctlGetString(\"kern.osproductversion\", &os->version);\n\n    if(__builtin_expect(os->buildID.length == 0, 0))\n        ffSysctlGetString(\"kern.osversion\", &os->buildID);\n\n    ffStrbufAppend(&os->versionID, &os->version);\n\n    detectOSCodeName(os);\n\n    ffStrbufSetF(&os->prettyName, \"%s %s %s (%s)\", os->name.chars, os->codename.chars, os->version.chars, os->buildID.chars);\n}\n"
  },
  {
    "path": "src/detection/os/os_haiku.c",
    "content": "#include \"os.h\"\n#include <OS.h>\n#include <image.h>\n\nvoid ffDetectOSImpl(FFOSResult* os)\n{\n    ffStrbufSetStatic(&os->name, \"Haiku\");\n\n    ffStrbufSetStatic(&os->id, \"haiku\");\n\n    image_info image;\n    int32 cookie = 0;\n    while (get_next_image_info(B_SYSTEM_TEAM, &cookie, &image) == B_OK)\n    {\n        int32 ver = image.api_version;\n        if (ver == 0) continue;\n\n        // https://github.com/haiku/haiku/blob/e63683b2fb337d2034059a7e053c170eaf978142/headers/os/BeBuild.h#L36\n        if (ver < B_HAIKU_VERSION_1_ALPHA_1)\n        {\n            switch (ver)\n            {\n                case B_HAIKU_VERSION_BEOS:\n                    ffStrbufSetStatic(&os->version, \"BEOS\");\n                    break;\n                case B_HAIKU_VERSION_BONE:\n                    ffStrbufSetStatic(&os->version, \"BONE\");\n                    break;\n                case B_HAIKU_VERSION_DANO:\n                    ffStrbufSetStatic(&os->version, \"DANO\");\n                    break;\n            }\n        }\n        else\n        {\n            int32 relVer = ver / 0x10000;\n            ver %= 0x10000;\n            if (ver == 0)\n            {\n                ffStrbufSetF(&os->version, \"R%d\", relVer);\n            }\n            else\n            {\n                relVer++;\n\n                bool isPre = !!(ver & 1);\n                if (ver < B_HAIKU_VERSION_1_PRE_BETA_1)\n                {\n                    int32 alphaVer = ver / 0x100;\n                    if (isPre)\n                        ffStrbufSetF(&os->version, \"R%dA%d-\", relVer, alphaVer + 1);\n                    else\n                        ffStrbufSetF(&os->version, \"R%dA%d\", relVer, alphaVer);\n                }\n                else if (ver < 0x00010000 /* B_HAIKU_VERSION_1 */)\n                {\n                    int32 betaVer = (ver - B_HAIKU_VERSION_1_ALPHA_4) / 0x100;\n                    if (isPre)\n                        ffStrbufSetF(&os->version, \"R%dB%d-\", relVer, betaVer + 1);\n                    else\n                        ffStrbufSetF(&os->version, \"R%dB%d\", relVer, betaVer);\n                }\n            }\n        }\n    }\n\n    if (!os->version.length)\n    {\n        system_info sys;\n        if (get_system_info(&sys) == B_OK)\n            ffStrbufAppendF(&os->version, \"R%ldx\", sys.kernel_version);\n    }\n}\n"
  },
  {
    "path": "src/detection/os/os_linux.c",
    "content": "#include \"os.h\"\n#include \"common/properties.h\"\n#include \"common/parsing.h\"\n#include \"common/io.h\"\n#include \"common/processing.h\"\n#include \"common/stringUtils.h\"\n\n#include <string.h>\n#include <stdlib.h>\n\n#define FF_STR_INDIR(x) #x\n#define FF_STR(x) FF_STR_INDIR(x)\n\nstatic bool parseLsbRelease(const char* fileName, FFOSResult* result)\n{\n    return ffParsePropFileValues(fileName, 4, (FFpropquery[]) {\n        {\"DISTRIB_ID =\", &result->id},\n        {\"DISTRIB_DESCRIPTION =\", &result->prettyName},\n        {\"DISTRIB_RELEASE =\", &result->version},\n        {\"DISTRIB_CODENAME =\", &result->codename},\n    });\n}\n\nstatic bool parseOsRelease(const char* fileName, FFOSResult* result)\n{\n    return ffParsePropFileValues(fileName, 11, (FFpropquery[]) {\n        {\"PRETTY_NAME =\", &result->prettyName},\n        {\"NAME =\", &result->name},\n        {\"ID =\", &result->id},\n        {\"ID_LIKE =\", &result->idLike},\n        {\"VARIANT =\", &result->variant},\n        {\"VARIANT_ID =\", &result->variantID},\n        {\"VERSION =\", &result->version},\n        {\"VERSION_ID =\", &result->versionID},\n        {\"VERSION_CODENAME =\", &result->codename},\n        {\"CODENAME =\", &result->codename},\n        {\"BUILD_ID =\", &result->buildID},\n    });\n}\n\n// Common logic for detecting Armbian image version\nFF_MAYBE_UNUSED static bool detectArmbianVersion(FFOSResult* result)\n{\n    // Possible values `PRETTY_NAME` starts with on Armbian:\n    // - `Armbian` for official releases\n    // - `Armbian_community` for community releases\n    // - `Armbian_Security` for images with kali repo added\n    // - `Armbian-unofficial` for an unofficial image built from source, e.g. during development and testing\n    if (ffStrbufStartsWithS(&result->prettyName, \"Armbian\"))\n        ffStrbufSetStatic(&result->name, \"Armbian\");\n    else\n        return false;\n    ffStrbufSet(&result->idLike, &result->id);\n    ffStrbufSetS(&result->id, \"armbian\");\n    ffStrbufClear(&result->versionID);\n    uint32_t versionStart = ffStrbufFirstIndexC(&result->prettyName, ' ') + 1;\n    uint32_t versionEnd = ffStrbufNextIndexC(&result->prettyName, versionStart, ' ');\n    ffStrbufSetNS(&result->versionID, versionEnd - versionStart, result->prettyName.chars + versionStart);\n    return true;\n}\n\n// Returns false if PrettyName should be updated by caller\nFF_MAYBE_UNUSED static bool getUbuntuFlavour(FFOSResult* result)\n{\n    if (detectArmbianVersion(result))\n        return true;\n    else if(ffStrbufStartsWithS(&result->prettyName, \"Linux Lite \"))\n    {\n        ffStrbufSetStatic(&result->name, \"Linux Lite\");\n        ffStrbufSetStatic(&result->id, \"linuxlite\");\n        ffStrbufSetStatic(&result->idLike, \"ubuntu\");\n        ffStrbufSetS(&result->versionID, result->prettyName.chars + strlen(\"Linux Lite \"));\n        return true;\n    }\n    else if(ffStrbufStartsWithS(&result->prettyName, \"Rhino Linux \"))\n    {\n        ffStrbufSetStatic(&result->name, \"Rhino Linux\");\n        ffStrbufSetStatic(&result->id, \"rhinolinux\");\n        ffStrbufSetStatic(&result->idLike, \"ubuntu\");\n        ffStrbufSetS(&result->versionID, result->prettyName.chars + strlen(\"Rhino Linux \"));\n        return true;\n    }\n    else if(ffStrbufStartsWithS(&result->prettyName, \"VanillaOS \"))\n    {\n        ffStrbufSetStatic(&result->id, \"vanilla\");\n        ffStrbufSetStatic(&result->idLike, \"ubuntu\");\n        return true;\n    }\n\n    if (ffPathExists(\"/usr/bin/lliurex-version\", FF_PATHTYPE_FILE))\n    {\n        ffStrbufSetStatic(&result->name, \"LliureX\");\n        ffStrbufSetStatic(&result->id, \"lliurex\");\n        ffStrbufClear(&result->version);\n        if (ffProcessAppendStdOut(&result->version, (char* const[]) {\n            \"/usr/bin/lliurex-version\",\n            NULL,\n        }) == NULL) // 8.2.2\n            ffStrbufTrimRightSpace(&result->version);\n        ffStrbufSetF(&result->prettyName, \"LliureX %s\", result->version.chars);\n        ffStrbufSetStatic(&result->idLike, \"ubuntu\");\n        return true;\n    }\n\n    const char* xdgConfigDirs = getenv(\"XDG_CONFIG_DIRS\");\n    if(!ffStrSet(xdgConfigDirs))\n        return false;\n\n    if(ffStrContains(xdgConfigDirs, \"kde\") || ffStrContains(xdgConfigDirs, \"plasma\") || ffStrContains(xdgConfigDirs, \"kubuntu\"))\n    {\n        ffStrbufSetStatic(&result->name, \"Kubuntu\");\n        ffStrbufSetStatic(&result->id, \"kubuntu\");\n        ffStrbufSetStatic(&result->idLike, \"ubuntu\");\n        return false;\n    }\n\n    if(ffStrContains(xdgConfigDirs, \"xfce\") || ffStrContains(xdgConfigDirs, \"xubuntu\"))\n    {\n        ffStrbufSetStatic(&result->name, \"Xubuntu\");\n        ffStrbufSetStatic(&result->id, \"xubuntu\");\n        ffStrbufSetStatic(&result->idLike, \"ubuntu\");\n        return false;\n    }\n\n    if(ffStrContains(xdgConfigDirs, \"lxqt\") || ffStrContains(xdgConfigDirs, \"lubuntu\"))\n    {\n        ffStrbufSetStatic(&result->name, \"Lubuntu\");\n        ffStrbufSetStatic(&result->id, \"lubuntu\");\n        ffStrbufSetStatic(&result->idLike, \"ubuntu\");\n        return false;\n    }\n\n    if(ffStrContains(xdgConfigDirs, \"budgie\"))\n    {\n        ffStrbufSetStatic(&result->name, \"Ubuntu Budgie\");\n        ffStrbufSetStatic(&result->id, \"ubuntu-budgie\");\n        ffStrbufSetStatic(&result->idLike, \"ubuntu\");\n        return false;\n    }\n\n    if(ffStrContains(xdgConfigDirs, \"cinnamon\"))\n    {\n        ffStrbufSetStatic(&result->name, \"Ubuntu Cinnamon\");\n        ffStrbufSetStatic(&result->id, \"ubuntu-cinnamon\");\n        ffStrbufSetStatic(&result->idLike, \"ubuntu\");\n        return false;\n    }\n\n    if(ffStrContains(xdgConfigDirs, \"mate\"))\n    {\n        ffStrbufSetStatic(&result->name, \"Ubuntu MATE\");\n        ffStrbufSetStatic(&result->id, \"ubuntu-mate\");\n        ffStrbufSetStatic(&result->idLike, \"ubuntu\");\n        return false;\n    }\n\n    if(ffStrContains(xdgConfigDirs, \"studio\"))\n    {\n        ffStrbufSetStatic(&result->name, \"Ubuntu Studio\");\n        ffStrbufSetStatic(&result->id, \"ubuntu-studio\");\n        ffStrbufSetStatic(&result->idLike, \"ubuntu\");\n        return false;\n    }\n\n    if(ffStrContains(xdgConfigDirs, \"sway\"))\n    {\n        ffStrbufSetStatic(&result->name, \"Ubuntu Sway\");\n        ffStrbufSetStatic(&result->id, \"ubuntu-sway\");\n        ffStrbufSetStatic(&result->idLike, \"ubuntu\");\n        return false;\n    }\n\n    if(ffStrContains(xdgConfigDirs, \"touch\"))\n    {\n        ffStrbufSetStatic(&result->name, \"Ubuntu Touch\");\n        ffStrbufSetStatic(&result->id, \"ubuntu-touch\");\n        ffStrbufSetStatic(&result->idLike, \"ubuntu\");\n        return false;\n    }\n\n    return false;\n}\n\nFF_MAYBE_UNUSED static void getDebianVersion(FFOSResult* result)\n{\n    FF_STRBUF_AUTO_DESTROY debianVersion = ffStrbufCreate();\n    ffAppendFileBuffer(\"/etc/debian_version\", &debianVersion);\n    ffStrbufTrimRightSpace(&debianVersion);\n    if (!debianVersion.length) return;\n    ffStrbufDestroy(&result->versionID);\n    ffStrbufInitMove(&result->versionID, &debianVersion);\n\n    ffStrbufSetF(&result->prettyName, \"%s %s (%s)\", result->name.chars, result->versionID.chars, result->codename.chars);\n}\n\nFF_MAYBE_UNUSED static bool detectDebianDerived(FFOSResult* result)\n{\n    if (detectArmbianVersion(result))\n        return true;\n    else if (ffStrbufStartsWithS(&result->name, \"Loc-OS\"))\n    {\n        ffStrbufSetStatic(&result->id, \"locos\");\n        ffStrbufSetStatic(&result->idLike, \"debian\");\n        return true;\n    }\n    else if (ffStrbufEqualS(&result->name, \"Parrot Security\"))\n    {\n        // https://github.com/ParrotSec/base-files/blob/c06f6d42ddf8d79564882306576576eddab7d907/etc/os-release\n        ffStrbufSetS(&result->id, \"parrot\");\n        ffStrbufSetS(&result->idLike, \"debian\");\n        return true;\n    }\n    else if (ffStrbufStartsWithS(&result->name, \"Lilidog GNU/Linux\"))\n    {\n        // https://github.com/fastfetch-cli/fastfetch/issues/1373\n        ffStrbufSetStatic(&result->id, \"lilidog\");\n        ffStrbufSetStatic(&result->idLike, \"debian\");\n        return true;\n    }\n    else if (access(\"/usr/bin/pveversion\", X_OK) == 0)\n    {\n        ffStrbufSetStatic(&result->id, \"pve\");\n        ffStrbufSetStatic(&result->idLike, \"debian\");\n        ffStrbufSetStatic(&result->name, \"Proxmox VE\");\n        ffStrbufClear(&result->versionID);\n        if (ffProcessAppendStdOut(&result->versionID, (char* const[]) {\n            \"/usr/bin/dpkg-query\",\n            \"--showformat=${version}\",\n            \"--show\",\n            \"pve-manager\",\n            NULL,\n        }) == NULL) // 8.2.2\n            ffStrbufTrimRightSpace(&result->versionID);\n        ffStrbufSetF(&result->prettyName, \"Proxmox VE %s\", result->versionID.chars);\n        return true;\n    }\n    else if (ffPathExists(\"/etc/rpi-issue\", FF_PATHTYPE_FILE))\n    {\n        // Raspberry Pi OS\n        ffStrbufSetStatic(&result->id, \"raspbian\");\n        ffStrbufSetStatic(&result->idLike, \"debian\");\n        ffStrbufSetStatic(&result->name, \"Raspberry Pi OS\");\n        getDebianVersion(result);\n        return true;\n    }\n    else if (ffPathExists(\"/boot/dietpi/.version\", FF_PATHTYPE_FILE))\n    {\n        // DietPi\n        ffStrbufSetStatic(&result->id, \"dietpi\");\n        ffStrbufSetStatic(&result->name, \"DietPi\");\n        ffStrbufSetStatic(&result->prettyName, \"DietPi\");\n        ffStrbufSetStatic(&result->idLike, \"debian\");\n        FF_STRBUF_AUTO_DESTROY core = ffStrbufCreate();\n        FF_STRBUF_AUTO_DESTROY sub = ffStrbufCreate();\n        FF_STRBUF_AUTO_DESTROY rc = ffStrbufCreate();\n        if (ffParsePropFileValues(\"/boot/dietpi/.version\", 3, (FFpropquery[]) {\n            {\"G_DIETPI_VERSION_CORE=\", &core},\n            {\"G_DIETPI_VERSION_SUB=\", &sub},\n            {\"G_DIETPI_VERSION_RC=\", &rc},\n        })) ffStrbufAppendF(&result->prettyName, \" %s.%s.%s\", core.chars, sub.chars, rc.chars);\n        return true;\n    }\n    else if (ffStrbufEndsWithS(&instance.state.platform.sysinfo.release, \"+truenas\"))\n    {\n        // TrueNAS Scale\n        ffStrbufSetStatic(&result->id, \"truenas-scale\");\n        ffStrbufSetStatic(&result->idLike, \"debian\");\n        ffStrbufSetStatic(&result->name, \"TrueNAS Scale\");\n        ffStrbufSetStatic(&result->prettyName, \"TrueNAS Scale\");\n        return true;\n    }\n    else if (ffPathExists(\"/usr/bin/emmabuntus_config.sh\", FF_PATHTYPE_FILE))\n    {\n        // Emmabuntüs\n        ffStrbufSetStatic(&result->id, \"emmabuntus\");\n        ffStrbufSetStatic(&result->idLike, \"debian\");\n        ffStrbufSetStatic(&result->name, \"Emmabuntüs\");\n        getDebianVersion(result);\n        return true;\n    }\n    else\n    {\n        // Hack for MX Linux. See #847\n        FF_STRBUF_AUTO_DESTROY lsbRelease = ffStrbufCreate();\n        if (ffAppendFileBuffer(\"/etc/lsb-release\", &lsbRelease) && ffStrbufContainS(&lsbRelease, \"DISTRIB_ID=MX\"))\n        {\n            ffStrbufSetStatic(&result->id, \"mx\");\n            ffStrbufSetStatic(&result->idLike, \"debian\");\n            ffStrbufSetStatic(&result->name, \"MX\");\n\n            ffStrbufClear(&result->version);\n            ffParsePropLines(lsbRelease.chars, \"DISTRIB_RELEASE=\", &result->version);\n            ffStrbufSet(&result->versionID, &result->version);\n\n            ffStrbufClear(&result->codename);\n            ffParsePropLines(lsbRelease.chars, \"DISTRIB_CODENAME=\", &result->codename);\n\n            ffStrbufClear(&result->prettyName);\n            ffParsePropLines(lsbRelease.chars, \"DISTRIB_DESCRIPTION=\", &result->prettyName);\n            return true;\n        }\n    }\n    return false;\n}\n\nFF_MAYBE_UNUSED static bool detectFedoraVariant(FFOSResult* result)\n{\n    if (ffStrbufEqualS(&result->variantID, \"coreos\")\n        || ffStrbufEqualS(&result->variantID, \"kinoite\")\n        || ffStrbufEqualS(&result->variantID, \"sericea\")\n        || ffStrbufEqualS(&result->variantID, \"silverblue\"))\n    {\n        ffStrbufAppendC(&result->id, '-');\n        ffStrbufAppend(&result->id, &result->variantID);\n        ffStrbufSetStatic(&result->idLike, \"fedora\");\n        return true;\n    }\n    return false;\n}\n\nstatic bool detectBedrock(FFOSResult* os)\n{\n    const char* bedrockRestrict = getenv(\"BEDROCK_RESTRICT\");\n    if(bedrockRestrict && bedrockRestrict[0] == '1') return false;\n    return parseOsRelease(FASTFETCH_TARGET_DIR_ROOT \"/bedrock/strata/bedrock/etc/os-release\", os);\n}\n\nstatic void detectOS(FFOSResult* os)\n{\n    #ifdef FF_CUSTOM_OS_RELEASE_PATH\n    parseOsRelease(FF_STR(FF_CUSTOM_OS_RELEASE_PATH), os);\n        #ifdef FF_CUSTOM_LSB_RELEASE_PATH\n        parseLsbRelease(FF_STR(FF_CUSTOM_LSB_RELEASE_PATH), os);\n        #endif\n    return;\n    #endif\n\n    if (detectBedrock(os))\n        return;\n\n    // Refer: https://gist.github.com/natefoo/814c5bf936922dad97ff\n\n    parseOsRelease(FASTFETCH_TARGET_DIR_ETC \"/os-release\", os);\n    if (os->id.length == 0 || os->version.length == 0 || os->prettyName.length == 0 || os->codename.length == 0)\n        parseLsbRelease(FASTFETCH_TARGET_DIR_ETC \"/lsb-release\", os);\n    if (os->id.length == 0 || os->name.length == 0 || os->prettyName.length == 0)\n        parseOsRelease(FASTFETCH_TARGET_DIR_USR \"/lib/os-release\", os);\n    if (os->id.length == 0 && os->name.length == 0 && os->prettyName.length == 0)\n    {\n        // HarmonyOS has no os-release file\n        if (ffStrbufEqualS(&instance.state.platform.sysinfo.name, \"HarmonyOS\"))\n        {\n            ffStrbufSetS(&os->id, \"harmonyos\");\n            ffStrbufSetS(&os->idLike, \"harmonyos\");\n            ffStrbufSetS(&os->name, \"HarmonyOS\");\n            ffStrbufSetS(&os->prettyName, \"HarmonyOS\");\n        }\n    }\n}\n\nvoid ffDetectOSImpl(FFOSResult* os)\n{\n    detectOS(os);\n\n    #if __linux__ || __GNU__\n    if(ffStrbufEqualS(&os->id, \"ubuntu\"))\n    {\n        if (!getUbuntuFlavour(os))\n        {\n            if (!ffStrbufEndsWithS(&os->prettyName, \" (development branch)\"))\n                ffStrbufSetF(&os->prettyName, \"%s %s\", os->name.chars, os->version.chars); // os->version contains code name\n        }\n    }\n    else if(ffStrbufEqualS(&os->id, \"debian\"))\n    {\n        if (!detectDebianDerived(os))\n            getDebianVersion(os);\n    }\n    else if(ffStrbufEqualS(&os->id, \"fedora\"))\n        detectFedoraVariant(os);\n    else if(ffStrbufEqualS(&os->id, \"linuxmint\"))\n    {\n        if (ffStrbufEqualS(&os->name, \"LMDE\"))\n        {\n            ffStrbufSetS(&os->id, \"lmde\");\n            ffStrbufSetS(&os->idLike, \"linuxmint\");\n        }\n    }\n    #endif\n}\n"
  },
  {
    "path": "src/detection/os/os_nbsd.c",
    "content": "#include \"os.h\"\n\nvoid ffDetectOSImpl(FFOSResult* os)\n{\n    ffStrbufSetStatic(&os->name, \"NetBSD\");\n    ffStrbufSet(&os->version, &instance.state.platform.sysinfo.release);\n}\n"
  },
  {
    "path": "src/detection/os/os_obsd.c",
    "content": "#include \"os.h\"\n\nvoid ffDetectOSImpl(FFOSResult* os)\n{\n    ffStrbufSetStatic(&os->name, \"OpenBSD\");\n    ffStrbufSet(&os->version, &instance.state.platform.sysinfo.release);\n}\n"
  },
  {
    "path": "src/detection/os/os_sunos.c",
    "content": "#include \"os.h\"\n#include \"common/io.h\"\n\nvoid ffDetectOSImpl(FFOSResult* os)\n{\n    if (!ffReadFileBuffer(\"/etc/release\", &os->prettyName))\n        return;\n\n    ffStrbufSubstrBeforeFirstC(&os->prettyName, '\\n');\n    ffStrbufSubstrBeforeLastC(&os->prettyName, '(');\n    ffStrbufTrim(&os->prettyName, ' ');\n\n    // OpenIndiana Hipster 2024.04\n    uint32_t idx = ffStrbufFirstIndexC(&os->prettyName, ' ');\n    ffStrbufSetNS(&os->id, idx, os->prettyName.chars);\n    ffStrbufSetStatic(&os->idLike, \"sunos\");\n}\n"
  },
  {
    "path": "src/detection/os/os_windows.c",
    "content": "#include \"os.h\"\n#include \"common/library.h\"\n#include \"common/stringUtils.h\"\n#include \"common/windows/registry.h\"\n#include \"common/windows/unicode.h\"\n\n#include <windows.h>\n\nPWSTR WINAPI BrandingFormatString(PCWSTR format);\n\nstatic bool getCodeName(FFOSResult* os)\n{\n    FF_AUTO_CLOSE_FD HANDLE hKey = NULL;\n    if(!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, L\"SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\", &hKey, NULL))\n        return false;\n\n    if(!ffRegReadStrbuf(hKey, L\"DisplayVersion\", &os->codename, NULL))\n    {\n        if (!ffRegReadStrbuf(hKey, L\"CSDVersion\", &os->codename, NULL)) // For Windows 7 and Windows 8\n            if (!ffRegReadStrbuf(hKey, L\"ReleaseId\", &os->codename, NULL)) // For old Windows 10\n                return false;\n    }\n\n    return true;\n}\n\nvoid ffDetectOSImpl(FFOSResult* os)\n{\n    //https://dennisbabkin.com/blog/?t=how-to-tell-the-real-version-of-windows-your-app-is-running-on#ver_string\n    const wchar_t* rawName = BrandingFormatString(L\"%WINDOWS_LONG%\");\n    ffStrbufSetWS(&os->variant, rawName);\n    GlobalFree((HGLOBAL)rawName);\n    ffStrbufSet(&os->prettyName, &os->variant);\n    ffStrbufTrimRight(&os->variant, ' ');\n\n    //WMI returns the \"Microsoft\" prefix while BrandingFormatString doesn't. Make them consistent.\n    if(ffStrbufStartsWithS(&os->variant, \"Microsoft \"))\n        ffStrbufSubstrAfter(&os->variant, strlen(\"Microsoft \") - 1);\n\n    if(os->variant.length == 0) // Windows PE?\n    {\n        FF_AUTO_CLOSE_FD HANDLE hKey = NULL;\n        if(ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, L\"SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\", &hKey, NULL))\n            ffRegReadStrbuf(hKey, L\"ProductName\", &os->variant, NULL);\n    }\n\n    ffStrbufSet(&os->prettyName, &os->variant);\n\n    if(ffStrbufStartsWithS(&os->variant, \"Windows \"))\n    {\n        ffStrbufAppendS(&os->name, \"Windows\");\n\n        ffStrbufSubstrAfter(&os->variant, strlen(\"Windows \") - 1);\n\n        if(ffStrbufStartsWithS(&os->variant, \"Server \"))\n        {\n            ffStrbufAppendS(&os->name, \" Server\");\n            ffStrbufSubstrAfter(&os->variant, strlen(\" Server\") - 1);\n        }\n\n        if(ffStrbufStartsWithIgnCaseS(&os->variant, \"(TM) \"))\n            ffStrbufSubstrAfter(&os->variant, strlen(\" (TM)\") - 1);\n\n        uint32_t index = ffStrbufFirstIndexC(&os->variant, ' ');\n        ffStrbufAppendNS(&os->version, index, os->variant.chars);\n        ffStrbufSubstrAfter(&os->variant, index);\n\n        // Windows Server 20xx Rx\n        if(ffStrbufEndsWithC(&os->name, 'r'))\n        {\n            if(os->variant.chars[0] == 'R' &&\n                ffCharIsDigit(os->variant.chars[1]) &&\n                (os->variant.chars[2] == '\\0' || os->variant.chars[2] == ' '))\n            {\n                ffStrbufAppendF(&os->version, \" R%c\", os->variant.chars[1]);\n                ffStrbufSubstrAfter(&os->variant, strlen(\"Rx \") - 1);\n            }\n        }\n    }\n    else\n    {\n        // Unknown Windows name, please report this\n        ffStrbufAppend(&os->name, &os->variant);\n        ffStrbufClear(&os->variant);\n    }\n\n    ffStrbufAppendF(&os->id, \"%s %s\", os->name.chars, os->version.chars);\n    ffStrbufSetStatic(&os->idLike, \"Windows\");\n\n    if (getCodeName(os) && os->codename.length > 0)\n        ffStrbufAppendF(&os->prettyName, \" (%s)\", os->codename.chars);\n}\n"
  },
  {
    "path": "src/detection/packages/packages.c",
    "content": "#include \"packages.h\"\n#include \"common/io.h\"\n#include \"common/time.h\"\n\n#include <inttypes.h>\n#include <stddef.h>\n\n#ifdef __APPLE__\n#define st_mtim st_mtimespec\n#endif\n\nvoid ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options);\n\nconst char* ffDetectPackages(FFPackagesResult* result, FFPackagesOptions* options)\n{\n    ffDetectPackagesImpl(result, options);\n\n    for(uint32_t i = 0; i < offsetof(FFPackagesResult, all) / sizeof(uint32_t); ++i)\n        result->all += ((uint32_t *)result)[i];\n\n    if (result->all == 0)\n        return \"No packages from known package managers found\";\n\n    return NULL;\n}\n\nbool ffPackagesReadCache(FFstrbuf* cacheDir, FFstrbuf* cacheContent, const char* filePath, const char* packageId, uint32_t* result)\n{\n    #ifndef _WIN32\n    struct stat st;\n    if (stat(filePath, &st) < 0) // file doesn't exist or isn't accessible\n    {\n        *result = 0;\n        return true;\n    }\n\n    if (__builtin_expect(st.st_mtim.tv_sec <= 0, false))\n        return false;\n\n    uint64_t mtime_current = (uint64_t) st.st_mtim.tv_sec * 1000ull + (uint64_t) st.st_mtim.tv_nsec / 1000000ull;\n    #else\n    FF_AUTO_CLOSE_FD HANDLE handle = CreateFileA(filePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);\n\n    if (handle == INVALID_HANDLE_VALUE) // file doesn't exist or isn't accessible\n    {\n        *result = 0;\n        return true;\n    }\n\n    uint64_t mtime_current;\n    FILE_BASIC_INFORMATION fileInfo;\n    IO_STATUS_BLOCK iosb;\n    if (!NT_SUCCESS(NtQueryInformationFile(handle, &iosb, &fileInfo, sizeof(fileInfo), FileBasicInformation)))\n        return false;\n\n    mtime_current = ffFileTimeToUnixMs((uint64_t) fileInfo.LastWriteTime.QuadPart);\n    #endif\n\n    ffStrbufSet(cacheDir, &instance.state.platform.cacheDir);\n    ffStrbufEnsureEndsWithC(cacheDir, '/');\n    ffStrbufAppendF(cacheDir, \"fastfetch/packages/%s.txt\", packageId);\n\n    if (ffReadFileBuffer(cacheDir->chars, cacheContent))\n    {\n        uint64_t mtime_cached;\n        uint32_t num_cached;\n        if (sscanf(cacheContent->chars, \"%\" SCNu64 \" %\" SCNu32, &mtime_cached, &num_cached) == 2 &&\n            mtime_cached == mtime_current && num_cached > 0)\n        {\n            *result = num_cached;\n            return true;\n        }\n    }\n\n    ffStrbufSetF(cacheContent, \"%\" PRIu64 \" \", mtime_current);\n\n    return false;\n}\n\nbool ffPackagesWriteCache(FFstrbuf* cacheDir, FFstrbuf* cacheContent, uint32_t num_elements)\n{\n    if (__builtin_expect(cacheContent->length == 0, false))\n        return false;\n\n    ffStrbufAppendF(cacheContent, \"%\" PRIu32, num_elements);\n    return ffWriteFileBuffer(cacheDir->chars, cacheContent);\n}\n\n#ifndef _WIN32\nuint32_t ffPackagesGetNumElements(const char* dirname, bool isdir)\n{\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(dirname);\n    if(dirp == NULL)\n        return 0;\n\n    uint32_t num_elements = 0;\n\n    struct dirent *entry;\n    while((entry = readdir(dirp)) != NULL)\n    {\n        bool ok = false;\n\n        if (entry->d_name[0] != '.')\n        {\n#if !defined(__sun) && !defined(__HAIKU__)\n            if(entry->d_type != DT_UNKNOWN && entry->d_type != DT_LNK)\n                ok = entry->d_type == (isdir ? DT_DIR : DT_REG);\n            else\n#endif\n            {\n                struct stat stbuf;\n                if (fstatat(dirfd(dirp), entry->d_name, &stbuf, 0) == 0)\n                    ok = isdir ? S_ISDIR(stbuf.st_mode) : S_ISREG(stbuf.st_mode);\n            }\n        }\n\n        if(ok) ++num_elements;\n    }\n\n\n    return num_elements;\n}\n#endif\n"
  },
  {
    "path": "src/detection/packages/packages.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/packages/option.h\"\n\ntypedef struct FFPackagesResult\n{\n    uint32_t amSystem;\n    uint32_t amUser;\n    uint32_t apk;\n    uint32_t brew;\n    uint32_t brewCask;\n    uint32_t choco;\n    uint32_t dpkg;\n    uint32_t emerge;\n    uint32_t eopkg;\n    uint32_t flatpakSystem;\n    uint32_t flatpakUser;\n    uint32_t guixHome;\n    uint32_t guixSystem;\n    uint32_t guixUser;\n    uint32_t hpkgSystem;\n    uint32_t hpkgUser;\n    uint32_t kiss;\n    uint32_t linglong;\n    uint32_t lpkg;\n    uint32_t lpkgbuild;\n    uint32_t macports;\n    uint32_t mport;\n    uint32_t moss;\n    uint32_t nixDefault;\n    uint32_t nixSystem;\n    uint32_t nixUser;\n    uint32_t opkg;\n    uint32_t pacman;\n    uint32_t pacstall;\n    uint32_t paludis;\n    uint32_t pisi;\n    uint32_t pkg;\n    uint32_t pkgsrc;\n    uint32_t pkgtool;\n    uint32_t rpm;\n    uint32_t scoopUser;\n    uint32_t scoopGlobal;\n    uint32_t snap;\n    uint32_t soar;\n    uint32_t sorcery;\n    uint32_t winget;\n    uint32_t xbps;\n\n    uint32_t all; //Make sure this goes last\n\n    FFstrbuf pacmanBranch;\n} FFPackagesResult;\n\nconst char* ffDetectPackages(FFPackagesResult* result, FFPackagesOptions* options);\nbool ffPackagesReadCache(FFstrbuf* cacheDir, FFstrbuf* cacheContent, const char* filePath, const char* packageId, uint32_t* result);\nbool ffPackagesWriteCache(FFstrbuf* cacheDir, FFstrbuf* cacheContent, uint32_t num_elements);\n\n#if defined(__linux__) || defined(__APPLE__) || defined(__GNU__)\nuint32_t ffPackagesGetNix(FFstrbuf* baseDir, const char* dirname);\n#endif\n#ifndef _WIN32\nuint32_t ffPackagesGetNumElements(const char* dirname, bool isdir);\n#endif\n"
  },
  {
    "path": "src/detection/packages/packages_apple.c",
    "content": "#include \"packages.h\"\n#include \"common/io.h\"\n#include \"common/parsing.h\"\n#include \"common/processing.h\"\n#include \"common/stringUtils.h\"\n\nstatic void countBrewPackages(FFstrbuf* baseDir, FFPackagesResult* result)\n{\n\n    uint32_t baseDirLength = baseDir->length;\n\n    ffStrbufAppendS(baseDir, \"/Caskroom\");\n    result->brewCask += ffPackagesGetNumElements(baseDir->chars, true);\n    ffStrbufSubstrBefore(baseDir, baseDirLength);\n\n    ffStrbufAppendS(baseDir, \"/Cellar\");\n    result->brew += ffPackagesGetNumElements(baseDir->chars, true);\n    ffStrbufSubstrBefore(baseDir, baseDirLength);\n}\n\nstatic uint32_t getMacPortsPackages(FFstrbuf* baseDir)\n{\n    ffStrbufAppendS(baseDir, \"/var/macports/software\");\n    return ffPackagesGetNumElements(baseDir->chars, true);\n}\n\nvoid ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options)\n{\n    FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreate();\n    if (!(options->disabled & FF_PACKAGES_FLAG_BREW_BIT))\n    {\n        const char* prefix = getenv(\"HOMEBREW_PREFIX\");\n        if (ffStrSet(prefix))\n        {\n            ffStrbufSetS(&baseDir, prefix);\n        }\n        else\n        {\n            #ifdef __aarch64__\n            ffStrbufSetS(&baseDir, FASTFETCH_TARGET_DIR_ROOT \"/opt/homebrew\");\n            #else\n            ffStrbufSetS(&baseDir, FASTFETCH_TARGET_DIR_USR \"/local\");\n            #endif\n        }\n        countBrewPackages(&baseDir, result);\n    }\n    if (!(options->disabled & FF_PACKAGES_FLAG_MACPORTS_BIT))\n    {\n        const char* prefix = getenv(\"MACPORTS_PREFIX\");\n        if (ffStrSet(prefix))\n        {\n            ffStrbufSetS(&baseDir, prefix);\n        }\n        else\n        {\n            ffStrbufSetS(&baseDir, FASTFETCH_TARGET_DIR_ROOT \"/opt/local\");\n        }\n\n        result->macports = getMacPortsPackages(&baseDir);\n    }\n    if (!(options->disabled & FF_PACKAGES_FLAG_NIX_BIT))\n    {\n        ffStrbufSetS(&baseDir, FASTFETCH_TARGET_DIR_ROOT);\n        result->nixDefault += ffPackagesGetNix(&baseDir, \"/nix/var/nix/profiles/default\");\n        result->nixSystem += ffPackagesGetNix(&baseDir, \"/run/current-system\");\n        ffStrbufSet(&baseDir, &instance.state.platform.homeDir);\n        result->nixUser = ffPackagesGetNix(&baseDir, \"/.nix-profile\");\n    }\n}\n"
  },
  {
    "path": "src/detection/packages/packages_bsd.c",
    "content": "#include \"packages.h\"\n\n#include \"common/settings.h\"\n\nstatic uint32_t getSQLite3Int(const char* dbPath, const char* query, const char* packageId)\n{\n    FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate();\n\n    uint32_t num_elements;\n    if (ffPackagesReadCache(&cacheDir, &cacheContent, dbPath, packageId, &num_elements))\n        return num_elements;\n\n    num_elements = (uint32_t) ffSettingsGetSQLite3Int(dbPath, query);\n\n    ffPackagesWriteCache(&cacheDir, &cacheContent, num_elements);\n\n    return num_elements;\n}\n\nvoid ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options)\n{\n    if (!(options->disabled & FF_PACKAGES_FLAG_PKG_BIT))\n        result->pkg = getSQLite3Int(FASTFETCH_TARGET_DIR_ROOT \"/var/db/pkg/local.sqlite\", \"SELECT count(*) FROM packages\", \"pkg\");\n    if (!(options->disabled & FF_PACKAGES_FLAG_MPORT_BIT))\n        result->mport = getSQLite3Int(FASTFETCH_TARGET_DIR_ROOT \"/var/db/mport/master.db\", \"SELECT count(*) FROM packages\", \"mport\");\n}\n"
  },
  {
    "path": "src/detection/packages/packages_haiku.c",
    "content": "#include \"packages.h\"\n\n#include \"common/io.h\"\n\nvoid ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options)\n{\n    // TODO: Use the Package Kit C++ API instead (would account for disabled packages)\n\n    if (!(options->disabled & FF_PACKAGES_FLAG_HPKG_BIT))\n    {\n        result->hpkgSystem = ffPackagesGetNumElements(FASTFETCH_TARGET_DIR_ROOT \"/system/packages\", false);\n        result->hpkgUser = ffPackagesGetNumElements(FASTFETCH_TARGET_DIR_ROOT \"/boot/home/config/packages\", false);\n    }\n}\n"
  },
  {
    "path": "src/detection/packages/packages_linux.c",
    "content": "#include \"packages.h\"\n#include \"common/io.h\"\n#include \"common/parsing.h\"\n#include \"common/properties.h\"\n#include \"common/settings.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/os/os.h\"\n\nstatic uint32_t getNumElements(FFstrbuf* baseDir, const char* dirname, bool isdir)\n{\n    uint32_t baseDirLength = baseDir->length;\n    ffStrbufAppendS(baseDir, dirname);\n    uint32_t num_elements = ffPackagesGetNumElements(baseDir->chars, isdir);\n    ffStrbufSubstrBefore(baseDir, baseDirLength);\n    return num_elements;\n}\n\nstatic uint32_t getNumStringsImpl(const char* filename, const char* needle)\n{\n    FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate();\n    if (!ffReadFileBuffer(filename, &content))\n        return 0;\n\n    uint32_t count = 0;\n    char *iter = content.chars;\n    size_t needleLength = strlen(needle);\n    while ((iter = memmem(iter, content.length - (size_t)(iter - content.chars), needle, needleLength)) != NULL)\n    {\n        ++count;\n        iter += needleLength;\n    }\n\n    return count;\n}\n\nstatic uint32_t getNumStrings(FFstrbuf* baseDir, const char* filename, const char* needle, const char* packageId)\n{\n    uint32_t baseDirLength = baseDir->length;\n    ffStrbufAppendS(baseDir, filename);\n\n    FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate();\n\n    uint32_t num_elements;\n    if (ffPackagesReadCache(&cacheDir, &cacheContent, baseDir->chars, packageId, &num_elements))\n    {\n        ffStrbufSubstrBefore(baseDir, baseDirLength);\n        return num_elements;\n    }\n\n    num_elements = getNumStringsImpl(baseDir->chars, needle);\n    ffStrbufSubstrBefore(baseDir, baseDirLength);\n\n    ffPackagesWriteCache(&cacheDir, &cacheContent, num_elements);\n\n    return num_elements;\n}\n\nstatic uint32_t getSQLite3Int(FFstrbuf* baseDir, const char* dbPath, const char* query, const char* packageId)\n{\n    uint32_t baseDirLength = baseDir->length;\n    ffStrbufAppendS(baseDir, dbPath);\n\n    FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate();\n\n    uint32_t num_elements;\n    if (ffPackagesReadCache(&cacheDir, &cacheContent, baseDir->chars, packageId, &num_elements))\n    {\n        ffStrbufSubstrBefore(baseDir, baseDirLength);\n        return num_elements;\n    }\n\n    num_elements = (uint32_t) ffSettingsGetSQLite3Int(baseDir->chars, query);\n    ffStrbufSubstrBefore(baseDir, baseDirLength);\n\n    ffPackagesWriteCache(&cacheDir, &cacheContent, num_elements);\n\n    return num_elements;\n}\n\nstatic uint32_t countFilesRecursiveImpl(FFstrbuf* baseDirPath, const char* filename)\n{\n    uint32_t baseDirPathLength = baseDirPath->length;\n\n    ffStrbufAppendC(baseDirPath, '/');\n    ffStrbufAppendS(baseDirPath, filename);\n    bool exists = ffPathExists(baseDirPath->chars, FF_PATHTYPE_FILE);\n    ffStrbufSubstrBefore(baseDirPath, baseDirPathLength);\n    if(exists)\n        return 1;\n\n    DIR* dirp = opendir(baseDirPath->chars);\n    if(dirp == NULL)\n        return 0;\n\n    ffStrbufAppendC(baseDirPath, '/');\n    baseDirPathLength = baseDirPath->length;\n\n    uint32_t sum = 0;\n\n    struct dirent *entry;\n    while((entry = readdir(dirp)) != NULL) {\n        // According to the PMS, neither category nor package name can begin with '.', so no need to check for . or .. specifically\n        if(entry->d_type != DT_DIR || entry->d_name[0] == '.')\n            continue;\n\n        ffStrbufAppendS(baseDirPath, entry->d_name);\n        sum += countFilesRecursiveImpl(baseDirPath, filename);\n        ffStrbufSubstrBefore(baseDirPath, baseDirPathLength);\n    }\n\n    closedir(dirp);\n    return sum;\n}\n\nstatic uint32_t countFilesRecursive(FFstrbuf* baseDir, const char* dirname, const char* filename)\n{\n    uint32_t baseDirLength = baseDir->length;\n    ffStrbufAppendS(baseDir, dirname);\n    uint32_t sum = countFilesRecursiveImpl(baseDir, filename);\n    ffStrbufSubstrBefore(baseDir, baseDirLength);\n    return sum;\n}\n\nstatic uint32_t getXBPSImpl(FFstrbuf* baseDir)\n{\n    DIR* dir = opendir(baseDir->chars);\n    if(dir == NULL)\n        return 0;\n\n    uint32_t result = 0;\n\n    struct dirent *entry;\n    while((entry = readdir(dir)) != NULL)\n    {\n        if(entry->d_type != DT_REG || !ffStrStartsWithIgnCase(entry->d_name, \"pkgdb-\"))\n            continue;\n\n        ffStrbufAppendC(baseDir, '/');\n        ffStrbufAppendS(baseDir, entry->d_name);\n        result = getNumStringsImpl(baseDir->chars, \"<string>installed</string>\");\n        break;\n    }\n\n    closedir(dir);\n    return result;\n}\n\nstatic uint32_t getXBPS(FFstrbuf* baseDir, const char* dirname)\n{\n    uint32_t baseDirLength = baseDir->length;\n    ffStrbufAppendS(baseDir, dirname);\n    uint32_t result = getXBPSImpl(baseDir);\n    ffStrbufSubstrBefore(baseDir, baseDirLength);\n    return result;\n}\n\nstatic uint32_t getSnap(FFstrbuf* baseDir)\n{\n    uint32_t result = getNumElements(baseDir, \"/snap\", true);\n\n    if (result == 0)\n        result = getNumElements(baseDir, \"/var/lib/snapd/snap\", true);\n\n    //Accounting for the /snap/bin folder\n    return result > 0 ? result - 1 : 0;\n}\n\n#ifdef FF_HAVE_RPM\n#include \"common/library.h\"\n#include <rpm/rpmlib.h>\n#include <rpm/rpmts.h>\n#include <rpm/rpmdb.h>\n#include <rpm/rpmlog.h>\n\nstatic uint32_t getRpmFromLibrpm(void)\n{\n    FF_LIBRARY_LOAD(rpm, 0, \"librpm\" FF_LIBRARY_EXTENSION, 12)\n    FF_LIBRARY_LOAD_SYMBOL(rpm, rpmReadConfigFiles, 0)\n    FF_LIBRARY_LOAD_SYMBOL(rpm, rpmtsCreate, 0)\n    FF_LIBRARY_LOAD_SYMBOL(rpm, rpmtsInitIterator, 0)\n    FF_LIBRARY_LOAD_SYMBOL(rpm, rpmdbGetIteratorCount, 0)\n    FF_LIBRARY_LOAD_SYMBOL(rpm, rpmdbFreeIterator, 0)\n    FF_LIBRARY_LOAD_SYMBOL(rpm, rpmtsFree, 0)\n    FF_LIBRARY_LOAD_SYMBOL(rpm, rpmlogSetMask, 0)\n\n    // Don't print any error messages\n    ffrpmlogSetMask(RPMLOG_MASK(RPMLOG_EMERG));\n\n    if(ffrpmReadConfigFiles(NULL, NULL) != 0)\n        return 0;\n\n    rpmts ts = ffrpmtsCreate();\n    if(ts == NULL)\n        return 0;\n\n    rpmdbMatchIterator mi = ffrpmtsInitIterator(ts, RPMDBI_LABEL, NULL, 0);\n    if(mi == NULL)\n    {\n        ffrpmtsFree(ts);\n        return 0;\n    }\n\n    int count = ffrpmdbGetIteratorCount(mi);\n\n    ffrpmdbFreeIterator(mi);\n    ffrpmtsFree(ts);\n\n    return count > 0 ? (uint32_t) count : 0;\n}\n\n#endif //FF_HAVE_RPM\n\nstatic uint32_t getAMPackages(FFstrbuf* baseDir)\n{\n    uint32_t baseLength = baseDir->length;\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars);\n    if (!dirp) return 0;\n\n    uint32_t result = 0;\n    struct dirent *entry;\n    while ((entry = readdir(dirp)) != NULL)\n    {\n        if (entry->d_name[0] == '.') continue;\n        if (entry->d_type == DT_DIR)\n        {\n            ffStrbufAppendF(baseDir, \"/%s/remove\", entry->d_name);\n            if (ffPathExists(baseDir->chars, FF_PATHTYPE_FILE))\n                ++result;\n            ffStrbufSubstrBefore(baseDir, baseLength);\n        }\n    }\n    return result;\n}\n\nstatic uint32_t getAMSystem(FFstrbuf* baseDir)\n{\n    // #771\n    uint32_t baseDirLength = baseDir->length;\n\n    ffStrbufAppendS(baseDir, \"/opt\");\n    uint32_t optDirLength = baseDir->length;\n\n    uint32_t result = 0;\n\n    ffStrbufAppendS(baseDir, \"/am/APP-MANAGER\");\n    if (ffPathExists(baseDir->chars, FF_PATHTYPE_FILE))\n    {\n        ++result; // `am` itself is counted as a package too\n        ffStrbufSubstrBefore(baseDir, optDirLength);\n        result = getAMPackages(baseDir);\n    }\n\n    ffStrbufSubstrBefore(baseDir, baseDirLength);\n    return result;\n}\n\nstatic uint32_t getAMUser(void)\n{\n    if (instance.state.platform.configDirs.length == 0) return 0;\n\n    // check if $XDG_CONFIG_HOME/appman/appman-config exists\n    FFstrbuf* baseDir = FF_LIST_FIRST(FFstrbuf, instance.state.platform.configDirs);\n    uint32_t baseLen = baseDir->length;\n    ffStrbufAppendS(baseDir, \"appman/appman-config\");\n    FF_STRBUF_AUTO_DESTROY packagesPath = ffStrbufCreate();\n    if (ffReadFileBuffer(baseDir->chars, &packagesPath))\n        ffStrbufTrimRightSpace(&packagesPath);\n    ffStrbufSubstrBefore(baseDir, baseLen);\n\n    return packagesPath.length > 0 ? getAMPackages(&packagesPath) : 0;\n}\n\nstatic int compareHash(const void* a, const void* b)\n{\n    return memcmp(a, b, 32);\n}\n\nstatic uint32_t getGuixPackagesImpl(char* filename)\n{\n    FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate();\n    if (!ffAppendFileBuffer(filename, &content))\n        return 0;\n\n    // Count number of unique /gnu/store/ paths in PROFILE/manifest based on their hash value.\n    // Contains packages explicitly installed and their propagated inputs.\n    char* pend = content.chars;\n\n    for (const char* pattern = content.chars; (pattern = strstr(pattern, \"/gnu/store/\")); pattern += 32)\n    {\n        pattern += strlen(\"/gnu/store/\");\n        memmove(pend, pattern, 32);\n        pend += 32;\n    }\n\n    if (pend == content.chars)\n        return 0;\n\n    qsort(content.chars, (size_t) (pend - content.chars) / 32, 32, compareHash);\n\n    uint32_t count = 1;\n    for (const char* p = content.chars + 32; p < pend; p += 32)\n        count += compareHash(p - 32, p) != 0;\n\n    return count;\n}\n\nstatic uint32_t getGuixPackages(FFstrbuf* baseDir, const char* dirname)\n{\n    uint32_t baseDirLength = baseDir->length;\n    ffStrbufAppendS(baseDir, dirname);\n    ffStrbufAppendS(baseDir, \"/manifest\");\n    uint32_t num_elements = getGuixPackagesImpl(baseDir->chars);\n    ffStrbufSubstrBefore(baseDir, baseDirLength);\n    return num_elements;\n}\n\nstatic inline uint32_t getFlatpakRuntimePackagesArch(FFstrbuf* baseDir)\n{\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars);\n    if (dirp == NULL)\n        return 0;\n\n    uint32_t num_elements = 0;\n\n    struct dirent *entry;\n    while ((entry = readdir(dirp)) != NULL)\n    {\n        if(entry->d_type == DT_DIR && entry->d_name[0] != '.')\n        {\n            num_elements += getNumElements(baseDir, entry->d_name, true);\n        }\n    }\n\n    return num_elements;\n}\n\nstatic inline uint32_t getFlatpakRuntimePackages(FFstrbuf* baseDir)\n{\n    ffStrbufAppendS(baseDir, \"runtime/\");\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars);\n    if (dirp == NULL)\n        return 0;\n\n    uint32_t runtimeDirLength = baseDir->length;\n    uint32_t num_elements = 0;\n\n    struct dirent *entry;\n    while ((entry = readdir(dirp)) != NULL)\n    {\n        if(entry->d_type == DT_DIR && entry->d_name[0] != '.')\n        {\n            // `flatpak list` ignores `.Locale` and `.Debug` packages, and maybe others\n            const char* dot = strrchr(entry->d_name, '.');\n            if (__builtin_expect(!dot, false)) continue;\n            dot++;\n\n            if (ffStrEquals(dot, \"Locale\") || ffStrEquals(dot, \"Debug\"))\n                continue;\n\n            ffStrbufAppendS(baseDir, entry->d_name);\n            ffStrbufAppendC(baseDir, '/');\n            num_elements += getFlatpakRuntimePackagesArch(baseDir);\n            ffStrbufSubstrBefore(baseDir, runtimeDirLength);\n        }\n    }\n\n    return num_elements;\n}\n\nstatic inline uint32_t getFlatpakAppPackages(FFstrbuf* baseDir)\n{\n    ffStrbufAppendS(baseDir, \"app/\");\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars);\n    if (dirp == NULL)\n        return 0;\n\n    uint32_t appDirLength = baseDir->length;\n    uint32_t num_elements = 0;\n\n    struct dirent *entry;\n    while ((entry = readdir(dirp)) != NULL)\n    {\n        if(entry->d_type == DT_DIR && entry->d_name[0] != '.')\n        {\n            ffStrbufAppendS(baseDir, entry->d_name);\n            ffStrbufAppendS(baseDir, \"/current\");\n            if (ffPathExists(baseDir->chars, FF_PATHTYPE_ANY)) // Exclude deleted apps, #1856\n                ++num_elements;\n            ffStrbufSubstrBefore(baseDir, appDirLength);\n        }\n    }\n    return num_elements;\n}\n\nstatic uint32_t getFlatpakPackages(FFstrbuf* baseDir, const char* dirname)\n{\n    uint32_t num_elements = 0;\n    uint32_t baseDirLength = baseDir->length;\n    ffStrbufAppendS(baseDir, dirname);\n    ffStrbufAppendS(baseDir, \"/flatpak/\");\n    uint32_t flatpakDirLength = baseDir->length;\n\n    num_elements += getFlatpakAppPackages(baseDir);\n    ffStrbufSubstrBefore(baseDir, flatpakDirLength);\n\n    num_elements += getFlatpakRuntimePackages(baseDir);\n\n    ffStrbufSubstrBefore(baseDir, baseDirLength);\n\n    return num_elements;\n}\n\nstatic uint32_t getPacmanPackages(FFstrbuf* baseDir)\n{\n    FF_STRBUF_AUTO_DESTROY dbPath = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY rootDir = ffStrbufCreate();\n\n    // Get path to pacman.conf\n    uint32_t baseDirLen = baseDir->length;\n    ffStrbufAppendS(baseDir, \"/etc/pacman.conf\");\n\n    bool confFound = ffParsePropFileValues(baseDir->chars, 2, (FFpropquery[]){\n        { \"DBPath =\", &dbPath },\n        { \"RootDir =\", &rootDir },\n    });\n    ffStrbufSubstrBefore(baseDir, baseDirLen);\n\n    if (confFound)\n    {\n        if (dbPath.length > 0)\n        {\n            // If DBPath is specified, use it\n            ffStrbufEnsureEndsWithC(&dbPath, '/');\n            ffStrbufAppendS(&dbPath, \"local\");\n        }\n        else if (rootDir.length > 0)\n        {\n            // ... otherwise, use RootDir\n            ffStrbufDestroy(&dbPath);\n            ffStrbufInitMove(&dbPath, &rootDir);\n            ffStrbufEnsureEndsWithC(&dbPath, '/');\n            ffStrbufAppendS(&dbPath, \"var/lib/pacman/local\");\n        }\n    }\n\n    if (dbPath.length == 0)\n        ffStrbufSetStatic(&dbPath, \"/var/lib/pacman/local\");\n\n    return getNumElements(baseDir, dbPath.chars, true);\n}\n\nstatic void getPackageCounts(FFstrbuf* baseDir, FFPackagesResult* packageCounts, FFPackagesOptions* options)\n{\n    if (!(options->disabled & FF_PACKAGES_FLAG_APK_BIT)) packageCounts->apk += getNumStrings(baseDir, \"/lib/apk/db/installed\", \"C:Q\", \"apk\");\n    if (!(options->disabled & FF_PACKAGES_FLAG_DPKG_BIT)) packageCounts->dpkg += getNumStrings(baseDir, \"/var/lib/dpkg/status\", \"Status: install ok installed\", \"dpkg\");\n    if (!(options->disabled & FF_PACKAGES_FLAG_LPKG_BIT)) packageCounts->lpkg += getNumStrings(baseDir, \"/opt/Loc-OS-LPKG/installed-lpkg/Listinstalled-lpkg.list\", \"\\n\", \"lpkg\");\n    if (!(options->disabled & FF_PACKAGES_FLAG_EMERGE_BIT)) packageCounts->emerge += countFilesRecursive(baseDir, \"/var/db/pkg\", \"SIZE\");\n    if (!(options->disabled & FF_PACKAGES_FLAG_EOPKG_BIT)) packageCounts->eopkg += getNumElements(baseDir, \"/var/lib/eopkg/package\", true);\n    if (!(options->disabled & FF_PACKAGES_FLAG_FLATPAK_BIT)) packageCounts->flatpakSystem += getFlatpakPackages(baseDir, \"/var/lib\");\n    if (!(options->disabled & FF_PACKAGES_FLAG_KISS_BIT)) packageCounts->kiss += getNumElements(baseDir, \"/var/db/kiss/installed\", true);\n    if (!(options->disabled & FF_PACKAGES_FLAG_NIX_BIT))\n    {\n        packageCounts->nixDefault += ffPackagesGetNix(baseDir, \"/nix/var/nix/profiles/default\");\n        packageCounts->nixSystem += ffPackagesGetNix(baseDir, \"/run/current-system\");\n    }\n    if (!(options->disabled & FF_PACKAGES_FLAG_PACMAN_BIT)) packageCounts->pacman += getPacmanPackages(baseDir);\n    if (!(options->disabled & FF_PACKAGES_FLAG_LPKGBUILD_BIT)) packageCounts->lpkgbuild += getNumElements(baseDir, \"/opt/Loc-OS-LPKG/lpkgbuild/remove\", false);\n    if (!(options->disabled & FF_PACKAGES_FLAG_PKGTOOL_BIT)) packageCounts->pkgtool += getNumElements(baseDir, \"/var/log/packages\", false);\n    if (!(options->disabled & FF_PACKAGES_FLAG_RPM_BIT))\n    {\n        // `Sigmd5` is the only table that doesn't contain the virtual `gpg-pubkey` package\n        packageCounts->rpm += getSQLite3Int(baseDir, \"/var/lib/rpm/rpmdb.sqlite\", \"SELECT count(*) FROM Sigmd5\", \"rpm\");\n    }\n    if (!(options->disabled & FF_PACKAGES_FLAG_SNAP_BIT)) packageCounts->snap += getSnap(baseDir);\n    if (!(options->disabled & FF_PACKAGES_FLAG_XBPS_BIT)) packageCounts->xbps += getXBPS(baseDir, \"/var/db/xbps\");\n    if (!(options->disabled & FF_PACKAGES_FLAG_BREW_BIT))\n    {\n        packageCounts->brewCask += getNumElements(baseDir, \"/home/linuxbrew/.linuxbrew/Caskroom\", true);\n        packageCounts->brew += getNumElements(baseDir, \"/home/linuxbrew/.linuxbrew/Cellar\", true);\n    }\n    if (!(options->disabled & FF_PACKAGES_FLAG_PALUDIS_BIT)) packageCounts->paludis += countFilesRecursive(baseDir, \"/var/db/paludis/repositories\", \"environment.bz2\");\n    if (!(options->disabled & FF_PACKAGES_FLAG_OPKG_BIT)) packageCounts->opkg += getNumStrings(baseDir, \"/usr/lib/opkg/status\", \"Package:\", \"opkg\"); // openwrt\n    if (!(options->disabled & FF_PACKAGES_FLAG_AM_BIT)) packageCounts->amSystem = getAMSystem(baseDir);\n    if (!(options->disabled & FF_PACKAGES_FLAG_SORCERY_BIT)) packageCounts->sorcery += getNumStrings(baseDir, \"/var/state/sorcery/packages\", \":installed:\", \"sorcery\");\n    if (!(options->disabled & FF_PACKAGES_FLAG_GUIX_BIT))\n    {\n      packageCounts->guixSystem += getGuixPackages(baseDir, \"/run/current-system/profile\");\n    }\n    if (!(options->disabled & FF_PACKAGES_FLAG_LINGLONG_BIT)) packageCounts->linglong += getNumElements(baseDir, \"/var/lib/linglong/layers\", true);\n    if (!(options->disabled & FF_PACKAGES_FLAG_PACSTALL_BIT)) packageCounts->pacstall += getNumElements(baseDir, \"/var/lib/pacstall/metadata\", false);\n    if (!(options->disabled & FF_PACKAGES_FLAG_PISI_BIT)) packageCounts->pisi += getNumElements(baseDir, \"/var/lib/pisi/package\", true);\n    if (!(options->disabled & FF_PACKAGES_FLAG_PKGSRC_BIT)) packageCounts->pkgsrc += getNumElements(baseDir, \"/usr/pkg/pkgdb\", DT_DIR);\n    if (!(options->disabled & FF_PACKAGES_FLAG_MOSS_BIT)) packageCounts->moss += getSQLite3Int(baseDir, \"/.moss/db/state\", \"SELECT COUNT(*) FROM state_selections WHERE state_id = (SELECT MAX(id) FROM state)\", \"moss\");\n}\n\nstatic void getPackageCountsRegular(FFstrbuf* baseDir, FFPackagesResult* packageCounts, FFPackagesOptions* options)\n{\n    getPackageCounts(baseDir, packageCounts, options);\n\n    if (!(options->disabled & FF_PACKAGES_FLAG_PACMAN_BIT))\n    {\n        uint32_t baseDirLength = baseDir->length;\n        ffStrbufAppendS(baseDir, FASTFETCH_TARGET_DIR_ETC \"/pacman-mirrors.conf\");\n        if(ffParsePropFile(baseDir->chars, \"Branch =\", &packageCounts->pacmanBranch) && packageCounts->pacmanBranch.length == 0)\n            ffStrbufAppendS(&packageCounts->pacmanBranch, \"stable\");\n        ffStrbufSubstrBefore(baseDir, baseDirLength);\n    }\n}\n\nstatic void getPackageCountsBedrock(FFstrbuf* baseDir, FFPackagesResult* packageCounts, FFPackagesOptions* options)\n{\n    uint32_t baseDirLength = baseDir->length;\n\n    ffStrbufAppendS(baseDir, \"/bedrock/strata\");\n\n    FF_AUTO_CLOSE_DIR DIR* dir = opendir(baseDir->chars);\n    if(dir == NULL)\n    {\n        ffStrbufSubstrBefore(baseDir, baseDirLength);\n        return;\n    }\n\n    ffStrbufAppendC(baseDir, '/');\n    uint32_t baseDirLength2 = baseDir->length;\n\n    struct dirent* entry;\n    while((entry = readdir(dir)) != NULL)\n    {\n        if(entry->d_type != DT_DIR)\n            continue;\n        if(entry->d_name[0] == '.')\n            continue;\n\n        ffStrbufAppendS(baseDir, entry->d_name);\n        getPackageCounts(baseDir, packageCounts, options);\n        ffStrbufSubstrBefore(baseDir, baseDirLength2);\n    }\n\n    ffStrbufSubstrBefore(baseDir, baseDirLength);\n}\n\nvoid ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options)\n{\n    FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreateA(512);\n    ffStrbufAppendS(&baseDir, FASTFETCH_TARGET_DIR_ROOT);\n\n    if(ffStrbufIgnCaseEqualS(&ffDetectOS()->id, \"bedrock\"))\n        getPackageCountsBedrock(&baseDir, result, options);\n    else\n        getPackageCountsRegular(&baseDir, result, options);\n\n    // If SQL failed, we can still try with librpm.\n    // This is needed on openSUSE, which seems to use a proprietary database file\n    // This method doesn't work on bedrock, so we do it here.\n    #ifdef FF_HAVE_RPM\n        if(!(options->disabled & FF_PACKAGES_FLAG_RPM_BIT) && result->rpm == 0)\n            result->rpm = getRpmFromLibrpm();\n    #endif\n\n    ffStrbufSet(&baseDir, &instance.state.platform.homeDir);\n    if (!(options->disabled & FF_PACKAGES_FLAG_NIX_BIT))\n    {\n        // Count packages from $HOME/.nix-profile\n        result->nixUser += ffPackagesGetNix(&baseDir, \".nix-profile\");\n\n        // Check in $XDG_STATE_HOME/nix/profile\n        FF_STRBUF_AUTO_DESTROY stateHome = ffStrbufCreate();\n        const char* stateHomeEnv = getenv(\"XDG_STATE_HOME\");\n        if (ffStrSet(stateHomeEnv))\n        {\n            ffStrbufSetS(&stateHome, stateHomeEnv);\n            ffStrbufEnsureEndsWithC(&stateHome, '/');\n        }\n        else\n        {\n            ffStrbufSet(&stateHome, &instance.state.platform.homeDir);\n            ffStrbufAppendS(&stateHome, \".local/state/\");\n        }\n        result->nixUser += ffPackagesGetNix(&stateHome, \"nix/profile\");\n\n        // Check in /etc/profiles/per-user/$USER\n        FF_STRBUF_AUTO_DESTROY userPkgsDir = ffStrbufCreateStatic(\"/etc/profiles/per-user/\");\n        result->nixUser += ffPackagesGetNix(&userPkgsDir, instance.state.platform.userName.chars);\n    }\n\n    if (!(options->disabled & FF_PACKAGES_FLAG_GUIX_BIT))\n    {\n       result->guixUser += getGuixPackages(&baseDir, \".guix-profile\");\n       result->guixHome += getGuixPackages(&baseDir, \".guix-home/profile\");\n    }\n\n    if (!(options->disabled & FF_PACKAGES_FLAG_FLATPAK_BIT))\n        result->flatpakUser = getFlatpakPackages(&baseDir, \"/.local/share\");\n\n    if (!(options->disabled & FF_PACKAGES_FLAG_AM_BIT))\n        result->amUser = getAMUser();\n\n    if (!(options->disabled & FF_PACKAGES_FLAG_SOAR_BIT))\n        result->soar += getSQLite3Int(&baseDir, \".local/share/soar/db/soar.db\", \"SELECT COUNT(DISTINCT pkg_id || pkg_name) FROM packages WHERE is_installed = true\", \"soar\");\n}\n"
  },
  {
    "path": "src/detection/packages/packages_nbsd.c",
    "content": "#include \"packages.h\"\n\n#include \"common/io.h\"\n\nvoid ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options)\n{\n    if (!(options->disabled & FF_PACKAGES_FLAG_PKGSRC_BIT))\n        result->pkgsrc = ffPackagesGetNumElements(FASTFETCH_TARGET_DIR_ROOT \"/usr/pkg/pkgdb\", true);\n}\n"
  },
  {
    "path": "src/detection/packages/packages_nix.c",
    "content": "#include \"packages.h\"\n#include \"common/io.h\"\n#include \"common/processing.h\"\n#include \"common/stringUtils.h\"\n\nstatic bool isValidNixPkg(FFstrbuf* pkg)\n{\n    if (!ffPathExists(pkg->chars, FF_PATHTYPE_DIRECTORY))\n        return false;\n\n    ffStrbufSubstrAfterLastC(pkg, '/');\n    if (\n        ffStrbufStartsWithS(pkg, \"nixos-system-nixos-\") ||\n        ffStrbufEndsWithS(pkg, \"-doc\") ||\n        ffStrbufEndsWithS(pkg, \"-man\") ||\n        ffStrbufEndsWithS(pkg, \"-info\") ||\n        ffStrbufEndsWithS(pkg, \"-dev\") ||\n        ffStrbufEndsWithS(pkg, \"-bin\")\n    ) return false;\n\n    enum { START, DIGIT, DOT, MATCH } state = START;\n\n    for (uint32_t i = 0; i < pkg->length; i++)\n    {\n        char c = pkg->chars[i];\n        switch (state)\n        {\n            case START:\n                if (ffCharIsDigit(c))\n                    state = DIGIT;\n                break;\n            case DIGIT:\n                if (ffCharIsDigit(c))\n                    continue;\n                if (c == '.')\n                    state = DOT;\n                else\n                    state = START;\n                break;\n            case DOT:\n                if (ffCharIsDigit(c))\n                    state = MATCH;\n                else\n                    state = START;\n                break;\n            case MATCH:\n                break;\n        }\n    }\n\n    return state == MATCH;\n}\n\nstatic bool checkNixCache(FFstrbuf* cacheDir, FFstrbuf* hash, uint32_t* count)\n{\n    if (!ffPathExists(cacheDir->chars, FF_PATHTYPE_FILE))\n        return false;\n\n    FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate();\n    if (!ffReadFileBuffer(cacheDir->chars, &cacheContent))\n        return false;\n\n    // Format: <hash>\\n<count>\n    uint32_t split = ffStrbufFirstIndexC(&cacheContent, '\\n');\n    if (split == cacheContent.length)\n        return false;\n\n    ffStrbufSetNS(hash, split, cacheContent.chars);\n    *count = (uint32_t)atoi(cacheContent.chars + split + 1);\n\n    return true;\n}\n\nstatic bool writeNixCache(FFstrbuf* cacheDir, FFstrbuf* hash, uint32_t count)\n{\n    FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreateCopy(hash);\n    ffStrbufAppendF(&cacheContent, \"\\n%u\", count);\n    return ffWriteFileBuffer(cacheDir->chars, &cacheContent);\n}\n\nstatic uint32_t getNixPackagesImpl(char* path)\n{\n    //Nix detection is kinda slow, so we only do it if the dir exists\n    if(!ffPathExists(path, FF_PATHTYPE_DIRECTORY))\n        return 0;\n\n    FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreateCopy(&instance.state.platform.cacheDir);\n    ffStrbufEnsureEndsWithC(&cacheDir, '/');\n    ffStrbufAppendS(&cacheDir, \"fastfetch/packages/nix\");\n    ffStrbufAppendS(&cacheDir, path);\n\n    //Check the hash first to determine if we need to recompute the count\n    FF_STRBUF_AUTO_DESTROY hash = ffStrbufCreateA(64);\n    FF_STRBUF_AUTO_DESTROY cacheHash = ffStrbufCreateA(64);\n    uint32_t count = 0;\n\n    ffProcessAppendStdOut(&hash, (char* const[]) {\n        \"nix-store\",\n        \"--query\",\n        \"--hash\",\n        path,\n        NULL\n    });\n\n    if (checkNixCache(&cacheDir, &cacheHash, &count) && ffStrbufEqual(&hash, &cacheHash))\n        return count;\n\n    //Cache is invalid, recompute the count\n    count = 0;\n\n    //Implementation based on bash script from here:\n    //https://github.com/fastfetch-cli/fastfetch/issues/195#issuecomment-1191748222\n\n    FF_STRBUF_AUTO_DESTROY output = ffStrbufCreateA(1024);\n\n    ffProcessAppendStdOut(&output, (char* const[]) {\n        \"nix-store\",\n        \"--query\",\n        \"--requisites\",\n        path,\n        NULL\n    });\n\n    uint32_t lineLength = 0;\n    for (uint32_t i = 0; i < output.length; i++)\n    {\n        if (output.chars[i] != '\\n')\n        {\n            lineLength++;\n            continue;\n        }\n\n        output.chars[i] = '\\0';\n        FFstrbuf line = {\n            .allocated = 0,\n            .length = lineLength,\n            .chars = output.chars + i - lineLength\n        };\n        if (isValidNixPkg(&line))\n            count++;\n        lineLength = 0;\n    }\n\n    writeNixCache(&cacheDir, &hash, count);\n    return count;\n}\n\nuint32_t ffPackagesGetNix(FFstrbuf* baseDir, const char* dirname)\n{\n    uint32_t baseDirLength = baseDir->length;\n    ffStrbufAppendS(baseDir, dirname);\n    uint32_t num_elements = getNixPackagesImpl(baseDir->chars);\n    ffStrbufSubstrBefore(baseDir, baseDirLength);\n    return num_elements;\n}\n"
  },
  {
    "path": "src/detection/packages/packages_nosupport.c",
    "content": "#include \"packages.h\"\n\nvoid ffDetectPackagesImpl(FF_MAYBE_UNUSED FFPackagesResult* result, FF_MAYBE_UNUSED FFPackagesOptions* options)\n{\n}\n"
  },
  {
    "path": "src/detection/packages/packages_obsd.c",
    "content": "#include \"packages.h\"\n\n#include \"common/io.h\"\n\nvoid ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options)\n{\n    if (!(options->disabled & FF_PACKAGES_FLAG_PKG_BIT))\n        result->pkg = ffPackagesGetNumElements(FASTFETCH_TARGET_DIR_ROOT \"/var/db/pkg\", true);\n}\n"
  },
  {
    "path": "src/detection/packages/packages_sunos.c",
    "content": "#include \"packages.h\"\n#include <dirent.h>\n\nvoid ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options)\n{\n    if (!(options->disabled & FF_PACKAGES_FLAG_PKG_BIT))\n    {\n        yyjson_doc* doc = yyjson_read_file(FASTFETCH_TARGET_DIR_ROOT \"/var/pkg/state/installed/catalog.attrs\", YYJSON_READ_NOFLAG, NULL, NULL);\n        if (doc)\n        {\n            yyjson_val* packageCount = yyjson_obj_get(yyjson_doc_get_root(doc), \"package-count\");\n            if (packageCount)\n                result->pkg = (uint32_t) yyjson_get_uint(packageCount);\n        }\n    }\n    if (!(options->disabled & FF_PACKAGES_FLAG_PKGSRC_BIT))\n        result->pkgsrc = ffPackagesGetNumElements(FASTFETCH_TARGET_DIR_ROOT \"/usr/pkg/pkgdb\", true);\n}\n"
  },
  {
    "path": "src/detection/packages/packages_windows.c",
    "content": "#include \"packages.h\"\n#include \"common/processing.h\"\n#include \"common/stringUtils.h\"\n#include \"common/path.h\"\n#include \"common/windows/unicode.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/io.h\"\n\n#include <stdalign.h>\n#include <windows.h>\n#include \"common/windows/nt.h\"\n#include <ntstatus.h>\n#include <shlobj.h>\n\nstatic uint32_t getNumElements(const char* searchPath, DWORD type, const wchar_t* ignore)\n{\n    FF_AUTO_CLOSE_FD HANDLE dfd = CreateFileA(searchPath, FILE_LIST_DIRECTORY | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);\n    if (dfd == INVALID_HANDLE_VALUE) return 0;\n\n    bool flag = ignore == NULL;\n    uint32_t counter = 0;\n    alignas(8) uint8_t buffer[64 * 1024];\n    BOOLEAN firstScan = TRUE;\n\n    size_t ignoreLen = ignore ? wcslen(ignore) : 0;\n\n    while (true) {\n        IO_STATUS_BLOCK ioStatus = {};\n        NTSTATUS status = NtQueryDirectoryFile(\n            dfd,\n            NULL, NULL, NULL,\n            &ioStatus,\n            buffer, ARRAY_SIZE(buffer),\n            FileDirectoryInformation,\n            FALSE,\n            NULL,\n            firstScan\n        );\n        firstScan = FALSE;\n\n        if (!NT_SUCCESS(status) && status != STATUS_BUFFER_OVERFLOW) break;\n\n        for (FILE_DIRECTORY_INFORMATION* entry = (FILE_DIRECTORY_INFORMATION*) buffer;\n            ;\n            entry = (FILE_DIRECTORY_INFORMATION*) ((uint8_t*) entry + entry->NextEntryOffset))\n        {\n            if (!(entry->FileAttributes & type)) continue;\n\n            if (!flag &&\n                ignoreLen == entry->FileNameLength / sizeof(*entry->FileName) &&\n                _wcsnicmp(entry->FileName, ignore, ignoreLen) == 0)\n            {\n                flag = true;\n                continue;\n            }\n\n            counter++;\n\n            if (entry->NextEntryOffset == 0) break;\n        }\n\n        if (status == STATUS_SUCCESS) break; // No next page\n    }\n\n    if(type == FILE_ATTRIBUTE_DIRECTORY && counter >= 2)\n        counter -= 2; // accounting for . and ..\n\n    return counter;\n}\n\nstatic inline void wrapYyjsonFree(yyjson_doc** doc)\n{\n    assert(doc);\n    if (*doc)\n        yyjson_doc_free(*doc);\n}\n\nstatic void detectScoop(FFPackagesResult* result)\n{\n    FF_STRBUF_AUTO_DESTROY scoopPath = ffStrbufCreateA(MAX_PATH + 3);\n    ffStrbufAppend(&scoopPath, &instance.state.platform.homeDir);\n    ffStrbufAppendS(&scoopPath, \".config/scoop/config.json\");\n\n    yyjson_val* root = NULL;\n\n    yyjson_doc* __attribute__((__cleanup__(wrapYyjsonFree))) doc = yyjson_read_file(scoopPath.chars, 0, NULL, NULL);\n    if (doc)\n    {\n        root = yyjson_doc_get_root(doc);\n        if (!yyjson_is_obj(root)) root = NULL;\n    }\n\n    {\n        ffStrbufClear(&scoopPath);\n        if (root)\n            ffStrbufSetJsonVal(&scoopPath, yyjson_obj_get(root, \"root_path\"));\n        if (scoopPath.length == 0)\n        {\n            ffStrbufSet(&scoopPath, &instance.state.platform.homeDir);\n            ffStrbufAppendS(&scoopPath, \"/scoop\");\n        }\n        ffStrbufAppendS(&scoopPath, \"/apps/\");\n        result->scoopUser = getNumElements(scoopPath.chars, FILE_ATTRIBUTE_DIRECTORY, L\"scoop\");\n    }\n\n    {\n        ffStrbufClear(&scoopPath);\n        if (root)\n            ffStrbufSetJsonVal(&scoopPath, yyjson_obj_get(root, \"global_path\"));\n        if (scoopPath.length == 0)\n        {\n            PWSTR pPath = NULL;\n            if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_ProgramData, KF_FLAG_DEFAULT, NULL, &pPath)))\n            {\n                ffStrbufSetWS(&scoopPath, pPath);\n                CoTaskMemFree(pPath);\n            }\n            ffStrbufAppendS(&scoopPath, \"/scoop\");\n        }\n        ffStrbufAppendS(&scoopPath, \"/apps/\");\n        result->scoopGlobal = getNumElements(scoopPath.chars, FILE_ATTRIBUTE_DIRECTORY, L\"scoop\");\n    }\n}\n\nstatic void detectChoco(FF_MAYBE_UNUSED FFPackagesResult* result)\n{\n    const char* chocoInstall = getenv(\"ChocolateyInstall\");\n    if(!chocoInstall || chocoInstall[0] == '\\0')\n        return;\n\n    char chocoPath[MAX_PATH + 3];\n    char* pend = ffStrCopy(chocoPath, chocoInstall, ARRAY_SIZE(chocoPath));\n    ffStrCopy(pend, \"/lib/\", ARRAY_SIZE(chocoPath) - (size_t) (pend - chocoPath));\n    result->choco = getNumElements(chocoPath, FILE_ATTRIBUTE_DIRECTORY, L\"choco\");\n}\n\nstatic void detectPacman(FFPackagesResult* result)\n{\n    const char* msystemPrefix = getenv(\"MSYSTEM_PREFIX\");\n    if(!msystemPrefix)\n        return;\n\n    // MSYS2\n    char pacmanPath[MAX_PATH + 3];\n    char* pend = ffStrCopy(pacmanPath, msystemPrefix, ARRAY_SIZE(pacmanPath));\n    ffStrCopy(pend, \"/../var/lib/pacman/local/\", ARRAY_SIZE(pacmanPath) - (size_t) (pend - pacmanPath));\n    result->pacman = getNumElements(pacmanPath, FILE_ATTRIBUTE_DIRECTORY, NULL);\n}\n\nstatic void detectWinget(FFPackagesResult* result)\n{\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n    if (ffProcessAppendStdOut(&buffer, (char* []) {\n        \"winget.exe\",\n        \"list\",\n        \"--disable-interactivity\",\n        NULL,\n    }))\n        return;\n\n    uint32_t index = ffStrbufFirstIndexS(&buffer, \"--\\r\\n\"); // Ignore garbage and table headers\n    if (index == buffer.length)\n        return;\n\n    uint32_t count = 0;\n    for (\n        index += strlen(\"--\\r\\n\");\n        (index = ffStrbufNextIndexC(&buffer, index, '\\n')) < buffer.length;\n        ++index\n    )\n        ++count;\n\n    if (buffer.chars[buffer.length - 1] != '\\n') // count last line\n        ++count;\n\n    result->winget = count;\n}\n\nvoid ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options)\n{\n    if (!(options->disabled & FF_PACKAGES_FLAG_SCOOP_BIT)) detectScoop(result);\n    if (!(options->disabled & FF_PACKAGES_FLAG_CHOCO_BIT)) detectChoco(result);\n    if (!(options->disabled & FF_PACKAGES_FLAG_PACMAN_BIT)) detectPacman(result);\n    if (!(options->disabled & FF_PACKAGES_FLAG_WINGET_BIT)) detectWinget(result);\n}\n"
  },
  {
    "path": "src/detection/physicaldisk/physicaldisk.h",
    "content": "#include \"fastfetch.h\"\n#include \"modules/physicaldisk/option.h\"\n\n#define FF_PHYSICALDISK_TEMP_UNSET (-DBL_MAX)\n\ntypedef enum __attribute__((__packed__)) FFPhysicalDiskType\n{\n    FF_PHYSICALDISK_TYPE_NONE = 0,\n\n    // If neither is set, it's unknown\n    FF_PHYSICALDISK_TYPE_HDD = 1 << 0,\n    FF_PHYSICALDISK_TYPE_SSD = 1 << 1,\n\n    FF_PHYSICALDISK_TYPE_FIXED = 1 << 2,\n    FF_PHYSICALDISK_TYPE_REMOVABLE = 1 << 3,\n\n    FF_PHYSICALDISK_TYPE_READWRITE = 1 << 4,\n    FF_PHYSICALDISK_TYPE_READONLY = 1 << 5,\n\n    FF_PHYSICALDISK_TYPE_FORCE_UNSIGNED = UINT8_MAX,\n} FFPhysicalDiskType;\nstatic_assert(sizeof(FFPhysicalDiskType) == sizeof(uint8_t), \"\");\n\ntypedef struct FFPhysicalDiskResult\n{\n    FFstrbuf name;\n    FFstrbuf interconnect;\n    FFstrbuf serial;\n    FFstrbuf devPath;\n    FFstrbuf revision;\n    FFPhysicalDiskType type;\n    uint64_t size;\n    double temperature;\n} FFPhysicalDiskResult;\n\nconst char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options);\n"
  },
  {
    "path": "src/detection/physicaldisk/physicaldisk_apple.c",
    "content": "#include \"physicaldisk.h\"\n#include \"common/apple/cf_helpers.h\"\n\n#include <IOKit/IOKitLib.h>\n#include <IOKit/IOBSD.h>\n#include <IOKit/storage/IOMedia.h>\n#include <IOKit/storage/IOBlockStorageDriver.h>\n#include <IOKit/storage/IOStorageDeviceCharacteristics.h>\n#include <IOKit/storage/IOStorageProtocolCharacteristics.h>\n#ifdef MAC_OS_X_VERSION_10_15\n    #include <IOKit/storage/nvme/NVMeSMARTLibExternal.h>\n#endif\n\n#ifdef MAC_OS_X_VERSION_10_15\nstatic inline void wrapIoDestroyPlugInInterface(IOCFPlugInInterface*** pluginInf)\n{\n    assert(pluginInf);\n    if (*pluginInf)\n        IODestroyPlugInInterface(*pluginInf);\n}\n#endif\n\nstatic const char* detectSsdTemp(io_service_t entryPhysical, double* temp)\n{\n    #ifdef MAC_OS_X_VERSION_10_15\n    __attribute__((__cleanup__(wrapIoDestroyPlugInInterface))) IOCFPlugInInterface** pluginInf = NULL;\n    int32_t score;\n    if (IOCreatePlugInInterfaceForService(entryPhysical, kIONVMeSMARTUserClientTypeID, kIOCFPlugInInterfaceID, &pluginInf, &score) != kIOReturnSuccess)\n        return \"IOCreatePlugInInterfaceForService() failed\";\n\n    IONVMeSMARTInterface** smartInf = NULL;\n    if ((*pluginInf)->QueryInterface(pluginInf, CFUUIDGetUUIDBytes(kIONVMeSMARTInterfaceID), (LPVOID) &smartInf) != kIOReturnSuccess)\n        return \"QueryInterface() failed\";\n\n    NVMeSMARTData smartData;\n    const char* error = NULL;\n    if ((*smartInf)->SMARTReadData(smartInf, &smartData) == kIOReturnSuccess)\n        *temp = smartData.TEMPERATURE - 273;\n    else\n        error = \"SMARTReadData() failed\";\n\n    (*pluginInf)->Release(smartInf);\n    return error;\n    #else\n    return \"No support for old MacOS version\";\n    #endif\n}\n\nconst char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options)\n{\n    FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = 0;\n    if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceMatching(kIOMediaClass), &iterator) != KERN_SUCCESS)\n        return \"IOServiceGetMatchingServices() failed\";\n\n    io_registry_entry_t registryEntry;\n    while ((registryEntry = IOIteratorNext(iterator)) != IO_OBJECT_NULL)\n    {\n        FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryPartition = registryEntry;\n\n        io_name_t deviceName;\n        if (IORegistryEntryGetName(registryEntry, deviceName) != KERN_SUCCESS)\n            continue;\n\n        if (options->namePrefix.length && strncmp(deviceName, options->namePrefix.chars, options->namePrefix.length) != 0)\n            continue;\n\n        FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDriver = 0;\n        if (IORegistryEntryGetParentEntry(entryPartition, kIOServicePlane, &entryDriver) != KERN_SUCCESS)\n            continue;\n\n        if (!IOObjectConformsTo(entryDriver, kIOBlockStorageDriverClass)) // physical disk only\n            continue;\n\n        FFPhysicalDiskResult* device = (FFPhysicalDiskResult*) ffListAdd(result);\n        ffStrbufInit(&device->serial);\n        ffStrbufInit(&device->revision);\n        ffStrbufInitS(&device->name, deviceName);\n        ffStrbufInit(&device->devPath);\n        ffStrbufInit(&device->interconnect);\n        device->type = FF_PHYSICALDISK_TYPE_NONE;\n        device->size = 0;\n        device->temperature = FF_PHYSICALDISK_TEMP_UNSET;\n\n        FF_CFTYPE_AUTO_RELEASE CFBooleanRef removable = IORegistryEntryCreateCFProperty(entryPartition, CFSTR(kIOMediaRemovableKey), kCFAllocatorDefault, kNilOptions);\n        if (removable)\n            device->type |= CFBooleanGetValue(removable) ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED;\n\n        FF_CFTYPE_AUTO_RELEASE CFBooleanRef writable = IORegistryEntryCreateCFProperty(entryPartition, CFSTR(kIOMediaWritableKey), kCFAllocatorDefault, kNilOptions);\n        if (writable)\n            device->type |= CFBooleanGetValue(writable) ? FF_PHYSICALDISK_TYPE_READWRITE : FF_PHYSICALDISK_TYPE_READONLY;\n\n        FF_CFTYPE_AUTO_RELEASE CFStringRef bsdName = IORegistryEntryCreateCFProperty(entryPartition, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, kNilOptions);\n        if (bsdName)\n        {\n            ffCfStrGetString(bsdName, &device->devPath);\n            ffStrbufPrependS(&device->devPath, \"/dev/\");\n        }\n\n        FF_CFTYPE_AUTO_RELEASE CFNumberRef mediaSize = IORegistryEntryCreateCFProperty(entryPartition, CFSTR(kIOMediaSizeKey), kCFAllocatorDefault, kNilOptions);\n        if (mediaSize)\n            ffCfNumGetInt64(mediaSize, (int64_t*) &device->size);\n        else\n            device->size = 0;\n\n        FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryPhysical = 0;\n        if (IORegistryEntryGetParentEntry(entryDriver, kIOServicePlane, &entryPhysical) == KERN_SUCCESS)\n        {\n            FF_CFTYPE_AUTO_RELEASE CFDictionaryRef protocolCharacteristics = IORegistryEntryCreateCFProperty(entryPhysical, CFSTR(kIOPropertyProtocolCharacteristicsKey), kCFAllocatorDefault, kNilOptions);\n            if (protocolCharacteristics)\n                ffCfDictGetString(protocolCharacteristics, CFSTR(kIOPropertyPhysicalInterconnectTypeKey), &device->interconnect);\n\n            FF_CFTYPE_AUTO_RELEASE CFDictionaryRef deviceCharacteristics = IORegistryEntryCreateCFProperty(entryPhysical, CFSTR(kIOPropertyDeviceCharacteristicsKey), kCFAllocatorDefault, kNilOptions);\n            if (deviceCharacteristics)\n            {\n                ffCfDictGetString(deviceCharacteristics, CFSTR(kIOPropertyProductSerialNumberKey), &device->serial);\n                ffStrbufTrimSpace(&device->serial);\n                ffCfDictGetString(deviceCharacteristics, CFSTR(kIOPropertyProductRevisionLevelKey), &device->revision);\n                ffStrbufTrimRightSpace(&device->revision);\n\n                CFStringRef mediumType = (CFStringRef) CFDictionaryGetValue(deviceCharacteristics, CFSTR(kIOPropertyMediumTypeKey));\n                if (mediumType)\n                {\n                    if (CFStringCompare(mediumType, CFSTR(kIOPropertyMediumTypeSolidStateKey), 0) == 0)\n                        device->type |= FF_PHYSICALDISK_TYPE_SSD;\n                    else if (CFStringCompare(mediumType, CFSTR(kIOPropertyMediumTypeRotationalKey), 0) == 0)\n                        device->type |= FF_PHYSICALDISK_TYPE_HDD;\n                }\n            }\n\n            #ifdef MAC_OS_X_VERSION_10_15\n            if (options->temp)\n            {\n                FF_CFTYPE_AUTO_RELEASE CFBooleanRef nvmeSMARTCapable = IORegistryEntryCreateCFProperty(entryPhysical, CFSTR(kIOPropertyNVMeSMARTCapableKey), kCFAllocatorDefault, kNilOptions);\n                if (nvmeSMARTCapable && CFBooleanGetValue(nvmeSMARTCapable))\n                    detectSsdTemp(entryPhysical, &device->temperature);\n            }\n            #endif\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/physicaldisk/physicaldisk_bsd.c",
    "content": "#include \"physicaldisk.h\"\n\n#if __has_include(<libgeom.h>)\n\n#include \"common/stringUtils.h\"\n\n#include <devstat.h>\n#include <memory.h>\n#include <fcntl.h>\n#include <sys/ioctl.h>\n#include <sys/disk.h>\n#include <libgeom.h>\n\nconst char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options)\n{\n    struct gmesh geomTree;\n    if (geom_gettree(&geomTree) < 0)\n        return \"geom_gettree() failed\";\n\n    if (geom_stats_open() < 0)\n        return \"geom_stats_open() failed\";\n\n    void* snap = geom_stats_snapshot_get();\n    struct devstat* snapIter;\n    while ((snapIter = geom_stats_snapshot_next(snap)) != NULL)\n    {\n        if (snapIter->device_type & DEVSTAT_TYPE_PASS)\n            continue;\n        struct gident* geomId = geom_lookupid(&geomTree, snapIter->id);\n        if (geomId == NULL)\n            continue;\n        if (geomId->lg_what != ISPROVIDER)\n            continue;\n        struct gprovider* provider = (struct gprovider*) geomId->lg_ptr;\n        if (provider->lg_geom->lg_rank != 1)\n            continue;\n\n        // Should memory disk (MD) be considered as physical disk?\n        //if (!ffStrEquals(provider->lg_geom->lg_class->lg_name, \"DISK\"))\n        //    continue;\n\n        FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateS(provider->lg_name);\n        FF_STRBUF_AUTO_DESTROY identifier = ffStrbufCreate();\n        FFPhysicalDiskType type = FF_PHYSICALDISK_TYPE_NONE;\n        for (struct gconfig* ptr = provider->lg_config.lh_first; ptr; ptr = ptr->lg_config.le_next)\n        {\n            if (ffStrEquals(ptr->lg_name, \"descr\"))\n                ffStrbufSetS(&name, ptr->lg_val);\n            else if (ffStrEquals(ptr->lg_name, \"rotationrate\") && !ffStrEquals(ptr->lg_val, \"unknown\"))\n                type |= ffStrEquals(ptr->lg_val, \"0\") ? FF_PHYSICALDISK_TYPE_SSD : FF_PHYSICALDISK_TYPE_HDD;\n            else if (ffStrEquals(ptr->lg_name, \"ident\"))\n                ffStrbufSetS(&identifier, ptr->lg_val);\n            else if (ffStrEquals(ptr->lg_name, \"access\"))\n            {\n                if (ffStrEquals(ptr->lg_val, \"read-only\"))\n                    type |= FF_PHYSICALDISK_TYPE_READONLY;\n                else if (ffStrEquals(ptr->lg_val, \"read-write\"))\n                    type |= FF_PHYSICALDISK_TYPE_READWRITE;\n            }\n        }\n\n        if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix))\n            continue;\n\n        FFPhysicalDiskResult* device = (FFPhysicalDiskResult*) ffListAdd(result);\n        ffStrbufInitF(&device->devPath, \"/dev/%s\", provider->lg_name);\n        ffStrbufInitMove(&device->serial, &identifier);\n        ffStrbufTrimSpace(&device->serial);\n        ffStrbufInit(&device->revision);\n        ffStrbufInit(&device->interconnect);\n        switch (snapIter->device_type & DEVSTAT_TYPE_IF_MASK)\n        {\n            case DEVSTAT_TYPE_IF_SCSI: ffStrbufAppendS(&device->interconnect, \"SCSI\"); break;\n            case DEVSTAT_TYPE_IF_IDE: ffStrbufAppendS(&device->interconnect, \"IDE\"); break;\n            case DEVSTAT_TYPE_IF_OTHER: ffStrbufAppendS(&device->interconnect, \"OTHER\"); break;\n\n            // https://github.com/freebsd/freebsd-src/commit/d282baddb0b029ca8466d23ac51e95c918442535\n            case 0x040 /*DEVSTAT_TYPE_IF_NVME*/: ffStrbufAppendS(&device->interconnect, \"NVMe\"); break;\n        }\n        device->size = (uint64_t) provider->lg_mediasize;\n        ffStrbufInitMove(&device->name, &name);\n\n        if (!(device->type & FF_PHYSICALDISK_TYPE_READONLY) && !(device->type & FF_PHYSICALDISK_TYPE_READWRITE))\n        {\n            int acr = 1, acw = 1; // Number of partitions mounted for reading or writing\n            if (sscanf(provider->lg_mode, \"r%dw%de%*d\", &acr, &acw) == 2 && acr)\n                type |= acw ? FF_PHYSICALDISK_TYPE_READWRITE : FF_PHYSICALDISK_TYPE_READONLY;\n        }\n\n        device->type = type;\n        device->temperature = FF_PHYSICALDISK_TEMP_UNSET;\n    }\n\n    geom_stats_snapshot_free(snap);\n    geom_stats_close();\n\n    return NULL;\n}\n#else\nconst char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options)\n{\n    return \"Fastfetch was compiled without libgeom support\";\n}\n#endif\n"
  },
  {
    "path": "src/detection/physicaldisk/physicaldisk_haiku.c",
    "content": "#include \"physicaldisk.h\"\n\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n\n#include <OS.h>\n#include <StorageDefs.h>\n#include <Drivers.h>\n#include <sys/ioctl.h>\n\nstatic const char* detectDisk(FFstrbuf* path, const char* diskType, FFlist* result)\n{\n    FF_AUTO_CLOSE_FD int rawfd = open(path->chars, O_RDONLY | O_CLOEXEC);\n    if (rawfd < 0) return \"detectDisk: open(rawfd) failed\";\n\n    device_geometry geometry;\n    if (ioctl(rawfd, B_GET_GEOMETRY, &geometry, sizeof(geometry)) < 0)\n        return \"ioctl(B_GET_GEOMETRY) failed\";\n\n    char name[B_OS_NAME_LENGTH];\n    if (ioctl(rawfd, B_GET_DEVICE_NAME, name, sizeof(name)) != 0)\n    {\n        // ioctl reports `not a tty` for NVME drives for some reason\n        snprintf(name, sizeof(name), \"Unknown %s drive\", diskType);\n    }\n\n    FFPhysicalDiskResult* device = (FFPhysicalDiskResult*) ffListAdd(result);\n    ffStrbufInitS(&device->name, name);\n    ffStrbufInitCopy(&device->devPath, path);\n    ffStrbufInit(&device->serial);\n    ffStrbufInit(&device->revision);\n    ffStrbufInitS(&device->interconnect, diskType);\n    device->temperature = FF_PHYSICALDISK_TEMP_UNSET;\n    device->type = FF_PHYSICALDISK_TYPE_NONE;\n    device->type |= (geometry.read_only ? FF_PHYSICALDISK_TYPE_READONLY : FF_PHYSICALDISK_TYPE_READWRITE) |\n        (geometry.removable ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED);\n    device->size = (uint64_t) geometry.cylinder_count * geometry.head_count * geometry.sectors_per_track * geometry.bytes_per_sector;\n\n    return NULL;\n}\n\nstatic const char* searchRawDeviceFile(FFstrbuf* path, const char* diskType, FFlist* result)\n{\n    FF_AUTO_CLOSE_DIR DIR* dir = opendir(path->chars);\n    if (!dir) return \"detectDiskType: opendir() failed\";\n    uint32_t baseLen = path->length;\n\n    struct dirent* entry;\n    while((entry = readdir(dir)))\n    {\n        if (entry->d_name[0] == '.') continue;\n        ffStrbufAppendC(path, '/');\n        ffStrbufAppendS(path, entry->d_name);\n\n        struct stat st;\n        if (stat(path->chars, &st) != 0)\n        {\n            ffStrbufSubstrBefore(path, baseLen);\n            continue;\n        }\n\n        if (S_ISDIR(st.st_mode))\n            searchRawDeviceFile(path, diskType, result);\n        else if (ffStrEquals(entry->d_name, \"raw\"))\n            detectDisk(path, diskType, result);\n\n        ffStrbufSubstrBefore(path, baseLen);\n    }\n    return NULL;\n}\n\nconst char* ffDetectPhysicalDisk(FFlist* result, FF_MAYBE_UNUSED FFPhysicalDiskOptions* options)\n{\n    FF_AUTO_CLOSE_DIR DIR* dir = opendir(\"/dev/disk\");\n    if (!dir) return \"opendir(/dev/disk) failed\";\n\n    FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateA(64);\n    ffStrbufAppendS(&path, \"/dev/disk/\");\n    uint32_t baseLen = path.length;\n\n    struct dirent* entry;\n    while((entry = readdir(dir)))\n    {\n        if (entry->d_name[0] == '.' || ffStrEquals(entry->d_name, \"virtual\")) continue;\n        ffStrbufAppendS(&path, entry->d_name);\n        searchRawDeviceFile(&path, entry->d_name, result);\n        ffStrbufSubstrBefore(&path, baseLen);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/physicaldisk/physicaldisk_linux.c",
    "content": "#include \"physicaldisk.h\"\n#include \"common/io.h\"\n#include \"common/properties.h\"\n#include \"common/stringUtils.h\"\n\n#include <ctype.h>\n#include <limits.h>\n#include <unistd.h>\n#include <fcntl.h>\n\nstatic double detectNvmeTemp(int devfd)\n{\n    char pathHwmon[] = \"hwmon$/temp1_input\";\n\n    for (char c = '0'; c <= '9'; c++) // hopefully there's only one digit\n    {\n        pathHwmon[strlen(\"hwmon\")] = c;\n        char buffer[64];\n        ssize_t size = ffReadFileDataRelative(devfd, pathHwmon, ARRAY_SIZE(buffer), buffer);\n        if (size > 0)\n        {\n            buffer[size] = '\\0';\n            double temp = strtod(buffer, NULL);\n            return temp > 0 && temp < 10000000 /*VMware*/ ? temp / 1000 : FF_PHYSICALDISK_TEMP_UNSET;\n        }\n    }\n\n    return FF_PHYSICALDISK_TEMP_UNSET;\n}\n\nstatic void parsePhysicalDisk(int dfd, const char* devName, FFPhysicalDiskOptions* options, FFlist* result)\n{\n    int devfd = openat(dfd, \"device\", O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY);\n    if (devfd < 0) return; // virtual device\n\n    FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate();\n\n    {\n        if (ffAppendFileBufferRelative(devfd, \"vendor\", &name))\n        {\n            ffStrbufTrimRightSpace(&name);\n            if (name.length > 0)\n                ffStrbufAppendC(&name, ' ');\n        }\n\n        ffAppendFileBufferRelative(devfd, \"model\", &name);\n        ffStrbufTrimRightSpace(&name);\n\n        if (name.length == 0)\n            ffStrbufSetS(&name, devName);\n\n        if (ffStrStartsWith(devName, \"nvme\"))\n        {\n            int devid, nsid;\n            if (sscanf(devName, \"nvme%dn%d\", &devid, &nsid) == 2)\n            {\n                bool multiNs = nsid > 1;\n                if (!multiNs)\n                {\n                    char pathSysBlock[32];\n                    snprintf(pathSysBlock, ARRAY_SIZE(pathSysBlock), \"/dev/nvme%dn2\", devid);\n                    multiNs = access(pathSysBlock, F_OK) == 0;\n                }\n                if (multiNs)\n                {\n                    // In Asahi Linux, there are multiple namespaces for the same NVMe drive.\n                    ffStrbufAppendF(&name, \" - %d\", nsid);\n                }\n            }\n        }\n\n        if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix))\n            return;\n    }\n\n    FFPhysicalDiskResult* device = (FFPhysicalDiskResult*) ffListAdd(result);\n    device->type = FF_PHYSICALDISK_TYPE_NONE;\n    ffStrbufInitMove(&device->name, &name);\n    ffStrbufInitF(&device->devPath, \"/dev/%s\", devName);\n\n    bool isVirtual = false;\n    {\n        ffStrbufInit(&device->interconnect);\n        if (ffStrStartsWith(devName, \"nvme\"))\n            ffStrbufSetStatic(&device->interconnect, \"NVMe\");\n        else if (ffStrStartsWith(devName, \"mmcblk\"))\n            ffStrbufSetStatic(&device->interconnect, \"MMC\");\n        else if (ffStrStartsWith(devName, \"md\"))\n        {\n            ffStrbufSetStatic(&device->interconnect, \"RAID\");\n            isVirtual = true;\n        }\n        else\n        {\n            char pathSysDeviceLink[64];\n            snprintf(pathSysDeviceLink, ARRAY_SIZE(pathSysDeviceLink), \"/sys/block/%s/device\", devName);\n            char pathSysDeviceReal[PATH_MAX];\n            if (realpath(pathSysDeviceLink, pathSysDeviceReal))\n            {\n                if (strstr(pathSysDeviceReal, \"/usb\") != NULL)\n                    ffStrbufSetStatic(&device->interconnect, \"USB\");\n                else if (strstr(pathSysDeviceReal, \"/ata\") != NULL)\n                    ffStrbufSetStatic(&device->interconnect, \"ATA\");\n                else if (strstr(pathSysDeviceReal, \"/scsi\") != NULL)\n                    ffStrbufSetStatic(&device->interconnect, \"SCSI\");\n                else if (strstr(pathSysDeviceReal, \"/nvme\") != NULL)\n                    ffStrbufSetStatic(&device->interconnect, \"NVMe\");\n                else if (strstr(pathSysDeviceReal, \"/virtio\") != NULL)\n                {\n                    ffStrbufSetStatic(&device->interconnect, \"Virtual\");\n                    isVirtual = true;\n                }\n                else\n                {\n                    if (ffAppendFileBufferRelative(devfd, \"transport\", &device->interconnect))\n                        ffStrbufTrimRightSpace(&device->interconnect);\n                }\n            }\n        }\n    }\n\n    if (!isVirtual)\n    {\n        char isRotationalChar = '1';\n        if (ffReadFileDataRelative(dfd, \"queue/rotational\", 1, &isRotationalChar) > 0)\n            device->type |= isRotationalChar == '1' ? FF_PHYSICALDISK_TYPE_HDD : FF_PHYSICALDISK_TYPE_SSD;\n    }\n\n    {\n        char blkSize[32];\n        ssize_t fileSize = ffReadFileDataRelative(dfd, \"size\", ARRAY_SIZE(blkSize) - 1, blkSize);\n        if (fileSize > 0)\n        {\n            blkSize[fileSize] = 0;\n            device->size = (uint64_t) strtoul(blkSize, NULL, 10) * 512;\n        }\n        else\n            device->size = 0;\n    }\n\n    {\n        char removableChar = '0';\n        if (ffReadFileDataRelative(dfd, \"removable\", 1, &removableChar) > 0)\n            device->type |= removableChar == '1' ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED;\n    }\n\n    {\n        char roChar = '0';\n        if (ffReadFileDataRelative(dfd, \"ro\", 1, &roChar) > 0)\n            device->type |= roChar == '1' ? FF_PHYSICALDISK_TYPE_READONLY : FF_PHYSICALDISK_TYPE_READWRITE;\n    }\n\n    {\n        ffStrbufInit(&device->serial);\n        if (ffReadFileBufferRelative(devfd, \"serial\", &device->serial))\n            ffStrbufTrimSpace(&device->serial);\n    }\n\n    {\n        ffStrbufInit(&device->revision);\n        if (ffReadFileBufferRelative(devfd, \"firmware_rev\", &device->revision))\n            ffStrbufTrimRightSpace(&device->revision);\n        else\n        {\n            if (ffReadFileBufferRelative(devfd, \"rev\", &device->revision))\n                ffStrbufTrimRightSpace(&device->revision);\n        }\n    }\n\n    if (options->temp)\n        device->temperature = detectNvmeTemp(devfd);\n    else\n        device->temperature = FF_PHYSICALDISK_TEMP_UNSET;\n}\n\nconst char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options)\n{\n    FF_AUTO_CLOSE_DIR DIR* sysBlockDirp = opendir(\"/sys/block/\");\n    if(sysBlockDirp == NULL)\n        return \"opendir(\\\"/sys/block/\\\") == NULL\";\n\n    struct dirent* sysBlockEntry;\n    while ((sysBlockEntry = readdir(sysBlockDirp)) != NULL)\n    {\n        const char* const devName = sysBlockEntry->d_name;\n\n        if (devName[0] == '.')\n            continue;\n\n        char pathSysBlock[sizeof(\"/sys/block/\") + sizeof(sysBlockEntry->d_name)];\n        snprintf(pathSysBlock, ARRAY_SIZE(pathSysBlock), \"/sys/block/%s\", devName);\n\n        int dfd = openat(dirfd(sysBlockDirp), devName, O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY);\n        if (dfd > 0) parsePhysicalDisk(dfd, devName, options, result);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/physicaldisk/physicaldisk_nosupport.c",
    "content": "#include \"physicaldisk.h\"\n\nconst char* ffDetectPhysicalDisk(FF_MAYBE_UNUSED FFlist* result, FF_MAYBE_UNUSED FFPhysicalDiskOptions* options)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/physicaldisk/physicaldisk_sunos.c",
    "content": "#include \"physicaldisk.h\"\n#include \"common/stringUtils.h\"\n#include \"sys/scsi/generic/inquiry.h\"\n\n#include <libdevinfo.h>\n#include <sys/stat.h>\n\nstruct FFWalkTreeBundle\n{\n    FFPhysicalDiskOptions* options;\n    FFlist* disks;\n};\n\nstatic int walkDevTree(di_node_t node, di_minor_t minor, struct FFWalkTreeBundle* bundle)\n{\n    if (di_minor_spectype(minor) != S_IFCHR || !ffStrEquals(di_minor_name(minor), \"a,raw\")) return DI_WALK_CONTINUE;\n\n    char* productId;\n    char* vendorId;\n    if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, \"inquiry-product-id\", &productId) > 0\n        && di_prop_lookup_strings(DDI_DEV_T_ANY, node, \"inquiry-vendor-id\", &vendorId) > 0)\n    {\n        FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateF(\"%s %s\", vendorId, productId);\n        if (bundle->options->namePrefix.length && !ffStrbufStartsWithIgnCase(&name, &bundle->options->namePrefix))\n            return DI_WALK_CONTINUE;\n\n        FFPhysicalDiskResult* device = (FFPhysicalDiskResult*) ffListAdd(bundle->disks);\n        ffStrbufInitMove(&device->name, &name);\n        ffStrbufInitF(&device->devPath, \"/devices%s\", di_devfs_path(node));\n        ffStrbufInit(&device->serial);\n        ffStrbufInit(&device->revision);\n        ffStrbufInit(&device->interconnect);\n        device->temperature = FF_PHYSICALDISK_TEMP_UNSET;\n        device->type = FF_PHYSICALDISK_TYPE_NONE;\n        device->size = 0;\n\n        char* buf;\n        if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, \"inquiry-serial-no\", &buf) > 0)\n        {\n            ffStrbufSetS(&device->serial, buf);\n            ffStrbufTrimSpace(&device->serial);\n        }\n        if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, \"inquiry-revision-id\", &buf) > 0)\n        {\n            ffStrbufSetS(&device->revision, buf);\n            ffStrbufTrimRightSpace(&device->revision);\n        }\n        if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, \"class\", &buf) > 0)\n            ffStrbufSetS(&device->interconnect, buf);\n\n        device->type |= di_prop_find(DDI_DEV_T_ANY, node, \"removable-media\") ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED;\n\n        int* value;\n        if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, \"device-solid-state\", &value) > 0)\n            device->type |= *value ? FF_PHYSICALDISK_TYPE_SSD : FF_PHYSICALDISK_TYPE_HDD;\n        if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, \"inquiry-device-type\", &value) > 0)\n            device->type |= *value == DTYPE_DIRECT ? FF_PHYSICALDISK_TYPE_READWRITE : *value == DTYPE_RODIRECT ? FF_PHYSICALDISK_TYPE_READONLY : 0;\n\n        int64_t* nblocks;\n        if (di_prop_lookup_int64(DDI_DEV_T_ANY, node, \"device-nblocks\", &nblocks) > 0\n            && di_prop_lookup_ints(DDI_DEV_T_ANY, node, \"device-blksize\", &value) > 0)\n            device->size = (uint64_t) ((uint64_t) *nblocks * (uint64_t) *value);\n    }\n\n    return DI_WALK_CONTINUE;\n}\n\nconst char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options)\n{\n    di_node_t rootNode = di_init(\"/\", DINFOCPYALL);\n    if (rootNode == DI_NODE_NIL)\n        return \"di_init() failed\";\n    di_walk_minor(rootNode, DDI_NT_BLOCK, DI_WALK_CLDFIRST, &(struct FFWalkTreeBundle) { options, result }, (void*) walkDevTree);\n    di_fini(rootNode);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/physicaldisk/physicaldisk_windows.c",
    "content": "#include \"physicaldisk.h\"\n#include \"common/io.h\"\n#include \"common/windows/unicode.h\"\n\n#include <stdalign.h>\n#include <windows.h>\n#include <winioctl.h>\n\nstatic bool detectPhysicalDisk(const wchar_t* szDevice, FFlist* result, FFPhysicalDiskOptions* options)\n{\n    FF_AUTO_CLOSE_FD HANDLE hDevice = CreateFileW(szDevice, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);\n    if (hDevice == INVALID_HANDLE_VALUE)\n        return false;\n\n    DWORD retSize;\n    char sddBuffer[4096];\n    if(!DeviceIoControl(\n        hDevice,\n        IOCTL_STORAGE_QUERY_PROPERTY,\n        &(STORAGE_PROPERTY_QUERY) {\n            .PropertyId = StorageDeviceProperty,\n            .QueryType = PropertyStandardQuery,\n        },\n        sizeof(STORAGE_PROPERTY_QUERY),\n        &sddBuffer,\n        sizeof(sddBuffer),\n        &retSize,\n        NULL\n    ) || retSize == 0)\n        return true;\n\n    FFPhysicalDiskResult* device = (FFPhysicalDiskResult*) ffListAdd(result);\n    device->type = FF_PHYSICALDISK_TYPE_NONE;\n    STORAGE_DEVICE_DESCRIPTOR* sdd = (STORAGE_DEVICE_DESCRIPTOR*) sddBuffer;\n\n    ffStrbufInit(&device->name);\n    if (sdd->VendorIdOffset != 0)\n    {\n        ffStrbufSetS(&device->name, (const char*) sddBuffer + sdd->VendorIdOffset);\n        ffStrbufTrim(&device->name, ' ');\n    }\n    if (sdd->ProductIdOffset != 0)\n    {\n        if (device->name.length)\n            ffStrbufAppendC(&device->name, ' ');\n\n        ffStrbufAppendS(&device->name, (const char*) sddBuffer + sdd->ProductIdOffset);\n        ffStrbufTrimRight(&device->name, ' ');\n    }\n\n    if (!device->name.length)\n        ffStrbufSetWS(&device->name, szDevice);\n\n    if (options->namePrefix.length && !ffStrbufStartsWith(&device->name, &options->namePrefix))\n    {\n        ffStrbufDestroy(&device->name);\n        result->length--;\n        return true;\n    }\n\n    ffStrbufInitWS(&device->devPath, szDevice);\n    ffStrbufInit(&device->serial);\n    if (sdd->SerialNumberOffset != 0)\n    {\n        ffStrbufSetS(&device->serial, (const char*) sddBuffer + sdd->SerialNumberOffset);\n        ffStrbufTrimSpace(&device->serial);\n    }\n\n    ffStrbufInit(&device->revision);\n    if (sdd->ProductRevisionOffset != 0)\n    {\n        ffStrbufSetS(&device->revision, (const char*) sddBuffer + sdd->ProductRevisionOffset);\n        ffStrbufTrimRightSpace(&device->revision);\n    }\n\n    device->type |= sdd->RemovableMedia ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED;\n\n    ffStrbufInit(&device->interconnect);\n    switch (sdd->BusType)\n    {\n        case BusTypeUnknown: ffStrbufSetStatic(&device->interconnect, \"Unknown\"); break;\n        case BusTypeScsi: ffStrbufSetStatic(&device->interconnect, \"SCSI\"); break;\n        case BusTypeAtapi: ffStrbufSetStatic(&device->interconnect, \"ATAPI\"); break;\n        case BusTypeAta: ffStrbufSetStatic(&device->interconnect, \"ATA\"); break;\n        case BusType1394: ffStrbufSetStatic(&device->interconnect, \"1394\"); break;\n        case BusTypeSsa: ffStrbufSetStatic(&device->interconnect, \"SSA\"); break;\n        case BusTypeFibre: ffStrbufSetStatic(&device->interconnect, \"Fibre\"); break;\n        case BusTypeUsb: ffStrbufSetStatic(&device->interconnect, \"USB\"); break;\n        case BusTypeRAID: ffStrbufSetStatic(&device->interconnect, \"RAID\"); break;\n        case BusTypeiScsi: ffStrbufSetStatic(&device->interconnect, \"iSCSI\"); break;\n        case BusTypeSas: ffStrbufSetStatic(&device->interconnect, \"SAS\"); break;\n        case BusTypeSata: ffStrbufSetStatic(&device->interconnect, \"SATA\"); break;\n        case BusTypeSd: ffStrbufSetStatic(&device->interconnect, \"SD\"); break;\n        case BusTypeMmc: ffStrbufSetStatic(&device->interconnect, \"MMC\"); break;\n        case BusTypeVirtual: ffStrbufSetStatic(&device->interconnect, \"Virtual\"); break;\n        case BusTypeFileBackedVirtual: ffStrbufSetStatic(&device->interconnect, \"File Backed Virtual\"); break;\n        case BusTypeSpaces: ffStrbufSetStatic(&device->interconnect, \"Spaces\"); break;\n        case BusTypeNvme: ffStrbufSetStatic(&device->interconnect, \"NVMe\"); break;\n        case BusTypeSCM: ffStrbufSetStatic(&device->interconnect, \"SCM\"); break;\n        case BusTypeUfs: ffStrbufSetStatic(&device->interconnect, \"UFS\"); break;\n        default: ffStrbufSetF(&device->interconnect, \"Unknown (%d)\", (int) sdd->BusType); break;\n    }\n\n    {\n        DEVICE_SEEK_PENALTY_DESCRIPTOR dspd = {};\n        if(DeviceIoControl(\n            hDevice,\n            IOCTL_STORAGE_QUERY_PROPERTY,\n            &(STORAGE_PROPERTY_QUERY) {\n                .PropertyId = StorageDeviceSeekPenaltyProperty,\n                .QueryType = PropertyStandardQuery,\n            },\n            sizeof(STORAGE_PROPERTY_QUERY),\n            &dspd,\n            sizeof(dspd),\n            &retSize,\n            NULL\n        ) && retSize == sizeof(dspd))\n            device->type |= dspd.IncursSeekPenalty ? FF_PHYSICALDISK_TYPE_HDD : FF_PHYSICALDISK_TYPE_SSD;\n    }\n\n    {\n        DISK_GEOMETRY_EX dge = {};\n        if(DeviceIoControl(\n            hDevice,\n            IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,\n            NULL,\n            0,\n            &dge,\n            sizeof(dge),\n            &retSize,\n            NULL))\n            device->size = (uint64_t) dge.DiskSize.QuadPart;\n        else\n            device->size = 0;\n    }\n\n    {\n        alignas(GET_MEDIA_TYPES) uint8_t buffer[sizeof(GET_MEDIA_TYPES) + sizeof(DEVICE_MEDIA_INFO) * 7] = {};\n        GET_MEDIA_TYPES* gmt = (GET_MEDIA_TYPES*) buffer;\n        if(DeviceIoControl(\n            hDevice,\n            IOCTL_STORAGE_GET_MEDIA_TYPES_EX,\n            NULL,\n            0,\n            gmt,\n            sizeof(buffer),\n            &retSize,\n            NULL) && gmt->MediaInfoCount > 0\n        )\n        {\n            // DiskInfo and RemovableDiskInfo have the same structures. TapeInfo doesn't.\n            if (gmt->DeviceType != FILE_DEVICE_TAPE)\n            {\n                __auto_type diskInfo = &gmt->MediaInfo[0].DeviceSpecific.DiskInfo;\n                if (diskInfo->MediaCharacteristics & MEDIA_READ_ONLY)\n                    device->type |= FF_PHYSICALDISK_TYPE_READONLY;\n                else if (diskInfo->MediaCharacteristics & MEDIA_READ_WRITE)\n                    device->type |= FF_PHYSICALDISK_TYPE_READWRITE;\n                if (device->size == 0)\n                    device->size = (uint64_t) diskInfo->NumberMediaSides * diskInfo->TracksPerCylinder * diskInfo->SectorsPerTrack * diskInfo->BytesPerSector;\n            }\n            else\n            {\n                __auto_type tapeInfo = &gmt->MediaInfo[0].DeviceSpecific.TapeInfo;\n                if (tapeInfo->MediaCharacteristics & MEDIA_READ_ONLY)\n                    device->type |= FF_PHYSICALDISK_TYPE_READONLY;\n                else if (tapeInfo->MediaCharacteristics & MEDIA_READ_WRITE)\n                    device->type |= FF_PHYSICALDISK_TYPE_READWRITE;\n            }\n        }\n    }\n\n    device->temperature = FF_PHYSICALDISK_TEMP_UNSET;\n    if (options->temp)\n    {\n        STORAGE_TEMPERATURE_DATA_DESCRIPTOR stdd = {};\n        if(DeviceIoControl(\n            hDevice,\n            IOCTL_STORAGE_QUERY_PROPERTY,\n            &(STORAGE_PROPERTY_QUERY) {\n                .PropertyId = StorageDeviceTemperatureProperty,\n                .QueryType = PropertyStandardQuery,\n            },\n            sizeof(STORAGE_PROPERTY_QUERY),\n            &stdd,\n            sizeof(stdd),\n            &retSize,\n            NULL\n        ) && retSize == sizeof(stdd))\n            device->temperature = stdd.TemperatureInfo[0].Temperature;\n    }\n\n    return true;\n}\n\nconst char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options)\n{\n    {\n        wchar_t szPhysicalDrive[32] = L\"\\\\\\\\.\\\\PhysicalDrive\";\n        wchar_t* pNum = szPhysicalDrive + strlen(\"\\\\\\\\.\\\\PhysicalDrive\");\n        for (uint32_t idev = 0; ; ++idev)\n        {\n            _ultow(idev, pNum, 10);\n\n            if (!detectPhysicalDisk(szPhysicalDrive, result, options))\n                break;\n        }\n    }\n\n    {\n        wchar_t szCdrom[32] = L\"\\\\\\\\.\\\\CDROM\";\n        wchar_t* pNum = szCdrom + strlen(\"\\\\\\\\.\\\\CDROM\");\n        for (uint32_t idev = 0; ; ++idev)\n        {\n            _ultow(idev, pNum, 10);\n\n            if (!detectPhysicalDisk(szCdrom, result, options))\n                break;\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/physicalmemory/physicalmemory.c",
    "content": "#include \"physicalmemory.h\"\n\nstatic inline const char* getVendorString(unsigned vendorId)\n{\n    switch (vendorId)\n    {\n        case 0x017A: return \"Apacer\";\n        case 0x0198: return \"Kingston\";\n        case 0x029E: return \"Corsair\";\n        case 0x04CB: return \"A-DATA\";\n        case 0x04CD: return \"G-Skill\";\n        case 0x059B: case 0x859B: return \"Crucial\";\n        case 0x00CE: case 0x80CE: case 0xCE00: return \"Samsung\";\n        case 0x014F: return \"Transcend\";\n        case 0x2C00: case 0x802C: return \"Micron\";\n        case 0xAD00: case 0x80AD: return \"SK Hynix\";\n        case 0x5105: case 0x8551: return \"Qimonda\";\n        case 0x02FE: return \"Elpida\";\n        case 0x0467: return \"Ramaxel\";\n        default: return NULL;\n    }\n}\n\nvoid FFPhysicalMemoryUpdateVendorString(FFPhysicalMemoryResult* device)\n{\n    if (device->vendor.length == 0) return;\n    if (ffStrbufEqualS(&device->vendor, \"Unknown\"))\n    {\n        ffStrbufClear(&device->vendor);\n        return;\n    }\n\n    char vendorIdStr[5];\n    if (ffStrbufStartsWithS(&device->vendor, \"0x\"))\n    {\n        if (device->vendor.length < 6) return;\n        memcpy(vendorIdStr, device->vendor.chars + 2, 4);\n    }\n    else\n    {\n        if (device->vendor.length < 4) return;\n        memcpy(vendorIdStr, device->vendor.chars, 4);\n    }\n    vendorIdStr[4] = '\\0';\n    char* pEnd = NULL;\n    uint32_t vendorId = (uint32_t) strtoul(vendorIdStr, &pEnd, 16);\n    if (*pEnd != '\\0') return;\n    const char* vendorStr = getVendorString(vendorId);\n    if (vendorStr) ffStrbufSetStatic(&device->vendor, vendorStr);\n}\n"
  },
  {
    "path": "src/detection/physicalmemory/physicalmemory.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/physicalmemory/option.h\"\n\ntypedef struct FFPhysicalMemoryResult\n{\n    uint64_t size; // B\n    uint32_t maxSpeed; // MT/s\n    uint32_t runningSpeed; // MT/s\n    bool installed;\n    FFstrbuf type;\n    FFstrbuf formFactor;\n    FFstrbuf locator;\n    FFstrbuf partNumber;\n    FFstrbuf vendor;\n    FFstrbuf serial;\n    bool ecc;\n} FFPhysicalMemoryResult;\n\nconst char* ffDetectPhysicalMemory(FFlist* result); // list of FFPhysicalMemoryResult\n\nvoid FFPhysicalMemoryUpdateVendorString(FFPhysicalMemoryResult* device);\n"
  },
  {
    "path": "src/detection/physicalmemory/physicalmemory_apple.m",
    "content": "#include \"physicalmemory.h\"\n#include \"common/processing.h\"\n#include \"common/smbiosHelper.h\"\n#include \"common/stringUtils.h\"\n#include \"common/apple/cf_helpers.h\"\n\n#import <Foundation/Foundation.h>\n\nstatic void appendDevice(\n    FFlist* result,\n    NSString* type,\n    NSString* vendor,\n    NSString* size,\n\n    // Intel only\n    NSString* locator,\n    NSString* serial,\n    NSString* partNumber,\n    NSString* speed,\n    bool ecc)\n{\n    FFPhysicalMemoryResult* device = ffListAdd(result);\n    ffStrbufInitS(&device->type, type.UTF8String);\n    ffStrbufInit(&device->formFactor);\n    ffStrbufInitS(&device->locator, locator.UTF8String);\n    ffStrbufInitS(&device->vendor, vendor.UTF8String);\n    FFPhysicalMemoryUpdateVendorString(device);\n    ffStrbufInitS(&device->serial, serial.UTF8String);\n    ffCleanUpSmbiosValue(&device->serial);\n    ffStrbufInitS(&device->partNumber, partNumber.UTF8String);\n    ffCleanUpSmbiosValue(&device->partNumber);\n    device->size = 0;\n    device->maxSpeed = 0;\n    device->runningSpeed = 0;\n    device->installed = true;\n    device->ecc = ecc;\n\n    if (size)\n    {\n        char* unit = NULL;\n        device->size = strtoul(size.UTF8String, &unit, 10);\n        if (*unit == ' ') ++unit;\n\n        switch (*unit)\n        {\n            case 'G': device->size *= 1024ULL * 1024 * 1024; break;\n            case 'M': device->size *= 1024ULL * 1024; break;\n            case 'K': device->size *= 1024ULL; break;\n            case 'T': device->size *= 1024ULL * 1024 * 1024 * 1024; break;\n        }\n    }\n\n    if (speed)\n    {\n        char* unit = NULL;\n        device->maxSpeed = (uint32_t) strtoul(speed.UTF8String, &unit, 10);\n        if (*unit == ' ') ++unit;\n\n        switch (*unit)\n        {\n            case 'T': device->maxSpeed *= 1000 * 1000; break;\n            case 'G': device->maxSpeed *= 1000; break;\n            case 'K': device->maxSpeed /= 1000; break;\n        }\n        device->runningSpeed = device->maxSpeed;\n    }\n}\n\nstatic const char* detectFromSystemProfiler(FFlist* result)\n{\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n    if (ffProcessAppendStdOut(&buffer, (char* const[]) {\n        \"system_profiler\",\n        \"SPMemoryDataType\",\n        \"-xml\",\n        \"-detailLevel\",\n        \"full\",\n        NULL\n    }) != NULL)\n        return \"Starting `system_profiler SPMemoryDataType -xml -detailLevel full` failed\";\n\n    NSArray* arr = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytes:buffer.chars length:buffer.length]\n                    options:NSPropertyListImmutable\n                    format:nil\n                    error:nil];\n    if (!arr || !arr.count)\n        return \"system_profiler SPMemoryDataType returned an empty array\";\n\n    for (NSDictionary* data in arr[0][@\"_items\"])\n    {\n        if (data[@\"_items\"])\n        {\n            // for Intel\n            for (NSDictionary* item in data[@\"_items\"])\n            {\n                appendDevice(result,\n                    item[@\"dimm_type\"],\n                    item[@\"dimm_manufacturer\"],\n                    item[@\"dimm_size\"],\n                    item[@\"_name\"],\n                    item[@\"dimm_serial_number\"],\n                    item[@\"dimm_part_number\"],\n                    item[@\"dimm_speed\"],\n                    !![data[@\"global_ecc_state\"] isEqualToString:@\"ecc_enabled\"]);\n            }\n        }\n        else\n        {\n            // for Apple Silicon\n            appendDevice(result,\n                data[@\"dimm_type\"],\n                data[@\"dimm_manufacturer\"],\n                data[@\"SPMemoryDataType\"],\n                nil,\n                nil,\n                nil,\n                nil,\n                false);\n        }\n    }\n\n    return NULL;\n}\n\nFF_MAYBE_UNUSED static const char* detectFromIokit(FFlist* result)\n{\n    FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDevice = IORegistryEntryFromPath(MACH_PORT_NULL, \"IODeviceTree:/chosen\");\n    if (!entryDevice)\n        return \"IORegistryEntryFromPath() failed\";\n\n    FF_CFTYPE_AUTO_RELEASE CFTypeRef dramType = IORegistryEntryCreateCFProperty(entryDevice, CFSTR(\"dram-type\"), kCFAllocatorDefault, 0);\n    FF_CFTYPE_AUTO_RELEASE CFTypeRef dramSize = IORegistryEntryCreateCFProperty(entryDevice, CFSTR(\"dram-size\"), kCFAllocatorDefault, 0);\n    FF_CFTYPE_AUTO_RELEASE CFTypeRef dramVendor = IORegistryEntryCreateCFProperty(entryDevice, CFSTR(\"dram-vendor\"), kCFAllocatorDefault, 0);\n    if (!dramType || !dramSize || !dramVendor)\n        return \"IORegistryEntryCreateCFProperty() failed\";\n\n    FFPhysicalMemoryResult* device = ffListAdd(result);\n    ffStrbufInit(&device->type);\n    ffStrbufInit(&device->formFactor);\n    ffStrbufInit(&device->locator);\n    ffStrbufInit(&device->vendor);\n    ffStrbufInit(&device->serial);\n    ffStrbufInit(&device->partNumber);\n    device->size = 0;\n    device->maxSpeed = 0;\n    device->runningSpeed = 0;\n    device->installed = true;\n    device->ecc = false;\n\n    ffCfStrGetString(dramType, &device->type);\n    ffCfStrGetString(dramVendor, &device->vendor);\n    ffCfNumGetInt64(dramSize, (int64_t*) &device->size);\n    return NULL;\n}\n\nconst char* ffDetectPhysicalMemory(FFlist* result)\n{\n    #if __aarch64__\n    if (detectFromIokit(result) == NULL)\n        return NULL;\n    #endif\n\n    return detectFromSystemProfiler(result);\n}\n"
  },
  {
    "path": "src/detection/physicalmemory/physicalmemory_linux.c",
    "content": "#include \"physicalmemory.h\"\n#include \"common/smbiosHelper.h\"\n\n// 7.18\ntypedef struct FFSmbiosMemoryDevice\n{\n    FFSmbiosHeader Header;\n\n    // 2.1+\n    uint16_t PhysicalMemoryArrayHandle; // varies\n    uint16_t MemoryErrorInformationHandle; //varies\n    uint16_t TotalWidth; // varies\n    uint16_t DataWidth; // varies\n    uint16_t Size; // varies\n    uint8_t FormFactor; // enum\n    uint8_t DeviceSet; // varies\n    uint8_t DeviceLocator; // string\n    uint8_t BankLocator; // string\n    uint8_t MemoryType; // enum\n    uint16_t TypeDetail; // bit field\n\n    // 2.3+\n    uint16_t Speed; // varies\n    uint8_t Manufacturer; // string\n    uint8_t SerialNumber; // string\n    uint8_t AssetTag; // string\n    uint8_t PartNumber; // string\n\n    // 2.6+\n    uint8_t Attributes; // varies\n\n    // 2.7+\n    uint32_t ExtendedSize; // varies\n    uint16_t ConfiguredMemorySpeed; // varies\n\n    // 2.8+\n    uint16_t MinimumVoltage; // varies\n    uint16_t MaximumVoltage; // varies\n    uint16_t ConfiguredVoltage; // varies\n\n    // 3.2+\n    uint8_t MemoryTechnology; // varies\n    uint16_t MemoryOperatingMode; // bit field\n    uint8_t FirmwareVersion; // string\n    uint16_t ModuleManufacturerID; // varies\n    uint16_t ModuleProductID; // varies\n    uint16_t MemorySubsystemControllerManufacturerID; // vaies\n    uint16_t MemorySubsystemControllerProductID; // varies\n    uint64_t NonVolatileSize; // varies\n    uint64_t VolatileSize; // varies\n    uint64_t CacheSize; // varies\n    uint64_t LogicalSize; // varies\n\n    // 3.3+\n    uint32_t ExtendedSpeed; // varies\n    uint32_t ExtendedConfiguredSpeed; // varies\n\n    // 3.7+\n    uint16_t Pmic0ManufacturerID; // varies\n    uint16_t Pmic0RevisionNumber; // varies\n    uint16_t RcdManufacturerID; // varies\n    uint16_t RcdRevisionNumber; // varies\n} __attribute__((__packed__)) FFSmbiosMemoryDevice;\n\nstatic_assert(offsetof(FFSmbiosMemoryDevice, RcdRevisionNumber) == 0x62,\n    \"FFSmbiosMemoryDevice: Wrong struct alignment\");\n\nconst char* ffDetectPhysicalMemory(FFlist* result)\n{\n    const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();\n    if (!smbiosTable)\n        return \"Failed to get SMBIOS data\";\n\n    const FFSmbiosMemoryDevice* data = (const FFSmbiosMemoryDevice*) (*smbiosTable)[FF_SMBIOS_TYPE_MEMORY_DEVICE];\n    if (!data)\n        return \"Memory device is not found in SMBIOS data\";\n\n    for (; data->Header.Type < FF_SMBIOS_TYPE_END_OF_TABLE;\n        data = (const FFSmbiosMemoryDevice*) ffSmbiosNextEntry(&data->Header))\n    {\n        if (data->Header.Type != FF_SMBIOS_TYPE_MEMORY_DEVICE) continue;\n\n        const char* strings = (const char*) data + data->Header.Length;\n        bool installed = data->Size != 0;\n\n        FFPhysicalMemoryResult* device = ffListAdd(result);\n        ffStrbufInit(&device->type);\n        ffStrbufInit(&device->formFactor);\n        ffStrbufInit(&device->locator);\n        ffStrbufInit(&device->vendor);\n        ffStrbufInit(&device->serial);\n        ffStrbufInit(&device->partNumber);\n        device->size = 0;\n        device->maxSpeed = 0;\n        device->runningSpeed = 0;\n        device->installed = installed;\n        device->ecc = false;\n\n        if (installed && data->TotalWidth != 0xFFFF && data->DataWidth != 0xFFFF)\n            device->ecc = data->TotalWidth > data->DataWidth;\n\n        if (installed && data->Size != 0xFFFF)\n        {\n            if (data->Size == 0x7FFF)\n                device->size = (data->ExtendedSize & ~(1ULL << 31)) * 1024ULL * 1024ULL;\n            else if (data->Size & (1 << 15))\n            {\n                // in kB\n                device->size = (data->Size & ~(1ULL << 15)) * 1024ULL;\n            }\n            else\n            {\n                // in MB\n                device->size = data->Size * 1024ULL * 1024ULL;\n            }\n        }\n\n        // https://github.com/fastfetch-cli/fastfetch/issues/1051#issuecomment-2206687345\n        const char* lbank = ffSmbiosLocateString(strings, data->BankLocator);\n        const char* ldevice = ffSmbiosLocateString(strings, data->DeviceLocator);\n        if (lbank && ldevice)\n            ffStrbufSetF(&device->locator, \"%s/%s\", lbank, ldevice);\n        else if (lbank)\n            ffStrbufSetS(&device->locator, lbank);\n        else if (ldevice)\n            ffStrbufSetS(&device->locator, ldevice);\n\n        const char* formFactorNames[] = {\n            NULL,              // 0x00 (Placeholder for indexing)\n            \"Other\",           // 0x01\n            \"Unknown\",         // 0x02\n            \"SIMM\",            // 0x03\n            \"SIP\",             // 0x04\n            \"Chip\",            // 0x05\n            \"DIP\",             // 0x06\n            \"ZIP\",             // 0x07\n            \"Proprietary Card\",// 0x08\n            \"DIMM\",            // 0x09\n            \"TSOP\",            // 0x0A\n            \"Row of chips\",    // 0x0B\n            \"RIMM\",            // 0x0C\n            \"SODIMM\",          // 0x0D\n            \"SRIMM\",           // 0x0E\n            \"FBDIMM\",          // 0x0F\n            \"Die\",             // 0x10\n        };\n        if (data->FormFactor > 0 && data->FormFactor < ARRAY_SIZE(formFactorNames))\n            ffStrbufSetS(&device->formFactor, formFactorNames[data->FormFactor]);\n        else\n            ffStrbufSetF(&device->formFactor, \"Unknown (%d)\", (int) data->FormFactor);\n\n        const char* memoryTypeNames[] = {\n            NULL,         // 0x00 (Placeholder for indexing)\n            \"Other\",      // 0x01\n            \"Unknown\",    // 0x02\n            \"DRAM\",       // 0x03\n            \"EDRAM\",      // 0x04\n            \"VRAM\",       // 0x05\n            \"SRAM\",       // 0x06\n            \"RAM\",        // 0x07\n            \"ROM\",        // 0x08\n            \"FLASH\",      // 0x09\n            \"EEPROM\",     // 0x0A\n            \"FEPROM\",     // 0x0B\n            \"EPROM\",      // 0x0C\n            \"CDRAM\",      // 0x0D\n            \"3DRAM\",      // 0x0E\n            \"SDRAM\",      // 0x0F\n            \"SGRAM\",      // 0x10\n            \"RDRAM\",      // 0x11\n            \"DDR\",        // 0x12\n            \"DDR2\",       // 0x13\n            \"DDR2 FB-DIMM\", // 0x14\n            \"Reserved\",   // 0x15\n            \"Reserved\",   // 0x16\n            \"Reserved\",   // 0x17\n            \"DDR3\",       // 0x18\n            \"FBD2\",       // 0x19\n            \"DDR4\",       // 0x1A\n            \"LPDDR\",      // 0x1B\n            \"LPDDR2\",     // 0x1C\n            \"LPDDR3\",     // 0x1D\n            \"LPDDR4\",     // 0x1E\n            \"Logical non-volatile device\", // 0x1F\n            \"HBM\",        // 0x20\n            \"HBM2\",       // 0x21\n            \"DDR5\",       // 0x22\n            \"LPDDR5\",     // 0x23\n            \"HBM3\",       // 0x24\n        };\n        if (!installed)\n            ffStrbufSetStatic(&device->type, \"Empty\");\n        else if (data->MemoryType > 0 && data->MemoryType < ARRAY_SIZE(memoryTypeNames))\n            ffStrbufSetStatic(&device->type, memoryTypeNames[data->MemoryType]);\n        else\n            ffStrbufSetF(&device->type, \"Unknown (%d)\", (int) data->MemoryType);\n\n        if (installed && data->Header.Length > offsetof(FFSmbiosMemoryDevice, Speed)) // 2.3+\n        {\n            if (data->Speed)\n                device->maxSpeed = data->Speed == 0xFFFF ? data->ExtendedSpeed : data->Speed;\n\n            ffStrbufSetStatic(&device->vendor, ffSmbiosLocateString(strings, data->Manufacturer));\n            ffCleanUpSmbiosValue(&device->vendor);\n            FFPhysicalMemoryUpdateVendorString(device);\n\n            ffStrbufSetStatic(&device->serial, ffSmbiosLocateString(strings, data->SerialNumber));\n            ffCleanUpSmbiosValue(&device->serial);\n\n            ffStrbufSetStatic(&device->partNumber, ffSmbiosLocateString(strings, data->PartNumber));\n            ffCleanUpSmbiosValue(&device->partNumber);\n        }\n\n        if (installed && data->Header.Length > offsetof(FFSmbiosMemoryDevice, ConfiguredMemorySpeed)) // 2.7+\n        {\n            if (data->ConfiguredMemorySpeed)\n                device->runningSpeed = data->ConfiguredMemorySpeed == 0xFFFF\n                    ? data->ExtendedConfiguredSpeed : data->ConfiguredMemorySpeed;\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/physicalmemory/physicalmemory_nosupport.c",
    "content": "#include \"physicalmemory.h\"\n\nconst char* ffDetectPhysicalMemory(FF_MAYBE_UNUSED FFlist* result)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/poweradapter/poweradapter.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/poweradapter/option.h\"\n\ntypedef struct FFPowerAdapterResult\n{\n    FFstrbuf description;\n    FFstrbuf name;\n    FFstrbuf modelName;\n    FFstrbuf manufacturer;\n    FFstrbuf serial;\n    int watts;\n} FFPowerAdapterResult;\n\nconst char* ffDetectPowerAdapter(FFlist* results);\n"
  },
  {
    "path": "src/detection/poweradapter/poweradapter_apple.c",
    "content": "#include \"fastfetch.h\"\n#include \"poweradapter.h\"\n#include \"common/apple/cf_helpers.h\"\n\n#include <IOKit/ps/IOPowerSources.h>\n#include <IOKit/ps/IOPSKeys.h>\n\nconst char* ffDetectPowerAdapter(FFlist* results)\n{\n    FF_CFTYPE_AUTO_RELEASE CFDictionaryRef details = IOPSCopyExternalPowerAdapterDetails();\n    if (details && CFDictionaryContainsKey(details, CFSTR(kIOPSPowerAdapterWattsKey)))\n    {\n        FFPowerAdapterResult* adapter = ffListAdd(results);\n\n        ffStrbufInit(&adapter->name);\n        ffStrbufInit(&adapter->description);\n        ffStrbufInit(&adapter->manufacturer);\n        ffStrbufInit(&adapter->modelName);\n        ffStrbufInit(&adapter->serial);\n        adapter->watts = 0;\n\n        ffCfDictGetString(details, CFSTR(kIOPSNameKey), &adapter->name);\n        if (ffCfDictGetString(details, CFSTR(\"Model\"), &adapter->modelName) != NULL)\n        {\n            int adapterId;\n            if (ffCfDictGetInt(details, CFSTR(kIOPSPowerAdapterIDKey), &adapterId) == 0)\n                ffStrbufSetF(&adapter->modelName, \"%d\", adapterId);\n        }\n        ffCfDictGetString(details, CFSTR(\"Manufacturer\"), &adapter->manufacturer);\n        ffCfDictGetString(details, CFSTR(\"Description\"), &adapter->description);\n        if (ffCfDictGetString(details, CFSTR(\"SerialString\"), &adapter->serial) != NULL)\n        {\n            int serialNumber;\n            if (ffCfDictGetInt(details, CFSTR(kIOPSPowerAdapterSerialNumberKey), &serialNumber) == 0)\n                ffStrbufSetF(&adapter->serial, \"%X\", serialNumber);\n        }\n        ffCfDictGetInt(details, CFSTR(kIOPSPowerAdapterWattsKey), &adapter->watts);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/poweradapter/poweradapter_linux.c",
    "content": "#include \"poweradapter.h\"\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n\n#include <dirent.h>\n#include <unistd.h>\n#include <fcntl.h>\n\nstatic void parsePowerAdapter(int dfd, FF_MAYBE_UNUSED const char* id, FFlist* results)\n{\n    FF_STRBUF_AUTO_DESTROY tmpBuffer = ffStrbufCreate();\n\n    //type must exist and be \"Mains\"\n    if (ffReadFileBufferRelative(dfd, \"type\", &tmpBuffer))\n        ffStrbufTrimRightSpace(&tmpBuffer);\n\n    if(!ffStrbufIgnCaseEqualS(&tmpBuffer, \"Mains\"))\n        return;\n\n    //scope may not exist or must not be \"Device\" (?)\n    if (ffReadFileBufferRelative(dfd, \"scope\", &tmpBuffer))\n        ffStrbufTrimRightSpace(&tmpBuffer);\n\n    if(ffStrbufIgnCaseEqualS(&tmpBuffer, \"Device\"))\n        return;\n\n    char online = '\\0';\n    ffReadFileDataRelative(dfd, \"online\", sizeof(online), &online);\n\n    if (online != '1')\n        return;\n\n    //input_power_limit must exist and be not empty\n    if (!ffReadFileBufferRelative(dfd, \"input_power_limit\", &tmpBuffer) || tmpBuffer.length == 0)\n        return;\n\n    FFPowerAdapterResult* result = ffListAdd(results);\n    ffStrbufInit(&result->name);\n    ffStrbufInit(&result->description);\n    result->watts = (int) (ffStrbufToDouble(&tmpBuffer, 0) / 1e6 + 0.5);\n    ffStrbufInit(&result->manufacturer);\n    ffStrbufInit(&result->modelName);\n    ffStrbufInit(&result->serial);\n\n    if (ffReadFileBufferRelative(dfd, \"manufacturer\", &result->manufacturer))\n        ffStrbufTrimRightSpace(&result->manufacturer);\n    else if (ffStrEquals(id, \"macsmc-ac\")) // asahi\n        ffStrbufSetStatic(&result->manufacturer, \"Apple Inc.\");\n\n    if (ffReadFileBufferRelative(dfd, \"model_name\", &result->modelName))\n        ffStrbufTrimRightSpace(&result->modelName);\n\n    if (ffReadFileBufferRelative(dfd, \"serial_number\", &result->serial))\n        ffStrbufTrimRightSpace(&result->serial);\n}\n\nconst char* ffDetectPowerAdapter(FFlist* results)\n{\n    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(\"/sys/class/power_supply/\");\n    if(dirp == NULL)\n        return \"opendir(\\\"/sys/class/power_supply/\\\") == NULL\";\n\n    struct dirent* entry;\n    while((entry = readdir(dirp)) != NULL)\n    {\n        if(entry->d_name[0] == '.')\n            continue;\n\n        FF_AUTO_CLOSE_FD int dfd = openat(dirfd(dirp), entry->d_name, O_RDONLY | O_CLOEXEC);\n        if (dfd > 0) parsePowerAdapter(dfd, entry->d_name, results);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/poweradapter/poweradapter_nosupport.c",
    "content": "#include \"poweradapter.h\"\n\nconst char* ffDetectPowerAdapter(FF_MAYBE_UNUSED FFlist* results)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/processes/processes.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\nconst char* ffDetectProcesses(uint32_t* result);\n"
  },
  {
    "path": "src/detection/processes/processes_bsd.c",
    "content": "#include \"processes.h\"\n\n#include <sys/sysctl.h>\n#ifdef __FreeBSD__\n    #include <sys/types.h>\n    #include <sys/user.h>\n#endif\n\n#ifndef KERN_PROC_PROC\n    #define KERN_PROC_PROC KERN_PROC_ALL // Apple\n#endif\n\nconst char* ffDetectProcesses(uint32_t* result)\n{\n    int request[] = {CTL_KERN, KERN_PROC, KERN_PROC_PROC};\n    size_t length;\n\n    if(sysctl(request, ARRAY_SIZE(request), NULL, &length, NULL, 0) != 0)\n        return \"sysctl({CTL_KERN, KERN_PROC, KERN_PROC_PROC}) failed\";\n\n    *result = (uint32_t)(length / sizeof(struct kinfo_proc));\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/processes/processes_haiku.c",
    "content": "#include \"processes.h\"\n\n#include <OS.h>\n\nconst char* ffDetectProcesses(uint32_t* result)\n{\n    system_info info;\n    if (get_system_info(&info) != B_OK)\n        return \"Error getting system info\";\n\n    *result = info.used_teams;\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/processes/processes_linux.c",
    "content": "#include \"processes.h\"\n\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n\nconst char* ffDetectProcesses(uint32_t* result)\n{\n    FF_AUTO_CLOSE_DIR DIR* dir = opendir(\"/proc\");\n    if(dir == NULL)\n        return \"opendir(\\\"/proc\\\") failed\";\n\n    uint32_t num = 0;\n\n    struct dirent* entry;\n    while ((entry = readdir(dir)) != NULL)\n    {\n        if (\n        #ifdef _DIRENT_HAVE_D_TYPE\n                (entry->d_type == DT_DIR || entry->d_type == DT_UNKNOWN) &&\n        #endif\n                ffCharIsDigit(entry->d_name[0]))\n            ++num;\n    }\n\n    *result = num;\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/processes/processes_nbsd.c",
    "content": "#include \"processes.h\"\n\n#include <sys/sysctl.h>\n\nconst char* ffDetectProcesses(uint32_t* result)\n{\n    int request[] = {CTL_KERN, KERN_PROC2, KERN_PROC_ALL, -1, sizeof(struct kinfo_proc2), 0};\n    size_t length = 0;\n\n    if(sysctl(request, ARRAY_SIZE(request), NULL, &length, NULL, 0) != 0)\n        return \"sysctl({CTL_KERN, KERN_PROC2, KERN_PROC_ALL}) failed\";\n\n    *result = (uint32_t)(length / sizeof(struct kinfo_proc2));\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/processes/processes_nosupport.c",
    "content": "#include \"processes.h\"\n\nconst char* ffDetectProcesses(uint32_t* result)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/processes/processes_obsd.c",
    "content": "#include \"processes.h\"\n\n#include <sys/param.h>\n#include <sys/sysctl.h>\n#include <kvm.h>\n\nconst char* ffDetectProcesses(uint32_t* result)\n{\n    kvm_t* kd = kvm_open(NULL, NULL, NULL, KVM_NO_FILES, NULL);\n    const void* ret = kvm_getprocs(kd, KERN_PROC_ALL, 0, 1, result);\n    kvm_close(kd);\n    if (!ret) return \"kvm_getprocs() failed\";\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/processes/processes_windows.c",
    "content": "#include \"processes.h\"\n#include \"common/mallocHelper.h\"\n\n#include <ntstatus.h>\n#include <winternl.h>\n\nconst char* ffDetectProcesses(uint32_t* result)\n{\n    SYSTEM_PROCESS_INFORMATION* FF_AUTO_FREE pstart = NULL;\n\n    // Multiple attempts in case processes change while\n    // we are in the middle of querying them.\n    ULONG size = 0;\n    for (int attempts = 0;; ++attempts)\n    {\n        if (size)\n        {\n            pstart = (SYSTEM_PROCESS_INFORMATION*)realloc(pstart, size);\n            assert(pstart);\n        }\n        NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, pstart, size, &size);\n        if(NT_SUCCESS(status))\n            break;\n        else if(status == STATUS_INFO_LENGTH_MISMATCH && attempts < 4)\n            size += sizeof(SYSTEM_PROCESS_INFORMATION) * 5;\n        else\n            return \"NtQuerySystemInformation(SystemProcessInformation) failed\";\n    }\n\n    *result = 1; //Init with 1 because we test for ptr->NextEntryOffset\n    for (SYSTEM_PROCESS_INFORMATION* ptr = pstart; ptr->NextEntryOffset; ptr = (SYSTEM_PROCESS_INFORMATION*)((uint8_t*)ptr + ptr->NextEntryOffset))\n        ++*result;\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/publicip/publicip.c",
    "content": "#include \"publicip.h\"\n#include \"common/networking.h\"\n\n#define FF_UNITIALIZED ((const char*)(uintptr_t) -1)\nstatic FFNetworkingState states[2];\nstatic const char* statuses[2] = { FF_UNITIALIZED, FF_UNITIALIZED };\n\nvoid ffPreparePublicIp(FFPublicIPOptions* options)\n{\n    FFNetworkingState* state = &states[options->ipv6];\n    const char** status = &statuses[options->ipv6];\n    if (*status != FF_UNITIALIZED)\n    {\n        fputs(\"Error: PublicIp module can only be used once due to internal limitations\\n\", stderr);\n        exit(1);\n    }\n\n    state->timeout = options->timeout;\n    state->ipv6 = options->ipv6;\n\n    if (options->url.length == 0)\n    {\n        state->compression = true;\n        state->tfo = true;\n        *status = ffNetworkingSendHttpRequest(state, options->ipv6 ? \"v6.ipinfo.io\" : \"ipinfo.io\", \"/json\", NULL);\n    }\n    else\n    {\n        FF_STRBUF_AUTO_DESTROY host = ffStrbufCreateCopy(&options->url);\n        uint32_t hostStartIndex = ffStrbufFirstIndexS(&host, \"://\");\n        if (hostStartIndex < host.length)\n        {\n            if (hostStartIndex != 4 || !ffStrbufStartsWithIgnCaseS(&host, \"http\"))\n            {\n                fputs(\"Error: only http: protocol is supported. Use `Command` module with `curl` if needed\\n\", stderr);\n                exit(1);\n            }\n            ffStrbufSubstrAfter(&host, hostStartIndex + (uint32_t) (strlen(\"://\") - 1));\n        }\n        uint32_t pathStartIndex = ffStrbufFirstIndexC(&host, '/');\n\n        FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();\n        if(pathStartIndex != host.length)\n        {\n            ffStrbufAppendNS(&path, pathStartIndex, host.chars + (host.length - pathStartIndex));\n            host.length = pathStartIndex;\n            host.chars[pathStartIndex] = '\\0';\n        }\n\n        *status = ffNetworkingSendHttpRequest(state, host.chars, path.length == 0 ? \"/\" : path.chars, NULL);\n    }\n}\n\nstatic inline void wrapYyjsonFree(yyjson_doc** doc)\n{\n    assert(doc);\n    if (*doc)\n        yyjson_doc_free(*doc);\n}\n\nconst char* ffDetectPublicIp(FFPublicIPOptions* options, FFPublicIpResult* result)\n{\n    FFNetworkingState* state = &states[options->ipv6];\n    const char** status = &statuses[options->ipv6];\n    if (*status == FF_UNITIALIZED)\n        ffPreparePublicIp(options);\n\n    if (*status != NULL)\n        return *status;\n\n    FF_STRBUF_AUTO_DESTROY response = ffStrbufCreateA(4096);\n    const char* error = ffNetworkingRecvHttpResponse(state, &response);\n\n    *state = (FFNetworkingState){};\n    *status = FF_UNITIALIZED;\n\n    if (error == NULL)\n        ffStrbufSubstrAfterFirstS(&response, \"\\r\\n\\r\\n\");\n    else\n        return error;\n\n    if (response.length == 0)\n        return \"Empty server response received\";\n\n    if (options->url.length == 0)\n    {\n        yyjson_doc* __attribute__((__cleanup__(wrapYyjsonFree))) doc = yyjson_read_opts(response.chars, response.length, 0, NULL, NULL);\n        if (doc)\n        {\n            yyjson_val* root = yyjson_doc_get_root(doc);\n            ffStrbufAppendJsonVal(&result->ip, yyjson_obj_get(root, \"ip\"));\n            ffStrbufDestroy(&result->location);\n            ffStrbufInitF(&result->location, \"%s, %s\", yyjson_get_str(yyjson_obj_get(root, \"city\")), yyjson_get_str(yyjson_obj_get(root, \"country\")));\n            return NULL;\n        }\n    }\n\n    ffStrbufDestroy(&result->ip);\n    ffStrbufInitMove(&result->ip, &response);\n    ffStrbufTrimRightSpace(&result->ip);\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/publicip/publicip.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/publicip/option.h\"\n\ntypedef struct FFPublicIpResult\n{\n    FFstrbuf ip;\n    FFstrbuf location;\n} FFPublicIpResult;\n\nvoid ffPreparePublicIp(FFPublicIPOptions* options);\nconst char* ffDetectPublicIp(FFPublicIPOptions* options, FFPublicIpResult* result);\n"
  },
  {
    "path": "src/detection/sound/audio_oss_sunos.h",
    "content": "/*\n * CDDL HEADER START\n *\n * The contents of this file are subject to the terms of the\n * Common Development and Distribution License (the \"License\").\n * You may not use this file except in compliance with the License.\n *\n * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE\n * or http://www.opensolaris.org/os/licensing.\n * See the License for the specific language governing permissions\n * and limitations under the License.\n *\n * When distributing Covered Code, include this CDDL HEADER in each\n * file and include the License file at usr/src/OPENSOLARIS.LICENSE.\n * If applicable, add the following below this CDDL HEADER, with the\n * fields enclosed by brackets \"[]\" replaced with your own identifying\n * information: Portions Copyright [yyyy] [name of copyright owner]\n *\n * CDDL HEADER END\n */\n/*\n * Copyright (C) 4Front Technologies 1996-2008.\n *\n * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.\n * Use is subject to license terms.\n */\n\n\n#ifndef\t_SYS_AUDIO_OSS_H\n#define\t_SYS_AUDIO_OSS_H\n\n#include <sys/types.h>\n#include <sys/time.h>\n\n/*\n * These are the ioctl calls for all Solaris /dev/dsp and /dev/mixer audio\n * devices.\n *\n * Note that the contents of this file include definitions which exist\n * primarily for compatibility.  Many of the defines here are not\n * actually implemented, but exist solely to facilitate compilation of\n * programs from other operating systems.  Other definitions here may\n * not be fully supported or may otherwise be obsolete. There are many\n * things in this file which should not be used on SunOS.\n *\n * Please read the documentation to determine which portions of the\n * API are fully supported and recommended for use in new\n * applications.\n */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/*\n * Buffer status queries.\n * SNDCTL_DSP_GETOSPACE and SNDCTL_DSP_GETISPACE\n */\ntypedef struct audio_buf_info {\n\tint fragments;\t\t/* # of available fragments */\n\tint fragstotal;\t\t/* Total # of fragments allocated */\n\tint fragsize;\t\t/* Size of a fragment in bytes */\n\tint bytes;\t\t/* Available space in bytes */\n\t/* Note! 'bytes' could be more than fragments*fragsize */\n} audio_buf_info;\n\n/*\n * Sync groups for audio devices.\n * SNDCTL_DSP_SYNCGROUP and SNDCTL_DSP_SYNCSTART\n */\ntypedef struct oss_syncgroup {\n\tint id;\n\tint mode;\n\tint filler[16];\n} oss_syncgroup;\n\n/*\n * SNDCTL_DSP_GETERROR\n */\ntypedef struct audio_errinfo {\n\tint play_underruns;\n\tint rec_overruns;\n\tunsigned int play_ptradjust;\n\tunsigned int rec_ptradjust;\n\tint play_errorcount;\n\tint rec_errorcount;\n\tint play_lasterror;\n\tint rec_lasterror;\n\tint play_errorparm;\n\tint rec_errorparm;\n\tint filler[16];\n} audio_errinfo;\n\n/*\n * SNDCTL_DSP_GETIPTR and SNDCTL_DSP_GETOPTR\n */\ntypedef struct count_info {\n\tunsigned int bytes;\t/* Total # of bytes processed */\n\tint blocks;\t\t/* # of fragment transitions since last time */\n\tint ptr;\t\t/* Current DMA pointer value */\n} count_info;\n\n/*\n * SNDCTL_DSP_CURENT_IPTR and SNDCTL_DSP_CURRENT_OPTR\n */\ntypedef struct {\n\tlong long samples;\t/* Total # of samples */\n\tint fifo_samples;\t/* Samples in device FIFO */\n\tint filler[32];\t\t/* For future use */\n} oss_count_t;\n\n/*\n * SNDCTL_DSP_GET_RECSRC_NAMES and SNDCTL_DSP_GET_PLAYTGT_NAMES\n */\n#define\tOSS_ENUM_MAXVALUE\t255\ntypedef struct oss_mixer_enuminfo {\n\tint dev;\n\tint ctrl;\n\tint nvalues;\n\tint version;\n\tshort strindex[OSS_ENUM_MAXVALUE];\n\tchar strings[3000];\n} oss_mixer_enuminfo;\n\n/*\n * Digital interface (S/PDIF) control interface\n * SNDCTL_DSP_READCTL and SNDCTL_DSP_WRITECTL\n */\ntypedef struct oss_digital_control {\n\tunsigned int caps;\n#define\tDIG_CBITIN_NONE\t\t0x00000000\n#define\tDIG_CBITIN_LIMITED\t0x00000001\n#define\tDIG_CBITIN_DATA\t\t0x00000002\n#define\tDIG_CBITIN_BYTE0\t0x00000004\n#define\tDIG_CBITIN_FULL\t\t0x00000008\n#define\tDIG_CBITIN_MASK\t\t0x0000000f\n#define\tDIG_CBITOUT_NONE\t0x00000000\n#define\tDIG_CBITOUT_LIMITED\t0x00000010\n#define\tDIG_CBITOUT_BYTE0\t0x00000020\n#define\tDIG_CBITOUT_FULL\t0x00000040\n#define\tDIG_CBITOUT_DATA\t0x00000080\n#define\tDIG_CBITOUT_MASK\t0x000000f0\n#define\tDIG_UBITIN\t\t0x00000100\n#define\tDIG_UBITOUT\t\t0x00000200\n#define\tDIG_VBITOUT\t\t0x00000400\n#define\tDIG_OUTRATE\t\t0x00000800\n#define\tDIG_INRATE\t\t0x00001000\n#define\tDIG_INBITS\t\t0x00002000\n#define\tDIG_OUTBITS\t\t0x00004000\n#define\tDIG_EXACT\t\t0x00010000\n#define\tDIG_PRO\t\t\t0x00020000\n#define\tDIG_CONSUMER\t\t0x00040000\n#define\tDIG_PASSTHROUGH\t\t0x00080000\n#define\tDIG_OUTSEL\t\t0x00100000\n\n\tunsigned int valid;\n#define\tVAL_CBITIN\t\t0x00000001\n#define\tVAL_UBITIN\t\t0x00000002\n#define\tVAL_CBITOUT\t\t0x00000004\n#define\tVAL_UBITOUT\t\t0x00000008\n#define\tVAL_ISTATUS\t\t0x00000010\n#define\tVAL_IRATE\t\t0x00000020\n#define\tVAL_ORATE\t\t0x00000040\n#define\tVAL_INBITS\t\t0x00000080\n#define\tVAL_OUTBITS\t\t0x00000100\n#define\tVAL_REQUEST\t\t0x00000200\n#define\tVAL_OUTSEL\t\t0x00000400\n\n#define\tVAL_OUTMASK (VAL_CBITOUT|VAL_UBITOUT|VAL_ORATE|VAL_OUTBITS|VAL_OUTSEL)\n\n\tunsigned int request;\n\tunsigned int param;\n#define\tSPD_RQ_PASSTHROUGH\t1\n\n\tunsigned char cbitin[24];\n\tunsigned char ubitin[24];\n\tunsigned char cbitout[24];\n\tunsigned char ubitout[24];\n\n\tunsigned int outsel;\n#define\tOUTSEL_DIGITAL\t\t1\n#define\tOUTSEL_ANALOG\t\t2\n#define\tOUTSEL_BOTH\t\t(OUTSEL_DIGITAL|OUTSEL_ANALOG)\n\n\tint in_data;\t\t/* Audio/data if autodetectable by receiver */\n#define\tIND_UNKNOWN\t\t0\n#define\tIND_AUDIO\t\t1\n#define\tIND_DATA\t\t2\n\n\tint in_locked;\t\t/* Receiver locked */\n#define\tLOCK_NOT_INDICATED\t0\n#define\tLOCK_UNLOCKED\t\t1\n#define\tLOCK_LOCKED\t\t2\n\n\tint in_quality;\t\t/* Input signal quality */\n#define\tIN_QUAL_NOT_INDICATED\t0\n#define\tIN_QUAL_POOR\t\t1\n#define\tIN_QUAL_GOOD\t\t2\n\n\tint in_vbit;\n\tint out_vbit;\t\t/* V bits */\n#define\tVBIT_NOT_INDICATED\t0\n#define\tVBIT_OFF\t\t1\n#define\tVBIT_ON\t\t\t2\n\n\tunsigned int in_errors;\t/* Various input error conditions */\n#define\tINERR_CRC\t\t0x0001\n#define\tINERR_QCODE_CRC\t\t0x0002\n#define\tINERR_PARITY\t\t0x0004\n#define\tINERR_BIPHASE\t\t0x0008\n\n\tint srate_in;\n\tint srate_out;\n\tint bits_in;\n\tint bits_out;\n\n\tint filler[32];\n} oss_digital_control;\n\n/*\n * The \"new\" mixer API.\n *\n * This improved mixer API makes it possible to access every possible feature\n * of every possible device. However you should read the mixer programming\n * section of the OSS API Developer's Manual. There is no chance that you\n * could use this interface correctly just by examining this header.\n */\n#define\tOSS_VERSION\t\t0x040003\n#define\tSOUND_VERSION\t\tOSS_VERSION\n\ntypedef struct oss_sysinfo {\n\tchar product[32];\t/* E.g. SunOS Audio */\n\tchar version[32];\t/* E.g. 4.0a */\n\tint versionnum;\t\t/* See OSS_GETVERSION */\n\tchar options[128];\t/* NOT SUPPORTED */\n\n\tint numaudios;\t\t/* # of audio/dsp devices */\n\tint openedaudio[8];\t/* Mask of audio devices are busy */\n\n\tint numsynths;\t\t/* NOT SUPPORTED, always 0 */\n\tint nummidis;\t\t/* NOT SUPPORTED, always 0 */\n\tint numtimers;\t\t/* NOT SUPPORTED, always 0 */\n\tint nummixers;\t\t/* # of mixer devices */\n\n\tint openedmidi[8];\t/* Mask of midi devices are busy */\n\tint numcards;\t\t/* Number of sound cards in the system */\n\tint numaudioengines;\t/* Number of audio engines in the system */\n\tchar license[16];\t/* E.g. \"GPL\" or \"CDDL\" */\n\tchar revision_info[256];\t/* For internal use */\n\tint filler[172];\t/* For future expansion */\n} oss_sysinfo;\n\ntypedef struct oss_mixext {\n\tint dev;\t\t/* Mixer device number */\n\tint ctrl;\t\t/* Extension number */\n\tint type;\t\t/* Entry type */\n#define\tMIXT_DEVROOT\t\t0\t/* Device root entry */\n#define\tMIXT_GROUP\t\t1\t/* Controller group */\n#define\tMIXT_ONOFF\t\t2\t/* OFF (0) or ON (1) */\n#define\tMIXT_ENUM\t\t3\t/* Enumerated (0 to maxvalue) */\n#define\tMIXT_MONOSLIDER\t\t4\t/* Mono slider (0 to 255) */\n#define\tMIXT_STEREOSLIDER\t5\t/* Stereo slider (dual 0 to 255) */\n#define\tMIXT_MESSAGE\t\t6\t/* (Readable) textual message */\n#define\tMIXT_MONOVU\t\t7\t/* VU meter value (mono) */\n#define\tMIXT_STEREOVU\t\t8\t/* VU meter value (stereo) */\n#define\tMIXT_MONOPEAK\t\t9\t/* VU meter peak value (mono) */\n#define\tMIXT_STEREOPEAK\t\t10\t/* VU meter peak value (stereo) */\n#define\tMIXT_RADIOGROUP\t\t11\t/* Radio button group */\n#define\tMIXT_MARKER\t\t12\t/* Separator between entries */\n#define\tMIXT_VALUE\t\t13\t/* Decimal value entry */\n#define\tMIXT_HEXVALUE\t\t14\t/* Hexadecimal value entry */\n#define\tMIXT_MONODB\t\t15\t/* OBSOLETE */\n#define\tMIXT_STEREODB\t\t16\t/* OBSOLETE */\n#define\tMIXT_SLIDER\t\t17\t/* Slider (mono, 31 bit int range) */\n#define\tMIXT_3D\t\t\t18\n#define\tMIXT_MONOSLIDER16\t19\t/* Mono slider (0-32767) */\n#define\tMIXT_STEREOSLIDER16\t20\t/* Stereo slider (dual 0-32767) */\n#define\tMIXT_MUTE\t\t21\t/* Mute=1, unmute=0 */\n\n\t/* Possible value range (minvalue to maxvalue) */\n\t/* Note that maxvalue may also be smaller than minvalue */\n\tint maxvalue;\n\tint minvalue;\n\n\tint flags;\n#define\tMIXF_READABLE\t0x00000001\t/* Has readable value */\n#define\tMIXF_WRITEABLE\t0x00000002\t/* Has writeable value */\n#define\tMIXF_POLL\t0x00000004\t/* May change itself */\n#define\tMIXF_HZ\t\t0x00000008\t/* Hertz scale */\n#define\tMIXF_STRING\t0x00000010\t/* Use dynamic extensions for value */\n#define\tMIXF_DYNAMIC\t0x00000010\t/* Supports dynamic extensions */\n#define\tMIXF_OKFAIL\t0x00000020\t/* Interpret value as 1=OK, 0=FAIL */\n#define\tMIXF_FLAT\t0x00000040\t/* NOT SUPPORTED */\n#define\tMIXF_LEGACY\t0x00000080\t/* NOT SUPPORTED */\n#define\tMIXF_CENTIBEL\t0x00000100\t/* Centibel (0.1 dB) step size */\n#define\tMIXF_DECIBEL\t0x00000200\t/* Step size of 1 dB */\n#define\tMIXF_MAINVOL\t0x00000400\t/* Main volume control */\n#define\tMIXF_PCMVOL\t0x00000800\t/* PCM output volume control */\n#define\tMIXF_RECVOL\t0x00001000\t/* PCM recording volume control */\n#define\tMIXF_MONVOL\t0x00002000\t/* Input->output monitor volume */\n#define\tMIXF_WIDE\t0x00004000\t/* NOT SUPPORTED */\n#define\tMIXF_DESCR\t0x00008000\t/* NOT SUPPORTED */\n#define\tMIXF_DISABLE\t0x00010000\t/* Control has been disabled */\n\n\tchar id[16];\t\t\t/* Mnemonic ID (internal use) */\n\tint parent;\t\t\t/* Entry# of parent (-1 if root) */\n\n\tint dummy;\t\t\t/* NOT SUPPORTED */\n\n\tint timestamp;\n\n\tchar data[64];\t\t\t/* Misc data (entry type dependent) */\n\tunsigned char enum_present[32];\t/* Mask of allowed enum values */\n\tint control_no;\t\t\t/* NOT SUPPORTED, always -1 */\n\n\tunsigned int desc;\t\t/* Scope flags, etc */\n#define\tMIXEXT_SCOPE_MASK\t\t0x0000003f\n#define\tMIXEXT_SCOPE_OTHER\t\t0x00000000\n#define\tMIXEXT_SCOPE_INPUT\t\t0x00000001\n#define\tMIXEXT_SCOPE_OUTPUT\t\t0x00000002\n#define\tMIXEXT_SCOPE_MONITOR\t\t0x00000003\n#define\tMIXEXT_SCOPE_RECSWITCH\t\t0x00000004\n\n\tchar extname[32];\n\tint update_counter;\n#ifdef\t_KERNEL\n\tint filler[6];\n\tint enumbit;\n#else\n\tint filler[7];\n#endif\n} oss_mixext;\n\ntypedef struct oss_mixext_root {\n\tchar id[16];\n\tchar name[48];\n} oss_mixext_root;\n\ntypedef struct oss_mixer_value {\n\tint dev;\n\tint ctrl;\n\tint value;\n\tint flags;\t\t/* Reserved for future use. Initialize to 0 */\n\tint timestamp;\t\t/* Must be set to oss_mixext.timestamp */\n\tint filler[8];\t\t/* Reserved for future use. Initialize to 0 */\n} oss_mixer_value;\n\n#define\tOSS_LONGNAME_SIZE\t64\n#define\tOSS_LABEL_SIZE\t\t16\n#define\tOSS_DEVNODE_SIZE\t32\ntypedef\tchar\toss_longname_t[OSS_LONGNAME_SIZE];\ntypedef\tchar\toss_label_t[OSS_LABEL_SIZE];\ntypedef\tchar\toss_devnode_t[OSS_DEVNODE_SIZE];\n\n\ntypedef struct oss_audioinfo {\n\tint dev;\t\t/* Audio device number */\n\tchar name[64];\n\tint busy;\t\t/* 0, OPEN_READ, OPEN_WRITE, OPEN_READWRITE */\n\tint pid;\t\t/* Process ID, not used in SunOS */\n\tint caps;\t\t/* PCM_CAP_INPUT, PCM_CAP_OUTPUT */\n\tint iformats;\t\t/* Supported input formats */\n\tint oformats;\t\t/* Supported output formats */\n\tint magic;\t\t/* Internal use only */\n\tchar cmd[64];\t\t/* Command using the device (if known) */\n\tint card_number;\n\tint port_number;\n\tint mixer_dev;\n\tint legacy_device;\t/* Obsolete field. Replaced by devnode */\n\tint enabled;\t\t/* 1=enabled, 0=device not ready */\n\tint flags;\t\t/* internal use only - no practical meaning */\n\tint min_rate;\t\t/* Minimum sample rate */\n\tint max_rate;\t\t/* Maximum sample rate */\n\tint min_channels;\t/* Minimum number of channels */\n\tint max_channels;\t/* Maximum number of channels */\n\tint binding;\t\t/* DSP_BIND_FRONT, etc. 0 means undefined */\n\tint rate_source;\n\tchar handle[32];\n#define\tOSS_MAX_SAMPLE_RATES\t20\t/* Cannot be changed  */\n\tunsigned int nrates;\t/* Array of supported sample rates */\n\tunsigned int rates[OSS_MAX_SAMPLE_RATES];\n\toss_longname_t song_name;\t/* Song name (if given) */\n\toss_label_t label;\t/* Device label (if given) */\n\tint latency;\t\t/* In usecs, -1=unknown */\n\toss_devnode_t devnode;\t/* Device special file name (absolute path) */\n\tint next_play_engine;\n\tint next_rec_engine;\n\tint filler[184];\n} oss_audioinfo;\n\ntypedef struct oss_mixerinfo {\n\tint dev;\n\tchar id[16];\n\tchar name[32];\n\tint modify_counter;\n\tint card_number;\n\tint port_number;\n\tchar handle[32];\n\tint magic;\t\t/* Reserved */\n\tint enabled;\t\t/* Reserved */\n\tint caps;\n#define\tMIXER_CAP_VIRTUAL\t0x00000001\n#define\tMIXER_CAP_LAYOUT_B\t0x00000002\t/* For internal use only */\n#define\tMIXER_CAP_NARROW\t0x00000004\t/* Conserve horiz space */\n\tint flags;\t\t/* Reserved */\n\tint nrext;\n\t/*\n\t * The priority field can be used to select the default\n\t * (motherboard) mixer device. The mixer with the highest\n\t * priority is the most preferred one. -2 or less means that\n\t * this device cannot be used as the default mixer.\n\t */\n\tint priority;\n\toss_devnode_t devnode;  /* Device special file name (absolute path) */\n\tint legacy_device;\n\tint filler[245];\t/* Reserved */\n} oss_mixerinfo;\n\ntypedef struct oss_card_info {\n\tint card;\n\tchar shortname[16];\n\tchar longname[128];\n\tint flags;\n\tchar hw_info[400];\n\tint intr_count;\n\tint ack_count;\n\tint filler[154];\n} oss_card_info;\n\ntypedef struct mixer_info {\t/* OBSOLETE */\n\tchar id[16];\n\tchar name[32];\n\tint modify_counter;\n\tint card_number;\n\tint port_number;\n\tchar handle[32];\n} mixer_info;\n\n#define\tMAX_PEAK_CHANNELS\t128\ntypedef unsigned short oss_peaks_t[MAX_PEAK_CHANNELS];\n\n/* For use with SNDCTL_DSP_GET_CHNORDER */\n#define\tCHID_UNDEF\t\t0\n#define\tCHID_L\t\t\t1\n#define\tCHID_R\t\t\t2\n#define\tCHID_C\t\t\t3\n#define\tCHID_LFE\t\t4\n#define\tCHID_LS\t\t\t5\n#define\tCHID_RS\t\t\t6\n#define\tCHID_LR\t\t\t7\n#define\tCHID_RR\t\t\t8\n#define\tCHNORDER_UNDEF\t\t0x0000000000000000ULL\n#define\tCHNORDER_NORMAL\t\t0x0000000087654321ULL\n\n\n#define\tOSSIOCPARM_MASK\t0x1fff\t\t/* parameters must be < 8192 bytes */\n#define\tOSSIOC_VOID\t0x00000000\t/* no parameters */\n#define\tOSSIOC_OUT\t0x20000000\t/* copy out parameters */\n#define\tOSSIOC_IN\t0x40000000\t/* copy in parameters */\n#define\tOSSIOC_INOUT\t(OSSIOC_IN|OSSIOC_OUT)\n#define\tOSSIOC_SZ(t)\t((sizeof (t) & OSSIOCPARM_MASK) << 16)\n#define\tOSSIOC_GETSZ(x)\t(((x) >> 16) & OSSIOCPARM_MASK)\n\n#define\t__OSSIO(x, y)\t\t((int)(OSSIOC_VOID|(x<<8)|y))\n#define\t__OSSIOR(x, y, t)\t((int)(OSSIOC_OUT|OSSIOC_SZ(t)|(x<<8)|y))\n#define\t__OSSIOW(x, y, t)\t((int)(OSSIOC_IN|OSSIOC_SZ(t)|(x<<8)|y))\n#define\t__OSSIOWR(x, y, t)\t((int)(OSSIOC_INOUT|OSSIOC_SZ(t)|(x<<8)|y))\n\n#define\tSNDCTL_SYSINFO\t\t__OSSIOR('X', 1, oss_sysinfo)\n#define\tOSS_SYSINFO\t\tSNDCTL_SYSINFO  /* Old name */\n\n#define\tSNDCTL_MIX_NRMIX\t__OSSIOR('X', 2, int)\n#define\tSNDCTL_MIX_NREXT\t__OSSIOWR('X', 3, int)\n#define\tSNDCTL_MIX_EXTINFO\t__OSSIOWR('X', 4, oss_mixext)\n#define\tSNDCTL_MIX_READ\t\t__OSSIOWR('X', 5, oss_mixer_value)\n#define\tSNDCTL_MIX_WRITE\t__OSSIOWR('X', 6, oss_mixer_value)\n\n#define\tSNDCTL_AUDIOINFO\t__OSSIOWR('X', 7, oss_audioinfo)\n#define\tSNDCTL_MIX_ENUMINFO\t__OSSIOWR('X', 8, oss_mixer_enuminfo)\n#define\tSNDCTL_MIDIINFO\t\t__OSSIO('X', 9)\n#define\tSNDCTL_MIXERINFO\t__OSSIOWR('X', 10, oss_mixerinfo)\n#define\tSNDCTL_CARDINFO\t\t__OSSIOWR('X', 11, oss_card_info)\n#define\tSNDCTL_ENGINEINFO\t__OSSIOWR('X', 12, oss_audioinfo)\n#define\tSNDCTL_AUDIOINFO_EX\t__OSSIOWR('X', 13, oss_audioinfo)\n#define\tSNDCTL_MIX_DESCRIPTION\t__OSSIOWR('X', 14, oss_mixer_enuminfo)\n\n/* ioctl codes 'X', 200-255 are reserved for internal use */\n\n/*\n * Few more \"globally\" available ioctl calls.\n */\n#define\tSNDCTL_SETSONG\t\t__OSSIOW('Y', 2, oss_longname_t)\n#define\tSNDCTL_GETSONG\t\t__OSSIOR('Y', 2, oss_longname_t)\n#define\tSNDCTL_SETNAME\t\t__OSSIOW('Y', 3, oss_longname_t)\n#define\tSNDCTL_SETLABEL\t\t__OSSIOW('Y', 4, oss_label_t)\n#define\tSNDCTL_GETLABEL\t\t__OSSIOR('Y', 4, oss_label_t)\n\n/*\n * IOCTL commands for /dev/dsp\n */\n#define\tSNDCTL_DSP_HALT\t\t__OSSIO('P', 0)\n#define\tSNDCTL_DSP_RESET\tSNDCTL_DSP_HALT /* Old name */\n#define\tSNDCTL_DSP_SYNC\t\t__OSSIO('P', 1)\n#define\tSNDCTL_DSP_SPEED\t__OSSIOWR('P', 2, int)\n\n#define\tSNDCTL_DSP_STEREO\t__OSSIOWR('P', 3, int)\t/* OBSOLETE */\n\n#define\tSNDCTL_DSP_GETBLKSIZE\t__OSSIOWR('P', 4, int)\n#define\tSNDCTL_DSP_SAMPLESIZE\tSNDCTL_DSP_SETFMT\n#define\tSNDCTL_DSP_CHANNELS\t__OSSIOWR('P', 6, int)\n#define\tSNDCTL_DSP_POST\t\t__OSSIO('P', 8)\n#define\tSNDCTL_DSP_SUBDIVIDE\t__OSSIOWR('P', 9, int)\n#define\tSNDCTL_DSP_SETFRAGMENT\t__OSSIOWR('P', 10, int)\n\n#define\tSNDCTL_DSP_GETFMTS\t__OSSIOR('P', 11, int)\t/* Returns a mask */\n#define\tSNDCTL_DSP_SETFMT\t__OSSIOWR('P', 5, int)\t/* Selects ONE fmt */\n\n#define\tSNDCTL_DSP_GETOSPACE\t__OSSIOR('P', 12, audio_buf_info)\n#define\tSNDCTL_DSP_GETISPACE\t__OSSIOR('P', 13, audio_buf_info)\n#define\tSNDCTL_DSP_NONBLOCK\t__OSSIO('P', 14)\t/* Obsolete */\n#define\tSNDCTL_DSP_GETCAPS\t__OSSIOR('P', 15, int)\n\n#define\tSNDCTL_DSP_GETTRIGGER\t__OSSIOR('P', 16, int)\n#define\tSNDCTL_DSP_SETTRIGGER\t__OSSIOW('P', 16, int)\n\n#define\tSNDCTL_DSP_GETIPTR\t__OSSIOR('P', 17, count_info)\n#define\tSNDCTL_DSP_GETOPTR\t__OSSIOR('P', 18, count_info)\n\n#define\tSNDCTL_DSP_SETSYNCRO\t__OSSIO('P', 21)\n#define\tSNDCTL_DSP_SETDUPLEX\t__OSSIO('P', 22)\n\n#define\tSNDCTL_DSP_PROFILE\t__OSSIOW('P', 23, int)   /* OBSOLETE */\n#define\tAPF_NORMAL\t0\t/* Normal applications */\n#define\tAPF_NETWORK\t1\t/* Underruns caused by \"external\" delay */\n#define\tAPF_CPUINTENS\t2\t/* Underruns caused by \"overheating\" the CPU */\n\n\n#define\tSNDCTL_DSP_GETODELAY\t__OSSIOR('P', 23, int)\n\n#define\tSNDCTL_DSP_GETPLAYVOL\t__OSSIOR('P', 24, int)\n#define\tSNDCTL_DSP_SETPLAYVOL\t__OSSIOWR('P', 24, int)\n#define\tSNDCTL_DSP_GETERROR\t__OSSIOR('P', 25, audio_errinfo)\n\n#define\tSNDCTL_DSP_READCTL\t__OSSIOWR('P', 26, oss_digital_control)\n#define\tSNDCTL_DSP_WRITECTL\t__OSSIOWR('P', 27, oss_digital_control)\n\n#define\tSNDCTL_DSP_SYNCGROUP\t__OSSIOWR('P', 28, oss_syncgroup)\n#define\tSNDCTL_DSP_SYNCSTART\t__OSSIOW('P', 29, int)\n\n#define\tSNDCTL_DSP_COOKEDMODE\t__OSSIOW('P', 30, int)\n\n#define\tSNDCTL_DSP_SILENCE\t__OSSIO('P', 31)\n#define\tSNDCTL_DSP_SKIP\t\t__OSSIO('P', 32)\n\n#define\tSNDCTL_DSP_HALT_INPUT\t__OSSIO('P', 33)\n#define\tSNDCTL_DSP_RESET_INPUT\tSNDCTL_DSP_HALT_INPUT   /* Old name */\n#define\tSNDCTL_DSP_HALT_OUTPUT\t__OSSIO('P', 34)\n#define\tSNDCTL_DSP_RESET_OUTPUT\tSNDCTL_DSP_HALT_OUTPUT  /* Old name */\n\n#define\tSNDCTL_DSP_LOW_WATER\t__OSSIOW('P', 34, int)\n\n#define\tSNDCTL_DSP_CURRENT_IPTR\t__OSSIOR('P', 35, oss_count_t)\n#define\tSNDCTL_DSP_CURRENT_OPTR\t__OSSIOR('P', 36, oss_count_t)\n\n#define\tSNDCTL_DSP_GET_RECSRC_NAMES\t__OSSIOR('P', 37, oss_mixer_enuminfo)\n#define\tSNDCTL_DSP_GET_RECSRC\t__OSSIOR('P', 38, int)\n#define\tSNDCTL_DSP_SET_RECSRC\t__OSSIOWR('P', 38, int)\n\n#define\tSNDCTL_DSP_GET_PLAYTGT_NAMES\t__OSSIOR('P', 39, oss_mixer_enuminfo)\n#define\tSNDCTL_DSP_GET_PLAYTGT\t__OSSIOR('P', 40, int)\n#define\tSNDCTL_DSP_SET_PLAYTGT\t__OSSIOWR('P', 40, int)\n#define\tSNDCTL_DSP_GETRECVOL\t__OSSIOR('P', 41, int)\n#define\tSNDCTL_DSP_SETRECVOL\t__OSSIOWR('P', 41, int)\n\n#define\tSNDCTL_DSP_GET_CHNORDER\t__OSSIOR('P', 42, unsigned long long)\n#define\tSNDCTL_DSP_SET_CHNORDER\t__OSSIOWR('P', 42, unsigned long long)\n\n#define\tSNDCTL_DSP_GETIPEAKS\t__OSSIOR('P', 43, oss_peaks_t)\n#define\tSNDCTL_DSP_GETOPEAKS\t__OSSIOR('P', 44, oss_peaks_t)\n\n#define\tSNDCTL_DSP_POLICY\t__OSSIOW('P', 45, int)    /* See the manual */\n\n#define\tSNDCTL_DSP_GETCHANNELMASK\t__OSSIOWR('P', 64, int)\n#define\tSNDCTL_DSP_BIND_CHANNEL\t__OSSIOWR('P', 65, int)\n\n/*\n * These definitions are here for the benefit of compiling application\n * code.  Most of these are NOT implemented in the Solaris code,\n * however.  This is the older 3.x OSS API, and only the master input and\n * output levels are actually supported.\n */\n#define\tSOUND_MIXER_NRDEVICES\t28\n#define\tSOUND_MIXER_VOLUME\t0\n#define\tSOUND_MIXER_BASS\t1\n#define\tSOUND_MIXER_TREBLE\t2\n#define\tSOUND_MIXER_SYNTH\t3\n#define\tSOUND_MIXER_PCM\t\t4\n#define\tSOUND_MIXER_SPEAKER\t5\n#define\tSOUND_MIXER_LINE\t6\n#define\tSOUND_MIXER_MIC\t\t7\n#define\tSOUND_MIXER_CD\t\t8\n#define\tSOUND_MIXER_IMIX\t9\t/*  Recording monitor  */\n#define\tSOUND_MIXER_ALTPCM\t10\n#define\tSOUND_MIXER_RECLEV\t11\t/* Recording level */\n#define\tSOUND_MIXER_IGAIN\t12\t/* Input gain */\n#define\tSOUND_MIXER_OGAIN\t13\t/* Output gain */\n#define\tSOUND_MIXER_LINE1\t14\t/* Input source 1  (aux1) */\n#define\tSOUND_MIXER_LINE2\t15\t/* Input source 2  (aux2) */\n#define\tSOUND_MIXER_LINE3\t16\t/* Input source 3  (line) */\n#define\tSOUND_MIXER_DIGITAL1\t17\t/* Digital I/O 1 */\n#define\tSOUND_MIXER_DIGITAL2\t18\t/* Digital I/O 2 */\n#define\tSOUND_MIXER_DIGITAL3\t19\t/* Digital I/O 3 */\n#define\tSOUND_MIXER_PHONE\t20\t/* Phone */\n#define\tSOUND_MIXER_MONO\t21\t/* Mono Output */\n#define\tSOUND_MIXER_VIDEO\t22\t/* Video/TV (audio) in */\n#define\tSOUND_MIXER_RADIO\t23\t/* Radio in */\n#define\tSOUND_MIXER_DEPTH\t24\t/* Surround depth */\n#define\tSOUND_MIXER_REARVOL\t25\t/* Rear/Surround speaker vol */\n#define\tSOUND_MIXER_CENTERVOL\t26\t/* Center/LFE speaker vol */\n#define\tSOUND_MIXER_SIDEVOL\t27\t/* Side-Surround (8speaker) vol */\n#define\tSOUND_MIXER_SURRVOL\tSOUND_MIXER_SIDEVOL\n#define\tSOUND_ONOFF_MIN\t\t28\n#define\tSOUND_ONOFF_MAX\t\t30\n#define\tSOUND_MIXER_NONE\t31\n\n#define\tSOUND_MIXER_RECSRC\t0xff\t/* Recording sources */\n#define\tSOUND_MIXER_DEVMASK\t0xfe\t/* Supported devices */\n#define\tSOUND_MIXER_RECMASK\t0xfd\t/* Recording sources */\n#define\tSOUND_MIXER_CAPS\t0xfc\t/* Mixer capabilities (do not use) */\n#define\tSOUND_MIXER_STEREODEVS\t0xfb\t/* Mixer channels supporting stereo */\n#define\tSOUND_MIXER_OUTSRC\t0xfa\n#define\tSOUND_MIXER_OUTMASK\t0xf9\n\n#define\tSOUND_MIXER_ENHANCE\tSOUND_MIXER_NONE\n#define\tSOUND_MIXER_MUTE\tSOUND_MIXER_NONE\n#define\tSOUND_MIXER_LOUD\tSOUND_MIXER_NONE\n\n#define\tSOUND_MASK_VOLUME\t(1 << SOUND_MIXER_VOLUME)\n#define\tSOUND_MASK_BASS\t\t(1 << SOUND_MIXER_BASS)\n#define\tSOUND_MASK_TREBLE\t(1 << SOUND_MIXER_TREBLE)\n#define\tSOUND_MASK_SYNTH\t(1 << SOUND_MIXER_SYNTH)\n#define\tSOUND_MASK_PCM\t\t(1 << SOUND_MIXER_PCM)\n#define\tSOUND_MASK_SPEAKER\t(1 << SOUND_MIXER_SPEAKER)\n#define\tSOUND_MASK_LINE\t\t(1 << SOUND_MIXER_LINE)\n#define\tSOUND_MASK_MIC\t\t(1 << SOUND_MIXER_MIC)\n#define\tSOUND_MASK_CD\t\t(1 << SOUND_MIXER_CD)\n#define\tSOUND_MASK_IMIX\t\t(1 << SOUND_MIXER_IMIX)\n#define\tSOUND_MASK_ALTPCM\t(1 << SOUND_MIXER_ALTPCM)\n#define\tSOUND_MASK_RECLEV\t(1 << SOUND_MIXER_RECLEV)\n#define\tSOUND_MASK_IGAIN\t(1 << SOUND_MIXER_IGAIN)\n#define\tSOUND_MASK_OGAIN\t(1 << SOUND_MIXER_OGAIN)\n#define\tSOUND_MASK_LINE1\t(1 << SOUND_MIXER_LINE1)\n#define\tSOUND_MASK_LINE2\t(1 << SOUND_MIXER_LINE2)\n#define\tSOUND_MASK_LINE3\t(1 << SOUND_MIXER_LINE3)\n#define\tSOUND_MASK_DIGITAL1\t(1 << SOUND_MIXER_DIGITAL1)\n#define\tSOUND_MASK_DIGITAL2\t(1 << SOUND_MIXER_DIGITAL2)\n#define\tSOUND_MASK_DIGITAL3\t(1 << SOUND_MIXER_DIGITAL3)\n#define\tSOUND_MASK_MONO\t\t(1 << SOUND_MIXER_MONO)\n#define\tSOUND_MASK_PHONE\t(1 << SOUND_MIXER_PHONE)\n#define\tSOUND_MASK_RADIO\t(1 << SOUND_MIXER_RADIO)\n#define\tSOUND_MASK_VIDEO\t(1 << SOUND_MIXER_VIDEO)\n#define\tSOUND_MASK_DEPTH\t(1 << SOUND_MIXER_DEPTH)\n#define\tSOUND_MASK_REARVOL\t(1 << SOUND_MIXER_REARVOL)\n#define\tSOUND_MASK_CENTERVOL\t(1 << SOUND_MIXER_CENTERVOL)\n#define\tSOUND_MASK_SIDEVOL\t(1 << SOUND_MIXER_SIDEVOL)\n#define\tSOUND_MASK_SURRVOL\tSOUND_MASK_SIDEVOL\n#define\tSOUND_MASK_MUTE\t\t(1 << SOUND_MIXER_MUTE)\n#define\tSOUND_MASK_ENHANCE\t(1 << SOUND_MIXER_ENHANCE)\n#define\tSOUND_MASK_LOUD\t\t(1 << SOUND_MIXER_LOUD)\n\n/*\n * Again, DO NOT USE the following two macros.  They are here for SOURCE\n * COMPATIBILITY ONLY.\n */\n#define\tSOUND_DEVICE_LABELS\t{\t\t\t\t\t   \\\n\t\"Vol  \", \"Bass \", \"Treble\", \"Synth\", \"Pcm  \", \"Speaker \", \"Line \", \\\n\t\"Mic  \", \"CD   \", \"Mix  \", \"Pcm2 \", \"Rec  \", \"IGain\", \"OGain\",     \\\n\t\"Aux1\", \"Aux2\", \"Aux3\", \"Digital1\", \"Digital2\", \"Digital3\",\t   \\\n\t\"Phone\", \"Mono\", \"Video\", \"Radio\", \"Depth\",\t\t           \\\n\t\"Rear\", \"Center\", \"Side\" }\n\n#define\tSOUND_DEVICE_NAMES { \t\t\t\t\t\t\\\n\t\"vol\", \"bass\", \"treble\", \"synth\", \"pcm\", \"speaker\", \"line\",\t\\\n\t\"mic\", \"cd\", \"mix\", \"pcm2\", \"rec\", \"igain\", \"ogain\",\t\t\\\n\t\"aux1\", \"aux2\", \"aux3\", \"dig1\", \"dig2\", \"dig3\",\t\t\t\\\n\t\"phone\", \"mono\", \"video\", \"radio\", \"depth\",\t\t\t\\\n\t\"rear\", \"center\", \"side\" }\n\n#define\tMIXER_READ(dev)\t\t\t__OSSIOR('M', dev, int)\n#define\tMIXER_WRITE(dev)\t\t__OSSIOWR('M', dev, int)\n#define\tSOUND_MIXER_INFO\t\t__OSSIOR('M', 101, mixer_info)\n#define\tOSS_GETVERSION\t\t\t__OSSIOR('M', 118, int)\n\n/*\n * These macros are useful for some applications.  They are implemented\n * as soft values for the application, and do not affect real hardware.\n */\n#define\tSOUND_MIXER_READ_VOLUME\t\tMIXER_READ(SOUND_MIXER_VOLUME)\n#define\tSOUND_MIXER_READ_OGAIN\t\tMIXER_READ(SOUND_MIXER_OGAIN)\n#define\tSOUND_MIXER_READ_PCM\t\tMIXER_READ(SOUND_MIXER_PCM)\n#define\tSOUND_MIXER_READ_IGAIN\t\tMIXER_READ(SOUND_MIXER_IGAIN)\n#define\tSOUND_MIXER_READ_RECLEV\t\tMIXER_READ(SOUND_MIXER_RECLEV)\n#define\tSOUND_MIXER_READ_RECSRC\t\tMIXER_READ(SOUND_MIXER_RECSRC)\n#define\tSOUND_MIXER_READ_DEVMASK\tMIXER_READ(SOUND_MIXER_DEVMASK)\n#define\tSOUND_MIXER_READ_RECMASK\tMIXER_READ(SOUND_MIXER_RECMASK)\n#define\tSOUND_MIXER_READ_CAPS\t\tMIXER_READ(SOUND_MIXER_CAPS)\n#define\tSOUND_MIXER_READ_STEREODEVS\tMIXER_READ(SOUND_MIXER_STEREODEVS)\n#define\tSOUND_MIXER_READ_RECGAIN\t__OSSIOR('M', 119, int)\n#define\tSOUND_MIXER_READ_MONGAIN\t__OSSIOR('M', 120, int)\n\n#define\tSOUND_MIXER_WRITE_VOLUME\tMIXER_WRITE(SOUND_MIXER_VOLUME)\n#define\tSOUND_MIXER_WRITE_OGAIN\t\tMIXER_WRITE(SOUND_MIXER_OGAIN)\n#define\tSOUND_MIXER_WRITE_PCM\t\tMIXER_WRITE(SOUND_MIXER_PCM)\n#define\tSOUND_MIXER_WRITE_IGAIN\t\tMIXER_WRITE(SOUND_MIXER_IGAIN)\n#define\tSOUND_MIXER_WRITE_RECLEV\tMIXER_WRITE(SOUND_MIXER_RECLEV)\n#define\tSOUND_MIXER_WRITE_RECSRC\tMIXER_WRITE(SOUND_MIXER_RECSRC)\n#define\tSOUND_MIXER_WRITE_RECGAIN\t__OSSIOWR('M', 119, int)\n#define\tSOUND_MIXER_WRITE_MONGAIN\t__OSSIOWR('M', 120, int)\n\n/*\n * These macros are here for source compatibility.  They intentionally don't\n * map to any real hardware.  NOT SUPPORTED!\n */\n#define\tSOUND_MIXER_READ_BASS\t\tMIXER_READ(SOUND_MIXER_BASS)\n#define\tSOUND_MIXER_READ_TREBLE\t\tMIXER_READ(SOUND_MIXER_TREBLE)\n#define\tSOUND_MIXER_READ_SYNTH\t\tMIXER_READ(SOUND_MIXER_SYNTH)\n#define\tSOUND_MIXER_READ_SPEAKER\tMIXER_READ(SOUND_MIXER_SPEAKER)\n#define\tSOUND_MIXER_READ_LINE\t\tMIXER_READ(SOUND_MIXER_LINE)\n#define\tSOUND_MIXER_READ_MIC\t\tMIXER_READ(SOUND_MIXER_MIC)\n#define\tSOUND_MIXER_READ_CD\t\tMIXER_READ(SOUND_MIXER_CD)\n#define\tSOUND_MIXER_READ_IMIX\t\tMIXER_READ(SOUND_MIXER_IMIX)\n#define\tSOUND_MIXER_READ_ALTPCM\t\tMIXER_READ(SOUND_MIXER_ALTPCM)\n#define\tSOUND_MIXER_READ_LINE1\t\tMIXER_READ(SOUND_MIXER_LINE1)\n#define\tSOUND_MIXER_READ_LINE2\t\tMIXER_READ(SOUND_MIXER_LINE2)\n#define\tSOUND_MIXER_READ_LINE3\t\tMIXER_READ(SOUND_MIXER_LINE3)\n\n#define\tSOUND_MIXER_WRITE_BASS\t\tMIXER_WRITE(SOUND_MIXER_BASS)\n#define\tSOUND_MIXER_WRITE_TREBLE\tMIXER_WRITE(SOUND_MIXER_TREBLE)\n#define\tSOUND_MIXER_WRITE_SYNTH\t\tMIXER_WRITE(SOUND_MIXER_SYNTH)\n#define\tSOUND_MIXER_WRITE_SPEAKER\tMIXER_WRITE(SOUND_MIXER_SPEAKER)\n#define\tSOUND_MIXER_WRITE_LINE\t\tMIXER_WRITE(SOUND_MIXER_LINE)\n#define\tSOUND_MIXER_WRITE_MIC\t\tMIXER_WRITE(SOUND_MIXER_MIC)\n#define\tSOUND_MIXER_WRITE_CD\t\tMIXER_WRITE(SOUND_MIXER_CD)\n#define\tSOUND_MIXER_WRITE_IMIX\t\tMIXER_WRITE(SOUND_MIXER_IMIX)\n#define\tSOUND_MIXER_WRITE_ALTPCM\tMIXER_WRITE(SOUND_MIXER_ALTPCM)\n#define\tSOUND_MIXER_WRITE_LINE1\t\tMIXER_WRITE(SOUND_MIXER_LINE1)\n#define\tSOUND_MIXER_WRITE_LINE2\t\tMIXER_WRITE(SOUND_MIXER_LINE2)\n#define\tSOUND_MIXER_WRITE_LINE3\t\tMIXER_WRITE(SOUND_MIXER_LINE3)\n\n/*\n * Audio encoding types (Note! U8=8 and S16_LE=16 for compatibility)\n */\n#define\tAFMT_QUERY\t0x00000000\t/* Return current fmt */\n#define\tAFMT_MU_LAW\t0x00000001\n#define\tAFMT_A_LAW\t0x00000002\n#define\tAFMT_IMA_ADPCM\t0x00000004\n#define\tAFMT_U8\t\t0x00000008\n#define\tAFMT_S16_LE\t0x00000010\n#define\tAFMT_S16_BE\t0x00000020\n#define\tAFMT_S8\t\t0x00000040\n#define\tAFMT_U16_LE\t0x00000080\n#define\tAFMT_U16_BE\t0x00000100\n#define\tAFMT_MPEG\t0x00000200\t/* NOT SUPPORTED: MPEG (2) audio */\n#define\tAFMT_AC3\t0x00000400\t/* NOT SUPPORTED: AC3 compressed */\n#define\tAFMT_VORBIS\t0x00000800\t/* NOT SUPPORTED: Ogg Vorbis */\n#define\tAFMT_S32_LE\t0x00001000\n#define\tAFMT_S32_BE\t0x00002000\n#define\tAFMT_FLOAT\t0x00004000\t/* NOT SUPPORTED: IEEE double float */\n#define\tAFMT_S24_LE\t0x00008000\t/* LSB aligned in 32 bit word */\n#define\tAFMT_S24_BE\t0x00010000\t/* LSB aligned in 32 bit word */\n#define\tAFMT_SPDIF_RAW\t0x00020000\t/* NOT SUPPORTED: Raw S/PDIF frames */\n#define\tAFMT_S24_PACKED\t0x00040000\t/* 24 bit packed little endian */\n/*\n * Some big endian/little endian handling macros (native endian and\n * opposite endian formats).\n */\n#if defined(_BIG_ENDIAN)\n#define\tAFMT_S16_NE\tAFMT_S16_BE\n#define\tAFMT_U16_NE\tAFMT_U16_BE\n#define\tAFMT_S32_NE\tAFMT_S32_BE\n#define\tAFMT_S24_NE\tAFMT_S24_BE\n#define\tAFMT_S16_OE\tAFMT_S16_LE\n#define\tAFMT_S32_OE\tAFMT_S32_LE\n#define\tAFMT_S24_OE\tAFMT_S24_LE\n#else\n#define\tAFMT_S16_NE\tAFMT_S16_LE\n#define\tAFMT_U16_NE\tAFMT_U16_LE\n#define\tAFMT_S32_NE\tAFMT_S32_LE\n#define\tAFMT_S24_NE\tAFMT_S24_LE\n#define\tAFMT_S16_OE\tAFMT_S16_BE\n#define\tAFMT_S32_OE\tAFMT_S32_BE\n#define\tAFMT_S24_OE\tAFMT_S24_BE\n#endif\n\n/*\n * SNDCTL_DSP_GETCAPS bits\n */\n#define\tPCM_CAP_REVISION\t0x000000ff\t/* Revision level (0 to 255) */\n#define\tPCM_CAP_DUPLEX\t\t0x00000100\t/* Full duplex rec/play */\n#define\tPCM_CAP_REALTIME\t0x00000200\t/* NOT SUPPORTED */\n#define\tPCM_CAP_BATCH\t\t0x00000400\t/* NOT SUPPORTED */\n#define\tPCM_CAP_COPROC\t\t0x00000800\t/* NOT SUPPORTED */\n#define\tPCM_CAP_TRIGGER\t\t0x00001000\t/* Supports SETTRIGGER */\n#define\tPCM_CAP_MMAP\t\t0x00002000\t/* Supports mmap() */\n#define\tPCM_CAP_MULTI\t\t0x00004000\t/* Supports multiple open */\n#define\tPCM_CAP_BIND\t\t0x00008000\t/* Supports channel binding */\n#define\tPCM_CAP_INPUT\t\t0x00010000\t/* Supports recording */\n#define\tPCM_CAP_OUTPUT\t\t0x00020000\t/* Supports playback */\n#define\tPCM_CAP_VIRTUAL\t\t0x00040000\t/* Virtual device */\n#define\tPCM_CAP_ANALOGOUT\t0x00100000\t/* NOT SUPPORTED */\n#define\tPCM_CAP_ANALOGIN\t0x00200000\t/* NOT SUPPORTED */\n#define\tPCM_CAP_DIGITALOUT\t0x00400000\t/* NOT SUPPORTED */\n#define\tPCM_CAP_DIGITALIN\t0x00800000\t/* NOT SUPPORTED */\n#define\tPCM_CAP_ADMASK\t\t0x00f00000\t/* NOT SUPPORTED */\n#define\tPCM_CAP_SHADOW\t\t0x01000000\t/* \"Shadow\" device */\n#define\tPCM_CAP_CH_MASK\t\t0x06000000\t/* See DSP_CH_MASK below */\n#define\tPCM_CAP_HIDDEN\t\t0x08000000\t/* NOT SUPPORTED */\n#define\tPCM_CAP_FREERATE\t0x10000000\n#define\tPCM_CAP_MODEM\t\t0x20000000\t/* NOT SUPPORTED */\n#define\tPCM_CAP_DEFAULT\t\t0x40000000\t/* \"Default\" device */\n\n/*\n * Preferred channel usage. These bits can be used to give\n * recommendations to the application. Used by few drivers.  For\n * example if ((caps & DSP_CH_MASK) == DSP_CH_MONO) means that the\n * device works best in mono mode. However it doesn't necessarily mean\n * that the device cannot be used in stereo. These bits should only be\n * used by special applications such as multi track hard disk\n * recorders to find out the initial setup. However the user should be\n * able to override this selection.\n *\n * To find out which modes are actually supported the application\n * should try to select them using SNDCTL_DSP_CHANNELS.\n */\n#define\tDSP_CH_MASK\t\t0x06000000\t/* Mask */\n#define\tDSP_CH_ANY\t\t0x00000000\t/* No preferred mode */\n#define\tDSP_CH_MONO\t\t0x02000000\n#define\tDSP_CH_STEREO\t\t0x04000000\n#define\tDSP_CH_MULTI\t\t0x06000000\t/* More than two channels */\n\n\n/*\n * The PCM_CAP_* capability names used to be known as DSP_CAP_*, so\n * it's necessary to define the older names too.\n */\n#define\tDSP_CAP_ADMASK\t\tPCM_CAP_ADMASK\n#define\tDSP_CAP_ANALOGIN\tPCM_CAP_ANALOGIN\n#define\tDSP_CAP_ANALOGOUT\tPCM_CAP_ANALOGOUT\n#define\tDSP_CAP_BATCH\t\tPCM_CAP_BATCH\n#define\tDSP_CAP_BIND\t\tPCM_CAP_BIND\n#define\tDSP_CAP_COPROC\t\tPCM_CAP_COPROC\n#define\tDSP_CAP_DEFAULT\t\tPCM_CAP_DEFAULT\n#define\tDSP_CAP_DIGITALIN\tPCM_CAP_DIGITALIN\n#define\tDSP_CAP_DIGITALOUT\tPCM_CAP_DIGITALOUT\n#define\tDSP_CAP_DUPLEX\t\tPCM_CAP_DUPLEX\n#define\tDSP_CAP_FREERATE\tPCM_CAP_FREERATE\n#define\tDSP_CAP_HIDDEN\t\tPCM_CAP_HIDDEN\n#define\tDSP_CAP_INPUT\t\tPCM_CAP_INPUT\n#define\tDSP_CAP_MMAP\t\tPCM_CAP_MMAP\n#define\tDSP_CAP_MODEM\t\tPCM_CAP_MODEM\n#define\tDSP_CAP_MULTI\t\tPCM_CAP_MULTI\n#define\tDSP_CAP_OUTPUT\t\tPCM_CAP_OUTPUT\n#define\tDSP_CAP_REALTIME\tPCM_CAP_REALTIME\n#define\tDSP_CAP_REVISION\tPCM_CAP_REVISION\n#define\tDSP_CAP_SHADOW\t\tPCM_CAP_SHADOW\n#define\tDSP_CAP_TRIGGER\t\tPCM_CAP_TRIGGER\n#define\tDSP_CAP_VIRTUAL\t\tPCM_CAP_VIRTUAL\n\n/*\n * SNDCTL_DSP_GETTRIGGER and SNDCTL_DSP_SETTRIGGER\n */\n#define\tPCM_ENABLE_INPUT\t0x00000001\n#define\tPCM_ENABLE_OUTPUT\t0x00000002\n\n/*\n * SNDCTL_DSP_BIND_CHANNEL\n */\n#define\tDSP_BIND_QUERY\t\t0x00000000\n#define\tDSP_BIND_FRONT\t\t0x00000001\n#define\tDSP_BIND_SURR\t\t0x00000002\n#define\tDSP_BIND_CENTER_LFE\t0x00000004\n#define\tDSP_BIND_HANDSET\t0x00000008\n#define\tDSP_BIND_MIC\t\t0x00000010\n#define\tDSP_BIND_MODEM1\t\t0x00000020\n#define\tDSP_BIND_MODEM2\t\t0x00000040\n#define\tDSP_BIND_I2S\t\t0x00000080\n#define\tDSP_BIND_SPDIF\t\t0x00000100\n#define\tDSP_BIND_REAR\t\t0x00000200\n\n/*\n * SOUND_MIXER_READ_CAPS\n */\n#define\tSOUND_CAP_EXCL_INPUT\t0x00000001\n#define\tSOUND_CAP_NOLEGACY\t0x00000004\n#define\tSOUND_CAP_NORECSRC\t0x00000008\n\n/*\n * The following ioctl is for internal use only -- it is used to\n * coordinate /dev/sndstat numbering with file names in /dev/sound.\n * Applications must not use it.  (This is duplicated in sys/audioio.h\n * as well.)\n */\n#define\tSNDCTL_SUN_SEND_NUMBER\t__OSSIOW('X', 200, int)\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* _SYS_AUDIO_OSS_H */\n"
  },
  {
    "path": "src/detection/sound/sound.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\n#define FF_SOUND_VOLUME_UNKNOWN 255\n\ntypedef struct FFSoundDevice\n{\n    FFstrbuf identifier;\n    FFstrbuf name;\n    FFstrbuf platformApi;\n    uint8_t volume; // 0-100%\n    bool main;\n    bool active;\n} FFSoundDevice;\n\nconst char* ffDetectSound(FFlist* devices /* List of FFSoundDevice */);\n"
  },
  {
    "path": "src/detection/sound/sound_apple.c",
    "content": "#include \"sound.h\"\n#include \"common/apple/cf_helpers.h\"\n\n#include <CoreAudio/CoreAudio.h>\n#include <AvailabilityMacros.h>\n\n#ifndef MAC_OS_VERSION_12_0\n#define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster\n#endif\n\nconst char* ffDetectSound(FFlist* devices /* List of FFSoundDevice */)\n{\n    AudioDeviceID mainDeviceId;\n    UInt32 dataSize = sizeof(mainDeviceId);\n    if(AudioObjectGetPropertyData(kAudioObjectSystemObject, &(AudioObjectPropertyAddress){\n        kAudioHardwarePropertyDefaultOutputDevice,\n        kAudioObjectPropertyScopeOutput,\n        kAudioObjectPropertyElementMain\n    }, 0, NULL, &dataSize, &mainDeviceId) != kAudioHardwareNoError)\n        return \"AudioObjectGetPropertyData(kAudioHardwarePropertyDefaultOutputDevice) failed\";\n\n    AudioObjectID deviceIds[32] = {};\n    dataSize = sizeof(deviceIds);\n    if(AudioObjectGetPropertyData(kAudioObjectSystemObject, &(AudioObjectPropertyAddress){\n        kAudioHardwarePropertyDevices,\n        kAudioObjectPropertyScopeOutput,\n        kAudioObjectPropertyElementMain\n    }, 0, NULL, &dataSize, &deviceIds) != kAudioHardwareNoError)\n        return \"AudioObjectGetPropertyData(kAudioHardwarePropertyDevices) failed\";\n\n    for(uint32_t index = 0, length = dataSize / sizeof(*deviceIds); index < length; ++index)\n    {\n        AudioDeviceID deviceId = deviceIds[index];\n\n        // Ignore input devices\n        if(AudioObjectGetPropertyDataSize(deviceId, &(AudioObjectPropertyAddress){\n            kAudioDevicePropertyStreams,\n            kAudioObjectPropertyScopeInput,\n            kAudioObjectPropertyElementMain\n        }, 0, NULL, &dataSize) == kAudioHardwareNoError && dataSize > 0)\n            continue;\n\n        uint32_t dataSource;\n        dataSize = sizeof(dataSource);\n        if(AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress){\n            kAudioDevicePropertyDataSource,\n            kAudioObjectPropertyScopeOutput,\n            kAudioObjectPropertyElementMain\n        }, 0, NULL, &dataSize, &dataSource) == kAudioHardwareNoError && dataSource == 'hdpn')\n        {\n            uint32_t connected;\n            dataSize = sizeof(connected);\n            if(AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress){\n                kAudioDevicePropertyJackIsConnected,\n                kAudioObjectPropertyScopeOutput,\n                kAudioObjectPropertyElementMain\n            }, 0, NULL, &dataSize, &connected) == kAudioHardwareNoError)\n                if (!connected) continue;\n        }\n\n        FFSoundDevice* device = (FFSoundDevice*) ffListAdd(devices);\n        device->main = deviceId == mainDeviceId;\n        device->active = false;\n        device->volume = FF_SOUND_VOLUME_UNKNOWN;\n        ffStrbufInit(&device->identifier);\n        ffStrbufInit(&device->name);\n        ffStrbufInitStatic(&device->platformApi, \"Core Audio\");\n\n        FF_CFTYPE_AUTO_RELEASE CFStringRef uid = NULL;\n        dataSize = sizeof(uid);\n        if(AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) {\n            kAudioDevicePropertyDeviceUID,\n            kAudioObjectPropertyScopeOutput,\n            kAudioObjectPropertyElementMain\n        }, 0, NULL, &dataSize, &uid) == kAudioHardwareNoError)\n            ffCfStrGetString(uid, &device->identifier);\n        else\n            ffStrbufAppendF(&device->identifier, \"ID-%u\", (unsigned) deviceId);\n\n        FF_CFTYPE_AUTO_RELEASE CFStringRef name = NULL;\n        dataSize = sizeof(name);\n        if(AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress){\n            kAudioObjectPropertyName,\n            kAudioObjectPropertyScopeOutput,\n            kAudioObjectPropertyElementMain\n        }, 0, NULL, &dataSize, &name) == kAudioHardwareNoError)\n            ffCfStrGetString(name, &device->name);\n        else\n            ffStrbufSet(&device->name, &device->identifier);\n\n        uint32_t muted;\n        dataSize = sizeof(muted);\n        if(AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress){\n            kAudioDevicePropertyMute,\n            kAudioObjectPropertyScopeOutput,\n            kAudioObjectPropertyElementMain\n        }, 0, NULL, &dataSize, &muted) != kAudioHardwareNoError)\n            muted = false; // Device may not support volume control\n\n        uint32_t active;\n        dataSize = sizeof(active);\n        if(AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress){\n            kAudioDevicePropertyDeviceIsAlive,\n            kAudioObjectPropertyScopeOutput,\n            kAudioObjectPropertyElementMain\n        }, 0, NULL, &dataSize, &active) == kAudioHardwareNoError)\n            device->active = !!active;\n\n        if (muted)\n            device->volume = 0;\n        else\n        {\n            float volume;\n            dataSize = sizeof(volume);\n            if(AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress){\n                kAudioDevicePropertyVolumeScalar,\n                kAudioObjectPropertyScopeOutput,\n                kAudioObjectPropertyElementMain\n            }, 0, NULL, &dataSize, &volume) == kAudioHardwareNoError)\n                device->volume = (uint8_t) (volume * 100 + 0.5);\n            else\n            {\n                // Try detecting volume from channels\n                uint32_t channels[2];\n                dataSize = sizeof(channels);\n                if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress){\n                    kAudioDevicePropertyPreferredChannelsForStereo,\n                    kAudioObjectPropertyScopeOutput,\n                    kAudioObjectPropertyElementMain\n                }, 0, NULL, &dataSize, channels) == kAudioHardwareNoError)\n                {\n                    dataSize = sizeof(volume);\n                    if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress){\n                        kAudioDevicePropertyVolumeScalar,\n                        kAudioObjectPropertyScopeOutput,\n                        channels[0]\n                    }, 0, NULL, &dataSize, &volume) == kAudioHardwareNoError)\n                    {\n                        float temp;\n                        if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress){\n                            kAudioDevicePropertyVolumeScalar,\n                            kAudioObjectPropertyScopeOutput,\n                            channels[1]\n                        }, 0, NULL, &dataSize, &temp) == kAudioHardwareNoError)\n                            device->volume = (uint8_t) ((volume + temp) / 2 * 100 + 0.5);\n                    }\n                }\n            }\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/sound/sound_bsd.c",
    "content": "#include \"sound.h\"\n#include \"common/io.h\"\n#include \"common/sysctl.h\"\n\n#include <fcntl.h>\n#include <sys/soundcard.h>\n#include <unistd.h>\n\nconst char* ffDetectSound(FFlist* devices)\n{\n    #ifndef __NetBSD__\n    int defaultDev = ffSysctlGetInt(\"hw.snd.default_unit\", -1);\n    if (defaultDev == -1)\n        return \"sysctl(hw.snd.default_unit) failed\";\n    #else\n    int defaultDev;\n    {\n        char mixerp[12];\n        ssize_t plen = readlink(\"/dev/mixer\", mixerp, ARRAY_SIZE(mixerp));\n        if (plen < 6)\n            return \"readlink(/dev/mixer) failed\";\n        defaultDev = mixerp[plen - 1] - '0';\n        if (defaultDev < 0 || defaultDev > 9)\n            return \"Invalid mixer device\";\n    }\n    #endif\n\n    char path[] = \"/dev/mixer0\";\n\n    struct oss_sysinfo info = { .nummixers = 9 };\n\n    for (int idev = 0; idev <= info.nummixers; ++idev)\n    {\n        path[strlen(\"/dev/mixer\")] = (char) ('0' + idev);\n        FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);\n        if (fd < 0) break;\n\n        if (idev == 0)\n        {\n            if (ioctl(fd, SNDCTL_SYSINFO, &info) != 0)\n                return \"ioctl(SNDCTL_SYSINFO) failed\";\n        }\n\n        uint32_t devmask = 0;\n        if (ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0)\n            continue;\n        if (!(devmask & SOUND_MASK_VOLUME))\n            continue;\n\n        #if defined(SOUND_MIXER_MUTE) && (SOUND_MIXER_MUTE != SOUND_MIXER_NONE)\n        #define FF_SOUND_HAVE_MIXER_MUTE 1\n        uint32_t mutemask = 0;\n        ioctl(fd, SOUND_MIXER_READ_MUTE, &mutemask);\n        #endif\n\n        struct oss_card_info ci = { .card = idev };\n        if (ioctl(fd, SNDCTL_CARDINFO, &ci) < 0)\n            continue;\n\n        uint32_t volume;\n        if (ioctl(fd, SOUND_MIXER_READ_VOLUME, &volume) < 0)\n            continue;\n\n        FFSoundDevice* device = ffListAdd(devices);\n        ffStrbufInitS(&device->identifier, path);\n        ffStrbufInitF(&device->name, \"%s %s\", ci.longname, ci.hw_info);\n        ffStrbufTrimRightSpace(&device->name);\n        ffStrbufInitF(&device->platformApi, \"%s %s\", info.product, info.version);\n        device->volume =\n        #ifdef FF_SOUND_HAVE_MIXER_MUTE\n            mutemask & SOUND_MASK_VOLUME ? 0 :\n        #endif\n            ((uint8_t) volume /*left*/ + (uint8_t) (volume >> 8) /*right*/) / 2;\n        device->active = true;\n        device->main = defaultDev == idev;\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/sound/sound_haiku.cpp",
    "content": "extern \"C\"\n{\n#include \"sound.h\"\n#include \"common/stringUtils.h\"\n}\n#include <MediaAddOn.h>\n#include <MediaNode.h>\n#include <MediaRoster.h>\n#include <ParameterWeb.h>\n\nconst char* ffDetectSound(FFlist* devices /* List of FFSoundDevice */)\n{\n    BMediaRoster* roster = BMediaRoster::Roster();\n    media_node mediaNode;\n    live_node_info liveInfo;\n    dormant_node_info dormantInfo;\n\n    if (roster->GetAudioOutput(&mediaNode) != B_OK)\n        return NULL;\n\n    FFSoundDevice* device = (FFSoundDevice*)ffListAdd(devices);\n    ffStrbufInit(&device->identifier);\n    if (roster->GetDormantNodeFor(mediaNode, &dormantInfo) == B_OK)\n        ffStrbufAppendS(&device->identifier, dormantInfo.name);\n    ffStrbufInit(&device->name);\n    if (roster->GetLiveNodeInfo(mediaNode, &liveInfo) == B_OK)\n    {\n        ffStrbufAppendS(&device->name, liveInfo.name);\n        ffStrbufTrimRightSpace(&device->name);\n    }\n    ffStrbufInitStatic(&device->platformApi, \"MediaKit\");\n    // We'll check the Mixer actually\n    device->volume = 0;\n    device->active = true;\n    device->main = true;\n\n    roster->ReleaseNode(mediaNode);\n\n    media_node mixer;\n    if (roster->GetAudioMixer(&mixer) != B_OK)\n        return NULL;\n\n    BParameterWeb *web;\n    status_t status = roster->GetParameterWebFor(mixer, &web);\n    roster->ReleaseNode(mixer); // the web is all we need :-)\n    if (status != B_OK)\n        return NULL;\n\n    BContinuousParameter *gain = NULL;\n    BParameter *mute = NULL;\n    BParameter *parameter;\n    for (int32 index = 0; (parameter = web->ParameterAt(index)) != NULL; index++)\n    {\n        // assume the mute preceding master gain control\n        if (ffStrEquals(parameter->Kind(), B_MUTE))\n            mute = parameter;\n\n        if (ffStrEquals(parameter->Kind(), B_MASTER_GAIN))\n        {\n            // Can not use dynamic_cast due to fno-rtti\n            //gain = dynamic_cast<BContinuousParameter *>(parameter);\n            gain = (BContinuousParameter *)(parameter);\n            break;\n        }\n    }\n\n    if (gain == NULL)\n        return NULL;\n\n    bigtime_t when;\n    size_t size;\n\n    if (mute)\n    {\n        int32 isMute = false;\n        size = sizeof(isMute);\n        if (mute->GetValue(&isMute, &size, &when) == B_OK && isMute)\n            return NULL;\n    }\n\n    float volume = 0.0;\n    size = sizeof(volume);\n    if (gain->GetValue(&volume, &size, &when) == B_OK)\n        device->volume = (uint8_t) (100 * (volume - gain->MinValue()) / (gain->MaxValue() - gain->MinValue()));\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/sound/sound_linux.c",
    "content": "#include \"sound.h\"\n\n#ifdef FF_HAVE_PULSE\n#include \"common/library.h\"\n#include <pulse/pulseaudio.h>\n\nstatic void paSinkInfoCallback(pa_context *c, const pa_sink_info *i, int eol, void *userdata)\n{\n    FF_UNUSED(c);\n\n    if(eol > 0 || !i)\n        return;\n\n    FFSoundDevice* device = ffListAdd(userdata);\n    ffStrbufInitS(&device->identifier, i->name);\n    ffStrbufInitStatic(&device->platformApi, \"PulseAudio\");\n    ffStrbufTrimRightSpace(&device->identifier);\n    ffStrbufInitS(&device->name, i->description);\n    ffStrbufTrimRightSpace(&device->name);\n    ffStrbufTrimLeft(&device->name, ' ');\n    device->volume = i->mute ? 0 : (uint8_t) ((i->volume.values[0] * 100 + PA_VOLUME_NORM / 2 /*round*/) / PA_VOLUME_NORM);\n    device->active = i->active_port && i->active_port->available != PA_PORT_AVAILABLE_NO;\n    device->main = false;\n}\n\nstatic void paServerInfoCallback(FF_MAYBE_UNUSED pa_context *c, const pa_server_info *i, void *userdata)\n{\n    if(!i) return;\n\n    FF_STRBUF_AUTO_DESTROY api = ffStrbufCreate();\n    const char* realServer = strstr(i->server_name, \"(on \");\n    if (realServer)\n    {\n        ffStrbufSetS(&api, realServer + strlen(\"(on \"));\n        ffStrbufTrimRight(&api, ')');\n    }\n    else\n        ffStrbufSetF(&api, \"%s %s\", i->server_name, i->server_version);\n\n    FF_LIST_FOR_EACH(FFSoundDevice, device, *(FFlist*)userdata)\n    {\n        device->main = ffStrbufEqualS(&device->identifier, i->default_sink_name);\n        ffStrbufSet(&device->platformApi, &api);\n    }\n}\n\nstatic const char* detectSound(FFlist* devices)\n{\n    FF_LIBRARY_LOAD_MESSAGE(pulse, \"libpulse\" FF_LIBRARY_EXTENSION, 0)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_mainloop_new)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_mainloop_get_api)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_mainloop_iterate)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_mainloop_free)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_context_new)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_context_connect)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_context_get_state)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_context_get_sink_info_list)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_context_get_server_info)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_context_unref)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_operation_get_state)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_operation_unref)\n\n    pa_mainloop* mainloop = ffpa_mainloop_new();\n    if(!mainloop)\n        return \"Failed to create pulseaudio mainloop\";\n\n    pa_mainloop_api* mainloopApi = ffpa_mainloop_get_api(mainloop);\n    if(!mainloopApi)\n    {\n        ffpa_mainloop_free(mainloop);\n        return \"Failed to get pulseaudio mainloop api\";\n    }\n\n    pa_context* context = ffpa_context_new(mainloopApi, \"fastfetch\");\n    if(!context)\n    {\n        ffpa_mainloop_free(mainloop);\n        return \"Failed to create pulseaudio context\";\n    }\n\n    if(ffpa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0)\n    {\n        ffpa_context_unref(context);\n        ffpa_mainloop_free(mainloop);\n        return \"Failed to connect to pulseaudio context\";\n    }\n\n    pa_context_state_t state;\n    while((state = ffpa_context_get_state(context)) != PA_CONTEXT_READY)\n    {\n        if(!PA_CONTEXT_IS_GOOD(state))\n        {\n            ffpa_context_unref(context);\n            ffpa_mainloop_free(mainloop);\n            return \"Failed to get pulseaudio context state\";\n        }\n\n        ffpa_mainloop_iterate(mainloop, 1, NULL);\n    }\n\n    pa_operation* operation = ffpa_context_get_sink_info_list(context, paSinkInfoCallback, devices);\n    if(!operation)\n    {\n        ffpa_context_unref(context);\n        ffpa_mainloop_free(mainloop);\n        return \"Failed to get pulseaudio sink info list\";\n    }\n\n    while(ffpa_operation_get_state(operation) == PA_OPERATION_RUNNING)\n        ffpa_mainloop_iterate(mainloop, 1, NULL);\n\n    ffpa_operation_unref(operation);\n\n    operation = ffpa_context_get_server_info(context, paServerInfoCallback, devices);\n    if(operation)\n    {\n        while(ffpa_operation_get_state(operation) == PA_OPERATION_RUNNING)\n            ffpa_mainloop_iterate(mainloop, 1, NULL);\n\n        ffpa_operation_unref(operation);\n    }\n\n    ffpa_context_unref(context);\n    ffpa_mainloop_free(mainloop);\n    return NULL;\n}\n\n#endif // FF_HAVE_PULSE\n\nconst char* ffDetectSound(FFlist* devices)\n{\n    #ifdef FF_HAVE_PULSE\n        return detectSound(devices);\n    #else\n        FF_UNUSED(devices);\n        return \"Fastfetch was built without libpulse support\";\n    #endif\n}\n"
  },
  {
    "path": "src/detection/sound/sound_nbsd.c",
    "content": "#include \"sound.h\"\n#include \"common/io.h\"\n\n#include <fcntl.h>\n#include <stdint.h>\n#include <unistd.h>\n#include <sys/audioio.h>\n#include <sys/ioctl.h>\n\nconst char* ffDetectSound(FFlist* devices)\n{\n    int defaultDev;\n    {\n        char audiop[12];\n        ssize_t plen = readlink(\"/dev/audio\", audiop, ARRAY_SIZE(audiop));\n        if (plen < (ssize_t) strlen(\"audioN\"))\n            return \"readlink(/dev/audio) failed\";\n        defaultDev = audiop[plen - 1] - '0';\n        if (defaultDev < 0 || defaultDev > 9)\n            return \"Invalid audio device\";\n    }\n\n    char path[] = \"/dev/audio0\";\n\n    for (int idev = 0; idev < 9; ++idev)\n    {\n        path[strlen(\"/dev/audio\")] = (char) ('0' + idev);\n        FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);\n        if (fd < 0) break;\n\n        audio_device_t ad;\n        if (ioctl(fd, AUDIO_GETDEV, &ad) < 0)\n            continue;\n\n        audio_info_t ai;\n        if (ioctl(fd, AUDIO_GETINFO, &ai) < 0)\n            continue;\n\n        FFSoundDevice* device = ffListAdd(devices);\n        ffStrbufInitS(&device->identifier, path);\n        ffStrbufInitS(&device->name, ad.name);\n        ffStrbufTrimRightSpace(&device->name);\n        ffStrbufInitF(&device->platformApi, \"%s\", \"SunAudio\");\n        device->volume = (uint8_t) ((ai.play.gain * 100 + AUDIO_MAX_GAIN / 2) / AUDIO_MAX_GAIN);\n        device->active = true;\n        device->main = defaultDev == idev;\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/sound/sound_nosupport.c",
    "content": "#include \"sound.h\"\n\nconst char* ffDetectSound(FF_MAYBE_UNUSED FFlist* devices /* List of FFSoundDevice */)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/sound/sound_obsd.c",
    "content": "#include \"sound.h\"\n#include \"common/stringUtils.h\"\n\n#include <fcntl.h>\n#include <sndio.h>\n\nstatic void close_hdl(struct sioctl_hdl** phdl)\n{\n    assert(phdl);\n    if (*phdl) sioctl_close(*phdl);\n}\n\nenum { MAX_CHANNEL_NUM = 8 };\n\ntypedef struct FFSoundDeviceBundle\n{\n    char name[SIOCTL_DISPLAYMAX];\n    double level[MAX_CHANNEL_NUM];\n    uint8_t iLevel;\n    bool mute[MAX_CHANNEL_NUM];\n    uint8_t iMute;\n} FFSoundDeviceBundle;\n\nstatic void enumerate_props(FFSoundDeviceBundle* bundle, struct sioctl_desc* desc, int val)\n{\n    if (!desc) return;\n\n    if (desc->type == SIOCTL_SEL)\n    {\n        if (desc->display[0] != '\\0' && ffStrEquals(desc->node0.name, \"server\"))\n            ffStrCopy(bundle->name, desc->display, SIOCTL_DISPLAYMAX);\n        return;\n    }\n\n    if (desc->type != SIOCTL_NUM && desc->type != SIOCTL_SW)\n        return;\n\n    if (!ffStrEquals(desc->node0.name, \"output\"))\n        return;\n\n    if (ffStrEquals(desc->func, \"level\"))\n    {\n        if (__builtin_expect(bundle->iLevel == MAX_CHANNEL_NUM, false))\n            return;\n        bundle->level[bundle->iLevel] = (double) val / (double) desc->maxval;\n        ++bundle->iLevel;\n    }\n    else if (ffStrEquals(desc->func, \"mute\"))\n    {\n        if (__builtin_expect(bundle->iMute == MAX_CHANNEL_NUM, false))\n            return;\n        bundle->mute[bundle->iMute] = !!val;\n        ++bundle->iMute;\n    }\n}\n\nconst char* ffDetectSound(FFlist* devices)\n{\n    __attribute__((__cleanup__(close_hdl))) struct sioctl_hdl* hdl = sioctl_open(SIO_DEVANY, SIOCTL_READ, 0);\n    if (!hdl) return \"sio_open() failed\";\n\n    FFSoundDeviceBundle bundle = {};\n    if (sioctl_ondesc(hdl, (void*) enumerate_props, &bundle) == 0)\n        return \"sioctl_ondesc() failed\";\n\n    if (bundle.iLevel != bundle.iMute || bundle.iLevel == 0)\n        return \"Unexpected sioctl_ondesc() result\";\n\n    FFSoundDevice* device = ffListAdd(devices);\n    ffStrbufInitS(&device->name, bundle.name);\n    ffStrbufInitS(&device->identifier, SIO_DEVANY);\n    ffStrbufInitStatic(&device->platformApi, \"sndio\");\n    device->active = true;\n    device->main = true;\n    device->volume = 0;\n\n    double totalLevel = 0;\n    for (uint8_t i = 0; i < bundle.iLevel; ++i)\n    {\n        if (!bundle.mute[i])\n            totalLevel += bundle.level[i];\n    }\n    device->volume = (uint8_t) ((totalLevel * 100 + bundle.iLevel / 2) / bundle.iLevel);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/sound/sound_sunos.c",
    "content": "#include \"sound.h\"\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n\n#include <fcntl.h>\n#include <unistd.h>\n#if __has_include(<sys/soundcard.h>)\n    #include <sys/soundcard.h>\n#else\n    // Strangely, they don't provide this file on default installation\n    #include \"audio_oss_sunos.h\"\n#endif\n\nconst char* ffDetectSound(FFlist* devices)\n{\n    int defaultDev;\n    {\n        char mixerp[12];\n        ssize_t plen = readlink(\"/dev/audio\", mixerp, ARRAY_SIZE(mixerp));\n        if (plen < 6)\n            return \"readlink(/dev/audio) failed\";\n        defaultDev = mixerp[plen - 1] - '0';\n        if (defaultDev < 0 || defaultDev > 9)\n            return \"Invalid mixer device\";\n    }\n\n    char path[] = \"/dev/mixer0\";\n\n    FF_STRBUF_AUTO_DESTROY sndstat = ffStrbufCreate();\n\n    struct oss_sysinfo info = { .nummixers = 9 };\n\n    // The implementation is very different from *BSD's. They call it OSS4\n    for (int idev = 0; idev < info.nummixers; ++idev)\n    {\n        path[strlen(\"/dev/mixer\")] = (char) ('0' + idev);\n        FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);\n        if (fd < 0) break;\n\n        if (idev == 0)\n        {\n            if (ioctl(fd, SNDCTL_SYSINFO, &info) != 0)\n                return \"ioctl(SNDCTL_SYSINFO) failed\";\n            if (ffAppendFDBuffer(fd, &sndstat))\n                ffStrbufSubstrAfterFirstS(&sndstat, \"\\nMixers:\");\n        }\n\n        struct oss_mixerinfo mi = {};\n        if (ioctl(fd, SNDCTL_MIXERINFO, &mi) < 0)\n            continue;\n\n        int volume = -1;\n        for (int iext = 0; iext < mi.nrext; ++iext)\n        {\n            struct oss_mixext me = { .dev = mi.dev, .ctrl = iext };\n            if (ioctl(fd, SNDCTL_MIX_EXTINFO, &me) < 0)\n                continue;\n            if (me.flags & MIXF_PCMVOL)\n            {\n                struct oss_mixer_value mv = { .dev = mi.dev, .ctrl = iext, .timestamp = me.timestamp };\n                if (ioctl(fd, SNDCTL_MIX_READ, &mv) >= 0)\n                {\n                    mv.value -= me.minvalue;\n                    me.maxvalue -= me.minvalue;\n                    volume = (uint8_t) ((mv.value * 100 + me.maxvalue / 2) / me.maxvalue);\n                }\n                break;\n            }\n        }\n        if (volume == -1) continue;\n\n        FFSoundDevice* device = ffListAdd(devices);\n        ffStrbufInitS(&device->identifier, path);\n        char buf[16];\n        int bufLen = snprintf(buf, ARRAY_SIZE(buf), \"\\n%d: \", mi.dev);\n        assert(bufLen > 3);\n        const char* pLine = memmem(sndstat.chars, sndstat.length, buf, (size_t) bufLen);\n        if (pLine)\n        {\n            pLine += bufLen;\n            const char* pEnd = strchr(pLine, '\\n');\n            if (!pEnd) pEnd = sndstat.chars + sndstat.length;\n            ffStrbufInitNS(&device->name, (uint32_t) (pEnd - pLine), pLine);\n        }\n        else\n            ffStrbufInitS(&device->name, mi.name);\n        ffStrbufTrimRightSpace(&device->name);\n        ffStrbufInitF(&device->platformApi, \"%s %s\", info.product, info.version);\n        device->volume = (uint8_t) volume;\n        device->active = !!mi.enabled;\n        device->main = defaultDev == idev;\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/sound/sound_windows.cpp",
    "content": "extern \"C\" {\n    #include \"sound.h\"\n    #include \"common/windows/unicode.h\"\n}\n#include \"common/windows/com.hpp\"\n\n#include <initguid.h>\n#include <mmdeviceapi.h>\n#include <endpointvolume.h>\n#include <functiondiscoverykeys_devpkey.h>\n\nstatic void ffCoTaskMemFreeWrapper(void* pptr)\n{\n    assert(pptr != NULL);\n    void* ptr = *(void**)pptr;\n    if (ptr) CoTaskMemFree(ptr);\n}\n#define FF_COTASK_AUTO_FREE __attribute__((__cleanup__(ffCoTaskMemFreeWrapper)))\n\nconst char* ffDetectSound(FFlist* devices /* List of FFSoundDevice */)\n{\n    const char* error = ffInitCom();\n    if (error)\n        return error;\n\n    IMMDeviceEnumerator* FF_AUTO_RELEASE_COM_OBJECT pEnum = NULL;\n\n    if (FAILED(CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void **)&pEnum)))\n        return \"CoCreateInstance(CLSID_MMDeviceEnumerator) failed\";\n\n    LPWSTR FF_COTASK_AUTO_FREE mainDeviceId = NULL;\n\n    {\n        IMMDevice* FF_AUTO_RELEASE_COM_OBJECT pDefaultDevice = NULL;\n\n        if (FAILED(pEnum->GetDefaultAudioEndpoint(eRender, eMultimedia, &pDefaultDevice)))\n            return \"GetDefaultAudioEndpoint() failed\";\n\n        if (FAILED(pDefaultDevice->GetId(&mainDeviceId)))\n            return \"pDefaultDevice->GetId() failed\";\n    }\n\n    IMMDeviceCollection* FF_AUTO_RELEASE_COM_OBJECT pDevices = NULL;\n\n    if (FAILED(pEnum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED, &pDevices)))\n        return \"EnumAudioEndpoints() failed\";\n\n    uint32_t deviceCount;\n    if (FAILED(pDevices->GetCount(&deviceCount)))\n        return \"pDevices->GetCount() failed\";\n\n    for (uint32_t deviceIdx = 0; deviceIdx < deviceCount; ++deviceIdx)\n    {\n        IMMDevice* FF_AUTO_RELEASE_COM_OBJECT immDevice = NULL;\n        if (FAILED(pDevices->Item(deviceIdx, &immDevice)))\n            continue;\n\n        LPWSTR FF_COTASK_AUTO_FREE immDeviceId = NULL;\n        if (FAILED(immDevice->GetId(&immDeviceId)))\n            continue;\n\n        IPropertyStore* FF_AUTO_RELEASE_COM_OBJECT immPropStore;\n        if (FAILED(immDevice->OpenPropertyStore(STGM_READ, &immPropStore)))\n            continue;\n\n        DWORD immState;\n        if (FAILED(immDevice->GetState(&immState)))\n            continue;\n\n        FFSoundDevice* device = (FFSoundDevice*) ffListAdd(devices);\n        device->main = wcscmp(mainDeviceId, immDeviceId) == 0;\n        device->active = !!(immState & DEVICE_STATE_ACTIVE);\n        device->volume = FF_SOUND_VOLUME_UNKNOWN;\n        ffStrbufInitWS(&device->identifier, immDeviceId);\n        ffStrbufInit(&device->name);\n        ffStrbufInitStatic(&device->platformApi, \"Core Audio APIs\");\n\n        {\n            PROPVARIANT __attribute__((__cleanup__(PropVariantClear))) friendlyName;\n            PropVariantInit(&friendlyName);\n            if (SUCCEEDED(immPropStore->GetValue(PKEY_Device_FriendlyName, &friendlyName)))\n                ffStrbufSetWS(&device->name, friendlyName.pwszVal);\n        }\n\n        IAudioEndpointVolume* FF_AUTO_RELEASE_COM_OBJECT immEndpointVolume;\n        if(SUCCEEDED(immDevice->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (void**) &immEndpointVolume)))\n        {\n            BOOL muted;\n            if (FAILED(immEndpointVolume->GetMute(&muted)) || !muted)\n            {\n                FLOAT volume;\n                if (SUCCEEDED(immEndpointVolume->GetMasterVolumeLevelScalar(&volume)))\n                    device->volume = (uint8_t) (volume * 100 + 0.5);\n            }\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/swap/swap.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/swap/option.h\"\n\ntypedef struct FFSwapResult\n{\n    FFstrbuf name;\n    uint64_t bytesUsed;\n    uint64_t bytesTotal;\n} FFSwapResult;\n\nconst char* ffDetectSwap(FFlist* result /* List of FFSwapResult */);\n"
  },
  {
    "path": "src/detection/swap/swap_apple.c",
    "content": "#include \"swap.h\"\n\n#include <mach/mach.h>\n#include <sys/sysctl.h>\n\nconst char* ffDetectSwap(FFlist* result)\n{\n    struct xsw_usage xsw;\n    size_t size = sizeof(xsw);\n    if(sysctl((int[]){ CTL_VM, VM_SWAPUSAGE }, 2, &xsw, &size, NULL, 0) != 0)\n        return \"Failed to read vm.swapusage\";\n\n    FFSwapResult* swap = ffListAdd(result);\n    ffStrbufInitStatic(&swap->name, xsw.xsu_encrypted ? \"Encrypted\" : \"Normal\");\n    swap->bytesTotal = xsw.xsu_total;\n    swap->bytesUsed = xsw.xsu_used;\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/swap/swap_bsd.c",
    "content": "#include \"swap.h\"\n#include \"common/sysctl.h\"\n\n#include <vm/vm_param.h>\n#include <sys/stat.h>\n#include <sys/param.h>\n\nstatic void addSwapEntry(FFlist* result, struct xswdev* xsw, uint32_t pageSize)\n{\n    if (xsw->xsw_nblks == 0) // DFBSD reports some /dev/wdog devices with nblks == 0\n        return;\n\n    FFSwapResult* swap = ffListAdd(result);\n    if (xsw->xsw_dev == NODEV)\n        ffStrbufInitStatic(&swap->name, \"[NFS]\");\n    else\n        ffStrbufInitF(&swap->name, \"/dev/%s\", devname(xsw->xsw_dev, S_IFCHR));\n    swap->bytesUsed = (uint64_t) xsw->xsw_used * pageSize;\n    swap->bytesTotal = (uint64_t) xsw->xsw_nblks * pageSize;\n}\n\n#if __DragonFly__\n\nconst char* ffDetectSwap(FFlist* result)\n{\n    struct xswdev xsws[32];\n    size_t size = sizeof(xsws);\n    if (sysctlbyname(\"vm.swap_info_array\", xsws, &size, NULL, 0) < 0)\n        return \"sysctlbyname(\\\"vm.swap_info_array\\\") failed\";\n\n    uint32_t pageSize = instance.state.platform.sysinfo.pageSize;\n\n    size_t count = size / sizeof(struct xswdev);\n    if (count == 0)\n        return NULL;\n\n    if (xsws->xsw_version != XSWDEV_VERSION)\n        return \"xswdev version mismatch\";\n\n    for (uint32_t i = 0; i < count; ++i)\n        addSwapEntry(result, &xsws[i], pageSize);\n\n    return NULL;\n}\n\n#elif __FreeBSD__\n\nconst char* ffDetectSwap(FFlist* result)\n{\n    int mib[16];\n    size_t mibsize = ARRAY_SIZE(mib);\n    if (sysctlnametomib(\"vm.swap_info\", mib, &mibsize) < 0)\n        return \"sysctlnametomib(\\\"vm.swap_info\\\") failed\";\n\n    uint32_t pageSize = instance.state.platform.sysinfo.pageSize;\n\n    for (int n = 0; ; ++n)\n    {\n        mib[mibsize] = n;\n        struct xswdev xsw;\n        size_t size = sizeof(xsw);\n        if (sysctl(mib, (uint32_t) (mibsize + 1), &xsw, &size, NULL, 0) < 0)\n            break;\n        if (xsw.xsw_version != XSWDEV_VERSION)\n            return \"xswdev version mismatch\";\n\n        addSwapEntry(result, &xsw, pageSize);\n    }\n\n    return NULL;\n}\n\n#endif\n"
  },
  {
    "path": "src/detection/swap/swap_haiku.c",
    "content": "#include \"swap.h\"\n\n#include <OS.h>\n#include <driver_settings.h>\n\nconst char* ffDetectSwap(FFlist* result)\n{\n    system_info info;\n    if (get_system_info(&info) != B_OK)\n        return \"Error getting system info\";\n\n    uint32_t pageSize = instance.state.platform.sysinfo.pageSize;\n    FFSwapResult* swap = ffListAdd(result);\n    ffStrbufInitStatic(&swap->name, \"System\");\n    void* kvms = load_driver_settings(\"virtual_memory\"); // /boot/home/config/settings/kernel/drivers/virtual_memory\n    if (kvms)\n    {\n        const char* swapAuto = get_driver_parameter(kvms, \"swap_auto\", NULL, NULL);\n        if (swapAuto)\n            ffStrbufSetStatic(&swap->name, swapAuto[0] == 'y' ? \"Auto\" : \"Manual\");\n        unload_driver_settings(kvms);\n    }\n    swap->bytesTotal = pageSize * (uint64_t) info.max_swap_pages;\n    swap->bytesUsed = pageSize * (uint64_t) (info.max_swap_pages - info.free_swap_pages);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/swap/swap_linux.c",
    "content": "#include \"swap.h\"\n\n#include \"common/io.h\"\n#include \"common/mallocHelper.h\"\n\n#include <inttypes.h>\n\nstatic const char* detectByProcMeminfo(FFlist* result)\n{\n    // For Android\n    // Ref: #620\n    char buf[PROC_FILE_BUFFSIZ];\n    ssize_t nRead = ffReadFileData(\"/proc/meminfo\", ARRAY_SIZE(buf) - 1, buf);\n    if(nRead < 0)\n        return \"ffReadFileData(\\\"/proc/meminfo\\\", ARRAY_SIZE(buf)-1, buf)\";\n    buf[nRead] = '\\0';\n\n    uint64_t swapTotal = 0, swapFree = 0;\n\n    char *token = NULL;\n    if ((token = strstr(buf, \"SwapTotal:\")) != NULL)\n        swapTotal = strtoul(token + strlen(\"SwapTotal:\"), NULL, 10);\n\n    if ((token = strstr(buf, \"SwapFree:\")) != NULL)\n        swapFree = strtoul(token + strlen(\"SwapFree:\"), NULL, 10);\n\n    FFSwapResult* swap = ffListAdd(result);\n    ffStrbufInitStatic(&swap->name, \"Total\");\n    swap->bytesTotal = swapTotal * 1024lu;\n    swap->bytesUsed = (swapTotal - swapFree) * 1024lu;\n\n    return NULL;\n}\n\nstatic const char* detectByProcSwaps(FFlist* result)\n{\n    // Ref: #620\n    char buf[PROC_FILE_BUFFSIZ];\n    ssize_t nRead = ffReadFileData(\"/proc/swaps\", ARRAY_SIZE(buf) - 1, buf);\n    if(nRead <= 0)\n        return \"ffReadFileData(\\\"/proc/swaps\\\", ARRAY_SIZE(buf)-1, buf) failed\";\n    buf[nRead] = '\\0';\n\n    // Skip header\n    char* line = memchr(buf, '\\n', (size_t) nRead);\n\n    while(line && *++line)\n    {\n        uint64_t total, used;\n        char name[256];\n        if(sscanf(line, \"%255s %*[^\\t]%\" SCNu64 \"%\" SCNu64, name, &total, &used) != 3)\n            return \"Invalid /proc/swaps format found\";\n\n        uint32_t nameLen = (uint32_t) strnlen(name, sizeof(name));\n        FFSwapResult* swap = ffListAdd(result);\n        ffStrbufInitA(&swap->name, nameLen);\n        for (size_t i = 0; i < nameLen; ++i)\n        {\n            if(name[i] == '\\\\')\n            {\n                char octal[4] = { name[i + 1], name[i + 2], name[i + 3], '\\0' };\n                ffStrbufAppendC(&swap->name, (char) strtol(octal, NULL, 8));\n                i += 3;\n            }\n            else\n                ffStrbufAppendC(&swap->name, name[i]);\n        }\n        swap->bytesTotal = total * 1024u;\n        swap->bytesUsed = used * 1024u;\n\n        line = memchr(line, '\\n', (size_t) (nRead - (line - buf)));\n    }\n\n    return NULL;\n}\n\nconst char* ffDetectSwap(FFlist* result)\n{\n    if (detectByProcSwaps(result) == NULL)\n        return NULL;\n    return detectByProcMeminfo(result);\n}\n"
  },
  {
    "path": "src/detection/swap/swap_nosupport.c",
    "content": "#include \"swap.h\"\n\nconst char* ffDetectSwap(FFSwapResult* swap)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/swap/swap_obsd.c",
    "content": "#include \"swap.h\"\n#include \"common/FFlist.h\"\n#include \"common/mallocHelper.h\"\n\n#include <sys/types.h>\n#include <sys/swap.h>\n#include <sys/param.h>\n#include <unistd.h>\n\nconst char* ffDetectSwap(FFlist* result)\n{\n    int nswap = swapctl(SWAP_NSWAP, 0, 0);\n    if (nswap < 0) return \"swapctl(SWAP_NSWAP) failed\";\n    if (nswap == 0) return NULL;\n\n    FF_AUTO_FREE struct swapent* swdev = malloc((uint32_t) nswap * sizeof(*swdev));\n\n    if (swapctl(SWAP_STATS, swdev, nswap) < 0)\n        return \"swapctl(SWAP_STATS) failed\";\n\n    for (int i = 0; i < nswap; i++)\n    {\n        if (swdev[i].se_flags & SWF_ENABLE)\n        {\n            FFSwapResult* swap = ffListAdd(result);\n            ffStrbufInitS(&swap->name, swdev[i].se_path);\n            swap->bytesUsed = (uint64_t) swdev[i].se_inuse * DEV_BSIZE;\n            swap->bytesTotal = (uint64_t) swdev[i].se_nblks * DEV_BSIZE;\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/swap/swap_sunos.c",
    "content": "#include \"swap.h\"\n#include <sys/stat.h>\n#include <sys/swap.h>\n#include <limits.h>\n#include <stdalign.h>\n\nenum { FFMaxNSwap = 8 };\n\nconst char* ffDetectSwap(FFlist* result)\n{\n    char strings[FFMaxNSwap][PATH_MAX];\n    alignas(swaptbl_t) uint8_t buffer[sizeof(swaptbl_t) + sizeof(swapent_t) * (FFMaxNSwap - 1)] = {};\n    swaptbl_t* table = (swaptbl_t*) buffer;\n    table->swt_n = FFMaxNSwap;\n    for (int i = 0; i < FFMaxNSwap; ++i)\n        table->swt_ent[i].ste_path = strings[i];\n\n    int size = swapctl(SC_LIST, table);\n    if (size < 0)\n        return \"swapctl() failed\";\n\n    uint32_t pageSize = instance.state.platform.sysinfo.pageSize;\n    for (int i = 0; i < size; ++i)\n    {\n        FFSwapResult* swap = ffListAdd(result);\n        ffStrbufInitS(&swap->name, table->swt_ent[i].ste_path);\n        swap->bytesTotal = (uint64_t) table->swt_ent[i].ste_pages * pageSize;\n        swap->bytesUsed = swap->bytesTotal - (uint64_t) table->swt_ent[i].ste_free * pageSize;\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/swap/swap_windows.c",
    "content": "#include \"swap.h\"\n#include \"common/windows/unicode.h\"\n\n#include <winternl.h>\n#include <ntstatus.h>\n#include <windows.h>\n#include <stdalign.h>\n\nconst char* ffDetectSwap(FFlist* result)\n{\n    alignas(SYSTEM_PAGEFILE_INFORMATION) uint8_t buffer[4096];\n    ULONG size = sizeof(buffer);\n    SYSTEM_PAGEFILE_INFORMATION* pstart = (SYSTEM_PAGEFILE_INFORMATION*) buffer;\n    if(!NT_SUCCESS(NtQuerySystemInformation(SystemPagefileInformation, pstart, size, &size)))\n        return \"NtQuerySystemInformation(SystemPagefileInformation, size) failed\";\n\n    uint32_t pageSize = instance.state.platform.sysinfo.pageSize;\n    for (SYSTEM_PAGEFILE_INFORMATION* current = pstart; ; current = (SYSTEM_PAGEFILE_INFORMATION*)((uint8_t*) current + current->NextEntryOffset))\n    {\n        FFSwapResult* swap = ffListAdd(result);\n        ffStrbufInitNWS(&swap->name, current->FileName.Length / sizeof(wchar_t), current->FileName.Buffer);\n        if (ffStrbufStartsWithS(&swap->name, \"\\\\??\\\\\"))\n            ffStrbufSubstrAfter(&swap->name, strlen(\"\\\\??\\\\\") - 1);\n        swap->bytesUsed = (uint64_t) current->TotalUsed * pageSize;\n        swap->bytesTotal = (uint64_t) current->CurrentSize * pageSize;\n        if (current->NextEntryOffset == 0)\n            break;\n    }\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/terminalfont/terminalfont.c",
    "content": "#include \"terminalfont.h\"\n#include \"common/io.h\"\n#include \"common/properties.h\"\n#include \"common/processing.h\"\n#include \"common/debug.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/terminalshell/terminalshell.h\"\n\nstatic void detectAlacritty(FFTerminalFontResult* terminalFont)\n{\n    // Maybe using a toml parser to read the config file is better?\n    // https://github.com/cktan/tomlc17\n\n    // Doc: https://alacritty.org/config-alacritty.html#s26\n    FF_STRBUF_AUTO_DESTROY fontNormal = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY fontFamily = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY fontStyle = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();\n\n    do {\n        FFpropquery fontQueryToml[] = {\n            {\"normal =\", &fontNormal},\n            {\"size =\", &fontSize},\n        };\n\n        // alacritty parses config files in this order\n        if(ffParsePropFileConfigValues(\"alacritty/alacritty.toml\", 2, fontQueryToml))\n            break;\n        if(ffParsePropFileConfigValues(\"alacritty.toml\", 2, fontQueryToml))\n            break;\n        if(ffParsePropFileConfigValues(\".alacritty.toml\", 2, fontQueryToml))\n            break;\n    } while (false);\n\n    if(fontNormal.length > 0)\n    {\n        // { family = \"Fira Code\", style = \"Medium\" }\n        ffStrbufTrimSpace(&fontNormal);\n        ffStrbufTrimRight(&fontNormal, '}');\n        ffStrbufTrimLeft(&fontNormal, '{');\n        ffStrbufTrimSpace(&fontNormal);\n\n        // family = \"Fira Code\", style = \"Medium\"\n        ffStrbufReplaceAllC(&fontNormal, ',', '\\n'); // Assume no commas in font names\n        ffParsePropLines(fontNormal.chars, \"family =\", &fontFamily);\n        ffParsePropLines(fontNormal.chars, \"style =\", &fontStyle);\n    }\n\n    if (fontFamily.length == 0)\n    {\n        #if __APPLE__\n        ffStrbufSetStatic(&fontFamily, \"Menlo\");\n        #elif _WIN32\n        ffStrbufSetStatic(&fontFamily, \"Consolas\");\n        #else\n        ffStrbufSetStatic(&fontFamily, \"monospace\");\n        #endif\n    }\n    if (fontStyle.length == 0)\n        ffStrbufSetStatic(&fontStyle, \"Regular\");\n\n    if(fontSize.length == 0)\n        ffStrbufSetStatic(&fontSize, \"11.25\");\n\n    ffFontInitMoveValues(&terminalFont->font, &fontFamily, &fontSize, &fontStyle);\n}\n\nstatic void detectGhostty(const FFstrbuf* exe, FFTerminalFontResult* terminalFont)\n{\n    FF_DEBUG(\"detectGhostty: start\");\n    FF_STRBUF_AUTO_DESTROY configPath = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY fontNameFallback = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();\n\n    // Try ghostty +show-config first\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n    const char* error = ffProcessAppendStdOut(&buffer, (char* const[]){\n        exe->chars,\n        \"+show-config\",\n        NULL,\n    });\n    if(error != NULL)\n    {\n        FF_DEBUG(\"`ghostty +show-config` failed: %s\", error);\n        return;\n    }\n\n    char* line = NULL;\n    size_t len = 0;\n    while (ffStrbufGetline(&line, &len, &buffer))\n    {\n        if (!fontName.length || !fontNameFallback.length)\n        {\n            if (ffStrStartsWith(line, \"font-family = \")) {\n                FF_DEBUG(\"found %s\", line);\n                ffStrbufSetNS(\n                    !fontName.length ? &fontName : &fontNameFallback,\n                    (uint32_t) (len - strlen(\"font-family = \")),\n                    line + strlen(\"font-family = \"));\n                continue;\n            }\n        }\n        if (!fontSize.length)\n        {\n            if (ffStrStartsWith(line, \"font-size = \")) {\n                FF_DEBUG(\"found fallback %s\", line);\n                ffStrbufSetNS(\n                    &fontSize,\n                    (uint32_t) (len - strlen(\"font-size = \")),\n                    line + strlen(\"font-size = \"));\n                continue;\n            }\n        }\n    }\n\n    if (fontName.length == 0) {\n        ffStrbufAppendS(&fontName, \"JetBrainsMono Nerd Font\");\n        FF_DEBUG(\"using default family='%s'\", fontName.chars);\n    }\n\n    if (fontSize.length == 0) {\n        ffStrbufAppendS(&fontSize,\n            #if __APPLE__\n                \"13\"\n            #else\n                \"12\"\n            #endif\n        );\n        FF_DEBUG(\"using default size='%s'\", fontSize.chars);\n    }\n\n    ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars);\n    if (fontNameFallback.length > 0) {\n        FF_DEBUG(\"applying fallback family='%s'\", fontNameFallback.chars);\n        ffFontInitValues(&terminalFont->fallback, fontNameFallback.chars, NULL);\n    }\n    FF_DEBUG(\"result family='%s' size='%s'%s\", fontName.chars, fontSize.chars, fontNameFallback.length ? \" (with fallback)\" : \"\");\n    FF_DEBUG(\"detectGhostty: end\");\n}\n\nFF_MAYBE_UNUSED static void detectTTY(FFTerminalFontResult* terminalFont)\n{\n    FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();\n\n    ffParsePropFile(FASTFETCH_TARGET_DIR_ETC\"/vconsole.conf\", \"Font =\", &fontName);\n\n    if(fontName.length == 0)\n    {\n        ffStrbufAppendS(&fontName, \"VGA default kernel font \");\n        ffProcessAppendStdOut(&fontName, (char* const[]){\n            \"showconsolefont\",\n            \"--info\",\n            NULL\n        });\n\n        ffStrbufTrimRight(&fontName, ' ');\n    }\n\n    if(fontName.length > 0)\n        ffFontInitCopy(&terminalFont->font, fontName.chars);\n    else\n        ffStrbufAppendS(&terminalFont->error, \"Couldn't find Font in \"FASTFETCH_TARGET_DIR_ETC\"/vconsole.conf\");\n}\n\nFF_MAYBE_UNUSED static bool detectKitty(const FFstrbuf* exe, FFTerminalFontResult* result)\n{\n    FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();\n\n    char fontHex[512] = \"\", sizeHex[512] = \"\";\n    // https://github.com/fastfetch-cli/fastfetch/discussions/1030#discussioncomment-9845233\n    if (ffGetTerminalResponse(\n        \"\\eP+q6b697474792d71756572792d666f6e745f66616d696c79;6b697474792d71756572792d666f6e745f73697a65\\e\\\\\", // kitty-query-font_family;kitty-query-font_size\n        2,\n        \"\\eP1+r%*[^=]=%511[^\\e]\\e\\\\\\eP1+r%*[^=]=%511[^\\e]\\e\\\\\", fontHex, sizeHex) == NULL && *fontHex && *sizeHex)\n    {\n        // decode hex string\n        for (const char* p = fontHex; p[0] && p[1]; p += 2)\n        {\n            unsigned value;\n            if (sscanf(p, \"%2x\", &value) == 1)\n                ffStrbufAppendC(&fontName, (char) value);\n        }\n        for (const char* p = sizeHex; p[0] && p[1]; p += 2)\n        {\n            unsigned value;\n            if (sscanf(p, \"%2x\", &value) == 1)\n                ffStrbufAppendC(&fontSize, (char) value);\n        }\n    }\n    else\n    {\n        FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();\n        if(!ffProcessAppendStdOut(&buf, (char* const[]){\n            exe->chars,\n            \"+kitten\",\n            \"query-terminal\",\n            NULL,\n        }))\n        {\n            ffParsePropLines(buf.chars, \"font_family: \", &fontName);\n            ffParsePropLines(buf.chars, \"font_size: \", &fontSize);\n        }\n        else\n        {\n            FFpropquery fontQuery[] = {\n                {\"font_family \", &fontName},\n                {\"font_size \", &fontSize},\n            };\n\n            ffParsePropFileConfigValues(\"kitty/kitty.conf\", 2, fontQuery);\n\n            if(fontName.length == 0)\n                ffStrbufSetS(&fontName, \"monospace\");\n            if(fontSize.length == 0)\n                ffStrbufSetS(&fontSize, \"11.0\");\n        }\n    }\n\n    ffFontInitValues(&result->font, fontName.chars, fontSize.chars);\n\n    return true;\n}\n\nstatic bool detectWezterm(const FFstrbuf* exe, FFTerminalFontResult* result)\n{\n    FF_STRBUF_AUTO_DESTROY cli = ffStrbufCreateCopy(exe);\n    ffStrbufSubstrBeforeLastC(&cli, '-');\n\n    #ifdef _WIN32\n    ffStrbufAppendS(&cli, \".exe\");\n    #endif\n\n    FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();\n\n    ffStrbufSetS(&result->error, ffProcessAppendStdOut(&fontName, (char* const[]){\n        cli.chars,\n        \"ls-fonts\",\n        \"--text\",\n        \"a\",\n        NULL\n    }));\n    if(result->error.length)\n        return false;\n\n    //LeftToRight\n    // 0 a    \\u{61}       x_adv=7  cells=1  glyph=a,180  wezterm.font(\"JetBrains Mono\", {weight=\"Regular\", stretch=\"Normal\", style=\"Normal\"})\n    //                                      <built-in>, BuiltIn\n    ffStrbufSubstrAfterFirstC(&fontName, '\"');\n    ffStrbufSubstrBeforeFirstC(&fontName, '\"');\n\n    if(!fontName.length)\n        return false;\n\n    ffFontInitCopy(&result->font, fontName.chars);\n    return true;\n}\n\nstatic bool detectTabby(FFTerminalFontResult* result)\n{\n    FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();\n\n    FFpropquery fontQuery[] = {\n        {\"font: \", &fontName},\n        {\"fontSize: \", &fontSize},\n    };\n\n    if(!ffParsePropFileConfigValues(\"tabby/config.yaml\", 2, fontQuery))\n        return false;\n\n    if(fontName.length == 0)\n        ffStrbufSetS(&fontName, \"monospace\");\n    if(fontSize.length == 0)\n        ffStrbufSetS(&fontSize, \"14\");\n\n    ffFontInitValues(&result->font, fontName.chars, fontSize.chars);\n\n    return true;\n}\n\nstatic bool detectContour(const FFstrbuf* exe, FFTerminalFontResult* result)\n{\n    FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();\n    if(ffProcessAppendStdOut(&buf, (char* const[]){\n        exe->chars,\n        \"font-locator\",\n        NULL\n    }))\n    {\n        ffStrbufAppendS(&result->error, \"`contour font-locator` failed\");\n        return false;\n    }\n\n    //[error] Missing key .logging.enabled. Using default: false.\n    //[error] ...\n    //Matching fonts using  : Fontconfig\n    //Font description      : (family=Sarasa Term SC Nerd weight=Regular slant=Roman spacing=Monospace, strict_spacing=yes)\n    //Number of fonts found : 49\n    //  path /usr/share/fonts/google-noto/NotoSansMono-Regular.ttf Regular Roman\n    //  path ...\n\n    uint32_t index = ffStrbufFirstIndexS(&buf, \"Font description      : (family=\");\n    if(index >= buf.length) return false;\n    index += (uint32_t) strlen(\"Font description      : (family=\");\n    ffStrbufSubstrBefore(&buf, ffStrbufNextIndexS(&buf, index, \" weight=\"));\n    ffFontInitCopy(&result->font, buf.chars + index);\n    return true;\n}\n\nstatic bool detectRio(FFTerminalFontResult* terminalFont)\n{\n    FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();\n\n    FFpropquery fontQueryToml[] = {\n        {\"family =\", &fontName},\n        {\"size =\", &fontSize},\n    };\n\n    ffParsePropFileConfigValues(\"rio/config.toml\", 2, fontQueryToml);\n\n    if(fontName.length == 0)\n        ffStrbufAppendS(&fontName, \"Cascadia Code\");\n\n    if(fontSize.length == 0)\n        ffStrbufAppendS(&fontSize, \"18\");\n\n    ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars);\n\n    return true;\n}\n\nbool ffDetectTerminalFontPlatform(const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont);\n\nstatic bool detectTerminalFontCommon(const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont)\n{\n    if(ffStrbufStartsWithIgnCaseS(&terminal->processName, \"alacritty\"))\n        detectAlacritty(terminalFont);\n    else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, \"wezterm-gui\"))\n        detectWezterm(&terminal->exe, terminalFont);\n    else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, \"tabby\"))\n        detectTabby(terminalFont);\n    else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, \"contour\"))\n        detectContour(&terminal->exe, terminalFont);\n    else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, \"ghostty\"))\n        detectGhostty(&terminal->exe, terminalFont);\n    else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, \"rio\"))\n        detectRio(terminalFont);\n\n    #ifndef _WIN32\n    else if(ffStrbufStartsWithIgnCaseS(&terminal->exe, \"/dev/pts/\"))\n        ffStrbufAppendS(&terminalFont->error, \"Terminal font detection is not supported on PTS\");\n    else if(ffStrbufIgnCaseEqualS(&terminal->processName, \"kitty\"))\n        detectKitty(&terminal->exe, terminalFont);\n    else if(ffStrbufStartsWithIgnCaseS(&terminal->exe, \"/dev/tty\"))\n        detectTTY(terminalFont);\n    #endif\n\n    else\n        return false;\n\n    return true;\n}\n\nbool ffDetectTerminalFont(FFTerminalFontResult* result)\n{\n    const FFTerminalResult* terminal = ffDetectTerminal();\n\n    if(terminal->processName.length == 0)\n        ffStrbufAppendS(&result->error, \"Terminal font needs successful terminal detection\");\n\n    else if(!detectTerminalFontCommon(terminal, result))\n        ffDetectTerminalFontPlatform(terminal, result);\n\n    if(result->error.length == 0 && result->font.pretty.length == 0)\n        ffStrbufAppendF(&result->error, \"Unknown terminal: %s\", terminal->processName.chars);\n\n    return result->error.length == 0;\n}\n"
  },
  {
    "path": "src/detection/terminalfont/terminalfont.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"common/font.h\"\n#include \"modules/font/option.h\"\n\ntypedef struct FFTerminalFontResult\n{\n    FFstrbuf error;\n    FFfont font;\n    FFfont fallback;\n} FFTerminalFontResult;\n\nbool ffDetectTerminalFont(FFTerminalFontResult* result);\n"
  },
  {
    "path": "src/detection/terminalfont/terminalfont_android.c",
    "content": "#include \"fastfetch.h\"\n#include \"terminalfont.h\"\n#include \"detection/terminalshell/terminalshell.h\"\n#include \"common/io.h\"\n\n#ifdef FF_HAVE_FREETYPE\n    #include \"common/library.h\"\n    #include <ft2build.h>\n    #include FT_FREETYPE_H\n#endif\n\n#define FF_TERMUX_FONT_PATH FASTFETCH_TARGET_DIR_HOME \"/.termux/font.ttf\"\n\nconst char* detectTermux(FFTerminalFontResult* terminalFont)\n{\n    if(!ffPathExists(FF_TERMUX_FONT_PATH, FF_PATHTYPE_FILE))\n    {\n        ffFontInitCopy(&terminalFont->font, \"monospace\");\n        return NULL;\n    }\n\n    #ifdef FF_HAVE_FREETYPE\n\n    FF_LIBRARY_LOAD_MESSAGE(freetype, \"libfreetype\" FF_LIBRARY_EXTENSION, 2)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(freetype, FT_Init_FreeType);\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(freetype, FT_New_Face);\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(freetype, FT_Done_Face);\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(freetype, FT_Done_FreeType);\n\n    FT_Library library = NULL;\n    FT_Face face = NULL;\n    const char* error = NULL;\n\n    if(ffFT_Init_FreeType(&library))\n    {\n        error = \"FT_Init_FreeType() failed\";\n        goto exit;\n    }\n\n    if(ffFT_New_Face(library, FF_TERMUX_FONT_PATH, 0, &face))\n    {\n        error = \"FT_NEW_Face(\" FF_TERMUX_FONT_PATH \") failed\";\n        goto exit;\n    }\n\n    ffFontInitCopy(&terminalFont->font, face->family_name);\n\nexit:\n    if(face) ffFT_Done_Face(face);\n    if(library) ffFT_Done_FreeType(library);\n\n    return error;\n\n    #else\n\n    FF_UNUSED(terminalFont);\n    return \"Fastfetch was built without freetype2 support\";\n\n    #endif\n}\n\nbool ffDetectTerminalFontPlatform(const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont)\n{\n    if(ffStrbufEqualS(&terminal->processName, \"com.termux\"))\n        ffStrbufSetS(&terminalFont->error, detectTermux(terminalFont));\n    else\n    {\n        bool ffDetectTerminalFontPlatformLinux(const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont);\n        return ffDetectTerminalFontPlatformLinux(terminal, terminalFont);\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "src/detection/terminalfont/terminalfont_apple.m",
    "content": "#include \"terminalfont.h\"\n#include \"common/font.h\"\n#include \"detection/terminalshell/terminalshell.h\"\n#include \"common/apple/osascript.h\"\n\n#include <stdlib.h>\n#include <string.h>\n#import <Foundation/Foundation.h>\n\nstatic void detectIterm2(FFTerminalFontResult* terminalFont)\n{\n    const char* profile = getenv(\"ITERM_PROFILE\");\n    if (profile == NULL)\n    {\n        ffStrbufAppendS(&terminalFont->error, \"environment variable ITERM_PROFILE not set\");\n        return;\n    }\n\n    NSError* error;\n    NSString* fileName = [NSString stringWithFormat:@\"file://%s/Library/Preferences/com.googlecode.iterm2.plist\", instance.state.platform.homeDir.chars];\n    NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName]\n                                       error:&error];\n    if(error)\n    {\n        ffStrbufAppendS(&terminalFont->error, error.localizedDescription.UTF8String);\n        return;\n    }\n\n    for(NSDictionary* bookmark in dict[@\"New Bookmarks\"])\n    {\n        if(![bookmark[@\"Name\"] isEqualToString:@(profile)])\n            continue;\n\n        NSString* normalFont = bookmark[@\"Normal Font\"];\n        if(!normalFont)\n        {\n            ffStrbufAppendF(&terminalFont->error, \"`Normal Font` key in profile `%s` doesn't exist\", profile);\n            return;\n        }\n        ffFontInitWithSpace(&terminalFont->font, normalFont.UTF8String);\n\n        NSNumber* useNonAsciiFont = bookmark[@\"Use Non-ASCII Font\"];\n        if(useNonAsciiFont.boolValue)\n        {\n            NSString* nonAsciiFont = bookmark[@\"Non Ascii Font\"];\n            if (nonAsciiFont)\n                ffFontInitWithSpace(&terminalFont->fallback, nonAsciiFont.UTF8String);\n        }\n        return;\n    }\n\n    ffStrbufAppendF(&terminalFont->error, \"find profile `%s` bookmark failed\", profile);\n}\n\nstatic void detectAppleTerminal(FFTerminalFontResult* terminalFont)\n{\n    FF_STRBUF_AUTO_DESTROY font = ffStrbufCreate();\n    ffOsascript(\"tell application \\\"Terminal\\\" to font name of window frontmost & \\\" \\\" & font size of window frontmost\", &font);\n\n    if(font.length == 0)\n    {\n        ffStrbufAppendS(&terminalFont->error, \"executing osascript failed\");\n        return;\n    }\n\n    ffFontInitWithSpace(&terminalFont->font, font.chars);\n}\n\nstatic void detectWarpTerminal(FFTerminalFontResult* terminalFont)\n{\n    NSError* error;\n    NSString* fileName = [NSString stringWithFormat:@\"file://%s/Library/Preferences/dev.warp.Warp-Stable.plist\", instance.state.platform.homeDir.chars];\n    NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName]\n                                       error:&error];\n    if(error)\n    {\n        ffStrbufAppendS(&terminalFont->error, error.localizedDescription.UTF8String);\n        return;\n    }\n\n    NSString* fontName = dict[@\"FontName\"];\n    if(!fontName)\n        fontName = @\"Hack\";\n    else\n        fontName = [fontName stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@\"\\\"\"]];\n\n    NSString* fontSize = dict[@\"FontSize\"];\n    if(!fontSize)\n        fontSize = @\"13\";\n\n    ffFontInitValues(&terminalFont->font, fontName.UTF8String, fontSize.UTF8String);\n}\n\nbool ffDetectTerminalFontPlatform(const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont)\n{\n    if(ffStrbufIgnCaseEqualS(&terminal->processName, \"iterm.app\") ||\n        ffStrbufStartsWithIgnCaseS(&terminal->processName, \"iTermServer-\"))\n        detectIterm2(terminalFont);\n    else if(ffStrbufIgnCaseEqualS(&terminal->processName, \"Apple_Terminal\"))\n        detectAppleTerminal(terminalFont);\n    else if(ffStrbufIgnCaseEqualS(&terminal->processName, \"WarpTerminal\"))\n        detectWarpTerminal(terminalFont);\n    else\n        return false;\n    return true;\n}\n"
  },
  {
    "path": "src/detection/terminalfont/terminalfont_linux.c",
    "content": "#include \"common/font.h\"\n#include \"terminalfont.h\"\n#include \"common/settings.h\"\n#include \"common/properties.h\"\n#include \"common/parsing.h\"\n#include \"common/io.h\"\n#include \"common/processing.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/stringUtils.h\"\n#include \"common/binary.h\"\n#include \"detection/terminalshell/terminalshell.h\"\n#include \"detection/displayserver/displayserver.h\"\n\nstatic const char* getSystemMonospaceFont(void)\n{\n    const FFDisplayServerResult* wmde = ffConnectDisplayServer();\n\n    if(ffStrbufIgnCaseEqualS(&wmde->dePrettyName, \"Cinnamon\"))\n    {\n        const char* systemMonospaceFont = ffSettingsGetGnome(\"/org/cinnamon/desktop/interface/monospace-font-name\", \"org.cinnamon.desktop.interface\", NULL, \"monospace-font-name\", FF_VARIANT_TYPE_STRING).strValue;\n        if(ffStrSet(systemMonospaceFont))\n            return systemMonospaceFont;\n    }\n    else if(ffStrbufIgnCaseEqualS(&wmde->dePrettyName, \"Mate\"))\n    {\n        const char* systemMonospaceFont = ffSettingsGetGnome(\"/org/mate/interface/monospace-font-name\", \"org.mate.interface\", NULL, \"monospace-font-name\", FF_VARIANT_TYPE_STRING).strValue;\n        if(ffStrSet(systemMonospaceFont))\n            return systemMonospaceFont;\n    }\n\n    return ffSettingsGetGnome(\"/org/gnome/desktop/interface/monospace-font-name\", \"org.gnome.desktop.interface\", NULL, \"monospace-font-name\", FF_VARIANT_TYPE_STRING).strValue;\n}\n\nstatic void detectKgx(FFTerminalFontResult* terminalFont)\n{\n    // kgx (gnome console) doesn't support profiles\n    if(!ffSettingsGetGnome(\"/org/gnome/Console/use-system-font\", \"org.gnome.Console\", NULL, \"use-system-font\", FF_VARIANT_TYPE_BOOL).boolValue)\n    {\n        FF_AUTO_FREE const char* fontName = ffSettingsGetGnome(\"/org/gnome/Console/custom-font\", \"org.gnome.Console\", NULL, \"custom-font\", FF_VARIANT_TYPE_STRING).strValue;\n        if(ffStrSet(fontName))\n            ffFontInitPango(&terminalFont->font, fontName);\n        else\n            ffStrbufAppendF(&terminalFont->error, \"Couldn't get terminal font from GSettings (org.gnome.Console::custom-font)\");\n    }\n    else\n    {\n        FF_AUTO_FREE const char* fontName = getSystemMonospaceFont();\n        if(ffStrSet(fontName))\n            ffFontInitPango(&terminalFont->font, fontName);\n        else\n            ffStrbufAppendS(&terminalFont->error, \"Couldn't get system monospace font name from GSettings / DConf\");\n    }\n}\n\nstatic void detectPtyxis(FFTerminalFontResult* terminalFont)\n{\n    if(!ffSettingsGetGnome(\"/org/gnome/Ptyxis/use-system-font\", \"org.gnome.Ptyxis\", NULL, \"use-system-font\", FF_VARIANT_TYPE_BOOL).boolValue)\n    {\n        FF_AUTO_FREE const char* fontName = ffSettingsGetGnome(\"/org/gnome/Ptyxis/font-name\", \"org.gnome.Ptyxis\", NULL, \"font-name\", FF_VARIANT_TYPE_STRING).strValue;\n        if(ffStrSet(fontName))\n            ffFontInitPango(&terminalFont->font, fontName);\n        else\n            ffStrbufAppendF(&terminalFont->error, \"Couldn't get terminal font from GSettings (org.gnome.Ptyxis::font-name)\");\n    }\n    else\n    {\n        FF_AUTO_FREE const char* fontName = getSystemMonospaceFont();\n        if(ffStrSet(fontName))\n            ffFontInitPango(&terminalFont->font, fontName);\n        else\n            ffStrbufAppendS(&terminalFont->error, \"Couldn't get system monospace font name from GSettings / DConf\");\n    }\n}\n\nstatic void detectFromGSettings(const char* profilePath, const char* profileList, const char* profile, const char* defaultProfileKey, FFTerminalFontResult* terminalFont)\n{\n    FF_AUTO_FREE const char* defaultProfile = ffSettingsGetGSettings(profileList, NULL, defaultProfileKey, FF_VARIANT_TYPE_STRING).strValue;\n    if(!ffStrSet(defaultProfile))\n    {\n        ffStrbufAppendF(&terminalFont->error, \"Could not get default profile from gsettings: %s\", profileList);\n        return;\n    }\n\n    FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateA(128);\n    ffStrbufAppendS(&path, profilePath);\n    ffStrbufAppendS(&path, defaultProfile);\n    ffStrbufAppendC(&path, '/');\n\n    if(!ffSettingsGetGSettings(profile, path.chars, \"use-system-font\", FF_VARIANT_TYPE_BOOL).boolValue)\n    {\n        FF_AUTO_FREE const char* fontName = ffSettingsGetGSettings(profile, path.chars, \"font\", FF_VARIANT_TYPE_STRING).strValue;\n        if(ffStrSet(fontName))\n            ffFontInitPango(&terminalFont->font, fontName);\n        else\n            ffStrbufAppendF(&terminalFont->error, \"Couldn't get terminal font from GSettings (%s::%s::font)\", profile, path.chars);\n    }\n    else\n    {\n        FF_AUTO_FREE const char* fontName = getSystemMonospaceFont();\n        if(ffStrSet(fontName))\n            ffFontInitPango(&terminalFont->font, fontName);\n        else\n            ffStrbufAppendS(&terminalFont->error, \"Couldn't get system monospace font name from GSettings / DConf\");\n    }\n}\n\nstatic void detectFromConfigFile(const char* configFile, const char* start, FFTerminalFontResult* terminalFont)\n{\n    FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();\n    ffParsePropFileConfig(configFile, start, &fontName);\n\n    if(fontName.length == 0)\n        ffStrbufAppendF(&terminalFont->error, \"Couldn't find %s in .config/%s\", start, configFile);\n    else\n        ffFontInitPango(&terminalFont->font, fontName.chars);\n}\n\nstatic void detectKonsole(FFTerminalFontResult* terminalFont, const char* rcFile)\n{\n    FF_STRBUF_AUTO_DESTROY profile = ffStrbufCreate();\n    if(!ffParsePropFileConfig(rcFile, \"DefaultProfile =\", &profile))\n    {\n        ffStrbufAppendF(&terminalFont->error, \"Configuration \\\".config/%s\\\" doesn't exist\", rcFile);\n        return;\n    }\n\n    if(profile.length == 0)\n    {\n        ffStrbufAppendS(&terminalFont->error, \"Built-in profile is used\");\n        return;\n    }\n\n    FF_STRBUF_AUTO_DESTROY profilePath = ffStrbufCreateA(32);\n    ffStrbufAppendS(&profilePath, \"konsole/\");\n    ffStrbufAppend(&profilePath, &profile);\n\n    FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();\n    ffParsePropFileData(profilePath.chars, \"Font =\", &fontName);\n\n    if(fontName.length == 0)\n        ffStrbufAppendF(&terminalFont->error, \"Couldn't find \\\"Font=%%[^\\\\n]\\\" in \\\"%s\\\"\", profilePath.chars);\n    else\n        ffFontInitQt(&terminalFont->font, fontName.chars);\n}\n\nstatic void detectXFCETerminal(FFTerminalFontResult* terminalFont)\n{\n    FF_STRBUF_AUTO_DESTROY useSysFont = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();\n\n    const char* path = \"xfce4/xfconf/xfce-perchannel-xml/xfce4-terminal.xml\";\n    bool configFound = ffParsePropFileConfigValues(path, 2, (FFpropquery[]) {\n        {\"<property name=\\\"font-use-system\\\" type=\\\"bool\\\" value=\\\"\", &useSysFont},\n        {\"<property name=\\\"font-name\\\" type=\\\"string\\\" value=\\\"\", &fontName}\n    });\n\n    if (configFound)\n    {\n        ffStrbufSubstrBeforeLastC(&useSysFont, '\"');\n        ffStrbufSubstrBeforeLastC(&fontName, '\"');\n    }\n    else\n    {\n        path = \"xfce4/terminal/terminalrc\";\n        configFound = ffParsePropFileConfigValues(path, 2, (FFpropquery[]) {\n            {\"FontUseSystem = \", &useSysFont},\n            {\"FontName = \", &fontName}\n        });\n    }\n\n    if(configFound && (useSysFont.length == 0 || ffStrbufIgnCaseEqualS(&useSysFont, \"false\")))\n    {\n        if(fontName.length == 0)\n            ffStrbufAppendF(&terminalFont->error, \"Couldn't find FontName in %s\", path);\n        else\n            ffFontInitPango(&terminalFont->font, fontName.chars);\n    }\n    else\n    {\n        const char* systemFontName = ffSettingsGetXFConf(\"xsettings\", \"/Gtk/MonospaceFontName\", FF_VARIANT_TYPE_STRING).strValue;\n        if(ffStrSet(systemFontName))\n            ffFontInitPango(&terminalFont->font, systemFontName);\n        else\n            ffStrbufAppendS(&terminalFont->error, \"Couldn't find xsettings::/Gtk/MonospaceFontName in XFConf\");\n    }\n}\n\nstatic void detectDeepinTerminal(FFTerminalFontResult* terminalFont)\n{\n    FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();\n\n    FF_STRBUF_AUTO_DESTROY profile = ffStrbufCreateA(64);\n    ffSearchUserConfigFile(&instance.state.platform.configDirs, \"deepin/deepin-terminal/config.conf\", &profile);\n    FILE* file = fopen(profile.chars, \"r\");\n\n    if(file)\n    {\n        char* line = NULL;\n        size_t len = 0;\n\n        for(int count = 0; getline(&line, &len, file) != -1 && count < 2;)\n        {\n            if(ffStrEquals(line, \"[basic.interface.font]\\n\"))\n            {\n                if(getline(&line, &len, file) != -1)\n                    ffParsePropLine(line, \"value=\", &fontName);\n                ++count;\n            }\n            else if(ffStrEquals(line, \"[basic.interface.font_size]\\n\"))\n            {\n                if(getline(&line, &len, file) != -1)\n                    ffParsePropLine(line, \"value=\", &fontSize);\n                ++count;\n            }\n        }\n\n        free(line);\n        fclose(file);\n    }\n\n    if(fontName.length == 0)\n        ffStrbufAppendS(&fontName, \"Noto Sans Mono\");\n    if(fontSize.length == 0)\n        ffStrbufAppendS(&fontSize, \"11\");\n\n    ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars);\n}\n\nstatic void detectFootTerminal(FFTerminalFontResult* terminalFont)\n{\n    FF_STRBUF_AUTO_DESTROY font = ffStrbufCreate();\n\n    if (!ffParsePropFileConfig(\"foot/foot.ini\", \"font=\", &font) || !ffStrSet(font.chars))\n    {\n        ffFontInitValues(&terminalFont->font, \"monospace\", \"8\");\n        return;\n    }\n\n    //Sarasa Term SC Nerd:size=8\n    uint32_t colon = ffStrbufFirstIndexC(&font, ':');\n    if(colon == font.length)\n    {\n        ffFontInitValues(&terminalFont->font, font.chars, \"8\");\n        return;\n    }\n    uint32_t equal = ffStrbufNextIndexS(&font, colon, \"size=\");\n    font.chars[colon] = '\\0';\n    if (equal == font.length)\n    {\n        ffFontInitValues(&terminalFont->font, font.chars, \"8\");\n        return;\n    }\n    uint32_t size = equal + (uint32_t) strlen(\"size=\");\n    uint32_t comma = ffStrbufNextIndexC(&font, size, ',');\n    if (comma < font.length)\n        font.chars[comma] = '\\0';\n    ffFontInitValues(&terminalFont->font, font.chars, &font.chars[size]);\n    if (comma < font.length)\n        ffFontInitValues(&terminalFont->fallback, &font.chars[comma + 1], NULL);\n}\n\nstatic void detectQTerminal(FFTerminalFontResult* terminalFont)\n{\n    FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();\n\n    ffParsePropFileConfigValues(\"qterminal.org/qterminal.ini\", 2, (FFpropquery[]) {\n        {\"fontFamily=\", &fontName},\n        {\"fontSize=\", &fontSize},\n    });\n\n    if (fontName.length == 0)\n        ffStrbufAppendS(&fontName, \"monospace\");\n    if (fontSize.length == 0)\n        ffStrbufAppendS(&fontSize, \"12\");\n    ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars);\n}\n\nstatic void detectXterm(FFTerminalFontResult* terminalFont)\n{\n    FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();\n\n    ffParsePropFileHomeValues(\".Xresources\", 2, (FFpropquery[]) {\n        {\"xterm*faceName:\", &fontName},\n        {\"xterm*faceSize:\", &fontSize},\n    });\n\n    if (fontName.length == 0)\n    {\n        ffParsePropFileHomeValues(\".Xresources\", 2, (FFpropquery[]) {\n            {\"xterm.vt100.faceName:\", &fontName},\n            {\"xterm.vt100.faceSize:\", &fontSize},\n        });\n    }\n\n    if (fontName.length == 0)\n        ffStrbufAppendS(&fontName, \"fixed\");\n    if (fontSize.length == 0)\n        ffStrbufAppendS(&fontSize, \"8.0\");\n    ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars);\n}\n\nstatic bool extractStTermFont(const char* str, FF_MAYBE_UNUSED uint32_t len, void* userdata)\n{\n    if (!ffStrContains(str, \"size=\")) return true;\n    ffStrbufSetNS((FFstrbuf*) userdata, len, str);\n    return false;\n}\n\nstatic void detectSt(FFTerminalFontResult* terminalFont, const FFTerminalResult* terminal)\n{\n    FF_STRBUF_AUTO_DESTROY size = ffStrbufCreateF(\"/proc/%u/cmdline\", terminal->pid);\n    FF_STRBUF_AUTO_DESTROY font = ffStrbufCreate();\n    if (!ffAppendFileBuffer(size.chars, &font))\n    {\n        ffStrbufAppendF(&terminalFont->error, \"Failed to open %s\", size.chars);\n        return;\n    }\n\n    const char* p = memmem(font.chars, font.length, \"\\0-f\", sizeof(\"\\0-f\")); // find parameter of `-f`\n    if (p)\n    {\n        // st was executed with `-f` parameter\n        ffStrbufSubstrAfter(&font, (uint32_t) (p + (sizeof(\"\\0-f\") - 1) - font.chars));\n        ffStrbufRecalculateLength(&font);\n    }\n    else\n    {\n        ffStrbufClear(&font);\n\n        const char* error = ffBinaryExtractStrings(terminal->exePath.chars, extractStTermFont, &font, (uint32_t) strlen(\"size=0\"));\n        if (error)\n        {\n            ffStrbufAppendS(&terminalFont->error, error);\n            return;\n        }\n        if (font.length == 0)\n        {\n            ffStrbufAppendS(&terminalFont->error, \"No font config found in st binary\");\n            return;\n        }\n    }\n\n    // JetBrainsMono Nerd Font Mono:pixelsize=12:antialias=true:autohint=true\n\n    uint32_t index = ffStrbufFirstIndexC(&font, ':');\n    if (index != font.length)\n    {\n        uint32_t sIndex = ffStrbufNextIndexS(&font, index + 1, \"size=\");\n        if (sIndex != font.length)\n        {\n            sIndex += (uint32_t) strlen(\"size=\");\n            uint32_t sIndexEnd = ffStrbufNextIndexC(&font, sIndex, ':');\n            ffStrbufSetNS(&size, sIndexEnd - sIndex, font.chars + sIndex);\n        }\n        ffStrbufSubstrBefore(&font, index);\n    }\n    else\n        ffStrbufClear(&size);\n    ffFontInitValues(&terminalFont->font, font.chars, size.chars);\n}\n\nstatic void detectWarp(FFTerminalFontResult* terminalFont)\n{\n    FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreateA(64);\n\n    FF_LIST_FOR_EACH(FFstrbuf, dirPrefix, instance.state.platform.configDirs)\n    {\n        //We need to copy the dir each time, because it used by multiple threads, so we can't directly write to it.\n        ffStrbufSet(&baseDir, dirPrefix);\n        ffStrbufAppendS(&baseDir, \"warp-terminal/user_preferences.json\");\n\n        yyjson_doc* doc = yyjson_read_file(baseDir.chars, YYJSON_READ_INSITU | YYJSON_READ_ALLOW_TRAILING_COMMAS | YYJSON_READ_ALLOW_COMMENTS, NULL, NULL);\n        if (!doc) continue;\n\n        yyjson_val* prefs = yyjson_obj_get(yyjson_doc_get_root(doc), \"prefs\");\n        if (yyjson_is_obj(prefs))\n        {\n            const char* fontName = yyjson_get_str(yyjson_obj_get(prefs, \"FontName\"));\n            if (!fontName) fontName = \"Hack\";\n            const char* fontSize = yyjson_get_str(yyjson_obj_get(prefs, \"FontSize\"));\n            if (!fontSize) fontSize = \"13\";\n\n            ffFontInitValues(&terminalFont->font, fontName, fontSize);\n        }\n        yyjson_doc_free(doc);\n        return;\n    }\n}\n\nstatic void detectTerminator(FFTerminalFontResult* result)\n{\n    FF_STRBUF_AUTO_DESTROY useSystemFont = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();\n\n    if(!ffParsePropFileConfigValues(\"terminator/config\", 2, (FFpropquery[]) {\n        {\"use_system_font =\", &useSystemFont},\n        {\"font =\", &fontName},\n    }) || ffStrbufIgnCaseEqualS(&useSystemFont, \"True\"))\n    {\n        FF_AUTO_FREE const char* fontName = getSystemMonospaceFont();\n        if(ffStrSet(fontName))\n            ffFontInitPango(&result->font, fontName);\n        else\n            ffStrbufAppendS(&result->error, \"Couldn't get system monospace font name from GSettings / DConf\");\n        return;\n    }\n\n    if(fontName.length == 0)\n        ffFontInitValues(&result->font, \"Mono\", \"10\");\n    else\n        ffFontInitPango(&result->font, fontName.chars);\n}\n\nstatic void detectWestonTerminal(FFTerminalFontResult* terminalFont)\n{\n    FF_STRBUF_AUTO_DESTROY font = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY size = ffStrbufCreate();\n    ffParsePropFileConfigValues(\"weston.ini\", 2, (FFpropquery[]) {\n        {\"font=\", &font},\n        {\"font-size=\", &size},\n    });\n    if (!font.length) ffStrbufSetStatic(&font, \"DejaVu Sans Mono\");\n    if (!size.length) ffStrbufSetStatic(&size, \"14\");\n    ffFontInitValues(&terminalFont->font, font.chars, size.chars);\n}\n\nstatic void detectUrxvt(FFTerminalFontResult* terminalFont)\n{\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n\n    if (!(ffParsePropFileHomeValues(\".Xresources\", 1, (FFpropquery[]) {\n        {\"URxvt.font:\", &buffer},\n    }) || ffParsePropFileHomeValues(\".Xdefaults\", 1, (FFpropquery[]) {\n        {\"URxvt.font:\", &buffer},\n    })))\n    {\n        ffStrbufAppendS(&terminalFont->error, \"Could not find URxvt.font in .Xresources or .Xdefaults\");\n        return;\n    }\n\n    uint32_t index = 0;\n\n    char* line = NULL;\n    size_t len = 0;\n    while (ffStrbufGetdelim(&line, &len, ',', &buffer))\n    {\n        FFfont* font = index == 0 ? &terminalFont->font : &terminalFont->fallback;\n        if (line[0] == '-')\n            ffFontInitXlfd(font, line);\n        else if (ffStrStartsWith(line, \"xft:\"))\n            ffFontInitXft(font, line + 4);\n        else\n        {\n            ffStrbufAppendF(&terminalFont->error, \"Unknown URxvt font format: %s\", line);\n            continue;\n        }\n        index++;\n        if (index > 1) break;\n    }\n}\n\nstatic bool detectCosmicTerm(FFTerminalFontResult* terminalFont)\n{\n    FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();\n\n    FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateCopy(&instance.state.platform.homeDir);\n    ffStrbufAppendS(&path, \".config/cosmic/com.system76.CosmicTerm/v1/\");\n    uint32_t baseLen = path.length;\n\n    ffStrbufAppendS(&path, \"font_name\");\n    ffReadFileBuffer(path.chars, &fontName);\n    ffStrbufTrim(&path, '\"');\n    ffStrbufSubstrBefore(&path, baseLen);\n    if (fontName.length == 0) ffStrbufSetStatic(&fontName, \"Noto Sans Mono\");\n\n    ffStrbufAppendS(&path, \"font_size\");\n    if (ffReadFileBuffer(path.chars, &fontSize))\n        ffStrbufAppendS(&fontSize, \"px\");\n    ffStrbufSubstrBefore(&path, baseLen);\n    if (fontSize.length == 0) ffStrbufSetStatic(&fontSize, \"14px\");\n\n    ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars);\n\n    return true;\n}\n\n#ifdef __HAIKU__\nstatic void detectHaikuTerminal(FFTerminalFontResult* terminalFont)\n{\n    FF_STRBUF_AUTO_DESTROY font = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY size = ffStrbufCreate();\n    ffParsePropFileConfigValues(\"Terminal/Default\", 2, (FFpropquery[]) {\n        {\"\\\"Half Font Family\\\" , \", &font},\n        {\"\\\"Half Font Size\\\" , \", &size},\n    });\n    if (!font.length) ffStrbufSetStatic(&font, \"Noto Sans Mono\");\n    if (!size.length) ffStrbufSetStatic(&size, \"12\");\n    ffFontInitValues(&terminalFont->font, font.chars, size.chars);\n}\n#endif\n\nbool\n#ifdef __ANDROID__\nffDetectTerminalFontPlatformLinux\n#else\nffDetectTerminalFontPlatform\n#endif\n(const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont)\n{\n    if(ffStrbufIgnCaseEqualS(&terminal->processName, \"konsole\"))\n        detectKonsole(terminalFont, \"konsolerc\");\n    else if(ffStrbufIgnCaseEqualS(&terminal->processName, \"yakuake\"))\n        detectKonsole(terminalFont, \"yakuakerc\");\n    else if(ffStrbufIgnCaseEqualS(&terminal->processName, \"xfce4-terminal\"))\n        detectXFCETerminal(terminalFont);\n    else if(ffStrbufIgnCaseEqualS(&terminal->processName, \"lxterminal\"))\n        detectFromConfigFile(\"lxterminal/lxterminal.conf\", \"fontname =\", terminalFont);\n    else if(ffStrbufIgnCaseEqualS(&terminal->processName, \"tilix\"))\n        detectFromGSettings(\"/com/gexperts/Tilix/profiles/\", \"com.gexperts.Tilix.ProfilesList\", \"com.gexperts.Tilix.Profile\", \"default\", terminalFont);\n    else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, \"gnome-terminal\"))\n        detectFromGSettings(\"/org/gnome/terminal/legacy/profiles:/:\", \"org.gnome.Terminal.ProfilesList\", \"org.gnome.Terminal.Legacy.Profile\", \"default\", terminalFont);\n    else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, \"ptyxis-agent\"))\n        detectPtyxis(terminalFont);\n    else if(ffStrbufIgnCaseEqualS(&terminal->processName, \"kgx\"))\n        detectKgx(terminalFont);\n    else if(ffStrbufIgnCaseEqualS(&terminal->processName, \"mate-terminal\"))\n        detectFromGSettings(\"/org/mate/terminal/profiles/\", \"org.mate.terminal.global\", \"org.mate.terminal.profile\", \"default-profile\", terminalFont);\n    else if(ffStrbufIgnCaseEqualS(&terminal->processName, \"deepin-terminal\"))\n        detectDeepinTerminal(terminalFont);\n    else if(ffStrbufIgnCaseEqualS(&terminal->processName, \"foot\"))\n        detectFootTerminal(terminalFont);\n    else if(ffStrbufIgnCaseEqualS(&terminal->processName, \"qterminal\"))\n        detectQTerminal(terminalFont);\n    else if(ffStrbufIgnCaseEqualS(&terminal->processName, \"xterm\"))\n        detectXterm(terminalFont);\n    else if(ffStrbufIgnCaseEqualS(&terminal->processName, \"st\"))\n        detectSt(terminalFont, terminal);\n    else if(ffStrbufIgnCaseEqualS(&terminal->processName, \"warp\"))\n        detectWarp(terminalFont);\n    else if(ffStrbufIgnCaseEqualS(&terminal->processName, \"weston-terminal\"))\n        detectWestonTerminal(terminalFont);\n    else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, \"terminator\"))\n        detectTerminator(terminalFont);\n    else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, \"sakura\"))\n        detectFromConfigFile(\"sakura/sakura.conf\", \"font=\", terminalFont);\n    else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, \"cosmic-term\"))\n        detectCosmicTerm(terminalFont);\n    #ifdef __HAIKU__\n    else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, \"Terminal\"))\n        detectHaikuTerminal(terminalFont);\n    #endif\n    else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, \"termite\"))\n        detectFromConfigFile(\"termite/config\", \"font =\", terminalFont);\n    else if(ffStrbufIgnCaseEqualS(&terminal->processName, \"rxvt\")\n        || ffStrbufIgnCaseEqualS(&terminal->processName, \"urxvt\")\n        || ffStrbufIgnCaseEqualS(&terminal->processName, \"urxvtd\"))\n        detectUrxvt(terminalFont);\n    else\n        return false;\n    return true;\n}\n"
  },
  {
    "path": "src/detection/terminalfont/terminalfont_windows.c",
    "content": "#include \"common/library.h\"\n#include \"common/io.h\"\n#include \"common/path.h\"\n#include \"common/processing.h\"\n#include \"common/properties.h\"\n#include \"common/windows/unicode.h\"\n#include \"common/windows/registry.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/terminalshell/terminalshell.h\"\n#include \"terminalfont.h\"\n\n#include <shlobj.h>\n#include <windows.h>\n#include <stdlib.h>\n\nstatic const char* detectWTProfile(yyjson_val* profile, FFstrbuf* name, double* size)\n{\n    yyjson_val* font = yyjson_obj_get(profile, \"font\");\n    if (!font)\n        return \"yyjson_obj_get(profile, \\\"font\\\"); failed\";\n\n    if (!yyjson_is_obj(font))\n        return \"yyjson_is_obj(font) returns false\";\n\n    if (name->length == 0)\n    {\n        ffStrbufAppendJsonVal(name, yyjson_obj_get(font, \"face\"));\n    }\n\n    if (*size < 0)\n    {\n        yyjson_val* psize = yyjson_obj_get(font, \"size\");\n        if (yyjson_is_num(psize))\n            *size = unsafe_yyjson_get_num(psize);\n    }\n    return NULL;\n}\n\nstatic inline void wrapYyjsonFree(yyjson_doc** doc)\n{\n    assert(doc);\n    if (*doc)\n        yyjson_doc_free(*doc);\n}\n\nstatic const char* detectFromWTImpl(FFstrbuf* content, FFstrbuf* name, double* size)\n{\n    yyjson_doc* __attribute__((__cleanup__(wrapYyjsonFree))) doc = yyjson_read_opts(content->chars, content->length, YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS, NULL, NULL);\n    if (!doc)\n        return \"Failed to parse WT JSON config file\";\n\n    yyjson_val* const root = yyjson_doc_get_root(doc);\n    assert(root);\n\n    yyjson_val* profiles = yyjson_obj_get(root, \"profiles\");\n    if (!profiles)\n        return \"yyjson_obj_get(root, \\\"profiles\\\") failed\";\n\n    FF_STRBUF_AUTO_DESTROY wtProfileId = ffStrbufCreateS(getenv(\"WT_PROFILE_ID\"));\n    ffStrbufTrim(&wtProfileId, '\\'');\n    if (wtProfileId.length > 0)\n    {\n        yyjson_val* list = yyjson_obj_get(profiles, \"list\");\n        if (yyjson_is_arr(list))\n        {\n            yyjson_val* profile;\n            size_t idx, max;\n            yyjson_arr_foreach(list, idx, max, profile)\n            {\n                yyjson_val* guid = yyjson_obj_get(profile, \"guid\");\n\n                if(ffStrbufEqualS(&wtProfileId, yyjson_get_str(guid)))\n                {\n                    detectWTProfile(profile, name, size);\n                    break;\n                }\n            }\n        }\n    }\n\n    yyjson_val* defaults = yyjson_obj_get(profiles, \"defaults\");\n    if (defaults)\n        detectWTProfile(defaults, name, size);\n\n    if(name->length == 0)\n        ffStrbufSetS(name, \"Cascadia Mono\");\n    if(*size < 0)\n        *size = 12;\n    return NULL;\n}\n\nstatic void detectFromWindowsTerminal(const FFstrbuf* terminalExe, FFTerminalFontResult* terminalFont)\n{\n    //https://learn.microsoft.com/en-us/windows/terminal/install#settings-json-file\n    FF_STRBUF_AUTO_DESTROY json = ffStrbufCreate();\n    const char* error = NULL;\n\n    if(terminalExe && ffIsAbsolutePath(terminalExe->chars))\n    {\n        FF_STRBUF_AUTO_DESTROY jsonPath = ffStrbufCreateA(MAX_PATH);\n        ffStrbufAppendNS(&jsonPath, ffStrbufLastIndexC(terminalExe, '\\\\') + 1, terminalExe->chars);\n        ffStrbufAppendS(&jsonPath, \".portable\");\n\n        if(ffPathExists(jsonPath.chars, FF_PATHTYPE_ANY))\n        {\n            ffStrbufSubstrBefore(&jsonPath, jsonPath.length - strlen(\".portable\"));\n            ffStrbufAppendS(&jsonPath, \"settings\\\\settings.json\");\n            if(!ffAppendFileBuffer(jsonPath.chars, &json))\n                error = \"Error reading Windows Terminal portable settings JSON file\";\n        }\n        else\n        {\n            PWSTR localAppDataW = NULL;\n            if(SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_LocalAppData, KF_FLAG_DEFAULT, NULL, &localAppDataW)))\n            {\n                ffStrbufSetWS(&jsonPath, localAppDataW);\n                CoTaskMemFree(localAppDataW);\n\n                if(ffStrbufContainIgnCaseS(terminalExe, \"_8wekyb3d8bbwe\\\\\"))\n                {\n                    // Microsoft Store version\n                    if(ffStrbufContainIgnCaseS(terminalExe, \".WindowsTerminalPreview_\"))\n                    {\n                        // Preview version\n                        ffStrbufAppendS(&jsonPath, \"\\\\Packages\\\\Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe\\\\LocalState\\\\settings.json\");\n                        if(!ffAppendFileBuffer(jsonPath.chars, &json))\n                            error = \"Error reading Windows Terminal Preview settings JSON file\";\n                    }\n                    else\n                    {\n                        // Stable version\n                        ffStrbufAppendS(&jsonPath, \"\\\\Packages\\\\Microsoft.WindowsTerminal_8wekyb3d8bbwe\\\\LocalState\\\\settings.json\");\n                        if(!ffAppendFileBuffer(jsonPath.chars, &json))\n                            error = \"Error reading Windows Terminal settings JSON file\";\n                    }\n                }\n                else\n                {\n                    ffStrbufAppendS(&jsonPath, \"\\\\Microsoft\\\\Windows Terminal\\\\settings.json\");\n                    if(!ffAppendFileBuffer(jsonPath.chars, &json))\n                        error = \"Error reading Windows Terminal settings JSON file\";\n                }\n            }\n        }\n    }\n\n    if(!error && json.length == 0)\n    {\n        error = ffProcessAppendStdOut(&json, (char* const[]) {\n            \"cmd.exe\",\n            \"/c\",\n            //print the file content directly, so we don't need to handle the difference of Windows and POSIX path\n            \"if exist %LOCALAPPDATA%\\\\Packages\\\\Microsoft.WindowsTerminal_8wekyb3d8bbwe\\\\LocalState\\\\settings.json \"\n            \"( type %LOCALAPPDATA%\\\\Packages\\\\Microsoft.WindowsTerminal_8wekyb3d8bbwe\\\\LocalState\\\\settings.json ) \"\n            \"else if exist %LOCALAPPDATA%\\\\Packages\\\\Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe\\\\LocalState\\\\settings.json \"\n            \"( type %LOCALAPPDATA%\\\\Packages\\\\Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe\\\\LocalState\\\\settings.json ) \"\n            \"else if exist \\\"%LOCALAPPDATA%\\\\Microsoft\\\\Windows Terminal\\\\settings.json\\\" \"\n            \"( type %LOCALAPPDATA%\\\\Microsoft\\\\Windows Terminal\\\\settings.json ) \"\n            \"else ( call )\",\n            NULL\n        });\n    }\n\n    if(error)\n    {\n        ffStrbufAppendS(&terminalFont->error, error);\n        return;\n    }\n    ffStrbufTrimRight(&json, '\\n');\n    if(json.length == 0)\n    {\n        ffStrbufAppendS(&terminalFont->error, \"Cannot find file \\\"settings.json\\\"\");\n        return;\n    }\n\n    FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate();\n    double size = -1;\n    error = detectFromWTImpl(&json, &name, &size);\n\n    if(error)\n        ffStrbufAppendS(&terminalFont->error, error);\n    else\n    {\n        char sizeStr[16];\n        snprintf(sizeStr, ARRAY_SIZE(sizeStr), \"%g\", size);\n        ffFontInitValues(&terminalFont->font, name.chars, sizeStr);\n    }\n}\n\nstatic void detectMintty(FFTerminalFontResult* terminalFont)\n{\n    FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();\n\n    if(!ffParsePropFileConfigValues(\"mintty/config\", 2, (FFpropquery[]) {\n        {\"Font=\", &fontName},\n        {\"FontHeight=\", &fontSize}\n    }))\n        ffParsePropFileConfigValues(\".minttyrc\", 2, (FFpropquery[]) {\n            {\"Font=\", &fontName},\n            {\"FontHeight=\", &fontSize}\n        });\n    if(fontName.length == 0)\n        ffStrbufAppendS(&fontName, \"Lucida Console\");\n    if(fontSize.length == 0)\n        ffStrbufAppendC(&fontSize, '9');\n\n    ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars);\n}\n\nstatic void detectConhost(FFTerminalFontResult* terminalFont)\n{\n    CONSOLE_FONT_INFOEX cfi = { .cbSize = sizeof(cfi) };\n    if(!GetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi))\n    {\n        ffStrbufAppendS(&terminalFont->error, \"GetCurrentConsoleFontEx() failed\");\n        return;\n    }\n\n    FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreateWS(cfi.FaceName);\n\n    char fontSize[16];\n    _ultoa((unsigned long)(cfi.dwFontSize.Y), fontSize, 10);\n\n    ffFontInitValues(&terminalFont->font, fontName.chars, fontSize);\n}\n\nstatic void detectConEmu(FFTerminalFontResult* terminalFont)\n{\n    //https://conemu.github.io/en/ConEmuXml.html#search-sequence\n    FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();\n\n    const char* paths[] = { \"ConEmuDir\", \"ConEmuBaseDir\", \"APPDATA\" };\n    for (uint32_t i = 0; i < ARRAY_SIZE(paths); ++i)\n    {\n        ffStrbufSetS(&path, getenv(paths[i]));\n        if(path.length > 0)\n        {\n            ffStrbufAppendS(&path, \"/ConEmu.xml\");\n            if(ffParsePropFileValues(path.chars, 2, (FFpropquery[]){\n                {\"<value name=\\\"FontName\\\" type=\\\"string\\\" data=\\\"\", &fontName},\n                {\"<value name=\\\"FontSize\\\" type=\\\"ulong\\\" data=\\\"\", &fontSize}\n            }))\n                break;\n        }\n    }\n\n    if(fontName.length == 0 && fontSize.length == 0)\n    {\n        ffStrbufAppendS(&terminalFont->error, \"Failed to parse ConEmu.xml\");\n        return;\n    }\n\n    if(fontName.length > 0)\n        ffStrbufSubstrBeforeLastC(&fontName, '\"');\n    else\n        ffStrbufAppendS(&fontName, \"Consola\");\n\n    if(fontSize.length > 0)\n        ffStrbufSubstrBeforeLastC(&fontSize, '\"');\n    else\n        ffStrbufAppendS(&fontSize, \"14\");\n\n    ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars);\n}\n\nstatic void detectWarp(FFTerminalFontResult* terminalFont)\n{\n    FF_AUTO_CLOSE_FD HANDLE key = NULL;\n    if (!ffRegOpenKeyForRead(HKEY_CURRENT_USER, L\"Software\\\\Warp.dev\\\\Warp\", &key, &terminalFont->error))\n        return;\n\n    FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();\n    if (ffRegReadValues(key, 2, (FFRegValueArg[]) {\n        FF_ARG(fontName, L\"FontName\"),\n        FF_ARG(fontSize, L\"FontSize\")\n    }, &terminalFont->error))\n    {\n        ffStrbufTrim(&fontName, '\"');\n        ffStrbufAppendS(&fontSize, \"px\");\n    }\n    else\n    {\n        ffStrbufSetS(&fontName, \"Hack\");\n        ffStrbufSetS(&fontSize, \"13.0px\");\n    }\n\n    ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars);\n\n    FFstrbuf* fontWeight = (FFstrbuf*) ffListAdd(&terminalFont->font.styles);\n    ffStrbufInit(fontWeight);\n    if (ffRegReadStrbuf(key, L\"FontWeight\", fontWeight, NULL))\n        ffStrbufTrim(fontWeight, '\"');\n    else\n        ffStrbufSetStatic(fontWeight, \"Normal\");\n}\n\nbool ffDetectTerminalFontPlatform(const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont)\n{\n    if(ffStrbufIgnCaseEqualS(&terminal->processName, \"Windows Terminal\") ||\n        ffStrbufIgnCaseEqualS(&terminal->processName, \"WindowsTerminal.exe\"))\n        detectFromWindowsTerminal(&terminal->exe, terminalFont);\n    else if(ffStrbufIgnCaseEqualS(&terminal->processName, \"mintty\"))\n        detectMintty(terminalFont);\n    else if(ffStrbufIgnCaseEqualS(&terminal->processName, \"conhost.exe\"))\n        detectConhost(terminalFont);\n    else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, \"ConEmu\"))\n        detectConEmu(terminalFont);\n    else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, \"warp\"))\n        detectWarp(terminalFont);\n    else\n        return false;\n    return true;\n}\n"
  },
  {
    "path": "src/detection/terminalshell/terminalshell.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/io.h\"\n#include \"common/processing.h\"\n#include \"common/properties.h\"\n#include \"common/path.h\"\n#include \"common/stringUtils.h\"\n#include \"common/binary.h\"\n\n#include <ctype.h>\n#include <stdint.h>\n#ifdef __FreeBSD__\n    #include <paths.h>\n    #ifndef _PATH_LOCALBASE\n        #define _PATH_LOCALBASE \"/usr/local\"\n    #endif\n#elif __OpenBSD__\n    #define _PATH_LOCALBASE \"/usr/local\"\n#elif __NetBSD__\n    #define _PATH_LOCALBASE \"/usr/pkg\"\n#elif _WIN32\n\n#include \"common/windows/version.h\"\n#include <windows.h>\n\nstatic bool getFileVersion(const FFstrbuf* exePath, const wchar_t* stringName, FFstrbuf* version)\n{\n    wchar_t exePathW[PATH_MAX + 1];\n    if (!NT_SUCCESS(RtlUTF8ToUnicodeN(exePathW, (ULONG) sizeof(exePathW), NULL, exePath->chars, (ULONG)exePath->length + 1)))\n        return false;\n    return ffGetFileVersion(exePathW, stringName, version);\n}\n\n#elif __HAIKU__\n    #include \"common/haiku/version.h\"\n#endif\n\nstatic bool getExeVersionRaw(FFstrbuf* exe, FFstrbuf* version)\n{\n    return ffProcessAppendStdOut(version, (char* const[]) {\n        exe->chars,\n        \"--version\",\n        NULL\n    }) == NULL;\n}\n\nstatic bool getExeVersionGeneral(FFstrbuf* exe, FFstrbuf* version)\n{\n    if(!getExeVersionRaw(exe, version))\n        return false;\n\n    ffStrbufSubstrAfterFirstC(version, ' ');\n    ffStrbufSubstrBeforeFirstC(version, ' ');\n    return true;\n}\n\nstatic bool extractBashVersion(const char* line, FF_MAYBE_UNUSED uint32_t len, void *userdata)\n{\n    if (!ffStrStartsWith(line, \"@(#)Bash version \")) return true;\n    const char* start = line + strlen(\"@(#)Bash version \");\n    const char* end = strchr(start, '(');\n    if (!end) return true;\n    ffStrbufSetNS((FFstrbuf*) userdata, (uint32_t) (end - start), start);\n    return false;\n}\n\nstatic bool getShellVersionBash(FFstrbuf* exe, FFstrbuf* version)\n{\n    ffBinaryExtractStrings(exe->chars, extractBashVersion, version, (uint32_t) strlen(\"@(#)Bash version 0.0.0(0) release GNU\"));\n    if (version->length > 0) return true;\n\n    if(!getExeVersionRaw(exe, version))\n        return false;\n\n    // GNU bash, version 5.1.16(1)-release (x86_64-pc-msys)\\nCopyright...\n    ffStrbufSubstrBeforeFirstC(version, '('); // GNU bash, version 5.1.16\n    ffStrbufSubstrAfterLastC(version, ' '); // 5.1.16\n    return true;\n}\n\nstatic bool getShellVersionFish(FFstrbuf* exe, FFstrbuf* version)\n{\n    if(!getExeVersionRaw(exe, version))\n        return false;\n\n    //fish, version 4.0.2-1 (Built by MSYS2 project) // version can be localized if LC_ALL is set\n    if (version->length < strlen(\"fish, v\") || !ffStrbufStartsWithS(version, \"fish\")) return false;\n    uint32_t index = ffStrbufNextIndexC(version, strlen(\"fish, \"), ' ');\n    ffStrbufSubstrAfter(version, index);\n    ffStrbufSubstrBeforeFirstC(version, ' ');\n    return true;\n}\n\nstatic bool getShellVersionPwsh(FFstrbuf* exe, FFstrbuf* version)\n{\n    // Requires manually setting $POWERSHELL_VERSION\n    // $env:POWERSHELL_VERSION = $PSVersionTable.PSVersion.ToString(); fastfetch.exe\n    const char* env = getenv(\"POWERSHELL_VERSION\");\n    if (env)\n    {\n        ffStrbufSetS(version, env);\n        return true;\n    }\n\n    #ifdef _WIN32\n    if(getFileVersion(exe, NULL, version))\n    {\n        ffStrbufSubstrBeforeLastC(version, '.');\n        return true;\n    }\n    #endif\n\n    if(!getExeVersionRaw(exe, version))\n        return false;\n\n    ffStrbufSubstrAfterLastC(version, ' ');\n    return true;\n}\n\nstatic bool getShellVersionKsh(FFstrbuf* exe, FFstrbuf* version)\n{\n    if(ffProcessAppendStdErr(version, (char* const[]) {\n        exe->chars,\n        \"--version\",\n        NULL\n    }) == NULL && ffStrbufSubstrAfterFirstS(version, \" (AT&T Research) \"))\n    {\n        //  version         sh (AT&T Research) 93u+ 2012-08-01\n        ffStrbufSubstrBeforeFirstC(version, ' ');\n        return true;\n    }\n\n    ffStrbufClear(version);\n    if(ffProcessAppendStdOut(version, (char* const[]) {\n        exe->chars,\n        \"-c\",\n        \"echo $KSH_VERSION\",\n        NULL\n    }) == NULL && ffStrbufSubstrAfterFirstS(version, \" KSH \"))\n    {\n        // OKSH: @(#)PD KSH v5.2.14 99/07/13.2\n        // MKSH: @(#)MIRBSD KSH R59 2025/04/26 +Debian\n        // $OKSH_VERSION doesn't exist on OpenBSD\n        ffStrbufSubstrBeforeFirstC(version, ' ');\n        ffStrbufTrimLeft(version, 'v');\n        return true;\n    }\n\n    return false;\n}\n\nstatic bool getShellVersionOksh(FFstrbuf* exe, FFstrbuf* version)\n{\n    // Homebrew version\n    if(ffProcessAppendStdOut(version, (char* const[]) {\n        exe->chars,\n        \"-c\",\n        \"echo $OKSH_VERSION\",\n        NULL\n    }) != NULL)\n        return false;\n\n    //oksh 7.3\n    ffStrbufSubstrAfterFirstC(version, ' ');\n    return true;\n}\n\nstatic bool getShellVersionOils(FFstrbuf* exe, FFstrbuf* version)\n{\n    if(ffProcessAppendStdOut(version, (char* const[]) {\n        exe->chars,\n        \"--version\",\n        NULL\n    }) != NULL)\n        return false;\n\n    // Oils 0.18.0\t\thttps://www.oilshell.org/...\n    ffStrbufSubstrAfterFirstC(version, ' ');\n    ffStrbufSubstrBeforeFirstC(version, '\\t');\n    return true;\n}\n\nstatic bool getShellVersionNushell(FFstrbuf* exe, FFstrbuf* version)\n{\n    ffStrbufSetS(version, getenv(\"NU_VERSION\"));\n    if (version->length) return true;\n    return getExeVersionRaw(exe, version); //0.73.0\n}\n\nstatic bool getShellVersionAsh(FFstrbuf* exe, FFstrbuf* version)\n{\n    if(ffProcessAppendStdErr(version, (char* const[]) {\n        exe->chars,\n        \"--help\",\n        NULL\n    }) != NULL)\n        return false;\n\n    // BusyBox v1.36.1 (2023-11-07 18:53:09 UTC) multi-call binary...\n    ffStrbufSubstrAfterFirstC(version, ' ');\n    ffStrbufSubstrBeforeFirstC(version, ' ');\n    ffStrbufTrimLeft(version, 'v');\n    return true;\n}\n\nstatic bool getShellVersionXonsh(FF_MAYBE_UNUSED FFstrbuf* exe, FFstrbuf* version)\n{\n    ffStrbufSetS(version, getenv(\"XONSH_VERSION\"));\n    if (version->length) return true;\n\n    // exe is python here\n    if(ffProcessAppendStdErr(version, (char* const[]) {\n        \"xonsh\",\n        \"--version\",\n        NULL\n    }) != NULL)\n        return false;\n\n    // xonsh/0.14.1\n    ffStrbufSubstrAfterFirstC(version, '/');\n    return true;\n}\n\nstatic bool extractZshVersion(const char* line, FF_MAYBE_UNUSED uint32_t len, void *userdata)\n{\n    if (!ffStrStartsWith(line, \"zsh-\")) return true;\n    const char* start = line + strlen(\"zsh-\");\n    const char* end = strchr(start, '-');\n    if (!end) return true;\n\n    ffStrbufSetNS((FFstrbuf*) userdata, (uint32_t) (end - start), start);\n    return false;\n}\n\nstatic bool getShellVersionZsh(FFstrbuf* exe, FFstrbuf* version)\n{\n    ffBinaryExtractStrings(exe->chars, extractZshVersion, version, (uint32_t) strlen(\"zsh-0.0-0\"));\n    if (version->length) return true;\n\n    return getExeVersionGeneral(exe, version); //zsh 5.9 (arm-apple-darwin21.3.0)\n}\n\n#ifdef _WIN32\nstatic bool getShellVersionWinPowerShell(FFstrbuf* exe, FFstrbuf* version)\n{\n    const char* env = getenv(\"POWERSHELL_VERSION\");\n    if (env)\n    {\n        ffStrbufSetS(version, env);\n        return true;\n    }\n\n    return ffProcessAppendStdOut(version, (char* const[]) {\n        exe->chars,\n        \"-NoLogo\",\n        \"-NoProfile\",\n        \"-Command\",\n        \"$PSVersionTable.PSVersion.ToString()\",\n        NULL\n    }) == NULL;\n}\n#endif\n\nbool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, FFstrbuf* version)\n{\n    if (!instance.config.general.detectVersion) return false;\n\n    if(ffStrEqualsIgnCase(exeName, \"sh\")) // #849\n        return false;\n\n    if(ffStrEqualsIgnCase(exeName, \"bash\"))\n        return getShellVersionBash(exe, version);\n    if(ffStrEqualsIgnCase(exeName, \"zsh\"))\n        return getShellVersionZsh(exe, version);\n    if(ffStrEqualsIgnCase(exeName, \"fish\"))\n        return getShellVersionFish(exe, version);\n    if(ffStrEqualsIgnCase(exeName, \"pwsh\"))\n        return getShellVersionPwsh(exe, version);\n    if(ffStrEqualsIgnCase(exeName, \"csh\") || ffStrEqualsIgnCase(exeName, \"tcsh\"))\n        return getExeVersionGeneral(exe, version); //tcsh 6.24.07 (Astron) 2022-12-21 (aarch64-apple-darwin) options wide,nls,dl,al,kan,sm,rh,color,filec\n    if(ffStrEqualsIgnCase(exeName, \"nu\"))\n        return getShellVersionNushell(exe, version);\n    if(ffStrEqualsIgnCase(exeName, \"ksh\") || ffStrEqualsIgnCase(exeName, \"mksh\"))\n        return getShellVersionKsh(exe, version);\n    if(ffStrEqualsIgnCase(exeName, \"oksh\"))\n        return getShellVersionOksh(exe, version);\n    if(ffStrEqualsIgnCase(exeName, \"oil.ovm\"))\n        return getShellVersionOils(exe, version);\n    if(ffStrEqualsIgnCase(exeName, \"elvish\"))\n        return getExeVersionRaw(exe, version);\n    if(ffStrEqualsIgnCase(exeName, \"ash\"))\n        return getShellVersionAsh(exe, version);\n    if(ffStrEqualsIgnCase(exeName, \"xonsh\"))\n        return getShellVersionXonsh(exe, version);\n    if(ffStrEqualsIgnCase(exeName, \"brush\"))\n        return getExeVersionGeneral(exe, version); // brush 0.2.23 (git:2835487)\n\n    #ifdef _WIN32\n    if(ffStrEqualsIgnCase(exeName, \"powershell\") || ffStrEqualsIgnCase(exeName, \"powershell_ise\"))\n        return getShellVersionWinPowerShell(exe, version);\n\n    return getFileVersion(exe, NULL, version);\n    #endif\n\n    return false;\n}\n\nFF_MAYBE_UNUSED static bool getTerminalVersionTermux(FFstrbuf* version)\n{\n    ffStrbufSetS(version, getenv(\"TERMUX_VERSION\"));\n    return version->length > 0;\n}\n\nstatic bool extractGeneralVersion(const char *str, FF_MAYBE_UNUSED uint32_t len, void *userdata)\n{\n    if (!ffCharIsDigit(str[0])) return true;\n    int count = 0;\n    sscanf(str, \"%*d.%*d.%*d%n\", &count);\n    if (count == 0) return true;\n    ffStrbufSetS((FFstrbuf*) userdata, str);\n    return false;\n}\n\nFF_MAYBE_UNUSED static bool getTerminalVersionGnome(FFstrbuf* exe, FFstrbuf* version)\n{\n    if (ffIsAbsolutePath(exe->chars))\n    {\n        ffBinaryExtractStrings(exe->chars, extractGeneralVersion, version, (uint32_t) strlen(\"0.0.0\"));\n        if (version->length) return true;\n    }\n\n    if(ffProcessAppendStdOut(version, (char* const[]){\n        \"gnome-terminal\",\n        \"--version\",\n        NULL\n    })) return false;\n\n    //# GNOME Terminal 3.46.7 using VTE 0.70.2 +BIDI +GNUTLS +ICU +SYSTEMD\n    ffStrbufSubstrAfterFirstS(version, \"Terminal \");\n    ffStrbufSubstrBeforeFirstC(version, ' ');\n    return true;\n}\n\nFF_MAYBE_UNUSED static bool getTerminalVersionXfce4Terminal(FFstrbuf* exe, FFstrbuf* version)\n{\n    if (ffIsAbsolutePath(exe->chars))\n    {\n        ffBinaryExtractStrings(exe->chars, extractGeneralVersion, version, (uint32_t) strlen(\"0.0.0\"));\n        if (version->length) return true;\n    }\n\n    return getExeVersionGeneral(exe, version);//xfce4-terminal 1.0.4 (Xfce 4.18)...\n}\n\nFF_MAYBE_UNUSED static bool getTerminalVersionKgx(FFstrbuf* version)\n{\n    if(ffProcessAppendStdOut(version, (char* const[]){\n        \"kgx\",\n        \"--version\",\n        NULL\n    })) return false;\n\n    //# KGX 45.0 using VTE 0.74.0 +BIDI +GNUTLS +ICU +SYSTEMD\n    ffStrbufSubstrAfterFirstS(version, \"KGX \");\n    ffStrbufSubstrBeforeFirstC(version, ' ');\n    return true;\n}\n\nFF_MAYBE_UNUSED static bool getTerminalVersionKonsole(FFstrbuf* exe, FFstrbuf* version)\n{\n    const char* konsoleVersion = getenv(\"KONSOLE_VERSION\");\n    if(konsoleVersion)\n    {\n        //221201\n        long major = strtol(konsoleVersion, NULL, 10);\n        if (major >= 0)\n        {\n            long patch = major % 100;\n            major /= 100;\n            long minor = major % 100;\n            major /= 100;\n            ffStrbufSetF(version, \"%ld.%ld.%ld\", major, minor, patch);\n            return true;\n        }\n    }\n\n    return getExeVersionGeneral(exe, version);\n}\n\nFF_MAYBE_UNUSED static bool getTerminalVersionFoot(FFstrbuf* exe, FFstrbuf* version)\n{\n    uint32_t major = 0, minor = 0, patch = 0;\n    if (ffGetTerminalResponse(\"\\e[>c\", 3, \"\\e[>1;%2u%2u%2u;0c\", &major, &minor, &patch) == NULL)\n    {\n        ffStrbufSetF(version, \"%u.%u.%u\", major, minor, patch);\n        return true;\n    }\n\n    if(!getExeVersionRaw(exe, version)) return false;\n\n    //foot version: 1.13.1 -pgo +ime -graphemes -assertions\n    ffStrbufSubstrAfterFirstS(version, \"version: \");\n    ffStrbufSubstrBeforeFirstC(version, ' ');\n    return true;\n}\n\nFF_MAYBE_UNUSED static bool getTerminalVersionMateTerminal(FFstrbuf* exe, FFstrbuf* version)\n{\n    ffBinaryExtractStrings(exe->chars, extractGeneralVersion, version, (uint32_t) strlen(\"0.0.0\"));\n    if (version->length > 0) return true;\n\n    if(!getExeVersionRaw(exe, version)) return false;\n\n    //MATE Terminal 1.26.1\n    ffStrbufSubstrAfterLastC(version, ' ');\n    return version->length > 0;\n}\n\nFF_MAYBE_UNUSED static bool getTerminalVersionCockpit(FFstrbuf* exe, FFstrbuf* version)\n{\n    if(!getExeVersionRaw(exe, version)) return false;\n\n    //Version: 295\\n...\n    ffStrbufSubstrBeforeFirstC(version, '\\n');\n    ffStrbufSubstrAfterFirstC(version, ' ');\n    return version->length > 0;\n}\n\nFF_MAYBE_UNUSED static bool getTerminalVersionXterm(FFstrbuf* exe, FFstrbuf* version)\n{\n    ffStrbufSetS(version, getenv(\"XTERM_VERSION\"));\n    if (!version->length)\n    {\n        if(ffProcessAppendStdOut(version, (char* const[]){\n            exe->chars,\n            \"-v\",\n            NULL\n        })) return false;\n    }\n\n    //xterm(273)\n    ffStrbufTrimRight(version, ')');\n    ffStrbufSubstrAfterFirstC(version, '(');\n    return version->length > 0;\n}\n\nFF_MAYBE_UNUSED static bool getTerminalVersionBlackbox(FFstrbuf* exe, FFstrbuf* version)\n{\n    if(ffProcessAppendStdOut(version, (char* const[]){\n        exe->chars,\n        \"--version\",\n        NULL\n    })) return false;\n\n    //BlackBox version 0.14.0 (flatpak)\n    ffStrbufSubstrAfterFirstS(version, \"version \");\n    ffStrbufSubstrBeforeFirstC(version, ' ');\n    return version->length > 0;\n}\n\nFF_MAYBE_UNUSED static bool getTerminalVersionUrxvt(FF_MAYBE_UNUSED FFstrbuf* exe, FFstrbuf* version)\n{\n    if(ffProcessAppendStdErr(version, (char* const[]){\n        \"urxvt\", // Don't use exe because of urxvtd\n        \"-invalid\",\n        NULL\n    })) return false;\n\n    //urxvt: \"invalid\": unknown or malformed option.\n    //rxvt-unicode (urxvt) v9.31 - released: 2023-01-02\n    ffStrbufSubstrAfterFirstS(version, \"(urxvt) v\");\n    ffStrbufSubstrBeforeFirstC(version, ' ');\n\n    return version->length > 0;\n}\n\nFF_MAYBE_UNUSED static bool getTerminalVersionSt(FF_MAYBE_UNUSED FFstrbuf* exe, FFstrbuf* version)\n{\n    if(ffProcessAppendStdErr(version, (char* const[]){\n        exe->chars,\n        \"-v\",\n        NULL\n    })) return false;\n\n    //st 0.9\n    ffStrbufSubstrAfterFirstC(version, ' ');\n\n    return version->length > 0;\n}\n\nFF_MAYBE_UNUSED static bool getTerminalVersionLxterminal(FFstrbuf* exe, FFstrbuf* version)\n{\n    if(!getExeVersionRaw(exe, version)) return false;\n    // lxterminal 0.3.2\n    ffStrbufSubstrAfterFirstC(version, ' ');\n    return version->length > 0;\n}\n\nFF_MAYBE_UNUSED static bool getTerminalVersionWeston(FF_MAYBE_UNUSED FFstrbuf* exe, FFstrbuf* version)\n{\n    // weston-terminal doesn't report a version, use weston version instead\n    if(ffProcessAppendStdOut(version, (char* const[]){\n        \"weston\",\n        \"--version\",\n        NULL\n    })) return false;\n\n    //weston 8.0.0\n    ffStrbufSubstrAfterFirstC(version, ' ');\n\n    return version->length > 0;\n}\n\nstatic bool getTerminalVersionContour(FFstrbuf* exe, FFstrbuf* version)\n{\n    const char* env = getenv(\"TERMINAL_VERSION_STRING\");\n    if (env)\n    {\n        ffStrbufAppendS(version, env);\n        return true;\n    }\n    if(!getExeVersionRaw(exe, version)) return false;\n    // Contour Terminal Emulator 0.3.12.262\n    ffStrbufSubstrAfterLastC(version, ' ');\n    return version->length > 0;\n}\n\nstatic bool getTerminalVersionScreen(FFstrbuf* exe, FFstrbuf* version)\n{\n    if(!getExeVersionRaw(exe, version)) return false;\n    // Screen version 4.09.01 (GNU) 20-Aug-23\n    ffStrbufSubstrAfter(version, (uint32_t) strlen(\"Screen version \") - 1);\n    ffStrbufSubstrBeforeFirstC(version, ' ');\n    return version->length > 0;\n}\n\nstatic bool getTerminalVersionTmux(FFstrbuf* exe, FFstrbuf* version)\n{\n    if (ffProcessAppendStdOut(version, (char* const[]) {\n        exe->chars,\n        \"-V\",\n        NULL\n    }) != NULL)\n        return false;\n\n    // tmux 3.4\n    ffStrbufSubstrAfterFirstC(version, ' ');\n    return version->length > 0;\n}\n\nstatic bool getTerminalVersionZellij(FFstrbuf* exe, FFstrbuf* version)\n{\n    if(!getExeVersionRaw(exe, version)) return false;\n\n    // zellij 0.39.2\n    ffStrbufSubstrAfterFirstC(version, ' ');\n    return version->length > 0;\n}\n\nstatic bool getTerminalVersionZed(FFstrbuf* exe, FFstrbuf* version)\n{\n    FF_STRBUF_AUTO_DESTROY cli = ffStrbufCreateCopy(exe);\n    ffStrbufSubstrBeforeLastC(&cli, '/');\n    ffStrbufAppendS(&cli, \"/cli\"\n        #ifdef _WIN32\n            \".exe\"\n        #endif\n    );\n\n    if(ffProcessAppendStdOut(version, (char* const[]) {\n        cli.chars,\n        \"--version\",\n        NULL\n    }) != NULL)\n        return false;\n\n    // Zed 0.142.6 – /Applications/Zed.app\n    ffStrbufSubstrAfterFirstC(version, ' ');\n    ffStrbufSubstrBeforeFirstC(version, ' ');\n    return true;\n}\n\nstatic bool extractSshdVersion(const char *str, FF_MAYBE_UNUSED uint32_t len, void *userdata)\n{\n    if (!ffStrStartsWith(str, \"OpenSSH_\") || !ffCharIsDigit(str[strlen(\"OpenSSH_\")])) return true;\n    str += strlen(\"OpenSSH_\");\n    int count = 0;\n    sscanf(str, \"%*d.%*dp%*d%n\", &count);\n    if (count == 0) return true;\n    ffStrbufSetS((FFstrbuf*) userdata, str);\n    return false;\n}\n\nstatic bool getTerminalVersionSshd(FFstrbuf* exe, FFstrbuf* version)\n{\n    FF_STRBUF_AUTO_DESTROY exePath = ffStrbufCreate();\n    if (ffIsAbsolutePath(exe->chars))\n        ffStrbufSet(&exePath, exe);\n    else if (ffFindExecutableInPath(\"sshd\", &exePath) != NULL)\n        return false;\n\n    ffBinaryExtractStrings(exePath.chars, extractSshdVersion, version, (uint32_t) strlen(\"OpenSSH0.0\"));\n    if (version->length) return true;\n\n    if(ffProcessAppendStdOut(version, (char* const[]) {\n        exePath.chars,\n        \"-V\",\n        NULL\n    }) != NULL)\n        return false;\n\n    if (ffStrbufStartsWithS(version, \"unknown \")) // `unknown option -- V` (ancient OpenSSH version)\n        ffStrbufSubstrAfterFirstC(version, '\\n');\n\n    // OpenSSH_10.0p2 Ubuntu-5ubuntu5, OpenSSL 3.5.3 16 Sep 2025\n    ffStrbufSubstrAfterFirstC(version, '_');\n    ffStrbufSubstrBeforeFirstC(version, ',');\n    return true;\n}\n\n#ifndef _WIN32\nstatic bool getTerminalVersionKitty(FFstrbuf* exe, FFstrbuf* version)\n{\n    #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__GNU__)\n    char buffer[1024] = {};\n    if (\n        #if __linux__ || __GNU__\n        ffReadFileData(FASTFETCH_TARGET_DIR_USR \"/lib64/kitty/kitty/constants.py\", ARRAY_SIZE(buffer) - 1, buffer) ||\n        ffReadFileData(FASTFETCH_TARGET_DIR_USR \"/lib/kitty/kitty/constants.py\", ARRAY_SIZE(buffer) - 1, buffer)\n        #else\n        ffReadFileData(_PATH_LOCALBASE \"/share/kitty/kitty/constants.py\", ARRAY_SIZE(buffer) - 1, buffer)\n        #endif\n    )\n    {\n        // Starts from version 0.17.0\n        // https://github.com/kovidgoyal/kitty/blob/master/kitty/constants.py#L25\n        const char* p = memmem(buffer, ARRAY_SIZE(buffer) - 1, \"version: Version = Version(\", strlen(\"version: Version = Version(\"));\n        if (p)\n        {\n            p += strlen(\"version: Version = Version(\");\n            int major, minor, patch;\n            if (sscanf(p, \"%d,%d,%d\", &major, &minor, &patch) == 3)\n            {\n                ffStrbufSetF(version, \"%d.%d.%d\", major, minor, patch);\n                return true;\n            }\n        }\n    }\n    #elif __APPLE__\n    if (ffStrbufEndsWithS(exe, \"/kitty.app/Contents/MacOS/kitty\"))\n    {\n        ffStrbufSet(version, exe);\n        ffStrbufSubstrBeforeLastC(version, '/');\n        ffStrbufSubstrBeforeLastC(version, '/');\n        ffStrbufAppendS(version, \"/Info.plist\");\n        char buf[4096];\n        ssize_t size = ffReadFileData(version->chars, ARRAY_SIZE(buf) - 1, buf);\n        if (size > 0)\n        {\n            buf[size] = '\\0';\n\n            const char* p = strstr(buf, \"<key>CFBundleShortVersionString</key>\");\n            if (p)\n            {\n                p += strlen(\"<key>CFBundleShortVersionString</key>\");\n                p = strchr(p, '>');\n                if (p)\n                {\n                    p++;\n                    const char* end = strchr(p, '<');\n                    if (end)\n                    {\n                        ffStrbufSetNS(version, (uint32_t) (end - p), p);\n                        return true;\n                    }\n                }\n            }\n        }\n        ffStrbufClear(version);\n    }\n    #endif\n\n    char versionHex[64];\n    // https://github.com/fastfetch-cli/fastfetch/discussions/1030#discussioncomment-9845233\n    if (ffGetTerminalResponse(\n        \"\\eP+q6b697474792d71756572792d76657273696f6e\\e\\\\\", // kitty-query-version\n        1,\n        \"\\eP1+r%*[^=]=%63[^\\e]\\e\\\\\\\\\", versionHex) == NULL)\n    {\n        // decode hex string\n        for (const char* p = versionHex; p[0] && p[1]; p += 2)\n        {\n            unsigned value;\n            if (sscanf(p, \"%2x\", &value) == 1)\n                ffStrbufAppendC(version, (char) value);\n        }\n        return true;\n    }\n\n    //kitty 0.21.2 created by Kovid Goyal\n    return getExeVersionGeneral(exe, version);\n}\n\nFF_MAYBE_UNUSED static bool getTerminalVersionPtyxis(FF_MAYBE_UNUSED FFstrbuf* exe, FFstrbuf* version)\n{\n    if(ffProcessAppendStdOut(version, (char* const[]) {\n        \"ptyxis\",\n        \"--version\",\n        NULL\n    }) != NULL)\n        return false;\n\n    ffStrbufSubstrBeforeFirstC(version, '\\n');\n    ffStrbufSubstrAfterFirstC(version, ' ');\n    return true;\n}\n\nFF_MAYBE_UNUSED static bool getTerminalVersionTilix(FFstrbuf* exe, FFstrbuf* version)\n{\n    if (ffIsAbsolutePath(exe->chars))\n    {\n        ffBinaryExtractStrings(exe->chars, extractGeneralVersion, version, (uint32_t) strlen(\"0.0.0\"));\n        if (version->length) return true;\n    }\n\n    if(ffProcessAppendStdOut(version, (char* const[]) {\n        exe->chars,\n        \"--version\",\n        NULL\n    }) != NULL)\n        return false;\n\n    uint32_t index = ffStrbufFirstIndexS(version, \"Tilix version: \");\n    if (index == version->length) return false;\n\n    index += (uint32_t) strlen(\"Tilix version:\");\n    uint32_t end = ffStrbufNextIndexC(version, index, '\\n');\n\n    ffStrbufSubstrBefore(version, end);\n    ffStrbufSubstrAfter(version, index);\n    return true;\n}\n\nFF_MAYBE_UNUSED static bool getTerminalVersionSakura(FFstrbuf* exe, FFstrbuf* version)\n{\n    if(ffProcessAppendStdErr(version, (char* const[]) {\n        exe->chars,\n        \"--version\",\n        NULL\n    }) != NULL) // sakura version is 3.8.8\n        return false;\n\n    ffStrbufSubstrAfterLastC(version, ' ');\n    return true;\n}\n\nFF_MAYBE_UNUSED static bool getTerminalVersionTermite(FFstrbuf* exe, FFstrbuf* version)\n{\n    if(ffProcessAppendStdOut(version, (char* const[]) {\n        exe->chars,\n        \"--version\",\n        NULL\n    }) != NULL) // termite v16.9\\nvte 0.78.1 +BIDI +GNUTLS +ICU +SYSTEMD\n        return false;\n\n    ffStrbufSubstrBeforeFirstC(version, '\\n');\n    ffStrbufSubstrAfterLastC(version, 'v');\n    return true;\n}\n#endif\n\n#ifdef _WIN32\n\nstatic bool getTerminalVersionWindowsTerminal(FFstrbuf* exe, FFstrbuf* version)\n{\n    FF_STRBUF_AUTO_DESTROY buildInfoPath;\n    ffStrbufInitNS(&buildInfoPath, ffStrbufLastIndexC(exe, '\\\\') + 1, exe->chars);\n    ffStrbufAppendS(&buildInfoPath, \"BuildInfo.xml\");\n\n    if(ffParsePropFile(buildInfoPath.chars, \"StoreVersion=\\\"\", version))\n    {\n        ffStrbufTrimRight(version, '\"');\n        return true;\n    }\n\n    return getFileVersion(exe, NULL, version);\n}\n\nstatic bool getTerminalVersionConEmu(FFstrbuf* exe, FFstrbuf* version)\n{\n    ffStrbufSetS(version, getenv(\"ConEmuBuild\"));\n\n    if(version->length)\n        return true;\n\n    return getFileVersion(exe, NULL, version);\n}\n\n#endif\n\nbool fftsGetTerminalVersion(FFstrbuf* processName, FF_MAYBE_UNUSED FFstrbuf* exe, FFstrbuf* version)\n{\n    if (!instance.config.general.detectVersion) return false;\n\n    #ifdef __ANDROID__\n\n    if(ffStrbufEqualS(processName, \"com.termux\"))\n        return getTerminalVersionTermux(version);\n\n    #endif\n\n    #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__sun) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__)\n\n    if(ffStrbufStartsWithIgnCaseS(processName, \"gnome-terminal\"))\n        return getTerminalVersionGnome(exe, version);\n\n    if(ffStrbufIgnCaseEqualS(processName, \"konsole\"))\n        return getTerminalVersionKonsole(exe, version);\n\n    if(ffStrbufIgnCaseEqualS(processName, \"yakuake\"))\n        return getTerminalVersionKonsole(exe, version); // yakuake shares code with konsole\n\n    if(ffStrbufIgnCaseEqualS(processName, \"xfce4-terminal\"))\n        return getTerminalVersionXfce4Terminal(exe, version);\n\n    if(ffStrbufIgnCaseEqualS(processName, \"terminator\"))\n        return getExeVersionGeneral(exe, version);//terminator 2.1.3\n\n    if(ffStrbufIgnCaseEqualS(processName, \"deepin-terminal\"))\n        return getExeVersionGeneral(exe, version);//deepin-terminal 5.4.36\n\n    if(ffStrbufIgnCaseEqualS(processName, \"foot\"))\n        return getTerminalVersionFoot(exe, version);\n\n    if(ffStrbufIgnCaseEqualS(processName, \"qterminal\"))\n        return getExeVersionRaw(exe, version); //1.2.0\n\n    if(ffStrbufIgnCaseEqualS(processName, \"mate-terminal\"))\n        return getTerminalVersionMateTerminal(exe, version);\n\n    if(ffStrbufIgnCaseEqualS(processName, \"cockpit-bridge\"))\n        return getTerminalVersionCockpit(exe, version);\n\n    if(ffStrbufIgnCaseEqualS(processName, \"xterm\"))\n        return getTerminalVersionXterm(exe, version);\n\n    if(ffStrbufIgnCaseEqualS(processName, \"blackbox\"))\n        return getTerminalVersionBlackbox(exe, version);\n\n    if(ffStrbufIgnCaseEqualS(processName, \"st\"))\n        return getTerminalVersionSt(exe, version);\n\n    if(ffStrbufIgnCaseEqualS(processName, \"lxterminal\"))\n        return getTerminalVersionLxterminal(exe, version);\n\n    if(ffStrbufIgnCaseEqualS(processName, \"weston-terminal\"))\n        return getTerminalVersionWeston(exe, version);\n\n    if(ffStrbufIgnCaseEqualS(processName, \"urxvt\") ||\n        ffStrbufIgnCaseEqualS(processName, \"urxvtd\") ||\n        ffStrbufIgnCaseEqualS(processName, \"rxvt\") ||\n        ffStrbufIgnCaseEqualS(processName, \"rxvt-unicode\")\n    )\n        return getTerminalVersionUrxvt(exe, version);\n\n    if(ffStrbufIgnCaseEqualS(processName, \"ptyxis-agent\"))\n        return getTerminalVersionPtyxis(exe, version);\n\n    if(ffStrbufIgnCaseEqualS(processName, \"tilix\"))\n        return getTerminalVersionTilix(exe, version);\n\n    if(ffStrbufIgnCaseEqualS(processName, \"sakura\"))\n        return getTerminalVersionSakura(exe, version);\n\n    if(ffStrbufIgnCaseEqualS(processName, \"termite\"))\n        return getTerminalVersionTermite(exe, version);\n\n    if(ffStrbufIgnCaseEqualS(processName, \"cosmic-term\"))\n        return getTerminalVersionTmux(exe, version);\n\n    #endif\n\n    #ifdef _WIN32\n\n    if(ffStrbufIgnCaseEqualS(processName, \"WindowsTerminal.exe\"))\n        return getTerminalVersionWindowsTerminal(exe, version);\n\n    if(ffStrbufStartsWithIgnCaseS(processName, \"ConEmu\"))\n        return getTerminalVersionConEmu(exe, version);\n\n    if(ffStrbufIgnCaseEqualS(processName, \"warp.exe\"))\n        return getFileVersion(exe, L\"ProductVersion\", version);\n\n    #endif\n\n    #ifndef _WIN32\n\n    if(ffStrbufIgnCaseEqualS(processName, \"kitty\"))\n        return getTerminalVersionKitty(exe, version);\n\n    if (ffStrbufIgnCaseEqualS(processName, \"Tabby\") && getExeVersionRaw(exe, version))\n        return true;\n\n    #endif\n\n    if(ffStrbufStartsWithIgnCaseS(processName, \"alacritty\"))\n        return getExeVersionGeneral(exe, version);\n\n    if(ffStrbufStartsWithIgnCaseS(processName, \"contour\"))\n        return getTerminalVersionContour(exe, version);\n\n    if(ffStrbufStartsWithIgnCaseS(processName, \"screen\"))\n        return getTerminalVersionScreen(exe, version);\n\n    if(ffStrbufStartsWithIgnCaseS(processName, \"zellij\"))\n        return getTerminalVersionZellij(exe, version);\n\n    if(ffStrbufStartsWithIgnCaseS(processName, \"zed\"))\n        return getTerminalVersionZed(exe, version);\n\n    #if __HAIKU__\n    if(ffStrbufEqualS(processName, \"Terminal\"))\n        return ffGetFileVersion(exe->chars, version);\n    #endif\n\n    const char* termProgramVersion = getenv(\"TERM_PROGRAM_VERSION\");\n    if(termProgramVersion)\n    {\n        const char* termProgram = getenv(\"TERM_PROGRAM\");\n        if(termProgram)\n        {\n            if(ffStrbufStartsWithIgnCaseS(processName, termProgram) || // processName ends with `.exe` on Windows\n                (ffStrEquals(termProgram, \"vscode\") && ffStrbufStartsWithIgnCaseS(processName, \"code\")) ||\n\n                #ifdef __APPLE__\n                (ffStrEquals(termProgram, \"iTerm.app\") && ffStrbufStartsWithIgnCaseS(processName, \"iTermServer-\")) ||\n                #elif defined(__linux__)\n                (ffStrEquals(termProgram, \"WarpTerminal\") && ffStrbufEqualS(processName, \"warp\")) ||\n                #endif\n                false\n            ) {\n                ffStrbufSetS(version, termProgramVersion);\n                return true;\n            }\n        }\n    }\n\n    termProgramVersion = getenv(\"LC_TERMINAL_VERSION\");\n    if(termProgramVersion)\n    {\n        const char* termProgram = getenv(\"LC_TERMINAL\");\n        if(termProgram)\n        {\n            if(ffStrbufStartsWithIgnCaseS(processName, termProgram) || // processName ends with `.exe` on Windows\n                (ffStrEquals(termProgram, \"vscode\") && ffStrbufStartsWithIgnCaseS(processName, \"code\")) ||\n                (ffStrStartsWith(termProgram, \"iTerm\") && ffStrbufStartsWithIgnCaseS(processName, \"iTermServer-\"))\n            ) {\n                ffStrbufSetS(version, termProgramVersion);\n                return true;\n            }\n        }\n    }\n\n    if(ffStrbufStartsWithIgnCaseS(processName, \"tmux\"))\n        return getTerminalVersionTmux(exe, version);\n\n    if(ffStrbufIgnCaseEqualS(processName, \"sshd\") || ffStrbufStartsWithIgnCaseS(processName, \"sshd-\"))\n        return getTerminalVersionSshd(exe, version);\n\n    #ifdef _WIN32\n\n    return getFileVersion(exe, NULL, version);\n\n    #else\n\n    return false;\n\n    #endif\n}\n"
  },
  {
    "path": "src/detection/terminalshell/terminalshell.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/terminal/option.h\"\n#include \"modules/shell/option.h\"\n\ntypedef struct FFShellResult\n{\n    FFstrbuf processName;\n    FFstrbuf exe; //Actually arg0 in *nix\n    const char* exeName; //pointer to a char in exe\n    FFstrbuf exePath; //Full real path to executable file\n    FFstrbuf prettyName;\n    FFstrbuf version;\n    uint32_t pid;\n    uint32_t ppid;\n    int32_t tty;\n} FFShellResult;\n\ntypedef struct FFTerminalResult\n{\n    FFstrbuf processName;\n    FFstrbuf exe;\n    FFstrbuf prettyName;\n    const char* exeName; //pointer to a char in exe\n    FFstrbuf exePath; //Full real path to executable file\n    FFstrbuf version;\n    FFstrbuf tty;\n    uint32_t pid;\n    uint32_t ppid;\n} FFTerminalResult;\n\nconst FFShellResult* ffDetectShell();\nconst FFTerminalResult* ffDetectTerminal();\n"
  },
  {
    "path": "src/detection/terminalshell/terminalshell_linux.c",
    "content": "#include \"terminalshell.h\"\n#include \"common/io.h\"\n#include \"common/parsing.h\"\n#include \"common/processing.h\"\n#include \"common/thread.h\"\n#include \"common/stringUtils.h\"\n\n#include <string.h>\n#include <stdlib.h>\n#include <unistd.h>\n\nstatic void setExeName(FFstrbuf* exe, const char** exeName)\n{\n    assert(exe->length > 0);\n    uint32_t lastSlashIndex = ffStrbufLastIndexC(exe, '/');\n    if(lastSlashIndex < exe->length)\n        *exeName = exe->chars + lastSlashIndex + 1;\n}\n\nstatic pid_t getShellInfo(FFShellResult* result, pid_t pid)\n{\n    pid_t ppid = 0;\n    int32_t tty = -1;\n\n    const char* userShellName = NULL;\n    {\n        uint32_t index = ffStrbufLastIndexC(&instance.state.platform.userShell, '/');\n        if (index == instance.state.platform.userShell.length)\n            userShellName = instance.state.platform.userShell.chars;\n        else\n            userShellName = instance.state.platform.userShell.chars + index + 1;\n    }\n\n    while (pid > 1 && ffProcessGetBasicInfoLinux(pid, &result->processName, &ppid, &tty) == NULL)\n    {\n        if (!ffStrbufEqualS(&result->processName, userShellName))\n        {\n            //Common programs that are between terminal and own process, but are not the shell\n            if(\n                // tty < 0                                  || //A shell should connect to a tty\n                pid == 1 || // init/systemd\n                ffStrbufEqualS(&result->processName, \"sh\")                  || //This prevents us from detecting things like pipes and redirects, i hope nobody uses plain `sh` as shell\n                ffStrbufEqualS(&result->processName, \"sudo\")                ||\n                ffStrbufEqualS(&result->processName, \"su\")                  ||\n                ffStrbufEqualS(&result->processName, \"strace\")              ||\n                ffStrbufEqualS(&result->processName, \"gdb\")                 ||\n                ffStrbufEqualS(&result->processName, \"lldb\")                ||\n                ffStrbufEqualS(&result->processName, \"lldb-mi\")             ||\n                ffStrbufEqualS(&result->processName, \"login\")               ||\n                ffStrbufEqualS(&result->processName, \"ltrace\")              ||\n                ffStrbufEqualS(&result->processName, \"perf\")                ||\n                ffStrbufEqualS(&result->processName, \"guake-wrapped\")       ||\n                ffStrbufEqualS(&result->processName, \"time\")                ||\n                ffStrbufEqualS(&result->processName, \"clifm\")               || //https://github.com/leo-arch/clifm/issues/289\n                ffStrbufEqualS(&result->processName, \"valgrind\")            ||\n                ffStrbufEqualS(&result->processName, \"fastfetch\")           || //#994\n                ffStrbufEqualS(&result->processName, \"flashfetch\")          ||\n                ffStrbufEqualS(&result->processName, \"proot\")               ||\n                ffStrbufEqualS(&result->processName, \"script\")              ||\n                #ifdef __linux__\n                ffStrbufEqualS(&result->processName, \"run-parts\")           ||\n                #endif\n                ffStrbufContainS(&result->processName, \"debug\")             ||\n                ffStrbufContainS(&result->processName, \"command-not-\")      ||\n                ffStrbufEndsWithS(&result->processName, \".sh\")\n            )\n            {\n                pid = ppid;\n                ffStrbufClear(&result->processName);\n                continue;\n            }\n        }\n\n        result->pid = (uint32_t) pid;\n        result->ppid = (uint32_t) ppid;\n        result->tty = tty;\n        ffProcessGetInfoLinux(pid, &result->processName, &result->exe, &result->exeName, &result->exePath);\n        break;\n    }\n    return pid > 1 ? ppid : 0;\n}\n\nstatic pid_t getTerminalInfo(FFTerminalResult* result, pid_t pid)\n{\n    pid_t ppid = 0;\n\n    while (pid > 1 && ffProcessGetBasicInfoLinux(pid, &result->processName, &ppid, NULL) == NULL)\n    {\n        //Known shells\n        if (\n            pid == 1 || // init/systemd\n            ffStrbufEqualS(&result->processName, \"sudo\")       ||\n            ffStrbufEqualS(&result->processName, \"su\")         ||\n            ffStrbufEqualS(&result->processName, \"sh\")         ||\n            ffStrbufEqualS(&result->processName, \"ash\")        ||\n            ffStrbufEqualS(&result->processName, \"bash\")       ||\n            ffStrbufEqualS(&result->processName, \"zsh\")        ||\n            ffStrbufEqualS(&result->processName, \"ksh\")        ||\n            ffStrbufEqualS(&result->processName, \"mksh\")       ||\n            ffStrbufEqualS(&result->processName, \"oksh\")       ||\n            ffStrbufEqualS(&result->processName, \"csh\")        ||\n            ffStrbufEqualS(&result->processName, \"tcsh\")       ||\n            ffStrbufEqualS(&result->processName, \"fish\")       ||\n            ffStrbufEqualS(&result->processName, \"dash\")       ||\n            ffStrbufEqualS(&result->processName, \"pwsh\")       ||\n            ffStrbufEqualS(&result->processName, \"nu\")         ||\n            ffStrbufEqualS(&result->processName, \"git-shell\")  ||\n            ffStrbufEqualS(&result->processName, \"elvish\")     ||\n            ffStrbufEqualS(&result->processName, \"oil.ovm\")    ||\n            ffStrbufEqualS(&result->processName, \"xonsh\")      || // works in Linux but not in macOS because kernel returns `Python` in this case\n            ffStrbufEqualS(&result->processName, \"login\")      ||\n            ffStrbufEqualS(&result->processName, \"clifm\")      || // https://github.com/leo-arch/clifm/issues/289\n            ffStrbufEqualS(&result->processName, \"chezmoi\")    || // #762\n            ffStrbufEqualS(&result->processName, \"proot\")      ||\n            ffStrbufEqualS(&result->processName, \"script\")     ||\n            #ifdef __linux__\n            ffStrbufStartsWithS(&result->processName, \"Relay(\")   || // Unknown process in WSL2\n            ffStrbufStartsWithS(&result->processName, \"flatpak-\") || // #707\n            ffStrbufEqualS(&result->processName, \"run-parts\")  || // #2048\n            #endif\n            ffStrbufEndsWithS(&result->processName, \".sh\")\n        )\n        {\n            pid = ppid;\n            ffStrbufClear(&result->processName);\n            continue;\n        }\n\n        #ifdef __APPLE__\n        // https://github.com/fastfetch-cli/fastfetch/discussions/501\n        const char* pLeft = strstr(result->processName.chars, \" (\");\n        if (pLeft)\n        {\n            pLeft += 2;\n            const char* pRight = strstr(pLeft, \"term)\");\n            if (pRight && pRight[5] == '\\0')\n            {\n                for (; pLeft < pRight; ++pLeft)\n                    if (*pLeft < 'a' || *pLeft > 'z')\n                        break;\n                if (pLeft == pRight && ffProcessGetBasicInfoLinux(ppid, &result->processName, &ppid, NULL) != NULL)\n                    return 0;\n            }\n        }\n        #endif\n\n        result->pid = (uint32_t) pid;\n        result->ppid = (uint32_t) ppid;\n        ffProcessGetInfoLinux(pid, &result->processName, &result->exe, &result->exeName, &result->exePath);\n        break;\n    }\n    return pid > 1 ? ppid : 0;\n}\n\nstatic bool getTerminalInfoByPidEnv(FFTerminalResult* result, const char* pidEnv)\n{\n    const char* envStr = getenv(pidEnv);\n    if (envStr == NULL)\n        return false;\n\n    pid_t pid = (pid_t) strtol(envStr, NULL, 10);\n    result->pid = (uint32_t) pid;\n    if (ffProcessGetBasicInfoLinux(pid, &result->processName, (pid_t*) &result->ppid, NULL) == NULL)\n    {\n        ffProcessGetInfoLinux(pid, &result->processName, &result->exe, &result->exeName, &result->exePath);\n        return true;\n    }\n\n    return false;\n}\n\nstatic void getTerminalFromEnv(FFTerminalResult* result)\n{\n    if (result->processName.length > 0)\n    {\n        if (!ffStrbufStartsWithS(&result->processName, \"login\") &&\n            !ffStrbufEqualS(&result->processName, \"(login)\") &&\n\n            #ifdef __APPLE__\n            !ffStrbufEqualS(&result->processName, \"launchd\") &&\n            #else\n            !ffStrbufEqualS(&result->processName, \"systemd\") &&\n            !ffStrbufEqualS(&result->processName, \"init\") &&\n            !ffStrbufEqualS(&result->processName, \"(init)\") &&\n            !ffStrbufEqualS(&result->processName, \"SessionLeader\") && // #750\n            #endif\n\n            !ffStrbufEqualS(&result->processName, \"0\")\n        ) return;\n\n        ffStrbufClear(&result->processName);\n        ffStrbufClear(&result->exe);\n        result->exeName = result->exe.chars;\n        ffStrbufClear(&result->exePath);\n        result->pid = result->ppid = 0;\n    }\n\n    const char* term = NULL;\n\n    //SSH\n    if(\n        getenv(\"SSH_TTY\") != NULL\n    )\n        term = getenv(\"SSH_TTY\");\n    else if(\n        getenv(\"KITTY_PID\") != NULL ||\n        getenv(\"KITTY_INSTALLATION_DIR\") != NULL\n    )\n    {\n        if (getTerminalInfoByPidEnv(result, \"KITTY_PID\"))\n            return;\n        term = \"kitty\";\n    }\n\n    #ifdef __linux__ // WSL\n    //Windows Terminal\n    else if(\n        getenv(\"WT_SESSION\") != NULL ||\n        getenv(\"WT_PROFILE_ID\") != NULL\n    ) term = \"Windows Terminal\";\n\n    //ConEmu\n    else if(\n        getenv(\"ConEmuPID\") != NULL\n    ) term = \"ConEmu\";\n    #endif\n\n    //Alacritty\n    else if(\n        getenv(\"ALACRITTY_SOCKET\") != NULL ||\n        getenv(\"ALACRITTY_LOG\") != NULL ||\n        getenv(\"ALACRITTY_WINDOW_ID\") != NULL\n    ) term = \"Alacritty\";\n\n    #ifdef __ANDROID__\n    //Termux\n    else if(\n        getenv(\"TERMUX_VERSION\") != NULL ||\n        getenv(\"TERMUX_MAIN_PACKAGE_FORMAT\") != NULL\n    )\n    {\n        if (getTerminalInfoByPidEnv(result, \"TERMUX_APP__PID\"))\n            return;\n        term = \"com.termux\";\n    }\n    #endif\n\n    #if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__GNU__)\n    //Konsole\n    else if(\n        getenv(\"KONSOLE_VERSION\") != NULL\n    ) term = \"konsole\";\n\n    else if(\n        getenv(\"GNOME_TERMINAL_SCREEN\") != NULL ||\n        getenv(\"GNOME_TERMINAL_SERVICE\") != NULL\n    ) term = \"gnome-terminal\";\n    #endif\n\n    //MacOS, mintty\n    else if(getenv(\"TERM_PROGRAM\") != NULL)\n        term = getenv(\"TERM_PROGRAM\");\n\n    else if(getenv(\"LC_TERMINAL\") != NULL)\n        term = getenv(\"LC_TERMINAL\");\n\n    //Normal Terminal\n    else\n    {\n        term = getenv(\"TERM\");\n        //TTY\n        if(!ffStrSet(term) || ffStrEquals(term, \"linux\"))\n            term = ttyname(STDIN_FILENO);\n    }\n\n    if(ffStrSet(term))\n    {\n        ffStrbufSetS(&result->processName, term);\n        ffStrbufSetS(&result->exe, term);\n        setExeName(&result->exe, &result->exeName);\n    }\n}\n\nstatic void getUserShellFromEnv(FFShellResult* result)\n{\n    //If shell detection via processes failed\n    if(result->processName.length == 0 && instance.state.platform.userShell.length > 0)\n    {\n        ffStrbufSet(&result->exe, &instance.state.platform.userShell);\n        setExeName(&result->exe, &result->exeName);\n        ffStrbufAppendS(&result->processName, result->exeName);\n    }\n}\n\nbool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, FFstrbuf* version);\n\nbool fftsGetTerminalVersion(FFstrbuf* processName, FFstrbuf* exe, FFstrbuf* version);\n\nstatic void setShellInfoDetails(FFShellResult* result)\n{\n    ffStrbufClear(&result->version);\n    fftsGetShellVersion(result->exePath.length > 0 ? &result->exePath : &result->exe, result->exeName, &result->version);\n\n    if(ffStrbufEqualS(&result->processName, \"pwsh\"))\n        ffStrbufInitStatic(&result->prettyName, \"PowerShell\");\n    else if(ffStrbufEqualS(&result->processName, \"nu\"))\n        ffStrbufInitStatic(&result->prettyName, \"nushell\");\n    else if(ffStrbufEqualS(&result->processName, \"oil.ovm\"))\n        ffStrbufInitStatic(&result->prettyName, \"Oils\");\n    else\n    {\n        // https://github.com/fastfetch-cli/fastfetch/discussions/280#discussioncomment-3831734\n        ffStrbufInitS(&result->prettyName, result->exeName);\n    }\n}\n\nstatic void setTerminalInfoDetails(FFTerminalResult* result)\n{\n    if(ffStrbufStartsWithC(&result->processName, '.') && ffStrbufContainS(&result->processName, \"-wrap\"))\n    {\n        // For NixOS. Ref: #510 and https://github.com/NixOS/nixpkgs/pull/249428\n        // We use processName when detecting version and font, overriding it for simplification\n        ffStrbufSubstrBeforeLastC(&result->processName, '-');\n        ffStrbufSubstrAfter(&result->processName, 0);\n    }\n\n    if(ffStrbufEqualS(&result->processName, \"wezterm-gui\"))\n        ffStrbufInitStatic(&result->prettyName, \"WezTerm\");\n    else if(ffStrbufStartsWithS(&result->processName, \"tmux:\"))\n        ffStrbufInitStatic(&result->prettyName, \"tmux\");\n    else if(ffStrbufStartsWithS(&result->processName, \"screen-\"))\n        ffStrbufInitStatic(&result->prettyName, \"screen\");\n    else if(ffStrbufEqualS(&result->processName, \"sshd\") || ffStrbufStartsWithS(&result->processName, \"sshd-\"))\n    {\n        if (result->tty.length)\n            ffStrbufInitCopy(&result->prettyName, &result->tty);\n        else\n            ffStrbufSetStatic(&result->prettyName, \"sshd\");\n    }\n\n    #if defined(__ANDROID__)\n\n    else if(ffStrbufEqualS(&result->processName, \"com.termux\"))\n        ffStrbufInitStatic(&result->prettyName, \"Termux\");\n\n    #elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__GNU__)\n\n    else if(ffStrbufStartsWithS(&result->processName, \"gnome-terminal\"))\n        ffStrbufInitStatic(&result->prettyName, \"GNOME Terminal\");\n    else if(ffStrbufStartsWithS(&result->processName, \"kgx\"))\n        ffStrbufInitStatic(&result->prettyName, \"GNOME Console\");\n    else if(ffStrbufEqualS(&result->processName, \"urxvt\") ||\n        ffStrbufEqualS(&result->processName, \"urxvtd\") ||\n        ffStrbufEqualS(&result->processName, \"rxvt\")\n    )\n        ffStrbufInitStatic(&result->prettyName, \"rxvt-unicode\");\n    else if(ffStrbufStartsWithS(&result->processName, \"ptyxis-agent\"))\n        ffStrbufInitStatic(&result->prettyName, \"Ptyxis\");\n\n    #elif defined(__APPLE__)\n\n    else if(ffStrbufEqualS(&result->processName, \"iTerm.app\") || ffStrbufStartsWithS(&result->processName, \"iTermServer-\"))\n        ffStrbufInitStatic(&result->prettyName, \"iTerm\");\n    else if(ffStrbufEndsWithS(&result->exePath, \"Terminal.app/Contents/MacOS/Terminal\"))\n    {\n        ffStrbufSetStatic(&result->processName, \"Apple_Terminal\"); // $TERM_PROGRAM, for terminal font detection\n        ffStrbufInitStatic(&result->prettyName, \"Apple Terminal\");\n    }\n    else if(ffStrbufEqualS(&result->processName, \"Apple_Terminal\"))\n        ffStrbufInitStatic(&result->prettyName, \"Apple Terminal\");\n    else if(ffStrbufEndsWithS(&result->exePath, \"Warp.app/Contents/MacOS/stable\"))\n    {\n        ffStrbufSetStatic(&result->processName, \"WarpTerminal\"); // $TERM_PROGRAM, for terminal font detection\n        ffStrbufInitStatic(&result->prettyName, \"Warp\");\n    }\n    else if(ffStrbufEqualS(&result->processName, \"WarpTerminal\"))\n        ffStrbufInitStatic(&result->prettyName, \"Warp\");\n\n    #elif defined(__HAIKU__)\n\n    else if(ffStrbufEqualS(&result->processName, \"Terminal\"))\n        ffStrbufInitStatic(&result->prettyName, \"Haiku Terminal\");\n\n    #endif\n\n    else if(strncmp(result->exeName, result->processName.chars, result->processName.length) == 0) // if exeName starts with processName, print it. Otherwise print processName\n        ffStrbufInitS(&result->prettyName, result->exeName);\n    else\n        ffStrbufInitCopy(&result->prettyName, &result->processName);\n\n    fftsGetTerminalVersion(&result->processName, result->exePath.length > 0 ? &result->exePath : &result->exe, &result->version);\n}\n\n#if defined(MAXPATH)\n#define FF_EXE_PATH_LEN MAXPATH\n#elif defined(PATH_MAX)\n#define FF_EXE_PATH_LEN PATH_MAX\n#else\n#define FF_EXE_PATH_LEN 260\n#endif\n\nconst FFShellResult* ffDetectShell()\n{\n    static FFShellResult result;\n    static bool init = false;\n    if(init)\n        return &result;\n    init = true;\n\n    ffStrbufInit(&result.processName);\n    ffStrbufInitA(&result.exe, FF_EXE_PATH_LEN);\n    result.exeName = result.exe.chars;\n    ffStrbufInit(&result.exePath);\n    ffStrbufInit(&result.version);\n    result.pid = 0;\n    result.ppid = 0;\n    result.tty = -1;\n\n    pid_t ppid = getppid();\n\n    const char* ignoreParent = getenv(\"FFTS_IGNORE_PARENT\");\n    if (ignoreParent && ffStrEquals(ignoreParent, \"1\"))\n    {\n        FF_STRBUF_AUTO_DESTROY _ = ffStrbufCreate();\n        ffProcessGetBasicInfoLinux(ppid, &_, &ppid, NULL);\n    }\n\n    ppid = getShellInfo(&result, ppid);\n    getUserShellFromEnv(&result);\n    setShellInfoDetails(&result);\n\n    return &result;\n}\n\nconst FFTerminalResult* ffDetectTerminal()\n{\n    static FFTerminalResult result;\n    static bool init = false;\n    if(init)\n        return &result;\n    init = true;\n\n    ffStrbufInit(&result.processName);\n    ffStrbufInitA(&result.exe, FF_EXE_PATH_LEN);\n    result.exeName = result.exe.chars;\n    ffStrbufInit(&result.exePath);\n    ffStrbufInit(&result.version);\n    ffStrbufInitS(&result.tty, ttyname(STDOUT_FILENO));\n    result.pid = 0;\n    result.ppid = 0;\n\n    pid_t ppid = (pid_t) ffDetectShell()->ppid;\n\n    if (ppid)\n        ppid = getTerminalInfo(&result, ppid);\n    getTerminalFromEnv(&result);\n    setTerminalInfoDetails(&result);\n\n    return &result;\n}\n"
  },
  {
    "path": "src/detection/terminalshell/terminalshell_windows.c",
    "content": "#include \"terminalshell.h\"\n#include \"common/io.h\"\n#include \"common/processing.h\"\n#include \"common/thread.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/windows/registry.h\"\n#include \"common/windows/unicode.h\"\n#include \"common/windows/version.h\"\n#include \"common/windows/nt.h\"\n#include \"common/stringUtils.h\"\n\n#include <stdalign.h>\n#include <windows.h>\n#include <wchar.h>\n#include <tlhelp32.h>\n#include <ntstatus.h>\n#include <winternl.h>\n#include <shlobj.h>\n\nbool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, FFstrbuf* version);\n\nstatic uint32_t getShellInfo(FFShellResult* result, uint32_t pid)\n{\n    uint32_t ppid = 0;\n    bool gui = false;\n\n    while (pid != 0 && ffProcessGetInfoWindows(pid, &ppid, &result->processName, &result->exe, &result->exeName, &result->exePath, &gui))\n    {\n        ffStrbufSet(&result->prettyName, &result->processName);\n        if (ffStrbufEndsWithIgnCaseS(&result->prettyName, \".exe\"))\n            ffStrbufSubstrBefore(&result->prettyName, result->prettyName.length - 4);\n\n        //Common programs that are between terminal and own process, but are not the shell\n        if (\n            !gui && (\n            ffStrbufIgnCaseEqualS(&result->prettyName, \"sudo\")          ||\n            ffStrbufIgnCaseEqualS(&result->prettyName, \"su\")            ||\n            ffStrbufIgnCaseEqualS(&result->prettyName, \"gdb\")           ||\n            ffStrbufIgnCaseEqualS(&result->prettyName, \"lldb\")          ||\n            ffStrbufIgnCaseEqualS(&result->prettyName, \"lldb-dap\")      ||\n            ffStrbufIgnCaseEqualS(&result->prettyName, \"python\")        || // python on windows generates shim executables\n            ffStrbufIgnCaseEqualS(&result->prettyName, \"fastfetch\")     || // scoop warps the real binaries with a \"shim\" exe\n            ffStrbufIgnCaseEqualS(&result->prettyName, \"flashfetch\")    ||\n            ffStrbufContainIgnCaseS(&result->prettyName, \"debug\")       ||\n            ffStrbufContainIgnCaseS(&result->prettyName, \"time\")        ||\n            ffStrbufStartsWithIgnCaseS(&result->prettyName, \"ConEmuC\") // https://github.com/fastfetch-cli/fastfetch/issues/488#issuecomment-1619982014\n        )) {\n            ffStrbufClear(&result->processName);\n            ffStrbufClear(&result->prettyName);\n            ffStrbufClear(&result->exe);\n            result->exeName = NULL;\n            pid = ppid;\n            continue;\n        }\n\n        result->pid = pid;\n\n        if (gui)\n        {\n            // Started without shell\n            // In this case, terminal process will be created by fastfetch itself.\n            ppid = 0;\n            if (ffStrbufIgnCaseEqualS(&result->prettyName, \"explorer\"))\n                ffStrbufSetS(&result->prettyName, \"Windows Explorer\");\n        }\n        else\n        {\n            result->ppid = ppid;\n        }\n\n        break;\n    }\n    return ppid;\n}\n\nstatic void setShellInfoDetails(FFShellResult* result)\n{\n    if(ffStrbufIgnCaseEqualS(&result->prettyName, \"pwsh\"))\n        ffStrbufSetS(&result->prettyName, \"PowerShell\");\n    else if(ffStrbufIgnCaseEqualS(&result->prettyName, \"powershell\"))\n        ffStrbufSetS(&result->prettyName, \"Windows PowerShell\");\n    else if(ffStrbufIgnCaseEqualS(&result->prettyName, \"powershell_ise\"))\n        ffStrbufSetS(&result->prettyName, \"Windows PowerShell ISE\");\n    else if(ffStrbufIgnCaseEqualS(&result->prettyName, \"cmd\"))\n    {\n        ffStrbufSetS(&result->prettyName, \"CMD\");\n\n        if (instance.config.general.detectVersion)\n        {\n            FF_AUTO_CLOSE_FD HANDLE snapshot = NULL;\n            while(!(snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, result->pid)) && GetLastError() == ERROR_BAD_LENGTH) {}\n\n            if(snapshot)\n            {\n                MODULEENTRY32W module;\n                module.dwSize = sizeof(module);\n                for(BOOL success = Module32FirstW(snapshot, &module); success; success = Module32NextW(snapshot, &module))\n                {\n                    if(wcsncmp(module.szModule, L\"clink_dll_\", strlen(\"clink_dll_\")) == 0)\n                    {\n                        FF_STRBUF_AUTO_DESTROY clinkVersion = ffStrbufCreate();\n                        if (ffGetFileVersion(module.szExePath, NULL, &clinkVersion))\n                            ffStrbufAppendF(&result->prettyName, \" (with Clink %s)\", clinkVersion.chars);\n                        else\n                            ffStrbufAppendS(&result->prettyName, \" (with Clink)\");\n                        break;\n                    }\n                }\n            }\n        }\n    }\n    else if(ffStrbufIgnCaseEqualS(&result->prettyName, \"nu\"))\n        ffStrbufSetS(&result->prettyName, \"nushell\");\n    else if(ffStrbufIgnCaseEqualS(&result->prettyName, \"explorer\"))\n        ffStrbufSetS(&result->prettyName, \"Windows Explorer\");\n}\n\nstatic bool getTerminalFromEnv(FFTerminalResult* result)\n{\n    if(\n        result->processName.length > 0 &&\n        ffStrbufIgnCaseCompS(&result->processName, \"explorer\") != 0\n    ) return false;\n\n    const char* term = getenv(\"ConEmuPID\");\n\n    if(term)\n    {\n        //ConEmu\n        uint32_t pid = (uint32_t) strtoul(term, NULL, 10);\n        result->pid = pid;\n        if(ffProcessGetInfoWindows(pid, NULL, &result->processName, &result->exe, &result->exeName, &result->exePath, NULL))\n        {\n            ffStrbufSet(&result->prettyName, &result->processName);\n            if(ffStrbufEndsWithIgnCaseS(&result->prettyName, \".exe\"))\n                ffStrbufSubstrBefore(&result->prettyName, result->prettyName.length - 4);\n            return true;\n        }\n        else\n        {\n            term = \"ConEmu\";\n        }\n    }\n\n    //SSH\n    if(getenv(\"SSH_TTY\") != NULL)\n        term = getenv(\"SSH_TTY\");\n\n    //Windows Terminal\n    if(!term && (\n        getenv(\"WT_SESSION\") != NULL ||\n        getenv(\"WT_PROFILE_ID\") != NULL\n    )) term = \"WindowsTerminal\";\n\n    //Alacritty\n    if(!term && (\n        getenv(\"ALACRITTY_SOCKET\") != NULL ||\n        getenv(\"ALACRITTY_LOG\") != NULL ||\n        getenv(\"ALACRITTY_WINDOW_ID\") != NULL\n    )) term = \"Alacritty\";\n\n    if(!term)\n        term = getenv(\"TERM_PROGRAM\");\n\n    //Normal Terminal\n    if(!term)\n        term = getenv(\"TERM\");\n\n    if(term)\n    {\n        ffStrbufSetS(&result->processName, term);\n        ffStrbufSetS(&result->prettyName, term);\n        ffStrbufSetS(&result->exe, term);\n        result->exeName = \"\";\n        return true;\n    }\n\n    return false;\n}\n\nstatic bool detectDefaultTerminal(FFTerminalResult* result)\n{\n    wchar_t regPath[128] = L\"SOFTWARE\\\\Classes\\\\PackagedCom\\\\ClassIndex\\\\\";\n    wchar_t* uuid = regPath + strlen(\"SOFTWARE\\\\Classes\\\\PackagedCom\\\\ClassIndex\\\\\");\n    DWORD bufSize = 80;\n    if (RegGetValueW(HKEY_CURRENT_USER, L\"Console\\\\%%Startup\", L\"DelegationTerminal\", RRF_RT_REG_SZ, NULL, uuid, &bufSize) == ERROR_SUCCESS)\n    {\n        if(wcscmp(uuid, L\"{00000000-0000-0000-0000-000000000000}\") == 0 || // Let Windows decide\n            wcscmp(uuid, L\"{B23D10C0-E52E-411E-9D5B-C09FDF709C7D}\") == 0) // Conhost\n        {\n            goto conhost;\n        }\n\n        FF_AUTO_CLOSE_FD HANDLE hKey = NULL;\n        if(ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, regPath, &hKey, NULL))\n        {\n            FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();\n            if(ffRegGetSubKey(hKey, 0, &path, NULL))\n            {\n                if (ffStrbufStartsWithS(&path, \"Microsoft.WindowsTerminal\"))\n                {\n                    ffStrbufSetS(&result->processName, \"WindowsTerminal.exe\");\n                    ffStrbufSetS(&result->prettyName, \"WindowsTerminal\");\n\n                    PWSTR programFiles = NULL;\n                    if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_ProgramFiles, KF_FLAG_DEFAULT, NULL, &programFiles)))\n                    {\n                        ffStrbufSetWS(&result->exe, programFiles);\n                        CoTaskMemFree(programFiles);\n                        programFiles = NULL;\n\n                        ffStrbufAppendS(&result->exe, \"\\\\WindowsApps\\\\\");\n                        ffStrbufAppend(&result->exe, &path);\n                        ffStrbufAppendS(&result->exe, \"\\\\WindowsTerminal.exe\");\n\n                        if(ffPathExists(result->exe.chars, FF_PATHTYPE_FILE))\n                        {\n                            result->exeName = result->exe.chars + ffStrbufLastIndexC(&result->exe, '\\\\') + 1;\n                            ffStrbufSet(&result->exePath, &result->exe);\n                        }\n                        else\n                        {\n                            ffStrbufDestroy(&result->exe);\n                            ffStrbufInitMove(&result->exe, &path);\n                            result->exeName = \"\";\n                        }\n                    }\n                    return true;\n                }\n            }\n        }\n    }\n\nconhost:;\n    ULONG_PTR conhostPid = 0;\n    ULONG size;\n    if(NT_SUCCESS(NtQueryInformationProcess(NtCurrentProcess(), ProcessConsoleHostProcess, &conhostPid, sizeof(conhostPid), &size)) && conhostPid != 0)\n    {\n        // For Windows Terminal, it reports the PID of OpenConsole\n        if(ffProcessGetInfoWindows((uint32_t) conhostPid, NULL, &result->processName, &result->exe, &result->exeName, &result->exePath, NULL))\n        {\n            ffStrbufSet(&result->prettyName, &result->processName);\n            if(ffStrbufEndsWithIgnCaseS(&result->prettyName, \".exe\"))\n                ffStrbufSubstrBefore(&result->prettyName, result->prettyName.length - 4);\n            return true;\n        }\n    }\n\n    ffStrbufClear(&result->exe);\n    return false;\n}\n\nstatic uint32_t getTerminalInfo(FFTerminalResult* result, uint32_t pid)\n{\n    if (getenv(\"MSYSTEM\"))\n    {\n        // Don't try to detect terminals in MSYS shell\n        // It won't work because MSYS doesn't follow process tree of native Windows programs\n        return 0;\n    }\n\n    uint32_t ppid = 0;\n    bool gui;\n\n    while (pid != 0 && ffProcessGetInfoWindows(pid, &ppid, &result->processName, &result->exe, &result->exeName, &result->exePath, &gui))\n    {\n        if (!gui)\n        {\n            //We are in nested shell\n            ffStrbufClear(&result->processName);\n            ffStrbufClear(&result->prettyName);\n            ffStrbufClear(&result->exe);\n            ffStrbufClear(&result->exePath);\n            result->exeName = \"\";\n            pid = ppid;\n            continue;\n        }\n\n        ffStrbufSet(&result->prettyName, &result->processName);\n        if(ffStrbufEndsWithIgnCaseS(&result->prettyName, \".exe\"))\n            ffStrbufSubstrBefore(&result->prettyName, result->prettyName.length - 4);\n\n        if(ffStrbufIgnCaseEqualS(&result->prettyName, \"sihost\")           ||\n            ffStrbufIgnCaseEqualS(&result->prettyName, \"explorer\")        ||\n            ffStrbufIgnCaseEqualS(&result->prettyName, \"wininit\")\n        ) {\n            // A CUI program created by Windows Explorer will spawn a conhost as its child.\n            // However the conhost process is just a placeholder;\n            // The true terminal can be Windows Terminal or others.\n            ffStrbufClear(&result->processName);\n            ffStrbufClear(&result->prettyName);\n            ffStrbufClear(&result->exe);\n            ffStrbufClear(&result->exePath);\n            result->exeName = \"\";\n            return 0;\n        }\n        else\n        {\n            result->pid = pid;\n            result->ppid = ppid;\n        }\n\n        break;\n    }\n    return ppid;\n}\n\nstatic void setTerminalInfoDetails(FFTerminalResult* result)\n{\n    if(ffStrbufIgnCaseEqualS(&result->prettyName, \"WindowsTerminal\"))\n        ffStrbufSetStatic(&result->prettyName, ffStrbufContainIgnCaseS(&result->exe, \".WindowsTerminalPreview_\")\n            ? \"Windows Terminal Preview\"\n            : \"Windows Terminal\"\n        );\n    else if(ffStrbufIgnCaseEqualS(&result->prettyName, \"conhost\"))\n        ffStrbufSetStatic(&result->prettyName, \"Windows Console\");\n    else if(ffStrbufIgnCaseEqualS(&result->prettyName, \"Code\"))\n        ffStrbufSetStatic(&result->prettyName, \"Visual Studio Code\");\n    else if(ffStrbufIgnCaseEqualS(&result->prettyName, \"explorer\"))\n        ffStrbufSetStatic(&result->prettyName, \"Windows Explorer\");\n    else if(ffStrbufEqualS(&result->prettyName, \"wezterm-gui\"))\n        ffStrbufSetStatic(&result->prettyName, \"WezTerm\");\n    else if(ffStrbufIgnCaseEqualS(&result->prettyName, \"sshd\") || ffStrbufStartsWithIgnCaseS(&result->prettyName, \"sshd-\"))\n    {\n        const char* tty = getenv(\"SSH_TTY\");\n        if (tty) ffStrbufSetS(&result->prettyName, tty);\n    }\n}\n\nbool fftsGetTerminalVersion(FFstrbuf* processName, FFstrbuf* exe, FFstrbuf* version);\n\nconst FFShellResult* ffDetectShell(void)\n{\n    static FFShellResult result;\n    static bool init = false;\n    if(init)\n        return &result;\n    init = true;\n\n    ffStrbufInit(&result.processName);\n    ffStrbufInitA(&result.exe, MAX_PATH);\n    result.exeName = \"\";\n    ffStrbufInit(&result.exePath);\n    ffStrbufInit(&result.prettyName);\n    ffStrbufInit(&result.version);\n    result.pid = 0;\n    result.ppid = 0;\n    result.tty = -1;\n\n    uint32_t ppid;\n    if(!ffProcessGetInfoWindows(0, &ppid, NULL, NULL, NULL, NULL, NULL))\n        return &result;\n\n    const char* ignoreParent = getenv(\"FFTS_IGNORE_PARENT\");\n    if (ignoreParent && ffStrEquals(ignoreParent, \"1\"))\n        ffProcessGetInfoWindows(ppid, &ppid, NULL, NULL, NULL, NULL, NULL);\n\n    ppid = getShellInfo(&result, ppid);\n\n    if (result.processName.length > 0)\n    {\n        setShellInfoDetails(&result);\n        char tmp[MAX_PATH];\n        strcpy(tmp, result.exeName);\n        char* ext = strrchr(tmp, '.');\n        if (ext) *ext = '\\0';\n        fftsGetShellVersion(result.exePath.length > 0 ? &result.exePath : &result.exe, tmp, &result.version);\n    }\n\n    return &result;\n}\n\nconst FFTerminalResult* ffDetectTerminal(void)\n{\n    static FFTerminalResult result;\n    static bool init = false;\n    if(init)\n        return &result;\n    init = true;\n\n    ffStrbufInit(&result.processName);\n    ffStrbufInitA(&result.exe, MAX_PATH);\n    result.exeName = \"\";\n    ffStrbufInit(&result.exePath);\n    ffStrbufInit(&result.prettyName);\n    ffStrbufInit(&result.version);\n    ffStrbufInit(&result.tty);\n    result.pid = 0;\n    result.ppid = 0;\n\n    uint32_t ppid = ffDetectShell()->ppid;\n    if(ppid)\n        getTerminalInfo(&result, ppid);\n\n    if(result.processName.length == 0)\n        getTerminalFromEnv(&result);\n    if(result.processName.length == 0)\n        detectDefaultTerminal(&result);\n\n    if(result.processName.length > 0)\n    {\n        setTerminalInfoDetails(&result);\n        fftsGetTerminalVersion(&result.processName, result.exePath.length > 0 ? &result.exePath : &result.exe, &result.version);\n    }\n\n    return &result;\n}\n"
  },
  {
    "path": "src/detection/terminalsize/terminalsize.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/terminalsize/option.h\"\n\ntypedef struct FFTerminalSizeResult\n{\n    uint16_t rows;\n    uint16_t columns;\n    uint16_t width;\n    uint16_t height;\n} FFTerminalSizeResult;\n\nbool ffDetectTerminalSize(FFTerminalSizeResult* result);\n"
  },
  {
    "path": "src/detection/terminalsize/terminalsize_linux.c",
    "content": "#include \"terminalsize.h\"\n#include \"common/io.h\"\n\n#include <sys/ioctl.h>\n#include <fcntl.h>\n#include <unistd.h>\n\n#ifdef __sun\n    #include <sys/termios.h>\n#endif\n\nbool ffDetectTerminalSize(FFTerminalSizeResult* result)\n{\n    struct winsize winsize = {};\n    static int ttyfd = STDOUT_FILENO;\n    if (!isatty(ttyfd))\n        ttyfd = open(\"/dev/tty\", O_RDWR | O_NOCTTY | O_CLOEXEC);\n\n    ioctl(ttyfd, TIOCGWINSZ, &winsize);\n\n    if (winsize.ws_row == 0 || winsize.ws_col == 0)\n        ffGetTerminalResponse(\"\\e[18t\", 2, \"\\e[8;%hu;%hut\", &winsize.ws_row, &winsize.ws_col);\n\n    if (winsize.ws_ypixel == 0 || winsize.ws_xpixel == 0)\n        ffGetTerminalResponse(\"\\e[14t\", 2, \"\\e[4;%hu;%hut\", &winsize.ws_ypixel, &winsize.ws_xpixel);\n\n    if (winsize.ws_row == 0 && winsize.ws_col == 0)\n        return false;\n\n    result->rows = winsize.ws_row;\n    result->columns = winsize.ws_col;\n    result->width = winsize.ws_xpixel;\n    result->height = winsize.ws_ypixel;\n    return true;\n}\n"
  },
  {
    "path": "src/detection/terminalsize/terminalsize_windows.c",
    "content": "#include \"terminalsize.h\"\n#include \"common/io.h\"\n\n#include <windows.h>\n\nbool ffDetectTerminalSize(FFTerminalSizeResult* result)\n{\n    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);\n    FF_AUTO_CLOSE_FD HANDLE hConout = INVALID_HANDLE_VALUE;\n    {\n        DWORD outputMode;\n        if (!GetConsoleMode(hOutput, &outputMode))\n        {\n            hConout = CreateFileW(L\"CONOUT$\", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, NULL);\n            hOutput = hConout;\n        }\n    }\n    {\n        CONSOLE_SCREEN_BUFFER_INFO csbi;\n        if (GetConsoleScreenBufferInfo(hOutput, &csbi))\n        {\n            result->columns = (uint16_t) (csbi.srWindow.Right - csbi.srWindow.Left + 1);\n            result->rows = (uint16_t) (csbi.srWindow.Bottom - csbi.srWindow.Top + 1);\n        }\n        else\n        {\n            // Windows Terminal doesn't report `\\e` for some reason\n            ffGetTerminalResponse(\"\\e[18t\", 2, \"%*[^;];%hu;%hut\", &result->rows, &result->columns);\n        }\n    }\n\n    if (result->columns == 0 && result->rows == 0)\n        return false;\n\n    {\n        CONSOLE_FONT_INFOEX cfi;\n        if(GetCurrentConsoleFontEx(hOutput, FALSE, &cfi)) // Only works for ConHost\n        {\n            result->width = result->columns * (uint16_t) cfi.dwFontSize.X;\n            result->height = result->rows * (uint16_t) cfi.dwFontSize.Y;\n        }\n        if (result->width == 0 || result->height == 0)\n        {\n            // Windows Terminal doesn't report `\\e` for some reason\n            ffGetTerminalResponse(\"\\e[14t\", 2, \"%*[^;];%hu;%hut\", &result->height, &result->width);\n        }\n    }\n\n    return result->columns > 0 && result->rows > 0;\n}\n"
  },
  {
    "path": "src/detection/terminaltheme/terminaltheme.c",
    "content": "#include \"terminaltheme.h\"\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n\n#include <inttypes.h>\n\nstatic bool detectByEscapeCode(FFTerminalThemeResult* result)\n{\n    // Windows Terminal removes all `\\e`s in its output\n    if (ffGetTerminalResponse(\"\\e]10;?\\e\\\\\" /*fg*/ \"\\e]11;?\\e\\\\\" /*bg*/,\n        6,\n        \"%*[^0-9]10;rgb:%\" SCNx16 \"/%\" SCNx16 \"/%\" SCNx16 /*\"\\e\\\\\"*/ \"%*[^0-9]11;rgb:%\" SCNx16 \"/%\" SCNx16 \"/%\" SCNx16 /*\"\\e\\\\\"*/,\n        &result->fg.r, &result->fg.g, &result->fg.b,\n        &result->bg.r, &result->bg.g, &result->bg.b) == NULL)\n    {\n        if (result->fg.r > 0x0100 || result->fg.g > 0x0100 || result->fg.b > 0x0100)\n            result->fg.r /= 0x0100, result->fg.g /= 0x0100, result->fg.b /= 0x0100;\n        if (result->bg.r > 0x0100 || result->bg.g > 0x0100 || result->bg.b > 0x0100)\n            result->bg.r /= 0x0100, result->bg.g /= 0x0100, result->bg.b /= 0x0100;\n    }\n    else\n        return false;\n\n    return true;\n}\n\nstatic FFTerminalThemeColor fgbgToColor(int num)\n{\n    // https://github.com/dalance/termbg/blob/13c478a433fa182e65c401d26a1e7792a7f7f453/src/lib.rs#L251\n    switch (num)\n    {\n        case  0: return (FFTerminalThemeColor){  0,   0,   0, false}; // black\n        case  1: return (FFTerminalThemeColor){205,   0,   0, false}; // red\n        case  2: return (FFTerminalThemeColor){  0, 205,   0, false}; // green\n        case  3: return (FFTerminalThemeColor){205, 205,   0, false}; // yellow\n        case  4: return (FFTerminalThemeColor){  0,   0, 238, false}; // blue\n        case  5: return (FFTerminalThemeColor){205,   0, 205, false}; // magenta\n        case  6: return (FFTerminalThemeColor){  0, 205, 205, false}; // cyan\n        case  7: return (FFTerminalThemeColor){229, 229, 229, false}; // white\n\n        case  8: return (FFTerminalThemeColor){127, 127, 127, false}; // bright black\n        case  9: return (FFTerminalThemeColor){255,   0,   0, false}; // bright red\n        case 10: return (FFTerminalThemeColor){  0, 255,   0, false}; // bright green\n        case 11: return (FFTerminalThemeColor){255, 255,   0, false}; // bright yellow\n        case 12: return (FFTerminalThemeColor){ 92,  92, 255, false}; // bright blue\n        case 13: return (FFTerminalThemeColor){255,   0, 255, false}; // bright magenta\n        case 14: return (FFTerminalThemeColor){  0, 255, 255, false}; // bright cyan\n        case 15: return (FFTerminalThemeColor){255, 255, 255, false}; // bright white\n\n        default: return (FFTerminalThemeColor){  0,   0,   0, false}; // invalid\n    }\n}\n\nstatic bool detectByEnv(FFTerminalThemeResult* result)\n{\n    const char* color = getenv(\"COLORFGBG\"); // 7;0\n\n    if (!ffStrSet(color))\n        return false;\n\n    int f, g;\n    if (sscanf(color, \"%d;%d\", &f, &g) != 2)\n        return false;\n\n    result->fg = fgbgToColor(f);\n    result->bg = fgbgToColor(g);\n    return true;\n}\n\nstatic inline bool detectColor(FFTerminalThemeResult* result, bool forceEnv)\n{\n    if (!forceEnv && detectByEscapeCode(result))\n        return true;\n\n    return detectByEnv(result);\n}\n\nbool ffDetectTerminalTheme(FFTerminalThemeResult* result, bool forceEnv)\n{\n    if (!detectColor(result, forceEnv)) return false;\n    result->fg.dark = result->fg.r * 299 + result->fg.g * 587 + result->fg.b * 114 < 128000;\n    result->bg.dark = result->bg.r * 299 + result->bg.g * 587 + result->bg.b * 114 < 128000;\n    return true;\n}\n"
  },
  {
    "path": "src/detection/terminaltheme/terminaltheme.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/terminaltheme/option.h\"\n\ntypedef struct FFTerminalThemeColor\n{\n    uint16_t r;\n    uint16_t g;\n    uint16_t b;\n    bool dark;\n} FFTerminalThemeColor;\n\ntypedef struct FFTerminalThemeResult\n{\n    FFTerminalThemeColor fg;\n    FFTerminalThemeColor bg;\n} FFTerminalThemeResult;\n\nbool ffDetectTerminalTheme(FFTerminalThemeResult* result, bool forceEnv);\n"
  },
  {
    "path": "src/detection/theme/theme.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/theme/option.h\"\n\ntypedef struct FFThemeResult\n{\n    FFstrbuf theme1;\n    FFstrbuf theme2;\n} FFThemeResult;\n\nconst char* ffDetectTheme(FFThemeResult* result);\n"
  },
  {
    "path": "src/detection/theme/theme_apple.c",
    "content": "#include \"theme.h\"\n\n#include \"detection/os/os.h\"\n\nconst char* ffDetectTheme(FFThemeResult* result)\n{\n    const FFOSResult* os = ffDetectOS();\n\n    char* str_end;\n    const char* version = os->version.chars;\n    unsigned long osNum = strtoul(version, &str_end, 10);\n    if (str_end != version)\n    {\n        if (osNum > 15) { // Tahoe\n            ffStrbufSetStatic(&result->theme1, \"Liquid Glass\");\n        } else if (osNum < 10) {\n            ffStrbufSetStatic(&result->theme1, \"Platinum\");\n        } else {\n            ffStrbufSetStatic(&result->theme1, \"Aqua\");\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/theme/theme_linux.c",
    "content": "#include \"theme.h\"\n#include \"common/parsing.h\"\n#include \"detection/gtk_qt/gtk_qt.h\"\n#include \"detection/displayserver/displayserver.h\"\n\nconst char* ffDetectTheme(FFThemeResult* result)\n{\n    const FFDisplayServerResult* wmde = ffConnectDisplayServer();\n\n    if(ffStrbufIgnCaseEqualS(&wmde->wmProtocolName, FF_WM_PROTOCOL_TTY))\n        return \"Theme isn't supported in TTY\";\n\n    const FFQtResult* plasma = ffDetectQt();\n    const FFstrbuf* gtk2 = &ffDetectGTK2()->theme;\n    const FFstrbuf* gtk3 = &ffDetectGTK3()->theme;\n    const FFstrbuf* gtk4 = &ffDetectGTK4()->theme;\n\n    if(plasma->widgetStyle.length == 0 && plasma->colorScheme.length == 0 && gtk2->length == 0 && gtk3->length == 0 && gtk4->length == 0)\n        return \"No themes found\";\n\n    ffParseGTK(&result->theme2, gtk2, gtk3, gtk4);\n\n    FF_STRBUF_AUTO_DESTROY plasmaColorPretty = ffStrbufCreate();\n    if(ffStrbufStartsWithIgnCase(&plasma->colorScheme, &plasma->widgetStyle))\n        ffStrbufAppendNS(&plasmaColorPretty, plasma->colorScheme.length - plasma->widgetStyle.length, &plasma->colorScheme.chars[plasma->widgetStyle.length]);\n    else\n        ffStrbufAppend(&plasmaColorPretty, &plasma->colorScheme);\n\n    ffStrbufTrim(&plasmaColorPretty, ' ');\n\n    if(plasma->widgetStyle.length > 0)\n    {\n        ffStrbufAppend(&result->theme1, &plasma->widgetStyle);\n\n        if(plasma->colorScheme.length > 0)\n        {\n            ffStrbufAppendS(&result->theme1, \" (\");\n\n            if(plasmaColorPretty.length > 0)\n                ffStrbufAppend(&result->theme1, &plasmaColorPretty);\n            else\n                ffStrbufAppend(&result->theme1, &plasma->colorScheme);\n\n            ffStrbufAppendC(&result->theme1, ')');\n        }\n    }\n    else if(plasma->colorScheme.length > 0)\n    {\n        if(plasmaColorPretty.length > 0)\n            ffStrbufAppend(&result->theme1, &plasmaColorPretty);\n        else\n            ffStrbufAppend(&result->theme1, &plasma->colorScheme);\n    }\n\n    if(plasma->widgetStyle.length > 0 || plasma->colorScheme.length > 0)\n        ffStrbufAppendS(&result->theme1, \" [Qt]\");\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/theme/theme_nosupport.c",
    "content": "#include \"theme.h\"\n\nconst char* ffDetectTheme(FF_MAYBE_UNUSED FFThemeResult* result)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/theme/theme_windows.c",
    "content": "#include \"theme.h\"\n\n#include \"detection/os/os.h\"\n\nconst char* ffDetectTheme(FFThemeResult* result)\n{\n    const FFOSResult* os = ffDetectOS();\n    uint32_t ver = (uint32_t) ffStrbufToUInt(&os->version, 0);\n    if (ver > 1000)\n    {\n        // Windows Server\n        if (ver >= 2016)\n            ffStrbufSetStatic(&result->theme1, \"Fluent\");\n        else if (ver >= 2012)\n            ffStrbufSetStatic(&result->theme1, \"Metro\");\n        else\n            ffStrbufSetStatic(&result->theme1, \"Aero\");\n    }\n    else\n    {\n        if (ver >= 10)\n            ffStrbufSetStatic(&result->theme1, \"Fluent\");\n        else if (ver >= 8)\n            ffStrbufSetStatic(&result->theme1, \"Metro\");\n        else\n            ffStrbufSetStatic(&result->theme1, \"Aero\");\n    }\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/tpm/tpm.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/tpm/option.h\"\n\ntypedef struct FFTPMResult\n{\n    FFstrbuf version;\n    FFstrbuf description;\n} FFTPMResult;\n\nconst char* ffDetectTPM(FFTPMResult* result);\n"
  },
  {
    "path": "src/detection/tpm/tpm_apple.c",
    "content": "#include \"tpm.h\"\n\n#ifndef __aarch64__\n    #include \"common/apple/cf_helpers.h\"\n    #include <IOKit/IOKitLib.h>\n#endif\n\nconst char* ffDetectTPM(FFTPMResult* result)\n{\n    #ifdef __aarch64__\n\n    ffStrbufSetStatic(&result->version, \"2.0\");\n    ffStrbufSetStatic(&result->description, \"Apple Silicon Security\");\n    return NULL;\n\n    #else\n\n    FF_IOOBJECT_AUTO_RELEASE io_service_t t2Service = IOServiceGetMatchingService(\n        MACH_PORT_NULL,\n        IOServiceMatching(\"AppleT2\"));\n\n    if (t2Service) {\n        ffStrbufSetStatic(&result->version, \"2.0\");\n        ffStrbufSetStatic(&result->description, \"Apple T2 Security Chip\");\n        return NULL;\n    }\n\n    #endif\n\n    return \"No Apple Security hardware detected\";\n}\n"
  },
  {
    "path": "src/detection/tpm/tpm_bsd.c",
    "content": "#include \"tpm.h\"\n#include \"common/sysctl.h\"\n#include \"common/kmod.h\"\n\nconst char* ffDetectTPM(FFTPMResult* result)\n{\n    if (ffSysctlGetString(\"dev.tpmcrb.0.%desc\", &result->description) != NULL)\n    {\n        if (!ffKmodLoaded(\"tpm\")) return \"`tpm` kernel module is not loaded\";\n        return \"TPM device is not found\";\n    }\n\n    if (ffStrbufContainS(&result->description, \"2.0\"))\n        ffStrbufSetStatic(&result->version, \"2.0\");\n    else if (ffStrbufContainS(&result->description, \"1.2\"))\n        ffStrbufSetStatic(&result->version, \"1.2\");\n    else\n        ffStrbufSetStatic(&result->version, \"unknown\");\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/tpm/tpm_linux.c",
    "content": "#include \"tpm.h\"\n#include \"common/io.h\"\n\nconst char* ffDetectTPM(FFTPMResult* result)\n{\n    if (!ffPathExists(\"/sys/class/tpm/tpm0/\", FF_PATHTYPE_DIRECTORY))\n    {\n        if (!ffPathExists(\"/sys/class/tpm/\", FF_PATHTYPE_DIRECTORY))\n            return \"TPM is not supported by kernel\";\n        return \"TPM device is not found\";\n    }\n\n    if (ffReadFileBuffer(\"/sys/class/tpm/tpm0/tpm_version_major\", &result->version))\n    {\n        ffStrbufTrimRightSpace(&result->version);\n        if (ffStrbufEqualS(&result->version, \"2\"))\n            ffStrbufSetStatic(&result->version, \"2.0\");\n    }\n\n    if (ffReadFileBuffer(\"/sys/class/tpm/tpm0/device/description\", &result->description))\n        ffStrbufTrimRightSpace(&result->description);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/tpm/tpm_nosupport.c",
    "content": "#include \"tpm.h\"\n\nconst char* ffDetectTPM(FF_MAYBE_UNUSED FFTPMResult* result)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/tpm/tpm_windows.c",
    "content": "#include \"tpm.h\"\n#include \"common/library.h\"\n\n#include <windef.h>\n#include <tbs.h>\n#include <winerror.h>\n\nconst char* ffDetectTPM(FFTPMResult* result)\n{\n    FF_LIBRARY_LOAD_MESSAGE(tbs, \"TBS\" FF_LIBRARY_EXTENSION, -1)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(tbs, Tbsi_GetDeviceInfo)\n\n    TPM_DEVICE_INFO deviceInfo = {};\n    TBS_RESULT code = ffTbsi_GetDeviceInfo(sizeof(deviceInfo), &deviceInfo);\n    if (code != TBS_SUCCESS)\n        return code == (TBS_RESULT) TBS_E_TPM_NOT_FOUND ? \"TPM device is not found\" : \"Tbsi_GetDeviceInfo() failed\";\n\n    switch (deviceInfo.tpmVersion)\n    {\n    case TPM_VERSION_12:\n        ffStrbufSetStatic(&result->version, \"1.2\");\n        break;\n    case TPM_VERSION_20:\n        ffStrbufSetStatic(&result->version, \"2.0\");\n        break;\n    default:\n        ffStrbufSetStatic(&result->version, \"unknown\");\n        break;\n    }\n\n    switch (deviceInfo.tpmInterfaceType)\n    {\n    case TPM_IFTYPE_1:\n        ffStrbufSetF(&result->description, \"I/O-port or MMIO TPM %s\", result->version.chars);\n        break;\n    case TPM_IFTYPE_TRUSTZONE:\n        ffStrbufSetF(&result->description, \"Trustzone TPM %s\", result->version.chars);\n        break;\n    case TPM_IFTYPE_HW:\n        ffStrbufSetF(&result->description, \"HW TPM %s\", result->version.chars);\n        break;\n    case TPM_IFTYPE_EMULATOR:\n        ffStrbufSetF(&result->description, \"SW-emulator TPM %s\", result->version.chars);\n        break;\n    case TPM_IFTYPE_SPB:\n        ffStrbufSetF(&result->description, \"SPB attached TPM %s\", result->version.chars);\n        break;\n    default:\n        break;\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/uptime/uptime.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/uptime/option.h\"\n\ntypedef struct FFUptimeResult\n{\n    uint64_t bootTime;\n    uint64_t uptime;\n} FFUptimeResult;\n\nconst char* ffDetectUptime(FFUptimeResult* result);\n"
  },
  {
    "path": "src/detection/uptime/uptime_bsd.c",
    "content": "#include \"uptime.h\"\n#include \"common/time.h\"\n\n#include <sys/sysctl.h>\n#include <sys/time.h>\n\nconst char* ffDetectUptime(FFUptimeResult* result)\n{\n    #if __NetBSD__\n    struct timespec bootTime;\n    #else\n    struct timeval bootTime;\n    #endif\n    size_t bootTimeSize = sizeof(bootTime);\n    if(sysctl(\n        (int[]) {CTL_KERN, KERN_BOOTTIME}, 2,\n        &bootTime, &bootTimeSize,\n        NULL, 0\n    ) != 0)\n        return \"sysctl({CTL_KERN, KERN_BOOTTIME}) failed\";\n\n    #if __NetBSD__\n    result->bootTime = (uint64_t) bootTime.tv_sec * 1000 + (uint64_t) bootTime.tv_nsec / 1000000;\n    #else\n    result->bootTime = (uint64_t) bootTime.tv_sec * 1000 + (uint64_t) bootTime.tv_usec / 1000;\n    #endif\n    result->uptime = ffTimeGetNow() - result->bootTime;\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/uptime/uptime_haiku.c",
    "content": "#include \"uptime.h\"\n#include \"common/time.h\"\n\nconst char* ffDetectUptime(FFUptimeResult* result)\n{\n    result->uptime = (uint64_t) system_time() / 1000;\n    result->bootTime = ffTimeGetNow() - result->uptime;\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/uptime/uptime_linux.c",
    "content": "#include \"uptime.h\"\n#include \"common/time.h\"\n#include \"common/io.h\"\n\n#include <inttypes.h>\n\nconst char* ffDetectUptime(FFUptimeResult* result)\n{\n    #ifndef __ANDROID__ // cat: /proc/uptime: Permission denied\n\n    // #620\n    char buf[64];\n    ssize_t nRead = ffReadFileData(\"/proc/uptime\", ARRAY_SIZE(buf) - 1, buf);\n    if(nRead > 0)\n    {\n        buf[nRead] = '\\0';\n\n        char *err = NULL;\n        double sec = strtod(buf, &err);\n        if(err != buf)\n        {\n            result->uptime = (uint64_t) (sec * 1000);\n            result->bootTime = ffTimeGetNow() - result->uptime;\n            return NULL;\n        }\n    }\n\n    #endif\n    #ifndef __GNU__\n    struct timespec uptime;\n    if (clock_gettime(CLOCK_BOOTTIME, &uptime) != 0)\n        return \"clock_gettime(CLOCK_BOOTTIME) failed\";\n\n    result->uptime = (uint64_t) uptime.tv_sec * 1000 + (uint64_t) uptime.tv_nsec / 1000000;\n    result->bootTime = ffTimeGetNow() - result->uptime;\n    return NULL;\n    #else\n    return \"read(/proc/uptime) failed\";\n    #endif\n}\n"
  },
  {
    "path": "src/detection/uptime/uptime_sunos.c",
    "content": "#include \"uptime.h\"\n#include \"common/time.h\"\n\n#include <utmpx.h>\n\nconst char* ffDetectUptime(FFUptimeResult* result)\n{\n    struct utmpx* ut;\n\n    setutxent();\n    while (NULL != (ut = getutxent()))\n    {\n        if (ut->ut_type == BOOT_TIME)\n        {\n            result->bootTime = (uint64_t) ut->ut_tv.tv_sec * 1000 + (uint64_t) ut->ut_tv.tv_usec / 1000000;\n            result->uptime = ffTimeGetNow() - result->bootTime;\n            break;\n        }\n    }\n    endutxent();\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/uptime/uptime_windows.c",
    "content": "#include \"uptime.h\"\n#include \"common/time.h\"\n#include \"common/windows/nt.h\"\n\nconst char* ffDetectUptime(FFUptimeResult* result)\n{\n    // GetInterruptTime with Win7 support\n    uint64_t interruptTime = ffKSystemTimeToUInt64(&SharedUserData->InterruptTime);\n\n    result->uptime = interruptTime / 10000; // Convert from 100-nanosecond intervals to milliseconds\n    result->bootTime = ffTimeGetNow() - result->uptime;\n\n    // Alternatively, `NtQuerySystemInformation(SystemTimeOfDayInformation)` reports the boot time directly,\n    // whose result exactly equals what WMI `Win32_OperatingSystem` reports\n    // with much lower accuracy (0.5 seconds)\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/users/users.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/users/option.h\"\n\ntypedef struct FFUserResult\n{\n    FFstrbuf name;\n    FFstrbuf hostName;\n    FFstrbuf clientIp;\n    FFstrbuf sessionName;\n    uint64_t loginTime; // ms\n} FFUserResult;\n\nconst char* ffDetectUsers(FFUsersOptions* options, FFlist* users);\n"
  },
  {
    "path": "src/detection/users/users_linux.c",
    "content": "#include \"common/io.h\"\n#include \"common/properties.h\"\n#include \"fastfetch.h\"\n#include \"users.h\"\n\n#include <unistd.h>\n\n#if FF_HAVE_UTMPX\n    #include <utmpx.h>\n#else\n    //for Android compatibility\n    #include <utmp.h>\n    #define utmpx utmp\n    #define setutxent setutent\n    #define getutxent getutent\n#endif\n#if __linux__ || __GNU__\n    #include <netinet/in.h>\n    #include <arpa/inet.h>\n#endif\n\n#if __linux__\nbool detectUserBySystemd(const FFstrbuf* pathUsers, FFlist* users)\n{\n    FF_STRBUF_AUTO_DESTROY state = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY userName = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY loginTime = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY sessions = ffStrbufCreate();\n\n    // WARNING: This is private data. Do not parse\n    if (!ffParsePropFileValues(pathUsers->chars, 4, (FFpropquery[]) {\n        {\"NAME=\", &userName},\n        {\"STATE=\", &state},\n        {\"REALTIME=\", &loginTime},\n        {\"ONLINE_SESSIONS=\", &sessions},\n    }) || !ffStrbufEqualS(&state, \"active\"))\n        return false;\n\n    FFUserResult* user = FF_LIST_ADD(FFUserResult, *users);\n    ffStrbufInitMove(&user->name, &userName);\n    ffStrbufInit(&user->hostName);\n    ffStrbufInit(&user->sessionName);\n    ffStrbufInit(&user->clientIp);\n    ffStrbufSubstrBefore(&loginTime, loginTime.length - 3); // converts us to ms\n    user->loginTime = ffStrbufToUInt(&loginTime, 0);\n\n    FF_STRBUF_AUTO_DESTROY pathSessions = ffStrbufCreateS(\"/run/systemd/sessions/\");\n    const uint32_t pathSessionsBaseLen = pathSessions.length;\n\n    FF_STRBUF_AUTO_DESTROY tty = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY remoteHost = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY service = ffStrbufCreate();\n\n    char* token = NULL;\n    size_t n = 0;\n    while (ffStrbufGetdelim(&token, &n, ' ', &sessions))\n    {\n        ffStrbufSubstrBefore(&pathSessions, pathSessionsBaseLen);\n        ffStrbufAppendS(&pathSessions, token);\n\n        ffStrbufClear(&remoteHost);\n        ffStrbufClear(&service);\n        ffStrbufClear(&tty);\n        ffStrbufClear(&loginTime);\n\n        // WARNING: This is private data. Do not parse\n        if (ffParsePropFileValues(pathSessions.chars, 4, (FFpropquery[]) {\n            {\"REMOTE_HOST=\", &remoteHost},\n            {\"TTY=\", &tty},\n            {\"SERVICE=\", &service},\n            {\"REALTIME=\", &loginTime},\n        }) && !ffStrbufEqualS(&service, \"systemd-user\"))\n        {\n            if (remoteHost.length)\n            {\n                ffStrbufTrimRight(&remoteHost, ']');\n                ffStrbufTrimLeft(&remoteHost, '[');\n                ffStrbufInitMove(&user->hostName, &remoteHost);\n            }\n            else\n                ffStrbufSetStatic(&user->hostName, \"localhost\");\n            ffStrbufInitMove(&user->sessionName, tty.length ? &tty : &service);\n            if (loginTime.length)\n            {\n                ffStrbufSubstrBefore(&loginTime, loginTime.length - 3); // converts us to ms\n                user->loginTime = ffStrbufToUInt(&loginTime, 0);\n            }\n            break;\n        }\n    }\n\n    return true;\n}\n\nconst char* detectBySystemd(FFUsersOptions* options, FFlist* users)\n{\n    // For some reason, debian/ubuntu no longer updates `/var/run/utmp` (#2064)\n    // Query systemd instead\n    FF_STRBUF_AUTO_DESTROY pathUsers = ffStrbufCreateS(\"/run/systemd/users/\");\n\n    if (options->myselfOnly)\n    {\n        ffStrbufAppendUInt(&pathUsers, instance.state.platform.uid);\n        detectUserBySystemd(&pathUsers, users);\n    }\n    else\n    {\n        const uint32_t pathUsersBaseLen = pathUsers.length;\n        FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pathUsers.chars);\n        if (!dirp) return \"opendir(\\\"/run/systemd/users/\\\") failed\";\n\n        struct dirent* entry;\n        while ((entry = readdir(dirp)))\n        {\n            if (entry->d_type != DT_REG) continue;\n\n            ffStrbufAppendS(&pathUsers, entry->d_name);\n            detectUserBySystemd(&pathUsers, users);\n            ffStrbufSubstrBefore(&pathUsers, pathUsersBaseLen);\n        }\n    }\n    return NULL;\n}\n#endif\n\n#if __linux__ || __GNU__\nstatic void fillUtmpIpAddr(FFUserResult* user, struct utmpx* n)\n{\n    bool isIpv6 = false;\n    for (int i = 1; i < 4; ++i) {\n        if (n->ut_addr_v6[i] != 0) {\n            isIpv6 = true;\n            break;\n        }\n    }\n\n    if (isIpv6) {\n        char ipv6_str[INET6_ADDRSTRLEN];\n        if (inet_ntop(AF_INET6, n->ut_addr_v6, ipv6_str, INET6_ADDRSTRLEN) != NULL) {\n            ffStrbufSetS(&user->clientIp, ipv6_str);\n        }\n    } else if (n->ut_addr_v6[0] != 0) {\n        char ipv4_str[INET_ADDRSTRLEN];\n        if (inet_ntop(AF_INET, n->ut_addr_v6, ipv4_str, INET_ADDRSTRLEN) != NULL) {\n            ffStrbufSetS(&user->clientIp, ipv4_str);\n        }\n    }\n}\n#else\nstatic void fillUtmpIpAddr(FF_MAYBE_UNUSED FFUserResult* user, FF_MAYBE_UNUSED struct utmpx* n)\n{\n}\n#endif\n\nconst char* detectByUtmp(FFUsersOptions* options, FFlist* users)\n{\n    struct utmpx* n = NULL;\n    setutxent();\n\nnext:\n    while ((n = getutxent()))\n    {\n        if (n->ut_type != USER_PROCESS)\n            continue;\n\n        if (options->myselfOnly && !ffStrbufEqualS(&instance.state.platform.userName, n->ut_user))\n            continue;\n\n        FF_LIST_FOR_EACH(FFUserResult, user, *users)\n        {\n            if(ffStrbufEqualS(&user->name, n->ut_user))\n            {\n                uint64_t newLoginTime = (uint64_t) n->ut_tv.tv_sec * 1000 + (uint64_t) n->ut_tv.tv_usec / 1000;\n                if (newLoginTime > user->loginTime)\n                {\n                    ffStrbufSetS(&user->hostName, n->ut_host);\n                    ffStrbufSetS(&user->sessionName, n->ut_line);\n                    fillUtmpIpAddr(user, n);\n                    user->loginTime = newLoginTime;\n                }\n                goto next;\n            }\n        }\n\n        FFUserResult* user = FF_LIST_ADD(FFUserResult, *users);\n        ffStrbufInitS(&user->name, n->ut_user);\n        ffStrbufInitS(&user->hostName, n->ut_host);\n        ffStrbufInitS(&user->sessionName, n->ut_line);\n        ffStrbufInit(&user->clientIp);\n        fillUtmpIpAddr(user, n);\n        user->loginTime = (uint64_t) n->ut_tv.tv_sec * 1000 + (uint64_t) n->ut_tv.tv_usec / 1000;\n    }\n\n    endutxent();\n\n    return NULL;\n}\n\nconst char* ffDetectUsers(FFUsersOptions* options, FFlist* users)\n{\n    const char* err = detectByUtmp(options, users);\n    if (err) return err;\n\n    #if __linux__\n    if (users->length == 0) detectBySystemd(options, users);\n    #endif\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/users/users_nosupport.c",
    "content": "#include \"fastfetch.h\"\n#include \"users.h\"\n\nconst char* ffDetectUsers(FFUsersOptions* options, FFlist* users)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/users/users_obsd.c",
    "content": "#include \"fastfetch.h\"\n#include \"users.h\"\n#include \"common/io.h\"\n\n#include <utmp.h>\n\nconst char* ffDetectUsers(FF_MAYBE_UNUSED FFUsersOptions* options, FFlist* users)\n{\n    FF_AUTO_CLOSE_FILE FILE* fp = fopen(_PATH_UTMP, \"r\");\n    if (!fp) return \"fopen(_PATH_UTMP, r) failed\";\n\n    struct utmp n;\nnext:\n    while (fread(&n, sizeof(n), 1, fp) == 1)\n    {\n        if (!n.ut_name[0]) continue;\n\n        if (options->myselfOnly && !ffStrbufEqualS(&instance.state.platform.userName, n.ut_name))\n            continue;\n\n        FF_LIST_FOR_EACH(FFUserResult, user, *users)\n        {\n            if (ffStrbufEqualS(&user->name, n.ut_name))\n                goto next;\n        }\n\n        FFUserResult* user = (FFUserResult*) ffListAdd(users);\n        ffStrbufInitS(&user->name, n.ut_name);\n        ffStrbufInitS(&user->hostName, n.ut_host);\n        ffStrbufInitS(&user->sessionName, n.ut_line);\n        ffStrbufInit(&user->clientIp);\n        user->loginTime = (uint64_t) n.ut_time * 1000;\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/users/users_windows.c",
    "content": "#include \"users.h\"\n#include \"common/windows/unicode.h\"\n#include \"common/time.h\"\n\n#include <windows.h>\n#include <wtsapi32.h>\n#include <ws2tcpip.h>\n\nconst char* ffDetectUsers(FFUsersOptions* options, FFlist* users)\n{\n    WTS_SESSION_INFO_1W* sessionInfo;\n    DWORD sessionCount;\n    DWORD level = 1;\n\n    if (!WTSEnumerateSessionsExW(WTS_CURRENT_SERVER_HANDLE, &level, 0, &sessionInfo, &sessionCount))\n        return \"WTSEnumerateSessionsW(WTS_CURRENT_SERVER_HANDLE) failed\";\n\n    for (DWORD i = 0; i < sessionCount; i++)\n    {\n        WTS_SESSION_INFO_1W* session = &sessionInfo[i];\n        if (session->State != WTSActive)\n            continue;\n\n        FF_STRBUF_AUTO_DESTROY userName = ffStrbufCreateWS(session->pUserName);\n\n        if (options->myselfOnly && !ffStrbufEqual(&instance.state.platform.userName, &userName))\n            continue;\n\n        FFUserResult* user = (FFUserResult*) ffListAdd(users);\n        ffStrbufInitMove(&user->name, &userName);\n        ffStrbufInitWS(&user->hostName, session->pHostName);\n        ffStrbufInitWS(&user->sessionName, session->pSessionName);\n        ffStrbufInit(&user->clientIp);\n        user->loginTime = 0;\n\n        DWORD bytes = 0;\n        PWTS_CLIENT_ADDRESS address = NULL;\n        if (WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, session->SessionId, WTSClientAddress, (LPWSTR *) &address, &bytes))\n        {\n            if (address->AddressFamily == AF_INET)\n                ffStrbufSetF(&user->clientIp, \"%u.%u.%u.%u\", address->Address[2], address->Address[3], address->Address[4], address->Address[5]);\n            else if (address->AddressFamily == AF_INET6)\n            {\n                char ipStr[INET6_ADDRSTRLEN];\n                const char* end = RtlIpv6AddressToStringA((const IN6_ADDR *) address->Address, ipStr);\n                ffStrbufSetNS(&user->clientIp, (uint32_t) (end - ipStr), ipStr);\n            }\n            WTSFreeMemory(address);\n        }\n\n        bytes = 0;\n        PWTSINFOW wtsInfo = NULL;\n        if (WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, session->SessionId, WTSSessionInfo, (LPWSTR *) &wtsInfo, &bytes))\n        {\n            user->loginTime = ffFileTimeToUnixMs((uint64_t) wtsInfo->LogonTime.QuadPart);\n            WTSFreeMemory(wtsInfo);\n        }\n    }\n\n    WTSFreeMemoryExW(WTSTypeSessionInfoLevel1, sessionInfo, 1);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/version/version.c",
    "content": "#include \"version.h\"\n\n#if defined(__x86_64__)\n    #define FF_ARCHITECTURE \"x86_64\"\n#elif defined(__i386__)\n    #define FF_ARCHITECTURE \"i386\"\n#elif defined(__ia64__)\n    #define FF_ARCHITECTURE \"ia64\"\n#elif defined(__aarch64__)\n    #define FF_ARCHITECTURE \"aarch64\"\n#elif defined(__arm__)\n    #define FF_ARCHITECTURE \"arm\"\n#elif defined(__mips__)\n    #define FF_ARCHITECTURE \"mips\"\n#elif defined(__powerpc__) || defined(__powerpc)\n    #define FF_ARCHITECTURE \"powerpc\"\n#elif defined(__riscv__) || defined(__riscv)\n    #define FF_ARCHITECTURE \"riscv\"\n#elif defined(__s390x__)\n    #define FF_ARCHITECTURE \"s390x\"\n#elif defined(__loongarch__)\n    #define FF_ARCHITECTURE \"loongarch\"\n#elif defined(__sparc__)\n    #define FF_ARCHITECTURE \"sparc\"\n#elif defined(__alpha__)\n    #define FF_ARCHITECTURE \"alpha\"\n#elif defined(__hppa__)\n    #define FF_ARCHITECTURE \"hppa\"\n#elif defined(__sh__)\n    #define FF_ARCHITECTURE \"sh\"\n#elif defined(__m68k__)\n    #define FF_ARCHITECTURE \"m68k\"\n#else\n    #define FF_ARCHITECTURE \"Unknown\"\n#endif\n\n#if defined(__ANDROID__)\n    #define FF_SYSNAME \"Android\"\n#elif defined(__linux__)\n    #define FF_SYSNAME \"Linux\"\n#elif defined(__DragonFly__) // We define `__FreeBSD__` on DragonFly BSD for simplification\n    #define FF_SYSNAME \"DragonFly\"\n#elif defined(__MidnightBSD__)\n    #define FF_SYSNAME \"MidnightBSD\"\n#elif defined(__FreeBSD__)\n    #define FF_SYSNAME \"FreeBSD\"\n#elif defined(__APPLE__)\n    #define FF_SYSNAME \"macOS\"\n#elif defined(_WIN32)\n    #define FF_SYSNAME \"Windows\"\n#elif defined(__sun)\n    #define FF_SYSNAME \"SunOS\"\n#elif defined(__OpenBSD__)\n    #define FF_SYSNAME \"OpenBSD\"\n#elif defined(__NetBSD__)\n    #define FF_SYSNAME \"NetBSD\"\n#elif defined(__HAIKU__)\n    #define FF_SYSNAME \"Haiku\"\n#elif defined(__GNU__)\n    #define FF_SYSNAME \"GNU\"\n#else\n    #define FF_SYSNAME \"Unknown\"\n#endif\n\n#define FF_STR_INDIR(x) #x\n#define FF_STR(x) FF_STR_INDIR(x)\n\nFFVersionResult ffVersionResult = {\n    .projectName = FASTFETCH_PROJECT_NAME,\n    .sysName = FF_SYSNAME,\n    .architecture = FF_ARCHITECTURE,\n    .version = FASTFETCH_PROJECT_VERSION,\n    .versionTweak = FASTFETCH_PROJECT_VERSION_TWEAK,\n    .versionGit = FASTFETCH_PROJECT_VERSION_GIT,\n    .cmakeBuiltType = FASTFETCH_PROJECT_CMAKE_BUILD_TYPE,\n    .compileTime = __DATE__ \", \" __TIME__,\n    .compiler =\n\n    #ifdef __clang__\n        #ifdef _MSC_VER\n            \"clang-cl \" ;\n        #elif defined(__APPLE__) && defined(__apple_build_version__)\n            \"Apple clang \"\n        #else\n            \"clang \"\n        #endif\n\n        FF_STR(__clang_major__) \".\" FF_STR(__clang_minor__) \".\" FF_STR(__clang_patchlevel__)\n\n        #if defined(__APPLE__) && defined(__apple_build_version__)\n            \" (\" FF_STR(__apple_build_version__) \")\"\n        #endif\n        ,\n    #elif defined(__GNUC__)\n        \"gcc \" FF_STR(__GNUC__) \".\" FF_STR(__GNUC_MINOR__) \".\" FF_STR(__GNUC_PATCHLEVEL__),\n    #elif defined(_MSC_VER)\n        \"msvc \" FF_STR(_MSC_VER),\n    #else\n        \"unknown\",\n    #endif\n\n    .debugMode =\n    #ifndef NDEBUG\n        true,\n    #else\n        false,\n    #endif\n};\n"
  },
  {
    "path": "src/detection/version/version.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/version/option.h\"\n\ntypedef struct FFVersionResult\n{\n    const char* projectName;\n    const char* sysName;\n    const char* architecture;\n    const char* version;\n    const char* versionTweak;\n    const char* versionGit;\n    const char* cmakeBuiltType;\n    const char* compileTime;\n    const char* compiler;\n    bool debugMode;\n} FFVersionResult;\n\nextern FFVersionResult ffVersionResult;\n"
  },
  {
    "path": "src/detection/vulkan/vulkan.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/debug.h\"\n#include \"detection/gpu/gpu.h\"\n#include \"detection/vulkan/vulkan.h\"\n\n#ifdef FF_HAVE_VULKAN\n#include \"common/library.h\"\n#include \"common/io.h\"\n#include \"common/parsing.h\"\n#include \"common/stringUtils.h\"\n\n#include <stdlib.h>\n#include <vulkan/vulkan.h>\n\nstatic inline void applyVulkanVersion(uint32_t vulkanVersion, FFVersion* ffVersion)\n{\n    ffVersion->major = VK_VERSION_MAJOR(vulkanVersion);\n    ffVersion->minor = VK_VERSION_MINOR(vulkanVersion);\n    ffVersion->patch = VK_VERSION_PATCH(vulkanVersion);\n}\n\nstatic void applyDriverName(VkPhysicalDeviceDriverPropertiesKHR* properties, FFstrbuf* result)\n{\n    if(!ffStrSet(properties->driverName))\n        return;\n\n    ffStrbufAppendS(result, properties->driverName);\n\n    /*\n     * Some drivers (android for example) expose a multiline string as driver info.\n     * It contains too much info anyways, so we just don't append it.\n     */\n    if(!ffStrSet(properties->driverInfo) || strchr(properties->driverInfo, '\\n') != NULL)\n        return;\n\n    ffStrbufAppendS(result, \" [\");\n    ffStrbufAppendS(result, properties->driverInfo);\n    ffStrbufAppendC(result, ']');\n}\n\nstatic const char* detectVulkan(FFVulkanResult* result)\n{\n    FF_DEBUG(\"Starting Vulkan detection\");\n\n    FF_LIBRARY_LOAD_MESSAGE(vulkan,\n        #if __APPLE__\n            \"libMoltenVK\" FF_LIBRARY_EXTENSION, -1\n        #elif _WIN32\n            \"vulkan-1\" FF_LIBRARY_EXTENSION, -1\n        #else\n            \"libvulkan\" FF_LIBRARY_EXTENSION, 2\n        #endif\n    )\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkGetInstanceProcAddr)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkCreateInstance)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkDestroyInstance)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkEnumeratePhysicalDevices)\n\n    //Some drivers (nvdc) print messages to stdout\n    //and that is the best way I found to disable that\n    FF_SUPPRESS_IO();\n    FF_DEBUG(\"Suppressed stdout/stderr during Vulkan probing to avoid noisy drivers\");\n\n    FFVersion instanceVersion = FF_VERSION_INIT;\n\n    //We need to get the function pointer this way, because it is only provided by vulkan 1.1 and higher.\n    //a dlsym would fail on 1.0 implementations\n    PFN_vkEnumerateInstanceVersion ffvkEnumerateInstanceVersion = (PFN_vkEnumerateInstanceVersion) ffvkGetInstanceProcAddr(NULL, \"vkEnumerateInstanceVersion\");\n    if(ffvkEnumerateInstanceVersion != NULL)\n    {\n        uint32_t version;\n        if(ffvkEnumerateInstanceVersion(&version) == VK_SUCCESS)\n        {\n            applyVulkanVersion(version, &instanceVersion);\n            FF_DEBUG(\"Detected Vulkan instance version: %u.%u.%u\", instanceVersion.major, instanceVersion.minor, instanceVersion.patch);\n        }\n        else\n            FF_DEBUG(\"vkEnumerateInstanceVersion() is available but returned a non-success status\");\n    }\n    else\n        FF_DEBUG(\"vkEnumerateInstanceVersion() is unavailable (likely Vulkan 1.0 runtime)\");\n\n    const uint32_t projectVersion = VK_MAKE_VERSION(\n        FASTFETCH_PROJECT_VERSION_MAJOR,\n        FASTFETCH_PROJECT_VERSION_MINOR,\n        FASTFETCH_PROJECT_VERSION_PATCH\n    );\n\n    VkInstance vkInstance;\n    FF_DEBUG(\"Creating Vulkan instance with requested API version %s\", instanceVersion.minor >= 1 ? \"1.1\" : \"1.0\");\n    VkResult res = ffvkCreateInstance(&(VkInstanceCreateInfo) {\n        .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,\n        .pNext = NULL,\n        .pApplicationInfo = &(VkApplicationInfo) {\n            .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,\n            .pNext = NULL,\n            .pApplicationName = FASTFETCH_PROJECT_NAME,\n            .applicationVersion = projectVersion,\n            .pEngineName = \"vulkanPrintGPUs\",\n            .engineVersion = projectVersion,\n\n            // We need to request 1.1 to get physicalDeviceDriverProperties\n            .apiVersion = instanceVersion.minor >= 1 ? VK_API_VERSION_1_1 : VK_API_VERSION_1_0\n        },\n        .enabledLayerCount = 0,\n        .ppEnabledLayerNames = NULL,\n        .enabledExtensionCount = 0,\n        .ppEnabledExtensionNames = NULL,\n        .flags = 0\n    }, NULL, &vkInstance);\n    if(res != VK_SUCCESS)\n    {\n        FF_DEBUG(\"ffvkCreateInstance() failed with VkResult=%d\", res);\n        switch (res)\n        {\n            case VK_ERROR_OUT_OF_HOST_MEMORY:\n                return \"ffvkCreateInstance() failed: VK_ERROR_OUT_OF_HOST_MEMORY\";\n            case VK_ERROR_OUT_OF_DEVICE_MEMORY:\n                return \"ffvkCreateInstance() failed: VK_ERROR_OUT_OF_DEVICE_MEMORY\";\n            case VK_ERROR_INITIALIZATION_FAILED:\n                return \"ffvkCreateInstance() failed: VK_ERROR_INITIALIZATION_FAILED\";\n            case VK_ERROR_LAYER_NOT_PRESENT:\n                return \"ffvkCreateInstance() failed: VK_ERROR_LAYER_NOT_PRESENT\";\n            case VK_ERROR_EXTENSION_NOT_PRESENT:\n                return \"ffvkCreateInstance() failed: VK_ERROR_EXTENSION_NOT_PRESENT\";\n            case VK_ERROR_INCOMPATIBLE_DRIVER:\n                return \"ffvkCreateInstance() failed: VK_ERROR_INCOMPATIBLE_DRIVER\";\n            default:\n                return \"ffvkCreateInstance() failed: unknown error\";\n        }\n    }\n    FF_DEBUG(\"Vulkan instance created successfully\");\n\n    //if instance creation succeeded, but vkEnumerateInstanceVersion didn't, this means we are running against a vulkan 1.0 implementation\n    //explicitly set this version, if no device is found, so we still have at least this info\n    if(instanceVersion.major == 0 && instanceVersion.minor == 0 && instanceVersion.patch == 0)\n    {\n        instanceVersion.major = 1;\n        FF_DEBUG(\"Falling back to Vulkan instance version 1.0 due to unavailable enumerate call\");\n    }\n\n    VkPhysicalDevice physicalDevices[128];\n    uint32_t physicalDeviceCount = (uint32_t) ARRAY_SIZE(physicalDevices);\n    res = ffvkEnumeratePhysicalDevices(vkInstance, &physicalDeviceCount, physicalDevices);\n    if(res != VK_SUCCESS)\n    {\n        FF_DEBUG(\"ffvkEnumeratePhysicalDevices() failed with VkResult=%d\", res);\n        ffvkDestroyInstance(vkInstance, NULL);\n        switch (res)\n        {\n        case VK_ERROR_OUT_OF_HOST_MEMORY:\n            return \"ffvkEnumeratePhysicalDevices() failed: VK_ERROR_OUT_OF_HOST_MEMORY\";\n        case VK_ERROR_OUT_OF_DEVICE_MEMORY:\n            return \"ffvkEnumeratePhysicalDevices() failed: VK_ERROR_OUT_OF_DEVICE_MEMORY\";\n        case VK_ERROR_INITIALIZATION_FAILED:\n            return \"ffvkEnumeratePhysicalDevices() failed: VK_ERROR_INITIALIZATION_FAILED\";\n        case VK_INCOMPLETE:\n            return \"ffvkEnumeratePhysicalDevices() failed: VK_INCOMPLETE\";\n        default:\n            return \"ffvkEnumeratePhysicalDevices() failed\";\n        }\n    }\n    FF_DEBUG(\"Enumerated %u Vulkan physical device(s)\", physicalDeviceCount);\n\n    PFN_vkGetPhysicalDeviceProperties ffvkGetPhysicalDeviceProperties = NULL;\n    PFN_vkGetPhysicalDeviceProperties2 ffvkGetPhysicalDeviceProperties2 = (PFN_vkGetPhysicalDeviceProperties2) ffvkGetInstanceProcAddr(vkInstance, \"vkGetPhysicalDeviceProperties2\"); // 1.1\n    if(!ffvkGetPhysicalDeviceProperties2)\n        ffvkGetPhysicalDeviceProperties = (PFN_vkGetPhysicalDeviceProperties) ffvkGetInstanceProcAddr(vkInstance, \"vkGetPhysicalDeviceProperties\");\n\n    FF_DEBUG(\"Using %s for querying physical device properties\", ffvkGetPhysicalDeviceProperties2 ? \"vkGetPhysicalDeviceProperties2\" : \"vkGetPhysicalDeviceProperties\");\n\n    PFN_vkGetPhysicalDeviceMemoryProperties ffvkGetPhysicalDeviceMemoryProperties = (PFN_vkGetPhysicalDeviceMemoryProperties) ffvkGetInstanceProcAddr(vkInstance, \"vkGetPhysicalDeviceMemoryProperties\");\n    if(!ffvkGetPhysicalDeviceMemoryProperties)\n    {\n        FF_DEBUG(\"vkGetPhysicalDeviceMemoryProperties is unavailable\");\n        ffvkDestroyInstance(vkInstance, NULL);\n        return \"vkGetPhysicalDeviceMemoryProperties is not available\";\n    }\n\n    FFVersion maxDeviceApiVersion = FF_VERSION_INIT;\n    FFVersion maxDeviceConformanceVersion = FF_VERSION_INIT;\n\n    for(uint32_t i = 0; i < physicalDeviceCount; i++)\n    {\n        //Get device properties.\n        //On VK 1.1 and up, we use vkGetPhysicalDeviceProperties2, so we can put VkPhysicalDeviceDriverProperties in the pNext chain.\n        //This is required to get the driver name and conformance version.\n\n        VkPhysicalDeviceDriverPropertiesKHR driverProperties = {\n            .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES_KHR,\n        };\n        VkPhysicalDeviceProperties2 physicalDeviceProperties = {\n            .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,\n            .pNext = &driverProperties,\n        };\n\n        if(ffvkGetPhysicalDeviceProperties2 != NULL)\n            ffvkGetPhysicalDeviceProperties2(physicalDevices[i], &physicalDeviceProperties);\n        else\n            ffvkGetPhysicalDeviceProperties(physicalDevices[i], &physicalDeviceProperties.properties);\n\n        FF_DEBUG(\"Processing Vulkan device #%u: name='%s', vendorId=0x%04X, deviceId=0x%04X, type=%u\", i,\n            physicalDeviceProperties.properties.deviceName,\n            physicalDeviceProperties.properties.vendorID,\n            physicalDeviceProperties.properties.deviceID,\n            physicalDeviceProperties.properties.deviceType);\n\n\n        //We don't want software rasterizers to show up as physical gpu\n        if(physicalDeviceProperties.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU)\n        {\n            FF_DEBUG(\"Skipping CPU Vulkan device '%s'\", physicalDeviceProperties.properties.deviceName);\n            continue;\n        }\n\n        //If the device api version is higher than the current highest device api version, overwrite it\n        //In this case, also use the current device driver name as the shown driver name\n\n        FFVersion deviceAPIVersion = FF_VERSION_INIT;\n        applyVulkanVersion(physicalDeviceProperties.properties.apiVersion, &deviceAPIVersion);\n        if(ffVersionCompare(&deviceAPIVersion, &maxDeviceApiVersion) > 0)\n        {\n            maxDeviceApiVersion = deviceAPIVersion;\n            applyDriverName(&driverProperties, &result->driver);\n            FF_DEBUG(\"Updated max Vulkan device API version to %u.%u.%u (driver='%s')\",\n                maxDeviceApiVersion.major,\n                maxDeviceApiVersion.minor,\n                maxDeviceApiVersion.patch,\n                result->driver.chars);\n        }\n\n        //If the device conformance version is higher than the current highest device conformance version, overwrite it\n        if(ffvkGetPhysicalDeviceProperties2)\n        {\n            FFVersion deviceConformanceVersion = {\n                .major = driverProperties.conformanceVersion.major,\n                .minor = driverProperties.conformanceVersion.minor,\n                .patch = driverProperties.conformanceVersion.patch,\n            };\n\n            if(ffVersionCompare(&deviceConformanceVersion, &maxDeviceConformanceVersion) > 0)\n            {\n                maxDeviceConformanceVersion = deviceConformanceVersion;\n                FF_DEBUG(\"Updated max Vulkan conformance version to %u.%u.%u\",\n                    maxDeviceConformanceVersion.major,\n                    maxDeviceConformanceVersion.minor,\n                    maxDeviceConformanceVersion.patch);\n            }\n        }\n\n        //Add the device to the list of devices shown by the GPU module\n\n        // #456\n        FF_LIST_FOR_EACH(FFGPUResult, gpu, result->gpus)\n        {\n            if (gpu->deviceId == physicalDeviceProperties.properties.deviceID)\n            {\n                FF_DEBUG(\"Skipping duplicate Vulkan GPU entry for deviceId=0x%04X\", physicalDeviceProperties.properties.deviceID);\n                goto next;\n            }\n        }\n\n        FFGPUResult* gpu = ffListAdd(&result->gpus);\n\n        ffStrbufInitF(&gpu->platformApi, \"Vulkan %u.%u.%u\", deviceAPIVersion.major, deviceAPIVersion.minor, deviceAPIVersion.patch);\n        gpu->deviceId = physicalDeviceProperties.properties.deviceID;\n\n        ffStrbufInitS(&gpu->name, physicalDeviceProperties.properties.deviceName);\n\n        gpu->type = physicalDeviceProperties.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU ? FF_GPU_TYPE_DISCRETE : FF_GPU_TYPE_INTEGRATED;\n        ffStrbufInitS(&gpu->vendor, ffGPUGetVendorString(physicalDeviceProperties.properties.vendorID));\n        ffStrbufInitS(&gpu->driver, driverProperties.driverInfo);\n        ffStrbufInit(&gpu->memoryType);\n        FF_DEBUG(\"Added Vulkan GPU '%s' (vendor='%s', type=%s)\",\n            gpu->name.chars,\n            gpu->vendor.chars,\n            gpu->type == FF_GPU_TYPE_DISCRETE ? \"discrete\" : \"integrated\");\n\n        VkPhysicalDeviceMemoryProperties memoryProperties = {};\n        ffvkGetPhysicalDeviceMemoryProperties(physicalDevices[i], &memoryProperties);\n\n        gpu->dedicated.total = gpu->shared.total = 0;\n        gpu->dedicated.used = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;\n        for(uint32_t index = 0; index < memoryProperties.memoryHeapCount; ++index)\n        {\n            const VkMemoryHeap* heap = &memoryProperties.memoryHeaps[index];\n            FFGPUMemory* vmem = gpu->type == FF_GPU_TYPE_DISCRETE && (heap->flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) ? &gpu->dedicated : &gpu->shared;\n            vmem->total += heap->size;\n        }\n        FF_DEBUG(\"Computed memory for '%s': dedicatedTotal=%llu, sharedTotal=%llu\",\n            gpu->name.chars,\n            (unsigned long long) gpu->dedicated.total,\n            (unsigned long long) gpu->shared.total);\n\n        //No way to detect those using vulkan\n        gpu->index = FF_GPU_INDEX_UNSET;\n        gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;\n        gpu->temperature = FF_GPU_TEMP_UNSET;\n        gpu->frequency = FF_GPU_FREQUENCY_UNSET;\n        gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;\n\n    next:\n        continue;\n    }\n\n    ffVersionToPretty(&instanceVersion, &result->instanceVersion);\n    ffVersionToPretty(&maxDeviceApiVersion, &result->apiVersion);\n    ffVersionToPretty(&maxDeviceConformanceVersion, &result->conformanceVersion);\n\n    FF_DEBUG(\"Vulkan detection finished: instanceVersion=%s, apiVersion=%s, conformanceVersion=%s, gpuCount=%u\",\n        result->instanceVersion.chars,\n        result->apiVersion.chars,\n        result->conformanceVersion.chars,\n        result->gpus.length);\n\n    ffvkDestroyInstance(vkInstance, NULL);\n    FF_DEBUG(\"Destroyed Vulkan instance\");\n    return NULL;\n}\n\n#endif\n\nFFVulkanResult* ffDetectVulkan(void)\n{\n    static FFVulkanResult result;\n\n    if (result.gpus.elementSize == 0)\n    {\n        FF_DEBUG(\"Initializing Vulkan detection cache\");\n        ffStrbufInit(&result.driver);\n        ffStrbufInit(&result.apiVersion);\n        ffStrbufInit(&result.conformanceVersion);\n        ffStrbufInit(&result.instanceVersion);\n        ffListInit(&result.gpus, sizeof(FFGPUResult));\n\n        #ifdef FF_HAVE_VULKAN\n            result.error = detectVulkan(&result);\n            if (result.error)\n                FF_DEBUG(\"Vulkan detection returned error: %s\", result.error);\n        #else\n            result.error = \"fastfetch was compiled without vulkan support\";\n            FF_DEBUG(\"Vulkan support is disabled at compile time\");\n        #endif\n    }\n    else\n        FF_DEBUG(\"Reusing cached Vulkan detection result\");\n\n    return &result;\n}\n"
  },
  {
    "path": "src/detection/vulkan/vulkan.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/vulkan/option.h\"\n\ntypedef struct FFVulkanResult\n{\n    FFstrbuf driver;\n    FFstrbuf apiVersion;\n    FFstrbuf conformanceVersion;\n    FFstrbuf instanceVersion;\n    FFlist gpus; //List of FFGPUResult, see detection/gpu/gpu.h\n    const char* error;\n} FFVulkanResult;\n\nFFVulkanResult* ffDetectVulkan();\n"
  },
  {
    "path": "src/detection/wallpaper/wallpaper.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\nconst char* ffDetectWallpaper(FFstrbuf* result);\n"
  },
  {
    "path": "src/detection/wallpaper/wallpaper_apple.m",
    "content": "#include \"wallpaper.h\"\n#include \"common/settings.h\"\n#include \"common/apple/osascript.h\"\n\n#import <Foundation/Foundation.h>\n\nconst char* ffDetectWallpaper(FFstrbuf* result)\n{\n    {\n        // For Sonoma\n        // https://github.com/JohnCoates/Aerial/issues/1332\n        NSError* error;\n        NSString* fileName = [NSString stringWithFormat:@\"file://%s/Library/Application Support/com.apple.wallpaper/Store/Index.plist\", instance.state.platform.homeDir.chars];\n        NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName]\n                                        error:&error];\n        if (!error)\n        {\n            NSArray* choices = [dict valueForKeyPath:@\"SystemDefault.Desktop.Content.Choices\"];\n            if (choices.count > 0)\n            {\n                NSDictionary* choice = choices[0];\n                NSArray* files = choice[@\"Files\"];\n                if (files.count > 0)\n                {\n                    NSString* file = files[0][@\"relative\"];\n                    ffStrbufAppendS(result, [NSURL URLWithString:file].path.UTF8String);\n                }\n                else\n                {\n                    NSString* provider = choice[@\"Provider\"];\n                    NSString* builtinPrefix = @\"com.apple.wallpaper.choice.\";\n                    if ([provider hasPrefix:builtinPrefix])\n                        provider = [provider substringFromIndex:builtinPrefix.length];\n                    if ([provider isEqualToString:@\"sonoma\"])\n                        ffStrbufSetStatic(result, \"macOS Sonoma\");\n                    else if ([provider isEqualToString:@\"aerials\"]) // Most builtin aerial wallpapers are private\n                        ffStrbufSetStatic(result, \"Built-in aerial photography\");\n                    else\n                        ffStrbufAppendF(result, \"Built-in %s wallpaper\", provider.UTF8String);\n                }\n            }\n            if (result->length > 0)\n                return NULL;\n        }\n    }\n\n    #ifdef FF_HAVE_SQLITE3\n\n    {\n        // For Ventura\n        // https://stackoverflow.com/questions/301215/getting-desktop-background-on-mac\n        FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateCopy(&instance.state.platform.homeDir);\n        ffStrbufAppendS(&path, \"Library/Application Support/Dock/desktoppicture.db\");\n        if (ffSettingsGetSQLite3String(path.chars,\n            \"SELECT value\\n\"\n            \"FROM preferences\\n\"\n            \"JOIN data ON preferences.data_id=data.ROWID\\n\"\n            \"JOIN pictures ON preferences.picture_id=pictures.ROWID\\n\"\n            \"JOIN displays ON pictures.display_id=displays.ROWID\\n\"\n            \"JOIN spaces ON pictures.space_id=spaces.ROWID\\n\"\n            \"WHERE display_id=1 AND space_id=1 AND key=1\", result)\n        )\n            return NULL;\n    }\n\n    #endif\n\n    if (ffOsascript(\"tell application \\\"Finder\\\" to get POSIX path of (get desktop picture as alias)\", result))\n        return NULL;\n\n    return \"All detection methods failed\";\n}\n"
  },
  {
    "path": "src/detection/wallpaper/wallpaper_linux.c",
    "content": "#include \"wallpaper.h\"\n#include \"common/settings.h\"\n#include \"detection/gtk_qt/gtk_qt.h\"\n\nconst char* ffDetectWallpaper(FFstrbuf* result)\n{\n    const FFstrbuf* wallpaper = NULL;\n    const FFGTKResult* gtk = ffDetectGTK4();\n    if (gtk->wallpaper.length)\n        wallpaper = &gtk->wallpaper;\n    else\n    {\n        const FFQtResult* qt = ffDetectQt();\n        if (qt->wallpaper.length)\n            wallpaper = &qt->wallpaper;\n    }\n\n    if (!wallpaper)\n        return \"Failed to detect the current wallpaper path\";\n\n    if (ffStrbufStartsWithS(wallpaper, \"file:///\"))\n        ffStrbufAppendS(result, wallpaper->chars + strlen(\"file://\"));\n    else\n        ffStrbufAppend(result, wallpaper);\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/wallpaper/wallpaper_nosupport.c",
    "content": "#include \"wallpaper.h\"\r\n\r\nconst char* ffDetectWallpaper(FF_MAYBE_UNUSED FFstrbuf* result)\r\n{\r\n    return \"Not supported on this platform\";\r\n}\r\n"
  },
  {
    "path": "src/detection/wallpaper/wallpaper_windows.c",
    "content": "#include \"wallpaper.h\"\n#include \"common/windows/registry.h\"\n\nconst char* ffDetectWallpaper(FFstrbuf* result)\n{\n    FF_AUTO_CLOSE_FD HANDLE hKey = NULL;\n    if(!ffRegOpenKeyForRead(HKEY_CURRENT_USER, L\"Control Panel\\\\Desktop\", &hKey, NULL))\n        return \"ffRegOpenKeyForRead(Control Panel\\\\Desktop) failed\";\n\n    if(!ffRegReadStrbuf(hKey, L\"WallPaper\", result, NULL))\n        return \"ffRegReadStrbuf(WallPaper) failed\";\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/weather/weather.c",
    "content": "#include \"weather.h\"\n#include \"common/networking.h\"\n\n#define FF_UNITIALIZED ((const char*)(uintptr_t) -1)\nstatic FFNetworkingState state;\nstatic const char* status = FF_UNITIALIZED;\n\nvoid ffPrepareWeather(FFWeatherOptions* options)\n{\n    if (status != FF_UNITIALIZED)\n    {\n        fputs(\"Error: Weather module can only be used once due to internal limitations\\n\", stderr);\n        exit(1);\n    }\n\n    state.timeout = options->timeout;\n\n    FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateS(\"/\");\n    if (options->location.length)\n        ffStrbufAppend(&path, &options->location);\n    ffStrbufAppendS(&path, \"?format=\");\n    ffStrbufAppend(&path, &options->outputFormat);\n    switch (instance.config.display.tempUnit)\n    {\n        case FF_TEMPERATURE_UNIT_CELSIUS:\n            ffStrbufAppendS(&path, \"&m\");\n            break;\n        case FF_TEMPERATURE_UNIT_FAHRENHEIT:\n            ffStrbufAppendS(&path, \"&u\");\n            break;\n        default:\n            break;\n    }\n    status = ffNetworkingSendHttpRequest(&state, \"wttr.in\", path.chars, \"User-Agent: curl/0.0.0\\r\\n\");\n}\n\nconst char* ffDetectWeather(FFWeatherOptions* options, FFstrbuf* result)\n{\n    if(status == FF_UNITIALIZED)\n        ffPrepareWeather(options);\n\n    if(status != NULL)\n        return status;\n\n    ffStrbufEnsureFree(result, 4095);\n    const char* error = ffNetworkingRecvHttpResponse(&state, result);\n\n    state = (FFNetworkingState){};\n    status = FF_UNITIALIZED;\n\n    if (error == NULL)\n    {\n        ffStrbufSubstrAfterFirstS(result, \"\\r\\n\\r\\n\");\n        ffStrbufTrimRightSpace(result);\n    }\n    else\n        return error;\n\n    if(result->length == 0)\n        return \"Empty server response received\";\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/weather/weather.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/weather/option.h\"\n\nvoid ffPrepareWeather(FFWeatherOptions* options);\nconst char* ffDetectWeather(FFWeatherOptions* options, FFstrbuf* result);\n"
  },
  {
    "path": "src/detection/wifi/wifi.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/wifi/option.h\"\n\nstruct FFWifiInterface\n{\n    FFstrbuf description;\n    FFstrbuf status;\n};\n\nstruct FFWifiConnection\n{\n    FFstrbuf status;\n    FFstrbuf ssid;\n    FFstrbuf bssid;\n    FFstrbuf protocol;\n    FFstrbuf security;\n    double signalQuality; // Percentage\n    double rxRate;\n    double txRate;\n    uint16_t channel;\n    uint16_t frequency; // MHz\n};\n\ntypedef struct FFWifiResult\n{\n    struct FFWifiInterface inf;\n    struct FFWifiConnection conn;\n} FFWifiResult;\n\nconst char* ffDetectWifi(FFlist* result /*list of FFWifiItem*/);\n\nstatic inline uint16_t ffWifiFreqToChannel(uint16_t frequency)\n{\n    // https://github.com/opetryna/win32wifi/blob/master/win32wifi/Win32Wifi.py#L140\n    // FIXME: Does it work for 6 GHz?\n    if (frequency == 2484)\n        return 14;\n    else if (frequency < 2484)\n        return (uint16_t) ((frequency - 2407) / 5);\n    else\n        return (uint16_t) ((frequency / 5) - 1000);\n}\n"
  },
  {
    "path": "src/detection/wifi/wifi_android.c",
    "content": "#include \"wifi.h\"\n\n#include \"common/processing.h\"\n#include \"common/properties.h\"\n\n#define FF_TERMUX_API_PATH FASTFETCH_TARGET_DIR_ROOT \"/libexec/termux-api\"\n#define FF_TERMUX_API_PARAM \"WifiConnectionInfo\"\n\nstatic inline void wrapYyjsonFree(yyjson_doc** doc)\n{\n    assert(doc);\n    if (*doc)\n        yyjson_doc_free(*doc);\n}\n\nconst char* ffDetectWifi(FFlist* result)\n{\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n\n    if(ffProcessAppendStdOut(&buffer, (char* const[]){\n        FF_TERMUX_API_PATH,\n        FF_TERMUX_API_PARAM,\n        NULL\n    }))\n        return \"Starting `\" FF_TERMUX_API_PATH \" \" FF_TERMUX_API_PARAM \"` failed\";\n\n    yyjson_doc* __attribute__((__cleanup__(wrapYyjsonFree))) doc = yyjson_read_opts(buffer.chars, buffer.length, 0, NULL, NULL);\n    if (!doc)\n        return \"Failed to parse wifi connection info\";\n\n    yyjson_val* root = yyjson_doc_get_root(doc);\n    if (!yyjson_is_obj(root))\n        return \"Wifi info result is not a JSON object\";\n\n    FFWifiResult* item = (FFWifiResult*)ffListAdd(result);\n    ffStrbufInit(&item->inf.description);\n    ffStrbufInit(&item->inf.status);\n    ffStrbufInit(&item->conn.status);\n    ffStrbufInit(&item->conn.ssid);\n    ffStrbufInit(&item->conn.bssid);\n    ffStrbufInit(&item->conn.protocol);\n    ffStrbufInit(&item->conn.security);\n    item->conn.signalQuality = -DBL_MAX;\n    item->conn.rxRate = -DBL_MAX;\n    item->conn.txRate = -DBL_MAX;\n    item->conn.channel = 0;\n    item->conn.frequency = 0;\n\n    ffStrbufAppendJsonVal(&item->inf.status, yyjson_obj_get(root, \"supplicant_state\"));\n    if(!item->inf.status.length)\n    {\n        ffStrbufAppendS(&item->inf.status, \"Unknown\");\n        return NULL;\n    }\n\n    if(!ffStrbufEqualS(&item->inf.status, \"COMPLETED\"))\n        return NULL;\n\n    double rssi = yyjson_get_num(yyjson_obj_get(root, \"rssi\"));\n    item->conn.signalQuality = rssi >= -50 ? 100 : rssi <= -100 ? 0 : (rssi + 100) * 2;\n\n    ffStrbufAppendJsonVal(&item->inf.description, yyjson_obj_get(root, \"ip\"));\n    ffStrbufAppendJsonVal(&item->conn.bssid, yyjson_obj_get(root, \"bssid\"));\n    ffStrbufAppendJsonVal(&item->conn.ssid, yyjson_obj_get(root, \"ssid\"));\n    item->conn.frequency = (uint16_t) yyjson_get_int(yyjson_obj_get(root, \"frequency_mhz\"));\n    item->conn.txRate = yyjson_get_num(yyjson_obj_get(root, \"link_speed_mbps\"));\n    item->conn.channel = ffWifiFreqToChannel(item->conn.frequency);\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/wifi/wifi_apple.m",
    "content": "#include \"wifi.h\"\n#include \"common/processing.h\"\n#include \"common/stringUtils.h\"\n\n#import <CoreWLAN/CoreWLAN.h>\n\nstatic inline double rssiToSignalQuality(int rssi)\n{\n    return (double) (rssi >= -50 ? 100 : rssi <= -100 ? 0 : (rssi + 100) * 2);\n}\n\n@interface CWNetworkProfile()\n@property(readonly, retain, nullable) NSArray<NSDictionary *> *bssidList;\n@end\n\nconst char* ffDetectWifi(FFlist* result)\n{\n    NSArray<CWInterface*>* interfaces = CWWiFiClient.sharedWiFiClient.interfaces;\n    if (!interfaces)\n        return \"CWWiFiClient.sharedWiFiClient.interfaces is nil\";\n\n    for (CWInterface* inf in interfaces)\n    {\n        FFWifiResult* item = (FFWifiResult*) ffListAdd(result);\n        ffStrbufInit(&item->inf.description);\n        ffStrbufInit(&item->inf.status);\n        ffStrbufInit(&item->conn.status);\n        ffStrbufInit(&item->conn.ssid);\n        ffStrbufInit(&item->conn.bssid);\n        ffStrbufInit(&item->conn.protocol);\n        ffStrbufInit(&item->conn.security);\n        item->conn.signalQuality = -DBL_MAX;\n        item->conn.rxRate = -DBL_MAX;\n        item->conn.txRate = -DBL_MAX;\n        item->conn.channel = 0;\n        item->conn.frequency = 0;\n\n        ffStrbufAppendS(&item->inf.description, inf.interfaceName.UTF8String);\n        ffStrbufSetStatic(&item->inf.status, inf.powerOn ? \"Power On\" : \"Power Off\");\n        if(!inf.powerOn)\n            continue;\n\n        ffStrbufSetStatic(&item->conn.status, inf.interfaceMode != kCWInterfaceModeNone ? \"Active\" : \"Inactive\");\n        if(inf.interfaceMode == kCWInterfaceModeNone)\n            continue;\n\n        FF_STRBUF_AUTO_DESTROY ipconfig = ffStrbufCreate();\n\n        CWNetworkProfile* networkProfile = inf.configuration.networkProfiles.firstObject;\n\n        if (inf.ssid) // https://developer.apple.com/forums/thread/732431\n            ffStrbufAppendS(&item->conn.ssid, inf.ssid.UTF8String);\n        else if (networkProfile.ssid)\n            ffStrbufSetStatic(&item->conn.ssid, inf.configuration.networkProfiles.firstObject.ssid.UTF8String);\n        else\n            ffStrbufSetStatic(&item->conn.ssid, \"<redacted>\"); // https://developer.apple.com/forums/thread/732431\n\n        if (inf.bssid)\n            ffStrbufAppendS(&item->conn.bssid, inf.bssid.UTF8String);\n        else if (networkProfile.bssidList)\n            ffStrbufSetStatic(&item->conn.bssid, [networkProfile.bssidList.firstObject[@\"BSSID\"] UTF8String]);\n        else\n            ffStrbufSetStatic(&item->conn.bssid, \"<redacted>\");\n\n        switch(inf.activePHYMode)\n        {\n            case kCWPHYModeNone:\n                ffStrbufSetStatic(&item->conn.protocol, \"none\");\n                break;\n            case kCWPHYMode11a:\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11a\");\n                break;\n            case kCWPHYMode11b:\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11b\");\n                break;\n            case kCWPHYMode11g:\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11g\");\n                break;\n            case kCWPHYMode11n:\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11n (Wi-Fi 4)\");\n                break;\n            case kCWPHYMode11ac:\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11ac (Wi-Fi 5)\");\n                break;\n            case 6 /*kCWPHYMode11ax*/:\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11ax (Wi-Fi 6)\");\n                break;\n            case 7 /*kCWPHYMode11be?*/:\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11be (Wi-Fi 7)\");\n                break;\n            default:\n                if (inf.activePHYMode < 8)\n                    ffStrbufAppendF(&item->conn.protocol, \"Unknown (%ld)\", inf.activePHYMode);\n                break;\n        }\n        item->conn.signalQuality = rssiToSignalQuality((int) inf.rssiValue);\n        item->conn.txRate = inf.transmitRate;\n\n        switch(inf.security)\n        {\n            case kCWSecurityNone:\n                ffStrbufSetStatic(&item->conn.security, \"Insecure\");\n                break;\n            case kCWSecurityWEP:\n                ffStrbufSetStatic(&item->conn.security, \"WEP\");\n                break;\n            case kCWSecurityWPAPersonal:\n                ffStrbufSetStatic(&item->conn.security, \"WPA Personal\");\n                break;\n            case kCWSecurityWPAPersonalMixed:\n                ffStrbufSetStatic(&item->conn.security, \"WPA Persional Mixed\");\n                break;\n            case kCWSecurityWPA2Personal:\n                ffStrbufSetStatic(&item->conn.security, \"WPA2 Personal\");\n                break;\n            case kCWSecurityPersonal:\n                ffStrbufSetStatic(&item->conn.security, \"Personal\");\n                break;\n            case kCWSecurityDynamicWEP:\n                ffStrbufSetStatic(&item->conn.security, \"Dynamic WEP\");\n                break;\n            case kCWSecurityWPAEnterprise:\n                ffStrbufSetStatic(&item->conn.security, \"WPA Enterprise\");\n                break;\n            case kCWSecurityWPAEnterpriseMixed:\n                ffStrbufSetStatic(&item->conn.security, \"WPA Enterprise Mixed\");\n                break;\n            case kCWSecurityWPA2Enterprise:\n                ffStrbufSetStatic(&item->conn.security, \"WPA2 Enterprise\");\n                break;\n            case kCWSecurityEnterprise:\n                ffStrbufSetStatic(&item->conn.security, \"Enterprise\");\n                break;\n            case 11 /*kCWSecurityWPA3Personal*/:\n                ffStrbufSetStatic(&item->conn.security, \"WPA3 Personal\");\n                break;\n            case 12 /*kCWSecurityWPA3Enterprise*/:\n                ffStrbufSetStatic(&item->conn.security, \"WPA3 Enterprise\");\n                break;\n            case 13 /*kCWSecurityWPA3Transition*/:\n                ffStrbufSetStatic(&item->conn.security, \"WPA3 Transition\");\n                break;\n            case 14 /*kCWSecurityOWE*/:\n                ffStrbufSetStatic(&item->conn.security, \"OWE\");\n                break;\n            case 15 /*kCWSecurityOWETransition*/:\n                ffStrbufSetStatic(&item->conn.security, \"OWE Transition\");\n                break;\n            default:\n                ffStrbufAppendF(&item->conn.security, \"Unknown (%ld)\", inf.security);\n                break;\n        }\n\n        item->conn.channel = (uint16_t) inf.wlanChannel.channelNumber;\n        switch (inf.wlanChannel.channelBand)\n        {\n            case kCWChannelBand2GHz: item->conn.frequency = 2400; break;\n            case kCWChannelBand5GHz: item->conn.frequency = 5400; break;\n            case 3 /*kCWChannelBand6GHz*/: item->conn.frequency = 6400; break;\n            default: item->conn.frequency = 0; break;\n        }\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/wifi/wifi_bsd.c",
    "content": "#include \"wifi.h\"\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n\n#include <sys/ioctl.h>\n#include <sys/socket.h>\n#include <net/if.h>\n#include <net/if_media.h>\n#include <net80211/ieee80211_ioctl.h>\n\nconst char* ffDetectWifi(FFlist* result)\n{\n    struct if_nameindex* infs = if_nameindex();\n    if(!infs) {\n        return \"if_nameindex() failed\";\n    }\n\n    FF_AUTO_CLOSE_FD int sock = socket(AF_INET, SOCK_DGRAM, 0);\n    if(sock < 0) {\n        if_freenameindex(infs);\n        return \"socket() failed\";\n    }\n\n    for(struct if_nameindex* i = infs; !(i->if_index == 0 && i->if_name == NULL); ++i)\n    {\n        if (!ffStrStartsWith(i->if_name, \"wlan\")) {\n            continue;\n        }\n\n        FFWifiResult* item = (FFWifiResult*) ffListAdd(result);\n        ffStrbufInitS(&item->inf.description, i->if_name);\n        ffStrbufInit(&item->inf.status);\n        ffStrbufInit(&item->conn.status);\n        ffStrbufInit(&item->conn.ssid);\n        ffStrbufInit(&item->conn.bssid);\n        ffStrbufInit(&item->conn.protocol);\n        ffStrbufInit(&item->conn.security);\n        item->conn.signalQuality = -DBL_MAX;\n        item->conn.rxRate = -DBL_MAX;\n        item->conn.txRate = -DBL_MAX;\n        item->conn.channel = 0;\n        item->conn.frequency = 0;\n\n        char ssid[IEEE80211_NWID_LEN + 1] = {};\n        struct ieee80211req ireq = {};\n        strlcpy(ireq.i_name, i->if_name, sizeof(ireq.i_name));\n        ireq.i_type = IEEE80211_IOC_SSID;\n        ireq.i_data = ssid;\n        ireq.i_len = sizeof(ssid) - 1;\n\n        if (ioctl(sock, SIOCG80211, &ireq) < 0 || ireq.i_len == 0) {\n            struct ifreq ifr;\n            strlcpy(ifr.ifr_name, i->if_name, sizeof(ifr.ifr_name));\n            if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {\n                ffStrbufSetStatic(&item->inf.status, \"Unknown\");\n            } else {\n                ffStrbufSetStatic(&item->inf.status, ifr.ifr_flags & IFF_UP ? \"Up\" : \"Down\");\n            }\n            ffStrbufAppendS(&item->conn.status, \"Not associated\");\n            continue;\n        }\n\n        ffStrbufSetStatic(&item->inf.status, \"Up\");\n        ffStrbufSetStatic(&item->conn.status, \"Associated\");\n        ffStrbufAppendNS(&item->conn.ssid, ireq.i_len, ssid);\n\n        uint8_t bssid[IEEE80211_ADDR_LEN] = {};\n        ireq.i_type = IEEE80211_IOC_BSSID;\n        ireq.i_data = bssid;\n        ireq.i_len = sizeof(bssid);\n\n        if (ioctl(sock, SIOCG80211, &ireq) >= 0) {\n            ffStrbufSetF(&item->conn.bssid, \"%02X:%02X:%02X:%02X:%02X:%02X\",\n                         bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);\n        }\n\n        struct ieee80211_channel curchan = {};\n        ireq.i_type = IEEE80211_IOC_CURCHAN;\n        ireq.i_data = &curchan;\n        ireq.i_len = sizeof(curchan);\n\n        if (ioctl(sock, SIOCG80211, &ireq) >= 0) {\n            item->conn.channel = curchan.ic_ieee;\n            item->conn.frequency = curchan.ic_freq;\n\n            #ifdef IEEE80211_IS_CHAN_HE // for future use\n            if (IEEE80211_IS_CHAN_HE(&curchan))\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11ax (Wi-Fi 6)\");\n            else\n            #endif\n            if (IEEE80211_IS_CHAN_VHT(&curchan))\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11ac (Wi-Fi 5)\");\n            else if (IEEE80211_IS_CHAN_HT(&curchan))\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11n (Wi-Fi 4)\");\n            else if (IEEE80211_IS_CHAN_ANYG(&curchan))\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11g\");\n            else if (IEEE80211_IS_CHAN_B(&curchan))\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11b\");\n            else if (IEEE80211_IS_CHAN_A(&curchan))\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11a\");\n            else if (IEEE80211_IS_CHAN_FHSS(&curchan))\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11 (FHSS)\");\n        }\n\n        union {\n            struct ieee80211req_sta_req req;\n            uint8_t buf[1024];\n        } stareq = {};\n        memcpy(stareq.req.is_u.macaddr, bssid, sizeof(bssid));\n        ireq.i_type = IEEE80211_IOC_STA_INFO;\n        ireq.i_data = &stareq;\n        ireq.i_len = sizeof(stareq);\n\n        if (ioctl(sock, SIOCG80211, &ireq) >= 0) {\n            struct ieee80211req_sta_info* sta = stareq.req.info;\n            if (sta->isi_len != 0) {\n                item->conn.signalQuality = (sta->isi_rssi >= -50 ? 100 : sta->isi_rssi <= -100 ? 0 : (sta->isi_rssi + 100) * 2);\n                item->conn.rxRate = sta->isi_txmbps * 0.5;\n            }\n        }\n\n        ireq.i_type = IEEE80211_IOC_AUTHMODE;\n        ireq.i_data = NULL;\n        ireq.i_len = 0;\n        if (ioctl(sock, SIOCG80211, &ireq) >= 0) {\n            switch (ireq.i_val) {\n            case IEEE80211_AUTH_NONE:\n                ffStrbufSetStatic(&item->conn.security, \"Insecure\");\n                break;\n            case IEEE80211_AUTH_OPEN:\n                ffStrbufSetStatic(&item->conn.security, \"Open\");\n                break;\n            case IEEE80211_AUTH_SHARED:\n                ffStrbufSetStatic(&item->conn.security, \"Shared\");\n                break;\n            case IEEE80211_AUTH_8021X:\n                ffStrbufSetStatic(&item->conn.security, \"8021X\");\n                break;\n            case IEEE80211_AUTH_AUTO:\n                ffStrbufSetStatic(&item->conn.security, \"Auto\");\n                break;\n            case IEEE80211_AUTH_WPA:\n                ffStrbufSetStatic(&item->conn.security, \"WPA\");\n                break;\n            default:\n                ffStrbufSetF(&item->conn.security, \"Unknown (%d)\", ireq.i_val);\n                break;\n            }\n        }\n    }\n\n    if_freenameindex(infs);\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/wifi/wifi_linux.c",
    "content": "#include \"wifi.h\"\n#include \"common/dbus.h\"\n#include \"common/io.h\"\n#include \"common/processing.h\"\n#include \"common/properties.h\"\n#include \"common/stringUtils.h\"\n#include \"common/debug.h\"\n\n#include <net/if.h>\n\n#ifdef FF_HAVE_DBUS\n// https://people.freedesktop.org/~lkundrak/nm-docs/nm-dbus-types.html#NM80211ApFlags\ntypedef enum {\n    NM_802_11_AP_FLAGS_NONE    = 0x00000000,\n    NM_802_11_AP_FLAGS_PRIVACY = 0x00000001,\n    NM_802_11_AP_FLAGS_WPS     = 0x00000002,\n    NM_802_11_AP_FLAGS_WPS_PBC = 0x00000004,\n    NM_802_11_AP_FLAGS_WPS_PIN = 0x00000008,\n} NM80211ApFlags;\n\n// https://people.freedesktop.org/~lkundrak/nm-docs/nm-dbus-types.html#NM80211ApSecurityFlags\ntypedef enum {\n    NM_802_11_AP_SEC_NONE                     = 0x00000000,\n    NM_802_11_AP_SEC_PAIR_WEP40               = 0x00000001,\n    NM_802_11_AP_SEC_PAIR_WEP104              = 0x00000002,\n    NM_802_11_AP_SEC_PAIR_TKIP                = 0x00000004,\n    NM_802_11_AP_SEC_PAIR_CCMP                = 0x00000008,\n    NM_802_11_AP_SEC_GROUP_WEP40              = 0x00000010,\n    NM_802_11_AP_SEC_GROUP_WEP104             = 0x00000020,\n    NM_802_11_AP_SEC_GROUP_TKIP               = 0x00000040,\n    NM_802_11_AP_SEC_GROUP_CCMP               = 0x00000080,\n    NM_802_11_AP_SEC_KEY_MGMT_PSK             = 0x00000100,\n    NM_802_11_AP_SEC_KEY_MGMT_802_1X          = 0x00000200,\n    NM_802_11_AP_SEC_KEY_MGMT_SAE             = 0x00000400,\n    NM_802_11_AP_SEC_KEY_MGMT_OWE             = 0x00000800,\n    NM_802_11_AP_SEC_KEY_MGMT_OWE_TM          = 0x00001000,\n    NM_802_11_AP_SEC_KEY_MGMT_EAP_SUITE_B_192 = 0x00002000,\n} NM80211ApSecurityFlags;\n\n#define FF_DBUS_ITER_CONTINUE(dbus, iterator) \\\n    { \\\n        if(!(dbus).lib->ffdbus_message_iter_next(iterator)) \\\n            break; \\\n        continue; \\\n    }\n\nstatic const char* detectWifiWithNm(FFWifiResult* item, FFstrbuf* buffer)\n{\n    FF_DEBUG(\"Starting NetworkManager wifi detection for interface %s\", item->inf.description.chars);\n    FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};\n    const char* error = ffDBusLoadData(DBUS_BUS_SYSTEM, &dbus);\n    if(error)\n    {\n        FF_DEBUG(\"Failed to load DBus data: %s\", error);\n        return error;\n    }\n\n    {\n        FF_DEBUG(\"Getting device by IP interface name\");\n        DBusMessage* device = ffDBusGetMethodReply(&dbus, \"org.freedesktop.NetworkManager\", \"/org/freedesktop/NetworkManager\", \"org.freedesktop.NetworkManager\", \"GetDeviceByIpIface\", item->inf.description.chars, NULL);\n        if(!device)\n        {\n            FF_DEBUG(\"GetDeviceByIpIface failed for interface %s\", item->inf.description.chars);\n            return \"Failed to call GetDeviceByIpIface\";\n        }\n\n        ffStrbufClear(buffer);\n        DBusMessageIter rootIter;\n        if(!dbus.lib->ffdbus_message_iter_init(device, &rootIter) || !ffDBusGetString(&dbus, &rootIter, buffer))\n        {\n            FF_DEBUG(\"Failed to initialize message iterator or get device path\");\n            dbus.lib->ffdbus_message_unref(device);\n            return \"Failed to get device path\";\n        }\n        FF_DEBUG(\"Got device path: %s\", buffer->chars);\n        dbus.lib->ffdbus_message_unref(device);\n    }\n\n    if (item->conn.txRate == -DBL_MAX)\n    {\n        FF_DEBUG(\"Getting bitrate from NetworkManager\");\n        uint32_t bitrate;\n        if (ffDBusGetPropertyUint(&dbus, \"org.freedesktop.NetworkManager\", buffer->chars, \"org.freedesktop.NetworkManager.Device.Wireless\", \"Bitrate\", &bitrate))\n        {\n            item->conn.txRate = bitrate / 1000.;\n            FF_DEBUG(\"Got bitrate: %.2f Mbps\", item->conn.txRate);\n        }\n        else\n            FF_DEBUG(\"Failed to get bitrate\");\n    }\n\n    FF_DEBUG(\"Getting active access point path\");\n    FF_STRBUF_AUTO_DESTROY apPath = ffStrbufCreate();\n    if (!ffDBusGetPropertyString(&dbus, \"org.freedesktop.NetworkManager\", buffer->chars, \"org.freedesktop.NetworkManager.Device.Wireless\", \"ActiveAccessPoint\", &apPath))\n    {\n        FF_DEBUG(\"Failed to get active access point path\");\n        return \"Failed to get active access point path\";\n    }\n    FF_DEBUG(\"Got access point path: %s\", apPath.chars);\n\n    if (!item->conn.status.length)\n    {\n        ffStrbufSetStatic(&item->conn.status, \"connected\");\n        FF_DEBUG(\"Setting connection status to 'connected'\");\n    }\n\n    FF_DEBUG(\"Getting access point properties\");\n    DBusMessage* reply = ffDBusGetAllProperties(&dbus, \"org.freedesktop.NetworkManager\", apPath.chars, \"org.freedesktop.NetworkManager.AccessPoint\");\n    if(reply == NULL)\n    {\n        FF_DEBUG(\"Failed to get access point properties\");\n        return \"Failed to get access point properties\";\n    }\n\n    DBusMessageIter rootIterator;\n    if(!dbus.lib->ffdbus_message_iter_init(reply, &rootIterator) &&\n        dbus.lib->ffdbus_message_iter_get_arg_type(&rootIterator) != DBUS_TYPE_ARRAY)\n    {\n        FF_DEBUG(\"Invalid type of access point properties\");\n        dbus.lib->ffdbus_message_unref(reply);\n        return \"Invalid type of access point properties\";\n    }\n\n    DBusMessageIter arrayIterator;\n    dbus.lib->ffdbus_message_iter_recurse(&rootIterator, &arrayIterator);\n\n    NM80211ApFlags flags;\n    NM80211ApSecurityFlags wpaFlags, rsnFlags;\n    int flagCount = 0;\n\n    FF_DEBUG(\"Parsing access point properties\");\n    while(true)\n    {\n        if(dbus.lib->ffdbus_message_iter_get_arg_type(&arrayIterator) != DBUS_TYPE_DICT_ENTRY)\n            FF_DBUS_ITER_CONTINUE(dbus, &arrayIterator)\n\n        DBusMessageIter dictIterator;\n        dbus.lib->ffdbus_message_iter_recurse(&arrayIterator, &dictIterator);\n\n        const char* key;\n        dbus.lib->ffdbus_message_iter_get_basic(&dictIterator, &key);\n\n        dbus.lib->ffdbus_message_iter_next(&dictIterator);\n\n        if (ffStrEquals(key, \"Ssid\"))\n        {\n            if (!item->conn.ssid.length)\n            {\n                FF_DEBUG(\"Found SSID property\");\n                ffDBusGetString(&dbus, &dictIterator, &item->conn.ssid);\n                FF_DEBUG(\"SSID: %s\", item->conn.ssid.chars);\n            }\n        }\n        else if (ffStrEquals(key, \"HwAddress\"))\n        {\n            if (!item->conn.bssid.length)\n            {\n                FF_DEBUG(\"Found HwAddress property\");\n                ffDBusGetString(&dbus, &dictIterator, &item->conn.bssid);\n                FF_DEBUG(\"BSSID: %s\", item->conn.bssid.chars);\n            }\n        }\n        else if (ffStrEquals(key, \"Strength\"))\n        {\n            if (item->conn.signalQuality == -DBL_MAX)\n            {\n                FF_DEBUG(\"Found Strength property\");\n                uint32_t strengthPercent;\n                if (ffDBusGetUint(&dbus, &dictIterator, &strengthPercent))\n                {\n                    item->conn.signalQuality = strengthPercent;\n                    FF_DEBUG(\"Signal quality: %u%%\", strengthPercent);\n                }\n            }\n        }\n        else if (ffStrEquals(key, \"Frequency\"))\n        {\n            if (item->conn.frequency == 0)\n            {\n                FF_DEBUG(\"Found Frequency property\");\n                uint32_t frequency;\n                if (ffDBusGetUint(&dbus, &dictIterator, &frequency))\n                {\n                    item->conn.frequency = (uint16_t) frequency;\n                    FF_DEBUG(\"Frequency: %u MHz\", item->conn.frequency);\n                    if (item->conn.channel == 0)\n                    {\n                        item->conn.channel = ffWifiFreqToChannel(item->conn.frequency);\n                        FF_DEBUG(\"Calculated channel: %u\", item->conn.channel);\n                    }\n                }\n            }\n        }\n        else if ((ffStrEquals(key, \"Flags\") && ffDBusGetUint(&dbus, &dictIterator, &flags)) ||\n            (ffStrEquals(key, \"WpaFlags\") && ffDBusGetUint(&dbus, &dictIterator, &wpaFlags)) ||\n            (ffStrEquals(key, \"RsnFlags\") && ffDBusGetUint(&dbus, &dictIterator, &rsnFlags))\n        )\n            ++flagCount;\n\n        FF_DBUS_ITER_CONTINUE(dbus, &arrayIterator)\n    }\n\n    if (flagCount == 3)\n    {\n        FF_DEBUG(\"Determining security type from flags (Flags: 0x%08x, WPA: 0x%08x, RSN: 0x%08x)\",\n                flags, wpaFlags, rsnFlags);\n        if ((flags & NM_802_11_AP_FLAGS_PRIVACY) && (wpaFlags == NM_802_11_AP_SEC_NONE)\n            && (rsnFlags == NM_802_11_AP_SEC_NONE))\n        {\n            ffStrbufAppendS(&item->conn.security, \"WEP/\");\n            FF_DEBUG(\"Adding security: WEP\");\n        }\n        if (wpaFlags != NM_802_11_AP_SEC_NONE)\n        {\n            ffStrbufAppendS(&item->conn.security, \"WPA/\");\n            FF_DEBUG(\"Adding security: WPA\");\n        }\n        if ((rsnFlags & NM_802_11_AP_SEC_KEY_MGMT_PSK)\n            || (rsnFlags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) {\n            ffStrbufAppendS(&item->conn.security, \"WPA2/\");\n            FF_DEBUG(\"Adding security: WPA2\");\n        }\n        if (rsnFlags & NM_802_11_AP_SEC_KEY_MGMT_SAE) {\n            ffStrbufAppendS(&item->conn.security, \"WPA3/\");\n            FF_DEBUG(\"Adding security: WPA3\");\n        }\n        if ((rsnFlags & NM_802_11_AP_SEC_KEY_MGMT_OWE)\n            || (rsnFlags & NM_802_11_AP_SEC_KEY_MGMT_OWE_TM)) {\n            ffStrbufAppendS(&item->conn.security, \"OWE/\");\n            FF_DEBUG(\"Adding security: OWE\");\n        }\n        if ((wpaFlags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)\n            || (rsnFlags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) {\n            ffStrbufAppendS(&item->conn.security, \"802.1X/\");\n            FF_DEBUG(\"Adding security: 802.1X\");\n        }\n        if (!item->conn.security.length)\n        {\n            ffStrbufAppendS(&item->conn.security, \"Insecure\");\n            FF_DEBUG(\"No security detected, marking as 'Insecure'\");\n        }\n        else\n        {\n            ffStrbufTrimRight(&item->conn.security, '/');\n            FF_DEBUG(\"Final security string: %s\", item->conn.security.chars);\n        }\n\n        if (wpaFlags & NM_802_11_AP_SEC_PAIR_TKIP || rsnFlags & NM_802_11_AP_SEC_PAIR_TKIP) {\n            FF_DEBUG(\"Detected TKIP encryption\");\n        }\n        if (wpaFlags & NM_802_11_AP_SEC_PAIR_CCMP || rsnFlags & NM_802_11_AP_SEC_PAIR_CCMP) {\n            FF_DEBUG(\"Detected CCMP/AES encryption\");\n        }\n    }\n\n    FF_DEBUG(\"NetworkManager wifi detection completed successfully\");\n    dbus.lib->ffdbus_message_unref(reply);\n    return NULL;\n}\n#endif // FF_HAVE_DBUS\n\nstatic const char* detectWifiWithIw(FFWifiResult* item, FFstrbuf* buffer)\n{\n    FF_DEBUG(\"Starting iw wifi detection for interface %s\", item->inf.description.chars);\n    const char* error = NULL;\n    FF_STRBUF_AUTO_DESTROY output = ffStrbufCreate();\n    FF_DEBUG(\"Executing 'iw dev %s link'\", item->inf.description.chars);\n    if((error = ffProcessAppendStdOut(&output, (char* const[]){\n        \"iw\",\n        \"dev\",\n        item->inf.description.chars,\n        \"link\",\n        NULL\n    })))\n    {\n        FF_DEBUG(\"iw command execution failed: %s\", error);\n        return error;\n    }\n\n    if(output.length == 0)\n    {\n        FF_DEBUG(\"iw command output is empty\");\n        return \"iw command execution failed\";\n    }\n\n    if(!ffParsePropLines(output.chars, \"Connected to \", &item->conn.bssid))\n    {\n        FF_DEBUG(\"Not connected to any access point\");\n        ffStrbufAppendS(&item->conn.status, \"disconnected\");\n        return NULL;\n    }\n\n    FF_DEBUG(\"Connected to an access point\");\n    ffStrbufAppendS(&item->conn.status, \"connected\");\n    ffStrbufSubstrBeforeFirstC(&item->conn.bssid, ' ');\n    ffStrbufUpperCase(&item->conn.bssid);\n    FF_DEBUG(\"BSSID: %s\", item->conn.bssid.chars);\n\n    if(ffParsePropLines(output.chars, \"SSID: \", &item->conn.ssid))\n        FF_DEBUG(\"SSID: %s\", item->conn.ssid.chars);\n    else\n        FF_DEBUG(\"SSID not found in iw output\");\n\n    ffStrbufClear(buffer);\n    if(ffParsePropLines(output.chars, \"signal: \", buffer))\n    {\n        int level = (int) ffStrbufToSInt(buffer, INT_MAX);\n        if (level != INT_MAX)\n        {\n            item->conn.signalQuality = level >= -50 ? 100 : level <= -100 ? 0 : (level + 100) * 2;\n            FF_DEBUG(\"Signal level: %d dBm, quality: %.0f%%\", level, item->conn.signalQuality);\n        }\n    }\n\n    ffStrbufClear(buffer);\n    if(ffParsePropLines(output.chars, \"rx bitrate: \", buffer))\n    {\n        item->conn.rxRate = ffStrbufToDouble(buffer, -DBL_MAX);\n        FF_DEBUG(\"RX bitrate: %.2f Mbps\", item->conn.rxRate);\n    }\n\n    ffStrbufClear(buffer);\n    if(ffParsePropLines(output.chars, \"tx bitrate: \", buffer))\n    {\n        item->conn.txRate = ffStrbufToDouble(buffer, -DBL_MAX);\n        FF_DEBUG(\"TX bitrate: %.2f Mbps (raw: %s)\", item->conn.txRate, buffer->chars);\n\n        if(ffStrbufContainS(buffer, \" EHT-MCS \"))\n        {\n            ffStrbufSetStatic(&item->conn.protocol, \"802.11be (Wi-Fi 7)\");\n            FF_DEBUG(\"Detected protocol: Wi-Fi 7\");\n        }\n        else if(ffStrbufContainS(buffer, \" HE-MCS \"))\n        {\n            ffStrbufSetStatic(&item->conn.protocol, \"802.11ax (Wi-Fi 6)\");\n            FF_DEBUG(\"Detected protocol: Wi-Fi 6\");\n        }\n        else if(ffStrbufContainS(buffer, \" VHT-MCS \"))\n        {\n            ffStrbufSetStatic(&item->conn.protocol, \"802.11ac (Wi-Fi 5)\");\n            FF_DEBUG(\"Detected protocol: Wi-Fi 5\");\n        }\n        else if(ffStrbufContainS(buffer, \" MCS \"))\n        {\n            ffStrbufSetStatic(&item->conn.protocol, \"802.11n (Wi-Fi 4)\");\n            FF_DEBUG(\"Detected protocol: Wi-Fi 4\");\n        }\n    }\n\n    ffStrbufClear(buffer);\n    if(ffParsePropLines(output.chars, \"freq: \", buffer))\n    {\n        item->conn.frequency = (uint16_t) ffStrbufToUInt(buffer, 0);\n        item->conn.channel = ffWifiFreqToChannel(item->conn.frequency);\n        FF_DEBUG(\"Frequency: %u MHz, Channel: %u\", item->conn.frequency, item->conn.channel);\n    }\n\n    FF_DEBUG(\"iw wifi detection completed successfully\");\n    return NULL;\n}\n\n#if FF_HAVE_LINUX_WIRELESS\n#include <sys/ioctl.h>\n#include <sys/types.h>\n#include <unistd.h>\n#include <linux/wireless.h>\n\nstatic const char* detectWifiWithIoctls(FFWifiResult* item)\n{\n    FF_DEBUG(\"Starting ioctl wifi detection for interface %s\", item->inf.description.chars);\n    FF_AUTO_CLOSE_FD int sock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);\n    if(sock < 0)\n    {\n        FF_DEBUG(\"Failed to create socket: %m\");\n        return \"socket() failed\";\n    }\n\n    struct iwreq iwr;\n    ffStrCopy(iwr.ifr_name, item->inf.description.chars, IFNAMSIZ);\n\n    // Get SSID\n    FF_DEBUG(\"Getting SSID via ioctl\");\n    ffStrbufEnsureFree(&item->conn.ssid, IW_ESSID_MAX_SIZE);\n    iwr.u.essid.pointer = (caddr_t) item->conn.ssid.chars;\n    iwr.u.essid.length = IW_ESSID_MAX_SIZE + 1;\n    iwr.u.essid.flags = 0;\n    if(ioctl(sock, SIOCGIWESSID, &iwr) >= 0)\n    {\n        ffStrbufSetStatic(&item->conn.status, \"connected\");\n        ffStrbufRecalculateLength(&item->conn.ssid);\n        FF_DEBUG(\"SSID: %s\", item->conn.ssid.chars);\n    }\n    else\n        FF_DEBUG(\"Failed to get SSID via ioctl: %m\");\n\n    // Get protocol name\n    FF_DEBUG(\"Getting protocol name via ioctl\");\n    if(ioctl(sock, SIOCGIWNAME, &iwr) >= 0 && !ffStrEqualsIgnCase(iwr.u.name, \"IEEE 802.11\"))\n    {\n        if(ffStrStartsWithIgnCase(iwr.u.name, \"IEEE \"))\n            ffStrbufSetS(&item->conn.protocol, iwr.u.name + strlen(\"IEEE \"));\n        else\n            ffStrbufSetS(&item->conn.protocol, iwr.u.name);\n        FF_DEBUG(\"Protocol: %s\", item->conn.protocol.chars);\n    }\n    else\n        FF_DEBUG(\"Failed to get protocol name via ioctl: %m\");\n\n    // Get BSSID\n    FF_DEBUG(\"Getting BSSID via ioctl\");\n    if(ioctl(sock, SIOCGIWAP, &iwr) >= 0)\n    {\n        for(int i = 0; i < 6; ++i)\n            ffStrbufAppendF(&item->conn.bssid, \"%.2X:\", (uint8_t) iwr.u.ap_addr.sa_data[i]);\n        ffStrbufTrimRight(&item->conn.bssid, ':');\n        FF_DEBUG(\"BSSID: %s\", item->conn.bssid.chars);\n    }\n    else\n        FF_DEBUG(\"Failed to get BSSID via ioctl: %m\");\n\n    // Get bitrate\n    FF_DEBUG(\"Getting bitrate via ioctl\");\n    if(ioctl(sock, SIOCGIWRATE, &iwr) >= 0)\n    {\n        item->conn.txRate = iwr.u.bitrate.value / 1000000.;\n        FF_DEBUG(\"TX bitrate: %.2f Mbps\", item->conn.txRate);\n    }\n    else\n        FF_DEBUG(\"Failed to get bitrate via ioctl: %m\");\n\n    // Get frequency/channel\n    FF_DEBUG(\"Getting frequency via ioctl\");\n    if(ioctl(sock, SIOCGIWFREQ, &iwr) >= 0)\n    {\n        if (iwr.u.freq.e == 0 && iwr.u.freq.m <= 1000)\n        {\n            item->conn.channel = (uint16_t) iwr.u.freq.m;\n            FF_DEBUG(\"Direct channel value: %u\", item->conn.channel);\n        }\n        else\n        {\n            // convert it to MHz\n            while (iwr.u.freq.e < 6)\n            {\n                iwr.u.freq.m /= 10;\n                iwr.u.freq.e++;\n            }\n            while (iwr.u.freq.e > 6)\n            {\n                iwr.u.freq.m *= 10;\n                iwr.u.freq.e--;\n            }\n            item->conn.frequency = (uint16_t) iwr.u.freq.m;\n            item->conn.channel = ffWifiFreqToChannel(item->conn.frequency);\n            FF_DEBUG(\"Frequency: %u MHz, Channel: %u\", item->conn.frequency, item->conn.channel);\n        }\n    }\n    else\n        FF_DEBUG(\"Failed to get frequency via ioctl: %m\");\n\n    // Get signal strength\n    FF_DEBUG(\"Getting signal stats via ioctl\");\n    struct iw_statistics stats;\n    iwr.u.data.pointer = &stats;\n    iwr.u.data.length = sizeof(stats);\n    iwr.u.data.flags = 0;\n\n    if(ioctl(sock, SIOCGIWSTATS, &iwr) >= 0)\n    {\n        int8_t level = (int8_t) stats.qual.level;\n        item->conn.signalQuality = level >= -50 ? 100 : level <= -100 ? 0 : (level + 100) * 2;\n        FF_DEBUG(\"Signal level: %d dBm, quality: %.0f%%\", level, item->conn.signalQuality);\n    }\n    else\n        FF_DEBUG(\"Failed to get signal stats via ioctl: %m\");\n\n    // Get security info\n    FF_DEBUG(\"Getting security info via ioctl\");\n    struct iw_encode_ext iwe;\n    iwr.u.data.pointer = &iwe;\n    iwr.u.data.length = sizeof(iwe);\n    iwr.u.data.flags = 0;\n    if(ioctl(sock, SIOCGIWENCODEEXT, &iwr) >= 0)\n    {\n        switch(iwe.alg)\n        {\n            case IW_ENCODE_ALG_WEP:\n                ffStrbufAppendS(&item->conn.security, \"WEP\");\n                FF_DEBUG(\"Security: WEP\");\n                break;\n            case IW_ENCODE_ALG_TKIP:\n                ffStrbufAppendS(&item->conn.security, \"TKIP\");\n                FF_DEBUG(\"Security: TKIP\");\n                break;\n            case IW_ENCODE_ALG_CCMP:\n                ffStrbufAppendS(&item->conn.security, \"CCMP\");\n                FF_DEBUG(\"Security: CCMP\");\n                break;\n            case IW_ENCODE_ALG_PMK:\n                ffStrbufAppendS(&item->conn.security, \"PMK\");\n                FF_DEBUG(\"Security: PMK\");\n                break;\n            case IW_ENCODE_ALG_AES_CMAC:\n                ffStrbufAppendS(&item->conn.security, \"CMAC\");\n                FF_DEBUG(\"Security: CMAC\");\n                break;\n            default:\n                ffStrbufAppendF(&item->conn.security, \"Unknown (%d)\", (int) iwe.alg);\n                FF_DEBUG(\"Security: Unknown (%d)\", (int) iwe.alg);\n                break;\n        }\n    }\n    else\n        FF_DEBUG(\"Failed to get security info via ioctl: %m\");\n\n    FF_DEBUG(\"ioctl wifi detection completed\");\n    return NULL;\n}\n#endif // FF_HAVE_LINUX_WIRELESS\n\nconst char* ffDetectWifi(FF_MAYBE_UNUSED FFlist* result)\n{\n    FF_DEBUG(\"Starting wifi detection\");\n    struct if_nameindex* infs = if_nameindex();\n    if(!infs)\n    {\n        FF_DEBUG(\"if_nameindex() failed: %m\");\n        return \"if_nameindex() failed\";\n    }\n\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n\n    for(struct if_nameindex* i = infs; !(i->if_index == 0 && i->if_name == NULL); ++i)\n    {\n        FF_DEBUG(\"Checking interface: %s (index: %u)\", i->if_name, i->if_index);\n        ffStrbufSetF(&buffer, \"/sys/class/net/%s/phy80211/\", i->if_name);\n        if(!ffPathExists(buffer.chars, FF_PATHTYPE_DIRECTORY))\n        {\n            FF_DEBUG(\"Not a wifi interface (no phy80211 directory)\");\n            continue;\n        }\n\n        FF_DEBUG(\"Found wifi interface: %s\", i->if_name);\n        FFWifiResult* item = (FFWifiResult*)ffListAdd(result);\n        ffStrbufInitS(&item->inf.description, i->if_name);\n        ffStrbufInit(&item->inf.status);\n        ffStrbufInit(&item->conn.status);\n        ffStrbufInit(&item->conn.ssid);\n        ffStrbufInit(&item->conn.bssid);\n        ffStrbufInit(&item->conn.protocol);\n        ffStrbufInit(&item->conn.security);\n        item->conn.signalQuality = -DBL_MAX;\n        item->conn.rxRate = -DBL_MAX;\n        item->conn.txRate = -DBL_MAX;\n        item->conn.channel = 0;\n        item->conn.frequency = 0;\n\n        char operstate;\n        ffStrbufSetF(&buffer, \"/sys/class/net/%s/operstate\", i->if_name);\n        if (!ffReadFileData(buffer.chars, 1, &operstate))\n        {\n            FF_DEBUG(\"Failed to read operstate file\");\n            continue;\n        }\n\n        FF_DEBUG(\"Connection status: %c\", operstate);\n        if (operstate != 'u')\n        {\n            FF_DEBUG(\"Skipping interface as it's not up\");\n            ffStrbufSetStatic(&item->conn.status, \"disconnected\");\n\n            ffStrbufSetF(&buffer, \"/sys/class/net/%s/flags\", i->if_name);\n            char flags[16];\n            ssize_t len = ffReadFileData(buffer.chars, sizeof(flags), flags);\n            if (len <= 0)\n            {\n                FF_DEBUG(\"Failed to read flags file\");\n                ffStrbufSetStatic(&item->inf.status, \"unknown\");\n                continue;\n            }\n            flags[len] = '\\0';\n            FF_DEBUG(\"Interface flags: %s\", flags);\n            unsigned flagsVal = (unsigned) strtoul(flags, NULL, 16);\n            if (flagsVal & IFF_UP)\n            {\n                ffStrbufSetStatic(&item->inf.status, \"up\");\n                FF_DEBUG(\"Interface is up but not connected\");\n            }\n            else\n            {\n                ffStrbufSetStatic(&item->inf.status, \"down\");\n                FF_DEBUG(\"Interface is down\");\n            }\n\n            continue;\n        }\n\n        ffStrbufSetStatic(&item->inf.status, \"up\");\n\n        FF_DEBUG(\"Trying to detect wifi with iw\");\n        if (detectWifiWithIw(item, &buffer) != NULL)\n        {\n            FF_DEBUG(\"iw detection failed, trying fallback methods\");\n            #ifdef FF_HAVE_LINUX_WIRELESS\n                FF_DEBUG(\"Trying to detect wifi with ioctls\");\n                detectWifiWithIoctls(item);\n            #endif\n        }\n\n        #ifdef FF_HAVE_DBUS\n            FF_DEBUG(\"Enhancing wifi info with NetworkManager\");\n            detectWifiWithNm(item, &buffer);\n        #endif\n    }\n    if_freenameindex(infs);\n\n    FF_DEBUG(\"Wifi detection completed, found %u wifi interfaces\", result->length);\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/wifi/wifi_nbsd.c",
    "content": "#include \"wifi.h\"\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n\n#define COMPAT_FREEBSD_NET80211 1\n#include <sys/ioctl.h>\n#include <sys/socket.h>\n#include <net/if.h>\n#include <net/if_media.h>\n#include <net80211/ieee80211.h>\n#include <net80211/ieee80211_ioctl.h>\n\n// ieee80211 header of NetBSD is full of mess. Add compatibility macros from FreeBSD\n#undef IEEE80211_IS_CHAN_ANYG\n#define IEEE80211_IS_CHAN_ANYG(x) (IEEE80211_IS_CHAN_PUREG(x) || IEEE80211_IS_CHAN_G(x))\n#undef IEEE80211_IS_CHAN_HT\n#define IEEE80211_IS_CHAN_HT(x) (((x)->ic_flags & IEEE80211_CHAN_HT) != 0)\n#undef IEEE80211_IS_CHAN_VHT\n#define IEEE80211_IS_CHAN_VHT(x) (((x)->ic_flags & IEEE80211_CHAN_VHT) != 0)\n\nconst char* ffDetectWifi(FFlist* result)\n{\n    struct if_nameindex* infs = if_nameindex();\n    if(!infs) {\n        return \"if_nameindex() failed\";\n    }\n\n    FF_AUTO_CLOSE_FD int sock = socket(AF_INET, SOCK_DGRAM, 0);\n    if(sock < 0) {\n        if_freenameindex(infs);\n        return \"socket() failed\";\n    }\n\n    for(struct if_nameindex* i = infs; !(i->if_index == 0 && i->if_name == NULL); ++i)\n    {\n        if (!ffStrStartsWith(i->if_name, \"iwm\")) {\n            continue;\n        }\n\n        FFWifiResult* item = (FFWifiResult*) ffListAdd(result);\n        ffStrbufInitS(&item->inf.description, i->if_name);\n        ffStrbufInit(&item->inf.status);\n        ffStrbufInit(&item->conn.status);\n        ffStrbufInit(&item->conn.ssid);\n        ffStrbufInit(&item->conn.bssid);\n        ffStrbufInit(&item->conn.protocol);\n        ffStrbufInit(&item->conn.security);\n        item->conn.signalQuality = -DBL_MAX;\n        item->conn.rxRate = -DBL_MAX;\n        item->conn.txRate = -DBL_MAX;\n        item->conn.channel = 0;\n        item->conn.frequency = 0;\n\n        char ssid[IEEE80211_NWID_LEN + 1] = {};\n        struct ieee80211req ireq = {};\n        strlcpy(ireq.i_name, i->if_name, sizeof(ireq.i_name));\n        ireq.i_type = IEEE80211_IOC_SSID;\n        ireq.i_data = ssid;\n        ireq.i_len = sizeof(ssid) - 1;\n\n        if (ioctl(sock, SIOCG80211, &ireq) < 0 || ireq.i_len == 0) {\n            struct ifreq ifr;\n            strlcpy(ifr.ifr_name, i->if_name, sizeof(ifr.ifr_name));\n            if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {\n                ffStrbufSetStatic(&item->inf.status, \"Unknown\");\n            } else {\n                ffStrbufSetStatic(&item->inf.status, ifr.ifr_flags & IFF_UP ? \"Up\" : \"Down\");\n            }\n            ffStrbufAppendS(&item->conn.status, \"Not associated\");\n            continue;\n        }\n\n        ffStrbufSetStatic(&item->inf.status, \"Up\");\n        ffStrbufSetStatic(&item->conn.status, \"Associated\");\n        ffStrbufAppendNS(&item->conn.ssid, ireq.i_len, ssid);\n\n        uint8_t bssid[IEEE80211_ADDR_LEN] = {};\n        ireq.i_type = IEEE80211_IOC_BSSID;\n        ireq.i_data = bssid;\n        ireq.i_len = sizeof(bssid);\n\n        if (ioctl(sock, SIOCG80211, &ireq) >= 0) {\n            ffStrbufSetF(&item->conn.bssid, \"%02X:%02X:%02X:%02X:%02X:%02X\",\n                         bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);\n        }\n\n        struct ieee80211_channel curchan = {};\n        ireq.i_type = IEEE80211_IOC_CHANNEL;\n        ireq.i_data = &curchan;\n        ireq.i_len = sizeof(curchan);\n\n        if (ioctl(sock, SIOCG80211, &ireq) >= 0) {\n            // item->conn.channel = curchan.ic_ieee; // No ic_ieee in NetBSD\n            item->conn.channel = ffWifiFreqToChannel(curchan.ic_freq);\n            item->conn.frequency = curchan.ic_freq;\n\n            #ifdef IEEE80211_IS_CHAN_HE // for future use\n            if (IEEE80211_IS_CHAN_HE(&curchan))\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11ax (Wi-Fi 6)\");\n            else\n            #endif\n            if (IEEE80211_IS_CHAN_VHT(&curchan))\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11ac (Wi-Fi 5)\");\n            else if (IEEE80211_IS_CHAN_HT(&curchan))\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11n (Wi-Fi 4)\");\n            else if (IEEE80211_IS_CHAN_ANYG(&curchan))\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11g\");\n            else if (IEEE80211_IS_CHAN_B(&curchan))\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11b\");\n            else if (IEEE80211_IS_CHAN_A(&curchan))\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11a\");\n            else if (IEEE80211_IS_CHAN_FHSS(&curchan))\n                ffStrbufSetStatic(&item->conn.protocol, \"802.11 (FHSS)\");\n        }\n\n        union {\n            struct ieee80211req_sta_req req;\n            uint8_t buf[1024];\n        } stareq = {};\n        memcpy(stareq.req.is_u.macaddr, bssid, sizeof(bssid));\n        ireq.i_type = IEEE80211_IOC_STA_INFO;\n        ireq.i_data = &stareq;\n        ireq.i_len = sizeof(stareq);\n\n        if (ioctl(sock, SIOCG80211, &ireq) >= 0) {\n            struct ieee80211req_sta_info* sta = stareq.req.info;\n            if (sta->isi_len != 0) {\n                int8_t rssi = (int8_t) sta->isi_rssi; // Strangely, `sta->isi_rssi` is unsigned\n                item->conn.signalQuality = (rssi >= -50 ? 100 : rssi <= -100 ? 0 : (rssi + 100) * 2);\n\n                if (sta->isi_txrate) {\n                    item->conn.txRate = (double)sta->isi_txrate / 2.0;\n                }\n            }\n        }\n\n        ireq.i_type = IEEE80211_IOC_AUTHMODE;\n        ireq.i_data = NULL;\n        ireq.i_len = 0;\n        if (ioctl(sock, SIOCG80211, &ireq) >= 0) {\n            switch (ireq.i_val) {\n            case IEEE80211_AUTH_NONE:\n                ffStrbufSetStatic(&item->conn.security, \"Insecure\");\n                break;\n            case IEEE80211_AUTH_OPEN:\n                ffStrbufSetStatic(&item->conn.security, \"Open\");\n                break;\n            case IEEE80211_AUTH_SHARED:\n                ffStrbufSetStatic(&item->conn.security, \"Shared\");\n                break;\n            case IEEE80211_AUTH_8021X:\n                ffStrbufSetStatic(&item->conn.security, \"8021X\");\n                break;\n            case IEEE80211_AUTH_AUTO:\n                ffStrbufSetStatic(&item->conn.security, \"Auto\");\n                break;\n            case IEEE80211_AUTH_WPA:\n                ffStrbufSetStatic(&item->conn.security, \"WPA\");\n                break;\n            default:\n                ffStrbufSetF(&item->conn.security, \"Unknown (%d)\", ireq.i_val);\n                break;\n            }\n        }\n    }\n\n    if_freenameindex(infs);\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/wifi/wifi_nosupport.c",
    "content": "#include \"wifi.h\"\n\nconst char* ffDetectWifi(FF_MAYBE_UNUSED FFlist* result)\n{\n    return \"Not support on this platform\";\n}\n"
  },
  {
    "path": "src/detection/wifi/wifi_obsd.c",
    "content": "#include \"wifi.h\"\n#include \"common/io.h\"\n#include \"common/stringUtils.h\"\n\n#include <sys/ioctl.h>\n#include <sys/socket.h>\n#include <net/if.h>\n#include <net80211/ieee80211.h>\n#include <net80211/ieee80211_ioctl.h>\n#include <unistd.h>\n\nconst char* ffDetectWifi(FFlist* result)\n{\n    struct if_nameindex* infs = if_nameindex();\n    if(!infs) {\n        return \"if_nameindex() failed\";\n    }\n\n    FF_AUTO_CLOSE_FD int sock = socket(AF_INET, SOCK_DGRAM, 0);\n    if(sock < 0) {\n        return \"socket() failed\";\n    }\n\n    for(struct if_nameindex* i = infs; !(i->if_index == 0 && i->if_name == NULL); ++i)\n    {\n        if (!ffStrStartsWith(i->if_name, \"iwm\")) {\n            continue;\n        }\n\n        FFWifiResult* item = (FFWifiResult*) ffListAdd(result);\n        ffStrbufInitS(&item->inf.description, i->if_name);\n        ffStrbufInit(&item->inf.status);\n        ffStrbufInit(&item->conn.status);\n        ffStrbufInit(&item->conn.ssid);\n        ffStrbufInit(&item->conn.bssid);\n        ffStrbufInit(&item->conn.protocol);\n        ffStrbufInit(&item->conn.security);\n        item->conn.signalQuality = -DBL_MAX;\n        item->conn.rxRate = -DBL_MAX;\n        item->conn.txRate = -DBL_MAX;\n        item->conn.channel = 0;\n        item->conn.frequency = 0;\n\n        struct ieee80211_nodereq nr = {};\n        strlcpy(nr.nr_ifname, i->if_name, sizeof(nr.nr_ifname));\n\n        struct ifreq ifr = {};\n        strlcpy(ifr.ifr_name, i->if_name, sizeof(ifr.ifr_name));\n        if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {\n            ffStrbufSetStatic(&item->inf.status, \"Unknown\");\n        } else {\n            ffStrbufSetStatic(&item->inf.status, ifr.ifr_flags & IFF_UP ? \"Up\" : \"Down\");\n        }\n\n        if (ioctl(sock, SIOCG80211NODE, &nr) < 0) {\n            ffStrbufSetStatic(&item->conn.status, \"Not associated\");\n            continue;\n        }\n\n        if (nr.nr_nwid_len > 0) {\n            ffStrbufSetStatic(&item->conn.status, \"Associated\");\n            ffStrbufAppendNS(&item->conn.ssid, nr.nr_nwid_len, (char*)nr.nr_nwid);\n        } else {\n            ffStrbufSetStatic(&item->conn.status, \"Not associated\");\n            continue;\n        }\n\n        ffStrbufSetF(&item->conn.bssid, \"%02X:%02X:%02X:%02X:%02X:%02X\",\n                    nr.nr_bssid[0], nr.nr_bssid[1], nr.nr_bssid[2],\n                    nr.nr_bssid[3], nr.nr_bssid[4], nr.nr_bssid[5]);\n\n        item->conn.channel = nr.nr_channel;\n\n        if (nr.nr_max_rssi) {\n            item->conn.signalQuality = ((float)nr.nr_rssi / nr.nr_max_rssi) * 100.0;\n        }\n\n        if (nr.nr_flags & IEEE80211_NODEREQ_HT) {\n            ffStrbufSetStatic(&item->conn.protocol, \"802.11n (Wi-Fi 4)\");\n        } else if (nr.nr_flags & IEEE80211_NODEREQ_VHT) {\n            ffStrbufSetStatic(&item->conn.protocol, \"802.11ac (Wi-Fi 5)\");\n        } else if (nr.nr_chan_flags & IEEE80211_CHANINFO_5GHZ) {\n            ffStrbufSetStatic(&item->conn.protocol, \"802.11a\");\n        } else if (nr.nr_chan_flags & IEEE80211_CHANINFO_2GHZ) {\n            ffStrbufSetStatic(&item->conn.protocol, \"802.11g\");\n        }\n\n        struct ieee80211_wpaparams wpa = {};\n        strlcpy(wpa.i_name, i->if_name, sizeof(wpa.i_name));\n\n        if (ioctl(sock, SIOCG80211WPAPARMS, &wpa) >= 0 && wpa.i_enabled) {\n            if (wpa.i_protos & IEEE80211_WPA_PROTO_WPA2)\n                ffStrbufSetStatic(&item->conn.security, \"WPA2\");\n            else if (wpa.i_protos & IEEE80211_WPA_PROTO_WPA1)\n                ffStrbufSetStatic(&item->conn.security, \"WPA\");\n        } else {\n            struct ieee80211_nwkey nwkey = {};\n            strlcpy(nwkey.i_name, i->if_name, sizeof(nwkey.i_name));\n\n            if (ioctl(sock, SIOCG80211NWKEY, &nwkey) >= 0) {\n                if (nwkey.i_wepon)\n                    ffStrbufSetStatic(&item->conn.security, \"WEP\");\n                else\n                    ffStrbufSetStatic(&item->conn.security, \"Open\");\n            }\n        }\n    }\n\n    if_freenameindex(infs);\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/wifi/wifi_windows.c",
    "content": "#include \"wifi.h\"\n#include \"common/library.h\"\n#include \"common/windows/unicode.h\"\n\n#include <windows.h>\n#include <wlanapi.h>\n\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wswitch\"\n\nstatic void convertIfStateToString(WLAN_INTERFACE_STATE state, FFstrbuf* result)\n{\n    switch (state) {\n    case wlan_interface_state_not_ready:\n        ffStrbufAppendS(result, \"Not ready\");\n        break;\n    case wlan_interface_state_connected:\n        ffStrbufAppendS(result, \"Connected\");\n        break;\n    case wlan_interface_state_ad_hoc_network_formed:\n        ffStrbufAppendS(result, \"Ad hoc network formed\");\n        break;\n    case wlan_interface_state_disconnecting:\n        ffStrbufAppendS(result, \"Disconnecting\");\n        break;\n    case wlan_interface_state_disconnected:\n        ffStrbufAppendS(result, \"Disconnected\");\n        break;\n    case wlan_interface_state_associating:\n        ffStrbufAppendS(result, \"Associating\");\n        break;\n    case wlan_interface_state_discovering:\n        ffStrbufAppendS(result, \"Discovering\");\n        break;\n    case wlan_interface_state_authenticating:\n        ffStrbufAppendS(result, \"Authenticating\");\n        break;\n    default:\n        ffStrbufAppendS(result, \"Unknown\");\n        break;\n    }\n}\n\nconst char* ffDetectWifi(FFlist* result)\n{\n    FF_LIBRARY_LOAD_MESSAGE(wlanapi, \"wlanapi\" FF_LIBRARY_EXTENSION, 1)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wlanapi, WlanOpenHandle)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wlanapi, WlanEnumInterfaces)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wlanapi, WlanQueryInterface)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wlanapi, WlanFreeMemory)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wlanapi, WlanCloseHandle)\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wlanapi, WlanGetNetworkBssList)\n\n    DWORD curVersion;\n    HANDLE hClient = NULL;\n    WLAN_INTERFACE_INFO_LIST* ifList = NULL;\n    const char* error = NULL;\n\n    if(ffWlanOpenHandle(2, NULL, &curVersion, &hClient) != ERROR_SUCCESS)\n    {\n        error = \"WlanOpenHandle() failed\";\n        goto exit;\n    }\n\n    if(ffWlanEnumInterfaces(hClient, NULL, &ifList) != ERROR_SUCCESS)\n    {\n        error = \"WlanEnumInterfaces() failed\";\n        goto exit;\n    }\n\n    for(uint32_t index = 0; index < ifList->dwNumberOfItems; ++index)\n    {\n        WLAN_INTERFACE_INFO* ifInfo = (WLAN_INTERFACE_INFO*)&ifList->InterfaceInfo[index];\n\n        FFWifiResult* item = (FFWifiResult*)ffListAdd(result);\n        ffStrbufInitWS(&item->inf.description, ifInfo->strInterfaceDescription);\n        ffStrbufInit(&item->inf.status);\n        ffStrbufInit(&item->conn.status);\n        ffStrbufInit(&item->conn.ssid);\n        ffStrbufInit(&item->conn.bssid);\n        ffStrbufInit(&item->conn.protocol);\n        ffStrbufInit(&item->conn.security);\n        item->conn.signalQuality = -DBL_MAX;\n        item->conn.rxRate = -DBL_MAX;\n        item->conn.txRate = -DBL_MAX;\n        item->conn.channel = 0;\n        item->conn.frequency = 0;\n\n        convertIfStateToString(ifInfo->isState, &item->inf.status);\n\n        if(ifInfo->isState != wlan_interface_state_connected)\n            continue;\n\n        WLAN_CONNECTION_ATTRIBUTES* connInfo = NULL;\n        DWORD bufSize = sizeof(*connInfo);\n        WLAN_OPCODE_VALUE_TYPE opCode = wlan_opcode_value_type_query_only;\n\n        if(ffWlanQueryInterface(hClient,\n            &ifInfo->InterfaceGuid,\n            wlan_intf_opcode_current_connection,\n            NULL,\n            &bufSize,\n            (PVOID*)&connInfo,\n            &opCode) != ERROR_SUCCESS\n        ) continue;\n\n        convertIfStateToString(connInfo->isState, &item->conn.status);\n        ffStrbufAppendNS(&item->conn.ssid,\n            connInfo->wlanAssociationAttributes.dot11Ssid.uSSIDLength,\n            (const char *)connInfo->wlanAssociationAttributes.dot11Ssid.ucSSID);\n\n        for (size_t i = 0; i < sizeof(connInfo->wlanAssociationAttributes.dot11Bssid); i++)\n            ffStrbufAppendF(&item->conn.bssid, \"%.2X:\", connInfo->wlanAssociationAttributes.dot11Bssid[i]);\n        ffStrbufTrimRight(&item->conn.bssid, ':');\n\n        switch (connInfo->wlanAssociationAttributes.dot11PhyType)\n        {\n            case dot11_phy_type_fhss:\n                ffStrbufAppendS(&item->conn.protocol, \"802.11 (FHSS)\");\n                break;\n            case dot11_phy_type_dsss:\n                ffStrbufAppendS(&item->conn.protocol, \"802.11 (DSSS)\");\n                break;\n            case dot11_phy_type_irbaseband:\n                ffStrbufAppendS(&item->conn.protocol, \"802.11 (IR)\");\n                break;\n            case dot11_phy_type_ofdm:\n                ffStrbufAppendS(&item->conn.protocol, \"802.11a\");\n                break;\n            case dot11_phy_type_hrdsss:\n                ffStrbufAppendS(&item->conn.protocol, \"802.11b\");\n                break;\n            case dot11_phy_type_erp:\n                ffStrbufAppendS(&item->conn.protocol, \"802.11g\");\n                break;\n            case dot11_phy_type_ht:\n                ffStrbufAppendS(&item->conn.protocol, \"802.11n (Wi-Fi 4)\");\n                break;\n            case dot11_phy_type_vht:\n                ffStrbufAppendS(&item->conn.protocol, \"802.11ac (Wi-Fi 5)\");\n                break;\n            case dot11_phy_type_dmg:\n                ffStrbufAppendS(&item->conn.protocol, \"802.11ad (WiGig)\");\n                break;\n            case dot11_phy_type_he:\n                ffStrbufAppendS(&item->conn.protocol, \"802.11ax (Wi-Fi 6)\");\n                break;\n            case dot11_phy_type_eht:\n                ffStrbufAppendS(&item->conn.protocol, \"802.11be (Wi-Fi 7)\");\n                break;\n            default:\n                ffStrbufAppendF(&item->conn.protocol, \"Unknown (%u)\", (unsigned)connInfo->wlanAssociationAttributes.dot11PhyType);\n                break;\n        }\n\n        item->conn.signalQuality = connInfo->wlanAssociationAttributes.wlanSignalQuality;\n        item->conn.rxRate = connInfo->wlanAssociationAttributes.ulRxRate / 1000.;\n        item->conn.txRate = connInfo->wlanAssociationAttributes.ulTxRate / 1000.;\n\n        if(connInfo->wlanSecurityAttributes.bSecurityEnabled)\n        {\n            switch (connInfo->wlanSecurityAttributes.dot11AuthAlgorithm)\n            {\n                case DOT11_AUTH_ALGO_80211_OPEN:\n                    ffStrbufAppendS(&item->conn.security, \"802.11 Open\");\n                    break;\n                case DOT11_AUTH_ALGO_80211_SHARED_KEY:\n                    ffStrbufAppendS(&item->conn.security, \"802.11 Shared\");\n                    break;\n                case DOT11_AUTH_ALGO_WPA:\n                    ffStrbufAppendS(&item->conn.security, \"WPA\");\n                    break;\n                case DOT11_AUTH_ALGO_WPA_PSK:\n                    ffStrbufAppendS(&item->conn.security, \"WPA-PSK\");\n                    break;\n                case DOT11_AUTH_ALGO_WPA_NONE:\n                    ffStrbufAppendS(&item->conn.security, \"WPA-None\");\n                    break;\n                case DOT11_AUTH_ALGO_RSNA:\n                    ffStrbufAppendS(&item->conn.security, \"WPA2\");\n                    break;\n                case DOT11_AUTH_ALGO_RSNA_PSK:\n                    ffStrbufAppendS(&item->conn.security, \"WPA2-PSK\");\n                    break;\n                case DOT11_AUTH_ALGO_WPA3:\n                    ffStrbufAppendS(&item->conn.security, \"WPA3\");\n                    break;\n                case DOT11_AUTH_ALGO_WPA3_SAE:\n                    ffStrbufAppendS(&item->conn.security, \"WPA3-SAE\");\n                    break;\n                case 10 /* DOT11_AUTH_ALGO_OWE */:\n                    ffStrbufAppendS(&item->conn.security, \"OWE\");\n                    break;\n                case 11 /* DOT11_AUTH_ALGO_WPA3_ENT */:\n                    ffStrbufAppendS(&item->conn.security, \"WPA3-ENT\");\n                    break;\n                default:\n                    ffStrbufAppendF(&item->conn.security, \"Unknown (%u)\", (unsigned)connInfo->wlanSecurityAttributes.dot11AuthAlgorithm);\n                    break;\n            }\n            if(connInfo->wlanSecurityAttributes.bOneXEnabled)\n                ffStrbufAppendS(&item->conn.security, \" 802.11X\");\n        }\n        else\n            ffStrbufAppendS(&item->conn.security, \"Insecure\");\n\n        WLAN_BSS_LIST* bssList = NULL;\n        if (ffWlanGetNetworkBssList(hClient,\n            &ifInfo->InterfaceGuid,\n            &connInfo->wlanAssociationAttributes.dot11Ssid,\n            connInfo->wlanAssociationAttributes.dot11BssType,\n            connInfo->wlanSecurityAttributes.bSecurityEnabled,\n            NULL,\n            &bssList) == ERROR_SUCCESS && bssList->dwNumberOfItems > 0\n        ) {\n            item->conn.frequency = (uint16_t) (bssList->wlanBssEntries[0].ulChCenterFrequency / 1000);\n            ffWlanFreeMemory(bssList);\n        }\n\n        ffWlanFreeMemory(connInfo);\n\n        ULONG* channelNumber = 0;\n        bufSize = sizeof(*channelNumber);\n        if(ffWlanQueryInterface(hClient,\n            &ifInfo->InterfaceGuid,\n            wlan_intf_opcode_channel_number,\n            NULL,\n            &bufSize,\n            (PVOID*)&channelNumber,\n            &opCode) == ERROR_SUCCESS\n        ) {\n            item->conn.channel = (uint16_t) *channelNumber;\n            ffWlanFreeMemory(channelNumber);\n        }\n    }\n\nexit:\n    if(ifList) ffWlanFreeMemory(ifList);\n    if(hClient) ffWlanCloseHandle(hClient, NULL);\n    return error;\n}\n\n#pragma GCC diagnostic pop\n"
  },
  {
    "path": "src/detection/wm/wm.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/wm/wm.h\"\n\nconst char* ffDetectWMPlugin(FFstrbuf* pluginName);\nconst char* ffDetectWMVersion(const FFstrbuf* wmName, FFstrbuf* result, FFWMOptions* options);\n"
  },
  {
    "path": "src/detection/wm/wm_apple.m",
    "content": "#include \"wm.h\"\n\n#include \"common/sysctl.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/stringUtils.h\"\n\n#include <ctype.h>\n#include <libproc.h>\n#import <Foundation/Foundation.h>\n\nconst char* ffDetectWMPlugin(FFstrbuf* pluginName)\n{\n    int request[] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL};\n    u_int requestLength = ARRAY_SIZE(request);\n\n    size_t length = 0;\n    FF_AUTO_FREE struct kinfo_proc* processes = ffSysctlGetData(request, requestLength, &length);\n    if(processes == NULL)\n        return \"sysctl(CTL_KERN, KERN_PROC, KERN_PROC_ALL) failed\";\n    assert(length % sizeof(struct kinfo_proc) == 0);\n\n    for(size_t i = 0; i < length / sizeof(struct kinfo_proc); i++)\n    {\n        const struct kinfo_proc* proc = &processes[i];\n        if (proc->kp_eproc.e_ppid != 1) continue;\n\n        const char* comm = proc->kp_proc.p_comm;\n\n        if(\n            !ffStrEqualsIgnCase(comm, \"rectangle\") && // 28.6k\n            !ffStrEqualsIgnCase(comm, \"yabai\") && // 28.4k\n            !ffStrEqualsIgnCase(comm, \"aerospace\") && // 19.6k\n            !ffStrEqualsIgnCase(comm, \"amethyst\") && // 16k\n            !ffStrEqualsIgnCase(comm, \"glazewm\") && // 11.6k\n\n            #if 0\n            // Unmaintained\n            !ffStrEqualsIgnCase(comm, \"spectacle\") && // 13.6k\n            !ffStrEqualsIgnCase(comm, \"chunkwm\") && // repo deleted; was https://github.com/koekeishiya/chunkwm\n            !ffStrEqualsIgnCase(comm, \"kwm\") && // repo deleted; was https://github.com/koekeishiya/kwm\n            #endif\n            true\n        ) continue;\n\n        if (instance.config.general.detectVersion)\n        {\n            char buf[PROC_PIDPATHINFO_MAXSIZE];\n            int length = proc_pidpath(proc->kp_proc.p_pid, buf, ARRAY_SIZE(buf) - strlen(\"Info.plist\"));\n            if (length > 0)\n            {\n                char* lastSlash = strrchr(buf, '/');\n                if (lastSlash)\n                {\n                    *lastSlash = '\\0';\n                    if (ffStrEndsWith(buf, \".app/Contents/MacOS\"))\n                    {\n                        lastSlash -= strlen(\"MacOS\");\n                        strcpy(lastSlash, \"Info.plist\"); // X.app/Contents/Info.plist\n                        NSError* error;\n                        NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL fileURLWithPath:@(buf)]\n                                                        error:&error];\n                        if (dict)\n                        {\n                            NSString* name = dict[@\"CFBundleDisplayName\"] ?: dict[@\"CFBundleName\"];\n                            ffStrbufSetS(pluginName, name.UTF8String ?: comm);\n\n                            NSString* version = dict[@\"CFBundleShortVersionString\"];\n                            if (version)\n                            {\n                                ffStrbufAppendC(pluginName, ' ');\n                                ffStrbufAppendS(pluginName, version.UTF8String);\n                            }\n\n                            break;\n                        }\n                    }\n                }\n            }\n        }\n\n        ffStrbufAppendS(pluginName, comm);\n        pluginName->chars[0] = (char) toupper(pluginName->chars[0]);\n        break;\n    }\n\n    return NULL;\n}\n\nconst char* ffDetectWMVersion(const FFstrbuf* wmName, FFstrbuf* result, FF_MAYBE_UNUSED FFWMOptions* options)\n{\n    if (!wmName)\n        return \"No WM detected\";\n\n    if (ffStrbufEqualS(wmName, \"WindowServer\"))\n    {\n        NSError* error;\n        NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL fileURLWithPath:@\"/System/Library/PrivateFrameworks/SkyLight.framework/Resources/version.plist\" isDirectory:NO]\n                                           error:&error];\n        if (!dict)\n        {\n            dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL fileURLWithPath:@\"/System/Library/Frameworks/ApplicationServices.framework/Frameworks/CoreGraphics.framework/Resources/version.plist\" isDirectory:NO]\n                                           error:&error];\n        }\n\n        if (dict)\n            ffStrbufSetS(result, ((NSString*) dict[@\"CFBundleShortVersionString\"]).UTF8String);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/detection/wm/wm_linux.c",
    "content": "#include \"wm.h\"\n\n#include \"common/processing.h\"\n#include \"common/io.h\"\n#include \"common/binary.h\"\n#include \"common/path.h\"\n#include \"common/stringUtils.h\"\n#include \"common/debug.h\"\n#include \"detection/displayserver/displayserver.h\"\n\nconst char* ffDetectWMPlugin(FF_MAYBE_UNUSED FFstrbuf* pluginName)\n{\n    return \"Not supported on this platform\";\n}\n\nstatic bool extractCommonWmVersion(const char* line, FF_MAYBE_UNUSED uint32_t len, void *userdata)\n{\n    int count = 0;\n    sscanf(line, \"%*d.%*d.%*d%n\", &count);\n    if (count == 0) return true;\n\n    ffStrbufSetNS((FFstrbuf*) userdata, len, line);\n    return false;\n}\n\n#if !__ANDROID__\nstatic bool extractHyprlandVersion(const char* line, uint32_t len, void *userdata)\n{\n    if (line[0] != 'v') return true;\n    ++line; --len;\n    int count = 0;\n    sscanf(line, \"%*d.%*d.%*d%n\", &count);\n    if (count == 0) return true;\n\n    ffStrbufSetNS((FFstrbuf*) userdata, len, line);\n    return false;\n}\n\nstatic const char* getHyprland(FFstrbuf* result)\n{\n    FF_DEBUG(\"Detecting Hyprland version\");\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n\n    FF_DEBUG(\"Checking for \" FASTFETCH_TARGET_DIR_USR \"/include/hyprland/src/version.h\" \" file\");\n    if (ffReadFileBuffer(FASTFETCH_TARGET_DIR_USR \"/include/hyprland/src/version.h\", result))\n    {\n        FF_DEBUG(\"Found version.h file, extracting version\");\n        if (ffStrbufSubstrAfterFirstS(result, \"\\n#define GIT_TAG \"))\n        {\n            ffStrbufSubstrAfterFirstC(result, '\"');\n            ffStrbufSubstrBeforeFirstC(result, '\"');\n            ffStrbufTrimLeft(result, 'v');\n            FF_DEBUG(\"Extracted version from version.h: %s\", result->chars);\n            return NULL;\n        }\n        FF_DEBUG(\"Failed to extract version from version.h\");\n        ffStrbufClear(result);\n    }\n    else\n    {\n        FF_DEBUG(\"version.h file not found, trying Hyprland executable\");\n    }\n\n    const char* error = ffFindExecutableInPath(\"Hyprland\", &buffer);\n    if (error) {\n        FF_DEBUG(\"Error finding Hyprland executable: %s\", error);\n        return \"Failed to find Hyprland executable path\";\n    }\n    FF_DEBUG(\"Found Hyprland executable at: %s\", buffer.chars);\n\n    ffBinaryExtractStrings(buffer.chars, extractHyprlandVersion, result, (uint32_t) strlen(\"v0.0.0\"));\n    if (result->length > 0) {\n        FF_DEBUG(\"Extracted version from binary strings: %s\", result->chars);\n        return NULL;\n    }\n    FF_DEBUG(\"Failed to extract version from binary strings, trying --version option\");\n\n    if (ffProcessAppendStdOut(result, (char* const[]){\n        buffer.chars,\n        \"--version\",\n        NULL\n    }) == NULL)\n    {\n        // Hyprland 0.48.1 built from branch  at commit 29e2e59...\n        // Date: ...\n        // Tag: v0.48.1, commits: 5937\n        // ...\n\n        FF_DEBUG(\"Raw version output: %s\", result->chars);\n        // Use tag if available\n        if (ffStrbufSubstrAfterFirstS(result, \"\\nTag: v\"))\n        {\n            ffStrbufSubstrBeforeFirstC(result, ',');\n            FF_DEBUG(\"Extracted version from Tag: %s\", result->chars);\n        }\n        else\n        {\n            ffStrbufSubstrAfterFirstC(result, ' ');\n            ffStrbufSubstrBeforeFirstC(result, ' ');\n            FF_DEBUG(\"Extracted version from output: %s\", result->chars);\n        }\n        return NULL;\n    }\n    FF_DEBUG(\"Failed to run Hyprland --version command\");\n\n    return \"Failed to run command `Hyprland --version`\";\n}\n\nstatic bool extractSwayVersion(const char* line, FF_MAYBE_UNUSED uint32_t len, void *userdata)\n{\n    if (!ffStrStartsWith(line, \"sway version \")) return true;\n\n    FFstrbuf* result = (FFstrbuf*) userdata;\n    ffStrbufSetNS(result, len - (uint32_t) strlen(\"sway version \"), line + strlen(\"sway version \"));\n    ffStrbufTrimRightSpace(result);\n    return false;\n}\n\nstatic const char* getSway(FFstrbuf* result)\n{\n    FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();\n    const char* error = ffFindExecutableInPath(\"sway\", &path);\n    if (error) return \"Failed to find sway executable path\";\n\n    ffBinaryExtractStrings(path.chars, extractSwayVersion, result, (uint32_t) strlen(\"v0.0.0\"));\n    if (result->length > 0) return NULL;\n\n    if (ffProcessAppendStdOut(result, (char* const[]){\n        path.chars,\n        \"--version\",\n        NULL\n    }) == NULL)\n    { // sway version 1.10\n        ffStrbufSubstrAfterLastC(result, ' ');\n        ffStrbufTrimRightSpace(result);\n        return NULL;\n    }\n\n    return \"Failed to run command `sway --version`\";\n}\n\nstatic const char* getLabwc(FFstrbuf* result)\n{\n    FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();\n    const char* error = ffFindExecutableInPath(\"labwc\", &path);\n    if (error) return \"Failed to find labwc executable path\";\n\n    ffBinaryExtractStrings(path.chars, extractCommonWmVersion, result, (uint32_t) strlen(\"0.0.0\"));\n    if (result->length > 0) return NULL;\n\n    if (ffProcessAppendStdOut(result, (char* const[]){\n        path.chars,\n        \"--version\",\n        NULL\n    }) == NULL)\n    { // labwc 0.9.0 (+xwayland +nls +rsvg +libsfdo)\n        ffStrbufSubstrAfterFirstC(result, ' ');\n        ffStrbufSubstrBeforeFirstC(result, ' ');\n        return NULL;\n    }\n\n    return \"Failed to run command `labwc --version`\";\n}\n\nstatic const char* getNiri(FFstrbuf* result)\n{\n    if (ffProcessAppendStdOut(result, (char* const[]){\n        \"niri\",\n        \"--version\",\n        NULL\n    }) == NULL)\n    { // niri 25.11 (commit b35bcae)\n        ffStrbufSubstrAfterFirstC(result, ' ');\n        ffStrbufSubstrBeforeLastC(result, '(');\n        ffStrbufTrimRightSpace(result);\n        return NULL;\n    }\n\n    return \"Failed to run command `niri --version`\";\n}\n\n#ifdef __linux__\nstatic const char* getWslg(FFstrbuf* result)\n{\n    if (!ffAppendFileBuffer(\"/mnt/wslg/versions.txt\", result))\n        return \"Failed to read /mnt/wslg/versions.txt\";\n\n    if (!ffStrbufStartsWithS(result, \"WSLg \"))\n        return \"Failed to find WSLg version\";\n\n    ffStrbufSubstrBeforeFirstC(result, '\\n');\n    ffStrbufSubstrBeforeFirstC(result, '+');\n    ffStrbufSubstrAfterFirstC(result, ':');\n    ffStrbufTrimLeft(result, ' ');\n    return NULL;\n}\n#endif\n\n#endif // !__ANDROID__\n\nstatic bool extractI3Version(const char* line, FF_MAYBE_UNUSED uint32_t len, void *userdata)\n{\n    int count = 0;\n    sscanf(line, \"%*d.%*d%n\", &count);\n    if (count == 0) return true;\n\n    ffStrbufSetNS((FFstrbuf*) userdata, len, line);\n    return false;\n}\n\nstatic const char* getI3(FFstrbuf* result)\n{\n    FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();\n    const char* error = ffFindExecutableInPath(\"i3\", &path);\n    if (error) return \"Failed to find i3 executable path\";\n\n    ffBinaryExtractStrings(path.chars, extractI3Version, result, (uint32_t) strlen(\"0.0\"));\n    if (result->length > 0) return NULL;\n\n    if (ffProcessAppendStdOut(result, (char* const[]){\n        path.chars,\n        \"--version\",\n        NULL\n    }) == NULL)\n    { // i3 version 1.10 C 2009...\n        ffStrbufSubstrAfterFirstS(result, \"version \");\n        ffStrbufSubstrBeforeFirstC(result, ' ');\n        return NULL;\n    }\n\n    return \"Failed to run command `i3 --version`\";\n}\n\nstatic const char* getCtwm(FFstrbuf* result)\n{\n    FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();\n    const char* error = ffFindExecutableInPath(\"ctwm\", &path);\n    if (error) return \"Failed to find ctwm executable path\";\n\n    ffBinaryExtractStrings(path.chars, extractCommonWmVersion, result, (uint32_t) strlen(\"0.0.0\"));\n    if (result->length > 0) return NULL;\n\n    if (ffProcessAppendStdOut(result, (char* const[]){\n        path.chars,\n        \"--version\",\n        NULL\n    }) == NULL)\n    { // ctwm version 4.0.1\\n...\n        ffStrbufSubstrBeforeFirstC(result, '\\n');\n        ffStrbufSubstrAfterLastC(result, ' ');\n        return NULL;\n    }\n\n    return \"Failed to run command `ctwm --version`\";\n}\n\nstatic const char* getFvwm(FFstrbuf* result)\n{\n    FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();\n    const char* error = ffFindExecutableInPath(\"fvwm\", &path);\n    if (error) return \"Failed to find fvwm executable path\";\n\n    ffBinaryExtractStrings(path.chars, extractCommonWmVersion, result, (uint32_t) strlen(\"0.0.0\"));\n    if (result->length > 0) return NULL;\n\n    if (ffProcessAppendStdOut(result, (char* const[]){\n        path.chars,\n        \"-version\",\n        NULL\n    }) == NULL)\n    { // [FVWM][main]: fvwm Version 2.2.5\\n...\n        ffStrbufSubstrBeforeFirstC(result, '\\n');\n        ffStrbufSubstrAfterLastC(result, ' ');\n        return NULL;\n    }\n\n    return \"Failed to run command `fvwm -version`\";\n}\n\nstatic const char* getOpenbox(FFstrbuf* result)\n{\n    FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();\n    const char* error = ffFindExecutableInPath(\"openbox\", &path);\n    if (error) return \"Failed to find openbox executable path\";\n\n    ffBinaryExtractStrings(path.chars, extractCommonWmVersion, result, (uint32_t) strlen(\"0.0.0\"));\n    if (result->length > 0) return NULL;\n\n    if (ffProcessAppendStdOut(result, (char* const[]){\n        path.chars,\n        \"--version\",\n        NULL\n    }) == NULL)\n    { // Openbox 3.6.1\\n...\n        ffStrbufSubstrBeforeFirstC(result, '\\n');\n        ffStrbufSubstrAfterLastC(result, ' ');\n        return NULL;\n    }\n\n    return \"Failed to run command `openbox --version`\";\n}\n\nconst char* ffDetectWMVersion(const FFstrbuf* wmName, FFstrbuf* result, FF_MAYBE_UNUSED FFWMOptions* options)\n{\n    if (!wmName)\n        return \"No WM detected\";\n\n    #if !__ANDROID__\n    // Wayland compositors\n    if (ffStrbufIgnCaseEqualS(wmName, \"Hyprland\"))\n        return getHyprland(result);\n\n    if (ffStrbufEqualS(wmName, \"sway\"))\n        return getSway(result);\n\n    if (ffStrbufEqualS(wmName, \"labwc\"))\n        return getLabwc(result);\n\n    if (ffStrbufEqualS(wmName, \"niri\"))\n        return getNiri(result);\n\n    #if __linux__\n    if (ffStrbufEqualS(wmName, \"WSLg\"))\n        return getWslg(result);\n    #endif\n    #endif\n\n    // X11 WMs\n    if (ffStrbufEqualS(wmName, \"i3\"))\n        return getI3(result);\n\n    if (ffStrbufEqualS(wmName, \"ctwm\"))\n        return getCtwm(result);\n\n    if (ffStrbufEqualS(wmName, \"fvwm\"))\n        return getFvwm(result);\n\n    if (ffStrbufEqualS(wmName, \"Openbox\"))\n        return getOpenbox(result);\n\n    return \"Unsupported WM\";\n}\n"
  },
  {
    "path": "src/detection/wm/wm_nosupport.c",
    "content": "#include \"wm.h\"\n\nconst char* ffDetectWMPlugin(FF_MAYBE_UNUSED FFstrbuf* pluginName)\n{\n    return \"Not supported on this platform\";\n}\n\nconst char* ffDetectWMVersion(FF_MAYBE_UNUSED const FFstrbuf* wmName, FF_MAYBE_UNUSED FFstrbuf* result, FF_MAYBE_UNUSED FFWMOptions* options)\n{\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/wm/wm_windows.c",
    "content": "#include \"wm.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/io.h\"\n#include \"common/library.h\"\n#include \"common/processing.h\"\n#include \"common/windows/nt.h\"\n#include \"common/windows/unicode.h\"\n#include \"common/windows/version.h\"\n\n#include <stdalign.h>\n#include <windows.h>\n#include <ntstatus.h>\n#include <shlobj.h>\n#include <softpub.h>\n\ntypedef enum {\n    FF_PROCESS_TYPE_NONE,\n    FF_PROCESS_TYPE_SIGNED = 1 << 0,\n    FF_PROCESS_TYPE_WINDOWS_STORE = 1 << 1,\n    FF_PROCESS_TYPE_GUI = 1 << 2,\n    FF_PROCESS_TYPE_CUI = 1 << 3,\n} FFProcessType;\n\nstatic bool verifySignature(const wchar_t* filePath)\n{\n    FF_LIBRARY_LOAD(wintrust, true, \"wintrust\" FF_LIBRARY_EXTENSION, -1)\n    FF_LIBRARY_LOAD_SYMBOL(wintrust, WinVerifyTrustEx, true)\n\n    WINTRUST_FILE_INFO fileInfo = {\n        .cbStruct = sizeof(fileInfo),\n        .pcwszFilePath = filePath,\n    };\n\n    GUID actionID = WINTRUST_ACTION_GENERIC_VERIFY_V2;\n\n    WINTRUST_DATA trustData = {\n        .cbStruct = sizeof(trustData),\n        .dwUIChoice = WTD_UI_NONE,\n        .fdwRevocationChecks = WTD_REVOKE_NONE,\n        .dwUnionChoice = WTD_CHOICE_FILE,\n        .pFile = &fileInfo,\n        .dwStateAction = WTD_STATEACTION_VERIFY,\n        .dwProvFlags = WTD_SAFER_FLAG,\n    };\n\n    LONG status = ffWinVerifyTrustEx(NULL, &actionID, &trustData);\n    trustData.dwStateAction = WTD_STATEACTION_CLOSE;\n    ffWinVerifyTrustEx(NULL, &actionID, &trustData);\n\n    return status == ERROR_SUCCESS;\n}\n\nstatic bool isProcessTrusted(DWORD processId, FFProcessType processType, UNICODE_STRING* buffer, size_t bufSize)\n{\n    FF_AUTO_CLOSE_FD HANDLE hProcess = NULL;\n    if (!NT_SUCCESS(NtOpenProcess(&hProcess, PROCESS_QUERY_LIMITED_INFORMATION, &(OBJECT_ATTRIBUTES) {\n        .Length = sizeof(OBJECT_ATTRIBUTES),\n    }, &(CLIENT_ID) { .UniqueProcess = (HANDLE)(uintptr_t) processId })))\n        return false;\n\n    ULONG size;\n    if(!NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessImageFileNameWin32, buffer, (ULONG) bufSize, &size)) ||\n        buffer->Length == 0) return false;\n    assert(buffer->MaximumLength >= buffer->Length + 2); // NULL terminated\n\n    if (processType & FF_PROCESS_TYPE_WINDOWS_STORE)\n    {\n        static wchar_t windowsAppsPath[MAX_PATH];\n        static uint32_t windowsAppsPathLen;\n        if (windowsAppsPathLen == 0)\n        {\n            PWSTR pPath = NULL;\n            if(SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_ProgramFiles, KF_FLAG_DEFAULT, NULL, &pPath)))\n            {\n                windowsAppsPathLen = (uint32_t) wcslen(pPath);\n                memcpy(windowsAppsPath, pPath, windowsAppsPathLen * sizeof(wchar_t));\n                memcpy(windowsAppsPath + windowsAppsPathLen, L\"\\\\WindowsApps\\\\\", sizeof(L\"\\\\WindowsApps\\\\\"));\n                windowsAppsPathLen += strlen(\"\\\\WindowsApps\\\\\");\n            }\n            else\n            {\n                windowsAppsPathLen = -1u;\n            }\n            CoTaskMemFree(pPath);\n        }\n        if (windowsAppsPathLen != -1u &&\n            (buffer->Length <= windowsAppsPathLen * sizeof(wchar_t) || // Path is too short to be in WindowsApps\n            _wcsnicmp(buffer->Buffer, windowsAppsPath, windowsAppsPathLen) != 0) // Path does not start with WindowsApps\n        ) return false;\n    }\n\n    if (processType & FF_PROCESS_TYPE_SIGNED)\n    {\n        if (!verifySignature(buffer->Buffer)) return false;\n    }\n\n    if (processType & (FF_PROCESS_TYPE_GUI | FF_PROCESS_TYPE_CUI))\n    {\n        SECTION_IMAGE_INFORMATION info = {};\n        if(!NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessImageInformation, &info, sizeof(info), &size)) ||\n            size != sizeof(info)) return false;\n\n        if ((processType & FF_PROCESS_TYPE_GUI) && info.SubSystemType != IMAGE_SUBSYSTEM_WINDOWS_GUI)\n            return false;\n        if ((processType & FF_PROCESS_TYPE_CUI) && info.SubSystemType != IMAGE_SUBSYSTEM_WINDOWS_CUI)\n            return false;\n    }\n\n    return true;\n}\n\n#define ffStrEqualNWS(str, compareTo) (_wcsnicmp(str, L ## compareTo, sizeof(compareTo) - 1) == 0)\n\nconst char* ffDetectWMPlugin(FFstrbuf* pluginName)\n{\n    alignas(UNICODE_STRING) uint8_t buffer[4096];\n    UNICODE_STRING* filePath = (UNICODE_STRING*) buffer;\n    SYSTEM_PROCESS_INFORMATION* FF_AUTO_FREE pstart = NULL;\n\n    // Multiple attempts in case processes change while\n    // we are in the middle of querying them.\n    ULONG size = 0;\n    for (int attempts = 0;; ++attempts)\n    {\n        if (size)\n        {\n            pstart = (SYSTEM_PROCESS_INFORMATION*)realloc(pstart, size);\n            assert(pstart);\n        }\n        NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, pstart, size, &size);\n        if(NT_SUCCESS(status))\n            break;\n        else if(status == STATUS_INFO_LENGTH_MISMATCH && attempts < 4)\n            size += sizeof(SYSTEM_PROCESS_INFORMATION) * 5;\n        else\n            return \"NtQuerySystemInformation(SystemProcessInformation) failed\";\n    }\n\n    for (SYSTEM_PROCESS_INFORMATION* ptr = pstart; ; ptr = (SYSTEM_PROCESS_INFORMATION*)((uint8_t*)ptr + ptr->NextEntryOffset))\n    {\n        assert(ptr->ImageName.Length == 0 || ptr->ImageName.MaximumLength >= ptr->ImageName.Length + 2); // NULL terminated\n        if (ptr->ImageName.Length == strlen(\"FancyWM-GUI.exe\") * sizeof(wchar_t) &&\n            ffStrEqualNWS(ptr->ImageName.Buffer, \"FancyWM-GUI.exe\") &&\n            isProcessTrusted((DWORD) (uintptr_t) ptr->UniqueProcessId, FF_PROCESS_TYPE_WINDOWS_STORE | FF_PROCESS_TYPE_GUI, filePath, sizeof(buffer))\n        ) {\n            if (instance.config.general.detectVersion && ffGetFileVersion(filePath->Buffer, NULL, pluginName))\n                ffStrbufPrependS(pluginName, \"FancyWM \");\n            else\n                ffStrbufSetStatic(pluginName, \"FancyWM\");\n            break;\n        }\n        else if (ptr->ImageName.Length == strlen(\"glazewm-watcher.exe\") * sizeof(wchar_t) &&\n            ffStrEqualNWS(ptr->ImageName.Buffer, \"glazewm-watcher.exe\") &&\n            isProcessTrusted((DWORD) (uintptr_t) ptr->UniqueProcessId, FF_PROCESS_TYPE_SIGNED | FF_PROCESS_TYPE_GUI, filePath, sizeof(buffer))\n        ) {\n            if (instance.config.general.detectVersion && ffGetFileVersion(filePath->Buffer, NULL, pluginName))\n                ffStrbufPrependS(pluginName, \"GlazeWM \");\n            else\n                ffStrbufSetStatic(pluginName, \"GlazeWM\");\n            break;\n        }\n        else if (ptr->ImageName.Length == strlen(\"komorebi.exe\") * sizeof(wchar_t) &&\n            ffStrEqualNWS(ptr->ImageName.Buffer, \"komorebi.exe\") &&\n            isProcessTrusted((DWORD) (uintptr_t) ptr->UniqueProcessId, FF_PROCESS_TYPE_CUI, filePath, sizeof(buffer))\n        ) {\n            if (instance.config.general.detectVersion)\n            {\n                FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateNWS(filePath->Length / sizeof(wchar_t), filePath->Buffer);\n                if (ffProcessAppendStdOut(pluginName, (char *const[]) {\n                    path.chars,\n                    \"--version\",\n                    NULL,\n                }) == NULL)\n                    ffStrbufSubstrBeforeFirstC(pluginName, '\\n');\n            }\n            if (pluginName->length == 0)\n                ffStrbufSetStatic(pluginName, \"Komorebi\");\n            break;\n        }\n\n        if (ptr->NextEntryOffset == 0) break;\n    }\n\n    return NULL;\n}\n\nconst char* ffDetectWMVersion(const FFstrbuf* wmName, FFstrbuf* result, FF_MAYBE_UNUSED FFWMOptions* options)\n{\n    if (!wmName)\n        return \"No WM detected\";\n\n    if (ffStrbufEqualS(wmName, \"dwm.exe\"))\n    {\n        PWSTR pPath = NULL;\n        if(SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_System, KF_FLAG_DEFAULT, NULL, &pPath)))\n        {\n            wchar_t fullPath[MAX_PATH];\n            wcscpy(fullPath, pPath);\n            wcscat(fullPath, L\"\\\\dwm.exe\");\n            ffGetFileVersion(fullPath, NULL, result);\n        }\n        CoTaskMemFree(pPath);\n        return NULL;\n    }\n    return \"Not supported on this platform\";\n}\n"
  },
  {
    "path": "src/detection/wmtheme/wmtheme.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\nbool ffDetectWmTheme(FFstrbuf* themeOrError);\n"
  },
  {
    "path": "src/detection/wmtheme/wmtheme_apple.m",
    "content": "#include \"fastfetch.h\"\n#include \"wmtheme.h\"\n\n#import <Foundation/Foundation.h>\n\nbool ffDetectWmTheme(FFstrbuf* themeOrError)\n{\n    NSError* error;\n    NSString* fileName = [NSString stringWithFormat:@\"file://%s/Library/Preferences/.GlobalPreferences.plist\", instance.state.platform.homeDir.chars];\n    NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName]\n                                       error:&error];\n    if(error)\n    {\n        ffStrbufAppendS(themeOrError, error.localizedDescription.UTF8String);\n        return false;\n    }\n\n    NSNumber* wmThemeColor = dict[@\"AppleAccentColor\"];\n    if(!wmThemeColor)\n        ffStrbufAppendS(themeOrError, \"Multicolor\");\n    else\n    {\n        switch(wmThemeColor.intValue)\n        {\n            case -1: ffStrbufAppendS(themeOrError, \"Graphite\"); break;\n            case 0: ffStrbufAppendS(themeOrError, \"Red\"); break;\n            case 1: ffStrbufAppendS(themeOrError, \"Orange\"); break;\n            case 2: ffStrbufAppendS(themeOrError, \"Yellow\"); break;\n            case 3: ffStrbufAppendS(themeOrError, \"Green\"); break;\n            case 4: ffStrbufAppendS(themeOrError, \"Blue\"); break;\n            case 5: ffStrbufAppendS(themeOrError, \"Purple\"); break;\n            case 6: ffStrbufAppendS(themeOrError, \"Pink\"); break;\n            default: ffStrbufAppendS(themeOrError, \"Unknown\"); break;\n        }\n    }\n\n    NSString* wmTheme = dict[@\"AppleInterfaceStyle\"];\n    ffStrbufAppendF(themeOrError, \" (%s)\", wmTheme ? wmTheme.UTF8String : \"Light\");\n    return true;\n}\n"
  },
  {
    "path": "src/detection/wmtheme/wmtheme_linux.c",
    "content": "#include \"wmtheme.h\"\n#include \"common/io.h\"\n#include \"common/properties.h\"\n#include \"common/parsing.h\"\n#include \"common/settings.h\"\n#include \"common/stringUtils.h\"\n#include \"common/mallocHelper.h\"\n#include \"detection/gtk_qt/gtk_qt.h\"\n#include \"detection/displayserver/displayserver.h\"\n\nstatic bool detectWMThemeFromConfigFile(const char* configFile, const char* themeRegex, const char* defaultValue, FFstrbuf* themeOrError)\n{\n    if(!ffParsePropFileConfig(configFile, themeRegex, themeOrError))\n    {\n        ffStrbufAppendF(themeOrError, \"Config file %s doesn't exist\", configFile);\n        return false;\n    }\n\n    if(themeOrError->length == 0)\n    {\n        if(defaultValue == NULL)\n        {\n            ffStrbufAppendF(themeOrError, \"Couldn't find WM theme in %s\", configFile);\n            return false;\n        }\n\n        ffStrbufAppendS(themeOrError, defaultValue);\n        return true;\n    }\n\n    // Remove Plasma-generated prefixes\n    uint32_t idx = 0;\n\n    idx = ffStrbufFirstIndexS(themeOrError, \"qml_\");\n    if(idx != themeOrError->length)\n        ffStrbufSubstrAfter(themeOrError, idx + 3);\n\n    idx = ffStrbufFirstIndexS(themeOrError, \"svg__\");\n    if(idx != themeOrError->length)\n        ffStrbufSubstrAfter(themeOrError, idx + 4);\n\n    return true;\n}\n\nstatic bool detectWMThemeFromSettings(const char* dconfKey, const char* gsettingsSchemaName, const char* gsettingsPath, const char* gsettingsKey, FFstrbuf* themeOrError)\n{\n    const char* theme = ffSettingsGetGnome(dconfKey, gsettingsSchemaName, gsettingsPath, gsettingsKey, FF_VARIANT_TYPE_STRING).strValue;\n\n    if(!ffStrSet(theme))\n    {\n        ffStrbufAppendS(themeOrError, \"Couldn't find WM theme in DConf or GSettings\");\n        return false;\n    }\n\n    ffStrbufAppendS(themeOrError, theme);\n    return true;\n}\n\nstatic bool detectGTKThemeAsWMTheme(FFstrbuf* themeOrError)\n{\n    const FFGTKResult* gtk = ffDetectGTK4();\n    if(gtk->theme.length > 0)\n        goto ok;\n\n    gtk = ffDetectGTK3();\n    if(gtk->theme.length > 0)\n        goto ok;\n\n    gtk = ffDetectGTK2();\n    if(gtk->theme.length > 0)\n        goto ok;\n\n    ffStrbufAppendS(themeOrError, \"Couldn't detect GTK4/3/2 theme\");\n    return false;\n\nok:\n    ffStrbufAppend(themeOrError, &gtk->theme);\n    return true;\n}\n\nstatic bool detectMutter(FFstrbuf* themeOrError)\n{\n    const char* theme = ffSettingsGetGnome(\"/org/gnome/shell/extensions/user-theme/name\", \"org.gnome.shell.extensions.user-theme\", NULL, \"name\", FF_VARIANT_TYPE_STRING).strValue;\n    if(ffStrSet(theme))\n    {\n        ffStrbufAppendS(themeOrError, theme);\n        return true;\n    }\n\n    return detectGTKThemeAsWMTheme(themeOrError);\n}\n\nstatic bool detectMuffin(FFstrbuf* themeOrError)\n{\n    FF_AUTO_FREE const char* name = ffSettingsGetGnome(\"/org/cinnamon/theme/name\", \"org.cinnamon.theme\", NULL, \"name\", FF_VARIANT_TYPE_STRING).strValue;\n    FF_AUTO_FREE const char* theme = ffSettingsGetGnome(\"/org/cinnamon/desktop/wm/preferences/theme\", \"org.cinnamon.desktop.wm.preferences\", NULL, \"theme\", FF_VARIANT_TYPE_STRING).strValue;\n\n    if(name == NULL && theme == NULL)\n    {\n        ffStrbufAppendS(themeOrError, \"Couldn't find muffin theme in GSettings / DConf\");\n        return false;\n    }\n\n    if(name == NULL)\n    {\n        ffStrbufAppendS(themeOrError, theme);\n        return true;\n    }\n\n    if(theme == NULL)\n    {\n        ffStrbufAppendS(themeOrError, name);\n        return true;\n    }\n\n    ffStrbufAppendF(themeOrError, \"%s (%s)\", name, theme);\n    return true;\n}\n\nstatic bool detectXFWM4(FFstrbuf* themeOrError)\n{\n    const char* theme = ffSettingsGetXFConf(\"xfwm4\", \"/general/theme\", FF_VARIANT_TYPE_STRING).strValue;\n\n    if(theme == NULL)\n    {\n        ffStrbufAppendS(themeOrError, \"Couldn't find xfwm4::/general/theme in XFConf\");\n        return false;\n    }\n\n    ffStrbufAppendS(themeOrError, theme);\n    return true;\n}\n\nstatic bool detectOpenbox(const FFstrbuf* dePrettyName, FFstrbuf* themeOrError)\n{\n    FF_STRBUF_AUTO_DESTROY absolutePath = ffStrbufCreateA(64);\n    const char *configFileSubpath = \"openbox/rc.xml\";\n    if (ffStrbufIgnCaseEqualS(dePrettyName, \"LXQt\"))\n        configFileSubpath = \"openbox/lxqt-rc.xml\";\n    else if (ffStrbufIgnCaseEqualS(dePrettyName, \"LXDE\"))\n        configFileSubpath = \"openbox/lxde-rc.xml\";\n\n    if (!ffSearchUserConfigFile(&instance.state.platform.configDirs, configFileSubpath, &absolutePath))\n    {\n        ffStrbufAppendF(themeOrError, \"Couldn't find config file \\\"%s\\\"\", configFileSubpath);\n        return false;\n    }\n\n    FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate();\n    if (!ffReadFileBuffer(absolutePath.chars, &content))\n    {\n        ffStrbufAppendF(themeOrError, \"Couldn't read \\\"%s\\\"\", absolutePath.chars);\n        return false;\n    }\n\n    const char *themeStart = strstr(content.chars, \"<theme>\");\n    if (themeStart == NULL)\n        goto theme_not_found;\n\n    const char *themeEnd = strstr(themeStart, \"</theme>\");\n    if (__builtin_expect(themeEnd == NULL, false)) // very rare case\n        goto theme_not_found;\n\n    const char *nameStart = strstr(themeStart, \"<name>\");\n    if (nameStart == NULL)\n        goto name_not_found;\n\n    const char *nameEnd = strstr(nameStart, \"</name>\");\n    if (nameEnd == NULL || nameEnd > themeEnd) // (nameEnd > themeEnd) means name is not a theme's child\n        goto name_not_found;\n\n    nameStart += strlen(\"<name>\");\n    ffStrbufAppendNS(themeOrError, (uint32_t)(nameEnd - nameStart), nameStart);\n    ffStrbufTrim(themeOrError, ' ');\n\n    if(themeOrError->length == 0)\n        goto name_not_found;\n\n    return true;\n\ntheme_not_found:\n    ffStrbufAppendF(themeOrError, \"Couldn't find theme node in \\\"%s\\\"\", absolutePath.chars);\n    return false;\n\nname_not_found:\n    ffStrbufAppendF(themeOrError, \"Couldn't find theme name in \\\"%s\\\"\", absolutePath.chars);\n    return false;\n}\n\nbool ffDetectWmTheme(FFstrbuf* themeOrError)\n{\n    const FFDisplayServerResult* wm = ffConnectDisplayServer();\n\n    if(wm->wmPrettyName.length == 0)\n    {\n        ffStrbufAppendS(themeOrError, \"WM Theme needs successful WM detection\");\n        return false;\n    }\n\n    if(ffStrbufIgnCaseEqualS(&wm->wmPrettyName, FF_WM_PRETTY_KWIN))\n        return detectWMThemeFromConfigFile(\"kwinrc\", \"theme =\", \"Breeze\", themeOrError);\n\n    if(\n        ffStrbufIgnCaseEqualS(&wm->wmPrettyName, FF_WM_PRETTY_XFWM4) ||\n        (ffStrbufIgnCaseEqualS(&wm->wmPrettyName, \"labwc\") && ffStrbufIgnCaseEqualS(&wm->dePrettyName, FF_DE_PRETTY_XFCE4))\n    )\n        return detectXFWM4(themeOrError);\n\n    if(ffStrbufIgnCaseEqualS(&wm->wmPrettyName, FF_WM_PRETTY_MUTTER))\n    {\n        if(\n            ffStrbufIgnCaseEqualS(&wm->dePrettyName, FF_DE_PRETTY_GNOME) ||\n            ffStrbufIgnCaseEqualS(&wm->dePrettyName, FF_DE_PRETTY_GNOME_CLASSIC)\n        )\n            return detectMutter(themeOrError);\n        else\n            return detectGTKThemeAsWMTheme(themeOrError);\n    }\n\n    if(ffStrbufIgnCaseEqualS(&wm->wmPrettyName, FF_WM_PRETTY_MUFFIN))\n        return detectMuffin(themeOrError);\n\n    if(ffStrbufIgnCaseEqualS(&wm->wmPrettyName, FF_WM_PRETTY_MARCO))\n        return detectWMThemeFromSettings(\"/org/mate/Marco/general/theme\", \"org.mate.Marco.general\", NULL, \"theme\", themeOrError);\n\n    if(ffStrbufIgnCaseEqualS(&wm->wmPrettyName, FF_WM_PRETTY_OPENBOX))\n        return detectOpenbox(&wm->dePrettyName, themeOrError);\n\n    ffStrbufAppendS(themeOrError, \"Unknown WM: \");\n    ffStrbufAppend(themeOrError, &wm->wmPrettyName);\n    return false;\n}\n"
  },
  {
    "path": "src/detection/wmtheme/wmtheme_nosupport.c",
    "content": "#include \"fastfetch.h\"\n#include \"wmtheme.h\"\n\nbool ffDetectWmTheme(FFstrbuf* themeOrError)\n{\n    ffStrbufAppendS(themeOrError, \"Not supported on this platform\");\n    return false;\n}\n"
  },
  {
    "path": "src/detection/wmtheme/wmtheme_windows.c",
    "content": "#include \"fastfetch.h\"\n#include \"wmtheme.h\"\n#include \"common/windows/registry.h\"\n\nconst char* colorHexToString(DWORD hex)\n{\n    switch(hex)\n    {\n        case 0x696cc3: return \"Yellow gold\";\n        case 0xff8c00: return \"Gold\";\n        case 0xf7630c: return \"Orange bright\";\n        case 0xca5010: return \"Orange dark\";\n        case 0xda3b01: return \"Rust\";\n        case 0xef6950: return \"Pale rust\";\n        case 0xd13438: return \"Brick red\";\n        case 0xff4343: return \"Mod red\";\n        case 0xe74856: return \"Pale red\";\n        case 0xe81123: return \"Red\";\n        case 0xea005e: return \"Rose bright\";\n        case 0xc30052: return \"Rose\";\n        case 0xe3008c: return \"Plum light\";\n        case 0xbf0077: return \"Plum\";\n        case 0xc239b3: return \"Orchid light\";\n        case 0x9a0089: return \"Orchid\";\n        case 0x0078d4: return \"Blue\";\n        case 0x0063b1: return \"Navy blue\";\n        case 0x8d8bd7: return \"Purple shadow\";\n        case 0x6b69d6: return \"Purple shadow dark\";\n        case 0x8764b8: return \"Iris pastel\";\n        case 0x744da9: return \"Iris Spring\";\n        case 0xb146c2: return \"Violet red light\";\n        case 0x881798: return \"Violet red\";\n        case 0x0099bc: return \"Cool blue bright\";\n        case 0x2d7d9a: return \"Cool blue\";\n        case 0x00b7c3: return \"Seafoam\";\n        case 0x038387: return \"Seafoam teal\";\n        case 0x00b294: return \"Mint light\";\n        case 0x018574: return \"Mint dark\";\n        case 0x00cc6a: return \"Turf green\";\n        case 0x10893e: return \"Sport green\";\n        case 0x7a7574: return \"Gray\";\n        case 0x5d5a58: return \"Gray brown\";\n        case 0x68768a: return \"Steel blue\";\n        case 0x515c6b: return \"Metal blue\";\n        case 0x567c73: return \"Pale moss\";\n        case 0x486860: return \"Moss\";\n        case 0x498205: return \"Meadow green\";\n        case 0x107c10: return \"Green\";\n        case 0x767676: return \"Overcast\";\n        case 0x4c4a48: return \"Storm\";\n        case 0x69797e: return \"Blue gray\";\n        case 0x4a5459: return \"Gray dark\";\n        case 0x647c64: return \"Liddy green\";\n        case 0x4c574e: return \"Sage\";\n        case 0x807143: return \"Camouflage desert\";\n        case 0x766c59: return \"Camouflage\";\n        case 0x000000: return \"Black\";\n        case 0xFFFFFF: return \"White\";\n        default: return NULL;\n    }\n}\n\nbool ffDetectWmTheme(FFstrbuf* themeOrError)\n{\n    {\n        FF_AUTO_CLOSE_FD HANDLE hKey = NULL;\n        if (ffRegOpenKeyForRead(HKEY_CURRENT_USER, L\"SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Themes\", &hKey, NULL))\n        {\n            FF_STRBUF_AUTO_DESTROY theme = ffStrbufCreate();\n            if (ffRegReadStrbuf(hKey, L\"CurrentTheme\", &theme, NULL))\n            {\n                ffStrbufSubstrBeforeLastC(&theme, '.');\n                ffStrbufSubstrAfterLastC(&theme, '\\\\');\n                if(isalpha(theme.chars[0]))\n                    theme.chars[0] = (char)toupper(theme.chars[0]);\n\n                ffStrbufAppend(themeOrError, &theme);\n            }\n        }\n    }\n\n    do {\n        uint32_t rgbColor;\n        uint32_t bgrColor;\n        FF_AUTO_CLOSE_FD HANDLE hKey = NULL;\n        if (ffRegOpenKeyForRead(HKEY_CURRENT_USER, L\"Software\\\\Microsoft\\\\Windows\\\\DWM\", &hKey, NULL))\n        {\n            if (ffRegReadUint(hKey, L\"AccentColor\", &bgrColor, NULL))\n                rgbColor = ((bgrColor & 0xFF) << 16) | (bgrColor & 0xFF00) | ((bgrColor >> 16) & 0xFF);\n            else if (ffRegReadUint(hKey, L\"ColorizationColor\", &rgbColor, NULL))\n                rgbColor &= 0xFFFFFF;\n            else\n                break;\n        }\n        else break;\n\n        if (themeOrError->length > 0) ffStrbufAppendS(themeOrError, \" - \");\n        const char* text = colorHexToString(rgbColor);\n        if (text)\n            ffStrbufAppendS(themeOrError, text);\n        else\n            ffStrbufAppendF(themeOrError, \"#%06lX\", (long)rgbColor);\n    } while (false);\n\n    {\n        FF_AUTO_CLOSE_FD HANDLE hKey = NULL;\n        if (ffRegOpenKeyForRead(HKEY_CURRENT_USER, L\"SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Themes\\\\Personalize\", &hKey, NULL))\n        {\n            uint32_t system = 1, apps = 1;\n            if (ffRegReadValues(hKey, 2, (FFRegValueArg[]) {\n                FF_ARG(system, L\"SystemUsesLightTheme\"),\n                FF_ARG(apps, L\"AppsUseLightTheme\"),\n            }, NULL))\n            {\n                bool paren = themeOrError->length > 0;\n                if (paren)\n                    ffStrbufAppendS(themeOrError, \" (\");\n                ffStrbufAppendF(themeOrError, \"System: %s, Apps: %s\", system ? \"Light\" : \"Dark\", apps ? \"Light\" : \"Dark\");\n                if (paren)\n                    ffStrbufAppendC(themeOrError, ')');\n            }\n        }\n    }\n\n    if(themeOrError->length == 0)\n    {\n        ffStrbufSetStatic(themeOrError, \"Failed to find current theme\");\n        return false;\n    }\n    return true;\n}\n"
  },
  {
    "path": "src/detection/zpool/libzfs_simplified.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\n// From https://github.com/openzfs/zfs/blob/master/include/libzfs.h\n\n/*\n * CDDL HEADER START\n *\n * The contents of this file are subject to the terms of the\n * Common Development and Distribution License (the \"License\").\n * You may not use this file except in compliance with the License.\n *\n * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE\n * or https://opensource.org/licenses/CDDL-1.0.\n * See the License for the specific language governing permissions\n * and limitations under the License.\n *\n * When distributing Covered Code, include this CDDL HEADER in each\n * file and include the License file at usr/src/OPENSOLARIS.LICENSE.\n * If applicable, add the following below this CDDL HEADER, with the\n * fields enclosed by brackets \"[]\" replaced with your own identifying\n * information: Portions Copyright [yyyy] [name of copyright owner]\n *\n * CDDL HEADER END\n */\n\n// zpool_prop_t and zprop_source_t were previously enums in upstream OpenZFS.\n// However, the enum values for these types vary greatly between different platforms,\n// making it unsafe to use the enum values directly. To ensure portability,\n// we define them as simple int typedefs and use zpool_name_to_prop to look up\n// the correct value for a property at runtime.\ntypedef int zpool_prop_t;\ntypedef int zprop_source_t;\ntypedef int boolean_t;\n\ntypedef struct libzfs_handle libzfs_handle_t;\ntypedef struct zpool_handle zpool_handle_t;\ntypedef int (*zpool_iter_f)(zpool_handle_t *, void *);\n\nextern libzfs_handle_t *libzfs_init(void);\nextern void libzfs_fini(libzfs_handle_t *);\nextern int zpool_iter(libzfs_handle_t *, zpool_iter_f, void *);\nextern zpool_prop_t zpool_name_to_prop(const char *);\n// https://github.com/openzfs/zfs/blob/06c73cffabc30b61a695988ec8e290f43cb3768d/lib/libzfs/libzfs_pool.c#L300\nextern uint64_t zpool_get_prop_int(zpool_handle_t *zhp, zpool_prop_t prop, zprop_source_t *srctype);\nextern int zpool_get_prop(zpool_handle_t *zhp, zpool_prop_t prop, char *buf, size_t len, zprop_source_t *srctype, boolean_t literal);\nextern void zpool_close(zpool_handle_t *);\n"
  },
  {
    "path": "src/detection/zpool/zpool.c",
    "content": "#include \"zpool.h\"\n\n#if FF_HAVE_LIBZFS\n\n#include \"common/kmod.h\"\n\n#ifdef __sun\n#include <libzfs.h>\n    #ifndef __illumos__\n    // On Solaris 11, zpool_get_prop has only 5 arguments. #2173\n    #define ffzpool_get_prop(zhp, prop, buf, len, srctype, literal) \\\n        ffzpool_get_prop(zhp, prop, buf, len, srctype)\n    #endif\n#else\n#include \"libzfs_simplified.h\"\n#endif\n\n#include \"common/library.h\"\n\ntypedef struct FFZfsData\n{\n    FF_LIBRARY_SYMBOL(libzfs_fini)\n    FF_LIBRARY_SYMBOL(zpool_get_prop_int)\n    FF_LIBRARY_SYMBOL(zpool_get_prop)\n    FF_LIBRARY_SYMBOL(zpool_close)\n\n    // The fields in this struct store property IDs returned by `zpool_name_to_prop`,\n    // not the property values themselves.\n    struct {\n        int name;\n        int health;\n        int guid;\n        int size;\n        int free;\n        int allocated;\n        int fragmentation;\n        int readonly;\n    } props;\n\n    libzfs_handle_t* handle;\n    FFlist* result;\n} FFZfsData;\n\nstatic inline void cleanLibzfs(FFZfsData* data)\n{\n    if (data->fflibzfs_fini && data->handle)\n    {\n        data->fflibzfs_fini(data->handle);\n        data->handle = NULL;\n    }\n}\n\nstatic int enumZpoolCallback(zpool_handle_t* zpool, void* param)\n{\n    FFZfsData* data = (FFZfsData*) param;\n    zprop_source_t source;\n    FFZpoolResult* item = ffListAdd(data->result);\n    char buf[1024];\n    if (data->ffzpool_get_prop(zpool, data->props.name, buf, ARRAY_SIZE(buf), &source, false) == 0)\n        ffStrbufInitS(&item->name, buf);\n    else\n        ffStrbufInitStatic(&item->name, \"unknown\");\n    if (data->ffzpool_get_prop(zpool, data->props.health, buf, ARRAY_SIZE(buf), &source, false) == 0)\n        ffStrbufInitS(&item->state, buf);\n    else\n        ffStrbufInitStatic(&item->state, \"unknown\");\n    item->guid = data->ffzpool_get_prop_int(zpool, data->props.guid, &source);\n    item->total = data->ffzpool_get_prop_int(zpool, data->props.size, &source);\n    item->used = item->total - data->ffzpool_get_prop_int(zpool, data->props.free, &source);\n    item->allocated = data->ffzpool_get_prop_int(zpool, data->props.allocated, &source);\n    uint64_t fragmentation = data->ffzpool_get_prop_int(zpool, data->props.fragmentation, &source);\n    item->fragmentation = fragmentation == UINT64_MAX ? -DBL_MAX : (double) fragmentation;\n    item->readOnly = (bool) data->ffzpool_get_prop_int(zpool, data->props.readonly, &source);\n    data->ffzpool_close(zpool);\n    return 0;\n}\n\nconst char* ffDetectZpool(FFlist* result /* list of FFZpoolResult */)\n{\n    FF_LIBRARY_LOAD_MESSAGE(libzfs, \"libzfs\" FF_LIBRARY_EXTENSION, 6);\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libzfs, libzfs_init);\n\n    libzfs_handle_t* handle = fflibzfs_init();\n    if (!handle)\n    {\n        if (!ffKmodLoaded(\"zfs\")) return \"`zfs` kernel module is not loaded\";\n        return \"libzfs_init() failed\";\n    }\n\n    __attribute__((__cleanup__(cleanLibzfs))) FFZfsData data = {\n        .handle = handle,\n        .result = result,\n    };\n\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libzfs, zpool_name_to_prop);\n\n    #define FF_QUERY_ZPOOL_PROP_FROM_NAME(prop_name) do { \\\n        data.props.prop_name = ffzpool_name_to_prop(#prop_name); \\\n        if (data.props.prop_name < 0) \\\n            return \"Failed to query prop: \" #prop_name; \\\n    } while (false)\n    FF_QUERY_ZPOOL_PROP_FROM_NAME(name);\n    FF_QUERY_ZPOOL_PROP_FROM_NAME(health);\n    FF_QUERY_ZPOOL_PROP_FROM_NAME(guid);\n    FF_QUERY_ZPOOL_PROP_FROM_NAME(size);\n    FF_QUERY_ZPOOL_PROP_FROM_NAME(free);\n    FF_QUERY_ZPOOL_PROP_FROM_NAME(allocated);\n    FF_QUERY_ZPOOL_PROP_FROM_NAME(fragmentation);\n    FF_QUERY_ZPOOL_PROP_FROM_NAME(readonly);\n    #undef FF_QUERY_ZPOOL_PROP_FROM_NAME\n\n    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libzfs, zpool_iter);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libzfs, data, libzfs_fini);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libzfs, data, zpool_get_prop_int);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libzfs, data, zpool_get_prop);\n    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libzfs, data, zpool_close);\n\n    if (ffzpool_iter(handle, enumZpoolCallback, &data) < 0)\n        return \"zpool_iter() failed\";\n\n    return NULL;\n}\n\n#else\n\nconst char* ffDetectZpool(FF_MAYBE_UNUSED FFlist* result)\n{\n    return \"fastfetch was compiled without libzfs support\";\n}\n\n#endif\n"
  },
  {
    "path": "src/detection/zpool/zpool.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n#include \"modules/zpool/option.h\"\n\ntypedef struct FFZpoolResult\n{\n    FFstrbuf name;\n    FFstrbuf state;\n    uint64_t guid;\n    uint64_t used;\n    uint64_t total;\n    uint64_t allocated;\n    double fragmentation;\n    bool readOnly;\n} FFZpoolResult;\n\nconst char* ffDetectZpool(FFlist* result /* list of FFZpoolResult */);\n"
  },
  {
    "path": "src/fastfetch.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/ffdata.h\"\n#include \"detection/version/version.h\"\n#include \"logo/logo.h\"\n#include \"common/commandoption.h\"\n#include \"common/init.h\"\n#include \"common/io.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/time.h\"\n#include \"common/stringUtils.h\"\n#include \"common/mallocHelper.h\"\n#include \"fastfetch_datatext.h\"\n\n#include <stdlib.h>\n#include <ctype.h>\n#include <string.h>\n\n#ifdef _WIN32\n    #include \"common/windows/getline.h\"\n#endif\n\nstatic void printCommandFormatHelpJson(void)\n{\n    yyjson_mut_doc* doc = yyjson_mut_doc_new(NULL);\n    yyjson_mut_val* root = yyjson_mut_obj(doc);\n    yyjson_mut_doc_set_root(doc, root);\n\n    for (uint32_t i = 0; i <= 'Z' - 'A'; ++i)\n    {\n        for (FFModuleBaseInfo** modules = ffModuleInfos[i]; *modules; ++modules)\n        {\n            FFModuleBaseInfo* baseInfo = *modules;\n            if (!baseInfo->formatArgs.count) continue;\n\n            FF_STRBUF_AUTO_DESTROY type = ffStrbufCreateS(baseInfo->name);\n            ffStrbufLowerCase(&type);\n            ffStrbufAppendS(&type, \"Format\");\n\n            yyjson_mut_val* obj = yyjson_mut_obj(doc);\n            if (yyjson_mut_obj_add(root, yyjson_mut_strbuf(doc, &type), obj))\n            {\n                FF_STRBUF_AUTO_DESTROY content = ffStrbufCreateF(\"Output format of the module `%s`. See Wiki for formatting syntax\\n\", baseInfo->name);\n                for (unsigned i = 0; i < baseInfo->formatArgs.count; i++)\n                {\n                    const FFModuleFormatArg* arg = &baseInfo->formatArgs.args[i];\n                    ffStrbufAppendF(&content, \"    %u. {%s}: %s\\n\", i + 1, arg->name, arg->desc);\n                }\n                ffStrbufTrimRight(&content, '\\n');\n                yyjson_mut_obj_add_strbuf(doc, obj, \"description\", &content);\n                yyjson_mut_obj_add_str(doc, obj, \"type\", \"string\");\n            }\n        }\n    }\n    yyjson_mut_write_fp(stdout, doc, YYJSON_WRITE_PRETTY, NULL, NULL);\n    putchar('\\n');\n    yyjson_mut_doc_free(doc);\n}\n\nstatic void printCommandFormatHelp(const char* command)\n{\n    FF_STRBUF_AUTO_DESTROY type = ffStrbufCreateNS((uint32_t) (strlen(command) - strlen(\"-format\")), command);\n    ffStrbufLowerCase(&type);\n    for (FFModuleBaseInfo** modules = ffModuleInfos[toupper(command[0]) - 'A']; *modules; ++modules)\n    {\n        FFModuleBaseInfo* baseInfo = *modules;\n        if (ffStrbufIgnCaseEqualS(&type, baseInfo->name))\n        {\n            if (baseInfo->formatArgs.count > 0)\n            {\n                FF_STRBUF_AUTO_DESTROY variable = ffStrbufCreate();\n                printf(\"-- In config file: { \\\"type\\\": \\\"%s\\\", \\\"format\\\": \\\"{<format-variable>}\\\" }\\n\", type.chars);\n                printf(\"Sets the format string for %s output.\\n\", baseInfo->name);\n                puts(\"To see how a format string is constructed, take a look at https://github.com/fastfetch-cli/fastfetch/wiki/Format-String-Guide.\");\n                puts(\"The following variables are passed:\");\n\n                for (unsigned i = 0; i < baseInfo->formatArgs.count; i++)\n                {\n                    const FFModuleFormatArg* arg = &baseInfo->formatArgs.args[i];\n                    ffStrbufSetF(&variable, \"{%s}\", arg->name);\n                    printf(\"%20s: %s\\n\", variable.chars, arg->desc);\n                }\n            }\n            else\n                fprintf(stderr, \"Error: Module '%s' doesn't support output formatting\\n\", baseInfo->name);\n            return;\n        }\n    }\n\n    fprintf(stderr, \"Error: Module '%s' is not supported\\n\", type.chars);\n}\n\nstatic void printFullHelp()\n{\n    fputs(\"Fastfetch is a neofetch-like tool for fetching system information and displaying them in a pretty way\\n\\n\", stdout);\n    if (!instance.config.display.pipe)\n        fputs(\"\\e[1;4mUsage:\\e[m \\e[1mfastfetch\\e[m \\e[3m<?options>\\e[m\\n\\n\", stdout);\n    else\n        fputs(\"Usage: fastfetch <?options>\\n\\n\", stdout);\n\n    yyjson_doc* doc = yyjson_read(FASTFETCH_DATATEXT_JSON_HELP, strlen(FASTFETCH_DATATEXT_JSON_HELP), YYJSON_READ_NOFLAG);\n    assert(doc);\n    yyjson_val *groupKey, *flagArr;\n    size_t groupIdx, groupMax;\n    yyjson_obj_foreach(yyjson_doc_get_root(doc), groupIdx, groupMax, groupKey, flagArr)\n    {\n        if (!instance.config.display.pipe)\n            fputs(\"\\e[1;4m\", stdout);\n        printf(\"%s options:\", yyjson_get_str(groupKey));\n        if (!instance.config.display.pipe)\n            fputs(\"\\e[m\", stdout);\n        putchar('\\n');\n\n        yyjson_val* flagObj;\n        size_t flagIdx, flagMax;\n        yyjson_arr_foreach(flagArr, flagIdx, flagMax, flagObj)\n        {\n            yyjson_val* shortKey = yyjson_obj_get(flagObj, \"short\");\n            if (shortKey)\n            {\n                fputs(\"  \", stdout);\n                if (!instance.config.display.pipe)\n                    fputs(\"\\e[1m\", stdout);\n                printf(\"-%s\", yyjson_get_str(shortKey));\n                if (!instance.config.display.pipe)\n                    fputs(\"\\e[m\", stdout);\n                fputs(\", \", stdout);\n            }\n            else\n            {\n                fputs(\"      \", stdout);\n            }\n            yyjson_val* longKey = yyjson_obj_get(flagObj, \"long\");\n            assert(longKey);\n            if (!instance.config.display.pipe)\n                fputs(\"\\e[1m\", stdout);\n            printf(\"--%s\", yyjson_get_str(longKey));\n            if (!instance.config.display.pipe)\n                fputs(\"\\e[m\", stdout);\n\n            yyjson_val* argObj = yyjson_obj_get(flagObj, \"arg\");\n            if (argObj)\n            {\n                yyjson_val* typeKey = yyjson_obj_get(argObj, \"type\");\n                assert(typeKey);\n                yyjson_val* optionalKey = yyjson_obj_get(argObj, \"optional\");\n                bool optional = optionalKey && yyjson_get_bool(optionalKey);\n                putchar(' ');\n                if (!instance.config.display.pipe)\n                    fputs(\"\\e[3m\", stdout);\n                printf(\"<%s%s>\", optional ? \"?\" : \"\", yyjson_get_str(typeKey));\n                if (!instance.config.display.pipe)\n                    fputs(\"\\e[m\", stdout);\n            }\n\n            yyjson_val* descKey = yyjson_obj_get(flagObj, \"desc\");\n            assert(descKey);\n            if (yyjson_is_arr(descKey))\n            {\n                if (instance.config.display.pipe)\n                    putchar(':');\n\n                yyjson_val* descStr;\n                size_t descIdx, descMax;\n                yyjson_arr_foreach(descKey, descIdx, descMax, descStr)\n                {\n                    if (!instance.config.display.pipe)\n                        printf(\"\\e[46G%s\\n\", yyjson_get_str(descStr));\n                    else\n                        printf(\" %s\", yyjson_get_str(descStr));\n                }\n                if (instance.config.display.pipe)\n                    putchar('\\n');\n            }\n            else\n            {\n                if (!instance.config.display.pipe)\n                    fputs(\"\\e[46G\", stdout);\n                else\n                    fputs(\": \", stdout);\n                puts(yyjson_get_str(descKey));\n            }\n        }\n\n        putchar('\\n');\n    }\n    yyjson_doc_free(doc);\n\n    puts(\"\\n\\\nCommand flags are not case sensitive. E.g. `--print-logos` is equal to `--Print-Logos`\\n\\\nIf a value starts with a ?, it is optional. An optional boolean value defaults to true if not specified.\\n\\\nMore detailed help messages for each options can be printed with `-h <option_without_dash_prefix>`\\n\\\nFor detailed information on logo options, module configuration, and formatting, visit:\\n\\\n      https://github.com/fastfetch-cli/fastfetch/wiki/Configuration\");\n}\n\nstatic bool printSpecificCommandHelp(const char* command)\n{\n    yyjson_doc* doc = yyjson_read(FASTFETCH_DATATEXT_JSON_HELP, strlen(FASTFETCH_DATATEXT_JSON_HELP), YYJSON_READ_NOFLAG);\n    assert(doc);\n    yyjson_val *groupKey, *flagArr;\n    size_t groupIdx, groupMax;\n    yyjson_obj_foreach(yyjson_doc_get_root(doc), groupIdx, groupMax, groupKey, flagArr)\n    {\n        yyjson_val* flagObj;\n        size_t flagIdx, flagMax;\n        yyjson_arr_foreach(flagArr, flagIdx, flagMax, flagObj)\n        {\n            yyjson_val* pseudo = yyjson_obj_get(flagObj, \"pseudo\");\n            if (pseudo && yyjson_get_bool(pseudo))\n                continue;\n\n            yyjson_val* longKey = yyjson_obj_get(flagObj, \"long\");\n            assert(longKey);\n            if (ffStrEqualsIgnCase(command, yyjson_get_str(longKey)))\n            {\n                puts(yyjson_get_str(yyjson_obj_get(flagObj, \"desc\")));\n\n                printf(\"%10s: \", \"Usage\");\n                yyjson_val* shortKey = yyjson_obj_get(flagObj, \"short\");\n                if (shortKey)\n                {\n                    if (!instance.config.display.pipe)\n                        fputs(\"\\e[1m\", stdout);\n                    printf(\"-%s\", yyjson_get_str(shortKey));\n                    if (!instance.config.display.pipe)\n                        fputs(\"\\e[m\", stdout);\n                    fputs(\", \", stdout);\n                }\n                if (!instance.config.display.pipe)\n                    fputs(\"\\e[1m\", stdout);\n                printf(\"--%s\", yyjson_get_str(longKey));\n                if (!instance.config.display.pipe)\n                    fputs(\"\\e[m\", stdout);\n\n                yyjson_val* argObj = yyjson_obj_get(flagObj, \"arg\");\n                if (argObj)\n                {\n                    yyjson_val* typeKey = yyjson_obj_get(argObj, \"type\");\n                    assert(typeKey);\n                    yyjson_val* optionalKey = yyjson_obj_get(argObj, \"optional\");\n                    bool optional = optionalKey && yyjson_get_bool(optionalKey);\n                    putchar(' ');\n                    if (!instance.config.display.pipe)\n                        fputs(\"\\e[3m\", stdout);\n                    printf(\"<%s%s>\", optional ? \"?\" : \"\", yyjson_get_str(typeKey));\n                    if (!instance.config.display.pipe)\n                        fputs(\"\\e[m\", stdout);\n                    putchar('\\n');\n\n                    yyjson_val* defaultKey = yyjson_obj_get(argObj, \"default\");\n                    if (defaultKey)\n                    {\n                        if (ffStrEqualsIgnCase(yyjson_get_str(typeKey), \"structure\"))\n                            printf(\"%10s: %s\\n\", \"Default\", FASTFETCH_DATATEXT_STRUCTURE);\n                        else if (yyjson_is_bool(defaultKey))\n                            printf(\"%10s: %s\\n\", \"Default\", yyjson_get_bool(defaultKey) ? \"true\" : \"false\");\n                        else if (yyjson_is_num(defaultKey))\n                            printf(\"%10s: %d\\n\", \"Default\", yyjson_get_int(defaultKey));\n                        else if (yyjson_is_str(defaultKey))\n                            printf(\"%10s: %s\\n\", \"Default\", yyjson_get_str(defaultKey));\n                        else\n                            printf(\"%10s: Unknown\\n\", \"Default\");\n                    }\n\n                    yyjson_val* enumKey = yyjson_obj_get(argObj, \"enum\");\n                    if (enumKey)\n                    {\n                        printf(\"%10s:\\n\", \"Options\");\n                        yyjson_val *optKey, *optVal;\n                        size_t optIdx, optMax;\n                        yyjson_obj_foreach(enumKey, optIdx, optMax, optKey, optVal)\n                            printf(\"%12s: %s\\n\", yyjson_get_str(optKey), yyjson_get_str(optVal));\n                    }\n                }\n                else\n                    putchar('\\n');\n\n                yyjson_val* remarkKey = yyjson_obj_get(flagObj, \"remark\");\n                if (remarkKey)\n                {\n                    if (yyjson_is_str(remarkKey))\n                        printf(\"%10s: %s\\n\", \"Remark\", yyjson_get_str(remarkKey));\n                    else if (yyjson_is_arr(remarkKey) && yyjson_arr_size(remarkKey) > 0)\n                    {\n                        yyjson_val* remarkStr;\n                        size_t remarkIdx, remarkMax;\n                        yyjson_arr_foreach(remarkKey, remarkIdx, remarkMax, remarkStr)\n                        {\n                            if (remarkIdx == 0)\n                                printf(\"%10s: %s\\n\", \"Remark\", yyjson_get_str(remarkStr));\n                            else\n                                printf(\"            %s\\n\", yyjson_get_str(remarkStr));\n                        }\n                    }\n                }\n\n                yyjson_doc_free(doc);\n                return true;\n            }\n        }\n    }\n\n    yyjson_doc_free(doc);\n    return false;\n}\n\nstatic void printCommandHelp(const char* command)\n{\n    if(command == NULL)\n        printFullHelp();\n    else if(ffStrEqualsIgnCase(command, \"format-json\"))\n        printCommandFormatHelpJson();\n    else if(ffCharIsEnglishAlphabet(command[0]) && ffStrEndsWithIgnCase(command, \"-format\")) // <module>-format\n        printCommandFormatHelp(command);\n    else if(!printSpecificCommandHelp(command))\n        fprintf(stderr, \"Error: No specific help for command '%s' provided\\n\", command);\n}\n\nstatic void listAvailablePresets(bool pretty)\n{\n    FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.dataDirs)\n    {\n        ffStrbufAppendS(path, \"fastfetch/presets/\");\n        ffListFilesRecursively(path->chars, pretty);\n    }\n\n    if (instance.state.platform.exePath.length)\n    {\n        FF_STRBUF_AUTO_DESTROY absolutePath = ffStrbufCreateCopy(&instance.state.platform.exePath);\n        ffStrbufSubstrBeforeLastC(&absolutePath, '/');\n        ffStrbufAppendS(&absolutePath, \"/presets/\");\n        ffListFilesRecursively(absolutePath.chars, pretty);\n    }\n}\n\nstatic void listAvailableLogos(void)\n{\n    FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.dataDirs)\n    {\n        ffStrbufAppendS(path, \"fastfetch/logos/\");\n        ffListFilesRecursively(path->chars, true);\n    }\n}\n\nstatic void listConfigPaths(void)\n{\n    FF_LIST_FOR_EACH(FFstrbuf, folder, instance.state.platform.configDirs)\n    {\n        bool exists = false;\n        uint32_t length = folder->length + (uint32_t) strlen(\"fastfetch\") + 1 /* trailing slash */;\n        ffStrbufAppendS(folder, \"fastfetch/config.jsonc\");\n        exists = ffPathExists(folder->chars, FF_PATHTYPE_FILE);\n        ffStrbufSubstrBefore(folder, length);\n        printf(\"%s%s\\n\", folder->chars, exists ? \" (*)\" : \"\");\n    }\n}\n\nstatic void listDataPaths(void)\n{\n    FF_LIST_FOR_EACH(FFstrbuf, folder, instance.state.platform.dataDirs)\n    {\n        ffStrbufAppendS(folder, \"fastfetch/\");\n        puts(folder->chars);\n    }\n}\n\nstatic void listModules(bool pretty)\n{\n    unsigned count = 0;\n    for (int i = 0; i <= 'Z' - 'A'; ++i)\n    {\n        for (FFModuleBaseInfo** modules = ffModuleInfos[i]; *modules; ++modules)\n        {\n            ++count;\n            if (pretty)\n                printf(\"%d)%s%-14s: %s\\n\", count, count > 9 ? \" \" : \"  \", (*modules)->name, (*modules)->description);\n            else\n                printf(\"%s:%s\\n\", (*modules)->name, (*modules)->description);\n        }\n    }\n}\n\nstatic bool parseJsoncFile(FFdata* data, const char* path, yyjson_read_flag flg)\n{\n    assert(!data->configDoc);\n\n    {\n        yyjson_read_err error;\n        data->configDoc = path\n            ? yyjson_read_file(path, flg, NULL, &error)\n            : yyjson_read_fp(stdin, flg, NULL, &error);\n        if (!data->configDoc)\n        {\n            if (error.code != YYJSON_READ_ERROR_FILE_OPEN)\n            {\n                if (path)\n                {\n                    size_t row = 0, col = error.pos;\n                    FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate();\n                    if (ffAppendFileBuffer(path, &content))\n                        yyjson_locate_pos(content.chars, content.length, error.pos, &row, &col, NULL);\n                    fprintf(stderr, \"Error: failed to parse JSON config file `%s` at (%zu, %zu): %s\\n\", path, row, col, error.msg);\n                }\n                else\n                    fprintf(stderr, \"Error: failed to parse JSON from stdin at %zu: %s\\n\", error.pos, error.msg);\n\n                exit(477);\n            }\n            return false;\n        }\n    }\n\n    {\n        const char* error = NULL;\n\n        yyjson_val* const root = yyjson_doc_get_root(data->configDoc);\n        if (!yyjson_is_obj(root))\n            error = \"Invalid JSON config format. Root value must be an object\";\n\n        if (\n            error ||\n            (error = ffOptionsParseLogoJsonConfig(&instance.config.logo, root)) ||\n            (error = ffOptionsParseGeneralJsonConfig(&instance.config.general, root)) ||\n            (error = ffOptionsParseDisplayJsonConfig(&instance.config.display, root)) ||\n            false\n        ) {\n            fprintf(stderr, \"JsonConfig Error: %s\\n\", error);\n            exit(477);\n        }\n    }\n\n    return true;\n}\n\n\nstatic void generateConfigFile(FFdata* data, bool force, const char* filePath, bool fullConfig)\n{\n    if (data->resultDoc)\n    {\n        fprintf(stderr, \"Error: duplicated `--gen-config` or `--format json` flags found\\n\");\n        exit(477);\n    }\n\n    if (!filePath)\n    {\n        if (instance.state.platform.configDirs.length == 0)\n        {\n            fprintf(stderr, \"Error: No config directory found to generate config file in. Use --gen-config <path> to specify a path\\n\");\n            exit(477);\n        }\n\n        FFstrbuf* configDir = FF_LIST_FIRST(FFstrbuf, instance.state.platform.configDirs);\n        ffStrbufEnsureFixedLengthFree(&data->genConfigPath, configDir->length + strlen(\"fastfetch/config.jsonc\"));\n        ffStrbufSet(&data->genConfigPath, configDir);\n        ffStrbufAppendS(&data->genConfigPath, \"fastfetch/config.jsonc\");\n    }\n    else\n    {\n        ffStrbufSetS(&data->genConfigPath, filePath);\n    }\n\n    if (!force && ffPathExists(data->genConfigPath.chars, FF_PATHTYPE_ANY))\n    {\n        fprintf(stderr, \"Error: file `%s` exists. Use `--gen-config%s-force` to overwrite\\n\", data->genConfigPath.chars, fullConfig ? \"-full\" : \"\");\n        exit(477);\n    }\n\n    data->docType = fullConfig ? FF_RESULT_DOC_TYPE_CONFIG_FULL : FF_RESULT_DOC_TYPE_CONFIG;\n    data->resultDoc = yyjson_mut_doc_new(NULL);\n}\n\nstatic void optionParseConfigFile(FFdata* data, const char* key, const char* value)\n{\n    if (data->configLoaded)\n    {\n        fprintf(stderr, \"Error: only one config file can be loaded\\n\");\n        exit(413);\n    }\n\n    data->configLoaded = true;\n\n    if(value == NULL)\n    {\n        fprintf(stderr, \"Error: usage: %s <config>\\n\", key);\n        exit(413);\n    }\n\n    if (value[0] == '\\0' || ffStrEqualsIgnCase(value, \"none\"))\n        return;\n\n    if (value[0] == '-' && value[1] == '\\0')\n    {\n        parseJsoncFile(data, NULL, false);\n        return;\n    }\n\n    //Try to load as an absolute path\n\n    FF_STRBUF_AUTO_DESTROY absolutePath = ffStrbufCreateA(128);\n    ffStrbufSetS(&absolutePath, value);\n    bool strictJson = ffStrbufEndsWithIgnCaseS(&absolutePath, \".json\");\n    bool jsonc = !strictJson && ffStrbufEndsWithIgnCaseS(&absolutePath, \".jsonc\");\n    bool json5 = !strictJson && !jsonc && ffStrbufEndsWithIgnCaseS(&absolutePath, \".json5\");\n    bool needExtension = !strictJson && !jsonc && !json5;\n    if (needExtension)\n        ffStrbufAppendS(&absolutePath, \".jsonc\");\n\n    yyjson_read_flag flag = strictJson\n        ? 0\n        : jsonc\n            ? YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS\n            : YYJSON_READ_JSON5;\n\n    if (parseJsoncFile(data, absolutePath.chars, flag)) return;\n\n    //Try to load as a relative path with the config directory\n\n    FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.configDirs)\n    {\n        ffStrbufSet(&absolutePath, path);\n        ffStrbufAppendS(&absolutePath, \"fastfetch/\");\n        ffStrbufAppendS(&absolutePath, value);\n        if (needExtension)\n            ffStrbufAppendS(&absolutePath, \".jsonc\");\n\n        if (parseJsoncFile(data, absolutePath.chars, flag)) return;\n    }\n\n    //Try to load as a preset\n\n    FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.dataDirs)\n    {\n        ffStrbufSet(&absolutePath, path);\n        ffStrbufAppendS(&absolutePath, \"fastfetch/presets/\");\n        ffStrbufAppendS(&absolutePath, value);\n        if (needExtension)\n            ffStrbufAppendS(&absolutePath, \".jsonc\");\n\n        if (parseJsoncFile(data, absolutePath.chars, flag)) return;\n    }\n\n    //Try to load as a relative path with the directory of fastfetch binary, for Windows support\n\n    if (instance.state.platform.exePath.length)\n    {\n        uint32_t lastSlash = ffStrbufLastIndexC(&instance.state.platform.exePath, '/') + 1;\n        assert(lastSlash < instance.state.platform.exePath.length);\n\n        // Try {exePath}/\n        ffStrbufSetNS(&absolutePath, lastSlash, instance.state.platform.exePath.chars);\n        ffStrbufAppendS(&absolutePath, value);\n        if (needExtension)\n            ffStrbufAppendS(&absolutePath, \".jsonc\");\n        if (parseJsoncFile(data, absolutePath.chars, flag)) return;\n\n        // Try {exePath}/presets/\n        ffStrbufSubstrBefore(&absolutePath, lastSlash);\n        ffStrbufAppendS(&absolutePath, \"presets/\");\n        ffStrbufAppendS(&absolutePath, value);\n        if (needExtension)\n            ffStrbufAppendS(&absolutePath, \".jsonc\");\n        if (parseJsoncFile(data, absolutePath.chars, flag)) return;\n    }\n\n    //File not found\n\n    fprintf(stderr, \"Error: couldn't find config: %s\\n\", value);\n    exit(414);\n}\n\nstatic void printVersion()\n{\n    FFVersionResult* result = &ffVersionResult;\n    printf(\"%s %s%s%s (%s)\\n\", result->projectName, result->version, result->versionTweak, result->debugMode ? \"-debug\" : \"\", result->architecture);\n}\n\nstatic void enableJsonOutput(FFdata* data)\n{\n    if (data->resultDoc)\n    {\n        fprintf(stderr, \"Error: duplicated `--gen-config` or `--format json` flags found\\n\");\n        exit(477);\n    }\n\n    data->resultDoc = yyjson_mut_doc_new(NULL);\n    data->docType = FF_RESULT_DOC_TYPE_JSON;\n    yyjson_mut_doc_set_root(data->resultDoc, yyjson_mut_arr(data->resultDoc));\n}\n\nstatic void parseCommand(FFdata* data, char* key, char* value)\n{\n    if(ffStrEqualsIgnCase(key, \"-h\") || ffStrEqualsIgnCase(key, \"--help\"))\n    {\n        printCommandHelp(value);\n        exit(0);\n    }\n    if(ffStrEqualsIgnCase(key, \"--help-raw\"))\n    {\n        puts(FASTFETCH_DATATEXT_JSON_HELP);\n        exit(0);\n    }\n    else if(ffStrEqualsIgnCase(key, \"-v\") || ffStrEqualsIgnCase(key, \"--version\"))\n    {\n        printVersion();\n        exit(0);\n    }\n    else if(ffStrEqualsIgnCase(key, \"--version-raw\"))\n    {\n        puts(FASTFETCH_PROJECT_VERSION);\n        exit(0);\n    }\n    else if(ffStrStartsWithIgnCase(key, \"--print-\"))\n    {\n        const char* subkey = key + strlen(\"--print-\");\n        if(ffStrEndsWithIgnCase(subkey, \"structure\"))\n            puts(FASTFETCH_DATATEXT_STRUCTURE);\n        else if(ffStrEqualsIgnCase(subkey, \"logos\"))\n            ffLogoBuiltinPrint();\n        else\n        {\n            fprintf(stderr, \"Error: unsupported print option: %s\\n\", key);\n            exit(415);\n        }\n        exit(0);\n    }\n    else if(ffStrStartsWithIgnCase(key, \"--list-\"))\n    {\n        const char* subkey = key + strlen(\"--list-\");\n        if(ffStrEqualsIgnCase(subkey, \"modules\"))\n            listModules(!value || !ffStrEqualsIgnCase(value, \"autocompletion\"));\n        else if(ffStrEqualsIgnCase(subkey, \"presets\"))\n            listAvailablePresets(!value || !ffStrEqualsIgnCase(value, \"autocompletion\"));\n        else if(ffStrEqualsIgnCase(subkey, \"config-paths\"))\n            listConfigPaths();\n        else if(ffStrEqualsIgnCase(subkey, \"data-paths\"))\n            listDataPaths();\n        else if(ffStrEqualsIgnCase(subkey, \"features\"))\n            ffListFeatures();\n        else if(ffStrEqualsIgnCase(subkey, \"logos\"))\n        {\n            if (value)\n            {\n                if (ffStrEqualsIgnCase(value, \"autocompletion\"))\n                    ffLogoBuiltinListAutocompletion();\n                else if (ffStrEqualsIgnCase(value, \"builtin\"))\n                    ffLogoBuiltinList();\n                else if (ffStrEqualsIgnCase(value, \"custom\"))\n                    listAvailableLogos();\n                else\n                {\n                    fprintf(stderr, \"Error: unsupported logo type: %s\\n\", value);\n                    exit(415);\n                }\n            }\n            else\n            {\n                puts(\"Builtin logos:\");\n                ffLogoBuiltinList();\n                puts(\"\\nCustom logos:\");\n                listAvailableLogos();\n            }\n        }\n        else\n        {\n            fprintf(stderr, \"Error: unsupported list option: %s\\n\", key);\n            exit(415);\n        }\n\n        exit(0);\n    }\n    else if(ffStrEqualsIgnCase(key, \"--gen-config\"))\n        generateConfigFile(data, false, value, false);\n    else if(ffStrEqualsIgnCase(key, \"--gen-config-force\"))\n        generateConfigFile(data, true, value, false);\n    else if(ffStrEqualsIgnCase(key, \"--gen-config-full\"))\n        generateConfigFile(data, false, value, true);\n    else if(ffStrEqualsIgnCase(key, \"--gen-config-full-force\"))\n        generateConfigFile(data, true, value, true);\n    else if(ffStrEqualsIgnCase(key, \"-c\") || ffStrEqualsIgnCase(key, \"--config\"))\n        optionParseConfigFile(data, key, value);\n    else if(ffStrEqualsIgnCase(key, \"-j\") || ffStrEqualsIgnCase(key, \"--json\"))\n    {\n        if (ffOptionParseBoolean(value)) enableJsonOutput(data);\n    }\n    else if(ffStrEqualsIgnCase(key, \"--format\"))\n    {\n        if (!!ffOptionParseEnum(key, value, (FFKeyValuePair[]) {\n            { \"default\", false},\n            { \"json\", true },\n            {},\n        })) enableJsonOutput(data);\n    }\n    else if(ffStrEqualsIgnCase(key, \"--dynamic-interval\"))\n        instance.state.dynamicInterval = ffOptionParseUInt32(key, value); // seconds to milliseconds\n    else\n        return;\n\n    // Don't parse it again in parseOption.\n    // This is necessary because parseOption doesn't understand this option and will result in an unknown option error.\n    key[0] = '\\0';\n    if (value) value[0] = '\\0';\n}\n\nstatic void parseOption(FFdata* data, const char* key, const char* value)\n{\n    if(ffStrEqualsIgnCase(key, \"-s\") || ffStrEqualsIgnCase(key, \"--structure\"))\n        ffOptionParseString(key, value, &data->structure);\n\n    else if(\n        ffOptionsParseGeneralCommandLine(&instance.config.general, key, value) ||\n        ffOptionsParseLogoCommandLine(&instance.config.logo, key, value) ||\n        ffOptionsParseDisplayCommandLine(&instance.config.display, key, value) ||\n        ffParseModuleOptions(key, value)\n    ) {}\n\n    else if(ffStrEqualsIgnCase(key, \"--structure-disabled\"))\n        ffOptionParseString(key, value, &data->structureDisabled);\n\n    else\n    {\n        fprintf(stderr, \"Error: unknown option: %s\\n\", key);\n        exit(400);\n    }\n}\n\nstatic void parseConfigFiles(FFdata* data)\n{\n    if (__builtin_expect(data->genConfigPath.length == 0, true))\n    {\n        FF_LIST_FOR_EACH(FFstrbuf, dir, instance.state.platform.configDirs)\n        {\n            uint32_t dirLength = dir->length;\n\n            ffStrbufAppendS(dir, \"fastfetch/config.jsonc\");\n            bool success = parseJsoncFile(data, dir->chars, YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS);\n            ffStrbufSubstrBefore(dir, dirLength);\n            if (success) return;\n\n            ffStrbufAppendS(dir, \"fastfetch/config.json5\");\n            success = parseJsoncFile(data, dir->chars, YYJSON_READ_JSON5);\n            ffStrbufSubstrBefore(dir, dirLength);\n            if (success) return;\n        }\n    }\n}\n\nstatic void parseArguments(FFdata* data, int argc, char** argv, void (*parser)(FFdata* data, char* key, char* value))\n{\n    for(int i = 1; i < argc; i++)\n    {\n        const char* key = argv[i];\n        if(*key == '\\0')\n            continue; // has been handled by parseCommand\n\n        if(*key != '-')\n        {\n            fprintf(stderr, \"Error: invalid option: %s. An option must start with `-`\\n\", key);\n            exit(400);\n        }\n\n        if(i == argc - 1 || (\n            argv[i + 1][0] == '-' &&\n            argv[i + 1][1] != '\\0' && // `-` is used as an alias for `/dev/stdin`\n            !ffStrEqualsIgnCase(argv[i], \"--separator-string\") // Separator string can start with a -\n        )) {\n            parser(data, argv[i], NULL);\n        }\n        else\n        {\n            parser(data, argv[i], argv[i + 1]);\n            ++i;\n        }\n    }\n}\n\nstatic void run(FFdata* data)\n{\n    const bool useJsonConfig = data->structure.length == 0 && data->configDoc;\n\n    if (useJsonConfig)\n        ffPrintJsonConfig(data, true /* prepare */);\n    else\n    {\n        //If we don't have a custom structure, use the default one\n        if(data->structure.length == 0)\n            ffStrbufAppendS(&data->structure, FASTFETCH_DATATEXT_STRUCTURE); // Cannot use `ffStrbufSetStatic` here because we will modify the string\n        ffPrepareCommandOption(data);\n    }\n\n    ffStart();\n\n    if (!data->resultDoc)\n        ffLogoPrint();\n\n    #if defined(_WIN32)\n        if (!instance.config.display.noBuffer) fflush(stdout);\n    #endif\n\n    while (true)\n    {\n        if (useJsonConfig)\n            ffPrintJsonConfig(data, false);\n        else\n            ffPrintCommandOption(data);\n\n        if (instance.state.dynamicInterval > 0)\n        {\n            fflush(stdout);\n            ffTimeSleep(instance.state.dynamicInterval);\n            fputs(\"\\e[H\", stdout);\n        }\n        else\n            break;\n\n        if (useJsonConfig)\n            ffPrintJsonConfig(data, true /* prepare */);\n        else\n            ffPrepareCommandOption(data);\n    }\n\n    if (data->resultDoc)\n        yyjson_mut_write_fp(stdout, data->resultDoc, YYJSON_WRITE_INF_AND_NAN_AS_NULL | YYJSON_WRITE_PRETTY_TWO_SPACES | YYJSON_WRITE_NEWLINE_AT_END, NULL, NULL);\n    else\n    {\n        if (instance.config.logo.printRemaining)\n            ffLogoPrintRemaining();\n        ffFinish();\n    }\n}\n\nstatic void writeConfigFile(FFdata* data)\n{\n    const FFstrbuf* filename = &data->genConfigPath;\n\n    yyjson_mut_doc* doc = data->resultDoc;\n    yyjson_mut_val* root = yyjson_mut_obj(doc);\n    yyjson_mut_doc_set_root(doc, root);\n    yyjson_mut_obj_add_str(doc, root, \"$schema\", \"https://github.com/fastfetch-cli/fastfetch/raw/master/doc/json_schema.json\");\n\n    if (data->docType == FF_RESULT_DOC_TYPE_CONFIG_FULL)\n    {\n        ffOptionsGenerateLogoJsonConfig(data, &instance.config.logo);\n        ffOptionsGenerateDisplayJsonConfig(data, &instance.config.display);\n        ffOptionsGenerateGeneralJsonConfig(data, &instance.config.general);\n    }\n    ffMigrateCommandOptionToJsonc(data);\n\n    if (ffStrbufEqualS(filename, \"-\"))\n        yyjson_mut_write_fp(stdout, doc, YYJSON_WRITE_INF_AND_NAN_AS_NULL | YYJSON_WRITE_PRETTY_TWO_SPACES | YYJSON_WRITE_NEWLINE_AT_END, NULL, NULL);\n    else\n    {\n        size_t len;\n        FF_AUTO_FREE const char* str = yyjson_mut_write(doc, YYJSON_WRITE_INF_AND_NAN_AS_NULL | YYJSON_WRITE_PRETTY_TWO_SPACES | YYJSON_WRITE_NEWLINE_AT_END, &len);\n        if (!str)\n        {\n            printf(\"Error: failed to generate config file\\n\");\n            exit(1);\n        }\n        if (ffWriteFileData(filename->chars, len, str))\n        {\n            printf(\"✓ Configuration file generated: `%s`\\n\"\n                   \"* Tip: Use a JSON schema-aware editor for better editing experience\\n\"\n                   \"* Documentation: https://github.com/fastfetch-cli/fastfetch/wiki/Configuration\\n\", filename->chars);\n        }\n        else\n        {\n            printf(\"Error: failed to write file in `%s`\\n\", filename->chars);\n            exit(1);\n        }\n    }\n}\n\nint main(int argc, char** argv)\n{\n    ffInitInstance();\n    atexit(ffDestroyInstance);\n\n    //Data stores things only needed for the configuration of fastfetch\n    FFdata data = {\n        .structure = ffStrbufCreate(),\n        .structureDisabled = ffStrbufCreate(),\n        .genConfigPath = ffStrbufCreate(),\n    };\n\n    parseArguments(&data, argc, argv, parseCommand);\n    if(instance.state.dynamicInterval && data.resultDoc)\n    {\n        fprintf(stderr, \"Error: --dynamic-interval cannot be used with --json\\n\");\n        exit(400);\n    }\n\n    if(!data.configLoaded && !getenv(\"NO_CONFIG\"))\n        parseConfigFiles(&data);\n    parseArguments(&data, argc, argv, (void*) parseOption);\n\n    if (__builtin_expect(data.genConfigPath.length == 0, true))\n        run(&data);\n    else\n        writeConfigFile(&data);\n\n    ffStrbufDestroy(&data.structure);\n    ffStrbufDestroy(&data.structureDisabled);\n    yyjson_doc_free(data.configDoc);\n    yyjson_mut_doc_free(data.resultDoc);\n    ffStrbufDestroy(&data.genConfigPath);\n}\n"
  },
  {
    "path": "src/fastfetch.h",
    "content": "#pragma once\n\n#include \"fastfetch_config.h\"\n\n#include <stdint.h>\n#include <stdbool.h>\n\n#ifdef _MSC_VER\n    #define __attribute__(x)\n#endif\n\n#include \"common/arrayUtils.h\"\n#include \"common/FFstrbuf.h\"\n#include \"common/FFlist.h\"\n#include \"common/FFPlatform.h\"\n#include \"common/unused.h\"\n\n#include \"options/logo.h\"\n#include \"options/display.h\"\n#include \"options/general.h\"\n\n\ntypedef struct FFconfig\n{\n    FFOptionsLogo logo;\n    FFOptionsDisplay display;\n    FFOptionsGeneral general;\n} FFconfig;\n\ntypedef struct FFstate\n{\n    uint32_t logoWidth;\n    uint32_t logoHeight;\n    uint32_t keysHeight;\n    bool terminalLightTheme;\n    bool titleFqdn;\n    uint32_t dynamicInterval;\n    FFPlatform platform;\n} FFstate;\n\ntypedef struct FFinstance\n{\n    FFconfig config;\n    FFstate state;\n} FFinstance;\nextern FFinstance instance; // Defined in `common/init.c`\nextern FFModuleBaseInfo** ffModuleInfos[];\n"
  },
  {
    "path": "src/fastfetch_config.h.in",
    "content": "#pragma once\n\n#define FASTFETCH_PROJECT_NAME \"@PROJECT_NAME@\"\n#define FASTFETCH_PROJECT_VERSION \"@PROJECT_VERSION@\"\n#define FASTFETCH_PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@\n#define FASTFETCH_PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@\n#define FASTFETCH_PROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH@\n#define FASTFETCH_PROJECT_VERSION_GIT \"@PROJECT_VERSION_GIT@\"\n#define FASTFETCH_PROJECT_VERSION_TWEAK \"@PROJECT_VERSION_TWEAK@\"\n#define FASTFETCH_PROJECT_VERSION_TWEAK_NUM @PROJECT_VERSION_TWEAK_NUM@\n#define FASTFETCH_PROJECT_CMAKE_BUILD_TYPE \"@CMAKE_BUILD_TYPE@\"\n#define FASTFETCH_PROJECT_HOMEPAGE_URL \"@PROJECT_HOMEPAGE_URL@\"\n#define FASTFETCH_PROJECT_DESCRIPTION \"@PROJECT_DESCRIPTION@\"\n#define FASTFETCH_PROJECT_LICENSE \"@PROJECT_LICENSE@\"\n\n#define FASTFETCH_TARGET_DIR_ROOT \"@TARGET_DIR_ROOT@\"\n#define FASTFETCH_TARGET_DIR_USR \"@TARGET_DIR_USR@\"\n#define FASTFETCH_TARGET_DIR_HOME \"@TARGET_DIR_HOME@\"\n#define FASTFETCH_TARGET_DIR_ETC \"@TARGET_DIR_ETC@\"\n\n#define FASTFETCH_TARGET_DIR_INSTALL_SYSCONF \"@CMAKE_INSTALL_SYSCONFDIR@\"\n"
  },
  {
    "path": "src/fastfetch_datatext.h.in",
    "content": "#pragma once\n\n#define FASTFETCH_DATATEXT_JSON_HELP @DATATEXT_JSON_HELP@\n#define FASTFETCH_DATATEXT_STRUCTURE @DATATEXT_STRUCTURE@\n"
  },
  {
    "path": "src/flashfetch.c",
    "content": "#include \"fastfetch.h\"\n\n#include \"common/init.h\"\n#include \"logo/logo.h\"\n#include \"modules/modules.h\"\n\n// A dirty replicate of neofetch\nint main(void)\n{\n    ffInitInstance(); // Init everything\n\n    // Modify global config here if needed\n    instance.config.display.sizeMaxPrefix = 2; // MB\n    instance.config.display.sizeNdigits = 0;\n    instance.config.display.freqNdigits = 3;\n    instance.config.display.freqSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_NEVER;\n    instance.config.display.sizeSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_NEVER;\n\n    // Some preparation stuff\n    ffStart();\n\n    // Print logo\n    ffLogoPrint();\n\n    // Print all modules\n    {\n        __attribute__((cleanup(ffDestroyTitleOptions))) FFTitleOptions options;\n        ffInitTitleOptions(&options);\n        ffPrintTitle(&options);\n    }\n    {\n        __attribute__((cleanup(ffDestroySeparatorOptions))) FFSeparatorOptions options;\n        ffInitSeparatorOptions(&options);\n        ffPrintSeparator(&options);\n    }\n    {\n        __attribute__((cleanup(ffDestroyOSOptions))) FFOSOptions options;\n        ffInitOSOptions(&options);\n        ffPrintOS(&options);\n    }\n    {\n        __attribute__((cleanup(ffDestroyHostOptions))) FFHostOptions options;\n        ffInitHostOptions(&options);\n        ffPrintHost(&options);\n    }\n    {\n        __attribute__((cleanup(ffDestroyKernelOptions))) FFKernelOptions options;\n        ffInitKernelOptions(&options);\n        ffStrbufSetStatic(&options.moduleArgs.outputFormat, \"{release}\");\n        ffPrintKernel(&options);\n    }\n    {\n        __attribute__((cleanup(ffDestroyUptimeOptions))) FFUptimeOptions options;\n        ffInitUptimeOptions(&options);\n        ffPrintUptime(&options);\n    }\n    {\n        __attribute__((cleanup(ffDestroyPackagesOptions))) FFPackagesOptions options;\n        ffInitPackagesOptions(&options);\n        options.combined = true;\n        ffPrintPackages(&options);\n    }\n    {\n        __attribute__((cleanup(ffDestroyShellOptions))) FFShellOptions options;\n        ffInitShellOptions(&options);\n        ffPrintShell(&options);\n    }\n    {\n        __attribute__((cleanup(ffDestroyDisplayOptions))) FFDisplayOptions options;\n        ffInitDisplayOptions(&options);\n        options.compactType = FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT;\n        ffStrbufSetStatic(&options.moduleArgs.key, \"Resolution\");\n        ffPrintDisplay(&options);\n    }\n    {\n        __attribute__((cleanup(ffDestroyDEOptions))) FFDEOptions options;\n        ffInitDEOptions(&options);\n        ffPrintDE(&options);\n    }\n    {\n        instance.config.general.detectVersion = false;\n        __attribute__((cleanup(ffDestroyWMOptions))) FFWMOptions options;\n        ffInitWMOptions(&options);\n        options.detectPlugin = true;\n        ffPrintWM(&options);\n        instance.config.general.detectVersion = true;\n    }\n    {\n        __attribute__((cleanup(ffDestroyWMThemeOptions))) FFWMThemeOptions options;\n        ffInitWMThemeOptions(&options);\n        ffPrintWMTheme(&options);\n    }\n    {\n        __attribute__((cleanup(ffDestroyThemeOptions))) FFThemeOptions options;\n        ffInitThemeOptions(&options);\n        ffPrintTheme(&options);\n    }\n    {\n        __attribute__((cleanup(ffDestroyIconsOptions))) FFIconsOptions options;\n        ffInitIconsOptions(&options);\n        ffPrintIcons(&options);\n    }\n    {\n        __attribute__((cleanup(ffDestroyTerminalOptions))) FFTerminalOptions options;\n        ffInitTerminalOptions(&options);\n        ffPrintTerminal(&options);\n    }\n    {\n        __attribute__((cleanup(ffDestroyTerminalFontOptions))) FFTerminalFontOptions options;\n        ffInitTerminalFontOptions(&options);\n        ffStrbufSetStatic(&options.moduleArgs.outputFormat, \"{/name}{-}{/}{name}{?size} {size}{?}\");\n        ffPrintTerminalFont(&options);\n    }\n    {\n        __attribute__((cleanup(ffDestroyCPUOptions))) FFCPUOptions options;\n        ffInitCPUOptions(&options);\n        ffPrintCPU(&options);\n    }\n    {\n        __attribute__((cleanup(ffDestroyGPUOptions))) FFGPUOptions options;\n        ffInitGPUOptions(&options);\n        ffStrbufSetStatic(&options.moduleArgs.key, \"GPU\");\n        ffStrbufSetStatic(&options.moduleArgs.outputFormat, \"{name}\");\n        ffPrintGPU(&options);\n    }\n    {\n        __attribute__((cleanup(ffDestroyMemoryOptions))) FFMemoryOptions options;\n        ffInitMemoryOptions(&options);\n        ffStrbufSetStatic(&options.moduleArgs.outputFormat, \"{} / {}\");\n        ffPrintMemory(&options);\n    }\n    {\n        __attribute__((cleanup(ffDestroyBreakOptions))) FFBreakOptions options;\n        ffInitBreakOptions(&options);\n        ffPrintBreak(&options);\n    }\n    {\n        __attribute__((cleanup(ffDestroyColorsOptions))) FFColorsOptions options;\n        ffInitColorsOptions(&options);\n        ffPrintColors(&options);\n    }\n\n    ffLogoPrintRemaining();\n    ffFinish();\n    ffDestroyInstance();\n    return 0;\n}\n"
  },
  {
    "path": "src/logo/ascii/adelie.txt",
    "content": "                 $3 ,-^-___\n$3                /\\\\\\///\n$2refined.$1       /\\\\\\\\//\n$2reliable.$1     /\\\\\\///\n$2ready.$1       /\\\\/////\\\n        __///\\\\\\\\/////\\\n$3     _//////\\\\\\\\\\\\\\////\n$1   ///////$3\\\\\\\\\\\\\\\\\\\\//\n          //////$1\\\\\\\\\\/\n          /////\\\\\\\\\\/\n          /////$3\\\\\\\\/\n         /\\\\///\\\\\\/\n         /\\\\\\/$1\\\\/\n         /\\\\\\\\//\n        //////\n      /// $3\\\\\\\\\\"
  },
  {
    "path": "src/logo/ascii/aeon.txt",
    "content": "⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷\n⣿⡇              ⢸⣿\n⣿⡇   ⢀⣀    ⣀⡀   ⢸⣿\n⣿⣇   ⠸⣿⣄  ⣠⣿⠇   ⣸⣿\n⢹⣿⡄   ⠙⠻⠿⠿⠟⠋   ⢠⣿⡏\n ⠹⣿⣦⡀        ⢀⣴⣿⠏\n  ⠈⠛⢿⣶⣤⣄  ⣠⣤⣶⡿⠛⠁\n$2   ⣠⣴⡿⠿⠛  ⠛⠿⢿⣦⣄\n ⣠⣾⠟⠉        ⠉⠻⣷⣄\n⢰⣿⠏   ⢀⣤⣶⣶⣤⡀   ⠹⣿⡆\n⣿⡟   ⢰⣿⠏⠁⠈⠹⣿⡆   ⢿⣿\n⣿⡇   ⠈⠋    ⠙⠁   ⢸⣿\n⣿⡇              ⢸⣿\n⣿⣷⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣾⣿"
  },
  {
    "path": "src/logo/ascii/aeros.txt",
    "content": "                 ooo OOO OOO ooo\n             oOO                 OOo\n         oOO                         OOo\n      oOO                               OOo\n    oOO                                   OOo\n  oOO                                       OOo\n oOO                                         OOo\n                                              OOo\n                                               OOo\n                                               OOo\n                                               OOo\n                                               OOo\n                                               OOo\noOO                                           OOo\n oOO                                         OOo\n  oOO                                       OOo\n    oOO                                   OOo\n      oO                                OOo\n         oOO                         OOo\n             oOO                 OOo\n                 ooo OOO OOO ooo"
  },
  {
    "path": "src/logo/ascii/aerynos.txt",
    "content": "                       ;llll.\n                      0MMMMMM:\n                     NMMMMMMMMd\n    .@   .cccccccccoWMMMMMMMMMM0\n    @@  .MMMMMMMMMMMMMMMMMMMMMMMN\n                 OMMMMMMMMMMMMMMMW.\n.@             .MMMMMMMMMMMMMMMMMMM.\n.@    dMMMMMMMMMMMMMMMMMMl OMMMMMMMMk\n               .OWMMMMM;   dMMMMMMMMMk\n                            .MMMMMMMMMk\n @@   ooooooooooooooooooo    .MMMMMMMMMN\n .@  oooooMMMMMMMMMMMMP        NMMMMMMMMW.\n         KMMMMMMMMMM.           0MMMMMMMMM:\n        NMMMMMMMMM.              dMMMMMMMMMd\n      .WMMMMMMMW.                  XMMMMMMMMO\n     .MMMMMMMk.                     xMMMMMMMMX"
  },
  {
    "path": "src/logo/ascii/afterglow.txt",
    "content": "                        $2.\n               $1.      $2.{!\n             $1.L!     $2J@||*\n           $1gJJJJL` $2g@FFS\"\n        $1,@FFFJF`$2_g@@LLP`\n      $1_@FFFFF`$2_@@@@@P`        $4.\n    $1J@@@LLF $2_@@@@@P`        $4.J!\n  $1g@@@@@\" $2_@@@@@P`$3.       $4.L|||*\n$1g@@@@M\"     $2\"VP`$3.L!     $4<@JJJJ`\n $1\"@N\"         $3:||||!  $4JFFFFS\"\n           $3.{JJ||F`$4_gFFFF@'\n         $3.@FJJJF`$4,@LFFFF`\n       $3_@FFFFF   $4VLLLP`\n     $3J@@LL@\"      $4`\"\n      $3V@@\""
  },
  {
    "path": "src/logo/ascii/aix.txt",
    "content": "           `:+ssssossossss+-`\n        .oys///oyhddddhyo///sy+.\n      /yo:+hNNNNNNNNNNNNNNNNh+:oy/\n    :h/:yNNNNNNNNNNNNNNNNNNNNNNy-+h:\n  `ys.yNNNNNNNNNNNNNNNNNNNNNNNNNNy.ys\n `h+-mNNNNNNNNNNNNNNNNNNNNNNNNNNNNm-oh\n h+-NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN.oy\n/d`mNNNNNNN/::mNNNd::m+:/dNNNo::dNNNd`m:\nh//NNNNNNN: . .NNNh  mNo  od. -dNNNNN:+y\nN.sNNNNNN+ -N/ -NNh  mNNd.   sNNNNNNNo-m\nN.sNNNNNs  +oo  /Nh  mNNs` ` /mNNNNNNo-m\nh//NNNNh  ossss` +h  md- .hm/ `sNNNNN:+y\n:d`mNNN+/yNNNNNd//y//h//oNNNNy//sNNNd`m-\n yo-NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNm.ss\n `h+-mNNNNNNNNNNNNNNNNNNNNNNNNNNNNm-oy\n   sy.yNNNNNNNNNNNNNNNNNNNNNNNNNNs.yo\n    :h+-yNNNNNNNNNNNNNNNNNNNNNNs-oh-\n      :ys:/yNNNNNNNNNNNNNNNmy/:sy:\n        .+ys///osyhhhhys+///sy+.\n            -/osssossossso/-"
  },
  {
    "path": "src/logo/ascii/almalinux.txt",
    "content": "$1         'c:.\n$1        lkkkx, ..       $2..   ,cc,\n$1        okkkk:ckkx'  $2.lxkkx.okkkkd\n$1        .:llcokkx'  $2:kkkxkko:xkkd,\n$1      .xkkkkdood:  $2;kx,  .lkxlll;\n$1       xkkx.       $2xk'     xkkkkk:\n$1       'xkx.       $2xd      .....,.\n$3      .. $1:xkl'     $2:c      ..''..\n$3    .dkx'  $1.:ldl:'. $2'  $4':lollldkkxo;\n$3  .''lkkko'                     $4ckkkx.\n$3'xkkkd:kkd.       ..  $5;'        $4:kkxo.\n$3,xkkkd;kk'      ,d;    $5ld.   $4':dkd::cc,\n$3 .,,.;xkko'.';lxo.      $5dx,  $4:kkk'xkkkkc\n$3     'dkkkkkxo:.        $5;kx  $4.kkk:;xkkd.\n$3       .....   $5.;dk:.   $5lkk.  $4:;,\n             $5:kkkkkkkdoxkkx\n              ,c,,;;;:xkkd.\n                ;kkkkl...\n                ;kkkkl\n                 ,od;"
  },
  {
    "path": "src/logo/ascii/alpine.txt",
    "content": "       .hddddddddddddddddddddddh.\n      :dddddddddddddddddddddddddd:\n     /dddddddddddddddddddddddddddd/\n    +dddddddddddddddddddddddddddddd+\n  `sdddddddddddddddddddddddddddddddds`\n `ydddddddddddd++hdddddddddddddddddddy`\n.hddddddddddd+`  `+ddddh:-sdddddddddddh.\nhdddddddddd+`      `+y:    .sddddddddddh\nddddddddh+`   `//`   `.`     -sddddddddd\nddddddh+`   `/hddh/`   `:s-    -sddddddd\nddddh+`   `/+/dddddh/`   `+s-    -sddddd\nddd+`   `/o` :dddddddh/`   `oy-    .yddd\nhdddyo+ohddyosdddddddddho+oydddy++ohdddh\n.hddddddddddddddddddddddddddddddddddddh.\n `yddddddddddddddddddddddddddddddddddy`\n  `sdddddddddddddddddddddddddddddddds`\n    +dddddddddddddddddddddddddddddd+\n     /dddddddddddddddddddddddddddd/\n      :dddddddddddddddddddddddddd:\n       .hddddddddddddddddddddddh."
  },
  {
    "path": "src/logo/ascii/alpine2.txt",
    "content": "       .:::::::::::::::::::::.\n      .:::::::::::::::::::::::.\n     .:::::::::::::::::::::::::.\n    .:::::::::::::::::::::::::::.\n   .:::::::::$2,db,$1::::::::::::::::.\n  .::::::::$2,d%%%%b,$1::$2,db,$1:::::::::.\n .:::::::$2,%%%%P'%%%b,d%%%b,$1::::::::.\n.::::::$2,%%%%P,$1:::$2`%%%b'^q%%b,$1:::::::.\n'::::$2,%%%%P,d|$1:::::$2`%%%b:'^%%b,$1:::::'\n '::$2`%%%'$1:$2'q$|$1:::::::$2'q%%b'`q%%b'$1::'\n  ':::::::::::::::::::::::::::::::'\n   ':::::::::::::::::::::::::::::'\n    ':::::::::::::::::::::::::::'\n     ':::::::::::::::::::::::::'\n      ':::::::::::::::::::::::'\n       ':::::::::::::::::::::'\n"
  },
  {
    "path": "src/logo/ascii/alpine2_small.txt",
    "content": "$1   /\\ /\\\n  /$2/ $1\\  \\\n /$2//  $1\\  \\\n/$2//    $1\\  \\\n$2//      $1\\  \\\n         \\"
  },
  {
    "path": "src/logo/ascii/alpine3_small.txt",
    "content": "         ,db,\n       ,d%%%%b,  ,db,\n     ,%%%%P'%%%b,d%%%b,\n   ,%%%%P,   `%%%b'^q%%b,\n ,%%%%P,d|     `%%%b '^%%b,\n`%%%' 'q$|       'q%%b'`q%%b\n"
  },
  {
    "path": "src/logo/ascii/alpine_small.txt",
    "content": "   /\\ /\\\n  // \\  \\\n //   \\  \\\n///    \\  \\\n//      \\  \\\n         \\"
  },
  {
    "path": "src/logo/ascii/alter.txt",
    "content": "                      %,\n                    ^WWWw\n                   'wwwwww\n                  !wwwwwwww\n                 #`wwwwwwwww\n                @wwwwwwwwwwww\n               wwwwwwwwwwwwwww\n              wwwwwwwwwwwwwwwww\n             wwwwwwwwwwwwwwwwwww\n            wwwwwwwwwwwwwwwwwwww,\n           w~1i.wwwwwwwwwwwwwwwww,\n         3~:~1lli.wwwwwwwwwwwwwwww.\n        :~~:~?ttttzwwwwwwwwwwwwwwww\n       #<~:~~~~?llllltO-.wwwwwwwwwww\n      #~:~~:~:~~?ltlltlttO-.wwwwwwwww\n     @~:~~:~:~:~~(zttlltltlOda.wwwwwww\n    @~:~~: ~:~~:~:(zltlltlO    a,wwwwww\n   8~~:~~:~~~~:~~~~_1ltltu          ,www\n  5~~:~~:~~:~~:~~:~~~_1ltq             N,,\n g~:~~:~~~:~~:~~:~:~~~~1q                N,"
  },
  {
    "path": "src/logo/ascii/altlinux.txt",
    "content": "             ##############\n         ######################\n       ##########################\n     ##+$2####$1#######################\n   #####$2#$1*$2###%+$1######################\n  ########$2%$1*#$2%#####$1###################\n ##########$2##$1*#*$2#######%+$1##############\n#############$2%#############%$1############\n#############$2+################$1##########\n##############$2################*$1#########\n##############$2+################+$1########\n###############$2##########$1###$2+##%$1########\n###############$2+########$1######$2###$1#######\n#############$2*####$1############$2%#+$1#######\n############$2+###$3####$1##########$2%#*$1#######\n ##########$2###*$3######$2+#+$1#####$2+##*$1######\n  #########$2##%$3#####$2:%#####$1###$2###*$1#####\n   ########$2%#+$3######$2#############$1####\n     #####$2##%:$3######$2:############$1##\n       ##$2+##*$3########$2############$1\n         $2###$3#########$2##########$1\n             $3########$2######"
  },
  {
    "path": "src/logo/ascii/amazon.txt",
    "content": "             `-/oydNNdyo:.`\n      `.:+shmMMMMMMMMMMMMMMmhs+:.`\n    -+hNNMMMMMMMMMMMMMMMMMMMMMMNNho-\n.``      -/+shmNNMMMMMMNNmhs+/-      ``.\ndNmhs+:.       `.:/oo/:.`       .:+shmNd\ndMMMMMMMNdhs+:..        ..:+shdNMMMMMMMd\ndMMMMMMMMMMMMMMNds    odNMMMMMMMMMMMMMMd\ndMMMMMMMMMMMMMMMMh    yMMMMMMMMMMMMMMMMd\ndMMMMMMMMMMMMMMMMh    yMMMMMMMMMMMMMMMMd\ndMMMMMMMMMMMMMMMMh    yMMMMMMMMMMMMMMMMd\ndMMMMMMMMMMMMMMMMh    yMMMMMMMMMMMMMMMMd\ndMMMMMMMMMMMMMMMMh    yMMMMMMMMMMMMMMMMd\ndMMMMMMMMMMMMMMMMh    yMMMMMMMMMMMMMMMMd\ndMMMMMMMMMMMMMMMMh    yMMMMMMMMMMMMMMMMd\ndMMMMMMMMMMMMMMMMh    yMMMMMMMMMMMMMMMMd\ndMMMMMMMMMMMMMMMMh    yMMMMMMMMMMMMMMMMd\n.:+ydNMMMMMMMMMMMh    yMMMMMMMMMMMNdy+:.\n     `.:+shNMMMMMh    yMMMMMNhs+:``\n            `-+shy    shs+:`"
  },
  {
    "path": "src/logo/ascii/amazon_linux.txt",
    "content": "  ,     $2#_$1\n  ~\\_  $2####_$1\n ~~  \\_$2#####\\$1\n ~~     \\$2###|$1\n ~~       \\$2#/$1 ___\n  ~~       V~' '->\n   ~~~         /\n     ~~._.   _/\n        _/ _/\n      _/m/'"
  },
  {
    "path": "src/logo/ascii/amiga.txt",
    "content": "                        ----.---\n                      :-==.-==-\n$2                     -=== ===:\n                    ===-:===.\n$3                  -+++:=++=\n                 =+++ +++.\n$6               .+++-=++=\n$4.::. :::     $6 :**+ +**-\n$4 :--:.---. $6 .+**=-***.\n$5  :--- ---::$6***:***+\n$5   .---::-$6=##*.*##-\n$7     ----+$6##=:##*.\n$7      :-$6###-*##+\n"
  },
  {
    "path": "src/logo/ascii/amogos.txt",
    "content": "            ___________\n           /           \\\n          /   $2______$1    \\\n         /   $2/      \\$1    \\\n         |  $2(        )$1    \\\n        /    $2\\______/$1     |\n        |                 |\n       /                   \\\n       |                   |\n       |                   |\n      /                    |\n      |                    |\n      |     _______        |\n ____/     /       \\       |\n/          |       |       |\n|          /   ____/       |\n\\_________/   /            |\n              \\         __/\n               \\_______/"
  },
  {
    "path": "src/logo/ascii/anarchy.txt",
    "content": "                         $2..$1\n                        $2..$1\n                      $2:..$1\n                    $2:+++.$1\n              .:::++$2++++$1+::.\n          .:+######$2++++$1######+:.\n       .+#########$2+++++$1##########:.\n     .+##########$2+++++++$1##$2+$1#########+.\n    +###########$2+++++++++$1############:\n   +##########$2++++++$1#$2++++$1#$2+$1###########+\n  +###########$2+++++$1###$2++++$1#$2+$1###########+\n :##########$2+$1#$2++++$1####$2++++$1#$2+$1############:\n ###########$2+++++$1#####$2+++++$1#$2+$1###$2++$1######+\n.##########$2++++++$1#####$2++++++++++++$1#######.\n.##########$2+++++++++++++++++++$1###########.\n #####$2++++++++++++++$1###$2++++++++$1#########+\n :###$2++++++++++$1#########$2+++++++$1#########:\n  +######$2+++++$1##########$2++++++++$1#######+\n   +####$2+++++$1###########$2+++++++++$1#####+\n    :##$2++++++$1############$2++++++++++$1##:\n     .$2++++++$1#############$2++++++++++$1+.\n      :$2++++$1###############$2+++++++$1::\n     .$2++. .:+$1##############$2+++++++$1..\n     $2.:.$1      ..::++++++::..:$2++++$1+.\n     $2.$1                       $2.:+++$1.\n                                $2.:$1:\n                                   $2..$1\n                                    $2..$1"
  },
  {
    "path": "src/logo/ascii/android.txt",
    "content": "         -o          o-\n          +hydNNNNdyh+\n        +mMMMMMMMMMMMMm+\n      `dMM$2m:$1NMMMMMMN$2:m$1MMd`\n      hMMMMMMMMMMMMMMMMMMh\n  ..  yyyyyyyyyyyyyyyyyyyy  ..\n.mMMm`MMMMMMMMMMMMMMMMMMMM`mMMm.\n:MMMM-MMMMMMMMMMMMMMMMMMMM-MMMM:\n:MMMM-MMMMMMMMMMMMMMMMMMMM-MMMM:\n:MMMM-MMMMMMMMMMMMMMMMMMMM-MMMM:\n:MMMM-MMMMMMMMMMMMMMMMMMMM-MMMM:\n-MMMM-MMMMMMMMMMMMMMMMMMMM-MMMM-\n +yy+ MMMMMMMMMMMMMMMMMMMM +yy+\n      mMMMMMMMMMMMMMMMMMMm\n      `/++MMMMh++hMMMM++/`\n          MMMMo  oMMMM\n          MMMMo  oMMMM\n          oNMm-  -mMNs"
  },
  {
    "path": "src/logo/ascii/android_small.txt",
    "content": "  ;,           ,;\n   ';,.-----.,;'\n  ,'           ',\n /    O     O    \\\n|                 |\n'-----------------'"
  },
  {
    "path": "src/logo/ascii/anduinos.txt",
    "content": "                $1+++++++++++\n            +++++++++++++++++++++\n        +++++++++++++++++++++++++++++++\n=+++++++++++++++++++++++++++++++++++++++=\n+++++++++++++++++++++++++++++++++++++++++++++\n=++++++++++++++++++++++++++++++++++++++++++++\n    ==+++++++++++++++++++++++++++++++++==\n        +++++++++++++++++++++++++++\n$2****          $1++=+++++++++++=++          $2****\n**********         $1+++++++         $2**********\n**************               **************\n    ****************   ****************\n            ***********************\n******          *************          ******\n***********          ***          ***********\n***************           ***************\n        *************** ***************\n            *********************\n                ***********\n"
  },
  {
    "path": "src/logo/ascii/antergos.txt",
    "content": "$2              `.-/::/-``\n            .-/osssssssso/.\n           :osyysssssssyyys+-\n        `.+yyyysssssssssyyyyy+.\n       `/syyyyyssssssssssyyyyys-`\n      `/yhyyyyysss$1++$2ssosyyyyhhy/`\n     .ohhhyyyys$1o++/+o$2so$1+$2syy$1+$2shhhho.\n    .shhhhys$1oo++//+$2sss$1+++$2yyy$1+s$2hhhhs.\n   -yhhhhs$1+++++++o$2ssso$1+++$2yyy$1s+o$2hhddy:\n  -yddhhy$1o+++++o$2syyss$1++++$2yyy$1yooy$2hdddy-\n .yddddhs$1o++o$2syyyyys$1+++++$2yyhh$1sos$2hddddy`\n`odddddhyosyhyyyyyy$1++++++$2yhhhyosddddddo\n.dmdddddhhhhhhhyyyo$1+++++$2shhhhhohddddmmh.\nddmmdddddhhhhhhhso$1++++++$2yhhhhhhdddddmmdy\ndmmmdddddddhhhyso$1++++++$2shhhhhddddddmmmmh\n-dmmmdddddddhhys$1o++++o$2shhhhdddddddmmmmd-\n.smmmmddddddddhhhhhhhhhdddddddddmmmms.\n   `+ydmmmdddddddddddddddddddmmmmdy/.\n      `.:+ooyyddddddddddddyyso+:.`"
  },
  {
    "path": "src/logo/ascii/antix.txt",
    "content": "                    \\\n         , - ~ ^ ~ - \\        /\n     , '              \\ ' ,  /\n   ,                   \\   '/\n  ,                     \\  / ,\n ,___,                   \\/   ,\n /   |   _  _  _|_ o     /\\   ,\n|,   |  / |/ |  |  |    /  \\  ,\n \\,_/\\_/  |  |_/|_/|_/_/    \\,\n   ,                  /     ,\\\n     ,               /  , '   \\\n      ' - , _ _ _ ,  '"
  },
  {
    "path": "src/logo/ascii/anushos.txt",
    "content": " $4#######################\n $4#        $2#####        $4#\n $4#       $2#######       $4#\n $4#       $2##$5O$2#$5O$2##       $4#\n $4#       $2#$3#####$2#       $4#\n $4#     $2##$1##$3###$1##$2##     $4#\n $4#    $2#$1##########$2##    $4#\n $4#   $2#$1############$2##   $4#\n $4#  $2#$1######$5A_O$1####$2###  $4#\n $4# $3##$2#$1############$2##$3## $4#\n $4#$3######$2#$1#######$2#$3######$4#\n $4#$3#######$2#$1#####$2#$3#######$4#\n $4#  $3#####$2#######$3#####  $4#\n $4#######################\n $4#$5╔═╗╔╗╔╦ ╦╔═╗╦ ╦╔═╗╔═╗$4#\n $4#$5╠═╣║║║║ ║╚═╗╠═╣║ ║╚═╗$4#\n $4#$5╩ ╩╝╚╝╚═╝╚═╝╩ ╩╚═╝╚═╝$4#\n $4#######################\n $4#   $3WWW.ANUSHOS.ORG   $4#\n $4#######################\n"
  },
  {
    "path": "src/logo/ascii/aoscos.txt",
    "content": "                $2__\n             $2gpBBBBBBBBBP\n         $2_gBBBBBBBBBRP\n       $24BBBBBBBBRP  $4,_____\n            $2`\"\" $4_g@@@@@@@@@@@@@%g>\n            $4__@@@@@@@@@@@@@@@@P\"  $1___\n         $4_g@@@@@@@@@@@@@@@N\"` $1_gN@@@@@N^\n     $4_w@@@@@@@@@@@@@@@@P\" $1_g@@@@@@@P\"\n  $4_g@@@@@@@@@@@@@@@N\"`  $1VMNN@NNNM^`\n$4^MMM@@@@@@@@@@@MP\" $3,ggppww__\n        $4`\"\"\"\"\" $3_wNNNNNNNNNNNNNNNNNNN\n            $3_gBNNNNNNNNNNNNNNNNNP\"\n        $3_wNNNNNNNNNNNNNNNNNNMP`\n     $3_gBNNNNNNNNNNNNNNNNNP\"\n $3_wNNNNNNNNNNNNNNNNNNNM^\n $3\"\"Y^^MNNNNNNNNNNNNP`\n         $3`\"\"\"\"\"\"\""
  },
  {
    "path": "src/logo/ascii/aoscos_old.txt",
    "content": "             .:+syhhhhys+:.\n         .ohNMMMMMMMMMMMMMMNho.\n      `+mMMMMMMMMMMmdmNMMMMMMMMm+`\n     +NMMMMMMMMMMMM/   `./smMMMMMN+\n   .mMMMMMMMMMMMMMMo        -yMMMMMm.\n  :NMMMMMMMMMMMMMMMs          .hMMMMN:\n .NMMMMhmMMMMMMMMMMm+/-         oMMMMN.\n dMMMMs  ./ymMMMMMMMMMMNy.       sMMMMd\n-MMMMN`      oMMMMMMMMMMMN:      `NMMMM-\n/MMMMh       NMMMMMMMMMMMMm       hMMMM/\n/MMMMh       NMMMMMMMMMMMMm       hMMMM/\n-MMMMN`      :MMMMMMMMMMMMy.     `NMMMM-\n dMMMMs       .yNMMMMMMMMMMMNy/. sMMMMd\n .NMMMMo         -/+sMMMMMMMMMMMmMMMMN.\n  :NMMMMh.          .MMMMMMMMMMMMMMMN:\n   .mMMMMMy-         NMMMMMMMMMMMMMm.\n     +NMMMMMms/.`    mMMMMMMMMMMMN+\n      `+mMMMMMMMMNmddMMMMMMMMMMm+`\n         .ohNMMMMMMMMMMMMMMNho.\n             .:+syhhhhys+:."
  },
  {
    "path": "src/logo/ascii/aoscosretro.txt",
    "content": "$2          .........\n     ...................\n   .....................$1################$2\n ..............     ....$1################$2\n..............       ...$1################$2\n.............         ..$1****************$2\n............     .     .$1****************$2\n...........     ...     $1................$2\n..........     .....     $1...............$2\n.........     .......     ...\n .$3......                   $2.\n  $3.....      .....$2....    $4...........\n  $3....      ......$2.       $4...........\n  $3...      .......        $4...........\n  $3................        $4***********\n  $3................        $4###########\n  $3****************\n  $3################"
  },
  {
    "path": "src/logo/ascii/aoscosretro_small.txt",
    "content": "$2    _____   $1_____$2\n  -'     '-$1|     |$2\n /     ___ $1|     |$2\n|     / _ \\$1|_____|$2\n'    / /_\\ \\\n \\  / _____ \\$4___\n  $3|$2/_/  $3|   $4|   |\n  $3|     |   $4|___|\n  $3|_____|"
  },
  {
    "path": "src/logo/ascii/aperture.txt",
    "content": "              .,-:;//;:=,\n          $8. $1:H@@@MM@M#H/.$2,+%;,\n       $8,/X+ $1+M@@M@MM%=,$2-%HMMM@X/,\n     $8-+@MM; $1$$M@@MH+-,$2;XMMMM@MMMM@+-\n    $8;@M@@M- $1XM@X;. $2-+XXXXXHHH@M@M#@/.\n  $8,%MM@@MH $1,@%=             $2.---=-=:=,.\n  $8=@#@@@MX.,                $3-%HX$$$$%%%:;\n $7=-$8./@M@M$$                   $3.;@MMMM@MM:\n $7X@/$8 -$$MM/                    $3. +MM@@@M$$\n$7,@M@H:$8 :@:                    $4. $3=X#@@@@-\n$7,@@@MMX,$8 .                    $4/H- $3;@M@M=\n$7.H@@@@M@+,                    $4%MM+. $3%#$.\n $7/MMMM@MMH/.                  $4XM@MH; $3=;\n  $7/%+%$$XHH@$$=              $5, $4.H@@@@MX,\n   $6=----------.          $5-%H.$4,@@@@@MX,\n   $6.%MM@@@HHHXX$$$$$$%+- $5.:$$MMX$4 =M@@MM%.\n     $6=XMMM@MM@MM#H;$5,-+HMM@M+$4 /MMMX=\n       $6=%@M@M#@$$-$5.=$@MM@@@M;$4 %M%=\n         $6,:+$$+-$5,/H#MMMMMMM@=$4 =,\n               $5=++%%%%+/:-."
  },
  {
    "path": "src/logo/ascii/apricity.txt",
    "content": "                                    ./o-\n          ``...``              `:. -/:\n     `-+ymNMMMMMNmho-`      :sdNNm/\n   `+dMMMMMMMMMMMMMMMmo` sh:.:::-\n  /mMMMMMMMMMMMMMMMMMMMm/`sNd/\n oMMMMMMMMMMMMMMMMMMMMMMMs -`\n:MMMMMMMMMMMMMMMMMMMMMMMMM/\nNMMMMMMMMMMMMMMMMMMMMMMMMMd\nMMMMMMMmdmMMMMMMMMMMMMMMMMd\nMMMMMMy` .mMMMMMMMMMMMmho:`\nMMMMMMNo/sMMMMMMMNdy+-.`-/\nMMMMMMMMMMMMNdy+:.`.:ohmm:\nMMMMMMMmhs+-.`.:+ymNMMMy.\nMMMMMM/`.-/ohmNMMMMMMy-\nMMMMMMNmNNMMMMMMMMmo.\nMMMMMMMMMMMMMMMms:`\nMMMMMMMMMMNds/.\ndhhyys+/-`"
  },
  {
    "path": "src/logo/ascii/arch.txt",
    "content": "                  -`\n                 .o+`\n                `ooo/\n               `+oooo:\n              `+oooooo:\n              -+oooooo+:\n            `/:-:++oooo+:\n           `/++++/+++++++:\n          `/++++++++++++++:\n         `/+++o$2oooooooo$1oooo/`\n        ./$2ooosssso++osssssso$1+`\n$2       .oossssso-````/ossssss+`\n      -osssssso.      :ssssssso.\n     :osssssss/        osssso+++.\n    /ossssssss/        +ssssooo/-\n  `/ossssso+/:-        -:/+osssso+-\n `+sso+:-`                 `.-/+oso:\n`++:.                           `-/+/\n.`                                 `/"
  },
  {
    "path": "src/logo/ascii/arch2.txt",
    "content": "                  ▄\n                 ▟█▙\n                ▟███▙\n               ▟█████▙\n              ▟███████▙\n             ▂▔▀▜██████▙\n            ▟██▅▂▝▜█████▙\n           ▟█████████████▙\n          ▟███████████████▙\n         ▟█████████████████▙\n        ▟███████████████████▙\n$2       ▟█████████▛▀▀▜████████▙\n      ▟████████▛      ▜███████▙\n     ▟█████████        ████████▙\n    ▟██████████        █████▆▅▄▃▂\n   ▟██████████▛        ▜█████████▙\n  ▟██████▀▀▀              ▀▀██████▙\n ▟███▀▘                       ▝▀███▙\n▟▛▀                               ▀▜▙"
  },
  {
    "path": "src/logo/ascii/arch3.txt",
    "content": "                  .\n                 / \\\n                /   \\\n               /     \\\n              /       \\\n             />,       \\\n            /  `*.      \\\n           /      `      \\\n          /               \\\n         /                 \\\n$2        /      ,.-+-..      \\\n       /      ,/'   `\\.      \\\n      /      .|'     `|.   _  \\\n     /       :|.     ,|;    `+.\\\n    /        .\\:     ;/,      \"<\\\n   /     __,--+\"     \"+--.__     \\\n  /  _,+'\"                 \"'+._  \\\n /,-'                           `-.\\\n'                                   '"
  },
  {
    "path": "src/logo/ascii/arch_old.txt",
    "content": "             __\n         _=(SDGJT=_\n       _GTDJHGGFCVS)\n      ,GTDJGGDTDFBGX0\n$1     JDJDIJHRORVFSBSVL$2-=+=,_\n$1    IJFDUFHJNXIXCDXDSV,$2  \"DEBL\n$1   [LKDSDJTDU=OUSCSBFLD.$2   '?ZWX,\n$1  ,LMDSDSWH'     `DCBOSI$2     DRDS],\n$1  SDDFDFH'         !YEWD,$2   )HDROD\n$1 !KMDOCG            &GSU|$2\\_GFHRGO\\'\n$1 HKLSGP'$2           __$1\\TKM0$2\\GHRBV)'\n$1JSNRVW'$2       __+MNAEC$1\\IOI,$2\\BN'\n$1HELK['$2    __,=OFFXCBGHC$1\\FD)\n$1?KGHE $2\\_-#DASDFLSV='$1    'EF\n'EHTI                    !H\n `0F'                    '!"
  },
  {
    "path": "src/logo/ascii/arch_small.txt",
    "content": "      /\\\n     /  \\\n    /    \\\n   /      \\\n$2  /   ,,   \\\n /   |  |   \\\n/_-''    ''-_\\"
  },
  {
    "path": "src/logo/ascii/archbox.txt",
    "content": "              ...:+oh/:::..\n         ..-/oshhhhhh`   `::::-.\n     .:/ohhhhhhhhhhhh`        `-::::.\n .+shhhhhhhhhhhhhhhhh`             `.::-.\n /`-:+shhhhhhhhhhhhhh`            .-/+shh\n /      .:/ohhhhhhhhh`       .:/ohhhhhhhh\n /           `-:+shhh`  ..:+shhhhhhhhhhhh\n /                 .:ohhhhhhhhhhhhhhhhhhh\n /                  `hhhhhhhhhhhhhhhhhhhh\n /                  `hhhhhhhhhhhhhhhhhhhh\n /                  `hhhhhhhhhhhhhhhhhhhh\n /                  `hhhhhhhhhhhhhhhhhhhh\n /      .+o+        `hhhhhhhhhhhhhhhhhhhh\n /     -hhhhh       `hhhhhhhhhhhhhhhhhhhh\n /     ohhhhho      `hhhhhhhhhhhhhhhhhhhh\n /:::+`hhhhoos`     `hhhhhhhhhhhhhhhhhs+`\n    `--/:`   /:     `hhhhhhhhhhhho/-\n             -/:.   `hhhhhhs+:-`\n                ::::/ho/-`"
  },
  {
    "path": "src/logo/ascii/archcraft.txt",
    "content": "$1⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⢰⡆$1⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄\n$2⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⢠⣿⣿⡄$2⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄\n$3⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⢀⣾⣿⣿⣿⡀$3⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄\n$4⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⣼⣿⣿⣿⣿⣷⡀$4⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄\n$5⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⣼⣿⣿⣿⣿⣿⣿⣷$5⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄\n$6⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⢼⣿⣿⣿⣿⣿⣿⣿⣿⣧$6⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄\n$1⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⣰⣤⣈⠻⢿⣿⣿⣿⣿⣿⣿⣧$1⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄\n$2⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⣰⣿⣿⣿⣿⣮⣿⣿⣿⣿⣿⣿⣿⣧$2⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄\n$3⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧$3⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄\n$4⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧$4⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄\n$5⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧$5⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄\n$6⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⣼⣿⣿⣿⣿⣿⡿⣿⣿⡟$6⠄⠄$1⠸⣿⣿⡿⣿⣿⣿⣿⣿⣷⡀$6⠄⠄⠄⠄⠄⠄⠄⠄\n$1⠄⠄⠄⠄⠄⠄⠄⠄$1⣼⣿⣿⣿⣿⣿⡏$1⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⠈⣿⣿⣿⣿⣿⣷⡀$1⠄⠄⠄⠄⠄⠄⠄\n$2⠄⠄⠄⠄⠄⠄$1⢀⣼⣿⣿⣿⣿⣿⣿⡗$2⠄⠄⠄$1⢀⣠⣤⣀⠄⠄⠄$1⠸⣿⣿⣿⣿⣿⣿⣷⡀$2⠄⠄⠄⠄⠄⠄\n$3⠄⠄⠄⠄⠄$1⢀⣾⣿⣿⣿⣿⣿⡏⠁$3⠄⠄⠄$1⢠⣿⣿⣿⣿⡇$3⠄⠄⠄⠄$1⢙⣿⣿⣻⠿⣿⣷⡀$3⠄⠄⠄⠄⠄\n$4⠄⠄⠄⠄$1⢀⣾⣿⣿⣿⣿⣿⣿⣷⣤⡀$4⠄⠄⠄$1⠻⣿⣿⡿⠃$4⠄⠄⠄$1⢀⣼⣿⣿⣿⣿⣦⣌⠙⠄$4⠄⠄⠄⠄\n$5⠄⠄⠄$1⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏$5⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⢿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀$5⠄⠄⠄\n$6⠄⠄$1⢠⣿⣿⣿⣿⣿⣿⣿⡿⠟⠋⠁$6⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⠙⠻⣿⣿⣿⣿⣿⣿⣿⣿⡄$6⠄⠄\n$1⠄$1⣠⣿⣿⣿⣿⠿⠛⠋⠁$1⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⠉⠙⠻⢿⣿⣿⣿⣿⣆$1⠄\n$1⡰⠟⠛⠉⠁$2⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⠉⠙⠛⠿⢆"
  },
  {
    "path": "src/logo/ascii/archcraft2.txt",
    "content": "                   -o\\\n                  :ooo:\n                 .ooooo.\n                 ooooooo.\n                +oooooooo.\n               -oooooooooo.\n              --:-+oooooooo.\n             yooo+=+sooooooo.\n            yoooooosooooooooo.\n           y+ooooooooooooooooo.\n          yoooooooooooooooooooo`\n         yoooooo+oo=  :oo++ooooo`\n        :oooooo.           +ooooo-\n       -ooooooo.   .::.    +ooosoo=\n      -oooooo`    .oooo`     +os-=o=\n     =ooooooo=:    `oo+    :=ooo=--`.\n    +ooooooooos.          .=sooooooo+-\n  .+osossos+-`              `-+osososs+.\n :sss+=-:`                     `:-=+ssss:\n:=-:`                                `-=+:"
  },
  {
    "path": "src/logo/ascii/archlabs.txt",
    "content": "                     'c'\n                    'kKk,\n                   .dKKKx.\n                  .oKXKXKd.\n                 .l0XXXXKKo.\n                 c0KXXXXKX0l.\n                :0XKKOxxOKX0l.\n               :OXKOc. .c0XX0l.\n              :OK0o. $2...$1'dKKX0l.\n             :OX0c  $2;xOx'$1'dKXX0l.\n            :0KKo.$2.o0XXKd'.$1lKXX0l.\n           c0XKd.$2.oKXXXXKd..$1oKKX0l.\n         .c0XKk;$2.l0K0OO0XKd..$1oKXXKo.\n        .l0XXXk:$2,dKx,.'l0XKo.$1.kXXXKo.\n       .o0XXXX0d,$2:x;   .oKKx'$1.dXKXXKd.\n      .oKXXXXKK0c.$2;.    :00c'$1cOXXXXXKd.\n     .dKXXXXXXXXk,$2.     cKx'$1'xKXXXXXXKx'\n    'xKXXXXK0kdl:.     $2.ok; $1.cdk0KKXXXKx'\n   'xKK0koc,..         $2'c, $1    ..,cok0KKk,\n  ,xko:'.             $2.. $1           .':okx;\n .,'.                                   .',."
  },
  {
    "path": "src/logo/ascii/archstrike.txt",
    "content": "$1                   *\n                  **.\n                 ****\n                ******\n                *******\n              ** *******\n             **** *******\n            $1****$2_____$1***$2/$1*\n           ***$2/$1*******$2//$1***\n          **$2/$1********$2///$1*$2/$1**\n         **$2/$1*******$2////$1***$2/$1**\n        **$2/$1****$2//////.,$1****$2/$1**\n       ***$2/$1*****$2/////////$1**$2/$1***\n      ****$2/$1****    $2/////$1***$2/$1****\n     ******$2/$1***  $2////   $1**$2/$1******\n    ********$2/$1* $2///      $1*$2/$1********\n  ,******     $2// ______ /    $1******,"
  },
  {
    "path": "src/logo/ascii/arco.txt",
    "content": "                   /-\n                  ooo:\n                 yoooo/\n                yooooooo\n               yooooooooo\n              yooooooooooo\n            .yooooooooooooo\n           .oooooooooooooooo\n          .oooooooarcoooooooo\n         .ooooooooo-oooooooooo\n        .ooooooooo-  oooooooooo\n       :ooooooooo.    :ooooooooo\n      :ooooooooo.      :ooooooooo\n     :oooarcooo         .oooarcooo\n    :ooooooooy           .ooooooooo\n   $1:ooooooooo   $2/ooooooooooooooooooo\n  $1:ooooooooo      $2.-ooooooooooooooooo.\n  $1ooooooooo-            $2-ooooooooooooo.\n $1ooooooooo-                $2.-oooooooooo.\n$1ooooooooo.                    $2-ooooooooo"
  },
  {
    "path": "src/logo/ascii/arco_small.txt",
    "content": "          A\n         ooo\n        ooooo\n       ooooooo\n      ooooooooo\n     ooooo ooooo\n    ooooo   ooooo\n   ooooo     ooooo\n  ooooo  $2<oooooooo>$1\n ooooo      $2<oooooo>$1\nooooo          $2<oooo>"
  },
  {
    "path": "src/logo/ascii/arkane.txt",
    "content": "                    .:..\n                 ..:::......\n           $2.$1   .$2.$1.....\n           $2+=$1...$2==$1....\n   ......:.$3:-$2:$1..$3+*$2=$1....\n            $2:----::$1......\n          $2.=***##*+=:    $1..\n         $2=$3***######*$2=\n          $2.$3-*######+\n         $2:+$3###%%%###$1:\n         $2-+*$3########+$1.\n         $2=++*$3#######$1-\n        $2-+=+**$3*####$1=\n     $1.$2-=++==***$3##*$1-\n    $2-++++++==++++=\n  .-+++**+++=+===$1.\n$2:---===++++=-=--$1.\n$2-===============$1-==--:\n$2.-==+++***++*$3*#########$1=:::.\n $2.-=++++*++++**$3#######%%###$1=\n   $2.:==++++++**$3#############$1:\n            $2.$1-+*++*+++==$3###$1+\n                       -$3*+$1:"
  },
  {
    "path": "src/logo/ascii/armbian.txt",
    "content": "                ..\n            `:]x**j-,'\n       .,+t***********z\\<\"\n       ?******************;\n      '*n` .'`^,;;,^`'. ,cc.\n      -<.                .[l\n     //     ^^      ^^    \\\\\n     !^         $2^^$1         \":\n    'tt}`     $2!~]rj_$1     \")t/.\n    Itttt?'   $2~~]rr]$1   `{tttt,\n    \\tttttt!\"\"I$2_]r($1\"\"\"~tttttt1\n  '_tttttttttttt$2)f$1tttttttttttti.\n \\*ztttttttttttttttttttttttttf**[\nl**c)tttttttttttttttttttttttt(z**,\n.z*x.`tttttttttttttttttttttttt.`u*n\n>`   (tttttttttttttttttttttt]   \"I\n     ,tttttttttttttttttttttt`\n     ./ttttt$2f$1tttttttt$2f$1ttttt(\n      'I)$2))(\\()($1tt$2))|\\()($1{;'\n        $2.~~~~~~~|)~~~~~~~<$1\n        '$2[)))))1$1|($2)))))))$1?\n          $2\",,,\"    \",,,^"
  },
  {
    "path": "src/logo/ascii/armbian2.txt",
    "content": "   █ █ █ █ █ █ █ █ █ █ █\n  ███████████████████████\n▄▄██                   ██▄▄\n▄▄██    ███████████    ██▄▄\n▄▄██   ██         ██   ██▄▄\n▄▄██   ██         ██   ██▄▄\n▄▄██   ██         ██   ██▄▄\n▄▄██   █████████████   ██▄▄\n▄▄██   ██         ██   ██▄▄\n▄▄██   ██         ██   ██▄▄\n▄▄██   ██         ██   ██▄▄\n▄▄██                   ██▄▄\n  ███████████████████████\n   █ █ █ █ █ █ █ █ █ █ █"
  },
  {
    "path": "src/logo/ascii/arselinux.txt",
    "content": "                      ⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⠀⣶⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⠀⠀⠀⠀⣴⣶⠀⠀⠀⠀⠀\n⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣄⠀⠀⠀⠀⣼⠟⠁⠀⠀⢀⣀⠀\n⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀⢀⣤⡀⠀⠀⠀⠉⢻⣷⡄⠀⠀⠁⠀⢀⣤⣾⡿⠟⠀\n⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣷⣿⠏⠀⠀⠀⠀⠀⠀⠹⣿⡄⠀⠀⠀⠙⠉⠁⠀⠀⠀\n⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⡄⠀⠀⠀⠀⠀⠀⠀⢹⣿⠀⠀⠀⠀⠠⣶⣶⣶⡶\n⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣿⠀⠀⠀⠀⠀⠀⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀\n⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⠀⠀⠀⠀⠀⢠⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀\n⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠂⠀⠀⠀⠀⠀⢀⣾⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⠇⠀⠀⠀⠀⠀⣠⣾⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⣿⣇⣀⣀⣀⣠⣴⣾⣿⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⢸⣿⠀⠀⠀⠀⠀⣤⣤⣴⣶⣾⠿⠟⣿⡏⠙⠛⠛⠛⠋⠉⢀⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⠀⣿⡄⠀⠀⠀⠀⠈⠉⠉⠀⠀⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀⢸⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⠀⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⠇⠀⠀⠀⠀⠀⠀⠘⠿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀"
  },
  {
    "path": "src/logo/ascii/artix.txt",
    "content": "                   '\n                  'o'\n                 'ooo'\n                'ooxoo'\n               'ooxxxoo'\n              'oookkxxoo'\n             'oiioxkkxxoo'\n            ':;:iiiioxxxoo'\n               `'.;::ioxxoo'\n          '-.      `':;jiooo'\n         'oooio-..     `'i:io'\n        'ooooxxxxoio:,.   `'-;'\n       'ooooxxxxxkkxoooIi:-.  `'\n      'ooooxxxxxkkkkxoiiiiiji'\n     'ooooxxxxxkxxoiiii:'`     .i'\n    'ooooxxxxxoi:::'`       .;ioxo'\n   'ooooxooi::'`         .:iiixkxxo'\n  'ooooi:'`                `'';ioxxo'\n 'i:'`                          '':io'\n'`                                   `'"
  },
  {
    "path": "src/logo/ascii/artix2.txt",
    "content": "                   .:                   \n                  .==:                  \n                 .====:                 \n                .======:                \n               .========:               \n              .==========:              \n             .============:             \n              .-===========:            \n                 .:-========:           \n          .-:.       .:======:          \n         .======:..      :-===.         \n        .============:..    .:=.        \n       .==================:..           \n      .=======================:         \n     .====================::.    .      \n     ================::.      :-===     \n    ============-:.       .:========    \n   ========-:.           :-==========   \n  =====:.                    ..:-=====  \n =::.                              ..:: \n"
  },
  {
    "path": "src/logo/ascii/artix2_small.txt",
    "content": "      /\\\n     /  \\\n    /`'.,\\\n   /     ',\n  /      ,`\\\n /   ,.'`.  \\\n/.,'`     `'.\\\n"
  },
  {
    "path": "src/logo/ascii/artix_small.txt",
    "content": "            '\n           'A'\n          'ooo'\n         'ookxo'\n         `ookxxo'\n       '.   `ooko'\n      'ooo`.   `oo'\n     'ooxxxoo`.   `'\n    'ookxxxkooo.`   .\n   'ookxxkoo'`   .'oo'\n  'ooxoo'`     .:ooxxo'\n 'io'`             `'oo'\n'`                     `'\n"
  },
  {
    "path": "src/logo/ascii/arya.txt",
    "content": "$1                `oyyy/$2-yyyyyy+\n$1               -syyyy/$2-yyyyyy+\n$1              .syyyyy/$2-yyyyyy+\n$1              :yyyyyy/$2-yyyyyy+\n$1           `/ :yyyyyy/$2-yyyyyy+\n$1          .+s :yyyyyy/$2-yyyyyy+\n$1         .oys :yyyyyy/$2-yyyyyy+\n$1        -oyys :yyyyyy/$2-yyyyyy+\n$1       :syyys :yyyyyy/$2-yyyyyy+\n$1      /syyyys :yyyyyy/$2-yyyyyy+\n$1     +yyyyyys :yyyyyy/$2-yyyyyy+\n$1   .oyyyyyyo. :yyyyyy/$2-yyyyyy+ ---------\n$1  .syyyyyy+`  :yyyyyy/$2-yyyyy+-+syyyyyyyy\n$1 -syyyyyy/    :yyyyyy/$2-yyys:.syyyyyyyyyy\n$1:syyyyyy/     :yyyyyy/$2-yyo.:syyyyyyyyyyy"
  },
  {
    "path": "src/logo/ascii/asahi.txt",
    "content": "                   ##  $2**\n                $1*####$2****.\n                  $1###$2,\n               $3...,$1/#$3,,,..\n          $3/*,,,,,,,,$1*$3,........$4,,\n        $3,((((((//*,,,,,,,,$4,......\n       $3(((((((((((((($5%..$4..........\n     $3,((((((((((((((($5@@($4............\n    $3((((((((((((((((($5@@@@/$4............\n  $3,(((((((((((((((((($5@@@@@&*$4...........\n $3(((((((((((((((((((($5@@@@@@@&$4,...........\n$3((((((((((((((((((((($5@@@$6&%&$5@@@%$4,..........\n $3/((((((((((((((((((($5@@@$6&%%&$5@@@@($4........\n    $3,(((((((((((((((($5@@@$6&&$5@@&/&@@@/$4..\n        $3/(((((((((((($5@@@@@@/$4.../&&\n           $3.((((((((($5@@@@($4....\n               $3/((((($5@@#$4...\n                  $3.(($4&,"
  },
  {
    "path": "src/logo/ascii/asahi2.txt",
    "content": "                 $1_wwM $2_ww\n                  $1MMM$2MMMM\n                   $1MM\n           $3_ww##############yy_\n          $4wMMMMM$3###########$4MMMMm\n        $4,MMMMMMMMM$3######$4MMMMMMMM0_\n       $4wMMMMMMMMMMMMM$5MM$60MMMMMMMMMMm\n     $4,MMMMMMMMMMMMMMM$5MMMM$60MMMMMMMMM0,\n    $4wMMMMMMMMMMMMMMMM$5MMMMM0$6MMMMMMMMMMb\n  $4_MMMMMMMMMMMMMMMMMM$5MMMMMMM0$6MMMMMMMMM0,\n $4_MMMMMMMMMMMMMMMMMMM$5MMMM$7M$5MMMW$6MMMMMMMMMM_\n$4_MMMMMMMMMMMMMMMMMMMM$5MMMM$7M0$5MMMW$6MMMMMMMMMM_\n   $4~MMMMMMMMMMMMMMMMM$5MMMM$7M$5MMMW$6MMMMM00~\n       $4~MMMMMMMMMMMMM$5MMMWMMM$60MM$5MMM$6~\n          $4~MMMMMMMMMM$5MMMMM$60MMM~~\n              $4~MMMMMM$5MMM$60MM~\n                  $4~MM$5M$6@~\n                    $4M"
  },
  {
    "path": "src/logo/ascii/aster.txt",
    "content": "                ...''...\n           .;oOXWMWNXXXNMMN0d:.\n        .oXMWOo;..       ..:oO;\n      ;KMWx,       co,\n    'KMNl         dMMW.\n   oMMx          xMMMMk\n  xMM:          dMMMMMM;\n cMMl          dMMMMMMMW\n NMK          xMMMx::dXMx\n,MMl         xMMN'     .o.\ncMM;        dMMW'\n;MMc       oMMW,\n WMK      dMMW,  ccccccc.\n lMMl    oMMM;   ooooooo.\n  OMMc   ...\n   xMMx\n    ;XMN:\n      ,."
  },
  {
    "path": "src/logo/ascii/asteroidos.txt",
    "content": "$1                    ***\n$1                   *****\n$1                **********\n$1              ***************\n$1           *///****////****////.\n$2         (/////// /////// ///////(\n$2      /(((((//*     //,     //((((((.\n$2    (((((((((((     (((        ((((((((\n$2 *(((((((((((((((((((((((        ((((((((\n$3    (((((#(((((((#(((((        ((#(((((\n$3     (#(#(#####(#(#,       ####(#(#\n$3         #########        ########\n$3           /########   ########\n$4              #######%#######\n$4                (#%%%%%%%#\n$4                   %%%%%\n$4                    %%%"
  },
  {
    "path": "src/logo/ascii/astos.txt",
    "content": "                oQA#$%UMn\n                H       9\n                G       #\n                6       %\n                ?#M#%KW3\"\n                  // \\\\\n                //     \\\\\n              //         \\\\\n            //             \\\\\n        n%@$DK&ML       .0O3#@&M_\n        P       #       8       W\n        H       U       G       #\n        B       N       O       @\n        C&&#%HNAR       'WS3QMHB\"\n          // \\\\              \\\\\n        //     \\\\              \\\\\n      //         \\\\              \\\\\n    //             \\\\              \\\\\nuURF$##Bv       nKWB$%ABc       aM@3R@D@b\n8       M       @       O       #       %\n%       &       G       U       @       @\n&       @       #       %       %       #\n!HGN@MNCf       t&S9#%HQr       ?@G#6S@QP"
  },
  {
    "path": "src/logo/ascii/astra_linux.txt",
    "content": "                  AA\n                 AaaA\n                Aa$2/\\$1aA\n$1               Aa$2/$1aa$2\\$1aA\n$1              Aa$2/$1aAAa$2\\$1aA\n$1             aA$2/$1aaAAaa$2\\$1Aa\n$1            aA$2/$1aaAAAAaa$2\\$1Aa\n$1  aaaaaaAAAAa$2/$1aaAAAAAAaa$2\\$1aAAAAaaaaa\n$1aAAa$2-----$1aaaaaAAAAAAAAAAaaaaa$2-----$1aAAa\n$1  aAA$2\\ $1aAAAAAAAAAAAAAAAAAAAAAAa$2 /$1AAa\n$1    aAa$2\\$1aAAA$2\\$1AAAA$2\\$1AAAA$2\\$1AAA$2\\$1AAa$2/$1aAa\n$1      aAa$2\\$1aA$2\\\\$1AAA$2\\\\$1AAA$2\\\\$1AA$2\\\\/$1aAa\n$1       aAA$2\\$1aA$2\\\\$1AAA$2\\\\$1AAA$2\\\\$1Aa$2/$1AAa\n$1         aA$2\\$1aA$2\\\\$1AAA$2\\\\$1AAA$2\\\\/$1Aa\n$1         aA$2/$1AA$2\\\\\\$1AA$2\\\\\\$1AA$2\\\\\\$1Aa\n$1        aA$2/\\$1AAa$2\\\\\\$1Aa$2\\\\\\$1Aa$2\\\\\\$1Aa\n$1        aA$2/\\\\$1AAa$2\\\\/\\$1a$2\\\\\\$1Aa$2\\\\$1Aa\n$1       aA$2/$1a$2\\\\\\$1Aa$2\\/$1AA$2\\\\\\\\\\$1Aa$2\\\\$1Aa\n$1       aA$2/$1aA$2\\\\/$1aAa  aAa$2\\\\\\$1Aa$2\\$1Aa\n$1      aA$2/\\$1A$2\\/$1Aa        aA$2\\\\$1A$2\\\\$1Aa\n$1      A$2|/$1aaAa            aAaa$2\\|$1A\n$1     aAaa                    aaAa"
  },
  {
    "path": "src/logo/ascii/athenaos.txt",
    "content": "                                             u.\n                                          ..:Y\n                                        .Y..1\n                    ..::i::..          .br7S.\n                  .::::...::::.       5dDr:\n              ..:::..       .:i::..   Y7:\n         ..:::::..             ..::::.\n  ...:i:i:i:..         $2...        $1:.:..:i:::...\n ::i:.                $2.USL       Yr        $1..i::.\n.:::.                  $2:K      :u.           $1.::.\n:... $2jB.             .:vUi:   77         KB: $1..::\n...: $2iBR        vP::5X5PgSKXvLjS.       iBB  $1.::.\n:i::  $2BBQr       jgDIuj1UuJjXbbi       2BBP  $1:::;\n'i::   $2sQBg:      i2S55121XSSU.      YBQZ:  $1.:::\n r:::  $2r:JgBMu.   Q :BqEqZEB: Q   :qBBK7i:  $1:i:i\n .rir. $2IBqPDgQBDr P    rB    :q 1QBRgEqgB  $1.::i.\n  :i:i  $2:JsXPDDRQQPBL   P   qQXBQRDDPIYY   $1rir:\n   :iii  $25qvLUDDMgMgBB5  :DBQZRgMDdJ7Ygi  $1iir:\n    iri:  $2MBRgdZZggQgRBMIBQQRRggdZdMBBr  $1riri\n     irii  $2IBQgdZbDDRRRgQgQQMZDdZdgBB.  $1iiri\n      i7rr  $2.BBQZZbEEMQQDBQMEZdDDBBP  $1.rir:\n       :7ir.  $27BBQMDMQSbB5ZBggQBBQ.  $1ivr7.\n         :r7r.  $27BBBB:rBbQ.1BBBM.  $1:r7vi\n         ::rLrr.  $2.b:rBQ7QB uU.  $1:7vLi.\n       .BB  .iLYv:   $1idL B1    $1:r77r.\n      LBB      :vrvvr       rvu7r:\n     ZB.         ':7v177Lsvurr:'\n   .d2              ''::r:''\n   :'"
  },
  {
    "path": "src/logo/ascii/athenaos_old.txt",
    "content": "    .          ..\n$1   :####:     ####.\n$1  .################\n$1 :##################\n$1.###################.\n$1########     #######\n$1#######  $2####$1 #####\n$1:#######.      ####\n$1 #########  $2#$1   ##   #\n$1 #######   $2##$1      ####\n$1########  $2####$1    #######\n$1########  $2#####$1   ########\n$1########  $2#######$1  #######\n$1 #######  $2########$1  #######\n$1 ########  $2#########$1  ######\n$1  ########  $2#########$1  #####\n$1    #######  $2#########$1  ####\n$1     #######  $2#########$1  ##\n$1       #######  $2########$1 ##\n$1          ###### $2########$1 #\n$1               ### $2#######$1\n$1                     $2######$1\n$1                        $2####$1\n$1                          $2##$1"
  },
  {
    "path": "src/logo/ascii/aurora.txt",
    "content": "                  +++++++++\n               +++++$4+$1++++++$4+$1++\n           ++++++$4+$1+++  +++++++++++\n       +++++++++++     $2+$1   +++++$4+$1+++++\n    ++++++$4+$1++++      $4+++$1      +++++++++++\n  ++++++++++$4   +    +++++  ++    $2+++++$4+$2++++\n +++$4+$1++++$4 + +  +   +++++++    +  +  $2+++++$4+$2++\n++++++$4  $2+$4  +++    +++++++++     +++ $2++++++++\n+++$4+$1++$4      +    +++++ +++++     +\n++++++$4 $2+$4        +++++   +++++           +++++\n++$4+$2+++$4   +     +++++     ++++++    ++++++++++\n++++++$4 +++++  +++++   +   +++++++++++++++++++\n++++++$4   +   +++++   +   +++++++++++++++\n+++$4++$2+$4      ++++++    +++++++++++++\n++++++$4  +  ++++++ +++++++++++++ $2+++  $4++\n++++++$4    ++++++++++++++++      $2++$4+$2+  $4++  $1+\n++++++$4   ++++++++++++++     $3+    $2++++\n++++++$4  +++++++++++    +      $4+        $3++++++\n++$4+$3+++$4 ++++++++++     +++       $3+++++++++++++\n+++++ $4+++++++++        +   $3+++++++++++++\n +++ $4++++++++ $2++$4   +   $3++++++++++++++   ++++\n ++ $4++++++++    $3+  ++++++++++++++   +++++++\n    $4++++++   $2+   $3+++++++++++++   +++++++$2++\n      $4++++    $3+++++++++++++   ++++++$2+++\n            +++++++++++   ++++++$2+++\n              +++++    ++++++$2+++\n                    ++++++$2++\n                    +++++"
  },
  {
    "path": "src/logo/ascii/axos.txt",
    "content": "               ▂🬭𜴧⎻𜷗𜴗𜴦𜵎⎻𜴧🬭▂\n        ▁▂𜴧━𜵼𜵶𜶮▂🬭🬭𜴐🭗  🬁𜴜🬭🬭▂𜶮▁𜵁━𜴧▂▁\n     𜷓𜵐🬂🬂🮂𜶮𜴸𜴪━𜴆🮂▔        ▔🮂𜴆━𜴩𜵳𜴸🮂🬂🬂𜶚🬿\n    🭄🬨▍ 🬜🮂▔      ▁▂▂🬭🬭▂▂▁      ▔🮂🬪 🮈🬕𜶿\n   🭄🭙▐▎𜵫       🭃██████████🭎      🭢𜶡🮇▌🭤𜶿\n  🭄🭙 ▐🭄🭙      🭋█████🭜𜴦█████🭀      🭤🭏▌ 🭤🭏\n 𜵫🭗  🮉🭛       🭃███🭝🭙  🭥🭒███🭎       🭦▋  🭢𜶡\n▟🬮𜴧━🭷🭘       🭋████▄▂  ▂▄████🭀       🭣𜴇━𜴧🬯🭏\n🭕𜴸𜴆╾🭺🬽       🭃██🭡        🭖██🭐       🭈𜵡╼𜴆🬡🭠\n 𜴤🬼  🮉🭀     🭋███΄        🭤███🭀     🭋▊  🭇𜵐\n  𜴠🬾 ▐𜴢🬾▁▁  🭃██𜴍          𜶘██🭐  ▁▁🭉🭫▌ 🭉🭠\n   𜴠🬾🮈▎𜶚 ▔🮂🬂▀🬎🬎            🮅🬎▀🬂🮂▔ 𜵐🮇▌🭉𜴏\n    𜴢🭿▍ 𜴬▂▁                    ▁▂🬜΄🮈🬲𜴏\n     🭥𜶡🬭🬭▂𜶭▁𜵁━𜴧▂▁        ▁▂𜴧━𜶷𜶭𜶮▂🬭🬭𜵫🭚\n        ▔⎺𜴆━𜴩▔𜶮🮂🬂🬂𜴜🬼  🭇𜴐🬂🬂🮂𜶮▔𜴨━𜴆⎺▔\n              ⠈🮂🭷𜴆╾𜶦𜷞🭄𜴬╼𜴆🬂🮂΄"
  },
  {
    "path": "src/logo/ascii/azos.txt",
    "content": "$1  ////.                     $2                      (((((\n$1////////                    $2                    @((((((((\n$1////////                    $2                    @((((((((\n$1////////  ///////           $2           (((((((  @((((((((\n$1//////// /////////          $2          ((((((((( @((((((((\n$1//////// /////////          $2          ((((((((( @((((((((\n$1//////// /////////  //////  $2  ((((((  ((((((((( @((((((((\n$1//////// ///////// //////// $2 (((((((( ((((((((( @((((((((\n$1//////// ///////// //////// $2 (((((((( ((((((((( @((((((((\n$1//////// ///////// //////// $2  ((((((( ((((((((( @((((((((\n$1//////// /////////   ///    $2     (    ((((((((( @((((((((\n$1//////// /////////          $2          ((((((((( @((((((((\n$1//////// /////////          $2          &(((((((( @((((((((\n$1////////  //////            $2            @((((   @((((((((\n$1////////                    $2                    @((((((((\n$1////////                    $2                    @((((((((\n$1 /////                      $2                      ((((("
  },
  {
    "path": "src/logo/ascii/bedrock.txt",
    "content": "--------------------------------------\n--------------------------------------\n--------------------------------------\n---$2\\\\\\\\\\\\\\\\\\\\\\\\$1-----------------------\n----$2\\\\\\      \\\\\\$1----------------------\n-----$2\\\\\\      \\\\\\$1---------------------\n------$2\\\\\\      \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\$1------\n-------$2\\\\\\                    \\\\\\$1-----\n--------$2\\\\\\                    \\\\\\$1----\n---------$2\\\\\\        ______      \\\\\\$1---\n----------$2\\\\\\                   ///$1---\n-----------$2\\\\\\                 ///$1----\n------------$2\\\\\\               ///$1-----\n-------------$2\\\\\\////////////////$1------\n--------------------------------------\n--------------------------------------\n--------------------------------------"
  },
  {
    "path": "src/logo/ascii/bedrock_small.txt",
    "content": " _________\n| $2__     $1 |\n| $2\\ \\___ $1 |\n| $2 \\  _ \\$1 |\n| $2  \\___/$1 |\n|_________|\n"
  },
  {
    "path": "src/logo/ascii/biglinux.txt",
    "content": "                                 ...\n                              :OWMMMNd.\n                            :NMMMMMMMMWc\n                  okkl.    kMMMMMW0xdOWMl\n  :             xMMMMMW.  kMMMMNc      lW.\n :x             NMMMMMO  ,MMMM0.        'l\n Xx              \"lkk\"   kMMMX      .okx,\n$2.MX      .cc;.    .xXKx. KMMM:    .OMMMMMl\n:MM'   'KMMMMWK:  0MMMMk xMMM.   lWMMMMMMM'\ncMMN:;xMMMMk::MMO oMMMMX .XMM. .KMMMWOOMMMd\n'MMMMMMMMN,   NMMx OMMMMl .kM0OMMMMk.  ;MMd\n xMMMMMMd    .MMMW  :NMMMd  .ckKKx'     KMc\n  dWMNd.     oMMMN    lkNMX,            oM.\n ;.         ;MMMMx      \"MM:.           cO\n$3 .X.       oMMMMW.                      l.\n  dMk:..;xWMMMMW,\n   kMMMMMMMMMMX.\n    :XMMMMMMK:\n      ':MM:\"      Made in Brazil"
  },
  {
    "path": "src/logo/ascii/bitrig.txt",
    "content": "   `hMMMMN+\n   -MMo-dMd`\n   oMN- oMN`\n   yMd  /NM:\n  .mMmyyhMMs\n  :NMMMhsmMh\n  +MNhNNoyMm-\n  hMd.-hMNMN:\n  mMmsssmMMMo\n .MMdyyhNMMMd\n oMN.`/dMddMN`\n yMm/hNm+./MM/\n.dMMMmo.``.NMo\n:NMMMNmmmmmMMh\n/MN/-------oNN:\nhMd.       .dMh\nsm/         /ms"
  },
  {
    "path": "src/logo/ascii/blackarch.txt",
    "content": "$3                   00\n                   11\n                  ====$1\n                  .$3//$1\n                 `o$3//$1:\n                `+o$3//$1o:\n               `+oo$3//$1oo:\n               -+oo$3//$1oo+:\n             `/:-:+$3//$1ooo+:\n            `/+++++$3//$1+++++:\n           `/++++++$3//$1++++++:\n          `/+++o$2ooo$3//$2ooo$1oooo/`\n$2         $1./$2ooosssso$3//$2osssssso$1+`\n$2        .oossssso-`$3//$1`/ossssss+`\n       -osssssso.  $3//$1  :ssssssso.\n      :osssssss/   $3//$1   osssso+++.\n     /ossssssss/   $3//$1   +ssssooo/-\n   `/ossssso+/:-   $3//$1   -:/+osssso+-\n  `+sso+:-`        $3//$1       `.-/+oso:\n `++:.             $3//$1            `-/+/\n .`                $3/$1                `/"
  },
  {
    "path": "src/logo/ascii/blackmesa.txt",
    "content": "           .-;+$$XHHHHHHX$$+;-\n        ,:X@@X%/;=----=:\\%X@@X:,\n      =$$@@%=.              .=+H@X:\n    -XMX:                      =XMX=\n   /@@:                          =H@+\n  %@X.                            .$$@$$\n +@X,                               $$@%\n/@@,                                .@@\\\n%@%                                  +@$$\nH@:                                  :@H\nH@:         :HHHHHHHHHHHHHHHHHHX,    =@H\n%@%         ;@M@@@@@@@@@@@@@@@@@H-   +@$$\n=@@,        :@@@@@@@@@@@@@@@@@@@@@= .@@:\n =@X        :@@@@@@@@@@@@@@@@@@@@@@:%@%\n  $$@$$,      ;@@@@@@@@@@@@@@@@@M@@@@@@$$\n   +@@HHHHHHH@@@@@@@@@@@@@@@@@@@@@@@\n    =X@@@@@@@@@@@@@@@@@@@@@@@@@@@X=\n      :$$@@@@@@@@@@@@@@@@@@M@@@@$$:\n        \\$$@@@@@@@@@@@@@@@@@@X/-\n           .-;+$$XXHHHHHX$$+;-."
  },
  {
    "path": "src/logo/ascii/blackpanther.txt",
    "content": "$3                         ........\n                  .,»╔╗╗╬▄▄╫█▀▓▄▄╬╗╗g≈,.\n               ,j╗╬╣▓▓███████▌;»╙▀▀▀▀█▄▄╗j,\n            .≈╗╬▓██▀▀▀▀▀╠╙░░»»;:`$2``>$1▄ $3▐ ▓╫╗⌂,\n          .j╬▓█▀▒░░░░░░░░░»»»;:````      ╙▀█▌╬░,\n         ;╗▓█▄▄███████▀░░»»»»;```` ╓▄▄█▄▄φ  ██▌Ñ>.\n       .j╣█████▀▀░░░░░░░░»»╓▄▄¿``▄███████/▄████▓╬U.\n      .j╣▓██▀ÜÑ╦╦░░░░░░▐█@▄████⌐▐███████████████▓╬H.\n      «╫▓█▀░ÑÑ╩╦░░░░░░░░▀██████M\"▀███████████████▓╫░\n     :]╣█▌ÑÑÑÑ▄▄██▀░░░░»»██████████████████████████Ñ~\n     »╫▓█╫ÑÑ▄███▀░░░░░»»▐██████████████████████████▌░\n    `j╣█▌Ñ╬████░░░░░░░»»▐████████████████████████▌▐█U`\n    `/╫█▌▄███▌░░░░░░░»»»;▀██████████████▀████████w▐█░`\n     ;╟█▌███▌░░░░░░░▄▄»»;:`▀▀████████▀Ü▄████████▌ ▐▌>`\n     `]▓████░░░░░░░░██⌂;:````╓▄▄µp╓▄▄██████████▀ ,█M`\n      \"╠╣██▌░░░░░░░»██▌;````  ╙▀██████████████M  █▀\"\n       \"╟╣█░░░░░░░░»███⌂```      ▐▀████████▀░   █▌░`\n        \"╩█▄░░░░░░»»▀███ ``           └└`     ,█▀\"`\n         `░▀█▄░░░»»»»████@                  .▄█Ü`\n           `╙▀█▄@»»»;`▀███▌¿              ,▄▀Ñ\"`\n             `\"╨▀█▄▄▄░`▐█████▄,       ,▄▄▀▀░`\n                `\"╙╩▀▀▀▀████████▓▌▌▌▀▀▀╨\"``\n                    ``\"\"░╚╨╝╝╝╝╨╨░\"\"``"
  },
  {
    "path": "src/logo/ascii/blag.txt",
    "content": "             d\n            ,MK:\n            xMMMX:\n           .NMMMMMX;\n           lMMMMMMMM0clodkO0KXWW:\n           KMMMMMMMMMMMMMMMMMMX'\n      .;d0NMMMMMMMMMMMMMMMMMMK.\n .;dONMMMMMMMMMMMMMMMMMMMMMMx\n'dKMMMMMMMMMMMMMMMMMMMMMMMMl\n   .:xKWMMMMMMMMMMMMMMMMMMM0.\n       .:xNMMMMMMMMMMMMMMMMMK.\n          lMMMMMMMMMMMMMMMMMMK.\n          ,MMMMMMMMWkOXWMMMMMM0\n          .NMMMMMNd.     `':ldko\n           OMMMK:\n           oWk,\n           ;:"
  },
  {
    "path": "src/logo/ascii/blankon.txt",
    "content": "$2        `./ohdNMMMMNmho+.` $1       .+oo:`\n$2      -smMMMMMMMMMMMMMMMMmy-`    $1`yyyyy+\n$2   `:dMMMMMMMMMMMMMMMMMMMMMMd/`  $1`yyyyys\n$2  .hMMMMMMMNmhso/++symNMMMMMMMh- $1`yyyyys\n$2 -mMMMMMMms-`         -omMMMMMMN-$1.yyyyys\n$2.mMMMMMMy.              .yMMMMMMm:$1yyyyys\n$2sMMMMMMy                 `sMMMMMMh$1yyyyys\n$2NMMMMMN:                  .NMMMMMN$1yyyyys\n$2MMMMMMm.                   NMMMMMN$1yyyyys\n$2hMMMMMM+                  /MMMMMMN$1yyyyys\n$2:NMMMMMN:                :mMMMMMM+$1yyyyys\n$2 oMMMMMMNs-            .sNMMMMMMs.$1yyyyys\n$2  +MMMMMMMNho:.`  `.:ohNMMMMMMNo $1`yyyyys\n$2   -hMMMMMMMMNNNmmNNNMMMMMMMMh-  $1`yyyyys\n$2     :yNMMMMMMMMMMMMMMMMMMNy:`   $1`yyyyys\n$2       .:sdNMMMMMMMMMMNds/.      $1`yyyyyo\n$2           `.:/++++/:.`           $1:oys+."
  },
  {
    "path": "src/logo/ascii/bluelight.txt",
    "content": "              oMMNMMMMMMMMMMMMMMMMMMMMMM\n              oMMMMMMMMMMMMMMMMMMMMMMMMM\n              oMMMMMMMMMMMMMMMMMMMMMMMMM\n              oMMMMMMMMMMMMMMMMMMMMMMMMM\n              -+++++++++++++++++++++++mM$2\n             ```````````````````````..$1dM$2\n           ```````````````````````....$1dM$2\n         ```````````````````````......$1dM$2\n       ```````````````````````........$1dM$2\n     ```````````````````````..........$1dM$2\n   ```````````````````````............$1dM$2\n.::::::::::::::::::::::-..............$1dM$2\n `-+yyyyyyyyyyyyyyyyyyyo............$1+mMM$2\n     -+yyyyyyyyyyyyyyyyo..........$1+mMMMM$2\n        ./syyyyyyyyyyyyo........$1+mMMMMMM$2\n           ./oyyyyyyyyyo......$1+mMMMMMMMM$2\n              omdyyyyyyo....$1+mMMMMMMMMMM$2\n              $1oMMM$2mdhyyo..$1+mMMMMMMMMMMMM\n              oNNNNNNm$2dso$1mMMMMMMMMMMMMMM"
  },
  {
    "path": "src/logo/ascii/bodhi.txt",
    "content": "$1|           $2,,mmKKKKKKKKWm,,\n $1'      $2,aKKP$1LL**********|L*$2TKp,\n   $1t  $2aKP$1L**```          ```**L$2*Kp\n    IX$1EL$3L,wwww,              $1``*||$2Kp\n  ,#P$1L|$3KKKpPP@IPPTKmw,          $1`*||$2K\n ,K$1LL*$3{KKKKKKPPb$KPhpKKPKp        $1`||$2K\n #$1PL  $3!KKKKKKPhKPPP$KKEhKKKKp      $1`||$2K\n!H$1L*   $31KKKKKKKphKbPKKKKKK$KKp      $1`|I$2W\n$$$$1bL     $3KKKKKKKKBQKhKbKKKKKKKK       $1|I$2N\n$$$$1bL     $3!KKKKKKKKKKNKKKKKKKPP`       $1|I$2b\nTH$1L*     $3TKKKKKK##KKKN@KKKK^         $1|I$2M\n K@$1L      $3*KKKKKKKKKKKEKE5          $1||$2K\n `NL$1L      $3`KKKKKKKKKK\"```|L       $1||$2#P\n  `K@$1LL       $3`\"**\"`        $1'.   :||$2#P\n    Yp$1LL                      $1' |L$2$M`\n     `Tp$1pLL,                ,|||$2p'L\n        \"Kpp$1LL++,.,    ,,|||$$$$2#K*   $1'.\n           $2`\"MKWpppppppp#KM\"`        $1`h,"
  },
  {
    "path": "src/logo/ascii/bonsai.txt",
    "content": "$2   ,####,\n   $2#######,  $2,#####,\n   $2#####',#  $2'######\n    $2''###'$3';,,,'$2###'\n   $3       ,;  ''''\n   $3      ;;;   $2,#####,\n   $3     ;;;'  ,,;$2;;###\n   $3     ';;;;''$2'####'\n   $3      ;;;\n   $3   ,.;;';'',,,\n   $3  '     '\n$1 #\n #                        O\n ##, ,##,',##, ,##  ,#,   ,\n # # #  # #''# #,,  # #   #\n '#' '##' #  #  ,,# '##;, #"
  },
  {
    "path": "src/logo/ascii/bredos.txt",
    "content": "            ████████████████\n        ██████░░░░░░████░░░░██  ████\n    ████░░░░████░░░░░░██░░░░░░██░░░░██\n  ██░░░░░░░░░░██░░░░░░██░░░░░░░░██░░░░██\n  ██░░░░░░░░░░██░░░░░░░░░░░░░░░░██░░░░░░██\n██▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓██\n██▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓▓▓██\n██▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓██\n      ██████████████████████████"
  },
  {
    "path": "src/logo/ascii/bsd.txt",
    "content": "$1             ,        ,\n            /(        )`\n            \\ \\___   / |\n            /- _  `-/  '\n           ($2/\\/ \\ $1\\   /\\\n           $2/ /   | `    $1\\\n           $3O O   $2) $1/    |\n           $2`-^--'$1`<     '\n          (_.)  _  )   /\n           `.___/`    /\n             `-----' /\n$4<----.     __ / __   \\\n$4<----|====$1O)))$4==$1) \\) /$4====|\n<----'    $1`--' `.__,' \\\n             |        |\n              \\       /       /\\\n         $5______$1( (_  / \\______/\n       $5,'  ,-----'   |\n       `--{__________)"
  },
  {
    "path": "src/logo/ascii/bunsenlabs.txt",
    "content": "        `++\n      -yMMs\n    `yMMMMN`\n   -NMMMMMMm.\n  :MMMMMMMMMN-\n .NMMMMMMMMMMM/\n yMMMMMMMMMMMMM/\n`MMMMMMNMMMMMMMN.\n-MMMMN+ /mMMMMMMy\n-MMMm`   `dMMMMMM\n`MMN.     .NMMMMM.\n hMy       yMMMMM`\n -Mo       +MMMMN\n  /o       +MMMMs\n           +MMMN`\n           hMMM:\n          `NMM/\n          +MN:\n          mh.\n         -/"
  },
  {
    "path": "src/logo/ascii/cachyos.txt",
    "content": "           $3.$1-------------------------:\n          .$2+=$1========================.\n         :$2++$1===$2++===$1===============-       :$2++$1-\n        :$2*++$1====$2+++++==$1===========-        .==:\n       -$2*+++$1=====$2+***++=$1=========:\n      =$2*++++=$1=======------------:\n     =$2*+++++=$1====-                     $3...$1\n   .$2+*+++++$1=-===:                    .$2=+++=$1:\n  :$2++++$1=====-==:                     -***$2**$1+\n :$2++=$1=======-=.                      .=+**+$3.$1\n.$2+$1==========-.                          $3.$1\n :$2+++++++$1====-                                $3.$1--==-$3.$1\n  :$2++$1==========.                             $3:$2+++++++$1$3:\n   $1.-===========.                            =*****+*+\n    $1.-===========:                           .+*****+:\n      $1-=======$2++++$1:::::::::::::::::::::::::-:  $3.$1---:\n       :======$2++++$1====$2+++******************=.\n        $1:=====$2+++$1==========$2++++++++++++++*-\n         $1.====$2++$1==============$2++++++++++*-\n          $1.===$2+$1==================$2+++++++:\n           $1.-=======================$2+++:\n             $3.........................."
  },
  {
    "path": "src/logo/ascii/cachyos_small.txt",
    "content": "   /''''''''''''/\n  /''''''''''''/\n /''''''/\n/''''''/\n\\......\\\n \\......\\\n  \\.............../\n   \\............./"
  },
  {
    "path": "src/logo/ascii/calculate.txt",
    "content": "                              ......\n                           ,,+++++++,.\n                         .,,,....,,,$2+**+,,.$1\n                       ............,$2++++,,,$1\n                      ...............\n                    ......,,,........\n                  .....+*#####+,,,*+.\n              .....,*###############,..,,,,,,..\n           ......,*#################*..,,,,,..,,,..\n         .,,....*####################+***+,,,,...,++,\n       .,,..,..*#####################*,\n     ,+,.+*..*#######################.\n   ,+,,+*+..,########################*\n.,++++++.  ..+##**###################+\n.....      ..+##***#################*.\n           .,.*#*****##############*.\n           ..,,*********#####****+.\n     $2.,++*****+++$1*****************$2+++++,.$1\n      $2,++++++**+++++$1***********$2+++++++++,$1\n     $2.,,,,++++,..  .,,,,,.....,+++,.,,$1"
  },
  {
    "path": "src/logo/ascii/calinixos.txt",
    "content": "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣠⠤⠔⠒⠒⠋⠉⠉⠉⠉⠓⠒⠒⠦⠤⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠤⠒⠉⣁⣠⣤⣶⣶⣿⣿⣿⣿⣿⣿⣿⣿⣶⣶⣤⣄⣈⠙⠲⢤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠴⠋⢁⣤⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⡈⠑⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⣠⠞⢁⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡄⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⢀⠞⠁⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠛⠋⠉⠁⠀⠀⠀⠀⠈⠉⠙⠛⠿⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀⠈⢢⡀⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⡰⠃⣠⣾⣿⣿⣿⣿⣿⣿⡿⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠻⢿⡿⠁⠀⠀⠀⠀⠀⠀⠙⣄⠀⠀⠀⠀\n⠀⠀⠀⡼⠁⣴⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢆⠀⠀⠀\n⠀⠀⡼⠀⣼⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣆⠀⠀\n⠀⣰⠁⣸⣿⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠉⠻⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⡄⠀\n⢀⡇⢠⣿⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⢿⣿⣿⣿⣿⣿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢳⠀\n⢸⠀⣸⣿⣿⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⣿⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⡄\n⣼⠀⣿⣿⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣿⣿⣿⣷⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇\n⡇⠀⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢛⣿⣿⣿⣿⣿⣿⣿⡦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇\n⢻⠀⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣶⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇\n⢸⡀⢹⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣾⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠃\n⠀⣇⠘⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⣿⡿⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡼⠀\n⠀⠸⡄⢹⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⣠⣶⣿⣿⣿⣿⣿⣿⠟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠃⠀\n⠀⠀⢳⡀⢻⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠈⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠏⠀⠀\n⠀⠀⠀⠳⡀⠻⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣷⣄⡀⠀⠀⠀⠀⢠⠏⠀⠀⠀\n⠀⠀⠀⠀⠙⣄⠙⢿⣿⣿⣿⣿⣿⣿⣷⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣾⣿⣿⣿⣿⣿⣦⡀⠀⡰⠃⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠈⠢⡈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣤⣄⣀⡀⠀⠀⠀⠀⢀⣀⣠⣤⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⣠⠞⠁⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠈⠢⡈⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⣡⠞⠁⠀⠀⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠓⢤⡈⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠛⣁⠴⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢⢄⣉⠙⠛⠿⠿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⠛⠋⣉⡤⠖⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠓⠒⠢⠤⠤⠤⠤⠤⠤⠤⠤⠖⠒⠋⠉⠀"
  },
  {
    "path": "src/logo/ascii/calinixos_small.txt",
    "content": "⠀⠀⠀⠀⠀⠀⠀⠀⣀⠤⠐⣂⣈⣩⣭⣭⣍⣀⣐⠀⠄⡀⠀⠀⠀⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⡀⠔⣨⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣅⠢⡀⠀⠀⠀⠀⠀\n⠀⠀⠀⠠⢊⣴⣾⣿⣿⣿⣿⠿⠟⠛⠛⠛⠛⠻⠿⣿⣿⣿⣿⠃⠀⠠⡀⠀⠀⠀\n⠀⠀⡐⢡⣾⣿⣿⣿⠟⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠁⠀⠀⠀⠈⢆⠀⠀\n⠀⡘⢰⣿⣿⣿⡟⠁⠀⠀⢀⣀⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢂⠀\n⢠⢠⣿⣿⣿⡟⠀⠀⠀⠀⠀⠙⠿⣿⣿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡀\n⡄⢸⣿⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠁\n⡇⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣹⣿⣿⣿⣷⠄⠀⠀⠀⠀⠀⠀⠀⠀\n⠃⢸⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⡿⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⡀\n⠘⡘⣿⣿⣿⣧⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⠿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠁\n⠀⠡⠸⣿⣿⣿⣧⡀⠀⠀⠀⠉⠉⠉⠉⠁⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⢀⠆⠀\n⠀⠀⠡⡘⢿⣿⣿⣿⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣿⣷⣦⡀⢀⠊⠀⠀\n⠀⠀⠀⠈⠊⡻⢿⣿⣿⣿⣿⣶⣤⣤⣤⣤⣤⣤⣶⣿⣿⣿⣿⡿⢟⠕⠁⠀⠀⠀\n⠀⠀⠀⠀⠀⠈⠢⢙⠻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⡩⠐⠁⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠈⠐⠂⠭⠉⠙⣛⣛⠋⠉⠭⠐⠂⠁⠀⠀⠀⠀"
  },
  {
    "path": "src/logo/ascii/carbs.txt",
    "content": "             ..........\n          ..,;:ccccccc:;'..\n       ..,clllc:;;;;;:cllc,.\n      .,cllc,...     ..';;'.\n     .;lol;..           ..\n    .,lol;.\n    .coo:.\n   .'lol,.\n   .,lol,.\n   .,lol,.\n    'col;.\n    .:ooc'.\n    .'col:.\n     .'cllc'..          .''.\n      ..:lolc,'.......',cll,.\n        ..;cllllccccclllc;'.\n          ...',;;;;;;,,...\n                ....."
  },
  {
    "path": "src/logo/ascii/cbl_mariner.txt",
    "content": "                    .\n                  :-  .\n                :==. .=:\n              :===:  -==:\n            :-===:  .====:\n          :-====-   -=====:\n         -======   :=======:\n        -======.  .=========:\n       -======:   -==========.\n      -======-    -===========.\n     :======-      :===========.\n    :=======.       .-==========.\n   :=======:          -==========.\n  :=======-            :==========.\n :=======-              .-========-\n:--------.                :========-\n                    ..:::--=========-\n            ..::---================-=-"
  },
  {
    "path": "src/logo/ascii/celos.txt",
    "content": "             `-:/++++/:-`\n          -/syyyyyyyyyyyyy+-\n        :ssssyyyyyyyyyyyyyyyy/\n      .osy$2mmmmmmmmmmmmmmmNNNNNmmhy+\n     $1.sssshhhhhhhddddddddddddddds-\n    $1`osssssssyyyyyyyyyyyyyyyyyyhy`\n    $1:ssssssyyyyyyyyyyyyyyyyyyyyhh/\n$2sMMMMMMMMMMMMMMMMMMMMMMMh$1yyyyyyhho\n    :sssssssyyyyyyyyyyyyyyyyyyyhh/\n    `ssssssssyyyyyyyyyyyyyyyyyyhy.\n     -sssssyddddddddddddddddddddy\n      -ssss$2hmmmmmmmmmmmmmmmmmmmyssss-\n       $1`/ssssyyyyyyyyyyyyyyyy+`\n         $1`:osyyyyyyyyyyyyys/`\n            $1`.:/+ooooo+:-`"
  },
  {
    "path": "src/logo/ascii/center.txt",
    "content": "            .\n            o,\n    .       d,       .\n    ';'   ..d;..  .cl'\n        .:; 'oldO,.oo.\n        ..,:,xKXxoo;'.\n,;;;;;ldxkONMMMXxkxc;;;;;.\n.....':oddXWMNOxlcl:......\n        .:dlxk0c;:. .\n        :d:.,xcld,.,:.\n    ;l,    .l;     ';'\n            .o;\n            l,"
  },
  {
    "path": "src/logo/ascii/centos.txt",
    "content": "                 ..\n               .PLTJ.\n              <><><><>\n     $2KKSSV' 4KKK $1LJ$4 KKKL.'VSSKK\n     $2KKV' 4KKKKK $1LJ$4 KKKKAL 'VKK\n     $2V' ' 'VKKKK $1LJ$4 KKKKV' ' 'V\n     $2.4MA.' 'VKK $1LJ$4 KKV' '.4Mb.\n   $4. $2KKKKKA.' 'V $1LJ$4 V' '.4KKKKK $3.\n $4.4D $2KKKKKKKA.'' $1LJ$4 ''.4KKKKKKK $3FA.\n$4<QDD ++++++++++++  $3++++++++++++ GFD>\n '$4VD $3KKKKKKKK'.. $2LJ $1..'KKKKKKKK $3FV\n   $4' $3VKKKKK'. .4 $2LJ $1K. .'KKKKKV $3'\n      $3'VK'. .4KK $2LJ $1KKA. .'KV'\n     $3A. . .4KKKK $2LJ $1KKKKA. . .4\n     $3KKA. 'KKKKK $2LJ $1KKKKK' .4KK\n     $3KKSSA. VKKK $2LJ $1KKKV .4SSKK\n              $2<><><><>\n               $2'MKKM'\n                 $2''"
  },
  {
    "path": "src/logo/ascii/centos_small.txt",
    "content": " $2____$1^$4____\n $2|\\  $1|$4  /|\n $2| \\ $1|$4 / |\n$4<---- $3---->\n $3| / $2|$1 \\ |\n $3|/__$2|$1__\\|\n     $2v"
  },
  {
    "path": "src/logo/ascii/cereus.txt",
    "content": "                         ..\n                        '::,.....\n                        .,;:llll;. $4     ...\n                     $2...... $1''''.$4       ':::;.\n                $2 ..,::lll::;,..$4            ::::;.\n               $2 ':llllllllllll:' $4           ;:::;\n             $2 .;llllllllllllllll'  $4          ;::;.\n$3    .... $2    .;lllllllllllllllll:. $4            ;::;.\n$3  .;::::,  $2  ,lllllllllllllllllll.  $4             .\n$3  .::::::.$2  .lllllllllllllllllll:.\n$3  .::::::. $2 ,lllllllllllllllllll:.$3        ......\n$3  .;:::::'$2 .;lllllllllllllllllll,$3       .,::::::,.\n$3   .;::::;.$2.:lllllllllllllllllll.$3      .,::::::::;.\n$3     .,;::;$2;lllllllllllllllllll;$3       .:::::::::;.\n$3        ...$2,lllllllllllllllllll.$3       ':::::::::,.\n$2           .:lllllllllllllllll, $3      .;::::::::;.\n$2           .:llllllllllllllll:.$3      .;::::::::,.\n$2            ,llllllllllllllll'$3     .';:::::::;.\n$5 ...      $2  .llllllllllllllll;$3,,,,;:::::::;,.\n$5.:l:.    $2    ,llllllllllllll;$3,,:::::::;,'..\n$5.;::,    $2    .;lllllllllllll,$3 '''''''''''\n$5 .:::.     $2    ':lllllllllll:.\n$5  .;::'        $2 .,:llllllllll,\n$5   .,::;.      $2   .';lllllllll'\n$5     .,::;'..   $2     .';lllllll,.\n$5       ..,;::;,.  $2      ...,;lll:.\n$5          ...''.  $2           ..',;'."
  },
  {
    "path": "src/logo/ascii/chakra.txt",
    "content": "     _ _ _        \"kkkkkkkk.\n   ,kkkkkkkk.,    'kkkkkkkkk,\n   ,kkkkkkkkkkkk., 'kkkkkkkkk.\n  ,kkkkkkkkkkkkkkkk,'kkkkkkkk,\n ,kkkkkkkkkkkkkkkkkkk'kkkkkkk.\n  \"''\"''',;::,,\"''kkk''kkkkk;   __\n      ,kkkkkkkkkk, \"k''kkkkk' ,kkkk\n    ,kkkkkkk' ., ' .: 'kkkk',kkkkkk\n  ,kkkkkkkk'.k'   ,  ,kkkk;kkkkkkkkk\n ,kkkkkkkk';kk 'k  \"'k',kkkkkkkkkkkk\n.kkkkkkkkk.kkkk.'kkkkkkkkkkkkkkkkkk'\n;kkkkkkkk''kkkkkk;'kkkkkkkkkkkkk''\n'kkkkkkk; 'kkkkkkkk.,\"\"''\"''\"\"\n  ''kkkk;  'kkkkkkkkkk.,\n     ';'    'kkkkkkkkkkkk.,\n             ';kkkkkkkkkk'\n               ';kkkkkk'\n                  \"''\""
  },
  {
    "path": "src/logo/ascii/chaletos.txt",
    "content": "             `.//+osso+/:``\n         `/sdNNmhyssssydmNNdo:`\n       :hNmy+-`          .-+hNNs-\n     /mMh/`       `+:`       `+dMd:\n   .hMd-        -sNNMNo.  /yyy  /mMs`\n  -NM+       `/dMd/--omNh::dMM   `yMd`\n .NN+      .sNNs:/dMNy:/hNmo/s     yMd`\n hMs    `/hNd+-smMMMMMMd+:omNy-    `dMo\n:NM.  .omMy:/hNMMMMMMMMMMNy:/hMd+`  :Md`\n/Md` `sm+.omMMMMMMMMMMMMMMMMd/-sm+  .MN:\n/Md`      MMMMMMMMMMMMMMMMMMMN      .MN:\n:NN.      MMMMMMm....--NMMMMMN      -Mm.\n`dMo      MMMMMMd      mMMMMMN      hMs\n -MN:     MMMMMMd      mMMMMMN     oMm`\n  :NM:    MMMMMMd      mMMMMMN    +Mm-\n   -mMy.  mmmmmmh      dmmmmmh  -hMh.\n     oNNs-                    :yMm/\n      .+mMdo:`            `:smMd/`\n         -ohNNmhsoo++osshmNNh+.\n            `./+syyhhyys+:``"
  },
  {
    "path": "src/logo/ascii/chapeau.txt",
    "content": "               .-/-.\n            ////////.\n          ////////$2y+$1//.\n        ////////$2mMN$1/////.\n      ////////$2mMN+$1////////.\n    ////////////////////////.\n  /////////+$2shhddhyo$1+////////.\n ////////$2ymMNmdhhdmNNdo$1///////.\n///////+$2mMms$1////////$2hNMh$1///////.\n///////$2NMm+$1//////////$2sMMh$1///////\n//////$2oMMNmmmmmmmmmmmmMMm$1///////\n//////$2+MMmssssssssssssss+$1///////\n`//////$2yMMy$1////////////////////\n `//////$2smMNhso++oydNm$1////////\n  `///////$2ohmNMMMNNdy+$1///////\n    `//////////$2++$1//////////\n       `////////////////.\n           -////////-"
  },
  {
    "path": "src/logo/ascii/chimera_linux.txt",
    "content": "$3ddddddddddddddc  $1,cc:\n$3ddddddddddddddc  $1,cc:\n$3ddddddddddddddd  $1,cc:\n$3ddddddddddddl:'  $1,cc:\n$3dddddddddl'    $1..;cc:\n$3dddddddo.   $1,:cccccc:\n$3ddddddl   $1,ccc:'''''\n$3dddddo.  $1;ccc.          ............\n        .ccc.           cccccccccccc\n$2......  $1.ccc.          .ccc'''''''''\n$2OOOOOk.  $1;ccc.        .ccc;   ......\n$2OOOOOOd   $1'ccc:,....,:ccc'   $4coooooo\n$2OOOOOOOx.   $1':cccccccc:'   $4.looooooo\n$2OOOOOOOOOd,     $1`'''`     $4.coooooooo\n$2OOOOOOOOOOOOdc,.    $4..,coooooooooooo\n$2OOOOOOOOOOOOOOOO'  $4.oooooooooooooooo\n$2OOOOOOOOOOOOOOOO'  $4.oooooooooooooooo\n$2OOOOOOOOOOOOOOOO'  $4.oooooooooooooooo"
  },
  {
    "path": "src/logo/ascii/chonkysealos.txt",
    "content": "                  .-/-.\n            .:-=++****++=-:.\n        .:=+*##%%%%%%%%%%##*+=:.\n      :=*#%%%%%%%%%%%%%%%%%%%%#*=:\n    :=*#%%%%%%%%%%%%%%%%%%%%%%%%#*=.\n   -+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%#+-\n  =+#%%%%@@@@@@@%%%%%%%@@@@@@@%%%%%#+=\n =+#@%%%%*+=-==*%%%%%%%#+====*%%%%%@#+=\n:+*%%%%@*       +@%%%@#       -@%%%%%*+:\n=+#%%%%%%#+====*###%%##*=--=+*%%%%%%%#+=\n+*%%%%%%%@@##%%%%*=::=#%%%##%@%%%%%%%%*+\n+*%%%%%%%@**@%%%%%@==@%%%%%@+#%%%%%%%%*+\n=+#%%%%%%@#*@%%%%%%**%%%%%@%+%%%%%%%%#+=\n:+*%%%%%%%@#*####**###*####*%@%%%%%%%*+:\n =+#@%%%%%%@%%%%%%%@@%%%%%%%%%%%%%%@#+=\n  =+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#+=\n   -+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%*+-\n    .=*#%%%%%%%%%%%%%%%%%%%%%%%%#*=.\n      :=*##%%%%%%%%%%%%%%%%%%##*=:\n        .:=+*##%%%%%%%%%%##*+=:.\n            .:-=++****++=-:."
  },
  {
    "path": "src/logo/ascii/chrom.txt",
    "content": "$2            .,:loool:,.\n        .,coooooooooooooc,.\n     .,lllllllllllllllllllll,.\n    ;ccccccccccccccccccccccccc;\n$1  '$2ccccccccccccccccccccccccccccc.\n$1 ,oo$2c::::::::okO$5000$30OOkkkkkkkkkkk:\n$1.ooool$2;;;;:x$5K0$4kxxxxxk$50X$3K0000000000.\n$1:oooool$2;,;O$5K$4ddddddddddd$5KX$3000000000d\n$1lllllool$2;l$5N$4dllllllllllld$5N$3K000000000\n$1lllllllll$2o$5M$4dccccccccccco$5W$3K000000000\n$1;cllllllllX$5X$4c:::::::::c$50X$3000000000d\n$1.ccccllllllO$5Nk$4c;,,,;cx$5KK$30000000000.\n$1 .cccccclllllxOO$5OOO$1Okx$3O0000000000;\n$1  .:ccccccccllllllllo$3O0000000OOO,\n$1    ,:ccccccccclllcd$30000OOOOOOl.\n$1      '::ccccccccc$3dOOOOOOOkx:.\n$1        ..,::cccc$3xOOOkkko;.\n$1            ..,:$3dOkxl:."
  },
  {
    "path": "src/logo/ascii/cleanjaro.txt",
    "content": "███████▌ ████████████████\n███████▌ ████████████████\n███████▌ ████████████████\n███████▌\n███████▌\n███████▌\n███████▌\n███████▌\n█████████████████████████\n█████████████████████████\n█████████████████████████\n▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀"
  },
  {
    "path": "src/logo/ascii/cleanjaro_small.txt",
    "content": "█████ ██████████\n█████ ██████████\n█████\n█████\n█████\n████████████████\n████████████████"
  },
  {
    "path": "src/logo/ascii/clear_linux.txt",
    "content": "          BBB\n       BBBBBBBBB\n     BBBBBBBBBBBBBBB\n   BBBBBBBBBBBBBBBBBBBB\n   BBBBBBBBBBB         BBB\n  BBBBBBBB$2YYYYY\n$1  BBBBBBBB$2YYYYYY\n$1  BBBBBBBB$2YYYYYYY\n$1  BBBBBBBBB$2YYYYY$3W\n$4 GG$1BBBBBBBY$2YYYY$3WWW\n$4 GGG$1BBBBBBB$2YY$3WWWWWWWW\n$4 GGGGGG$1BBBBBB$3WWWWWWWW\n$4 GGGGGGGG$1BBBB$3WWWWWWWW\n$4GGGGGGGGGGG$1BBB$3WWWWWWW\n$4GGGGGGGGGGGGG$1B$3WWWWWW\n$4GGGGGGGG$3WWWWWWWWWWW\n$4GG$3WWWWWWWWWWWWWWWW\n WWWWWWWWWWWWWWWW\n      WWWWWWWWWW\n          WWW"
  },
  {
    "path": "src/logo/ascii/clearos.txt",
    "content": "             `.--::::::--.`\n         .-:////////////////:-.\n      `-////////////////////////-`\n     -////////////////////////////-\n   `//////////////-..-//////////////`\n  ./////////////:      ://///////////.\n `//////:..-////:      :////-..-//////`\n ://////`    -///:.``.:///-`    ://///:\n`///////:.     -////////-`    `:///////`\n.//:--////:.     -////-`    `:////--://.\n./:    .////:.     --`    `:////-    :/.\n`//-`    .////:.        `:////-    `-//`\n :///-`    .////:.    `:////-    `-///:\n `/////-`    -///:    :///-    `-/////`\n  `//////-   `///:    :///`   .//////`\n   `:////:   `///:    :///`   -////:`\n     .://:   `///:    :///`   -//:.\n       .::   `///:    :///`   -:.\n             `///:    :///`\n              `...    ...`"
  },
  {
    "path": "src/logo/ascii/clover.txt",
    "content": "               `omo``omo`\n             `oNMMMNNMMMNo`\n           `oNMMMMMMMMMMMMNo`\n          oNMMMMMMMMMMMMMMMMNo\n          `sNMMMMMMMMMMMMMMNs`\n     `omo`  `sNMMMMMMMMMMNs`  `omo`\n   `oNMMMNo`  `sNMMMMMMNs`  `oNMMMNo`\n `oNMMMMMMMNo`  `oNMMNs`  `oNMMMMMMMNo`\noNMMMMMMMMMMMNo`  `sy`  `oNMMMMMMMMMMMNo\n`sNMMMMMMMMMMMMNo.$2oNNs$1.oNMMMMMMMMMMMMNs`\n`oNMMMMMMMMMMMMNs.$2oNNs$1.oNMMMMMMMMMMMMNo`\noNMMMMMMMMMMMNs`  `sy`  `oNMMMMMMMMMMMNo\n `oNMMMMMMMNs`  `oNMMNo`  `oNMMMMMMMNs`\n   `oNMMMNs`  `sNMMMMMMNs`  `oNMMMNs`\n     `oNs`  `sNMMMMMMMMMMNs`  `oNs`\n          `sNMMMMMMMMMMMMMMNs`\n          +NMMMMMMMMMMMMMMMMNo\n           `oNMMMMMMMMMMMMNo`\n             `oNMMMNNMMMNs`\n               `omo``oNs`"
  },
  {
    "path": "src/logo/ascii/cobalt.txt",
    "content": "$1                          ///\n$1                  ,//////////////\n$1    ///////////////////////////////\n$1    ///////////////$5***********$1//////\n    ////$5***********************$1/////\n    /////$5***********************$1////\n   //////$5,,,,,,,,,,,,,,,,,,,,,,$1///\n //////$5,,,,,,,,,,,,,,,,,,,,,,,,,$1/////\n /////$5,,,,,,,,,,,,,,,,,,,,,,,,,,,,$1/////\n$4 *****$3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,$4*****\n ******$3,,,,,,,,,,,,,,,,,,,,,,,,,,,,$4*****\n  *******$3,,,,,,,,,,,,,,,,,,,,,,,,,$4******\n    *******$3......................$4*******\n      ******$3....$4***********************\n        ****************************\n         *****"
  },
  {
    "path": "src/logo/ascii/codex.txt",
    "content": "#:              :+#@@@@%#\n@@#:          .*@@@@@@@@@\n@@@@#-       .@@@@@@@@@@@\n@@@@@@%-     %@@@@@@@@@@@\n@@@@@@@@%=  :@@@@%%%%@@@@\n@@@@@@@@@@%==*-:     .:=#\n:*@@@@@@@@@@=\n  :*@@@@@@@@=\n    .*@@@@@@=\n      .+@@@@=\n        .+%@=\n           +=\n"
  },
  {
    "path": "src/logo/ascii/condres.txt",
    "content": "$1syyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy+$3.+.\n$1`oyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy+$3:++.\n$2/o$1+oyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy/$3oo++.\n$2/y+$1syyyyyyyyyyyyyyyyyyyyyyyyyyyyy$3+ooo++.\n$2/hy+$1oyyyhhhhhhhhhhhhhhyyyyyyyyy$3+oo+++++.\n$2/hhh+$1shhhhhdddddhhhhhhhyyyyyyy$3+oo++++++.\n$2/hhdd+$1oddddddddddddhhhhhyyyys$3+oo+++++++.\n$2/hhddd+$1odmmmdddddddhhhhyyyy$3+ooo++++++++.\n$2/hhdddmo$1odmmmdddddhhhhhyyy$3+oooo++++++++.\n$2/hdddmmms$1/dmdddddhhhhyyys$3+oooo+++++++++.\n$2/hddddmmmy$1/hdddhhhhyyyyo$3+oooo++++++++++:\n$2/hhdddmmmmy$1:yhhhhyyyyy+$3+oooo+++++++++++:\n$2/hhddddddddy$1-syyyyyys+$3ooooo++++++++++++:\n$2/hhhddddddddy$1-+yyyy+$3/ooooo+++++++++++++:\n$2/hhhhhdddddhhy$1./yo:$3+oooooo+++++++++++++/\n$2/hhhhhhhhhhhhhy$1:-.$3+sooooo+++++++++++///:\n$2:sssssssssssso++$1$3`:/:--------.````````"
  },
  {
    "path": "src/logo/ascii/cosmic.txt",
    "content": " .xMMMMMMMMMMMMMMMMMMMMMMx.\nJDMMMMMMMMMMMMMMMMMMMMMMMMOL\nIMMMY'                'YMMMI\nMMMM                    MMMM\nMMMM                    MMMM\nIMMMb                  dMMMI\n'YMMMMMMMMb.    .dMMMMMMMMK'\n 'OMMMMMMMP'    'YMMMMMMMP'\n\n  $2.x76767$36767676$476767$5676x.\n  $2'*76767$36767676$476767$5676*'\n"
  },
  {
    "path": "src/logo/ascii/crux.txt",
    "content": "         $1odddd\n      oddxkkkxxdoo\n     ddcoddxxxdoool\n     xdclodod  olol\n     xoc  xdd  olol\n     xdc  $2k00$1Okdlol\n     xxd$2kOKKKOkd$1ldd\n     xdco$2xOkdlo$1dldd\n     ddc:cl$2lll$1oooodo\n   odxxdd$3xkO000kx$1ooxdo\n  oxddx$30NMMMMMMWW0o$1dkkxo\n oooxd$30WMMMMMMMMMW0o$1dxkx\ndocldkXW$3MMMMMMMWWN$1Odolco\nxx$2dx$1kxxOKN$3WMMWN$10xdoxo::c\n$2xOkkO$10oo$3odOW$2WW$1XkdodOxc:l\n$2dkkkxkkk$3OKX$2NNNX0Oxx$1xc:cd\n $2odxxdx$3xllo$2dddooxx$1dc:ldo\n   $2lodd$1dolccc$2ccox$1xoloo"
  },
  {
    "path": "src/logo/ascii/crux_small.txt",
    "content": "    ___\n   ($3.· $1|\n   ($2<> $1|\n  / $3__  $1\\\n ( $3/  \\ $1/|\n$2_$1/\\ $3__)$1/$2_$1)\n$2\\/$1-____$2\\/"
  },
  {
    "path": "src/logo/ascii/crystal.txt",
    "content": "                  mysssym\n                mysssym\n              mysssym\n            mysssym\n          mysssyd\n        mysssyd    N\n      mysssyd    mysym\n    mysssyd      dysssym\n  mysssyd          dysssym\nmysssyd              dysssym\nmysssyd              dysssym\n  mysssyd          dysssym\n    mysssyd      dysssym\n      mysym    dysssym\n        N    dysssym\n           dysssym\n         dysssym\n       dysssym\n     dysssym\n   dysssym"
  },
  {
    "path": "src/logo/ascii/cucumber.txt",
    "content": "           `.-://++++++//:-.`\n        `:/+//$2::--------$1:://+/:`\n      -++/:$2----..........----$1:/++-\n    .++:$2---...........-......---$1:++.\n   /+:$2---....-::/:/--//:::-....---$1:+/\n `++:$2--.....:---::/--/::---:.....--$1:++`\n /+:$2--.....--.--::::-/::--.--.....--$1:+/\n-o:$2--.......-:::://--/:::::-.......--$1:o-\n/+:$2--...-:-::---:::..:::---:--:-...--$1:+/\no/:$2-...-:.:.-/:::......::/:.--.:-...-$1:/o\no/$2--...::-:/::/:-......-::::::-/-...-$1:/o\n/+:$2--..-/:/:::--:::..:::--::////-..--$1:+/\n-o:$2--...----::/:::/--/:::::-----...--$1:o-\n /+:$2--....://:::.:/--/:.::://:....--$1:+/\n `++:$2--...-:::.--.:..:.--.:/:-...--$1:++`\n   /+:$2---....----:-..-:----....---$1:+/\n    .++:$2---..................---$1:++.\n      -/+/:$2----..........----$1:/+/-\n        `:/+//$2::--------:::$1/+/:`\n           `.-://++++++//:-.`"
  },
  {
    "path": "src/logo/ascii/cuerdos.txt",
    "content": " ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⡀\n⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣶⣾⣿⣿⣿⣶⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣶⣿⣿⣿⡇\n⠀⠀⡄⢀⣤⣤⣤⣤⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⣤⣤⣤⣤⣤⣄⠀⣿⣿⣿⣿⣿⡇\n⠀⠀⡇⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣿⣿⣿⣿⠁\n⠀⠀⣧⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃\n⠀⠀⡿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁\n⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠋⠁\n⠀⠀⠇⠘⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠿⠿⠿⠿⠛⠛⠋⠉\n⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⡇\n⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉\n____________________________________\n| Optimizado hasta el último pixel |\n************************************"
  },
  {
    "path": "src/logo/ascii/cutefishos.txt",
    "content": "                     ___ww___\n_              _wwMMM@M^^^^MMMMww_\nM0w_       _wMMM~~             ~~MMm_\n  ~MMy _ww0M~                      ~MMy\n    ~MMMM~                      o    \"MM\n$3  jw0M~~MMMw_                      _wMM'\nwMM~      ~~MMmw__             __w0M~\n~             ~~MM0MmwwwwwwwwwMMM~\n                    ~~~~^^~~~"
  },
  {
    "path": "src/logo/ascii/cuteos.txt",
    "content": "                       $31ua$2\n                  $3MMM1ua$2\n $1MM$2EE        $3 MMMMM1uazE$2\n$1MM $2EEEE     $3M1MM1uazzEn $2EEEE  MME\n    EEEEE  $3MMM uazEno $2EEEE\n    EEEEE$1MMMMMMEno~; $2EE          E$2\n     EE $1MMMMMMMM~;;E  $2MMMMM      M $2\n     E $1MMMMMMMMM          $2  E  E   $2\n      $1MMMMMMMMMMM\n           $1MMMMMMMMM $2EE $1\n                MM1MMMM $2EEE $1\n                     MMMMM\n                          MMM\n                              M"
  },
  {
    "path": "src/logo/ascii/cyberos.txt",
    "content": "$3           !M$EEEEEEEEEEEP\n          .MMMMM000000Nr.\n          $3&MMMMMM$2MMMMMMMMMMMMM9\n         $3~MMM$1MMMM$2MMMMMMMMMMMMC\n    $1\"    $3M$1MMMMMMM$2MMMMMMMMMMs\n  $1iM$2MMM&&$1MMMMMMMM$2MMMMMMMMs\n $1BMMM$2MMMMM$1MMMMMMM$2MMMMMM$3\"\n$19MMMMM$2MMMMMMM$1MMMM$2MMMM$3MMMf-\n      $2sMMMMMMMM$1MM$2M$3MMMMMMMMM3_\n       $2+ffffffff$1P$3MMMMMMMMMMMM0\n                  $2CMMMMMMMMMMM\n                    }MMMMMMMMM\n                      ~MMMMMMM\n                        \"RMMMM\n                          .PMB"
  },
  {
    "path": "src/logo/ascii/cycledream.txt",
    "content": "                .:ox00000kdc,              \n      ;;    'cdO.            dxl,.         \n      00 'oO'                    0x;       \n      00ko                         ,Oc     \n      x00Okxx                        lO'   \n                    .x,               .0c  \n              ;c   .O00'   ::          '0: \n .                 O0000.               o0.\n.0c        ,,;;:clO00000Olc:;;,,        .0l\nx0          ;00000000000000000c          00\n00             0000000000000.            0O\nx0.        ...  00000000000. ...        '0:\n.0c            ,00000000000o            .o \n o0.           O000;   '0000               \n  0k          .0     O     O'              \n   lO.                        ''.....      \n    .0o                          d000      \n       Oo'                     ,dO O0      \n         ;Oo;.             .;oO,   00      \n              xkdocc:ccodko                \n                  ;k0k,                    "
  },
  {
    "path": "src/logo/ascii/dahlia.txt",
    "content": "                  .#.\n                *%@@@%*\n        .,,,,,(&@@@@@@@&/,,,,,.\n       ,#@@@@@@@@@@@@@@@@@@@@@#.\n       ,#@@@@@@@&#///#&@@@@@@@#.\n     ,/%&@@@@@%/,    .,(%@@@@@&#/.\n   *#&@@@@@@#,.         .*#@@@@@@&#,\n .&@@@@@@@@@(            .(@@@@@@@@@&&.\n#@@@@@@@@@@(               )@@@@@@@@@@@#\n °@@@@@@@@@@(            .(@@@@@@@@@@@°\n   *%@@@@@@@(.           ,#@@@@@@@%*\n     ,(&@@@@@@%*.     ./%@@@@@@%(,\n       ,#@@@@@@@&(***(&@@@@@@@#.\n       ,#@@@@@@@@@@@@@@@@@@@@@#.\n        ,*****#&@@@@@@@&(*****,\n               ,/%@@@%/.\n                  ,#,"
  },
  {
    "path": "src/logo/ascii/darkos.txt",
    "content": "$3⠀⠀⠀⠀  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n$1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣶⠋⡆⢹⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n$5⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡆⢀⣤⢛⠛⣠⣿⠀⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n$6⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣶⣿⠟⣡⠊⣠⣾⣿⠃⣠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n$2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣯⣿⠀⠊⣤⣿⣿⣿⠃⣴⣧⣄⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n$1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣶⣿⣿⡟⣠⣶⣿⣿⣿⢋⣤⠿⠛⠉⢁⣭⣽⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n$4  ⠀⠀⠀⠀⠀⠀ ⠀⣠⠖⡭⢉⣿⣯⣿⣯⣿⣿⣿⣟⣧⠛⢉⣤⣶⣾⣿⣿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n$5⠀⠀⠀⠀⠀⠀⠀⠀⣴⣫⠓⢱⣯⣿⢿⠋⠛⢛⠟⠯⠶⢟⣿⣯⣿⣿⣿⣿⣿⣿⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n$2⠀⠀⠀⠀⠀⠀⢀⡮⢁⣴⣿⣿⣿⠖⣠⠐⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠛⠛⠛⢿⣶⣄⠀⠀⠀⠀⠀⠀⠀\n$3⠀⠀⠀⠀⢀⣤⣷⣿⣿⠿⢛⣭⠒⠉⠀⠀⠀⣀⣀⣄⣤⣤⣴⣶⣶⣶⣿⣿⣿⣿⣿⠿⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀\n$1⠀⢀⣶⠏⠟⠝⠉⢀⣤⣿⣿⣶⣾⣿⣿⣿⣿⣿⣿⣟⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n$6⢴⣯⣤⣶⣿⣿⣿⣿⣿⡿⣿⣯⠉⠉⠉⠉⠀⠀⠀⠈⣿⡀⣟⣿⣿⢿⣿⣿⣿⣿⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n$5⠀⠀⠀⠉⠛⣿⣧⠀⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠃⣿⣿⣯⣿⣦⡀⠀⠉⠻⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀\n$3⠀⠀⠀⠀⠀⠀⠉⢿⣮⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⠀⣯⠉⠉⠛⢿⣿⣷⣄⠀⠈⢻⣆⠀⠀⠀⠀⠀⠀⠀⠀\n$2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠢⠀⠀⠀⠀⠀⠀⠀⢀⢡⠃⣾⣿⣿⣦⠀⠀⠀⠙⢿⣿⣤⠀⠙⣄⠀⠀⠀⠀⠀⠀⠀\n$6⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⢋⡟⢠⣿⣿⣿⠋⢿⣄⠀⠀⠀⠈⡄⠙⣶⣈⡄⠀⠀⠀⠀⠀⠀\n$1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠚⢲⣿⠀⣾⣿⣿⠁⠀⠀⠉⢷⡀⠀⠀⣇⠀⠀⠈⠻⡀⠀⠀⠀⠀⠀\n$4⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢢⣀⣿⡏⠀⣿⡿⠀⠀⠀⠀⠀⠀⠙⣦⠀⢧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n$3⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠿⣧⣾⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣮⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n$5⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠙⠛⠀⠀⠀⠀⠀⠀"
  },
  {
    "path": "src/logo/ascii/debian.txt",
    "content": "        $2_,met$$$$$$$$$$gg.\n     ,g$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$P.\n   ,g$$$$P\"\"       \"\"\"Y$$$$.\".\n  ,$$$$P'              `$$$$$$.\n',$$$$P       ,ggs.     `$$$$b:\n`d$$$$'     ,$P\"'   $1.$2    $$$$$$\n $$$$P      d$'     $1,$2    $$$$P\n $$$$:      $$$.   $1-$2    ,d$$$$'\n $$$$;      Y$b._   _,d$P'\n Y$$$$.    $1`.$2`\"Y$$$$$$$$P\"'\n `$$$$b      $1\"-.__\n  $2`Y$$$$b\n   `Y$$$$.\n     `$$$$b.\n       `Y$$$$b.\n         `\"Y$$b._\n             `\"\"\"\""
  },
  {
    "path": "src/logo/ascii/debian_small.txt",
    "content": "  _____\n /  __ \\\n|  /    |\n|  \\___-\n-_\n  --_"
  },
  {
    "path": "src/logo/ascii/deepin.txt",
    "content": "             ............\n         .';;;;;.       .,;,.\n      .,;;;;;;;.       ';;;;;;;.\n    .;::::::::'     .,::;;,''''',.\n   ,'.::::::::    .;;'.          ';\n  ;'  'cccccc,   ,' :: '..        .:\n ,,    :ccccc.  ;: .c, '' :.       ,;\n.l.     cllll' ., .lc  :; .l'       l.\n.c       :lllc  ;cl:  .l' .ll.      :'\n.l        'looc. .   ,o:  'oo'      c,\n.o.         .:ool::coc'  .ooo'      o.\n ::            .....   .;dddo      ;c\n  l:...            .';lddddo.     ,o\n   lxxxxxdoolllodxxxxxxxxxc      :l\n    ,dxxxxxxxxxxxxxxxxxxl.     'o,\n      ,dkkkkkkkkkkkkko;.    .;o;\n        .;okkkkkdl;.    .,cl:.\n            .,:cccccccc:,."
  },
  {
    "path": "src/logo/ascii/desaos.txt",
    "content": "███████████████████████\n███████████████████████\n███████████████████████\n███████████████████████\n████████               ███████\n████████               ███████\n████████               ███████\n████████               ███████\n████████               ███████\n████████               ███████\n████████               ███████\n██████████████████████████████\n██████████████████████████████\n████████████████████████\n████████████████████████\n████████████████████████"
  },
  {
    "path": "src/logo/ascii/devuan.txt",
    "content": "   ..,,;;;::;,..\n           `':ddd;:,.\n                 `'dPPd:,.\n                     `:b$$$$b`.\n                        'P$$$$$d`\n                         .$$$$$$$$$`\n                         ;$$$$$$$$$P\n                      .:P$$$$$$$$$$$$`\n                  .,:b$$$$$$$$$$$$$;'\n             .,:dP$$$$$$$$$$$$$$$$b:'\n      .,:;db$$$$$$$$$$$$$$$$$$$$Pd'`\n ,db$$$$$$$$$$$$$$$$$$$$$$$$$$$$b:'`\n:$$$$$$$$$$$$$$$$$$$$$$$$b:'`\n `$$$$$$$$$bd:''`\n   `'''`"
  },
  {
    "path": "src/logo/ascii/devuan_small.txt",
    "content": " ..:::.\n    ..-==-\n        .+#:\n         =@@\n      :+%@#:\n.:=+#@@%*:\n#@@@#=:"
  },
  {
    "path": "src/logo/ascii/dietpi.txt",
    "content": "  :=+******+-    -+******+=:\n =#-::-::::-=#:-#=-::::-::-#=\n :%-::--==-::-%%-::-==--::-%:\n  +#-:::::=+++$2@@$1+++=-::::-#=\n   :#+-::::=%$2@@@@@$1=::::-+#:\n     =@%##%$2@@@@@@@@$1%##%@=\n$2   .#@@@@@@@@@@@@@@@@@@@@#.\n   %@@@@@@@@@@@@@@@@@@@@@@%\n  -@@@@@@@@@@@@@@@@@@@@@@@@:\n.#@@@@@@@@@@%%%%%@@@@@@@@@@@#.\n#@@@$1+-=*#%$2%%%%%%%%%$1%%#+--#$2@@@#\n%@@%$1*.   .:$2=*%%%%*$1=:    .#$2@@@%\n:%@@@$1#+=-:$2:-*%%%%+::$1:-=+%$2@@@%:\n :@@@@%@%%%%@$1#$2#$1#$2%@%%%%@%@@@@.\n  +@@@@@@@@@$1%$2=*+$1%$2@%@@@@@@@@+\n   #@@@@@@@@@@@@@@@@@@@@@@#\n    -#@@@@@@@@@@@@@@@@@@#-\n       -*%@@@@@@@@@@%*-\n          .+%@@@@%+."
  },
  {
    "path": "src/logo/ascii/dracos.txt",
    "content": "       `-:/-\n          -os:\n            -os/`\n              :sy+-`\n               `/yyyy+.\n                 `+yyyyo-\n                   `/yyyys:\n`:osssoooo++-        +yyyyyy/`\n   ./yyyyyyo         yo`:syyyy+.\n      -oyyy+         +-   :yyyyyo-\n        `:sy:        `.    `/yyyyys:\n           ./o/.`           .oyyso+oo:`\n              :+oo+//::::///:-.`     `.`"
  },
  {
    "path": "src/logo/ascii/dragonfly.txt",
    "content": "$2,--,           $1|           $2,--,\n$2|   `-,       $1,^,       $2,-'   |\n$2 `,    `-,   $1(/ \\)   $2,-'    ,'\n$2   `-,    `-,$1/   \\$2,-'    ,-'\n$2      `------$1(   )$2------'\n$2  ,----------$1(   )$2----------,\n$2 |        _,-$1(   )$2-,_        |\n$2  `-,__,-'   $1\\   /$2   `-,__,-'\n$1              | |\n              | |\n              | |\n              | |\n              | |\n              | |\n              `|'"
  },
  {
    "path": "src/logo/ascii/dragonfly_old.txt",
    "content": "                        .-.\n                 $3 ()$1I$3()\n            $1 \"==.__:-:__.==\"\n            \"==.__/~|~\\__.==\"\n            \"==._(  Y  )_.==\"\n $2.-'~~\"\"~=--...,__$1\\/|\\/$2__,...--=~\"\"~~'-.\n(               ..=$1\\=$1/$2=..               )\n `'-.        ,.-\"`;$1/=\\$2;\"-.,_        .-'`\n     `~\"-=-~` .-~` $1|=|$2 `~-. `~-=-\"~`\n          .-~`    /$1|=|$2\\    `~-.\n       .~`       / $1|=|$2 \\       `~.\n   .-~`        .'  $1|=|$2  `.        `~-.\n (`     _,.-=\"`  $1  |=|$2    `\"=-.,_     `)\n  `~\"~\"`        $1   |=|$2           `\"~\"~`\n                 $1  /=\\\n                   \\=/\n                    ^"
  },
  {
    "path": "src/logo/ascii/dragonfly_small.txt",
    "content": "$2   ,$1_$2,\n('-_$1|$2_-')\n >--$1|$2--<\n(_-'$1|$2'-_)\n    $1|\n    |\n    |"
  },
  {
    "path": "src/logo/ascii/drauger.txt",
    "content": "                  -``-\n                `:+``+:`\n               `/++``++/.\n              .++/.  ./++.\n             :++/`    `/++:\n           `/++:        :++/`\n          ./+/-          -/+/.\n         -++/.            ./++-\n        :++:`              `:++:\n      `/++-                  -++/`\n     ./++.                    ./+/.\n    -++/`                      `/++-\n   :++:`                        `:++:\n `/++-                            -++/`\n.:-.`..............................`.-:.\n`.-/++++++++++++++++++++++++++++++++/-.`"
  },
  {
    "path": "src/logo/ascii/droidian.txt",
    "content": "$2       _,met$$$$$$$$$$gg.\n   ,g$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$P.\n ,$$$$P'              `$$$$$$.\n',$$$$P       ,ggs.     `$$$$b:\n`d$$$$'     ,$$P\"'   $1.$2    $$$$$$\n $$$$P      d$$'     $1,$2    $$$$P\n $$$$:      $$$$.   $1-$2    ,d$$$$'\n $$$$;      Y$$b._   _,d$$P'\n Y$$$$.    $1`.$2`\"Y$$$$$$$$P\"'\n$2 `$$$$b      $1\"-.__\n$2  `Y$$$$\n  `Y$$$$.\n     `$$$$b.\n       `Y$$$$b.\n          `\"Y$$b._"
  },
  {
    "path": "src/logo/ascii/elbrus.txt",
    "content": "▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄\n██▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀██\n██                       ██\n██   ███████   ███████   ██\n██   ██   ██   ██   ██   ██\n██   ██   ██   ██   ██   ██\n██   ██   ██   ██   ██   ██\n██   ██   ██   ██   ██   ██\n██   ██   ███████   ███████\n██   ██                  ██\n██   ██▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄██\n██   ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀██\n██                       ██\n███████████████████████████"
  },
  {
    "path": "src/logo/ascii/elementary.txt",
    "content": "         eeeeeeeeeeeeeeeee\n      eeeeeeeeeeeeeeeeeeeeeee\n    eeeee  eeeeeeeeeeee   eeeee\n  eeee   eeeee       eee     eeee\n eeee   eeee          eee     eeee\neee    eee            eee       eee\neee   eee            eee        eee\nee    eee           eeee       eeee\nee    eee         eeeee      eeeeee\nee    eee       eeeee      eeeee ee\neee   eeee   eeeeee      eeeee  eee\neee    eeeeeeeeee     eeeeee    eee\n eeeeeeeeeeeeeeeeeeeeeeee    eeeee\n  eeeeeeee eeeeeeeeeeee      eeee\n    eeeee                 eeeee\n      eeeeeee         eeeeeee\n         eeeeeeeeeeeeeeeee"
  },
  {
    "path": "src/logo/ascii/elementary_small.txt",
    "content": "  _______\n / ____  \\\n/  |  /  /\\\n|__\\ /  / |\n\\   /__/  /\n \\_______/"
  },
  {
    "path": "src/logo/ascii/elive.txt",
    "content": "             *@$2,,&(%%%..%*.\n         $1(@$2&%/##############((/$1*,\n      $2@$1@&$2#########$1*..../$2########%$1*..\n    $2@$1&$2#%%%%%.              $3,.$1,$2%%%%%%.\n  /%$2(%%%%.                      $1($2%%%%#.\n /$1*$2%%##,.                       .,%%###,\n ,####.   ,$1*$2#%$1#$3/,(/               $2/$1#$2###,\n((###/   ,,##########$1($3/(#          $2%####,\n%#((($1.   .$1./$2(((((((((((((($1($2#/$3*..   $3*.$2((($1/\n$2%#///$1.        $3***$2.*/////////////\n$3#$2#////*              $3***$2.*/////.\n $3($2(*****                   $3***\n  $2,*****..\n   ..$1*$2*****..                 *$1%$2/****.\n     .,,*******,$3,,../##($2%&$1&$2#******$1,$2.\n        ,*$1,$2,,,,,,,,,,,,,,,,,,,$1,$2..\n            *//$1/,,$2,,,,,,,$1,..$2"
  },
  {
    "path": "src/logo/ascii/emmabuntus.txt",
    "content": "                   _~~_\n          nmmmmmmm/$2/**\\$1\\\n        nmHhHMMMHh\\$2\\__/$1/\n      nm zot  $2__$1  t*~~*n\n     m  b $2_+*´cc`*+_$1 p  m\n    m  & $2/%cc,;;,cc%\\$1 &  m\n _~~_ &  $2c__      +cc$1  &  n\n/$2/**\\$1\\&            $2cc;$1 &  m\n\\$2\\__/$1/&  $2c~~      +cc´$1 &  n\n *~~*  & $2\\cc%*--*%cc/$1 &  m\n     m  b $2`+.cccc.+´$1 p  m\n      nm zo        o_~~_\n        nmHhHMMMHhH/$2/**\\$1\\\n          nmmmmmmmm\\$2\\__/$1/\n                    *~~*\n"
  },
  {
    "path": "src/logo/ascii/emperoros.txt",
    "content": "           !!\n          !!!!\n         llllll\n         llllll\n       IIIIIIIIII\n  IIIIIIIIIIIIIIIIIIII\n;;;;;;;;;;;;;;;;;;;;;;;;\n  ;;;;;;;;;;;;;;;;;;;;\n       :;;::;:;::\n         ::::::\n         ,,,,,,\n          ,,,,\n           \"\""
  },
  {
    "path": "src/logo/ascii/encryptos.txt",
    "content": "     *******\n   ***       **.\n   **         **\n   **         **\n\n *****************\n,,,,,,,,,,,,,,,,***\n,,,,,,,     ,,,,,,,\n,,,,,,,     ,,,,,,,\n,,,,,,,     ,,,,,,,\n,,,,,,,     ,,,,,,,\n,,,,,,,,,,,,,,,,,,,\n    ,,,,,,,,,,,,."
  },
  {
    "path": "src/logo/ascii/endeavouros.txt",
    "content": "                     $2./$1o$3.\n                   $2./$1sssso$3-\n                 $2`:$1osssssss+$3-\n               $2`:+$1sssssssssso$3/.\n             $2`-/o$1ssssssssssssso$3/.\n           $2`-/+$1sssssssssssssssso$3+:`\n         $2`-:/+$1sssssssssssssssssso$3+/.\n       $2`.://o$1sssssssssssssssssssso$3++-\n      $2.://+$1ssssssssssssssssssssssso$3++:\n    $2.:///o$1ssssssssssssssssssssssssso$3++:\n  $2`:////$1ssssssssssssssssssssssssssso$3+++.\n$2`-////+$1ssssssssssssssssssssssssssso$3++++-\n $2`..-+$1oosssssssssssssssssssssssso$3+++++/`\n   $3./++++++++++++++++++++++++++++++/:.\n  `:::::::::::::::::::::::::------``"
  },
  {
    "path": "src/logo/ascii/endeavouros_small.txt",
    "content": "          /$2o$3.\n$1        /$2sssso$3-\n$1      /$2ossssssso$3:\n$1    /$2ssssssssssso$3+\n$1  /$2ssssssssssssssso$3+\n$1//$2osssssssssssssso$3+-\n `+++++++++++++++-`"
  },
  {
    "path": "src/logo/ascii/endless.txt",
    "content": "           `:+yhmNMMMMNmhy+:`\n        -odMMNhso//////oshNMMdo-\n      /dMMh+.              .+hMMd/\n    /mMNo`                    `oNMm:\n  `yMMo`                        `oMMy`\n `dMN-                            -NMd`\n hMN.                              .NMh\n/MM/                  -os`          /MM/\ndMm    `smNmmhs/- `:sNMd+   ``       mMd\nMMy    oMd--:+yMMMMMNo.:ohmMMMNy`    yMM\nMMy    -NNyyhmMNh+oNMMMMMy:.  dMo    yMM\ndMm     `/++/-``/yNNh+/sdNMNddMm-    mMd\n/MM/          `dNy:       `-::-     /MM/\n hMN.                              .NMh\n `dMN-                            -NMd`\n  `yMMo`                        `oMMy`\n    /mMNo`                    `oNMm/\n      /dMMh+.              .+hMMd/\n        -odMMNhso//////oshNMMdo-\n           `:+yhmNMMMMNmhy+:`"
  },
  {
    "path": "src/logo/ascii/enso.txt",
    "content": "                .:--==--:.                     \n            :=*#############*+-.               \n         .+##################*##*:             \n       .*##########+==-==++*####*##-           \n      =########=:           .-+**#***.         \n     *#######-                  ++*#**.        \n    +######+                     -*+#**        \n   :######*                       .*+**=       \n   *######:                        --#*#       \n   #######                          +++#.      \n   #######.                         ++=*.      \n   *######+                        .-+*+       \n   :#######-                       -:*+:       \n    =#######*.                    :.*+-        \n     +########*-                  :*=-         \n      =###########+=:            =+=:          \n       .+#############.       .-==:            \n         .=###########=   ..:--:.              \n            .-+######+                         "
  },
  {
    "path": "src/logo/ascii/eshanizedos.txt",
    "content": "                 .:-==++++++++++++++++++++++-.\n              .:=+##############################+\n           .=*###################################-\n         :+######################################:\n       :+######################################*-\n     .+#############*+--:::::::::::::::::::::.\n    :*##########*=:\n   -###########=\n  :##########+.\n  *#########=\n =*********+             .::::::::::::::::.\n **********.           -*###################+:\n:**********           =#######################=\n-*********+           +************************\n:*********+           :***********************:\n.**********            .=******************+-\n +*********-\n .**********.\n  =**********:\n   +**********=.\n    =***********=:\n     -*************+-.\n      :+****************+++++++++++++++++++++==:\n        :+**************************************+.\n           -+************************************-\n             .-+*********************************.\n                :-==+*************************=."
  },
  {
    "path": "src/logo/ascii/eurolinux.txt",
    "content": "                __\n         -wwwWWWWWWWWWwww-\n        -WWWWWWWWWWWWWWWWWWw-\n          \\WWWWWWWWWWWWWWWWWWW-\n  _Ww      `WWWWWWWWWWWWWWWWWWWw\n -W$2E$1Www                -WWWWWWWWW-\n_WW$2U$1WWWW-                _WWWWWWWW\n_WW$2R$1WWWWWWWWWWWWWWWWWWWWWWWWWWWWWW-\nwWW$2O$1WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW\nWWW$2L$1WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWw\nWWW$2I$1WWWWWWWWWWWWWWWWWWWWWWWWWWWWww-\nwWW$2N$1WWWWw\n WW$2U$1WWWWWWw\n wW$2X$1WWWWWWWWww\n   wWWWWWWWWWWWWWWWw\n    wWWWWWWWWWWWWWWWw\n       WWWWWWWWWWWWWw\n           wWWWWWWWw"
  },
  {
    "path": "src/logo/ascii/evolutionos.txt",
    "content": "               $2.':ldxxxxdo:,.\n           .lXMMMMMMMMMMMMMMMMNo'\n         dWMMMMMMMMMMMMMMMMMMMMMMWk\n      .OMMMMWWWWWWWWWWWWWWWWWWWMMMMM0;\n     kMMMMM$1XxxxxkkkkkkkkkkkkkkkK$2WMMMMMK.\n   .kMMMMMM$1Xddd0KKKKKKKKKKKKKKKN$2MMMMMMMN.\n   KMMMMMMM$1XdddX$2MMMMMMMMMMMMMMMMMMMMMMMMX.\n  cMMMMMMMM$1XdddX$2MMMMMMMMMMMMMMMMMMMMMMMMMo\n  KMMMMMMMM$1XdddX$2MMMMMMMMMMMMMMMMMMMMMMMMMN\n  XMMMMMMMM$1XdddX$2WO$1kkkkkkkK$2WK$1OOOX$2MMMMMMMMMW\n  XMMMMMMMM$1XdddX$2WO$1kkkkkkkK$2WK$1OOOX$2MMMMMMMMMW\n  0MMMMMMMM$1XdddX$2MWWWWWWWWWMWWWWWMMMMMMMMMN\n  cMMMMMMMM$1XdddX$2MMMMMMMMMMMMMMMMMMMMMMMMMd\n  .kMMMMMMM$1XdddX$2MMMMMMMMMMMMMMMMMMMMMMMM0.\n   .kMMMMMM$1XxxxN$2W0$1OOOOOOOOOOOOOK$2MMMMMMMK.\n     oMMMMM$1XxxxN$2W0$1OOOOOOOOOOOOOK$2WMMMMMk.\n      '0MMMWNNNWMWWWWWWWWWWWWWWWMMMMX;\n         cWMMMMMMMMMMMMMMMMMMMMMMWd\n            :KWMMMMMMMMMMMMMMWXo.\n                .cdO0KK00xl'\n                     .."
  },
  {
    "path": "src/logo/ascii/evolutionos_old.txt",
    "content": "    dddddddddddddddddddddddd\n.dddd''''''''''''''''''''''dddd.\ndd:   dddddddddddddddddddd;   dd:\ndd:   ldl:''''''''''''''''    dd:\ndd:   ldl:                    dd:\ndd:   ldl:                    dd:\ndd:   ldl:                    dd:\ndd:   ldl:                    dd:\ndd:   ldl: ddddddd;  ddddd;   dd:\ndd:   ldl: '''''''   '''''    dd:\ndd:   ldl:                    dd:\ndd:   ldl:                    dd:\ndd:   ldl:                    dd:\ndd:   ldl:                    dd:\ndd:   ldl: ddddddddddddddd;   dd:\ndddd:.'''  '''''''''''''''  dddd:\n   dddddddddddddddddddddddddd;;'\n    '''''''''''''''''''''''''"
  },
  {
    "path": "src/logo/ascii/evolutionos_small.txt",
    "content": "      $2,coddoc'\n   'cddddddddddc'\n 'ddd$1OWWXXXXXXK$2ddo.\n.dddd$1OMX$2ddddddddddd.\nodddd$1OMX$2k$100O$2k$1OO$2ddddo\n.dddd$1OMX$2kOOOxOkdddd.\n .ddd$1OWW$2X$1XXXXXK$2ddd'\n   'dddddddddddd'\n      'cddddd,"
  },
  {
    "path": "src/logo/ascii/eweos.txt",
    "content": "$2          #####%%%\n$2       ##%%$3////$2%%%%%$3///\n$2      #%%%%$3////((((////$2%\n$1   *@@@@@@@$3/$5,,,$3/////$5,,,$2%$1@@@@@@@\n .@@@@@@@@@@@$3////////$2%%%$1@@@@@@@@@@@@\n@@@$4...$1@@@@@@$3////$2%%$3////$1@@@@@@@@@@@@@@@@\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n  @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n        @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n         @@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n          @@@@@@@@@@@@@@@@@@@@@@@@@@@\n            @@@@@@@@@@@@@@@@@@@@@@@\n             @@@@@@         @@@@@@\n               @@@           @@@\n"
  },
  {
    "path": "src/logo/ascii/exherbo.txt",
    "content": "$2 ,\nOXo.\nNXdX0:    .cok0KXNNXXK0ko:.\nKX  '0XdKMMK;.xMMMk, .0MMMMMXx;  ...\n'NO..xWkMMx   kMMM    cMMMMMX,NMWOxOXd.\n  cNMk  NK    .oXM.   OMMMMO. 0MMNo  kW.\n  lMc   o:       .,   .oKNk;   ;NMMWlxW'\n ;Mc    ..   .,,'    .0M$1g;$2WMN'dWMMMMMMO\n XX        ,WMMMMW.  cM$1cfli$2WMKlo.   .kMk\n.Mo        .WM$1GD$2MW.   XM$1WO0$2MMk        oMl\n,M:         ,XMMWx::,''oOK0x;          NM.\n'Ml      ,kNKOxxxxxkkO0XXKOd:.         oMk\n NK    .0Nxc$3:::::::::::::::$2fkKNk,      .MW\n ,Mo  .NXc$3::$2qXWXb$3::::::::::$2oo$3::$2lNK.    .MW\n  ;Wo oMd$3:::$2oNMNP$3::::::::$2oWMMMx$3:$2c0M;   lMO\n   'NO;W0c$3:::::::::::::::$2dMMMMO$3::$2lMk  .WM'\n     xWONXdc$3::::::::::::::$2oOOo$3::$2lXN. ,WMd\n      'KWWNXXK0Okxxo,$3:::::::$2,lkKNo  xMMO\n        :XMNxl,';:lodxkOO000Oxc. .oWMMo\n          'dXMMXkl;,.        .,o0MMNo'\n             ':d0XWMMMMWNNNNMMMNOl'\n                   ':okKXWNKkl'"
  },
  {
    "path": "src/logo/ascii/exodia_predator.txt",
    "content": "-                                  :\n+:                                :+\n++.                              .++\n+++             :  .             +++\n+++=           .+  +            =+++\n++++-          ++  +=          -++++\n++++++-       -++  ++-       -++++++\n++++++++:    .+++  +++.    :++++++++\n++++++++++:  ++++  ++++  :++++++++++\n+++++++++++==++++  ++++=++++++=+++++\n+++++.:++++++++++  ++++++++++:.+++++\n+++++. .+++++++++  +++++++++. .+++++\n+++++:   ++++++++  ++++++++   :+++++\n++++++-  =+++++++  +++++++=  -++++++\n :+++++= =+++++++  +++++++= =+++++:\n   :+++= =+++++++  +++++++= =+++:\n     -+= =+++++++  +++++++= ++-\n       : =++++++-  -++++++= :\n         =++++-      -++++=\n         =++=          =++=\n         =++            ++=\n         =+.            .+=\n         =-              -=\n         :                :"
  },
  {
    "path": "src/logo/ascii/fastfetch.txt",
    "content": "╭───────────────────────╮\n│  $2● $3● $4●    $5FASTFETCH   $1│\n├───────────────────────┤\n│                       │\n│  $2  /\\      $7►►►►►►►    $1│\n│  $2 /--\\     $7►►►►►►     $1│\n│  $2/----\\    $7►►►►►      $1│\n│   $6|xx|     $7►►►►       $1│\n│   $6|xx|     $7►►►        $1│\n│   $3^^^^                $1│\n╰───────────────────────╯"
  },
  {
    "path": "src/logo/ascii/fedora.txt",
    "content": "             .',;::::;,'.\n         .';:cccccccccccc:;,.\n      .;cccccccccccccccccccccc;.\n    .:cccccccccccccccccccccccccc:.\n  .;ccccccccccccc;$2.:dddl:.$1;ccccccc;.\n .:ccccccccccccc;$2OWMKOOXMWd$1;ccccccc:.\n.:ccccccccccccc;$2KMMc$1;cc;$2xMMc$1;ccccccc:.\n,cccccccccccccc;$2MMM.$1;cc;$2;WW:$1;cccccccc,\n:cccccccccccccc;$2MMM.$1;cccccccccccccccc:\n:ccccccc;$2oxOOOo$1;$2MMM000k.$1;cccccccccccc:\ncccccc;$20MMKxdd:$1;$2MMMkddc.$1;cccccccccccc;\nccccc;$2XMO'$1;cccc;$2MMM.$1;cccccccccccccccc'\nccccc;$2MMo$1;ccccc;$2MMW.$1;ccccccccccccccc;\nccccc;$20MNc.$1ccc$2.xMMd$1;ccccccccccccccc;\ncccccc;$2dNMWXXXWM0:$1;cccccccccccccc:,\ncccccccc;$2.:odl:.$1;cccccccccccccc:,.\nccccccccccccccccccccccccccccc:'.\n:ccccccccccccccccccccccc:;,..\n ':cccccccccccccccc::;,."
  },
  {
    "path": "src/logo/ascii/fedora2_small.txt",
    "content": "     __\n    /  \\\n __ |_\n/   |\n\\__/"
  },
  {
    "path": "src/logo/ascii/fedora_coreos.txt",
    "content": "                .....\n          .';:cccccccc:;'.\n        ':ccccclc$3lllllllll$1cc:.\n     .;cccccccc$3lllllllllllllll$1c,\n    ;clllccccc$3llllllllllllllllll$1c,\n  .cllclccccc$3lllll$2lll$3llllllllllll$1c:\n  ccclclcccc$3cllll$2kWMMNKk$3llllllllll$1c:\n :ccclclcccc$3llll$2oWMMMMMMWO$3lllllllll$1c,\n.ccllllllccc$3clll$2OMMMMMMMMM0$3lllllllll$1c\n.lllllclcccc$3llll$2KMMMMMMMMMMo$3llllllll$1c.\n.lllllllcccc$3clll$2KMMMMMMMMN0$3lllllllll$1c.\n.cclllllcccc$3lllld$2xkkxxdo$3llllllllllc$1lc\n :cccllllllcccc$3lllccllllcclccc$1cccccc;\n .ccclllllllcccccccc$3lll$1ccccclccccccc\n  .cllllllllllclcccclccclccllllcllc\n    :cllllllllccclcllllllllllllcc;\n     .cccccccccccccclcccccccccc:.\n       .;cccclccccccllllllccc,.\n          .';ccccclllccc:;..\n                ....."
  },
  {
    "path": "src/logo/ascii/fedora_kinoite.txt",
    "content": " ,clll:.$2          .,::::::::::::'\n$1:ooooooo$2        .;::::::::::::::\n$1looooooo$2       ,:::::::::::::::'\n$1looooooo$2      .::::::::::::::::\n$1looooooo$2      ;:::::::::::::::.\n$1looooooo$2     .::::::::::::::::\n$1looooool$2;;;;,::::::::::::::::\n$1looool$2::,   .::::::::::::::\n$1looooc$2::     ;::\n$1looooc$2::;.  .::;\n$1loooool$2:::::::::::.\n$1looooooo$2.    .::::::'\n$1looooooo$2       .::::::,;,..\n$1looooooo$2          :::;' ';:;.\n$1looooooo$2          :::     :::\n$1cooooooo$2          .::'   '::.\n$1 .ooooc $2            ::, ,::\n                      ''''"
  },
  {
    "path": "src/logo/ascii/fedora_old.txt",
    "content": "          /:-------------:\\\n       :-------------------::\n     :-----------$2/shhOHbmp$1---:\\\n   /-----------$2omMMMNNNMMD$1  ---:\n  :-----------$2sMMMMNMNMP$1.    ---:\n :-----------$2:MMMdP$1-------    ---\\\n,------------$2:MMMd$1--------    ---:\n:------------$2:MMMd$1-------    .---:\n:----    $2oNMMMMMMMMMNho$1     .----:\n:--     .$2+shhhMMMmhhy++$1   .------/\n:-    -------$2:MMMd$1--------------:\n:-   --------$2/MMMd$1-------------;\n:-    ------$2/hMMMy$1------------:\n:--$2 :dMNdhhdNMMNo$1------------;\n:---$2:sdNMMMMNds:$1------------:\n:------$2:://:$1-------------::\n:---------------------://"
  },
  {
    "path": "src/logo/ascii/fedora_sericea.txt",
    "content": "              :oooo,  .','\n      .';;;.;oooooooolooooo'\n     coooooooooooooooooooooooolc'\n  .':oooooooooooo$2ll$1ooooooooooooool\n.oooooooooooooooo$2ll$1oooooooooooo$2l$1ool\nooooooooooooooooo$2ll$1ooooooooooo$2ll$1oo'\n oooo$2l$1oooooooooo$2lll$1ooooooooo$2lll$1oo\n .ooooo$2lll$1ooooo$2lll$1ooooooooo$2lll$1ool\n .ooooooo$2lll$1oo$2llll$1oooo$2lllll$1ooooo:\n  'oooooooo$2llllllllllll$1oooooooo'\n     .c,.oo$2lllll$1oooooooo.$2\n           'll;\n           'll.\n           lll\n           lll\n          ;ll,\n          .l:"
  },
  {
    "path": "src/logo/ascii/fedora_silverblue.txt",
    "content": "    .;ooooooooooooooooooooooooooo.\n  ,dddddddddddddddddddddddddddddd'$3;\n$1 lddddddddddddddddddddddddddddd'$3;;;\n$1ddddd$2,XXX.$1ddddd$2,XXX.$1dddd'$2,XXX.$3;;;;;\n$1ddddd$2XX$1x$2XX$1ddddd$2XX$1x$2XX$1ddd'$2,XX$3x$2XX$3;;;;;\n$1ddddd$2'XXX'$1ddddd$2'XXX'$1dd'$2XXXXXX'$3;;;;;\n$1dddddd$2;X;$1ddddddd$2;X:$1d'$2XXX$3;;;;;;;;;;;\n$1dddddd$2;X;$1ddddddd$2;X:$2XXX$3;;;;;;;;;;;;;\n$1dddddd$2;X;$1dddddd'$2;XXX,,,,,,XXX.$3;;;;;\n$1dddddd$2;X;$1dddd'$2XXXX$2XXXXXXXXX$3x$2XX$3;;;;;\n$1dddddd$2;X;$1dd'$2XXX$3;;;;;;;;;;;$2XXX$3;;;;;;\n$1dddddd$2;X;$1'$2XXX$3;;;;;;;;;;;;;;;;;;;;;;\n$1dddddd$2;XXXXX,,,,,,,,,,,,,;XXX:$3;;;;;\n$1dddddd$2:XXXXXXXXXXXXXXXXXXXX$3x$2XX$3;;;;;\n$1ddddd'$3;;;;;;;;;;;;;;;;;;;$2'XXX'$3;;;;'\n$1ddd'$3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n$1o'$3;;;;;;;;;;;;;;;;;;;;;;;;;;;;'"
  },
  {
    "path": "src/logo/ascii/fedora_small.txt",
    "content": "        ,'''''.\n       |   ,.  |\n       |  |  '_'\n  ,....|  |..\n.'  ,_;|   ..'\n|  |   |  |\n|  ',_,'  |\n '.     ,'\n   '''''"
  },
  {
    "path": "src/logo/ascii/femboyos.txt",
    "content": "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\nMMMMWKkxkKWMMMMMMMMMMMMMMMMMMMMWKkxkKWMM\nMMMMXo. .;xKWMMMMMMMMMMMMMMMMMMXo. .oXMM\nMMWXx,..'..oXMMMMMMMMMMMMMMMMWKx,  .lXMM\nMMNo. .cOc.,xKWMMMMMMMMMMMMWXx;.....cXMM\nMMXl..;kKl. .oXMMMMMMMMMMWKx;..,ok:.'o0W\nWKx,.cKWNk;..lXMMMMMMMMWKx;..,o0NXl. .oN\nNo. .lXMMWKc.,dKWMMMMMMNo..;d0NWMNx,..lX\nNk:,:kNMMMNk:,ckNMMMMMMNxcxXWMMMMMN0ockN\nMWNNNWMMMMMWNNNWMMMMMMMMWWWMMMMMMMMMWWWM\nMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\nMMMMMMMNXKXNWMMMMMMMMMMMWNKOKWMMMMMMMMMM\nMMMMMMWKdccxXMMMMMMMMMMW0o'.oXMMMMMMMMMM\nMMMMMMMNO:.'o0NKkkkkkOXXo. .lXMMMMMMMMMM\nMMMMMMMMNx,..;o;.    .:o,..;kNMMMMMMMMMM\nMMMMMMMMMNO:     ...     .cKWMMMMMMMMMMM\nMMMMMMMMMMNx,. .;dk:.   .;kNMMMMMMMMMMMM\nMMMMMMMMMMMN0ocxXWNkl:,:xXWMMMMMMMMMMMMM\nMMMMMMMMMMMMMWNWMMMWWNNNWMMMMMMMMMMMMMMM\nMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM"
  },
  {
    "path": "src/logo/ascii/feren.txt",
    "content": " `----------`\n :+ooooooooo+.\n-o+oooooooooo+-\n..`/+++++++++++/...`````````````````\n   .++++++++++++++++++++++++++/////-\n    ++++++++++++++++++++++++++++++++//:`\n    -++++++++++++++++++++++++++++++/-`\n     ++++++++++++++++++++++++++++:.\n     -++++++++++++++++++++++++/.\n      +++++++++++++++++++++/-`\n      -++++++++++++++++++//-`\n        .:+++++++++++++//////-\n           .:++++++++//////////-\n             `-++++++---:::://///.\n           `.:///+++.             `\n          `........."
  },
  {
    "path": "src/logo/ascii/filotimo.txt",
    "content": "O      '\nlk:    ;xX.      .0K          '\nlodk   ;lldK'    .lld0O       :okX;\nlloo0  ;lllld0:  .lllllxKx    :clllkKk\nllllo, ;llllloox .clllllloxK  :cclllllx0N\nlllll, ;llllllll .cclllllllo' :cccclllllld\ncllll, ;cclllllc .ccccllllll' :cccccclllll\ncllll, ;cccllllc  ccccccllll' ::cccccclllc\nccccl, ;cccccllc  :cccccccll' :::ccccccclc\nccccc, ;cccccccc  ::cccccccl' :::::ccccccc\n:cccc, ;:ccccccc  ::::cccccc' :::::::ccccc\n:cccc, ;:::ccccc  ::::::cccc' :;::::::ccc:\n:::cc, ;:::::ccc  ;:::::::cc' :;;:::::::c:\n:::::, ;:::::::c  ;;:::::::c' :;;;;:::::::\n:::::, ;;::::::c  ;;;;::::::' :;;;;;::::::\n;::::, ,;;;::::c  ,;;;;;::::'  ';;;;;;::::\n';;::,   ;;;;:::    .;;;;;::'     .;;;;;::\n  ;;;,     ;;;;:       .;;;:'         ;;;:\n   ,;,       ,;:          ';'\n    .,"
  },
  {
    "path": "src/logo/ascii/finnix.txt",
    "content": "            ,,:;;;;:,,\n        ,;*%S########S%*;,\n      ;?#################S?:\n    :%######################?:\n   +##########################;\n  +############################;\n :#############.**,#############,\n *###########+      +###########+\n ?##########  $3Finnix$1  ##########*\n *###########,      ,###########+\n :#############%..%#############,\n  *############################+\n   *##########################+\n    ;S######################%:\n     ,+%##################%;\n        :+?S##########S?+:\n            ,:;++++;:,"
  },
  {
    "path": "src/logo/ascii/floflis.txt",
    "content": "              ,▄▄▄▌▓▓███▓▓▌▄▄▄,\n         ,▄▒▓███████████████████▓▄▄\n       ▄▓███████████████████████████▌\n      ▓███████████████████████████████\n   ,  ╙▓████████████████████████████▀   ▄\n  ╓█▓▄   ╙▀▓████████████████████▀▀`  ,▄██▓\n ╓█████▌▄,    '▀▀▀▀▓▓▓▓▓▓▀▀Å╙`    ▄▄▓█████▌\n ██████████▓▌▄  ,             ▄▓███████████▄\n╢████████████▓  ║████▓▓███▌  ╣█████████████▓\n▓█████████████  ▐█████████▀  ▓██████████████\n▓█████████████  ▐█████████▄  ███████████████\n▀████████████▌  ║█████████▌  ▀█████████████▌\n ████████████M  ▓██████████  ▐█████████████⌐\n ▀██████████▌  ▐███████████▌  ▀███████████▌\n   ╙▓█████▓   ▓██████████████▄  ▀███████▀\n     ╝▓██▀  ╓▓████████████████▓   ▀▓██▀\n          ,▄████████████████████▌,\n          ╝▀████████████████████▓▀'\n             `╙▀▀▓▓███████▓▀▀╩'"
  },
  {
    "path": "src/logo/ascii/freebsd.txt",
    "content": "```                        $2`\n  $1` `.....---...$2....--.```   -/\n  $1+o   .--`         $2/y:`      +.\n   $1yo`:.            $2:o      `+-\n    $1y/               $2-/`   -o/\n   $1.-                  $2::/sy+:.\n   $1/                     $2`--  /\n  $1`:                          $2:`\n  $1`:                          $2:`\n   $1/                          $2/\n   $1.-                        $2-.\n    $1--                      $2-.\n     $1`:`                  $2`:`\n       .--             `--.\n          .---.....----."
  },
  {
    "path": "src/logo/ascii/freebsd_small.txt",
    "content": "$1/\\,-'''''-,/\\\n\\_)       (_/\n|           |\n|           |\n ;         ;\n  '-_____-'"
  },
  {
    "path": "src/logo/ascii/freemint.txt",
    "content": "          ##\n          ##         #########\n                    ####      ##\n            ####  ####        ##\n####        ####  ##        ##\n        ####    ####      ##  ##\n        ####  ####  ##  ##  ##\n            ####  ######\n        ######  ##  ##  ####\n      ####    ################\n    ####        ##  ####\n    ##            ####  ######\n    ##      ##    ####  ####\n    ##    ##  ##    ##  ##  ####\n      ####  ##          ##  ##"
  },
  {
    "path": "src/logo/ascii/frugalware.txt",
    "content": "          `++/::-.`\n         /o+++++++++/::-.`\n        `o+++++++++++++++o++/::-.`\n        /+++++++++++++++++++++++oo++/:-.``\n       .o+ooooooooooooooooooosssssssso++oo++/:-`\n       ++osoooooooooooosssssssssssssyyo+++++++o:\n      -o+ssoooooooooooosssssssssssssyyo+++++++s`\n      o++ssoooooo++++++++++++++sssyyyyo++++++o:\n     :o++ssoooooo$2/-------------$1+syyyyyo+++++oo\n    `o+++ssoooooo$2/-----$1+++++ooosyyyyyyo++++os:\n    /o+++ssoooooo$2/-----$1ooooooosyyyyyyyo+oooss\n   .o++++ssooooos$2/------------$1syyyyyyhsosssy-\n   ++++++ssooooss$2/-----$1+++++ooyyhhhhhdssssso\n  -s+++++syssssss$2/-----$1yyhhhhhhhhhhhddssssy.\n  sooooooyhyyyyyh$2/-----$1hhhhhhhhhhhddddyssy+\n :yooooooyhyyyhhhyyyyyyhhhhhhhhhhdddddyssy`\n yoooooooyhyyhhhhhhhhhhhhhhhhhhhddddddysy/\n-ysooooooydhhhhhhhhhhhddddddddddddddddssy\n .-:/+osssyyyysyyyyyyyyyyyyyyyyyyyyyyssy:\n       ``.-/+oosysssssssssssssssssssssss\n               ``.:/+osyysssssssssssssh.\n                        `-:/+osyyssssyo\n                                .-:+++`"
  },
  {
    "path": "src/logo/ascii/funtoo.txt",
    "content": "   .dKXXd                         .\n  :XXl;:.                      .OXo\n.'OXO''  .''''''''''''''''''''':XNd..'oco.lco,\nxXXXXXX, cXXXNNNXXXXNNXXXXXXXXNNNNKOOK; d0O .k\n  kXX  xXo  KNNN0  KNN.       'xXNo   :c; 'cc.\n  kXX  xNo  KNNN0  KNN. :xxxx. 'NNo\n  kXX  xNo  loooc  KNN. oNNNN. 'NNo\n  kXX  xN0:.       KNN' oNNNX' ,XNk\n  kXX  xNNXNNNNNNNNXNNNNNNNNXNNOxXNX0Xl\n  ...  ......................... .;cc;."
  },
  {
    "path": "src/logo/ascii/furreto.txt",
    "content": "           .$2xOOko      $1.$2odd,\n          oX$1WW$2KOOOO. 'ON$1WW$20kkk.\n         $1.$2k0XKOOOOOOcOON$1W$2NOOOOO.\n          xOOOOOOOOOkkOOOOOOOOO;\n   $1.$2O0OkkocxO000000kcdk0OO0OOkx\n   k$1W$1M$2Xkkkkloxkkkx;  :dxxxxddc...\n  'kO0OOOOOkc          .cl:..kk0KK0Okc\n  ;kOOO0000xd.  dO00000Oo  .xkO$1NMM$2XOOOO\n.dddxkOOOkddc.kKN$1WW$2N000000l.ddk0000OOOO.\n 'dd:;ddddd;.dK$1MMMW$2K00KKK0O::ddxkO00Oko\n         .okxkOKK0kkOO00KKOxxlodddddddl\n       .00OOkkkkkkkkOOO00OOOO0O;  .dddl\n      'kkkkkxxkkkkkkkOOkxdxkxxddd.\n      cddddddddxxkkkkkxddddddddddo\n      'ddddddodddddddddddddddddddc\n       $1.$2ddddddodddddddddodddddddc\n                .odddo.\n\n               $1.$2kOOkkk;\n              lkK$1WN$2kkkxc\n             kkxkkkkkkx.\n              ,,..xxx."
  },
  {
    "path": "src/logo/ascii/galliumos.txt",
    "content": "sooooooooooooooooooooooooooooooooooooo+:\nyyooooooooooooooooooooooooooooooooo+/:::\nyyysoooooooooooooooooooooooooooo+/::::::\nyyyyyoooooooooooooooooooooooo+/:::::::::\nyyyyyysoooooooooooooooooo++/::::::::::::\nyyyyyyysoooooooooooooo++/:::::::::::::::\nyyyyyyyyysoooooo$2sydddys$1+/:::::::::::::::\nyyyyyyyyyysooo$2smMMMMMMMNd$1+::::::::::::::\nyyyyyyyyyyyyo$2sMMMMMMMMMMMN$1/:::::::::::::\nyyyyyyyyyyyyy$2dMMMMMMMMMMMM$1o//:::::::::::\nyyyyyyyyyyyyy$2hMMMMMMMMMMMm$1--//::::::::::\nyyyyyyyyyyyyyy$2hmMMMMMMMNy$1:..-://::::::::\nyyyyyyyyyyyyyyy$2yyhhyys+:$1......://:::::::\nyyyyyyyyyyyyyyys+:--...........-///:::::\nyyyyyyyyyyyys+:--................://::::\nyyyyyyyyyo+:-.....................-//:::\nyyyyyyo+:-..........................://:\nyyyo+:-..............................-//\no/:-...................................:"
  },
  {
    "path": "src/logo/ascii/garuda.txt",
    "content": "                   .%;888:8898898:\n                 x;XxXB%89b8:b8%b88:\n              .8Xxd                8X:.\n            .8Xx;                    8x:.\n          .tt8x          .d            x88;\n       .@8x8;          .db:              xx@;\n     ,tSXX°          .bbbbbbbbbbbbbbbbbbbB8x@;\n   .SXxx            bBBBBBBBBBBBBBBBBBBBbSBX8;\n ,888S                                     pd!\n8X88/                                       q\n8X88/\nGBB.\n x%88        d888@8@X@X@X88X@@XX@@X@8@X.\n   dxXd    dB8b8b8B8B08bB88b998888b88x.\n    dxx8o                      .@@;.\n      dx88                   .t@x.\n        d:SS@8ba89aa67a853Sxxad.\n          .d988999889889899dd."
  },
  {
    "path": "src/logo/ascii/garuda_dragon.txt",
    "content": "                .:--=========--:..\n           .:-=+++++===-----=======-:.\n         :=++++-:..            ..:-===-:.\n       .+++=:.                      .-=---.\n     . :-:                            :---:\n      :=           .                     :---:\n       .=-:        :-.                    .---:\n         :-===--::. .::::.                  ---:\n      .::--====-===+=-----=-:                ---:\n ::  :-++++====--=-:=====--=--:              .---\n+**=:+++===++++++==- -===== -==-.             ---:\n*****+==++==--::::::======== . ===-.          :---\n****==++-::.:::::-.::--======-.-===           .---\n***+=+-::::--:.     -==:-==--=======-::. .    :---\n=**++-::::==       -+===-==: :::.:-=-==---    :--:\n.*+*-:::-+=        ...::====  .::  .=-:--.     ..\n -*++:::=+-:             .:=-   -- .:  .  ...\n  =*++:.++-+:   .:.         =-.: :-:    ::   :.\n   -*++-=+=-+=-=++===-.     .====: .::::.\n    :++++++-:::--.-:===       --.\n     .=+++++- .=  +.=:=                .::\n       :=+++++:. .: : .              :----\n         .-++++=-:..            .:--===-.\n            .-=+++++===-----=======-:."
  },
  {
    "path": "src/logo/ascii/garuda_small.txt",
    "content": "     .----.\n   .'   ,  '.\n .'    '-----|\n'.   -----,\n  '.____.'"
  },
  {
    "path": "src/logo/ascii/gentoo.txt",
    "content": "         -/oyddmdhs+:.\n     -o$2dNMMMMMMMMNNmhy+$1-`\n   -y$2NMMMMMMMMMMMNNNmmdhy$1+-\n `o$2mMMMMMMMMMMMMNmdmmmmddhhy$1/`\n om$2MMMMMMMMMMMN$1hhyyyo$2hmdddhhhd$1o`\n.y$2dMMMMMMMMMMd$1hs++so/s$2mdddhhhhdm$1+`\n oy$2hdmNMMMMMMMN$1dyooy$2dmddddhhhhyhN$1d.\n  :o$2yhhdNNMMMMMMMNNNmmdddhhhhhyym$1Mh\n    .:$2+sydNMMMMMNNNmmmdddhhhhhhmM$1my\n       /m$2MMMMMMNNNmmmdddhhhhhmMNh$1s:\n    `o$2NMMMMMMMNNNmmmddddhhdmMNhs$1+`\n  `s$2NMMMMMMMMNNNmmmdddddmNMmhs$1/.\n /N$2MMMMMMMMNNNNmmmdddmNMNdso$1:`\n+M$2MMMMMMNNNNNmmmmdmNMNdso$1/-\nyM$2MNNNNNNNmmmmmNNMmhs+/$1-`\n/h$2MMNNNNNNNNMNdhs++/$1-`\n`/$2ohdmmddhys+++/:$1.`\n  `-//////:--."
  },
  {
    "path": "src/logo/ascii/gentoo_small.txt",
    "content": " _-----_\n(       \\\n\\    0   \\\n $2\\        )\n /      _/\n(     _-\n\\____-"
  },
  {
    "path": "src/logo/ascii/ghostbsd.txt",
    "content": "           ,gggggg.\n        ,agg9*   .g)\n      .agg* ._.,gg*\n    ,gga*  (ggg*'\n   ,ga*       ,ga*\n  ,ga'     .ag*\n ,ga'   .agga'\n 9g' .agg'g*,a\n 'gggg*',gga'\n      .gg*'\n    .gga*\n  .gga*\n (ga*"
  },
  {
    "path": "src/logo/ascii/ghostfreak.txt",
    "content": "              xSSSSSSSSSSSx:\n          XSSSSSSSSSSSSSSSSSSSSX\n       xSSSSSSSSSSSSSSSSSSSSSSSxSSX\n     xSSSSSSSSSSSSSSSSSSSSSSSSSSSSXSS\n    SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSXXXS;\n  +SSSSSSx++SSSSSSSSSSSSSSSSSSSSSSSSSXSX\n  SSSSS:::::::SSSSSSSSSSSSSXSSSSSSSSSS+S+\n XSSSX::::::::XxSSSSSSSSXSx;::xSSSSSSSSSS\n;SSSS:::::::::XSSSSSSSSS+:::::::xSSSSSSSS;\n+SSSSx::::::::SSSSSSSSS;:::::::::XSSSSSSSX\n SSSSXSx:::::SSSSxXSSSS::::::::::XSSSSSSSX\n xSSSSSSSSSSSS:::::SSSX::::::::::XSSSSSSS;\n  +SSSSSSSSSSSSSSX:SSSSS;:::::::SxSSSSSSS\n    +SSSSSSSSSSSSSSSSSSSSSSSSSXSSxSSSSSS\n       xxSSSSSSSSSSSSSSSSSSSSSSSSSSSSSx\n        SSSSSSSSSSSSSSSSSSSSSSSSSSSX\n        xXSSSSSSSSSSSSX\n        SSS +SSSxSSSSS\n             ;x  xSSx"
  },
  {
    "path": "src/logo/ascii/glaucus.txt",
    "content": "             ,,        ,d88P\n           ,d8P    ,ad8888*\n         ,888P    d88888*     ,,ad8888P*\n    d   d888P   a88888P*  ,ad8888888*\n  .d8  d8888:  d888888* ,d888888P*\n .888; 88888b d8888888b8888888P\n d8888J888888a88888888888888P*    ,d\n 88888888888888888888888888P   ,,d8*\n 888888888888888888888888888888888*\n *8888888888888888888888888888888*\n  Y888888888P* `*``*888888888888*\n   *^888^*            *Y888P**"
  },
  {
    "path": "src/logo/ascii/gnewsense.txt",
    "content": "                     ..,,,,..\n               .oocchhhhhhhhhhccoo.\n        .ochhlllllllc hhhhhh ollllllhhco.\n    ochlllllllllll hhhllllllhhh lllllllllllhco\n .cllllllllllllll hlllllo  +hllh llllllllllllllc.\nollllllllllhco''  hlllllo  +hllh  ``ochllllllllllo\nhllllllllc'       hllllllllllllh       `cllllllllh\nollllllh          +llllllllllll+          hllllllo\n `cllllh.           ohllllllho           .hllllc'\n    ochllc.            ++++            .cllhco\n       `+occooo+.                .+ooocco+'\n              `+oo++++      ++++oo+'"
  },
  {
    "path": "src/logo/ascii/gnome.txt",
    "content": "                               ,@@@@@@@@,\n                 @@@@@@      @@@@@@@@@@@@\n        ,@@.    @@@@@@@    *@@@@@@@@@@@@\n       @@@@@%   @@@@@@(    @@@@@@@@@@@&\n       @@@@@@    @@@@*     @@@@@@@@@#\n@@@@*   @@@@,              *@@@@@%\n@@@@@.\n @@@@#         @@@@@@@@@@@@@@@@\n         ,@@@@@@@@@@@@@@@@@@@@@@@,\n      ,@@@@@@@@@@@@@@@@@@@@@@@@@@&\n    .@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n    @@@@@@@@@@@@@@@@@@@@@@@@@@@\n   @@@@@@@@@@@@@@@@@@@@@@@@(\n   @@@@@@@@@@@@@@@@@@@@%\n    @@@@@@@@@@@@@@@@\n     @@@@@@@@@@@@*        @@@@@@@@/\n      &@@@@@@@@@@        @@@@@@@@@*\n        @@@@@@@@@@@,    @@@@@@@@@*\n          ,@@@@@@@@@@@@@@@@@@@@&\n              &@@@@@@@@@@@@@@\n                     ..."
  },
  {
    "path": "src/logo/ascii/gnu.txt",
    "content": "    _-`````-,           ,- '- .\n  .'   .- - |          | - -.  `.\n /.'  /                     `.   \\\n:/   :      _...   ..._      ``   :\n::   :     /._ .`:'_.._\\.    ||   :\n::    `._ ./  ,`  :    \\ . _.''   .\n`:.      /   |  -.  \\-. \\\\_      /\n  \\:._ _/  .'   .@)  \\@) ` `\\ ,.'\n     _/,--'       .- .\\,-.`--`.\n       ,'/''     (( \\ `  )\n        /'/'  \\    `-'  (\n         '/''  `._,-----'\n          ''/'    .,---'\n           ''/'      ;:\n             ''/''  ''/\n               ''/''/''\n                 '/'/'\n                  `;"
  },
  {
    "path": "src/logo/ascii/gobolinux.txt",
    "content": "  _____       _\n / ____|     | |\n| |  __  ___ | |__   ___\n| | |_ |/ _ \\| '_ \\ / _ \\\n| |__| | (_) | |_) | (_) |\n \\_____|\\___/|_.__/ \\___/"
  },
  {
    "path": "src/logo/ascii/goldendoglinux.txt",
    "content": "                     .:^~!!^.   .::\n                   ^7777!!!777!77Y5?\n                 :??!!~~~77!~~~~~!7Y.\n                :J7~~!?!~!~~~~~~~!??\n                ??~~~~7J7~~~~~~~7?!.\n                 J7~~~~~!J7~~~~!J7.\n                  !J~~~~!!?Y~~~~?7\n                  ~?7777?Y7~~~!Y^\n                   !J???7!~~~~!J!\n                 ~?7~~~~~~~~~~~7J\n              .~?7~~~~~~~~~~~~~7J\n            .~?7~~~~~~~~~~~~~~~7J\n           ^??~~~~~~~~~~~~~~~~!Y^\n           !J!~~~~~~!7777!!~~~~7J\n          !J!~~~~~!!!!!!77??~~~7J\n         :Y!~~~~~~~~~~~~~!!J?~~7J\n     :. !J!~~~~~~~~~~~~~!!JJ~~7Y.\n     JJ!!Y!~~~~~~~~~~~~!!JY!~~!J~\n.....~YJ?J!~~~~~~~~~~~~!JYJJJ7~!J^^:::...."
  },
  {
    "path": "src/logo/ascii/grapheneos.txt",
    "content": "                        B?\n                        G~\n                        G~&\n                      G!^:^?#\n                     &^.:::.J\n    &PG&          #G5JJ7~^~?JY5B&          #PG\n     B5JJPGJ77YG5JYP#   &&   &B5JYPGJ7?YG5JYP#\n        &Y..::.:P&               &?..::.:G\n         #!::::?                  B~::::J\n           B~J#                     B!?#\n            !P                       75\n            !P                       75\n            !5                       7Y\n         &Y~:^!P                  &J~:^!P\n         P..::.:B                 Y..::.:#\n      #PYJJ~^^!JJYP#          &B5YJ?~^^!JJYG#\n    &YYG#   &&   #PYJ5G5??JGGYJ5G&   &&   #PYP\n                     B^.::..7&\n                      J::::^G\n                       #Y^G&\n                        B~\n                        G!\n                        #"
  },
  {
    "path": "src/logo/ascii/grombyang.txt",
    "content": "            eeeeeeeeeeee\n         eeeeeeeeeeeeeeeee\n      eeeeeeeeeeeeeeeeeeeeeee\n    eeeee       $2.o+       $1eeee\n  eeee         $2`ooo/         $1eeee\n eeee         $2`+oooo:         $1eeee\neee          $2`+oooooo:          $1eee\neee          $2-+oooooo+:         $1eee\nee         $2`/:oooooooo+:         $1ee\nee        $2`/+   +++    +:        $1ee\nee              $2+o+\\             $1ee\neee             $2+o+\\            $1eee\neee        $2//  \\ooo/  \\\\        $1eee\n eee      $2//++++oooo++++\\\\     $1eee\n  eeee    $2::::++oooo+:::::   $1eeee\n    eeeee   $3Grombyang OS $1  eeee\n      eeeeeeeeeeeeeeeeeeeeeee\n         eeeeeeeeeeeeeeeee"
  },
  {
    "path": "src/logo/ascii/guix.txt",
    "content": " ..                             `.\n `--..```..`           `..```..--`\n   .-:///-:::.       `-:::///:-.\n      ````.:::`     `:::.````\n           -//:`    -::-\n            ://:   -::-\n            `///- .:::`\n             -+++-:::.\n              :+/:::-\n              `-....`"
  },
  {
    "path": "src/logo/ascii/guix_small.txt",
    "content": "|.__          __.|\n|__ \\        / __|\n   \\ \\      / /\n    \\ \\    / /\n     \\ \\  / /\n      \\ \\/ /\n       \\__/"
  },
  {
    "path": "src/logo/ascii/gxde.txt",
    "content": "            ################\n        ########################\n      ##########--    --##########\n    #######*     ******     *#######\n   ######     --*######---     ######\n  #####    ---          *####    #####\n #####   *--    #*#*#* *   ####   #####\n#####*  ##-    ##*-#* ###    ###  *#####\n#####  ###*   -##*#* *# *#   *###  #####\n#####  ###*   -*##*-*## *#*   ###  #####\n#####  ####  - --#### - ##*   ###  #####\n#####  *####*  *    -  ###-   ##*  #####\n#####*  #######-    *###-    ###  *#####\n #####*  ***#####****---    ###  *#####\n  ######   -----------  - ###   ######\n   ######      -----    ###    ######\n     #######                #######\n       ##########*----*##########\n         ######################\n             ##############\n"
  },
  {
    "path": "src/logo/ascii/haiku.txt",
    "content": "           MMMM              MMMM\n           MMMM              MMMM\n           MMMM              MMMM\n           MMMM              MMMM\n           MMMM$2       .ciO| /YMMMMM*\"\n$1           MMMM$2   .cOMMMMM|/MMMMM/`\n ,         ,iMM|/MMMMMMMMMMMMMMM*\n  `*.__,-cMMMMMMMMMMMMMMMMM/`$1.MMM\n           MM$2MMMMMMM/`:MMM/  $1MMMM\n           MMMM              MMMM\n           MMMM              MMMM\n           \"\"\"\"              \"\"\"\"\n"
  },
  {
    "path": "src/logo/ascii/haiku2.txt",
    "content": "$2          :dc'\n       'l:;'$1,$2'ck.    .;dc:.\n       co    $1..$2k.  .;;   ':o.\n       co    $1..$2k. ol      $1.$20.\n       co    $1..$2k. oc     $1..$20.\n       co    $1..$2k. oc     $1..$20.\n.Ol,.  co $1...''$2Oc;kkodxOdddOoc,.\n ';lxxlxOdxkxk0kd$1oooll$2dl$1ccc:$2clxd;\n     ..$1oOolllllccccccc:::::$2od;\n       cx:ooc$1:::::::;$2cooolcX.\n       cd$1.$2''cloxdoollc' $1...$20.\n       cd$1......$2k;$1.$2xl$1....  .$20.\n       .::c$1;..$2cx;$1.$2xo$1..... .$20.\n          '::c'$1...$2do$1..... .$2K,\n                  cd,.$1....:$2O,$1\n                    ':clod:'$1\n                        $1\n"
  },
  {
    "path": "src/logo/ascii/haiku_small.txt",
    "content": "       ,^,\n      /   \\\n*--_ ;     ; _--*\n\\   '\"     \"'   /\n '.           .'\n.-'\"         \"'-.\n '-.__.   .__.-'\n       |_|"
  },
  {
    "path": "src/logo/ascii/hamonikr.txt",
    "content": "                     cO0Ox.\n                  .ldddddddo.\n                .lddddddddddo\n              'lddddddddddddc\n            ,oddddddddddddd;\n          'ldddddddddddddo.\n        .oddddddddddddddc.\n      ,dddddddddddddddo.\n    ,ccoooooooocoddooo:\n  ,cooooooooooooooooop $3                 c000x.\n$1.cooooooooooooooopcllll$3              .cddddddo.\n$1coooooooooooooop' .qlll.$3           .ddoooooooo;\n$1cooooooooooc;        $3'qlllp.     .ddoooooooooo;\n$1.cooooooc;             $3'lllbc...coooooooooooo;\n$1  .cooc'                $3.llllcoooooooooooooo.\n                         .coooooooooooooop:\n                       .coooooooooooooop'\n                      .cooooooooooooop.\n                    .cooooooooooooop.\n                   .coooooooooooop.\n                  .cooooooooooop.\n                  .cooooooooop.\n                   .cooooop'"
  },
  {
    "path": "src/logo/ascii/hardclanz.txt",
    "content": "$1          ........::::....\n        ::################::..\n      :########################:.\n     :######**###################:\n    :###$2&&&&^$1############ $2&$1#######:\n   :#$2&&&&&$1.:##############:$2^&o$1`:###:\n  :#$2&&&&$1.:#################:.$2&&&$1`###:\n :##$2&^$1:######################:$2^&&$1::##:\n :#############################:$2&$1:##::\n :##########$2@@$1###########$2@@$1#####:.###:\n:#########$2@@$3o$2@@$1#########$2@@$3o$2@@$1########:\n:#######:$2@@$3o$50$3o$2@@@@$1###$2@@@@$3o$50$3o$2@@$1######: :\n :######:$2@@@$3o$2@@@@@@$1V$2@@@@@@$3o$2@@@$1######:\n   :#####:$2@@@@@@@@@@@@@@@@@@@$1:####;\n    :####:.$2@@@@@@@@@@@@@@@@$1:#####:\n    `:####:.$2@@@@@@@@@@@@@@$1:#####:\n      ``:##:.$2@@@@@@@@@@@@$1^## # :\n         : ##  $2\\@@@;@@@/ $1:: # :\n                 $2 VVV"
  },
  {
    "path": "src/logo/ascii/harmonyos.txt",
    "content": "                      ....-----....\n                  .-+##############+-..\n               .+######################+..\n             .########+-        -++#######-.\n           .+######-                -######+.\n          .######.                    .######..\n         .+####+                        +#####..\n        .-####+.                         +####+.\n        .+####+                          -####+..\n       ..+####+                          -####+...\n$2......--$1+####+$2--.......................--$1+####+$2-.........\n       ..$1--++++-$2......               ...$1-++++--$2..\n         ..$1--+++--$2...                ...$1-+++++-$2..\n          .$1--+++--$2....             ....$1--+++-$2...\n          ..$1-++++++--$2.....    ......$1---+---$2...\n          ...$1-+++++++++-----$2..$1----++++++--$2..\n            ...$1---++++++----++++++++---$2...\n                 .....$1-----+++-----$2...\n                       ...$1---$2..."
  },
  {
    "path": "src/logo/ascii/hash.txt",
    "content": "      +   ######   +\n    ###   ######   ###\n  #####   ######   #####\n ######   ######   ######\n\n####### '\"###### '\"########\n#######   ######   ########\n#######   ######   ########\n\n ###### '\"###### '\"######\n  #####   ######   #####\n    ###   ######   ###\n      ~   ######   ~"
  },
  {
    "path": "src/logo/ascii/hce.txt",
    "content": "          ti\n        jGGGGj\n       tGGGGGGt\n    ,LGGLGGGGGGGG,\n   jGGGGjGGGGGGGGGj\n GGGGGGGGGGGGGGGGGGGG\niGGGGGGt      tLLLLLLi\n,GGGGGi        iGGGGG,\n,jGGGG          LGGGG,\n,LjGGj           LLLL,\n,LLGG.           iiii.\n,LLj\n,LLj              jLL,\n,LLLL.           GGLL,\n,LLLLt           GGLL,\n,LGGGG          LGGGj,\n,GGGGGi        iGGGGG,\niGGGGGGj      tGGGGGG,\n GGGGGGGGGGGGGGGGGGGG\n   jGGGGGGGGGjGGGGj\n    ,LGGGGGGGGGGG,\n       tGGGGGGt\n        jGGGGj\n          it"
  },
  {
    "path": "src/logo/ascii/heliumos.txt",
    "content": "               ,,╥╥╥╦╦╦╥╥,,\n          ,╓╦Ñ╨^`_,,,,,,. `\"╨╩Nw\n       ,╥Ñ^`,╥╦╫╫╫╫╫╫╫╫╫╫╫╫ÑN≥,`╙Ñ╦_\n     ,j╩_,╦Ñ╙╙╩╫╫╫╫╫╫╫╫╫╫╫╫╫Ñ╨╨╩N,`╨N,\n    j╩_╓╫╫Ñ ]N ]╫╫╫╫╫╫╫╫╫╫╫╫_]N ]╫Ñw_╩N\n  ,╫^ ]╫╫╫Ñ ╫╫ ]╫╫╫╫╫╫╫╫╫╫╫╫ ╫╫ ]╫╫╫N_╙╫\n _╫`,╫╫╫╫╫Ñ ╫╫ ]╫╫╫╫╫╫╫╫╫╫╫╫ ╫╫ ]╫╫╫╫╫ `╫\n ÑH ╫╫╫╫╫╫Ñ ╫╫ ]╫╫╫╫╫╫╫╫╫╫╫╫ ╫╫ ]╫╫╫╫╫╫_]Ñ\nj╫ ]╫╫╫╫╫╫Ñ ╫╫ ]╫╫╫╫╫╫╫╫╫╫╫╫ ╫╫ ]╫╫╫╫╫╫H ╫H\nj╫ ╟╫╫╫╫╫╫Ñ ╫╫,,,,,,,,,,,,,,,╫╫ ]╫╫╫╫╫╫╫ ╠N\nj╫ ]╫╫╫╫╫╫Ñ ╫╫```````````````╫╫ ]╫╫╫╫╫╫╫ 1╡\n ╫_j╫╫╫╫╫╫Ñ ╫╫ ]╫╫╫╫╫╫╫╫╫╫╫╫ ╫╫ ]╫╫╫╫╫╫N ╫H\n ╟H_╫╫╫╫╫╫Ñ ╫╫ ]╫╫╫╫╫╫╫╫╫╫╫╫ ╫╫ ]╫╫╫╫╫╫ jÑ\n _╫╕ ╫╫╫╫╫Ñ ╫╫ ]╫╫╫╫╫╫╫╫╫╫╫╫ ╫╫ ]╫╫╫╫╫ ,╫\n   ╫╥ ╩╫╫╫Ñ ╫╫ ]╫╫╫╫╫╫╫╫╫╫╫╫ ╫╫ ]╫╫╫Ñ ╓Ñ\n   _╚N_`╫╫Ñ ╩╩ ]╫╫╫╫╫╫╫╫╫╫╫╫ ╩╩ 1╫Ñ`,jÑ_\n     `╩N,`╨N╦╦Ñ╫╫╫╫╫╫╫╫╫╫╫╫╫N╦╥]╩`,╦╩`\n       _╙Ñ╦,`\"╩Ñ╫╫╫╫╫╫╫╫╫╫╫Ñ╩^`,╥Ñ^_\n          _\"╨N╦w,_ `````_ ,╓╦N╩^\n              __`\"^╙╙╨╙╙^^`__"
  },
  {
    "path": "src/logo/ascii/huayra.txt",
    "content": "$2                     `\n            .       .       `\n       ``    -      .      .\n        `.`   -` `. -  `` .`\n          ..`-`-` + -  / .`     ```\n          .--.+--`+:- :/.` .-``.`\n            -+/so::h:.d-`./:`.`\n              :hNhyMomy:os-...-.  ````\n               .dhsshNmNhoo+:-``.```\n                $1`ohy:-$2NMds+::-.``\n            ````$1.hNN+`$2mMNho/:-....````\n       `````     `../dmNhoo+/:..``\n    ````            .dh++o/:....`\n.+s/`                `/s-.-.:.`` ````\n::`                    `::`..`\n                          .` `..\n                                ``"
  },
  {
    "path": "src/logo/ascii/hybrid.txt",
    "content": "$1   /                          $2#\n$1////&                      $2#####\n$1/////                      $2######\n$1/////      //////////      $2######\n$1///// //////////////////// $2######\n$1////////////////////////// $2######\n$1/////////              /// $2######\n$1///////                  / $2######\n$1//////                     $2######\n$1/////                      $2######\n$1/////                      $2######\n$1/////                      $2######\n$1/////                      $2######\n$1/////                      $2######\n$1/////                      $2#########\n$1////&                       $2########"
  },
  {
    "path": "src/logo/ascii/hydroos.txt",
    "content": " _    _           _            ____   _____\n| |  | |         | |          / __ \\ / ____|\n| |__| |_   _  __| |_ __ ___ | |  | | (___\n|  __  | | | |/ _` | '__/ _ \\| |  | |\\___ \\\n| |  | | |_| | (_| | | | (_) | |__| |____) |\n|_|  |_|\\__, |\\__,_|_|  \\___/ \\____/|_____/\n         __/ |\n        |___/"
  },
  {
    "path": "src/logo/ascii/hyperbola.txt",
    "content": "                     WW\n                     KX              W\n                    WO0W          NX0O\n                    NOO0NW  WNXK0OOKW\n                    W0OOOOOOOOOOOOKN\n                     N0OOOOOOO0KXW\n                       WNXXXNW\n                 NXK00000KN\n             WNK0OOOOOOOOOO0W\n           NK0OOOOOOOOOOOOOO0W\n         X0OOOOOOO00KK00OOOOOK\n       X0OOOO0KNWW      WX0OO0W\n     X0OO0XNW              KOOW\n   N00KNW                   KOW\n NKXN                       W0W\nWW                           W"
  },
  {
    "path": "src/logo/ascii/hyperbola_small.txt",
    "content": "    |`__.`/\n    \\____/\n    .--.\n   /    \\\n  /  ___ \\\n / .`   `.\\\n/.`      `.\\"
  },
  {
    "path": "src/logo/ascii/hypros.txt",
    "content": "           ___\n     ,adZZEEEE#&$2>=x.\n   $1,zAP*~'$4_,-$2'~*VM$2N&x.\n  $1,%&P^`$4<$3,.$4<<$2,-===--.N>x\n $1.%M7$4//$3,%^$2,x<3#$13EEbo$2<&>&b\n $1&#/$4/$3.<^$4/$2x<>^$4-.`$1`+&WW$2<&N&;\n$1/#/$4//$34$4//$2/W/    $4^+.`$1`###$2NM\\\n$1##'$4|$3.l$4|$2,&/      $4`.',$1I#I$2HI#\n$1#I$4||$3`I$4|$2(#(       $3)`'$1)##$2H~^\n$1@\\$4|||$3\\$4\\$2`X\\      $3///$1,##%V$3'/\n$4\\\\\\\\\\\\$3Y,$2*@b, $3.-+//$1/&#%#/$3,'\n$4`\\\\\\\\$2,.$4\\$3<$2`*$3^`x<$1,z<#&#x\"$3,'\n $3`x<<$2`Xx,$3`<_`$1+{##&@P^$4'>$3'\n  $3`<_<<$2^<\\-.$3`*`>$1<^'$4,-'\n    $3`<_=-$2^\\Xx$1XX\\.$3+<.\n      $3`^<_-$2^<Xx$1#X\\x$3+'-.\n          $3`\"^^$2+*$1*``` $3```"
  },
  {
    "path": "src/logo/ascii/iglunix.txt",
    "content": "     |\n     |        |\n              |\n|    ________\n|  /\\   |    \\\n  /  \\  |     \\  |\n /    \\        \\ |\n/      \\________\\\n\\      /        /\n \\    /        /\n  \\  /        /\n   \\/________/"
  },
  {
    "path": "src/logo/ascii/instantos.txt",
    "content": "     'cx0XWWMMWNKOd:'.\n  .;kNMMMMMMMMMMMMMWNKd'\n 'kNMMMMMMWNNNWMMMMMMMMXo.\n,0MMMMMW0o;'..,:dKWMMMMMWx.\nOMMMMMXl.        .xNMMMMMNo\nWMMMMNl           .kWWMMMMO'\nMMMMMX;            oNWMMMMK,\nNMMMMWo           .OWMMMMMK,\nkWMMMMNd.        ,kWMMMMMMK,\n'kWMMMMWXxl:;;:okNMMMMMMMMK,\n .oXMMMMMMMWWWMMMMMMMMMMMMK,\n   'oKWMMMMMMMMMMMMMMMMMMMK,\n     .;lxOKXXXXXXXXXXXXXXXO;......\n          ................,d0000000kd:.\n                          .kMMMMMMMMMW0;\n                          .kMMMMMMMMMMMX\n                          .xMMMMMMMMMMMW\n                           cXMMMMMMMMMM0\n                            :0WMMMMMMNx,\n                             .o0NMWNOc."
  },
  {
    "path": "src/logo/ascii/interix.txt",
    "content": "                   .$3.\n$1                  75$3G!\n$1                ^?PG$3&&J.\n$1              :!5GPP$3&&&B!\n$1             :YPPPPP$3&&&&&Y:\n$1            !5PPPPPP$3&&&&&&B!\n$1          :?PPPPPPPP$3&&&&&&&&Y~\n$1         !5PPPPPPPPP$3###&&&&&&B7\n$1       :?PPPP5555555$3B####&&&&&&5:\n$1      ~5PPPP555YJ$57!~7?$35B###&&&&&B?.\n$1   .:JPPPP5555Y$5?^....:^?$3G####&&&&&5:\n$1   75PPP555555Y$57:....:^!$35#####&&&&&B7.\n$1 :JPPPP$2555555YY?$5~::::^~$27YPGBB###$3&&&&&5^\n$175$2GGPPPPPP555555YJ?77??YYYYYY55PPGGB#$3&B?\n$2~!!7JY5PGGBBBBBBBBGGGGGGGBGGGGGP5YJ?7~~~\n       .::^~7?JYPGBB#BGPYJ?7!7^:.\n                 ..:^..."
  },
  {
    "path": "src/logo/ascii/irix.txt",
    "content": "           ./ohmNd/  +dNmho/-\n     `:+ydNMMMMMMMM.-MMMMMMMMMdyo:.\n   `hMMMMMMNhs/sMMM-:MMM+/shNMMMMMMh`\n   -NMMMMMmo-` /MMM-/MMM- `-omMMMMMN.\n `.`-+hNMMMMMNhyMMM-/MMMshmMMMMMmy+...`\n+mMNds:-:sdNMMMMMMMyyMMMMMMMNdo:.:sdMMm+\ndMMMMMMmy+.-/ymNMMMMMMMMNmy/-.+hmMMMMMMd\noMMMMmMMMMNds:.+MMMmmMMN/.-odNMMMMmMMMM+\n.MMMM-/ymMMMMMmNMMy..hMMNmMMMMMmy/-MMMM.\n hMMM/ `/dMMMMMMMN////NMMMMMMMd/. /MMMh\n /MMMdhmMMMmyyMMMMMMMMMMMMhymMMMmhdMMM:\n `mMMMMNho//sdMMMMM//NMMMMms//ohNMMMMd\n  `/so/:+ymMMMNMMMM` mMMMMMMMmh+::+o/`\n     `yNMMNho-yMMMM` NMMMm.+hNMMNh`\n     -MMMMd:  oMMMM. NMMMh  :hMMMM-\n      -yNMMMmooMMMM- NMMMyomMMMNy-\n        .omMMMMMMMM-`NMMMMMMMmo.\n          `:hMMMMMM. NMMMMMh/`\n             .odNm+  /dNms."
  },
  {
    "path": "src/logo/ascii/ironclad.txt",
    "content": "                 &#BGPPPPPG#&\n              B5?77!!?YJJ7!7YBB&\n           &G5YJ77!7JYYYYYBPJ&PY#\n         #PYYYYYY?!?YYYYY7?7JP5JJ\n        B?YYYYYY7!!7JYYYYJ!!?JJJ5\n &&    B7?J?77?7!!!!!77777!7Y5YYBBPGGG&\nG77?YBB!!!!!!!!!!!!!JYJ??7JYJJY# PYPPG&\nJ777JB?!7JJ???!!!7?JYYYYYPJ!7JB\nGYYG #JJJJJ??7!!!JYYY5PGB&GB&\n   #Y!?GB5YYJY5PG###&\n   GJJP"
  },
  {
    "path": "src/logo/ascii/itc.txt",
    "content": "$1....................-==============+...\n$1....................-==============:...\n$1...:===========-....-==============:...\n$1...-===========:....-==============-...\n$1....*==========+........-::********-...\n$1....*===========+.:*====**==*+-.-......\n$1....:============*+-..--:+**====*---...\n$1......::--........................::...\n$1..+-:+-.+::*:+::+:-++::++-.:-.*.:++:++.\n$1..:-:-++++:-::--:+::-::.:++-++:++--:-:."
  },
  {
    "path": "src/logo/ascii/januslinux.txt",
    "content": "               'l:\n        loooooo\n          loooo coooool\n looooooooooooooooooool\n  looooooooooooooooo\n         lool   cooo\n        coooooooloooooooo\n     clooooo  ;lood  cloooo\n  :loooocooo cloo      loooo\n loooo  :ooooool       loooo\nlooo    cooooo        cooooo\nlooooooooooooo      ;loooooo $2looooooc\n$1looooooooo loo   cloooooool    $2looooc\n$1 cooo       cooooooooooo       $2looolooooool\n$1            cooo:     $2coooooooooooooooooool\n                       loooooooooooolc:   loooc;\n                             cooo:    loooooooooooc\n                            ;oool         looooooo:\n                           coool          olc,\n                          looooc   ,,\n                        coooooc    loc\n                       :oooool,    coool:, looool:,\n                       looool:      ooooooooooooooo:\n                       cooolc        .ooooooooooool"
  },
  {
    "path": "src/logo/ascii/kaisen.txt",
    "content": "                  `:+oyyho.\n             `+:`sdddddd/\n        `+` :ho oyo++ohds-`\n       .ho :dd.  .: `sddddddhhyso+/-\n       ody.ddd-:yd- +hysssyhddddddddho`\n       yddddddhddd` ` `--`   -+hddddddh.\n       hddy-+dddddy+ohh/..+sddddy/:::+ys\n      :ddd/sdddddddddd- oddddddd       `\n     `yddddddddddddddd/ /ddddddd/\n:.  :ydddddddddddddddddo..sddddddy/`\nodhdddddddo- `ddddh+-``....-+hdddddds.\n-ddddddhd:   /dddo  -ydddddddhdddddddd-\n /hdy:o - `:sddds   .`./hdddddddddddddo\n  `/-  `+hddyosy+       :dddddddy-.-od/\n      :sydds           -hddddddd`    /\n       .+shd-      `:ohddddddddd`\n                `:+ooooooooooooo:"
  },
  {
    "path": "src/logo/ascii/kali.txt",
    "content": "..............\n            ..,;:ccc,.\n          ......''';lxO.\n.....''''..........,:ld;\n           .';;;:::;,,.x,\n      ..'''.            0Xxoc:,.  ...\n  ....                ,ONkc;,;cokOdc',.\n .                   OMo           ':$2dd$1o.\n                    dMc               :OO;\n                    0M.                 .:o.\n                    ;Wd\n                     ;XO,\n                       ,d0Odlc;,..\n                           ..',;:cdOOd::,.\n                                    .:d;.':;.\n                                       'd,  .'\n                                         ;l   ..\n                                          .o\n                                            c\n                                            .'\n                                             ."
  },
  {
    "path": "src/logo/ascii/kali_small.txt",
    "content": "     -#. #\n      @###\n  -######\n @#########\n=##.  .#####\n##      ## ##\n##       ## #\n##       @###\n##.        ###\n ##%       ##-\n  -##%    -*\n   :*##+\n     :*#*\n       -#\n        @\n        :"
  },
  {
    "path": "src/logo/ascii/kalpa_desktop.txt",
    "content": "       +++\n     ++++++++   +\n     ++++++++++++                +++\n      ++++++++++* +++++++++    ++++\n       +++++++ ++++++++++++++++++\n        ++++ ++++          +++++\n       ++++ +++              ++++\n     +++++ +++     ++++++     +++\n+++++++++++++     +++++++++    +++\n+++++++++++++    +++    +++    +++\n+++++++++++++    ++++   +++    +++\n  ++++++++ +++     +++++++     +++\n       +++++++                +++\n        ++++++++            ++++ ++\n        +++++++++++      +++++ +++++\n       ++++++++ ++++++++++++ +++++++++\n      ++++++++++++++    ++++++++++++++++\n     ++++++++++++++++++++++++  +++++++++\n      ++++++      +++++++++       ++++\n        +          +++++++\n                   +++++++\n                    +++++"
  },
  {
    "path": "src/logo/ascii/kaos.txt",
    "content": "                     ..\n  .....         ..OSSAAAAAAA..\n .KKKKSS.     .SSAAAAAAAAAAA.\n.KKKKKSO.    .SAAAAAAAAAA...\nKKKKKKS.   .OAAAAAAAA.\nKKKKKKS.  .OAAAAAA.\nKKKKKKS. .SSAA..\n.KKKKKS..OAAAAAAAAAAAA........\n DKKKKO.=AA=========A===AASSSO..\n  AKKKS.==========AASSSSAAAAAASS.\n  .=KKO..========ASS.....SSSSASSSS.\n    .KK.       .ASS..O.. =SSSSAOSS:\n     .OK.      .ASSSSSSSO...=A.SSA.\n       .K      ..SSSASSSS.. ..SSA.\n                 .SSS.AAKAKSSKA.\n                    .SSS....S.."
  },
  {
    "path": "src/logo/ascii/kdelinux.txt",
    "content": "            $1kB@BB     >BWWmm$2\n            $1$BBBB   .kBBBBB\"$2\n    ,mp.    $1%BBBB  {BBBBBF$2\n   '%BBBBmm $1$BBBR,$BBBBB`$2\n    `TBBB\"` $1$BBBBBBBBBF$2\n     kBF    $1%BBBRRBBBBB.$2\n,mmWBBF     $1kBBBB `%BBBBm$2\nT%BBBB      $1%BBBB   %BBBBBn$2\n  `\"T%m     $1$BBBB    \"%BBBBm$2\n     TBm    $1\"*\"\"\"   $2,m.$1\"F\"\"`$2\n    {BBBBm,.     ,zmBBBm.\n   !%BBBP\"T%BBBBBB\"\"R%BBB\n     ``     %BBBF     ``\n            !BBP"
  },
  {
    "path": "src/logo/ascii/kdeneon.txt",
    "content": "             `..---+/---..`\n         `---.``   ``   `.---.`\n      .--.`        ``        `-:-.\n    `:/:     `.----//----.`     :/-\n   .:.    `---`          `--.`    .:`\n  .:`   `--`                .:-    `:.\n `/    `:.      `.-::-.`      -:`   `/`\n /.    /.     `:++++++++:`     .:    .:\n`/    .:     `+++++++++++/      /`   `+`\n/+`   --     .++++++++++++`     :.   .+:\n`/    .:     `+++++++++++/      /`   `+`\n /`    /.     `:++++++++:`     .:    .:\n ./    `:.      `.:::-.`      -:`   `/`\n  .:`   `--`                .:-    `:.\n   .:.    `---`          `--.`    .:`\n    `:/:     `.----//----.`     :/-\n      .-:.`        ``        `-:-.\n         `---.``   ``   `.---.`\n             `..---+/---..`"
  },
  {
    "path": "src/logo/ascii/kernelos.txt",
    "content": "              .''''....''''.\n         .''...            ...''.\n       ''..                    ..''\n     '..                         ..''\n   .'.      .,,'..       .',,,'    ..'.\n  .'.       .,,,,'    .,,,,,'        .'.\n .'.        .,,,,'  .,,,,,.           .'.\n '..        .,,,,'',,,,;.             .''\n.'.         .,,,,,,,;;.               ..'\n.'.         .,,,,;;;;;$2,               $1..'.\n '..        .,;;;,$2';;;;;,             $1..'\n .'.        .;;$2;;'  .;;::::           $1.,.\n  '..       $2.;;;;'    .::::::.       $1.,'\n   '..      $2.;;;;'      .::::::.    $1.,$2'$1\n    .'..                          $2.',.\n      $1',..                      $2.','\n         $1.,,...              $2..',,.\n           ..,,''........',,,.\n                  ......"
  },
  {
    "path": "src/logo/ascii/kibojoe.txt",
    "content": "$3           ./+oooooo+/.\n           -/+ooooo+/:.`\n          $1`$3yyyo$2+++/++$3osss$1.\n         $1+NMN$3yssssssssssss$1.\n       $1.dMMMMN$3sssssssssssy$1Ns`\n      +MMMMMMMm$3sssssssssssh$1MNo`\n    `hMMMMMNNNMd$3sssssssssssd$1MMN/\n   .$3syyyssssssy$1NNmmmmd$3sssss$1hMMMMd:\n  -NMmh$3yssssssssyhhhhyssyh$1mMMMMMMMy`\n -NMMMMMNN$3mdhyyyyyyyhdm$1NMMMMMMMMMMMN+\n`NMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMd.\nods+/:-----://+oyydmNMMMMMMMMMMMMMMMMMN-\n`                     .-:+osyhhdmmNNNmdo"
  },
  {
    "path": "src/logo/ascii/kiss.txt",
    "content": "   $3 ___     \n   ($2.· $3|     \n   ($1<> $3|     \n  / $2__$3  \\    \n ( $1/  \\ $3/|   \n$1_$3/\\ $2__)$3/$1_$3)   \n$1\\/$3-____$1\\/$2    "
  },
  {
    "path": "src/logo/ascii/kiss2.txt",
    "content": "        ██████    ██████\n      ██$2██████$1████$2██████$1██\n    ██$3████$2████████████████$1██\n  ██$2██$3████$2██████████████████$1██\n██$2██$3██$2████$3████████████$2████████$1██\n██$2██████$3████████████████$2██████$1██\n██$2████████████████████████████$1██\n  ██$2████████████████████████$1██\n    ██$2██████$3████████$2██████$1██\n      ████$2████████████$1████\n          ████████████"
  },
  {
    "path": "src/logo/ascii/kogaion.txt",
    "content": "            ;;      ,;\n           ;;;     ,;;\n         ,;;;;     ;;;;\n      ,;;;;;;;;    ;;;;\n     ;;;;;;;;;;;   ;;;;;\n    ,;;;;;;;;;;;;  ';;;;;,\n    ;;;;;;;;;;;;;;, ';;;;;;;\n    ;;;;;;;;;;;;;;;;;, ';;;;;\n;    ';;;;;;;;;;;;;;;;;;, ;;;\n;;;,  ';;;;;;;;;;;;;;;;;;;,;;\n;;;;;,  ';;;;;;;;;;;;;;;;;;,\n;;;;;;;;,  ';;;;;;;;;;;;;;;;,\n;;;;;;;;;;;;, ';;;;;;;;;;;;;;\n';;;;;;;;;;;;; ';;;;;;;;;;;;;\n ';;;;;;;;;;;;;, ';;;;;;;;;;;\n  ';;;;;;;;;;;;;  ;;;;;;;;;;\n    ';;;;;;;;;;;; ;;;;;;;;\n        ';;;;;;;; ;;;;;;\n           ';;;;; ;;;;\n             ';;; ;;"
  },
  {
    "path": "src/logo/ascii/korora.txt",
    "content": "$2                ____________\n             _add55555555554$1:\n           _w?'$1``````````'$2)k$1:\n          _Z'$1`$2            ]k$1:\n          m($1`$2             )k$1:\n     _.ss$1`$2m[$1`$2,            ]e$1:\n   .uY\"^`$1`$2Xc$1`$2?Ss.         d($1`\n  jF'$1`$2    `@.  $1`$2Sc      .jr$1`\n jr$1`$2       `?n_ $1`$2$;   _a2\"$1`\n.m$1:$2          `~M$1`$21k$1`$25?!`$1`\n:#$1:$2             $1`$2)e$1```\n:m$1:$2             ,#'$1`\n:#$1:$2           .s2'$1`\n:m,________.aa7^$1`\n:#baaaaaaas!J'$1`\n ```````````"
  },
  {
    "path": "src/logo/ascii/krassos.txt",
    "content": "                  $2**@@@@@@@@@@@*\n             $2,@@@@%$1((((((((((((($2%@@@@,\n          $2#@@&$1((((((((((((((((((((((($2&@@%\n        $2@@&$1((((((((((((((((((((((((((((($2@@@\n      $2@@&$1((((((((((((((((((((((((((((((((($2&@@\n    $2.@@$1((((((((((((((((((((((((((((((((((((($2@@.\n    $2@@$1((((((((((((((((((((((((((((((((((((((($2@@\n   $2@@#$1((((((((((((((((((((((((((((($2%@@@@@@@#$1(#$2@@\n  $2.@@$1(((((((((((((((($2#%@@@@@@@@@&%#$1(((($2%@&$1(((($2@@.\n  $2.@@$1(((((((/($2&@@@@@@%$1(/(((((((((((((($2@@/$1((((($2@@.\n  $2.@@$1(///////////////////////////////$2@$1(///////$2@@\n   $2%@#$1/////////////////////////////($2#$1////////$2%@%\n   $2 @@$1(///////////////////////////$2%$1/////////($2@@\n     $2@@#$1***********************************$2%@@\n      $2*@@$1********************************$2/@@/\n        $2,@@#$1***************************$2%@@*\n           $2@@@&$1********************$2/@@@@\n               $2&@@@@&$1(//***//($2&@@@@&\n                  $2**@@@@@@@@@@@*"
  },
  {
    "path": "src/logo/ascii/kslinux.txt",
    "content": "K   K U   U RRRR   ooo\nK  K  U   U R   R o   o\nKKK   U   U RRRR  o   o\nK  K  U   U R  R  o   o\nK   K  UUU  R   R  ooo\n\n$2 SSS   AAA  W   W  AAA\nS     A   A W   W A   A\n SSS  AAAAA W W W AAAAA\n    S A   A WW WW A   A\n SSS  A   A W   W A   A"
  },
  {
    "path": "src/logo/ascii/kubuntu.txt",
    "content": "$1           `.:/ossyyyysso/:.\n        .:oyyyyyyyyyyyyyyyyyyo:`\n      -oyyyyyyyo$2dMMy$1yyyyyyysyyyyo-\n    -syyyyyyyyyy$2dMMy$1oyyyy$2dmMMy$1yyyys-\n   oyyys$2dMy$1syyyy$2dMMMMMMMMMMMMMy$1yyyyyyo\n `oyyyy$2dMMMMy$1syysoooooo$2dMMMMy$1yyyyyyyyo`\n oyyyyyy$2dMMMMy$1yyyyyyyyyyys$2dMMy$1sssssyyyo\n-yyyyyyyy$2dMy$1syyyyyyyyyyyyyys$2dMMMMMy$1syyy-\noyyyysoo$2dMy$1yyyyyyyyyyyyyyyyyy$2dMMMMy$1syyyo\nyyys$2dMMMMMy$1yyyyyyyyyyyyyyyyyysosyyyyyyyy\nyyys$2dMMMMMy$1yyyyyyyyyyyyyyyyyyyyyyyyyyyyy\noyyyyysos$2dy$1yyyyyyyyyyyyyyyyyy$2dMMMMy$1syyyo\n-yyyyyyyy$2dMy$1syyyyyyyyyyyyyys$2dMMMMMy$1syyy-\n oyyyyyy$2dMMMy$1syyyyyyyyyyys$2dMMy$1oyyyoyyyo\n `oyyyy$2dMMMy$1syyyoooooo$2dMMMMy$1oyyyyyyyyo\n   oyyysyyoyyyys$2dMMMMMMMMMMMy$1yyyyyyyo\n    -syyyyyyyyy$2dMMMy$1syyy$2dMMMy$1syyyys-\n      -oyyyyyyy$2dMMy$1yyyyyysosyyyyo-\n        ./oyyyyyyyyyyyyyyyyyyo/.\n           `.:/oosyyyysso/:.`"
  },
  {
    "path": "src/logo/ascii/kylin.txt",
    "content": "                 $1++\n             $2*  ***\n           $2*******\n         $2*******$1++\n      $2********$1+$3===\n    $2******$1#$2*$1+$3======\n   $2*******$1#$2***$1++$3===\n   $2*********$1+$2**$3===\n  $1##$2*********$1##$3==                  $3====$1+\n$4%$1##$2****$1++$3==$1+$2****$1#                $2***$1+$3=$2**\n$1#$2****$1++$3=====$1++$2****$1##            $2********\n$1####$2*$1+$3=======$1+$2******$3===$1-     $2***$1#$2*$1##$2****\n $1#$4%$1#$2******$1+$2**$1+$3==$1++$2**$3=======$1+$2*$1+$2*$1#$4%\n $4%$1#$2****$1+$2******$3=====$1+$3====$1+++$3==$2**\n  $1#$2*********$1##$2***$1###$2********$1##$4%\n   $2***$1##$4%$1#$4%%%%$2*****$1#$2*$1###$2*****$1#$4%\n   $2*$1#$4%%%$1#$4%%%%%$2****   $4%%$2*******$1#$4%\n   $1#$4%%%%%%          $4%%%$1##$2***$1###$4%%\n    $2**$1#$4%           $4%%%%$1#$4% $2**$1#$2**$1##\n   $1###$4%%         $4%%%%%$1   $4%%%$2***\n $4%%%%%%        $4%%%%%%%$1  $4%%%%$1#$2*\n $4%%%%%%                $4%%%%%%$1+$3="
  },
  {
    "path": "src/logo/ascii/lainos.txt",
    "content": "$2                    /==\\\n                    \\==/\n$1               · · · · · · ·\n            · · · · · · · · · ·\n           · · · $2.-======-.$1· · · ·\n$2        .::.$1 ·$2.-============-.$1· $2.::.\n     .:==:$1· $2.:===:'$1. ·· .$2':===:.$1 ·$2:==:.\n  .:===:$1 · $2:===.$1 ·  $3.--.$1  · $2.===:$1 · $2:===:.\n :===:$1· · $2:===.$1 · $3.:====:.$1 · $2.===:$1 · ·$2:===:\n(===:$1· · $2:===-$1 ·  $3:======:$1  · $2-===:$1 · ·$2:===)\n :===:$1· · $2:===.$1 · $3':====:'$1 · $2.===:$1 · ·$2:===:\n  ':===:$1 · $2:===.$1 ·  $3'--'$1  · $2.===:$1 · $2:===:'\n     ':==:$1· $2':===:.$1' ·· '$2.:===:'$1 ·$2:==:'\n        '::'$1 · $2'===-.  .-==='$1 · $2'::'\n  $2/==\\$1     · · · $2:===  ===:$1 · · ·     $2/==\\\n  \\==/$1      · · ·$2:===$1 ·$2===:$1· · ·      $2\\==/$2\n         .-.   $1· $2:===$1· $2===:$1 ·$2   $2.-.\n         .===.   .===  ===.   .===.\n           .========    ========.\n             '''''        '''''"
  },
  {
    "path": "src/logo/ascii/langitketujuh.txt",
    "content": "$2. $1'7L7L7L7L7L7L7L7L7L7L7L7L7L7L7L7L7L7\n$2L7.   $1'7L7L7L7L7L7L7L7L7L7L7L7L7L7L7L7\n$2L7L7L      $17L7L7L7L7L7L7L7L7L7L7L7L7L7\n$2L7L7L7                          $1L7L7L7\n$2L7L7L7           $1'L7L7L7L7L7L7L7L7L7L7\n$2L7L7L7               $1'L7L7L7L7L7L7L7L7\n$2L7L7L7                   $1'L7L7L7L7L7L7\n$2L7L7L7                          $1L7L7L7\n$2L7L7L7L7L7L7L7L7L7L7LL7L7L7.    $1'7L7L7\n$2L7L7L7L7L7L7L7L7L7L7L7L7L7L7L7L.   $1'L7\n$2L7L7L7L7L7L7L7L7L7L7L7L7L7L7L7L7L7.  $1'"
  },
  {
    "path": "src/logo/ascii/laxeros.txt",
    "content": "                    /.\n                 `://:-\n                `//////:\n               .////////:`\n              -//////////:`\n             -/////////////`\n            :///////////////.\n          `://////.```-//////-\n         `://///:`     .//////-\n        `//////:        `//////:\n       .//////-          `://///:`\n      -//////-            `://///:`\n     -//////.               ://////`\n    ://////`                 -//////.\n   `/////:`                   ./////:\n    .-::-`                     .:::-`\n\n.:://////////////////////////////////::.\n////////////////////////////////////////\n.:////////////////////////////////////:."
  },
  {
    "path": "src/logo/ascii/lede.txt",
    "content": "    _________\n   /        /\\\n  /  LE    /  \\\n /    DE  /    \\\n/________/  LE  \\\n\\        \\   DE /\n \\    LE  \\    /\n  \\  DE    \\  /\n   \\________\\/"
  },
  {
    "path": "src/logo/ascii/lfs.txt",
    "content": "$2            :@@@@@@@:\n$2            @@@@@@@@@-\n$2    .:%.    @@@@@@@@@+.       @%\n$2   *@@@%+:  :@@@@@@@%=: .=%@@@@@@=\n$2  :@@@@@@##@@@@@@@@@%*+%@%+@@@@@@@+\n$2  @@#$1####$2+@@@@@@@%:$1######$2=@@@@@@@@@-\n$2 *@%$1######$2.@@@@@#$1#########$2-@@@@@@@@#.\n$2 %@-$1#$2.@$1=$2:$1##$2+@@@@-$1###$2%@$1:$2=$1###$2*@#*+=-+#:\n$2 @@.$1#$2@@*$1=$2:$1#$2-%%**-$1##$2%@@%$1*$2*$1###$2#=-\n$2 @@-$1#$2@@@@+.-$3...$2:=.$1#$2%@@@@%$1###$2#-\n$2 %@%$1##$2*#:$3.o.....o...$2-%@+$1###$2#@+    -:\n$2 +@@*$1#$3....................$2+@@@@@@@@+\n$2  @%:$3....................._:$2@@@@@@@=.\n$2  .=:$3...............__*-=`.$2=@@@@@@#=.\n$2   :+:$3....:==*__*-=`:..==-:$2#@@@@@%+:\n$2     .--=-:  $3+..::.....-:    $2=%@*=:\n$2              :........-\n$2                .:...--."
  },
  {
    "path": "src/logo/ascii/libreelec.txt",
    "content": "$1          :+ooo/.      $2./ooo+:\n$1        :+ooooooo/.  $2./ooooooo+:\n$1      :+ooooooooooo:$2:ooooooooooo+:\n$1    :+ooooooooooo+-  $2-+ooooooooooo+:\n$1  :+ooooooooooo+-  $3--  $2-+ooooooooooo+:\n$1.+ooooooooooo+-  $3:+oo+:  $2-+ooooooooooo+-\n$1-+ooooooooo+-  $3:+oooooo+:  $2-+oooooooooo-\n$1  :+ooooo+-  $3:+oooooooooo+:  $2-+oooooo:\n$1    :+o+-  $3:+oooooooooooooo+:  $2-+oo:\n$4     ./   $3:oooooooooooooooooo:   $5/.\n$4   ./oo+:  $3-+oooooooooooooo+-  $5:+oo/.\n$4 ./oooooo+:  $3-+oooooooooo+-  $5:+oooooo/.\n$4-oooooooooo+:  $3-+oooooo+-  $5:+oooooooooo-\n$4.+ooooooooooo+:  $3-+oo+-  $5:+ooooooooooo+.\n$4  -+ooooooooooo+:  $3..  $5:+ooooooooooo+-\n$4    -+ooooooooooo+:  $5:+ooooooooooo+-\n$4      -+oooooooooo+:$5:+oooooooooo+-\n$4        -+oooooo+:    $5:+oooooo+-\n$4          -+oo+:        $5:+oo+-\n$4            ..            $5.."
  },
  {
    "path": "src/logo/ascii/lilidog.txt",
    "content": "        +DDDL+ +LDDDDD+\n      LD++++D+D+::::::+D\n     D+-::::=D+::::::::+LLDD+\n    D=::::::=L=-::::::=LL++=+D\n    D-:::::=LDD=-::-=+L=-::::=D\n    L+--==+++===++LDD+:::::::-D\n  DL+++==:::::::::-+D-:::::::=D\n D=:::::::::::::::::++:::::-+D\nD+::::::::::::::::::+D++==+LD+\n D=:::::::::::::::::+LD+=--:-+D\n  L+=-::::::::::::::L+-::::::=D\n   DLL++-::::::::::-D:::::::-+D\n       DL+:::::::::+D=-:::-=+D\n         D=:::::::-D +LDLLD+\n          L+--::-=D\n           +LDLDD+"
  },
  {
    "path": "src/logo/ascii/lingmo.txt",
    "content": "              ..',;;;;;;,,..              \n          ..,;;;;;;;;;;;;;;;;,..          \n       .';;;;;;;;;;;;;;;;;;;;;;;;,.       \n     .;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;'     \n   .;;;;;;;;;$2clodddol:$1;;;;;;;;;;;;;;;;.   \n  ';;;;;;;$2ckXWWWNNWWWN0d:$1;;;;;;;;;;;;;;'  \n ';;;;;;$2cOWWKxl:$1;;;:$2okXWXx$1;;;;;;;;;;;;;;, \n ;;;;;;$2:XWWo$1;;;;;;;;;;;$2ONNx$1;;;;;;;;;;;;;;.\n,;;;;;;$2xWMk$1;;;;;;;;;;;;;$20NX:$1;;;;;;;;;;;;;;\n;;;;;;;$2xMMk$1;;;;;;;;;;;;;$2oXNdcloooolc:$1;;;;;\n,;;;;;;$2:NMWl$1;;;;;;;;;;;;$2ckodKK00000Oc$1;;;;;\n.;;;;;;;$2cKMWOo:$1;;;;;$2cox0Xccdl:$1;;;;;:;;;;;'\n ;;;;;;;;;$2o0WWNK000KXXKkodKKo$1;;;;;;;;;;;; \n  ,;;;;;;;;;$2:loxxxxdlc$1;;;;$2xKKxc$1;;;;;;;;;  \n   ,;;;;;;;;;;;;;;;;;;;;;;;$2ckK0o;$1;;;;;;   \n     ;;;;;;;;;;;;;;;;;;;;;;;;$2:l$1;;;;;;.    \n       ;;;;;;;;;;;;;;;;;;;;;;;;;;;;.      \n          ;;;;;;;;;;;;;;;;;;;;;;.         \n              ';;;;;;;;;;;;,              \n                   ';;'.                  \n"
  },
  {
    "path": "src/logo/ascii/linspire.txt",
    "content": "$2                                                   __^\n$2                                                __/    \\\n$2   MMy        dMy                            __/        \\\n$2  dMMy        MMy                            $1MM$2          \\\n$2  MMMy            ,,        $1dMMMMn                        $2\\\n$2 dMMy        dMM dMMMMMMy  $1dMM  MM dMMMMMy  dMM MM.nMMM dMMMMMM\n$1MMM          $2MMy MMy  MMy $1dMM      MMy  MMy MMy MMy    dy   dMy\n$1MMM         $2dMM dMM   MMy  $1dMMMMy dMM  dMM dMM dMM    dMMMMMMM\n$2 dMMy       MMy MMy  MMy     $1dMMy MM  MMy MMy  MMy    dMM\n$2dMMy       dMM dMM  dMM $1dMM  MMy dMMMMMy dMM  dMM     MMy   MM\n$2MMMMMMMMMM MMy MMy  MMy $1dMMMyyy MMy     MMy  MMy      dMMMMMMy\n$2                                $1dy"
  },
  {
    "path": "src/logo/ascii/linux.txt",
    "content": "        $2#####\n       $2#######\n       $2##$1O$2#$1O$2##\n       $2#$3#####$2#\n     $2##$1##$3###$1##$2##\n    $2#$1##########$2##\n   $2#$1############$2##\n   $2#$1############$2###\n  $3##$2#$1###########$2##$3#\n$3######$2#$1#######$2#$3######\n$3#######$2#$1#####$2#$3#######\n  $3#####$2#######$3#####"
  },
  {
    "path": "src/logo/ascii/linux_small.txt",
    "content": "    $1___\n   ($2.. $1\\\n   ($3<> $1|\n  /$2/  \\ $1\\\n ( $2|  | $1/|\n$3_$1/\\ $2__)$1/$3_$1)\n$3\\/$1-____$3\\/"
  },
  {
    "path": "src/logo/ascii/linuxlite.txt",
    "content": "          ,xXc\n      .l0MMMMMO\n   .kNMMMMM$2W$1MMMN,\n   KMMMMMM$2K$1MMMMMMo\n  'MMMMMMN$2K$1MMMMMM:\n  kMMMMMM$2O$1MMMMMMO\n .MMMMMM$20$1XMMMMMW.\n oMMMMM$2x$1MMMMMMM:\n WMMMMM$2x$1MMMMMMO\n:MMMMMM$2O$1XMMMMW\n.0MMMMM$2x$1MMMMM;\n:;cKMMW$2x$1MMMMO\n'MMWMMX$2O$1MMMMl\n kMMMMK$2O$1MMMMMX:\n .WMMMMK$2O$1WMMM0c\n  lMMMMMW$2O$1WMNd:'\n   oollXMK$2o$1Xxl;.\n     ':. .:$2 .$1'\n              $2..\n                ."
  },
  {
    "path": "src/logo/ascii/linuxlite_small.txt",
    "content": "   /\\\n  /  \\\n / $2/ $1/\n/ $2/ $1/\n\\ $2\\ $1\\\n \\_$2\\$1_\\\n$2    \\"
  },
  {
    "path": "src/logo/ascii/linuxmint.txt",
    "content": "            $2_.-ppOOOOOOqq-._\n         .oOOOOPPPPPPPPPPOOOOo.\n      .oOOOO$1.=oOOOOOOOOOOo=.$2OOOOo.\n    .:OOO$1.=oOOOOOOOOOOOOOOOOo=.$2OOO:.\n   .OOO$1.OOOOOOOOOOOOOOOOOOOOOOOO.$2OOO.\n  .OOO$1.OO    OOO:´   `::´    `:OOO.$2OO:\n .OOO$1.OOO    OO                OOO.$2OOO:\n OOO$1.OOOO    OO    oo    oo    OOOO.$2OOO\n:OOO$1:OOOO    OO    OO    OO    OOOO:$2OOO:\n:OOO$1:OOOO    OO    OO    OO    OOOO:$2OOO:\n'OOO$1'OOOO    OO    OO    OO    OOOO'$2OOO'\n OOO$1'OOOO    OO____OO____OO    OOOO'$2OOO'\n 'OOO$1'OOO    'OOOOOOOOOOOO'    OOOO'$2OOO\n  'OOO$1'OOO                    .OOO'$2OOO'\n   'OOO$1'OOOO:ooooooooooooooo:OOOO'$2OOO'\n    ':OOOo$1'=OOOOOOOOOOOOOOOOO='$2oOOO:'\n      ':OOOOo$1'=OOOOOOOOOOO='$2oOOOO:'\n         ``-OOOOooooooooooOOOO-´´\n             ```-=:OOOO:=-´´´\n"
  },
  {
    "path": "src/logo/ascii/linuxmint2.txt",
    "content": "             $2...-:::::-...\n          .-MMMMMMMMMMMMMMM-.\n      .-MMMM$1`..-:::::::-..`$2MMMM-.\n    .:MMMM$1.:MMMMMMMMMMMMMMM:.$2MMMM:.\n   -MMM$1-M---MMMMMMMMMMMMMMMMMMM.$2MMM-\n  :MMM$1:MM`  :MMMM:....::-...-MMMM:$2MMM:\n :MMM$1:MMM`  :MM:`  ``    ``  `:MMM:$2MMM:\n.MMM$1.MMMM`  :MM.  -MM.  .MM-  `MMMM.$2MMM.\n:MMM$1:MMMM`  :MM.  -MM-  .MM:  `MMMM-$2MMM:\n:MMM$1:MMMM`  :MM.  -MM-  .MM:  `MMMM:$2MMM:\n:MMM$1:MMMM`  :MM.  -MM-  .MM:  `MMMM-$2MMM:\n.MMM$1.MMMM`  :MM:--:MM:--:MM:  `MMMM.$2MMM.\n :MMM$1:MMM-  `-MMMMMMMMMMMM-`  -MMM-$2MMM:\n  :MMM$1:MMM:`                `:MMM:$2MMM:\n   .MMM$1.MMMM:--------------:MMMM.$2MMM.\n     '-MMMM$1.-MMMMMMMMMMMMMMM-.$2MMMM-'\n       '.-MMMM$1``--:::::--``$2MMMM-.'\n            '-MMMMMMMMMMMMM-'\n               ``-:::::-``"
  },
  {
    "path": "src/logo/ascii/linuxmint_old.txt",
    "content": "MMMMMMMMMMMMMMMMMMMMMMMMMmds+.\nMMm----::-://////////////oymNMd+`\nMMd      $2/++                $1-sNMd:\nMMNso/`  $2dMM    `.::-. .-::.` $1.hMN:\nddddMMh  $2dMM   :hNMNMNhNMNMNh: $1`NMm\n    NMm  $2dMM  .NMN/-+MMM+-/NMN` $1dMM\n    NMm  $2dMM  -MMm  `MMM   dMM. $1dMM\n    NMm  $2dMM  -MMm  `MMM   dMM. $1dMM\n    NMm  $2dMM  .mmd  `mmm   yMM. $1dMM\n    NMm  $2dMM`  ..`   ...   ydm. $1dMM\n    hMM- $2+MMd/-------...-:sdds  $1dMM\n    -NMm- $2:hNMNNNmdddddddddy/`  $1dMM\n    -dMNs-$2``-::::-------.``    $1dMM\n    `/dMNmy+/:-------------:/yMMM\n      ./ydNMMMMMMMMMMMMMMMMMMMMM\n          .MMMMMMMMMMMMMMMMMMM"
  },
  {
    "path": "src/logo/ascii/linuxmint_small.txt",
    "content": " __________\n|_          \\\n  | $2| _____ $1|\n  | $2| | | | $1|\n  | $2| | | | $1|\n  | $2\\__$2___/ $1|\n  \\_________/"
  },
  {
    "path": "src/logo/ascii/live_raizo.txt",
    "content": "             `......`\n        -+shmNMMMMMMNmhs/.\n     :smMMMMMmmhyyhmmMMMMMmo-\n   -hMMMMd+:. `----` .:odMMMMh-\n `hMMMN+. .odNMMMMMMNdo. .yMMMMs`\n hMMMd. -dMMMMmdhhdNMMMNh` .mMMMh\noMMMm` :MMMNs.:sddy:-sMMMN- `NMMM+\nmMMMs  dMMMo sMMMMMMd yMMMd  sMMMm\n----`  .---` oNMMMMMh `---.  .----\n              .sMMy:\n               /MM/\n              +dMMms.\n             hMMMMMMN\n            `dMMMMMMm:\n      .+ss+sMNysMMoomMd+ss+.\n     +MMMMMMN` +MM/  hMMMMMNs\n     sMMMMMMm-hNMMMd-hMMMMMMd\n      :yddh+`hMMMMMMN :yddy/`\n             .hMMMMd:\n               `..`"
  },
  {
    "path": "src/logo/ascii/lliurex.txt",
    "content": "                    ~.       ........\n               ::~~ +=:    ~:========:~\n           ..~:+oo===oo:.~+oooooooooooo+~\n        .:+==ooooooooooo==ooooooooooooooo:\n      .:=oooooooooooooooooooooooooooooooo=\n..   .=oooooooooooooooooooooooooooooooooo=\n==. .+ooooooooooooooooooooooooooooooooooo=\n~+:~:oooooooooooooooooooooooooooooooooooo:\n ~ooooooooooooooooooooooooooooooooooooo=:\n ~oooooooooooooooooooooooooooooooooo==:.\n .+oooooooooooooooooooooooooo=~::::~..\n  ~+oooooooooooooooooooooooo=:\n   .:=ooooooooooooooooooooo=:\n     .:==ooooooooooooooo==:.\n        ~~:===oooooo==+:~\n           ...~~:~::..\n"
  },
  {
    "path": "src/logo/ascii/lmde.txt",
    "content": "$2          `.-::---..\n$1      .:++++ooooosssoo:.\n    .+o++::.      `.:oos+.\n$1   :oo:.`             -+oo$2:\n$1 $2`$1+o/`    .$2::::::$1-.    .++-$2`\n$1$2`$1/s/    .yyyyyyyyyyo:   +o-$2`\n$1$2`$1so     .ss       ohyo` :s-$2:\n$1$2`$1s/     .ss  h  m  myy/ /s`$2`\n$1`s:     `oo  s  m  Myy+-o:`\n`oo      :+sdoohyoydyso/.\n :o.      .:////////++:\n$1 `/++        $2-:::::-\n$1  $2`$1++-\n$1   $2`$1/+-\n$1     $2.$1+/.\n$1       $2.$1:+-.\n          `--.``"
  },
  {
    "path": "src/logo/ascii/locos.txt",
    "content": "              $3..;:::::::;.               \n           .0X'$1''''''''''''$3'N:\n         :Xd$1,.'''''''''''''''$3lKx\n       .0o$1'.'''''''''''''''''''$3:K;\n      .O$1;.'$3okOx:$1'''''''''$3,dOOx:$1''$3O;\n      xc$1.'$3xXl$1 $3kX;$1''$2:cc;$1''$3kXl$1 $3OX;$1''$3k.\n     'x$1..'$3:0X0Xx$1',$2OOOOOd$1,:$30X0Xx$1'''$3o:\n     ::$1..'$2,clollxOOOOOOOOdlddlc$1''''$3d\n     l'$1..$2':kkkkOOOOOOOOOOOOkkkx,$1'''$3x\n     o$1...''''',$2codxkkxxxdl$1:''''''''$3d.\n     d$1...''''''''''''''''''''''''''$3o.\n     d$1..''''$3,loc,$1''''''''$3;os:,$1'''''$3d'\n    :d$1..'$3,dKXXXXX0kxxxk0XXXXXX0l$1'''$3,O.\n   '0$1..'$3cKXXXXXXXXXXXXXXXXXXXXXXk$1'''$3ck\n  '0$1'.'$3cXXXXXXXXXXXXXXXXXXXXXXXXXO$1'''$3od\n .0$1,.''$30XXXXXXXXXXXXXXXXXXXXXXXXXXo$1'''$3k;\n cc$1..''$3XXXXXXXXXXXXXXXXXXXXXXXXXXXk$1''''$3k\n l$1...',$3XXXXXXXXXXXXXXXXXXXXXXXXXXXO$1''''$3d."
  },
  {
    "path": "src/logo/ascii/lubuntu.txt",
    "content": "           `.:/ossyyyysso/:.\n        `.:yyyyyyyyyyyyyyyyyy:.`\n      .:yyyyyyyyyyyyyyyyyyyyyyyy:.\n    .:yyyyyyyyyyyyyyyyyyyyyyyyyyyy:.\n   -yyyyyyyyyyyyyy$2+hNMMMNh+$1yyyyyyyyy-\n  :yy$2mNy+$1yyyyyyyy$2+Nmso++smMdhyysoo+$1yy:\n -yy$2+MMMmmy$1yyyyyy$2hh$1yyyyyyyyyyyyyyyyyyy-\n.yyyy$2NMN$1yy$2shhs$1yyy$2+o$1yyyyyyyyyyyyyyyyyyyy.\n:yyyy$2oNM+$1yyyy$2+sso$1yyyyyyy$2ss$1yyyyyyyyyyyyy:\n:yyyyy$2+dNs$1yyyyyyy$2++$1yyyyy$2oN+$1yyyyyyyyyyyy:\n:yyyyy$2oMMmhysso$1yyyyyyyyyy$2mN+$1yyyyyyyyyyy:\n:yyyyyy$2hMm$1yyyyy$2+++$1yyyyyyy$2+MN$1yyyyyyyyyyy:\n.yyyyyyy$2ohmy+$1yyyyyyyyyyyyy$2NMh$1yyyyyyyyyy.\n -yyyyyyyyyy$2++$1yyyyyyyyyyyy$2MMh$1yyyyyyyyy-\n  :yyyyyyyyyyyyyyyyyyyyy$2+mMN+$1yyyyyyyy:\n   -yyyyyyyyyyyyyyyyy$2+sdMMd+$1yyyyyyyy-\n    .:yyyyyyyyy$2hmdmmNMNdy+$1yyyyyyyy:.\n      .:yyyyyyy$2my$1yyyyyyyyyyyyyyy:.\n        `.:yyyy$2s$1yyyyyyyyyyyyy:.`\n           `.:/oosyyyysso/:.`\n"
  },
  {
    "path": "src/logo/ascii/lunar.txt",
    "content": "`-.                                 `-.\n  -ohys/-`                    `:+shy/`\n     -omNNdyo/`          :+shmNNy/`\n             $3      -\n                 /mMmo\n                 hMMMN`\n                 .NMMs\n    $1  -:+oooo+//: $3/MN$1. -///oooo+/-`\n     /:.`          $3/$1           `.:/`\n$3          __\n         |  |   _ _ ___ ___ ___\n         |  |__| | |   | .'|  _|\n         |_____|___|_|_|__,|_|"
  },
  {
    "path": "src/logo/ascii/macaronios.txt",
    "content": " .::-::::.                :======---:.\n .-=++++++++==-:.     .:-++===--=**+=:\n.=. ...:::::--=****+++=-:.        --\n-:      $2.-==-.$1  .::.              ::\n.      $2:*%@@@@@#.     .:::.\n      :#%%%%%@%#+. :*##%%%%#+.\n      *%%%%%%*. =. +*#%%%%%%%#:\n      *%%%%%%-       +%%%%%%#@=\n      :%%%%%%#-.  .:+%%%%%%%%+\n       :*%%%%%%+.  .-+*###*=:\n         :=+=-."
  },
  {
    "path": "src/logo/ascii/macos.txt",
    "content": "                     ..'\n                 ,xNMM.\n               .OMMMMo\n               lMM\"\n     .;loddo:.  .olloddol;.\n   cKMMMMMMMMMMNWMMMMMMMMMM0:\n $2.KMMMMMMMMMMMMMMMMMMMMMMMWd.\n XMMMMMMMMMMMMMMMMMMMMMMMX.\n$3;MMMMMMMMMMMMMMMMMMMMMMMM:\n:MMMMMMMMMMMMMMMMMMMMMMMM:\n$4.MMMMMMMMMMMMMMMMMMMMMMMMX.\n kMMMMMMMMMMMMMMMMMMMMMMMMWd.\n $5'XMMMMMMMMMMMMMMMMMMMMMMMMMMk\n  'XMMMMMMMMMMMMMMMMMMMMMMMMK.\n    $6kMMMMMMMMMMMMMMMMMMMMMMd\n     ;KMMMMMMMWXXWMMMMMMMk.\n       \"cooc*\"    \"*coo'\""
  },
  {
    "path": "src/logo/ascii/macos2.txt",
    "content": "                     ..'\n                 ,xN  .\n               .O    o\n               l  \"\n     .;loddo:.  .olloddol;.\n   cK          NW          0:\n $2.K                       Wd.\n X                       X.\n$3;                        :\n:                        :\n$4.                        X.\n k                        Wd.\n $5'X                          k\n  'X                        K.\n    $6k                      d\n     ;K       WXXW       k.\n       \"cooc*\"    \"*coo'\""
  },
  {
    "path": "src/logo/ascii/macos2_small.txt",
    "content": "$1        .:'\n    __ :'__\n$2 .'`  `-'  ``.\n$3:          .-'\n$4:         :\n :         `-;\n$5  `.__.-.__.'"
  },
  {
    "path": "src/logo/ascii/macos3.txt",
    "content": "                -:+:.\n               :++++.\n              /+++/.\n      .:-::- .+/:-\\`\\`.::-\n   .:/++++++/::::/++++++/::`\n $2.:///////////////////////::`\n /////////////////////////`\n$3+++++++++++++++++++++++++`\n++++++++++++++++++++++++\n$4ssssssssssssssssssssssss.\n:ssssssssssssssssssssssss-\n $5osssssssssssssssssssssssss`\n `syyyyyyyyyyyyyyyyyyyyyyyyys`\n  $6`ossssssssssssssssssssssss\n    :ooooooooooooooooooo+.\n     `:++oo+/:-  -:/+o+/-"
  },
  {
    "path": "src/logo/ascii/macos_small.txt",
    "content": "$1        .:'\n    __ :'__\n$2 .'`__`-'__``.\n$3:__________.-'\n$4:_________:\n :_________`-;\n$5  `.__.-.__.'"
  },
  {
    "path": "src/logo/ascii/mageia.txt",
    "content": "        .°°.\n         °°   .°°.\n         .°°°. °°\n         .   .\n          °°° .°°°.\n      .°°°.   '___'\n$2     .$1'___'     $2   .\n   :dkxc;'.  ..,cxkd;\n .dkk. kkkkkkkkkk .kkd.\n.dkk.  ';cloolc;.  .kkd\nckk.                .kk;\nxO:                  cOd\nxO:                  lOd\nlOO.                .OO:\n.k00.              .00x\n .k00;            ;00O.\n  .lO0Kc;,,,,,,;c0KOc.\n     ;d00KKKKKK00d;\n        .,KKKK,."
  },
  {
    "path": "src/logo/ascii/mageia_small.txt",
    "content": "   *\n    *\n   **\n$2 /\\__/\\\n/      \\\n\\      /\n \\____/"
  },
  {
    "path": "src/logo/ascii/magix.txt",
    "content": "                $2@\n           @@--=====@@\n      @@--==@@     @@====+@\n     @-@@               @==@\n    @=@\n   @=@$1   @=@  @-====  @=@$2\n  @=@$1    @-===@==++@===+@$2\n @=@$1     @--====@@=====+@$2\n-=@$1      @--==========++@$2\n==$1       @--==========++@$2     @=@\n@==$1      @--=======@==++@$2    @=+@\n @==$1      @-==========++$2    @=@\n  @==$1      @-=======@=%$2    @=@\n   @==$1        @@@@@@$2      @=@\n    @====@@@         @@===+%\n        @@=====@@==++++@@\n              =#@=@\n             @==@++@\n               @@@\n"
  },
  {
    "path": "src/logo/ascii/magpieos.txt",
    "content": "        ;00000     :000Ol\n     .x00kk00:    O0kk00k;\n    l00:   :00.  o0k   :O0k.\n  .k0k.     x$2d$dddd$1k'    .d00;\n  k0k.      $2.dddddl       $1o00,\n o00.        $2':cc:.        $1d0O\n.00l                       ,00.\nl00.                       d0x\nk0O                     .:k0o\nO0k                 ;dO0000d.\nk0O               .O0O$2xxxxk$100:\no00.              k0O$2dddddd$1occ\n'00l              x0O$2dddddo$3;..$1\n x00.             .x00$2kxxd$3:..$1\n .O0x               .:oxxx$4Okl.$1\n  .x0d                     $4,xx,$1\n    .:o.          $4.xd       ckd$1\n       ..          $4dxl     .xx;\n                    :xxolldxd'\n                      ;oxdl."
  },
  {
    "path": "src/logo/ascii/mainsailos.txt",
    "content": "                           -\n                          *%:\n                        :%%%#\n                       =%%%%%-\n                      *%%%%%%#\n                    :#%%%%%%%#.\n                   -%%%%%%%%+\n                  *%%%%%%%%-    :\n                .#%%%%%%%#.    *%=\n               -%%%%%%%%+    :#%%%*\n              +%%%%%%%%-    =%%%%%%#.\n            .#%%%%%%%#.    *%%%%%%%%:\n           -%%%%%%%%*    :#%%%%%%%#.\n          +%%%%%%%%-    =%%%%%%%%+    :%*.\n        .#%%%%%%%#:    *%%%%%%%%-    +%%%%*:\n       :%%%%%%%%*    :#%%%%%%%#.   .*%%%%%%%*\n      +%%%%%%%%=    -%%%%%%%%+    :%%%%%%%%*\n    .#%%%%%%%%:    *%%%%%%%%-    =%%%%%%%%="
  },
  {
    "path": "src/logo/ascii/mainsailos_small.txt",
    "content": "          -:\n         +%*\n       .#%%+\n      -%%%: +=\n     +%%#..#%%-\n   .#%%+ -%%%- +=\n  -%%%- +%%#..#%%+"
  },
  {
    "path": "src/logo/ascii/mandriva.txt",
    "content": "$2                        ``\n                       `-.\n$1      `               $2.---\n$1    -/               $2-::--`\n$1  `++    $2`----...```-:::::.\n$1 `os.      $2.::::::::::::::-```     `  `\n$1 +s+         $2.::::::::::::::::---...--`\n$1-ss:          $2`-::::::::::::::::-.``.``\n$1/ss-           $2.::::::::::::-.``   `\n$1+ss:          $2.::::::::::::-\n$1/sso         $2.::::::-::::::-\n$1.sss/       $2-:::-.`   .:::::\n$1 /sss+.    $2..`$1  `--`    $2.:::\n$1  -ossso+/:://+/-`        $2.:`\n$1    -/+ooo+/-.              $2`"
  },
  {
    "path": "src/logo/ascii/manjaro.txt",
    "content": "██████████████████  ████████\n██████████████████  ████████\n██████████████████  ████████\n██████████████████  ████████\n████████            ████████\n████████  ████████  ████████\n████████  ████████  ████████\n████████  ████████  ████████\n████████  ████████  ████████\n████████  ████████  ████████\n████████  ████████  ████████\n████████  ████████  ████████\n████████  ████████  ████████\n████████  ████████  ████████"
  },
  {
    "path": "src/logo/ascii/manjaro_small.txt",
    "content": "||||||||| ||||\n||||||||| ||||\n||||      ||||\n|||| |||| ||||\n|||| |||| ||||\n|||| |||| ||||\n|||| |||| ||||"
  },
  {
    "path": "src/logo/ascii/massos.txt",
    "content": "-+++/+++osyyhdmNNMMMMNdy/\n/MMMMMMMMMMMMMMMMMMMMMMMMm.\n/MMMMMMMMMMMMMMMMMMMMMMMMMm\n/MMMMMMMMMMMMMMMMMMMMMMMMMM:\n:ddddddddhddddddddhdddddddd/\n/NNNNNNNm:NNNNNNNN-NNNNNNNNo\n/MMMMMMMN:MMMMMMMM-MMMMMMMMs\n/MMMMMMMN:MMMMMMMM-MMMMMMMMs\n/MMMMMMMN:MMMMMMMM-MMMMMMMMs\n/MMMMMMMN:MMMMMMMM-MMMMMMMMs\n/MMMMMMMN:MMMMMMMM-MMMMMMMMs\n/MMMMMMMN:MMMMMMMM-MMMMMMMMs\n/MMMMMMMN:MMMMMMMM-MMMMMMMMs\n/MMMMMMMN:MMMMMMMM-MMMMMMMMs\n:MMMMMMMN:MMMMMMMM-MMMMMMMMs\n dMMMMMMN:MMMMMMMM-MMMMMMMMs\n `yNMMMMN:MMMMMMMM-MMMMMMMMs\n   `:+oss.ssssssss.ssssssss/"
  },
  {
    "path": "src/logo/ascii/matuusos.txt",
    "content": "░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░\n░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░\n░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░\n░░░░░░░░░░░░░░░░▒▓▓████▓▒▒░░░░░░░░░░░░░░░░\n░░░░░░░░░░░░░▒▓████████████▓░░░░░░░░░░░░░░\n░░░░░░░░░░░░▓████████████████▒░░░░░░░░░░░░\n░░░░░░░░░░░▓██████████████████▒░░░░░░░░░░░\n░░░░░░░░░░▓████▒▓███████▓▓█████░░░░░░░░░░░\n░░░░░░░░░░██████▓░▓████▒░██████▓░░░░░░░░░░\n░░░░░░░░░▒███████▓░▒▓▒░░████████░░░░░░░░░░\n░░░░░░░░░▒█████████▒░░░█████████░░░░░░░░░░\n░░░░░░░░░░██████████▓▒██████████░░░░░░░░░░\n░░░░░░░░░░▓████████████████████▒░░░░░░░░░░\n░░░░░░░░░░░███████████████████▓░░░░░░░░░░░\n░░░░░░░░░░░░█████████████████▓░░░░░░░░░░░░\n░░░░░░░░░░░░░▓██████████████▒░░░░░░░░░░░░░\n░░░░░░░░░░░░░░░▒▓████████▓░░░░░░░░░░░░░░░░\n░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░\n░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░\n░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░"
  },
  {
    "path": "src/logo/ascii/maui.txt",
    "content": "             `.-://////:--`\n         .:/oooooooooooooooo+:.\n      `:+ooooooooooooooooooooooo:`\n    `:oooooooooooooooooooooooooooo/`\n    ..```-oooooo/-`` `:oooooo+:.` `--\n  :.      +oo+-`       /ooo/`       -/\n -o.     `o+-          +o/`         -o:\n`oo`     ::`  :o/     `+.  .+o`     /oo.\n/o+      .  -+oo-     `   /oo/     `ooo/\n+o-        /ooo+`       .+ooo.     :ooo+\n++       .+oooo:       -oooo+     `oooo+\n:.      .oooooo`      :ooooo-     :oooo:\n`      .oooooo:      :ooooo+     `ooo+-`\n      .+oooooo`     -oooooo:     `o/-\n      +oooooo:     .ooooooo.\n     /ooooooo`     /ooooooo/       ..\n    `:oooooooo/:::/ooooooooo+:--:/:`\n      `:+oooooooooooooooooooooo+:`\n         .:+oooooooooooooooo+:.\n             `.-://////:-.`"
  },
  {
    "path": "src/logo/ascii/mauna.txt",
    "content": "        ..  :-=++++=-:\n    .-+*+ -********* **=\n   =***= +******+ =---=+*.\n  +**** +****+ :-=++*++=--\n =****= **** :+***********+:\n +****+ ***      $2.-- $1+******-\n =*****: *-       $2:+=: $1=*****:\n$2. $1+*****: :        $2-+++ $1:****=\n$2-+ $1:*****+        $2- ++++ $1:***-\n$2-++=: $1=+****:   $2:++ +++++ $1**+\n$2 +++++= $1-====-$2++++: +++++ $1*+\n$2 .=++++++++++++++: $2+++++= $1.\n$2   .=+++++++++= :=+++++=\n      .:::--- -+++++++-\n           :-==++==-."
  },
  {
    "path": "src/logo/ascii/meowix.txt",
    "content": "$1         #$2%            $3&$4*\n$1        ##$2%%          $3&&$4**\n$1       ##  $2%%        $3&&  $4**\n$1      ##    $2%%      $3&&    $4**\n$1     ##      $2%%    $3&&      $4**\n$1    ##        $2%%  $3&&        $4**\n$1   ##          $2%%$3&&          $4**\n$1  ##            $2%%            $4**\n$1 ##                            $4**\n$1##                              $4**"
  },
  {
    "path": "src/logo/ascii/mer.txt",
    "content": "                         dMs\n                         .-`\n                       `y`-o+`\n                        ``NMMy\n                      .--`:++.\n                    .hNNNNs\n                    /MMMMMN\n                    `ommmd/ +/\n                      ````  +/\n                     `:+sssso/-`\n  .-::. `-::-`     `smNMNmdmNMNd/      .://-`\n.ymNMNNdmNMMNm+`  -dMMh:.....+dMMs   `sNNMMNo\ndMN+::NMMy::hMM+  mMMo `ohhy/ `dMM+  yMMy::-\nMMm   yMM-  :MMs  NMN` `:::::--sMMh  dMM`\nMMm   yMM-  -MMs  mMM+ `ymmdsymMMMs  dMM`\nNNd   sNN-  -NNs  -mMNs-.--..:dMMh`  dNN\n---   .--`  `--.   .smMMmdddmMNdo`   .--\n                     ./ohddds+:`\n                     +h- `.:-.\n                     ./`.dMMMN+\n                        +MMMMMd\n                        `+dmmy-\n                      ``` .+`\n                     .dMNo-y.\n                     `hmm/\n                         .:`\n                         dMs"
  },
  {
    "path": "src/logo/ascii/midnightbsd.txt",
    "content": "         ..:::'''':::..\n      .:'''`        `''':.\n    .:'` .::`           `'::\n   :'  .::'                ':.\n .:` .:::'                  `:.\n :`  ::::                    `:\n::  :::::                     ::\n:   :::::              ::      :\n:   ::::::           .:::'     :\n::   ::::::.   :   .::::::    ::\n :.  :::::::.  : :::::::'    .:\n `:. `':::::::.'.:::::':    .:'\n  `:.  '':::::::::::::.:   .:`\n    ':.  `''::::::::'''  .:'\n      '':..   ``'``  ..:''\n         ''''::::::''''"
  },
  {
    "path": "src/logo/ascii/midos.txt",
    "content": "             ▁▂▃▄▅▆▇▇████▇▇▆▅▄▃▂▁\n          ▃▅▇████████████████████▇▅▃\n       ▂▅████████████████████████████▅▁\n     ▗▟██████████████▛▀▀▜██████████████▙▖\n   ▗▟████████████████▌  ▐████████████████▙▖\n  ▗██████████████████▌  ▐██████████████████▖\n ▗███████████████████▌  ▐███████████████████▖\n▕███████████████▀▘ ██▌  ▐██ ▝▀███████████████▏\n▐██████████▛▀▀    ▂██▌  ▐██▂    ▀▀▜██████████▌\n███████▀▀     ▁▄▆████▌  ▐████▆▄▁     ▀▀███████\n█████    ▂▄▂  ▔▀▜████▌  ▐████▛▀▔  ▂▄▂    █████\n▐████▄▄▆█████▄▂  ▔▀██▌  ▐██▀▔  ▂▄█████▆▄▄████▌\n▕██████████████▇▄▂ ██▌  ▐██ ▂▄▇██████████████▏\n ▝███████████████████▌  ▐███████████████████▘\n  ▝██████████████████▌  ▐██████████████████▘\n   ▝▜████████████████▌  ▐████████████████▛▘\n     ▝▜██████████████▙▄▄▟██████████████▛▘\n       ▔▀▜██████████████████████████▛▀▔\n          ▔▀▜████████████████████▛▀▔\n              ▔▔▀▀▀▜██████▛▀▀▀▔▔"
  },
  {
    "path": "src/logo/ascii/midos_old.txt",
    "content": "              .:=+*#%%@@@@@@%%#*+=:.\n          .=*%@@@@@@@@@@@@@@@@@@@@@@%*=:\n       .=%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%+.\n     :*@@@@@@@@@@@@@@@@@$2++$1%@@@@@@@@@@@@@@@@#-\n   .*@@@@@@@@@@@@@@@@@@%$2..$1#@@@@@@@@@@@@@@@@@@#.\n  -%@@@@@@@@@@@@@@@@@@@%$2..$1#@@@@@@@@@@@@@@@@@@@@-\n -@@@@@@@@@@@@@@@@@@@@@%$2..$1#@@@@@@@@@@@@@@@@@@@@@=\n:@@@@@@@@@@@@@@@@@%%@@@%$2..$1#@@@@#@@@@@@@@@@@@@@@@@:\n*@@@@@@@@@@@@#$2*=:..$1*@@@%$2..$1#@@@#$2..:=+$1#@@@@@@@@@@@@*\n%@@@@@@@#+$2=:...-+$1#@@@@@%$2..$1#@@@@@#$2+=:..:-$1+#@@@@@@@%\n%@@@@@$2:..:=+-..-+$1#@@@@@%$2..$1#@@@@@%$2+-..-+=:..:$1@@@@@@\n*@@@@@$2*%$1@@@@@@%$2+-..$1#@@@%$2..$1#@@@%$2..-+$1#@@@@@@$2%*$1@@@@@*\n:@@@@@@@@@@@@@@@@@$2#%$1@@@%$2..$1#@@@$2%#$1@@@@@@@@@@@@@@@@@:\n =@@@@@@@@@@@@@@@@@@@@@%$2..$1#@@@@@@@@@@@@@@@@@@@@@=\n  -@@@@@@@@@@@@@@@@@@@@%$2..$1#@@@@@@@@@@@@@@@@@@@@=\n   .*@@@@@@@@@@@@@@@@@@%$2..$1#@@@@@@@@@@@@@@@@@@#:\n     :*@@@@@@@@@@@@@@@@@$2==$1%@@@@@@@@@@@@@@@@#-.\n       .+%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%+:\n          :=*%@@@@@@@@@@@@@@@@@@@@@@%*=:.\n              .-=+*#%%@@@@@@%%#*+=-."
  },
  {
    "path": "src/logo/ascii/minimal.txt",
    "content": "         #####\n       #####\n     #####\n   ######\n ######\n#####\n#####     #####\n ######     #####\n   ######     #####\n     ######    ######\n       #####     ######\n         #####     #####\n                   #####\n                 ######\n               ######\n             ######\n            #####\n          #####\n"
  },
  {
    "path": "src/logo/ascii/minix.txt",
    "content": "$2   -sdhyo+:-`                -/syymm:\n   sdyooymmNNy.     ``    .smNmmdysNd\n   odyoso+syNNmysoyhhdhsoomNmm+/osdm/\n    :hhy+-/syNNmddhddddddmNMNo:sdNd:\n     `smNNdNmmNmddddddddddmmmmmmmy`\n   `ohhhhdddddmmNNdmddNmNNmdddddmdh-\n   odNNNmdyo/:/-/hNddNy-`..-+ydNNNmd:\n `+mNho:`   smmd/ sNNh :dmms`   -+ymmo.\n-od/       -m$1mm$2mo -NN+ +m$1mm$2m-       yms:\n+sms -.`    :so:  .NN+  :os/     .-`mNh:\n.-hyh+:////-     -sNNd:`    .--://ohNs-\n `:hNNNNNNNMMd/sNMmhsdMMh/ymmNNNmmNNy/\n  -+sNNNNMMNNNsmNMo: :NNmymNNNNMMMms:\n    //oydNMMMMydMMNysNMMmsMMMMMNyo/`\n       ../-yNMMy--/::/-.sMMmos+.`\n           -+oyhNsooo+omy/```\n              `::ohdmds-`"
  },
  {
    "path": "src/logo/ascii/miracle_linux.txt",
    "content": "            ,A\n          .###\n     .#' .####   .#.\n   r##:  :####   ####.\n  +###;  :####  ######C\n  :####:  #### ,######\".#.\n.# :####. :### #####'.#####.\n##: `####. ### ###'.########+.\n#### `####::## ##'.#######+'\n ####+.`###i## #',####:'\n `+###MI`##### 'l###:'\n   `+####+`### ;#E'\n      `+###:## #'\n         `:### '\n           '##\n            ';"
  },
  {
    "path": "src/logo/ascii/mos.txt",
    "content": "  :--==========================--:\n.-=================================.\n-==================================-\n====================================\n=======-....:==========:....:=======\n=======:      -======-.     .=======\n=======:       :====-       .=======\n=======:        :==:        .=======\n=======:         ..         .=======\n=======:    .:        .:    .=======\n=======:    .=-      :=:    .=======\n=======:    .===.  .-==:    .=======\n=======:    .==========:    .=======\n=======:    :==========:    :=======\n====================================\n-===================================\n.==================================:\n  :--===========================-:."
  },
  {
    "path": "src/logo/ascii/msys2.txt",
    "content": "$2                 ...\n              5GB###GJ.             !YPGGGG\n              7@@@@@@@B.          :G@@@@@@@\n              7@@@@@@@@Y         ~&@@@@@@@@$3YJYY5YY?L\n             $2!@@@@@@@@@@^       ^&@@@@@@@$3#PP555555PBY\n            $2~&@@@@@@@@@@?      ^&@@@@@@$3#5YY5YYYYYYYY#7\n           $2^&@@@@@@@@@@@B     :#@@@@@@@$3G5BBYGPYYYYYY#J\n          $2^#@@@&J#@@@@@@@~   .B@@@@@@@@@@@P $3?#YYYYYPB.\n         $2:#@@@@7 G@@@@@@@J   P@@@#!&@@@@@@G$3.GGYYYYGB^\n        $2:#@@@@J  Y@@@@@@@B  5@@@&:.&@@@@@@&$3BBYYY5B5.\n       $2:#@@@@Y   !@@@@@@@@!Y@@@&~ .#@@@@@@$3GYYYYYBP  JP~\n      $2:#@@@@P    :&@@@@@@@@@@@&~   B@@@@@$3#5YYYYYPGPGPGG\n     $2^#@@@@G.     P@@@@@@@@@@@!    P@@@@$3GYYYYYYYYYYYYBY\n    $2^#@@@@B:      ^@@@@@@@@@@7     !@@@$3#GGGGGGGPPPP5GB:\n   $2!&@@@@B:        Y@@@@@@@@?       P@@@@@@@@@&?  $3^PY:\n  $27&@@@@5.          P@@@@@@?         P@@@@@@@@@B\n Y@@@&P!             5@@@@7           7G@@@@@&P~\n.JJ?~:                ^JY~              ^!5J!^:\n                             $1:@P5#B. #G  7&^ :@P5#B.\n                             !&P^.   ?@~ #P  !&P^.  \n                              .?BG!   #G5@~   .?BG! \n                               :.B@.  7@@5     :.B@.\n                             !PYY5Y   :&@^   !PYY5Y \n                                      ~@Y\n                                      !5:"
  },
  {
    "path": "src/logo/ascii/mx.txt",
    "content": "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNMMMMMMMMM\nMMMMMMMMMMNs..yMMMMMMMMMMMMMm: +NMMMMMMM\nMMMMMMMMMN+    :mMMMMMMMMMNo` -dMMMMMMMM\nMMMMMMMMMMMs.   `oNMMMMMMh- `sNMMMMMMMMM\nMMMMMMMMMMMMN/    -hMMMN+  :dMMMMMMMMMMM\nMMMMMMMMMMMMMMh-    +ms. .sMMMMMMMMMMMMM\nMMMMMMMMMMMMMMMN+`   `  +NMMMMMMMMMMMMMM\nMMMMMMMMMMMMMMNMMd:    .dMMMMMMMMMMMMMMM\nMMMMMMMMMMMMm/-hMd-     `sNMMMMMMMMMMMMM\nMMMMMMMMMMNo`   -` :h/    -dMMMMMMMMMMMM\nMMMMMMMMMd:       /NMMh-   `+NMMMMMMMMMM\nMMMMMMMNo`         :mMMN+`   `-hMMMMMMMM\nMMMMMMh.            `oNMMd:    `/mMMMMMM\nMMMMm/                -hMd-      `sNMMMM\nMMNs`                   -          :dMMM\nMm:                                 `oMM\nMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM"
  },
  {
    "path": "src/logo/ascii/mx2.txt",
    "content": "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n@@@@@@@@@@@@%*+--:------=+*%@@@@@@@@@@@@\n@@@@@@@@@#=. .-+#%@@@@@%#*+--=#@@@@@@@@@\n@@@@@@@+. .=%@@@@@@@@@@@@@@@@*-:+@@@@@@@\n@@@@@*.  *@@@@@@@@@@@@@@@@@@@@@%-.*@@@@@\n@@@@-  -@@@@@@@@@@@@@@@@@@@@@@@#:  -@@@@\n@@@:  -@@@@@@@=.*@@@@@@@@@@@@%-   = :@@@\n@@=  .@@@@@@@@%- :%@@@@@@@@@+   -%@# =@@\n@%   +@@@@@@@@@@#. =@@@@@@*.  .*@@@@. %@\n@+   *@@@@@@*..*@@+  *@@%-   =@@@@@@- +@\n@=   *@@@@%-    -%@@- :=   -%@@@@@@@: +@\n@+   :@@@=        +@@=   .#@@@@@@@@%  *@\n@%    +*.          .:     *@@#: +@@:  @@\n@@+                   :%@- :-    ::  +@@\n@@@-                  .=@@=         -@@@\n@@+.                     .           +@@\n%=..:.................::...........:..-%\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
  },
  {
    "path": "src/logo/ascii/mx_small.txt",
    "content": "    \\\\  /\n     \\\\/\n      \\\\\n   /\\/ \\\\\n  /  \\  /\\\n /    \\/  \\\n/__________\\"
  },
  {
    "path": "src/logo/ascii/namib.txt",
    "content": "          .:+shysyhhhhysyhs+:.\n       -/yyys              syyy/-\n     -shy                      yhs-\n   -yhs                          shy-\n  +hy                              yh+\n +ds                                sd+\n/ys                  so              sy/\nsh                 smMMNdyo           hs\nyo               ymMMMMNNMMNho        oy\nN             ydMMMNNMMMMMMMMMmy       N\nN         shmMMMMNNMMMMMMMMMMMMMNy     N\nyo  ooshmNMMMNNNNMMMMMMMMMMMMMMMMMms  oy\nsd yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy ds\n/ys                                  sy/\n +ds                                sd+\n  +hy                              yh+\n   -yhs                          shy-\n     -shy                      yhs-\n       -/yyys              syyy/-\n          .:+shysyhyhhysyhs+:."
  },
  {
    "path": "src/logo/ascii/nekos.txt",
    "content": "                        @@@@@\n                     @@@@@@@@@.\n                  @@@@@@@@    @@@\n               @@@@@@@@@@@     @@.\n              @@@@@@@@@@@@@      .\n             @@@@@@@@@@@@@@@@@   ,\n           @@@@@@@@@@@@@@@@@@@\n          @@@@@$2///$1@@@@@@@$2///$1@@@\n          @@@@$2/***$1@@@@@@@$2**//$1@@@@\n       @@@@@@@@@@@@@@@@@@@@@@@@@@@@\n          @@@@@@@@@@@@@@@@@@@@@@@\n         @@@/   /@@@@@@@@@/   /@@@\n      @@@@@@     @@@$3██$1@@@@     @@@@@@\n      @@@@@@/   /@$2██$3██$2██$1@@/   /@@@@@@\n       @@@@@@@@@@@@@@@@@@@@@@@@@@@@\n                 ##########%%%%\n                 ##########%%  %%\n         @     @@@#######@@%%%\n      @@@      @@@@####@@@@   %\n   @@@        @@@@@@@#@@@@@@@\n   @@@        @@@@@@@@@@@@@@@\n   @@@@      @@@@@@@@@@@@@@@@@\n      @@@@@@@@@@@@@@@@@@@@@@@@"
  },
  {
    "path": "src/logo/ascii/neptune.txt",
    "content": "            ./+sydddddddys/-.\n        .+ymNNdyooo/:+oooymNNmy/`\n     `/hNNh/.`             `-+dNNy:`\n    /mMd/.          .++.:oy/   .+mMd-\n  `sMN/             oMMmdy+.     `oNNo\n `hMd.           `/ymy/.           :NMo\n oMN-          `/dMd:               /MM-\n`mMy          -dMN+`                 mMs\n.MMo         -NMM/                   yMs\n dMh         mMMMo:`                `NMo\n /MM/        /ymMMMm-               sMN.\n  +Mm:         .hMMd`              oMN/\n   +mNs.      `yNd/`             -dMm-\n    .yMNs:    `/.`            `/yNNo`\n      .odNNy+-`           .:ohNNd/.\n         -+ymNNmdyyyyyyydmNNmy+.\n             `-//sssssss//."
  },
  {
    "path": "src/logo/ascii/netbsd.txt",
    "content": "$1                     `-/oshdmNMNdhyo+:-`\n$2y$1/s+:-``    `.-:+oydNMMMMNhs/-``\n$2-m+$1NMMMMMMMMMMMMMMMMMMMNdhmNMMMmdhs+/-`\n $2-m+$1NMMMMMMMMMMMMMMMMMMMMmy+:`\n  $2-N/$1dMMMMMMMMMMMMMMMds:`\n   $2-N/$1hMMMMMMMMMmho:`\n    $2-N/$1-:/++/:.`\n$2     :M+\n      :Mo\n       :Ms\n        :Ms\n         :Ms\n          :Ms\n           :Ms\n            :Ms\n             :Ms\n              :Ms"
  },
  {
    "path": "src/logo/ascii/netbsd2.txt",
    "content": "                                  __,gnnnOCCCCCOObaau,_\n$2   _._                    $1__,gnnCCCCCCCCOPF\"''\n$2  (N\\\\$1XCbngg,._____.,gnnndCCCCCCCCCCCCF\"___,,,,___\n$2   \\N\\\\$1XCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCOOOOPYvv.\n$2    \\N\\\\$1XCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCPF\"''\n$2     \\N\\\\$1XCCCCCCCCCCCCCCCCCCCCCCCCCOF\"'\n$2      \\N\\\\$1XCCCCCCCCCCCCCCCCCCCCOF\"'\n$2       \\N\\\\$1XCCCCCCCCCCCCCCCPF\"'\n$2        \\N\\\\$1\\\"PCOCCCOCCFP\"\"\n$2         \\N\\\\\n          \\N\\\\\n           \\N\\\\\n            \\NN\\\n             \\NN\\\n              \\NNA.\n               \\NNA,\n                \\NNN,\n                 \\NNN\\\n                  \\NNN\\\n                   \\NNNA"
  },
  {
    "path": "src/logo/ascii/netbsd_small.txt",
    "content": "$2 \\\\$1`-______,----__\n$2  \\\\       $1 __,---`_\n$2   \\\\      $1 `.____\n$2    \\\\$1-______,----`-\n$2     \\\\\n$2      \\\\\n$2       \\\\\n$2        \\\\"
  },
  {
    "path": "src/logo/ascii/nethydra.txt",
    "content": "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⣶⣶⣦⣤⣄⡀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣦⣤⣌⣙⠻⢶⣄⠀\n⠀⠀⠀⠀⠀⠀⠀⢀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡴⠊⣤⣶⣶⣶⣦⣤⣼⡧⢤⡈⠙⠳⣦⡙⢷⣄⠀\n⠀⠀⣠⣴⣶⣿⣿⣿⣿⣿⣿⣶⣦⣀⠀⠀⠀⠀⠀⠀⠀⢀⣴⡿⢾⡷⣢⢄⣠⣬⣭⣝⣿⣿⣄⠈⠓⢄⠈⠳⡄⠙⢷\n⢀⣾⣿⡿⠛⠉⠀⠀⠀⠀⠉⠛⠿⣿⣷⣦⡀⠀⠀⠀⡖⠉⣠⠶⢛⣼⣡⡍⢉⠍⠉⠙⠻⣿⣿⣧⠀⠀⠁⠀⠀⠀⠀⠳⡀\n⢸⣿⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⢿⣿⣦⣄⠀⠀⠈⠁⡠⠟⣫⣵⡇⠀⠀⠀⠀⠀⢈⣿⣿\n⢸⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣿⣷⣄⠀⠚⠀⠞⠋⠀⠀⠀⠀⠀⠀⢀⣼⣿⡟\n⠸⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠙⢿⣿⣷⣤⡀⠀⠀⠀⠀⠀⠀⢀⣠⣿⣿⠏\n⠀⠹⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⣀⣴⡞⠁⠀⠀⠀⠀⠈⠛⠿⣿⣿⣶⣶⣶⣶⣾⡿⠟⠋\n⠀⠀⠙⠿⣿⣷⣶⣦⣴⣶⣾⣿⠿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠉⠉⠁\n⠀⠀⠀⠀⠈⠉⠛⠛⠛⠛⠉\n"
  },
  {
    "path": "src/logo/ascii/netrunner.txt",
    "content": "           .:oydmMMMMMMmdyo:`\n        -smMMMMMMMMMMMMMMMMMMds-\n      +mMMMMMMMMMMMMMMMMMMMMMMMMd+\n    /mMMMMMMMMMMMMMMMMMMMMMMMMMMMMm/\n  `hMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMy`\n .mMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMd`\n dMMMMMMMMMMMMMMMMMMMMMMNdhmMMMMMMMMMMh\n+MMMMMMMMMMMMMNmhyo+/-.   -MMMMMMMMMMMM/\nmMMMMMMMMd+:.`           `mMMMMMMMMMMMMd\nMMMMMMMMMMMdy/.          yMMMMMMMMMMMMMM\nMMMMMMMMMMMMMMMNh+`     +MMMMMMMMMMMMMMM\nmMMMMMMMMMMMMMMMMMs    -NMMMMMMMMMMMMMMd\n+MMMMMMMMMMMMMMMMMN.  `mMMMMMMMMMMMMMMM/\n dMMMMMMMMMMMMMMMMMy  hMMMMMMMMMMMMMMMh\n `dMMMMMMMMMMMMMMMMM-+MMMMMMMMMMMMMMMd`\n  `hMMMMMMMMMMMMMMMMmMMMMMMMMMMMMMMMy\n    /mMMMMMMMMMMMMMMMMMMMMMMMMMMMMm:\n      +dMMMMMMMMMMMMMMMMMMMMMMMMd/\n        -odMMMMMMMMMMMMMMMMMMdo-\n           `:+ydmNMMMMNmhy+-`"
  },
  {
    "path": "src/logo/ascii/nexalinux.txt",
    "content": "                                      \n                 ****                 \n              **********              \n          ******************          \n       ************************       \n    ******************************    \n ***************      *************** \n ************    ****    #*********** \n *********    **********    ********* \n ******   #****************#   #***** \n  *    ************************    *# \n    ******************************    \n #**************      **************# \n ************    ****    ************ \n *********    **********    ********* \n   ***     ****************     ***   \n        **********************        \n         ********************         \n            *****    *****            "
  },
  {
    "path": "src/logo/ascii/nitrux.txt",
    "content": "`:/.\n`/yo\n`/yo\n`/yo      .+:.\n`/yo      .sys+:.`\n`/yo       `-/sys+:.`\n`/yo           ./sss+:.`\n`/yo              .:oss+:-`\n`/yo                 ./o///:-`\n`/yo              `.-:///////:`\n`/yo           `.://///++//-``\n`/yo       `.-:////++++/-`\n`/yo    `-://///++o+/-`\n`/yo `-/+o+++ooo+/-`\n`/s+:+oooossso/.`\n`//+sssssso:.\n`+syyyy+:`\n:+s+-"
  },
  {
    "path": "src/logo/ascii/nixos.txt",
    "content": "          $1▗▄▄▄       $2▗▄▄▄▄    ▄▄▄▖\n          $1▜███▙       $2▜███▙  ▟███▛\n           $1▜███▙       $2▜███▙▟███▛\n            $1▜███▙       $2▜██████▛\n     $1▟█████████████████▙ $2▜████▛     $3▟▙\n    $1▟███████████████████▙ $2▜███▙    $3▟██▙\n           $6▄▄▄▄▖           $2▜███▙  $3▟███▛\n          $6▟███▛             $2▜██▛ $3▟███▛\n         $6▟███▛               $2▜▛ $3▟███▛\n$6▟███████████▛                  $3▟██████████▙\n$6▜██████████▛                  $3▟███████████▛\n      $6▟███▛ $5▟▙               $3▟███▛\n     $6▟███▛ $5▟██▙             $3▟███▛\n    $6▟███▛  $5▜███▙           $3▝▀▀▀▀\n    $6▜██▛    $5▜███▙ $4▜██████████████████▛\n     $6▜▛     $5▟████▙ $4▜████████████████▛\n           $5▟██████▙         $4▜███▙\n          $5▟███▛▜███▙         $4▜███▙\n         $5▟███▛  ▜███▙         $4▜███▙\n         $5▝▀▀▀    ▀▀▀▀▘         $4▀▀▀▘\n"
  },
  {
    "path": "src/logo/ascii/nixos_old.txt",
    "content": "$1              ____       $2_______        ____\n$1             /####\\      $2\\######\\      /####\\\n$1             ######\\      $2\\######\\    /#####/\n$1             \\######\\      $2\\######\\  /#####/\n$1              \\######\\      $2\\######\\/#####/    $1/\\\n$1               \\######\\      $2\\###########/    $1/##\\\n$1        ________\\######\\______$2\\#########/    $1/####\\\n$1       /#######################$2\\#######/    $1/######\n$1      /#########################$2\\######\\   $1/######/\n$1     /###########################$2\\######\\ $1/######/\n$1     ¯¯¯¯¯¯¯¯¯¯¯¯$2/######/$1¯¯¯¯¯¯¯¯¯$2\\######$1/######/\n$2                /######/           $2\\####$1/######/________\n$2  _____________/######/             $2\\##$1/################\\\n$2 /###################/               $2\\$1/##################\\\n$2 \\##################/$1\\               /###################/\n$2  \\################/$1##\\             /######/¯¯¯¯¯¯¯¯¯¯¯¯¯\n$2   ¯¯¯¯¯¯¯¯/######/$1####\\           /######/\n$2          /######/$1######\\$2_________$1/######/$2____________\n$2         /######/ $1\\######\\$2###########################/\n$2        /######/   $1\\######\\$2#########################/\n$2        ######/    $1/#######\\$2#######################/\n$2        \\####/    $1/#########\\$2¯¯¯¯¯¯\\######\\¯¯¯¯¯¯¯¯\n$2         \\##/    $1/###########\\$2      \\######\\\n$2          \\/    $1/#####/\\######\\$2      \\######\\\n$1               $1/#####/  \\######\\$2      \\######\\\n$1              $1/#####/    \\######\\$2      \\######\n$1              $1\\####/      \\######\\$2      \\####/\n$1               $1¯¯¯¯        ¯¯¯¯¯¯¯$2       ¯¯¯¯"
  },
  {
    "path": "src/logo/ascii/nixos_old_small.txt",
    "content": "$1  \\\\  $2\\\\ //\n$1 ==\\\\__$2\\\\/ $1//\n$2   //   $2\\\\$1//\n$2==//     $1//==\n$2 //$1\\\\$2___$1//\n$2// $1/\\\\  $2\\\\==\n  $1// \\\\  $2\\\\"
  },
  {
    "path": "src/logo/ascii/nixos_small.txt",
    "content": "$1  ▗▄   $2▗▄ ▄▖\n$1 ▄▄🬸█▄▄▄$2🬸█▛ $3▃\n$6   ▟▛    ▜$3▃▟🬕\n$6🬋🬋🬫█      $3█🬛🬋🬋\n$6 🬷▛🮃$5▙    $4▟▛\n$6 🮃 $5▟█🬴$4▀▀▀█🬴▀▀\n  $5▝▀ ▀▘   $4▀▘"
  },
  {
    "path": "src/logo/ascii/nobara.txt",
    "content": "⢀⣤⣴⣶⣶⣶⣦⣤⡀⠀⣀⣠⣤⣴⣶⣶⣶⣶⣶⣶⣶⣶⣤⣤⣀⡀\n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⡀\n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄\n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄\n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧\n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠋⠉⠁⠀⠀⠉⠉⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧\n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀⢀⣀⣀⡀⠀⠀⠀⠈⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇\n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠀⠀⠀⢠⣾⣿⣿⣿⣿⣷⡄⠀⠀⠀⠻⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿\n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠁⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⣀⣀⣬⣽⣿⣿⣿⣿⣿⣿\n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠈⠻⢿⣿⣿⡿⠟⠁⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿\n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿\n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣤⣤⣄⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿\n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿\n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿\n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠛⠉⠉⠛⠛⢿⣿⣿⠀⠀⠀⠀⠀⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿\n⠘⢿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠈⢿⠀⠀⠀⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⣿⣿⠟⠁\n  ⠈⠙⠛⠛⠛⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠛⠛⠛⠉⠁"
  },
  {
    "path": "src/logo/ascii/nomadbsd.txt",
    "content": "         _======__\n     (===============\\\n   (===================\\\n   _              _---__\n  (=               ====-\n (=                ======\n (==                ======\n (==                ======\n (==\\ \\=-_      _=/ /====/\n  (==\\ \\========/ /====/  /====-_\n   (==\\ \\=====/ /==/   /===--\n/================/  /===-\n\\===========/"
  },
  {
    "path": "src/logo/ascii/nuros.txt",
    "content": "         ___╓╓___\n     _▄▄▓▓▀▀╜╜╨▀▓▓▓╗_\n   ╓▓▓▀²          `╙▓▓╖\n  ╣▓▀   _▄▓▓▓▓▓▓W_   ╙▓▓\n ╣▓╜  ,▓▓▓▓▓▓▓▓▓▓▓▓_  ²▓▓\n╒▓▌   ▓▓▓▓▓▓▓▓▓▓▓▓▓▓   ║▓m\n╞▓▓  í▓▓▓▓▓▓▓▓▓▓▓▓▓▓h  ╞▓╡\n²▓▓   ▓▓▓▓▓▓▓▓▓▓▓▓▓▓   ║▓h\n ║▓▄   ╙▓▓▓▓▓▓▓▓▓▓╜   ƒ▓▓\n  ╙▓▓_   ⁿ╙╨╝╝╝╜²   _╢▓╜\n    ╙▓▓╗__       _╗▓▓╜\n      `╙╝▓▓▓▓▓▓▓▓╝╙"
  },
  {
    "path": "src/logo/ascii/nurunner.txt",
    "content": "                  ,xc\n                ;00cxXl\n              ;K0,   .xNo.\n            :KO'       .lXx.\n          cXk.    ;xl     cXk.\n        cXk.    ;k:.,xo.    cXk.\n     .lXx.    :x::0MNl,dd.    :KO,\n   .xNx.    cx;:KMMMMMNo'dx.    ;KK;\n .dNl.    cd,cXMMMMMMMMMWd,ox'    'OK:\n;WK.    'K,.KMMMMMMMMMMMMMWc.Kx     lMO\n 'OK:    'dl'xWMMMMMMMMMM0::x:    'OK:\n   .kNo    .xo'xWMMMMMM0;:O:    ;KK;\n     .dXd.   .do,oNMMO;ck:    ;00,\n        oNd.   .dx,;'cO;    ;K0,\n          oNx.    okk;    ;K0,\n            lXx.        :KO'\n              cKk'    cXk.\n                ;00:lXx.\n                  ,kd."
  },
  {
    "path": "src/logo/ascii/nutyx.txt",
    "content": "                                      .\n                                    .\n                                 ...\n                               ...\n            ....     .........--.\n       ..-++-----....--++++++---.\n    .-++++++-.   .-++++++++++++-----..\n  .--...  .++..-+++--.....-++++++++++--..\n .     .-+-. .**-            ....  ..-+----..\n     .+++.  .*+.         +            -++-----.\n   .+++++-  ++.         .*+.     .....-+++-----.\n  -+++-++. .+.          .-+***++***++--++++.  .\n -+-. --   -.          -*- ......        ..--.\n.-. .+-    .          -+.\n.  .+-                +.\n   --                 --\n  -+----.              .-\n  -++-.+.                .\n .++. --\n  +.  ----.\n  .  .+. ..\n      -  .\n      ."
  },
  {
    "path": "src/logo/ascii/obarun.txt",
    "content": "                 ,;::::;\n             ;cooolc;,\n          ,coool;\n        ,loool,\n       loooo;\n     :ooool\n    cooooc            ,:ccc;\n   looooc           :oooooool\n  cooooo          ;oooooooooo,\n :ooooo;         :ooooooooooo\n oooooo          oooooooooooc\n:oooooo         :ooooooooool\nloooooo         ;oooooooool\nlooooooc        .coooooooc\ncooooooo:           ,;co;\n,ooooooool;       ,:loc\n cooooooooooooloooooc\n  ;ooooooooooooool;\n    ;looooooolc;"
  },
  {
    "path": "src/logo/ascii/obrevenge.txt",
    "content": "        __   __\n     _@@@@   @@@g_\n   _@@@@@@   @@@@@@\n  _@@@@@@M   W@@@@@@_\n j@@@@P        ^W@@@@\n @@@@L____  _____Q@@@@\nQ@@@@@@@@@@j@@@@@@@@@@\n@@@@@    T@j@    T@@@@@\n@@@@@ ___Q@J@    _@@@@@\n@@@@@fMMM@@j@jggg@@@@@@\n@@@@@    j@j@^MW@P @@@@\nQ@@@@@ggg@@f@   @@@@@@L\n^@@@@WWMMP  ^    Q@@@@\n @@@@@_         _@@@@l\n  W@@@@@g_____g@@@@@P\n   @@@@@@@@@@@@@@@@l\n    ^W@@@@@@@@@@@P\n       ^TMMMMTll"
  },
  {
    "path": "src/logo/ascii/obsidianos.txt",
    "content": "$2  *+++++++#\n$2#*******###$3  @\n$2#*******###$3    @\n$2#*******###$3 @    @\n$2#*******##%$3   @\n$3@@@@@@@@@    @ @    @\n  @       @    @ @    @\n    @       @    @ @    @\n      @      @       @    @\n        @      @    @  @    @\n          @      @    @@@@@@@@@\n                   @$1#*******##%$3\n             @$1      ##########%$3\n               @$1    ##########%$3\n                 @$1  ##########%\n                    ########%\n"
  },
  {
    "path": "src/logo/ascii/omnios.txt",
    "content": "  ____   __  __  _   _  _\n / __ \\ |  \\/  || \\ | || |\n| |  | ||      ||  \\| || |\n| |__| || |\\/| || , `$2_$1||$2_$1|  $2____$1\n \\____/ |_|  |_||_|\\$2/ __ \\ / ___|\n                   | |  | ||(__\n       $3community$2   | |__| | ___)|\n            $3edition$2 \\____/ |____/"
  },
  {
    "path": "src/logo/ascii/openbsd.txt",
    "content": "$3                                     _\n                                    (_)\n$1              |    .\n$1          .   |L  /|   .         $3 _\n$1      _ . |\\ _| \\--+._/| .       $3(_)\n$1     / ||\\| Y J  )   / |/| ./\n    J  |)'( |        ` F`.'/       $3 _\n$1  -<|  F         __     .-<        $3(_)\n$1    | /       .-'$3. $1`.  /$3-. $1L___\n    J \\      <    $3\\ $1 | | $5O$3\\$1|.-' $3 _\n$1  _J \\  .-    \\$3/ $5O $3| $1| \\  |$1F    $3(_)\n$1 '-F  -<_.     \\   .-'  `-' L__\n__J  _   _.     >-'  $1)$4._.   $1|-'\n$1 `-|.'   /_.          $4\\_|  $1 F\n  /.-   .                _.<\n /'    /.'             .'  `\\\n  /L  /'   |/      _.-'-\\\n /'J       ___.---'\\|\n   |\\  .--' V  | `. `\n   |/`. `-.     `._)\n      / .-.\\\n      \\ (  `\\\n       `.\\"
  },
  {
    "path": "src/logo/ascii/openbsd_small.txt",
    "content": "      _____\n    \\-     -/\n \\_/         \\\n |        $2O O$1 |\n |_  <   )  3 )\n / \\         /\n    /-_____-\\"
  },
  {
    "path": "src/logo/ascii/openeuler.txt",
    "content": "                 `.cc.`\n             ``.cccccccc..`\n          `.cccccccccccccccc.`\n      ``.cccccccccccccccccccccc.``\n   `..cccccccccccccccccccccccccccc..`\n`.ccccccccccccccc$2/++/$1ccccccccccccccccc.`\n.ccccccccccccccc$2mNMMNdo+oso+$1ccccccccccc.\n.cccccccccc$2/++odms+//+mMMMMm/:+syso/$1cccc\n.ccccccccc$2yNNMMMs:::/::+o+/:$1c$2dMMMMMm$1cccc\n.ccccccc$2:+NmdyyhNNmNNNd:$1ccccc$1$2:oyyyo:$1cccc\n.ccc$2:ohdmMs:$1cccc$2+mNMNmy$1ccccccccccccccccc\n.cc$2/NMMMMMo////:$1c$2:///:$1cccccccccccccccccc\n.cc$2:syysyNMNNNMNy$1ccccccccccccccccccccccc\n.cccccccc$2+MMMMMNy$1c$2:/+++/$1cccccccccccccccc\n.ccccccccc$2ohhhs/$1c$2omMMMMNh$1ccccccccccccccc\n.ccccccccccccccc$2:MMMMMMMM/$1cccccccccccccc\n.cccccccccccccccc$2sNNNNNd+$1cccccccccccccc.\n`..cccccccccccccccc$2/+/:$1cccccccccccccc..`\n   ``.cccccccccccccccccccccccccccc.``\n       `.cccccccccccccccccccccc.`\n          ``.cccccccccccccc.``\n              `.cccccccc.`\n                 `....`"
  },
  {
    "path": "src/logo/ascii/openindiana.txt",
    "content": "$2                         .sy/\n                         .yh+\n\n           $1-+syyyo+-     $2 /+.\n         $1+ddo/---/sdh/   $2 ym-\n       $1`hm+        `sms$2   ym-```````.-.\n       $1sm+           sm/ $2 ym-         +s\n       $1hm.           /mo $2 ym-         /h\n       $1omo           ym: $2 ym-       `os`\n        $1smo`       .ym+ $2  ym-     .os-\n     ``  $1:ymy+///oyms- $2   ym-  .+s+.\n   ..`     $1`:+oo+/-`  $2    -//oyo-\n -:`                   .:oys/.\n+-               `./oyys/.\nh+`      `.-:+oyyyo/-`\n`/ossssysso+/-.`"
  },
  {
    "path": "src/logo/ascii/openkylin.txt",
    "content": "\n             /KKK]\n            KKKKKKK`   ]KKKK\\\n           KKKKK/  /KKKKKKKKK\\\n          KKKK/ ,KKKKKKKKKKKK^\n  ,]KKK  =KKK` /KKKKKKOOOOOO`\n,KKKKKK  =KK  /`    [\\OOOOOOO\\\n \\KKKKK  =K            ,OOOOOOO`\n ,KKKKK  =^              \\OOOOOO\n  ,KKKK   ^               OOOOOO^\n   *KKK^                  =OOOOO^\n    OOKK^                 OOOOOO^\n    \\OOOK\\               /OOOOOO`\n     OOOOOO]           ,OOOOOOO^\n     ,OOOOOOOO\\]   ,[OOOOOOOOO/\n       \\OOOOOOOOOOOOOOOOOOOOO`\n         [OOOOOOOOOOOOOOOO/`\n            ,[OOOOOOOOO]"
  },
  {
    "path": "src/logo/ascii/openmamba.txt",
    "content": "                 `````\n           .-/+ooooooooo+/:-`\n        ./ooooooooooooooooooo+:.\n      -+oooooooooooooooooooooooo+-\n    .+ooooooooo+/:---::/+ooooooooo+.\n   :oooooooo/-`          `-/oo$2s´$1oooo.$2s´$1\n  :ooooooo/`                `$2sNds$1ooo$2sNds$1\n -ooooooo-                   $2:dmy$1ooo$2:dmy$1\n +oooooo:                      :oooooo-\n.ooooooo                        .://:`\n:oooooo+                        ./+o+:`\n-ooooooo`                      `oooooo+\n`ooooooo:                      /oooooo+\n -ooooooo:                    :ooooooo.\n  :ooooooo+.                .+ooooooo:\n   :oooooooo+-`          `-+oooooooo:\n    .+ooooooooo+/::::://oooooooooo+.\n      -+oooooooooooooooooooooooo+-\n        .:ooooooooooooooooooo+:.\n           `-:/ooooooooo+/:.`\n                 ``````"
  },
  {
    "path": "src/logo/ascii/openmandriva.txt",
    "content": "                 ``````\n            `-:/+++++++//:-.`\n         .:+++oooo+/:.``   ``\n      `:+ooooooo+:.  `-:/++++++/:.`\n     -+oooooooo:` `-++o+/::::://+o+/-\n   `/ooooooooo-  -+oo/.`        `-/oo+.\n  `+ooooooooo.  :os/`              .+so:\n  +sssssssss/  :ss/                 `+ss-\n :ssssssssss`  sss`                  .sso\n ossssssssss  `yyo                    sys\n`sssssssssss` `yys                   `yys\n`sssssssssss:  +yy/                  +yy:\n oyyyyyyyyyys. `oyy/`              `+yy+\n :yyyyyyyyyyyo. `+yhs:.         `./shy/\n  oyyyyyyyyyyys:` .oyhys+:----/+syhy+. `\n  `syyyyyyyyyyyyo-` .:osyhhhhhyys+:``.:`\n   `oyyyyyyyyyyyyys+-`` `.----.```./oo.\n     /yhhhhhhhhhhhhhhyso+//://+osyhy/`\n      `/yhhhhhhhhhhhhhhhhhhhhhhhhy/`\n        `:oyhhhhhhhhhhhhhhhhhhyo:`\n            .:+syhhhhhhhhys+:-`\n                ``....``"
  },
  {
    "path": "src/logo/ascii/openstage.txt",
    "content": "                 /(/\n              .(((((((,\n             /(((((((((/\n           .(((((/,/(((((,\n          *(((((*   ,(((((/\n          (((((*      .*/((\n         *((((/  (//(/*\n         /((((*  ((((((((((,\n      .  /((((*  (((((((((((((.\n     ((. *((((/        ,((((((((\n   ,(((/  (((((/     **   ,((((((*\n  /(((((. .(((((/   //(((*  *(((((/\n .(((((,    ((/   .(((((/.   .(((((,\n /((((*        ,(((((((/      ,(((((\n /(((((((((((((((((((/.  /(((((((((/\n /(((((((((((((((((,   /(((((((((((/\n     */(((((//*.      */((/(/(/*"
  },
  {
    "path": "src/logo/ascii/opensuse.txt",
    "content": "           $2.;ldkO0000Okdl;.\n       .;d00xl:^''''''^:ok00d;.\n     .d00l'                'o00d.\n   .d0Kd'$1  Okxol:;,.          $2:O0d\n  .OK$1KKK0kOKKKKKKKKKKOxo:,      $2lKO.\n ,0K$1KKKKKKKKKKKKKKK0P^$2,,,$1^dx:$2    ;00,\n.OK$1KKKKKKKKKKKKKKKk'$2.oOPPb.$1'0k.$2   cKO.\n:KK$1KKKKKKKKKKKKKKK: $2kKx..dd $1lKd$2   'OK:\ndKK$1KKKKKKKKKOx0KKKd $2^0KKKO' $1kKKc$2   dKd\ndKK$1KKKKKKKKKK;.;oOKx,..$2^$1..;kKKK0.$2  dKd\n:KK$1KKKKKKKKKK0o;...^cdxxOK0O/^^'  $2.0K:\n kKK$1KKKKKKKKKKKKK0x;,,......,;od  $2lKk\n '0K$1KKKKKKKKKKKKKKKKKKKK00KKOo^  $2c00'\n  'kK$1KKOxddxkOO00000Okxoc;''   $2.dKk'\n    l0Ko.                    .c00l'\n     'l0Kk:.              .;xK0l'\n        'lkK0xl:;,,,,;:ldO0kl'\n            '^:ldxkkkkxdl:^'"
  },
  {
    "path": "src/logo/ascii/opensuse_leap.txt",
    "content": "          ====\n         ======\n       ==== ====+\n     +====    +====\n   +===+        ====\n  ====            ====\n+===               +====\n====               +====\n =====            ====\n   +===+        =====\n==+  =====    +===+  ===\n====   ==== =====  =====\n  ====  =======   ====\n    ====  ===   ====\n     ====+    ====\n       ==== =====\n         ======\n           =="
  },
  {
    "path": "src/logo/ascii/opensuse_leap_old.txt",
    "content": "                 .-++:.\n               ./oooooo/-\n            `:oooooooooooo:.\n          -+oooooooooooooooo+-`\n       ./oooooooooooooooooooooo/-\n      :oooooooooooooooooooooooooo:\n    `  `-+oooooooooooooooooooo/-   `\n `:oo/-   .:ooooooooooooooo+:`  `-+oo/.\n`/oooooo:.   -/oooooooooo/.   ./oooooo/.\n  `:+ooooo+-`  `:+oooo+-   `:oooooo+:`\n     .:oooooo/.   .::`   -+oooooo/.\n        -/oooooo:.    ./oooooo+-\n          `:+ooooo+-:+oooooo:`\n             ./oooooooooo/.\n                -/oooo+:`\n                  `:/."
  },
  {
    "path": "src/logo/ascii/opensuse_microos.txt",
    "content": "             ⣀⣠⣴⣶⣶⣿⣿⣿⣿⣶⣶⣦⣄⣀\n          ⢀⣴⣾⣿⠿⠛⠉⠉    ⠉⠉⠛⠿⣿⣷⣦⡀\n         ⣴⣿⡿⠋              ⠙⢿⣿⣦\n        ⣾⣿⡟     ⣠⣴⣶⣿⣿⣶⣦⣄     ⢻⣿⣷\n⣠⣤⣤⣤⣤⣤⣤⣼⣿⣿     ⣼⣿⡟⠉  ⠉⢻⣿⣧     ⣿⣿⣧⣤⣤⣤⣤⣤⣤⣄\n⠙⠛⠛⠛⠛⠛⠛⢻⣿⣿     ⢻⣿⣧⡀  ⢀⣼⣿⡟     ⣿⣿⡟⠛⠛⠛⠛⠛⠛⠋\n        ⢿⣿⣇     ⠙⠿⣿⣿⣿⣿⠿⠋     ⣸⣿⡿\n        ⠈⢻⣿⣧⣀              ⣀⣾⣿⡟⠁\n          ⠙⠻⣿⣷⣦⣄⣀      ⣀⣠⣴⣾⣿⠟⠋\n             ⠉⠛⠿⢿⣿⣿⣿⣿⣿⣿⡿⠿⠛⠉"
  },
  {
    "path": "src/logo/ascii/opensuse_slowroll.txt",
    "content": "                _,......,_\n           _aaQQQQQQWQQQQQQQaa.\n        ajQQQ??^'        '^??QQQw.\n     ,aQQP^                    ?4QQa\n    wQQP                         ^4QQa\n  ,QQP'          ,aaaaa.           ^$Q6,\n ,QQP         ajQQQWWWWQQQw.         4QQ\n QQP        aQQP?        ?$QQ.        4Q6\njQQ        jQQ'            ?WQr       'QQb\nQQf       ]QQ'              ]QQ        QQ6\nQQf       ]QQ               ]QQ)       QQQ\nQQ6       'QQ6              ]QQ        QQf\n]QQ.       ^4QQaa          .QQP       ,QQ'\n $QQ.        ^?QQQQf      _yQP       ,QQP\n ^WQQ6.                  aQQ?       _QQP\n  ^4QQQ6a.            ajQQP'       aQQP\n    ?QQQQQWQaaaaaaajQWQP?        aQQP'\n      ?QQQQP????????^'        saWQP?\n        ^?QQQgaaa._    __aaaQQQP?'\n            '^?QWQQQQQQQQ@??^'"
  },
  {
    "path": "src/logo/ascii/opensuse_small.txt",
    "content": "  _______\n__|   __ \\\n     / .\\ \\\n     \\__/ |\n   _______|\n   \\_______\n__________/"
  },
  {
    "path": "src/logo/ascii/opensuse_tumbleweed.txt",
    "content": "          ,...,\n     .,:lloooooc;.\n   ,ool'     oo,;oo:\n .lo'        oo.   oo:\n.oo.         oo.    oo:\n:ol          oo.    'oo\n:oo         .oo.    .oo.\n.oooooooooooooo.    .oo.\n ;oo.               .oo.\n  'oo,              .oo.\n    \"ooc,',,,,,,,,,,:ooc,,,,,,,,,,,\n       ':cooooooooooooooooooooooooool;.\n                    .oo.             .oo;\n                    .oo.               .oo.\n                    .oo.    'oooooooooo:ooo.\n                    .oo.    'oo.         col\n                    .oo'    'oo          col\n                     coo    'oo          oo'\n                      coc   'oo        .lo,\n                       `oo, 'oo      .:oo\n                         'ooooc,, ,:lol\n                            `''\"clc\"'"
  },
  {
    "path": "src/logo/ascii/opensuse_tumbleweed2.txt",
    "content": "     ⣀⣤⣤⣶⣶⣶⣶⣶⣤⣄⡀\n   ⣠⣾⣿⣿⡿⠿⠛⠛⣿⣿⣿⣿⣿⣶⣄\n ⢠⣾⣿⣿⠛⠁    ⣿⣿⣿⠉⠻⣿⣿⣦\n⢀⣿⣿⡿⠁      ⣿⣿⣿  ⠙⣿⣿⣧\n⢸⣿⣿⠃       ⣿⣿⣿   ⢹⣿⣿⡀\n⢸⣿⣿⣤⣤⣤⣤⣤⣤⣤⣤⣿⣿⣿   ⢸⣿⣿⡇\n⠘⣿⣿⣿⠿⠿⠿⠿⠿⠿⠿⠿⠿⠿   ⢸⣿⣿⡇\n ⠹⣿⣿⣷⡀           ⢸⣿⣿⡇\n  ⠙⢿⣿⣿⣷⣤⣄⣀⣀⣀⣀⣀⣀⣀⣀⣸⣿⣿⣇⣀⣀⣀⣀⣀⣀⣀⣀\n    ⠉⠻⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⡀\n        ⠈⠉⠉⠉⠉⠉⠉⠉⠉⢹⣿⣿⡏⠉⠉⠉⠉⠉⠉⠉⠉⠙⠛⢿⣿⣿⣷⡀\n                 ⢸⣿⣿⡇           ⠈⢻⣿⣿⣆\n                 ⢸⣿⣿⡇   ⢰⣶⣶⣶⣶⣶⣶⣶⣶⣶⣿⣿⣿⡄\n                 ⢸⣿⣿⡇   ⢸⣿⣿⠛⠛⠛⠛⠛⠛⠛⠛⣿⣿⡇\n                 ⠘⣿⣿⡇   ⢸⣿⣿       ⢰⣿⣿⡇\n                  ⢻⣿⣿⡄  ⢸⣿⣿      ⢀⣾⣿⡿⠁\n                   ⢻⣿⣿⣦⣀⢸⣿⣿    ⢀⣴⣿⣿⡿⠁\n                    ⠙⢿⣿⣿⣿⣿⣿⣤⣴⣶⣾⣿⣿⡿⠋\n                      ⠈⠙⠻⠿⠿⠿⠿⠿⠛⠋⠁"
  },
  {
    "path": "src/logo/ascii/opensuse_tumbleweed_old.txt",
    "content": "                                     ......\n     .,cdxxxoc,.               .:kKMMMNWMMMNk:.\n    cKMMN0OOOKWMMXo. A        ;0MWk:'      ':OMMk.\n  ;WMK;'       'lKMMNM,     :NMK'             'OMW;\n cMW;             WMMMN   ,XMK'                 oMM.\n.MMc             ''^*~l. xMN:                    KM0\n'MM.                   .NMO                      oMM\n.MM,                 .kMMl                       xMN\n KM0               .kMM0' .dl>~,.               .WMd\n 'XM0.           ,OMMK'    OMMM7'              .XMK\n   *WMO:.    .;xNMMk'       NNNMKl.          .xWMx\n     ^ONMMNXMMMKx;          V  'xNMWKkxllox0NMWk'\n         '''''                    ':dOOXXKOxl'"
  },
  {
    "path": "src/logo/ascii/opensuse_tumbleweed_small.txt",
    "content": " .oooo.\no   o  o\nooooo  oo\no      oo\n 'oooooooooooo.\n       oo      o\n       oo  ooooo\n        o  o   o\n         'oooo'"
  },
  {
    "path": "src/logo/ascii/openwrt.txt",
    "content": " _______\n|       |.-----.-----.-----.\n|   -   ||  _  |  -__|     |\n|_______||   __|_____|__|__|\n         |__|\n ________        __\n|  |  |  |.----.|  |_\n|  |  |  ||   _||   _|\n|________||__|  |____|"
  },
  {
    "path": "src/logo/ascii/opnsense.txt",
    "content": "    .'''''''''''''''''''''''''''''''''''\n  oocc:::::::::::::::::::::::::::::::cox\n ;00;                                o0O\n .,:'  .;;;;;;;;;;;;;;;;;;;;;;;;;;   ;:,\n  .',;;cxOOOOOOOOOOOOOOOOOOOOOOOkd:;;,..\n     .,cll:'                 ':llc,.\n    ,;;:okxdxd:           :dxdxko:;;,\n   .xxxx0XNNK0O.         .O0KNNX0xxxx.\n       ,$2cc:$1,.               .,$2:cc$1,\n ........;$2ccc:$1;.         .;$2:ccc$1;........\n $2ccccccccccccccc         ccccccccccccccc$1\n ........;$2ccc:$1;.         .;$2:ccc$1;........\n       ,$2cc:$1,.               .,$2:cc$1,\n   .xxxx0XNNK0O.         .O0KNNX0xxxx.\n    ,;;:okxdxd:           :dxdxko:;;,\n     .,cll:'                 ':llc,.\n  .,,;,ckOOOOOOOOOOOOOOOOOOOOOOOOx;,;,'.\n .:l'  ...........................   ;:;\n lOk'                                cdd\n ;lccccccccccccccccccccccccccccccccccc:."
  },
  {
    "path": "src/logo/ascii/oracle.txt",
    "content": "      `-/+++++++++++++++++/-.`\n   `/syyyyyyyyyyyyyyyyyyyyyyys/.\n  :yyyyo/-...............-/oyyyy/\n /yyys-                     .oyyy+\n.yyyy`                       `syyy-\n:yyyo                         /yyy/\n.yyyy`                       `syyy-\n /yyys.                     .oyyyo\n  /yyyyo:-...............-:oyyyy/`\n   `/syyyyyyyyyyyyyyyyyyyyyyys+.\n     `.:/+ooooooooooooooo+/:.`"
  },
  {
    "path": "src/logo/ascii/orchid.txt",
    "content": "$2                  .==.\n                .-$3#$1@@$3#$2-.\n              .-$3##$1@@@@$3##$2-.\n            .-$3##$1@@@@@@@@$3##$2-.\n           :*$1@@@@@$3####$1@@@@@$2*:\n         ..:*$1@@@@$2==--==$1@@@@$2*:..\n      .-*$1%%$3#$2==$3#$1@@$3#$2====$3#$1@@$3#$2==$3#$1%%$2*-.\n    .-$3#$1@@@@@$3##$2==$3#$1@@$2++$1@@$3##$2==$3#$1@@@@@$3#$2-.\n  .-$3#$1@@@@@$2#$1@@@$3#$2++#====$3#$2++#$1@@@$2#$1@@@@@$3#$2-.\n.-$3#$1@@@@@$3#$2-==**$3###$2+:--:+$3###$2**==-$3#$1@@@@@$3#$2-.\n.-$3#$1@@@@@$3#$2-==**$3###$2+:--:+$3###$2**==-$3#$1@@@@@$3#$2-.\n  .-$3#$1@@@@@$2#$1@@@$3#$2++#====$3#$2++#$1@@@$2#$1@@@@@$3#$2-.\n    .-$3#$1@@@@@$3##$2==$3#$1@@$2++$1@@$3##$2==$3#$1@@@@@$3#$2-.\n      .-*$1%%$3#$2==$3#$1@@$3#$2====$3#$1@@$3#$2==$3#$1%%$2*-.\n         ..:*$1@@@@$2==--==$1@@@@$2*:..\n           :*$1@@@@@$3####$1@@@@@$2*:\n            .-$3##$1@@@@@@@@$3##$2-.\n              .-$3##$1@@@@$3##$2-.\n                .-$3#$1@@$3#$2-.\n                  .==."
  },
  {
    "path": "src/logo/ascii/orchid_small.txt",
    "content": "            $2:##:\n          -#$1@@@@$2#-\n         #$1@@$2=..=$1@@$2#\n         +$1@@$2-  -$1@@$2+\n    -#$1@@$2*..*$1@$2..$1@$2*..*$1@@$2#-\n  :#$1@@$2*+%$1@$2= .  . =$1@$2%+*$1@@$2#:\n+$1@@@$2:    :-.    .-:   :$1@@@$2+\n  :#$1@@$2*+%$1@$2= .  . =$1@$2%+*$1@@$2#:\n    -#$1@@$2*..*$1@$2..$1@$2*..*$1@@$2#-\n         +$1@@$2-  -$1@@$2+\n         #$1@@$2=..=$1@@$2#\n          -#$1@@@@$2#-\n            :##:"
  },
  {
    "path": "src/logo/ascii/oreon.txt",
    "content": "                    @@@@@@@@@@\n                @@@@@@@@@@@@@@@@@@\n            @@@@@@@@@@@@@@@@@@@@@@@@@@\n          @@@@@@@@@@         @@@@@@@@@@@\n         @@@@@@@@                @@@@@@@@@\n       @@@@@@@@                    @@@@@@@@\n      @@@@@@@                        @@@@@@@\n    @@@@@@@@                          @@@@@@@\n   @@@@@@@@                            @@@@@@\n @@@@@@@@@                              @@@@@@\n@@@@@@@@@@                              @@@@@@\n@@@@@@@@@@                              @@@@@@\n @@@@@@@@@                              @@@@@@\n   @@@@@@@@                            @@@@@@\n    @@@@@@@@                          @@@@@@@\n      @@@@@@@                        @@@@@@@\n       @@@@@@@@                    @@@@@@@@\n         @@@@@@@@                @@@@@@@@\n          @@@@@@@@@@         @@@@@@@@@@@\n             @@@@@@@@@@@@@@@@@@@@@@@@\n                @@@@@@@@@@@@@@@@@@\n                    @@@@@@@@@@"
  },
  {
    "path": "src/logo/ascii/os2warp.txt",
    "content": "                 .-==:  .   .\n    -+:::+=     *      *-   = -.  -%*\n  **       *#  :%-      .  :.*=:   -%%\n %@:       -@%  =%@%%*:    -  :.   -%=\n-@@:       :@@-    -+#%%+ .:      :*.\n-@@:       :@@--       -% =     .*\n.%@-       -%% ++       # .   -:     .\n  #*       **  =.:*:  +- =  #%%%%#+-*:\n    -=====-                       .:- ..\n\n$2  =*+                               -+*:\n  .*+ :*+=                      -+*++**-\n   +*-++++ -++-:.       .:-=++.=*+. =*-\n   -***:-+:*++=*++*:  +*+=:-**+=**++-\n     :- :***==*+.=++  +*+=++*=.=*+.\n         .=-=*+***+*= +*+  :**+=++\n              .   -*+ +*+  .=:\n"
  },
  {
    "path": "src/logo/ascii/os_elbrus.txt",
    "content": "▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄\n██▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀██\n██                       ██\n██   ███████   ███████   ██\n██   ██   ██   ██   ██   ██\n██   ██   ██   ██   ██   ██\n██   ██   ██   ██   ██   ██\n██   ██   ██   ██   ██   ██\n██   ██   ███████   ███████\n██   ██                  ██\n██   ██▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄██\n██   ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀██\n██                       ██\n███████████████████████████"
  },
  {
    "path": "src/logo/ascii/osmc.txt",
    "content": "            -+shdmNNNNmdhs+-\n        .+hMNho/:..``..:/ohNMh+.\n      :hMdo.                .odMh:\n    -dMy-                      -yMd-\n   sMd-                          -dMs\n  hMy       +.            .+       yMh\n yMy        dMs.        .sMd        yMy\n:Mm         dMNMs`    `sMNMd        `mM:\nyM+         dM//mNs``sNm//Md         +My\nmM-         dM:  +NNNN+  :Md         -Mm\nmM-         dM: `oNN+    :Md         -Mm\nyM+         dM/+NNo`     :Md         +My\n:Mm`        dMMNs`       :Md        `mM:\n yMy        dMs`         -ms        yMy\n  hMy       +.                     yMh\n   sMd-                          -dMs\n    -dMy-                      -yMd-\n      :hMdo.                .odMh:\n        .+hMNho/:..``..:/ohNMh+.\n            -+shdmNNNNmdhs+-"
  },
  {
    "path": "src/logo/ascii/pacbsd.txt",
    "content": "      :+sMs.\n  `:ddNMd-                         -o--`\n -sMMMMh:                          `+N+``\n yMMMMMs`     .....-/-...           `mNh/\n yMMMMMmh+-`:sdmmmmmmMmmmmddy+-``./ddNMMm\n yNMMNMMMMNdyyNNMMMMMMMMMMMMMMMhyshNmMMMm\n :yMMMMMMMMMNdooNMMMMMMMMMMMMMMMMNmy:mMMd\n  +MMMMMMMMMmy:sNMMMMMMMMMMMMMMMMMMMmshs-\n  :hNMMMMMMN+-+MMMMMMMMMMMMMMMMMMMMMMMs.\n .omysmNNhy/+yNMMMMMMMMMMNMMMMMMMMMNdNNy-\n /hMM:::::/hNMMMMMMMMMMMm/-yNMMMMMMN.mMNh`\n.hMMMMdhdMMMMMMMMMMMMMMmo  `sMMMMMMN mMMm-\n:dMMMMMMMMMMMMMMMMMMMMMdo+  oMMMMMMN`smMNo`\n/dMMMMMMMMMMMMMMMMMMMMMNd/` :yMMMMMN:-hMMM.\n:dMMMMMMMMMMMMMMMMMMMMMNh`  oMMMMMMNo/dMNN`\n:hMMMMMMMMMMMMMMMMMMMMMMNs--sMMMMMMMNNmy++`\n sNMMMMMMMMMMMMMMMMMMMMMMMmmNMMMMMMNho::o.\n :yMMMMMMMMMMMMMNho+sydNNNNNNNmysso/` -//\n  /dMMMMMMMMMMMMMs-  ````````..``\n   .oMMMMMMMMMMMMNs`               ./y:`\n     +dNMMNMMMMMMMmy`          ``./ys.\n      `/hMMMMMMMMMMMNo-``    `.+yy+-`\n        `-/hmNMNMMMMMMmmddddhhy/-`\n            `-+oooyMMMdsoo+/:."
  },
  {
    "path": "src/logo/ascii/panwah.txt",
    "content": "         HHH\n        HAAAH                             HHH\n        HAAAAH                           HAAAH\n       HAAAAAAH                         HAAAAH\n       HAAAAAAH                       HAAAAAH\n      HAAAAAAAAH$2WWWWWWWWWWWWWWWW      $1HAAAAAH\n      HAAAAAAAAH$2WWWWWWWWWWWWWWWWWWWW$1 HAAAAAH\n      HAA$2WWWWWWWWWWWWWWWWWWWWWWWWWWWWW$1AAAAAH$2\n     WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW$1WAH$2\n    WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW\n  WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW\n WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW\nWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW\nWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW\nWWWWWWW$1AAA$2WWWW   WWWWWWWWWWWWWWWWWWWWWWWWWWW\n  WWWW$1AAA$2WWWWW    WWWWWWW    WWWWWWWWWWWWWWW\n   WW$1AAA$2WWWWWWWWWWWWWWWWW   WWWWW$1AAA$2WWWWWWWW\n    $1AAA$2WWWWW$1OOOOOOOOOOO$2WWWWWWWWWWW$1AAA$2WWWWWW\n          $1OOOO$3GGGGGGG$1OOOO$2WWWWWWWWWW$1AAA$2WWWW\n           $1OOO$3GGGGGGG$1OOO$2WWWWWWWWWWWW$1AAA$2W\n             $1OOOOOOOOO"
  },
  {
    "path": "src/logo/ascii/parabola.txt",
    "content": "                          `.-.    `.\n                   `.`  `:++.   `-+o+.\n             `` `:+/. `:+/.   `-+oooo+\n        ``-::-.:+/. `:+/.   `-+oooooo+\n    `.-:///-  ..`   .-.   `-+oooooooo-\n `..-..`                 `+ooooooooo:\n``                        :oooooooo/\n                          `ooooooo:\n                          `oooooo:\n                          -oooo+.\n                          +ooo/`\n                         -ooo-\n                        `+o/.\n                        /+-\n                       //`\n                      -."
  },
  {
    "path": "src/logo/ascii/parabola_small.txt",
    "content": "  __ __ __  _\n.`_//_//_/ / `.\n          /  .`\n         / .`\n        /.`\n       /`"
  },
  {
    "path": "src/logo/ascii/parch.txt",
    "content": "            ,:lodddd.\n          .:clooood.\n        ;clllooooc\n      ;cclllllloo\n     .cccccllllll\n   .   ,cccclllll\n  ':::;; ccccclll;\n .:::cccccccccccll;\n ;::::ccccllllllcll:\n.;::::cccclllloool::;\n;;;::::cccclllolc::::;.\n;;;::::cccclllccc:::::;.\n;;;::::cccclccccc::::::;.\n;;;;::::::llcccccc:::::'\n;;;;:; ,clllccccccc::\n.;;  .cllllllcccccc::;::::'\n    .'''''''''',:lddoooolll\n   '.....'''',cdddooooollll\n  ........':oddddoooolllllc\n   ....';ldddddooooolllllc:\n     ,cdddddddooooollllccc\n      :ddddddoooolllllccc\n        ;ddooooolllllcc.\n           :ooollllc.\n               c'"
  },
  {
    "path": "src/logo/ascii/pardus.txt",
    "content": " .smNdy+-    `.:/osyyso+:.`    -+ydmNs.\n/Md- -/ymMdmNNdhso/::/oshdNNmdMmy/. :dM/\nmN.     oMdyy- -y          `-dMo     .Nm\n.mN+`  sMy hN+ -:             yMs  `+Nm.\n `yMMddMs.dy `+`               sMddMMy`\n   +MMMo  .`  .                 oMMM+\n   `NM/    `````.`    `.`````    +MN`\n   yM+   `.-:yhomy    ymohy:-.`   +My\n   yM:          yo    oy          :My\n   +Ms         .N`    `N.      +h sM+\n   `MN      -   -::::::-   : :o:+`NM`\n    yM/    sh   -dMMMMd-   ho  +y+My\n    .dNhsohMh-//: /mm/ ://-yMyoshNd`\n      `-ommNMm+:/. oo ./:+mMNmmo:`\n     `/o+.-somNh- :yy: -hNmos-.+o/`\n    ./` .s/`s+sMdd+``+ddMs+s`/s. `/.\n        : -y.  -hNmddmNy.  .y- :\n         -+       `..`       +-"
  },
  {
    "path": "src/logo/ascii/parrot.txt",
    "content": "  `:oho/-`\n`mMMMMMMMMMMMNmmdhy-\n dMMMMMMMMMMMMMMMMMMs`\n +MMsohNMMMMMMMMMMMMMm/\n .My   .+dMMMMMMMMMMMMMh.\n  +       :NMMMMMMMMMMMMNo\n           `yMMMMMMMMMMMMMm:\n             /NMMMMMMMMMMMMMy`\n              .hMMMMMMMMMMMMMN+\n                  ``-NMMMMMMMMMd-\n                     /MMMMMMMMMMMs`\n                      mMMMMMMMsyNMN/\n                      +MMMMMMMo  :sNh.\n                      `NMMMMMMm     -o/\n                       oMMMMMMM.\n                       `NMMMMMM+\n                        +MMd/NMh\n                         mMm -mN`\n                         /MM  `h:\n                          dM`   .\n                          :M-\n                           d:\n                           -+\n                            -"
  },
  {
    "path": "src/logo/ascii/parsix.txt",
    "content": "                 $2-/+/:.\n               $2.syssssys.\n       $1.--.    $2ssssssssso$1   ..--.\n     :++++++:  $2+ssssssss+$1 ./++/+++:\n    /+++++++++.$2.yssooooy`$1-+///////o-\n    /++++++++++.$2+soooos:$1:+////////+-\n     :+++++////o-$2oooooo-$1+/////////-\n      `-/++//++-$4.-----.-$1:+/////:-\n  $3-://::--$1-:/:$4.--.````.--.$1:::-$3--::::::.\n$3-/:::::::://:$4.:-`      `-:$3`:/:::::::--/-\n$3/::::::::::/-$4--.        .-.$3-/://///::::/\n$3-/:::::::::/:$4`:-.      .-:$3`:///////////-\n `$3-::::--$1.-://.$4---....---$1`:+/:-$3--::::-`\n       $1-/+///+o/-$4.----.$1.:oo+++o+.\n     $1-+/////+++o:$2syyyyy.$1o+++++++++:\n    $1.+////+++++-$2+sssssy+$1.++++++++++\\\n    $1.+:/++++++.$2.yssssssy-$1`+++++++++:\n     $1:/+++++-  $2+sssssssss  $1-++++++-\n       $1`--`    $2+sssssssso    $1`--`\n                $2+sssssy+`\n                 $2`.::-`"
  },
  {
    "path": "src/logo/ascii/pcbsd.txt",
    "content": "                       ..\n                        s.\n                        +y\n                        yN\n                       -MN  `.\n                      :NMs `m\n                    .yMMm` `No\n            `-/+++sdMMMNs+-`+Ms\n        `:oo+-` .yMMMMy` `-+oNMh\n      -oo-     +NMMMM/       oMMh-\n    .s+` `    oMMMMM/     -  oMMMhy.\n   +s`- ::   :MMMMMd     -o `mMMMy`s+\n  y+  h .Ny+oNMMMMMN/    sh+NMMMMo  +y\n s+ .ds  -NMMMMMMMMMMNdhdNMMMMMMh`   +s\n-h .NM`   `hMMMMMMMMMMMMMMNMMNy:      h-\ny- hMN`     hMMmMMMMMMMMMNsdMNs.      -y\nm` mMMy`    oMMNoNMMMMMMo`  sMMMo     `m\nm` :NMMMdyydMMMMo+MdMMMs     sMMMd`   `m\nh-  `+ymMMMMMMMM--M+hMMN/    +MMMMy   -h\n:y     `.sMMMMM/ oMM+.yMMNddNMMMMMm   y:\n y:   `s  dMMN- .MMMM/ :MMMMMMMMMMh  :y\n `h:  `mdmMMM/  yMMMMs  sMMMMMMMMN- :h`\n   so  -NMMMN   /mmd+  `dMMMMMMMm- os\n    :y: `yMMM`       `+NMMMMMMNo`:y:\n      /s+`.omy      /NMMMMMNh/.+s:\n        .+oo:-.     /mdhs+::oo+.\n            -/o+++++++++++/-"
  },
  {
    "path": "src/logo/ascii/pclinuxos.txt",
    "content": "            mhhhyyyyhhhdN\n        dyssyhhhhhhhhhhhssyhN\n     Nysyhhyo/:-.....-/oyhhhssd\n   Nsshhy+.              `/shhysm\n  dohhy/                    -shhsy\n dohhs`                       /hhys\nN+hho   $2+ssssss+-   .+syhys+   $1/hhsy\nohhh`   $2ymmo++hmm+`smmy/::+y`   $1shh+\n+hho    $2ymm-  /mmy+mms          $1:hhod\n/hh+    $2ymmhhdmmh.smm/          $1.hhsh\n+hhs    $2ymm+::-`  /mmy`    `    $1/hh+m\nyyhh-   $2ymm-       /dmdyosyd`  $1`yhh+\n ohhy`  $2://`         -/+++/-   $1ohhom\n N+hhy-                      `shhoh\n   sshho.                  `+hhyom\n    dsyhhs/.            `:ohhhoy\n      dysyhhhso///://+syhhhssh\n         dhyssyhhhhhhyssyyhN\n              mddhdhdmN"
  },
  {
    "path": "src/logo/ascii/pearos.txt",
    "content": "                  .+yh\n                 sMMMo\n                sMMN+\n                +o:\n$2           ./oyyys+.\n         :dMMMMMMMMMm/\n        :MMMMMMMMMMMMMy\n        yMMMMMMMMMMMMMN\n$3        mMMMMMMMMMMMMs`\n       yMMMMMMMMMMMMo\n     -mMMMMMMMMMMMMM`\n    oMMMMMMMMMMMMMMM`\n$4   oMMMMMMMMMMMMMMMMy\n  .MMMMMMMMMMMMMMMMMMy`\n  +MMMMMMMMMMMMMMMMMMMMy/`\n  /MMMMMMMMMMMMMMMMMMMMMMMNds\n$5  `mMMMMMMMMMMMMMMMMMMMMMMMM/\n   .mMMMMMMMMMMMMMMMMMMMMMM+\n    `oNMMMMMMMMMMMMMMMMMMd-\n      `+hMMMMMMMMMMMMMms-\n          -/osyhhyso:."
  },
  {
    "path": "src/logo/ascii/pengwin.txt",
    "content": "$3              ...`\n$3              `-///:-`\n$3                .+$2ssys$3/\n$3                 +$2yyyyy$3o    $2\n$2                 -yyyyyy:\n$2    `.:/+ooo+/:` -yyyyyy+\n$2  `:oyyyyyys+:-.`syyyyyy:\n$2 .syyyyyyo-`   .oyyyyyyo\n$2`syyyyyy   `-+yyyyyyy/`\n$2/yyyyyy+ -/osyyyyyyo/.\n$2+yyyyyy-  `.-:::-.`\n$2.yyyyyy-\n$3 :$2yyyyy$3o\n$3  .+$2ooo$3+\n$3    `.::/:."
  },
  {
    "path": "src/logo/ascii/pentoo.txt",
    "content": "$2           `:oydNNMMMMNNdyo:`\n        :yNMMMMMMMMMMMMMMMMNy:\n      :dMMMMMMMMMMMMMMMMMMMMMMd:\n     oMMMMMMMho/-....-/ohMMMMMMMo\n    oMMMMMMy.            .yMMMMMMo\n   .MMMMMMo                oMMMMMM.\n   +MMMMMm                  mMMMMM+\n   oMMMMMh                  hMMMMMo\n //hMMMMMm//$1`$2          $1`$2////mMMMMMh//\nMMMMMMMMMMM$1/$2      $1/o/`$2  $1.$2smMMMMMMMMMMM\nMMMMMMMMMMm      $1`NMN:$2    $1.$2yMMMMMMMMMM\nMMMMMMMMMMMh$1:.$2              dMMMMMMMMM\nMMMMMMMMMMMMMy$1.$2            $1-$2NMMMMMMMMM\nMMMMMMMMMMMd:$1`$2           $1-$2yNMMMMMMMMMM\nMMMMMMMMMMh$1`$2          $1./$2hNMMMMMMMMMMMM\nMMMMMMMMMM$1s$2        $1.:$2ymMMMMMMMMMMMMMMM\nMMMMMMMMMMN$1s:..-/$2ohNMMMMMMMMMMMMMMMMMM\nMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\nMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM"
  },
  {
    "path": "src/logo/ascii/peppermint.txt",
    "content": "$1             PPPPPPPPPPPPPP\n$1         PPPP$2MMMMMMM$1PPPPPPPPPPP\n$1       PPPP$2MMMMMMMMMM$1PPPPPPPP$2MM$1PP\n$1     PPPPPPPP$2MMMMMMM$1PPPPPPPP$2MMMMM$1PP\n$1   PPPPPPPPPPPP$2MMMMMM$1PPPPPPP$2MMMMMMM$1PP\n$1  PPPPPPPPPPPP$2MMMMMMM$1PPPP$2M$1P$2MMMMMMMMM$1PP\n$1 PP$2MMMM$1PPPPPPPPPP$2MMM$1PPPPP$2MMMMMMM$1P$2MM$1PPPP\n$1 P$2MMMMMMMMMM$1PPPPPP$2MM$1PPPPP$2MMMMMM$1PPPPPPPP\n$1P$2MMMMMMMMMMMM$1PPPPP$2MM$1PP$2M$1P$2MM$1P$2MM$1PPPPPPPPPPP\n$1P$2MMMMMMMMMMMMMMMM$1PP$2M$1P$2MMM$1PPPPPPPPPPPPPPPP\n$1P$2MMM$1PPPPPPPPPPPPPPPPPPPPPPPPPPPPPP$2MMMMM$1P\n$1PPPPPPPPPPPPPPPP$2MMM$1P$2M$1P$2MMMMMMMMMMMMMMMM$1PP\n$1PPPPPPPPPPP$2MM$1P$2MM$1PPPP$2MM$1PPPPP$2MMMMMMMMMMM$1PP\n$1 PPPPPPPP$2MMMMMM$1PPPPP$2MM$1PPPPPP$2MMMMMMMMM$1PP\n$1 PPPP$2MM$1P$2MMMMMMM$1PPPPPP$2MM$1PPPPPPPPPP$2MMMM$1PP\n$1  PP$2MMMMMMMMM$1P$2M$1PPPP$2MMMMMM$1PPPPPPPPPPPPP\n$1   PP$2MMMMMMM$1PPPPPPP$2MMMMMM$1PPPPPPPPPPPP\n$1     PP$2MMMM$1PPPPPPPPP$2MMMMMMM$1PPPPPPPP\n$1       PP$2MM$1PPPPPPPP$2MMMMMMMMMM$1PPPP\n$1         PPPPPPPPPP$2MMMMMMMM$1PPPP\n$1             PPPPPPPPPPPPPP"
  },
  {
    "path": "src/logo/ascii/peropesis.txt",
    "content": "####  #### ####   ###   ####  ####  #### #  ####\n#   # #    #   # #   #  #   # #     #    #  #\n####  ###  #### #     # ####  ###     #  #    #\n#     #    #  #  #   #  #     #        # #     #\n#     #### #   #  ###   #     #### ####  # ####"
  },
  {
    "path": "src/logo/ascii/phyos.txt",
    "content": ".^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^.^^^^^.\n :777777777777777777777777777777^~7777:\n  .~~~~~~~~~~~~~~~~~~~~~^~7777!:!777!.\n    ~7!!!!!!!!!!!!!!!!!^:!777~^!777~\n     ^77777!!!!!!!!!7!^^7777^^7777^\n      ^7777~.~~~~^.  .~7777^~7777:\n       :!777~^!777~. !777!:~777!:\n        .!777!:~777!:~77~:!777!.\n          ~777!^~7777:^~^!777~\n           ^7777^^7777^^7777^\n            :7777~^!7777777:\n             .!777!:!7777!.\n              .~777!:~77~.\n                ~7777^~~\n                 ^7777.\n                  :77:\n                   .."
  },
  {
    "path": "src/logo/ascii/pikaos.txt",
    "content": "                  -                              \n                   ----------------               \n                    ------------------            \n                    -------------------           \n    --               -----------=*-=----#####     \n   -----------------------------####--=######     \n   ------------------------------++---+####       \n   =============----------------=+=-----          \n   =================-----------####+----          \n   ==================----------*###=----          \n   ===================-----------------           \n    ==================-----------------           \n     =================----------------            \n      ===============------------::::             \n       ============-:::::::::::::::               \n         =======--:::::::::::::::                 \n            .::::::::::::::::.                    \n                 :::::::                          "
  },
  {
    "path": "src/logo/ascii/pisi.txt",
    "content": "   \\Fv/!-                      `:?lzC\n$1   Q!::=zFx!  $2`;v6WBCicl;`  $1,vCC\\!::#.\n$1  ,%:::,'` $2+#%@@FQ@@.   ,cF%i$1``-',::a?\n$1  +m:,'```$2}3,/@@Q\\@@       \"af-$1 `-'\"7f\n  =o'.` $2/m'   :Q@:Qg         ,kl$1  `.|o\n  :k` '$2$+      'Narm           >d,$1  ii\n   #`$2!p.        `C ,            'd+$1 %'\n$2   !0m                           `6Kv\n   =a                              m+\n  !A     !\\L|:            :|L\\!     $:\n .8`     Q''%Q#'        '#Q%''Q     `0-\n :6      E|.6QQu        uQQ6.|E      p:\n  i{      \\jts9?        ?9stj\\      u\\\n   |a`            -''.            `e>\n    ,m+     $1'^ !`$2s@@@@a$1'\"`+`$2     >e'\n      !3|$1`|=>>r-  $2'U%:$1  '>>>=:`\\3!\n       'xopE|      $2`'$1     `ledoz-\n    `;=>>+`$2`^llci/|==|/iclc;`$1'>>>>:\n   `^`+~          $2````$1          !!-^"
  },
  {
    "path": "src/logo/ascii/pnm_linux.txt",
    "content": "               ``.---..` `--`\n            ``.---........-:.$2-::`$1\n           $2./::-$1........$2--::.````$1\n          $2.:://:::$1----$2::::-..$1\n          ..$2--:::::--::::++-$1.`\n  $2`-:-`$1   .-ohy+::$2-:::$1/sdmdd:.$2   `-:-\n   .-:::$1...$3sNNmds$y$1o/+$3sy+NN$m$1d+.`$2-:::-.\n     `.-:-$1./$3dN$1()$3yyooosd$1()$3$m$1dy$2-.::-.`$1\n      $2`.$1-...-$3+hNdyyyyyydmy$1:......$2`$1\n ``..--.....-$3yNNm$4hssssh$3mmdo$1.........```\n`-:://:.....$3hNNNNN$4mddm$3NNNmds$1.....//::--`\n  ```.:-...$3oNNNNNNNNNNNNNNmd/$1...:-.```\n      .....$3hNNNNNNNNNNNNNNmds$1....`\n      --...$3hNNNNNNNNNNNNNNmdo$1.....\n      .:...$3/NNNNNNNNNNNNNNdd$1:....`\n       `-...$3+mNNNNNNNNNNNmh$1:...-.\n     $4.:+o+/:-$1:+oo+///++o+/:-$4:/+ooo/:.\n       $4+oo/:o-            +oooooso.`\n       $4.`   `             `/  .-//-"
  },
  {
    "path": "src/logo/ascii/pop.txt",
    "content": "             /////////////\n         /////////////////////\n      ///////$2*767$1////////////////\n    //////$27676767676*$1//////////////\n   /////$276767$1//$27676767$1//////////////\n  /////$2767676$1///$2*76767$1///////////////\n ///////$2767676$1///$276767$1.///$27676*$1///////\n/////////$2767676$1//$276767$1///$2767676$1////////\n//////////$276767676767$1////$276767$1/////////\n///////////$276767676$1//////$27676$1//////////\n////////////,$27676$1,///////$2767$1///////////\n/////////////*$27676$1///////$276$1////////////\n///////////////$27676$1////////////////////\n ///////////////$27676$1///$2767$1////////////\n  //////////////////////$2'$1////////////\n   //////$2.7676767676767676767,$1//////\n    /////$2767676767676767676767$1/////\n      ///////////////////////////\n         /////////////////////\n             /////////////"
  },
  {
    "path": "src/logo/ascii/pop_small.txt",
    "content": "______\n\\   _ \\        __\n \\ \\ \\ \\      / /\n  \\ \\_\\ \\    / /\n   \\  ___\\  /_/\n    \\ \\    _\n   __\\_\\__(_)_\n  (___________)`"
  },
  {
    "path": "src/logo/ascii/porteus.txt",
    "content": "             `.-:::-.`\n         -+ydmNNNNNNNmdy+-\n      .+dNmdhs+//////+shdmdo.\n    .smmy+-`             ./sdy:\n  `omdo.    `.-/+osssso+/-` `+dy.\n `yms.   `:shmNmdhsoo++osyyo-``oh.\n hm/   .odNmds/.`    ``.....:::-+s\n/m:  `+dNmy:`   `./oyhhhhyyooo++so\nys  `yNmy-    .+hmmho:-.`     ```\ns:  yNm+`   .smNd+.\n`` /Nm:    +dNd+`\n   yN+   `smNy.\n   dm    oNNy`\n   hy   -mNm.\n   +y   oNNo\n   `y`  sNN:\n    `:  +NN:\n     `  .mNo\n         /mm`\n          /my`\n           .sy`\n             .+:\n                `"
  },
  {
    "path": "src/logo/ascii/postmarketos.txt",
    "content": "                 /\\\n                /  \\\n               /    \\\n              /      \\\n             /        \\\n            /          \\\n            \\           \\\n          /\\ \\____       \\\n         /  \\____ \\       \\\n        /       /  \\       \\\n       /       /    \\    ___\\\n      /       /      \\  / ____\n     /       /        \\/ /    \\\n    /       / __________/      \\\n   /        \\ \\                 \\\n  /          \\ \\                 \\\n /           / /                  \\\n/___________/ /____________________\\"
  },
  {
    "path": "src/logo/ascii/postmarketos_small.txt",
    "content": "        /\\\n       /  \\\n      /    \\\n      \\__   \\\n    /\\__ \\  _\\\n   /   /  \\/ __\n  /   / ____/  \\\n /    \\ \\       \\\n/_____/ /________\\"
  },
  {
    "path": "src/logo/ascii/prismlinux.txt",
    "content": "                    ⣤⣤    ⢀⡄\n               ⢀⣾⣦  ⠙⠛  ⢀⣴⣿⣷  ⢠⣤\n           ⣤⡄  ⣸⣿⣿⣷⣄  ⣠⣶⣿⣿⣿⣿  ⠈⠉      ⢀⣤⡀\n           ⠉⠁ ⢠⣿⣿⣿⣿⣿⣦⡈⢿⣿⣿⣿⣿⣿⡇⢀⣀⣤⣤⣤⣤⣴⣶⠆⠘⠿⠃   ⠉ ⡀\n       ⢤⣄⡀    ⣾⣿⣿⣿⣿⣿⣿⣷⡄⠻⣿⣿⣿⣿⡇⢸⣿⣿⣿⣿⣿⣿⡏         ⡇\n       ⠘⣿⣿⣿⣿⣶⣤⣈⣉⠛⠿⢿⣿⣿⣿⣿⣦⠘⢿⣿⣿⣷⠈⣿⣿⣿⣿⣿⠏         ⠐⠁\n        ⢿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣦⣤⣉⡛⠻⢿⣷⣄⠻⣿⣿ ⣿⣿⣿⣿⠟⢠⣀       ⡐⠁\n    ⢰⣶  ⠘⣿⣿⣿⣿⣿⣿⣿⠿⠿⠛⠛⠉⠉  ⠈⠉ ⠘⣿ ⣿⣿⣿⡏⢠⣿⣿⣿⣶⣦⣄⡀ ⠂\n    ⠈⠁   ⠙⠋⣉⣉⣤⣤⣶⣶⠾⠉         ⠈ ⢹⣿⠏⢠⣿⣿⣿⣿⣿⣿⣿⣿⠟\n   ⢀⣠⣤⣴⣶⣿⣿⣿⣿⣿⣿⣿⠟⠁             ⢸⠟⣰⣿⣿⣿⣿⣿⡿⠟⠉\n   ⠈⠛⢿⣿⣿⣿⣿⣿⣿⣿⠟⢁⣴⡏             ⠈⣰⣿⣿⣿⠿⠛⣁⡀   ⢀⡒\n  ⢤⡀  ⠙⢿⣿⣿⣿⠟⢁⣴⣿⣿              ⣰⡿⠟⢉⣠⣴⣾⣿⣿⣆\n  ⠛⠁ ⠔  ⠈⠛⢁⣴⣿⣿⣿⡇⢰⡆           ⠊⣁⣤⣾⣿⣿⣿⣿⣿⣿⣿⣷⡀\n   ⡠⠂   ⢀⣴⣿⣿⣿⣿⣿⠃⣾⣿⡄⢢⡀      ⠤⠶⠿⠿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡄\n ⢀⠈   ⢀⣴⣿⣿⣿⣿⣿⣿⡿⢠⣿⣿⣷ ⢿⣷⣄⠑⢶⣶⣶⣶⣶⣶⣤⣤⣤⣤⣤⣤\n⠠⠁    ⠚⠛⠿⠿⢿⣿⣿⣿⡇⢸⣿⣿⣿⣧⠈⢿⣿⣿⣦⣌⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⡄ ⢰⣶\n⡆              ⣼⣿⣿⣿⣿⣇⠘⣿⣿⣿⣿⣿⣦⣈⠛⢿⣿⣿⣿⣿⣿⡇\n⢁        ⣀ ⠄⠂⠁⢀⣿⣿⣿⣿⣿⣿⡆⠹⣿⣿⣿⣿⣿⣿⠗ ⠈⠛⢿⣿⣿⡇\n ⠁⠂  ⠒⠒⠈⠁     ⢸⣿⣿⣿⠿⠛⠉  ⢻⣿⣿⣿⣿⠟     ⠈⠙⠃\n              ⠼⠛⠉⠁      ⢻⣿⣿⠏  ⢀⣄\n                    ⠿⠇  ⠈⠿⠁   ⠘⠋\n"
  },
  {
    "path": "src/logo/ascii/prismlinux_small.txt",
    "content": "         ⢠⣦⡀⠘⠁⢀⣴⣾ ⢀⡄\n       ⠛ ⣸⣿⣿⣄⢰⣿⣿⣿⡄⢀⣀⣀⣀⡀⢾⠆⠐⠂⢄\n    ⠰⣤⣄⡀ ⢿⣿⣿⣿⣧⡹⣿⣿⡇⣿⣿⣿⡿⠁\n     ⢻⣿⣿⣿⣶⣶⣭⣝⡻⠷⣌⢿⡇⢸⣿⡿⢁⣀\n  ⠘⠛ ⠸⠿⠟⣛⣋⡭⠅     ⠻⢸⡿⢡⣿⣿⣿⣶⣤⠁\n  ⠠⣤⣶⣶⣿⣿⡿⢋        ⠸⣡⣿⣿⡿⠟⠉\n ⢠ ⠈⠻⣿⡿⢋⣴⡟        ⢰⠟⣋⣥⣾⣦⡀ ⠁\n ⠈⠠⠊ ⢈⣴⣿⣿⣇⣧⢀    ⢀⣠⣴⣿⣿⣿⣿⣿⣷⡄\n    ⣰⣿⣿⣿⣿⢹⣿⣆⢳⣦⣐⠶⣶⣦⣤⣬⣭⣭⠉⢉⠉⠉\n⡀    ⠈⠉⠉⢉⢸⣿⣿⣆⢻⣿⣷⣮⡙⢿⣿⣿⣿ ⠉\n  ⣀⣀ ⠄⠐⠈ ⣾⣿⣿⠿ ⣿⣿⣿⡿ ⠉⠻⢿\n         ⠛⠉ ⢀⡀⠘⣿⠟ ⢠⡀\n            ⠈⠁ ⠁\n"
  },
  {
    "path": "src/logo/ascii/proxmox.txt",
    "content": "$1         .://:`              `://:.\n       `hMMMMMMd/          /dMMMMMMh`\n        `sMMMMMMMd:      :mMMMMMMMs`\n$2`-/+oo+/:$1`.yMMMMMMMh-  -hMMMMMMMy.`$2:/+oo+/-`\n`:oooooooo/$1`-hMMMMMMMyyMMMMMMMh-`$2/oooooooo:`\n  `/oooooooo:$1`:mMMMMMMMMMMMMm:`$2:oooooooo/`\n    ./ooooooo+-$1 +NMMMMMMMMN+ $2-+ooooooo/.\n      .+ooooooo+-$1`oNMMMMNo`$2-+ooooooo+.\n        -+ooooooo/.$1`sMMs`$2./ooooooo+-\n          :oooooooo/$1`..`$2/oooooooo:\n          :oooooooo/`$1..$2`/oooooooo:\n        -+ooooooo/.`$1sMMs$2`./ooooooo+-\n      .+ooooooo+-`$1oNMMMMNo$2`-+ooooooo+.\n    ./ooooooo+-$1 +NMMMMMMMMN+ $2-+ooooooo/.\n  `/oooooooo:`$1:mMMMMMMMMMMMMm:$2`:oooooooo/`\n`:oooooooo/`$1-hMMMMMMMyyMMMMMMMh-$2`/oooooooo:`\n`-/+oo+/:`$1.yMMMMMMMh-  -hMMMMMMMy.$2`:/+oo+/-`\n$1        `sMMMMMMMm:      :dMMMMMMMs`\n       `hMMMMMMd/          /dMMMMMMh`\n         `://:`              `://:`"
  },
  {
    "path": "src/logo/ascii/puffos.txt",
    "content": "              _,..._,m,\n            ,/'      '\"\";\n           /             \".\n         ,'mmmMMMMmm.      \\\n       _/-\"^^^^^\"\"\"%#%mm,   ;\n ,m,_,'              \"###)  ;,\n(###%                 \\#/  ;##mm.\n ^#/  __        ___    ;  (######)\n  ;  //.\\\\     //.\\\\   ;   \\####/\n _; (#\\\"//     \\\\\"/#)  ;  ,/\n@##\\ \\##/   =   `\"=\" ,;mm/\n`\\##>.____,...,____,<####@"
  },
  {
    "path": "src/logo/ascii/puppy.txt",
    "content": "           `-/osyyyysosyhhhhhyys+-\n  -ohmNNmh+/hMMMMMMMMNNNNd+dMMMMNM+\n yMMMMNNmmddo/NMMMNNNNNNNNNo+NNNNNy\n.NNNNNNmmmddds:MMNNNNNNNNNNNh:mNNN/\n-NNNdyyyhdmmmd`dNNNNNmmmmNNmdd/os/\n.Nm+shddyooo+/smNNNNmmmmNh.   :mmd.\n NNNNy:`   ./hmmmmmmmNNNN:     hNMh\n NMN-    -++- +NNNNNNNNNNm+..-sMMMM-\n.MMo    oNNNNo hNNNNNNNNmhdNNNMMMMM+\n.MMs    /NNNN/ dNmhs+:-`  yMMMMMMMM+\n mMM+     .. `sNN+.      hMMMMhhMMM-\n +MMMmo:...:sNMMMMMms:` hMMMMm.hMMy\n  yMMMMMMMMMMMNdMMMMMM::/+o+//dMMd`\n   sMMMMMMMMMMN+:oyyo:sMMMNNMMMNy`\n    :mMMMMMMMMMMMmddNMMMMMMMMmh/\n      /dMMMMMMMMMMMMMMMMMMNdy/`\n        .+hNMMMMMMMMMNmdhs/.\n            .:/+ooo+/:-."
  },
  {
    "path": "src/logo/ascii/pureos.txt",
    "content": "dmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmd\ndNm//////////////////////////////////mNd\ndNd                                  dNd\ndNd                                  dNd\ndNd                                  dNd\ndNd                                  dNd\ndNd                                  dNd\ndNd                                  dNd\ndNd                                  dNd\ndNd                                  dNd\ndNm//////////////////////////////////mNd\ndmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmd"
  },
  {
    "path": "src/logo/ascii/pureos_small.txt",
    "content": " _____________\n|  _________  |\n| |         | |\n| |         | |\n| |_________| |\n|_____________|"
  },
  {
    "path": "src/logo/ascii/q4os.txt",
    "content": "           .:*****  :=====.\n        .:********  :========.\n      .***********  :===========.\n    .:************  :============-\n   .**************  :==============\n  :***************  :===============\n :**************.    :===============\n.*************:        .=============.\n*************.          .============:\n\n$1:############.          $2:==:\n$1:##############.      $2:======:\n $1:################  $2.==========:\n  $1:###############   $2.===========:\n   $1:##############     $2.===========:\n    $1:#############       $2.=========:\n      $1:###########         $2.=====:\n        $1.#########           $2.=:\n            $1.#####"
  },
  {
    "path": "src/logo/ascii/qts.txt",
    "content": "   $1###########################-\n $1###############################\n$1=#########**************#########\n$1##########               ########\n$1##########               ########\n$1##########               ########\n$1##########               ########\n$1##########               ########\n$1##########          $2.    $1########\n$1##########           $2+=.   $1:#####\n$1+#########+=========-   $2%%%*   $1=#\n $1#####################*   $2%%%%#\n  $1-#####################*   $2%%%%%.\n                               $2```````"
  },
  {
    "path": "src/logo/ascii/qubes.txt",
    "content": "               `..--..`\n            `.----------.`\n        `..----------------..`\n     `.------------------------.``\n `..-------------....-------------..`\n.::----------..``    ``..----------:+:\n:////:----..`            `..---:/ossso\n:///////:`                  `/osssssso\n:///////:                    /ssssssso\n:///////:                    /ssssssso\n:///////:                    /ssssssso\n:///////:                    /ssssssso\n:///////:                    /ssssssso\n:////////-`                .:sssssssso\n:///////////-.`        `-/osssssssssso\n`//////////////:-```.:+ssssssssssssso-\n  .-://////////////sssssssssssssso/-`\n     `.:///////////sssssssssssssso:.\n         .-:///////ssssssssssssssssss/`\n            `.:////ssss+/+ssssssssssss.\n                `--//-    `-/osssso/."
  },
  {
    "path": "src/logo/ascii/qubyt.txt",
    "content": "$1    ########################$2($3ooo\n$1    ########################$2($3ooo\n$1###$2($3ooo                  $1###$2($3ooo\n$1###$2($3ooo                  $1###$2($3ooo\n$1###$2($3ooo                  $1###$2($3ooo\n$1###$2($3ooo                  $1###$2($3ooo\n$1###$2($3ooo                  $1###$2($3ooo\n$1###$2($3ooo                  $1###$2($3ooo\n$1###$2($3ooo           $1##$3o    $2(((($3ooo\n$1###$2($3ooo          o$2(($1###   $3oooooo\n$1###$2($3ooo           oo$2(($1###$3o\n$1###$2($3ooo             ooo$2(($1###\n$1################$2($3oo    oo$2(((($3o\n$2((((((((((((((((($3ooo     ooooo\n  oooooooooooooooooo        o"
  },
  {
    "path": "src/logo/ascii/quibian.txt",
    "content": "            `.--::::::::--.`\n        `.-:::-..``   ``..-::-.`\n      .::::-`   .$2+$1:``       `.-::.`\n    .::::.`    -::::::-`       `.::.\n  `-:::-`    -:::::::::--..``     .::`\n `::::-     .$2oy$1:::::::---.```.:    `::`\n -::::  `.-:::::::::::-.```         `::\n.::::.`-:::::::::::::.               `:.\n-::::.:::::::::::::::                 -:\n::::::::::::::::::::`                 `:\n:::::::::::::::::::-                  `:\n:::::::::::::::::::                   --\n.:::::::::::::::::`                  `:`\n`:::::::::::::::::                   -`\n .:::::::::::::::-                  -`\n  `::::::::::::::-                `.`\n    .::::::::::::-               ``\n      `.--:::::-."
  },
  {
    "path": "src/logo/ascii/quirinux.txt",
    "content": "$2          @=++++++++++=@\n$2       =++++++++++++++++++=\n$2     *++++++++++++++++++++++*\n$2   =++++++++++++++++++++++++++=\n$2  *++++++++$1-..........-$2++++++++*\n$2 =++++++++$1..............$2++++++++=\n$2@++++++++$1:.....$2:++$1:.....:$2++++++++@\n$2=++++++++$1:.....$2++++$1.....:$2++++++++=\n$2=++++++++$1:.....$2++++$1.....:$2++++++++=\n$2#++++++++$1:.....$2++++$1.....:$2++++++++#\n$2 +++++++++$1......$2--$1......$2+++++++++\n$2 @++++++++$1:............:$2++++++++@\n$2  @+++++++++++$1-....-$2+++++++++++@\n$2    *++++++++++$1::::$2++++++++++*\n$2      *++++++++++++++++++++*\n$2        @*++++++++++++++*@\n$2             @#====#@"
  },
  {
    "path": "src/logo/ascii/radix.txt",
    "content": "                .:oyhdmNo\n             `/yhyoosdms`\n            -o+/ohmmho-\n           ..`.:/:-`\n     `.--:::-.``$2\n  .+ydNMMMMMMNmhs:`\n`omMMMMMMMMMMMMMMNh-\noNMMMNmddhhyyhhhddmy.\nmMMMMNmmddhhysoo+/:-`\nyMMMMMMMMMMMMMMMMNNh.\n-dmmmmmNNMMMMMMMMMMs`\n -+oossyhmMMMMMMMMd-\n `sNMMMMMMMMMMMMMm:\n  `yMMMMMMNmdhhhh:\n   `sNMMMMMNmmho.\n    `+mMMMMMMMy.\n      .yNMMMm+`\n       `:yd+."
  },
  {
    "path": "src/logo/ascii/raspbian.txt",
    "content": "   $2`.::///+:/-.        --///+//-:`\n `+oooooooooooo:   `+oooooooooooo:\n  /oooo++//ooooo:  ooooo+//+ooooo.\n  `+ooooooo:-:oo-  +o+::/ooooooo:\n   `:oooooooo+``    `.oooooooo+-\n     `:++ooo/.        :+ooo+/.`$1\n        ...`  `.----.` ``..\n     .::::-``:::::::::.`-:::-`\n    -:::-`   .:::::::-`  `-:::-\n   `::.  `.--.`  `` `.---.``.::`\n       .::::::::`  -::::::::` `\n .::` .:::::::::- `::::::::::``::.\n-:::` ::::::::::.  ::::::::::.`:::-\n::::  -::::::::.   `-::::::::  ::::\n-::-   .-:::-.``....``.-::-.   -::-\n .. ``       .::::::::.     `..`..\n   -:::-`   -::::::::::`  .:::::`\n   :::::::` -::::::::::` :::::::.\n   .:::::::  -::::::::. ::::::::\n    `-:::::`   ..--.`   ::::::.\n      `...`  `...--..`  `...`\n            .::::::::::\n             `.-::::-`"
  },
  {
    "path": "src/logo/ascii/raspbian_small.txt",
    "content": "   $2.~~.   .~~.\n  '. \\ ' ' / .'$1\n   .~ .~~~..~.\n  : .~.'~'.~. :\n ~ (   ) (   ) ~\n( : '~'.~.'~' : )\n ~ .~ (   ) ~. ~\n  (  : '~' :  )\n   '~ .~~~. ~'\n       '~'"
  },
  {
    "path": "src/logo/ascii/ravynos.txt",
    "content": "                ..oooo..\n           .o$$$$$$$$$$$$$$$$$$$$$$$$$$$$o.\n        od$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o\n      o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o\n    .$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$.\n   d$$$$$$$$$$$$$$$$$********$$$$$$$$$$$$$$$$$$$$$$$$$$$$$b\n  d$$$$$$$$$$$$$*            °****?$$$$$$$$$$$$$$$$b\n  $$$$$$$$$$$$*                     °$$$$$$$$$$$$$\n d$$$$**                     .oo$$$$$$$$$$$$$$$$b\n *°                     o$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n                      o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n                    o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$P\n                    *$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n                      ?$$$$$$$$$$$$$$$$$$$$$$$$$$$$P\n                       $$$$$$$$$$$$$$$$$$$$$$$$$P\n                       $$$$$$$$$$$$$$$$$$$$$$$$P\n                      ?$$$$$$$$$$$$$$$$$$$$*\n                       $$$$$$$$$$$$$*°\n                      d$$$$$$$$*°\n                       °"
  },
  {
    "path": "src/logo/ascii/rebornos.txt",
    "content": "          $1.======================.\n         $1.#$2#*********$1%%$2*********#$1%:\n        $1:%$2#**********$1%%$2**********#$1%-\n       $1-%$2************$1%%$2************$1%=\n      $1+%$2******$1%%#####$1%%#####%%$2******$1%+\n     $1*%%#$2****$1%#$3+=====$1%%$3=====+$1#%$2****$1#%%*\n    $1*%$2*#$1#%%#%#$3====+++$1%%$3+++====$1#%#%%#$2#*$1##.\n  $1.##$2*****$1#%%%#$3*++$1%######%$3*+*$1#%%%#$2*****$1#%.\n $1:%#$2*****$1#%$3*=+*$1#%%$3*++++++*$1%%#$3*+=*$1%#$2*****$1#%:\n$1-%#$2*****$1#%$3+====*$1%$3*++++++++*$1%#$3====+$1%#$2******$1%-\n$1-%#$2*****$1#%$3+====*$1%$3*++++++++*$1%#$3====+$1%#$2******$1%=\n $1:%#$2*****$1#%$3*=+*$1#%%$3*++++++*$1%%#$3*+=*$1%#$2*****$1#%-\n  $1.##$2*****$1#%%%#$3*+*$1%######%$3*+*$1#%%%#$2*****$1#%:\n   $1.##$2**$1#%%#%#$3====+++$1%%$3+++====$1#%#%%#$2#*$1##.\n     $1*%%#$2****$1%#$3+=====$1%%$3=====+$1#%$2****$1#%%*\n      $1+%$2******$1%%#####%%#####%%$2******$1%*\n       $1-%$2************$1%%$2************$1%=\n        $1:%$2#**********$1%%$2**********#$1%-\n         $1:%$2#*********$1%%$2*********#$1%:\n          $1.======================."
  },
  {
    "path": "src/logo/ascii/rebornos_small.txt",
    "content": "   _______\n  /\\_____/\\\n / /\\___/\\ \\\n/_/_/   \\_\\_\\\n\\ \\ \\___/ / /\n \\ \\/___\\/ /\n  \\/_____\\/"
  },
  {
    "path": "src/logo/ascii/redcore.txt",
    "content": "                 RRRRRRRRR\n               RRRRRRRRRRRRR\n        RRRRRRRRRR      RRRRR\n   RRRRRRRRRRRRRRRRRRRRRRRRRRR\n RRRRRRR  RRR         RRR RRRRRRRR\nRRRRR    RR                 RRRRRRRRR\nRRRR    RR     RRRRRRRR      RR RRRRRR\nRRRR   R    RRRRRRRRRRRRRR   RR   RRRRR\nRRRR   R  RRRRRRRRRRRRRRRRRR  R   RRRRR\nRRRR     RRRRRRRRRRRRRRRRRRR  R   RRRR\n RRR     RRRRRRRRRRRRRRRRRRRR R   RRRR\n  RRR    RRRRRRRRRRRRRRRRRRRR    RRRR\n    RR   RRRRRRRRRRRRRRRRRRR    RRR\n     RR   RRRRRRRRRRRRRRRRR    RRR\n       RR   RRRRRRRRRRRRRR   RR\n         R       RRRR      RR"
  },
  {
    "path": "src/logo/ascii/redos.txt",
    "content": "╭───────────────╮ ╭───────────────╮\n│###############│ │###############│\n│#+++++++++++++#│ │#+++++++++++++#│\n│#+++++++++++++#│ │#+++++++++++++#│\n│#+++++++++++++#│ │#+++++++++++++#│\n│#+++++++++++++#│ │#+++++++++++++#│\n│#+++++++++++++#│ │#+++++++++++++#│\n│###############│ │###############│\n╰───────────────╯ ╰───────────────╯\n╭───────────────╮                  \n│###############│                  \n│#+++++++++++++#│                  \n│#+++++++++++++#│                  \n│#+++++++++++++#│                  \n│#+++++++++++++#│                  \n│#+++++++++++++#│                  \n│###############│                  \n╰───────────────╯                  "
  },
  {
    "path": "src/logo/ascii/redos_small.txt",
    "content": "╭─────╮ ╭─────╮\n│     │ │     │\n│     │ │     │\n╰─────╯ ╰─────╯\n╭─────╮        \n│     │        \n│     │        \n╰─────╯        "
  },
  {
    "path": "src/logo/ascii/redstar.txt",
    "content": "                    ..\n                  .oK0l\n                 :0KKKKd.\n               .xKO0KKKKd\n              ,Od' .d0000l\n             .c;.   .'''...           ..'.\n.,:cloddxxxkkkkOOOOkkkkkkkkxxxxxxxxxkkkx:\n;kOOOOOOOkxOkc'...',;;;;,,,'',;;:cllc:,.\n .okkkkd,.lko  .......',;:cllc:;,,'''''.\n   .cdo. :xd' cd:.  ..';'',,,'',,;;;,'.\n      . .ddl.;doooc'..;oc;'..';::;,'.\n        coo;.oooolllllllcccc:'.  .\n       .ool''lllllccccccc:::::;.\n       ;lll. .':cccc:::::::;;;;'\n       :lcc:'',..';::::;;;;;;;,,.\n       :cccc::::;...';;;;;,,,,,,.\n       ,::::::;;;,'.  ..',,,,'''.\n        ........          ......"
  },
  {
    "path": "src/logo/ascii/refracta.txt",
    "content": "                            A\n                           VW\n                          VVW\\\n                        .yWWW\\\n,;,,u,;yy;;v;uyyyyyyy  ,WWWWW^\n   *WWWWWWWWWWWWWWWW/  $VWWWWw      ,\n       ^*%WWWWWWVWWX  $WWWW**    ,yy\n       ,    \"**WWW/' **'   ,yy/WWW*`\n      &WWWWwy    `*`  <,ywWW%VWWW*\n    yWWWWWWWWWW*    .,   \"**WW%W\n  ,&WWWWWM*\"`  ,y/  &WWWww   ^*\n XWWX*^   ,yWWWW09 .WWWWWWWWwy,\n*`        &WWWWWM  WWWWWWWWWWWWWww,\n          (WWWWW` /#####WWW***********\n          ^WWWW\n           VWW\n           Wh.\n           V/"
  },
  {
    "path": "src/logo/ascii/regata.txt",
    "content": "            ddhso+++++osydd\n        dho/.`hh$2.:/+/:.$1hhh`:+yd\n      do-hhhhhh$2/sssssss+`$1hhhhh./yd\n    h/`hhhhhhh$2-sssssssss:$1hhhhhhhh-yd\n  do`hhhhhhhhh$2`ossssssso.$1hhhhhhhhhh/d\n d/hhhhhhhhhhhh$2`/ossso/.$1hhhhhhhhhhhh.h\n /hhhhhhhhhhhh$3`-/osyso/-`$1hhhhhhhhhhhh.h\nshh$4-/ooo+-$1hhh$3:syyso+osyys/`$1hhh$5`+oo`$1hhh/\nh$4`ohhhhhhho`$3+yyo.$1hhhhh$3.+yyo`$5.sssssss.$1h`h\ns$4:hhhhhhhhho$3yys`$1hhhhhhh$3.oyy/$5ossssssso-$1hs\ns$4.yhhhhhhhy/$3yys`$1hhhhhhh$3.oyy/$5ossssssso-$1hs\nhh$4./syyys+.$1 $3+yy+.$1hhhhh$3.+yyo`$5.ossssso/$1h`h\nshhh$4``.`$1hhh$3`/syyso++oyys/`$1hhh$5`+++-`$1hh:h\nd/hhhhhhhhhhhh$3`-/osyso+-`$1hhhhhhhhhhhh.h\n d/hhhhhhhhhhhh$6`/ossso/.$1hhhhhhhhhhhh.h\n  do`hhhhhhhhh$6`ossssssso.$1hhhhhhhhhh:h\n    h/`hhhhhhh$6-sssssssss:$1hhhhhhhh-yd\n      h+.hhhhhh$6+sssssss+$1hhhhhh`/yd\n        dho:.hhh$6.:+++/.$1hhh`-+yd\n            ddhso+++++osyhd"
  },
  {
    "path": "src/logo/ascii/regolith.txt",
    "content": "                 ``....```\n            `.:/++++++/::-.`\n          -/+++++++:.`\n        -++++++++:`\n      `/++++++++-\n     `/++++++++.                    -/+/\n     /++++++++/             ``   .:+++:.\n    -+++++++++/          ./++++:+++/-`\n    :+++++++++/         `+++++++/-`\n    :++++++++++`      .-/+++++++`\n   `:++++++++++/``.-/++++:-:::-`      `\n `:+++++++++++++++++/:.`            ./`\n:++/-:+++++++++/:-..              -/+.\n+++++++++/::-...:/+++/-..````..-/+++.\n`......``.::/+++++++++++++++++++++/.\n         -/+++++++++++++++++++++/.\n           .:/+++++++++++++++/-`\n              `.-:://////:-."
  },
  {
    "path": "src/logo/ascii/rengeos.txt",
    "content": "                                    ..\n                                  .:~!.\n                      ...::^~!!7?J?:\n            ..::^^~~!!!7?JYYYYYYY?.\n           .::::..::^!7?JJYYYJYY!\n:~~~:..      .:~!?JJYYYYYYYJ!?J^\n  :!?JJ?7!!7?JYYYYYYYYYYYJ~.~?:\n     :~?YYYYYYYYYYYYYYY?~. ^7.\n        .~?YYYYYYYYYY?^   .~\n   ..:~!7?JYYYYYYYY?^\n.^~!7777777JYYYYY7:\n          ^YYYJ7:\n         :YYJ!:\n        :JJ!.\n       .7~.\n       .."
  },
  {
    "path": "src/logo/ascii/rhaymos.txt",
    "content": "                    ###\n                   #####\n\n              #######        /########\n           #############   ###########\n   ,###########      ####  ####(..\n  ####   ####       ####*  ##########\n  ####    #####     #####         (####\n  ####      ###########    ###########\n  ####       #########     ##########\n\n  ###################################\n #####################################\n#######################################"
  },
  {
    "path": "src/logo/ascii/rhel.txt",
    "content": "           .MMM..:MMMMMMM\n          MMMMMMMMMMMMMMMMMM\n          MMMMMMMMMMMMMMMMMMMM.\n         MMMMMMMMMMMMMMMMMMMMMM\n        ,MMMMMMMMMMMMMMMMMMMMMM:\n        MMMMMMMMMMMMMMMMMMMMMMMM\n  .MMMM'  MMMMMMMMMMMMMMMMMMMMMM\n MMMMMM    `MMMMMMMMMMMMMMMMMMMM.\nMMMMMMMM      MMMMMMMMMMMMMMMMMM .\nMMMMMMMMM.       `MMMMMMMMMMMMM' MM.\nMMMMMMMMMMM.                     MMMM\n`MMMMMMMMMMMMM.                 ,MMMMM.\n `MMMMMMMMMMMMMMMMM.          ,MMMMMMMM.\n    MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n      MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM:\n         MMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n            `MMMMMMMMMMMMMMMMMMMMMMMM:\n                ``MMMMMMMMMMMMMMMMM'"
  },
  {
    "path": "src/logo/ascii/rhel_old.txt",
    "content": "             `.-..........`\n            `////////::.`-/.\n            -: ....-////////.\n            //:-::///////////`\n     `--::: `-://////////////:\n     //////-    ``.-:///////// .`\n     `://////:-.`    :///////::///:`\n       .-/////////:---/////////////:\n          .-://////////////////////.\n$2         yMN+`.-$1::///////////////-`\n$2      .-`:NMMNMs`  `..-------..`\n       MN+/mMMMMMhoooyysshsss\nMMM    MMMMMMMMMMMMMMyyddMMM+\n MMMM   MMMMMMMMMMMMMNdyNMMh`     hyhMMM\n  MMMMMMMMMMMMMMMMyoNNNMMM+.   MMMMMMMM\n   MMNMMMNNMMMMMNM+ mhsMNyyyyMNMMMMsMM"
  },
  {
    "path": "src/logo/ascii/rhel_small.txt",
    "content": "      .M.:MMM\n     MMMMMMMMMM.\n    ,MMMMMMMMMMM\n .MM MMMMMMMMMMM\nMMMM   MMMMMMMMM\nMMMMMM           MM\n MMMMMMMMM     ,MMMM\n   MMMMMMMMMMMMMMMM:\n      `MMMMMMMMMMMM"
  },
  {
    "path": "src/logo/ascii/rhino.txt",
    "content": "$1       .;:;,.  .:\n$1    'coooooooo:oo.';.\n$1  ,oooooooooooooooo    ;\n$1 clllcccllloooooooo;c:'o\n$1.$4;$3';:::::::::$1cclooooooo'\n$4''',$3::::::::::::::$1ccclc.\n$4.'''$3;::::::::::$2l$3:::::::\n$4 ''''$3,:::::::::$2kd$3.\n$4 .'''''$3,;::$2ck:$2oW$3;\n$4   ''''''''$2kXOM.\n$4     .,,:$2dXMK\n$4       $2:k\n"
  },
  {
    "path": "src/logo/ascii/rocky.txt",
    "content": "          __wgliliiligw_,\n       _williiiiiiliilililw,\n     _%iiiiiilililiiiiiiiiiii_\n   .Qliiiililiiiiiiililililiilm.\n  _iiiiiliiiiiililiiiiiiiiiiliil,\n .lililiiilililiiiilililililiiiii,\n_liiiiiiliiiiiiiliiiiiF{iiiiiilili,\njliililiiilililiiili@`  ~ililiiiiiL\niiiliiiiliiiiiiili>`      ~liililii\nliliiiliiilililii`         -9liiiil\niiiiiliiliiiiii~             \"4lili\n4ililiiiiilil~|      -w,       )4lf\n-liiiiililiF'       _liig,       )'\n )iiiliii@`       _QIililig,\n  )iiii>`       .Qliliiiililw\n   )<>~       .mliiiiiliiiiiil,\n            _gllilililiililii~\n           giliiiiiiiiiiiiT`\n          -^~$ililili@~~'"
  },
  {
    "path": "src/logo/ascii/rocky_small.txt",
    "content": "    `-/+++++++++/-.`\n `-+++++++++++++++++-`\n.+++++++++++++++++++++.\n-+++++++++++++++++++++++.\n+++++++++++++++/-/+++++++\n+++++++++++++/.   ./+++++\n+++++++++++:.       ./+++\n+++++++++:`   `:/:`   .:/\n-++++++:`   .:+++++:`\n .+++-`   ./+++++++++:`\n  `-`   ./+++++++++++-\n       -+++++++++:-.`"
  },
  {
    "path": "src/logo/ascii/rosa.txt",
    "content": "$2            ⢀⣀⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣄\n$2            ⠈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇\n$1     ⢀⣤⣄                       $2⣿⣿⣿⡇\n$1   ⢀⣴⣿⣿⣿⠗                      $2⣿⣿⣿⡇\n$1  ⢠⣾⣿⣿⡿⠁                       $2⣿⣿⣿⡇\n$1 ⢠⣿⣿⣿⡟                         $2⣿⣿⣿⡇\n$1⢀⣿⣿⣿⡿                          $2⣿⣿⣿⡇\n$1⢸⣿⣿⣿⠃                          $2⣿⣿⣿⡇\n$1⢸⣿⣿⣿                           $2⣿⣿⣿⡇\n$1⢸⣿⣿⣿                           $2⣿⣿⣿⡇\n$1⢸⣿⣿⣿⡄                          $2⠙⢿⡿\n$1⠘⣿⣿⣿⣷\n ⠘⣿⣿⣿⣦\n  ⠙⣿⣿⣿⣷⡀                   ⢀⡀\n   ⠘⢿⣿⣿⣿⣦⣄               ⣠⣴⣿⣿⡦\n     ⠙⢿⣿⣿⣿⣷⣦⣀⣀⡀     ⢀⣀⣠⣴⣾⣿⣿⣿⠟⠁\n       ⠈⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠋⠁\n           ⠈⠙⠛⠿⠿⠿⠿⠿⠿⠟⠛⠋⠁\n"
  },
  {
    "path": "src/logo/ascii/sabayon.txt",
    "content": "            ...........\n         ..             ..\n      ..                   ..\n    ..           $2o           $1..\n  ..            $2:W'            $1..\n ..             $2.d.             $1..\n:.             $2.KNO              $1.:\n:.             $2cNNN.             $1.:\n:              $2dXXX,              $1:\n:   $2.          dXXX,       .cd,   $1:\n:   $2'kc ..     dKKK.    ,ll;:'    $1:\n:     $2.xkkxc;..dkkkc',cxkkl       $1:\n:.     $2.,cdddddddddddddo:.       $1.:\n ..         $2:lllllll:           $1..\n   ..         $2',,,,,          $1..\n     ..                     ..\n        ..               ..\n          ..............."
  },
  {
    "path": "src/logo/ascii/sabotage.txt",
    "content": " .|'''.|      |     '||''|.    ..|''||\n ||..  '     |||     ||   ||  .|'    ||\n  ''|||.    |  ||    ||'''|.  ||      ||\n.     '||  .''''|.   ||    || '|.     ||\n|'....|'  .|.  .||. .||...|'   ''|...|'\n\n|''||''|     |      ..|'''.|  '||''''|\n   ||       |||    .|'     '   ||  .\n   ||      |  ||   ||    ....  ||''|\n   ||     .''''|.  '|.    ||   ||\n  .||.   .|.  .||.  ''|...'|  .||.....|"
  },
  {
    "path": "src/logo/ascii/sailfish.txt",
    "content": "              _a@b\n           _#b (b\n         _@@   @_         _,\n       _#^@ _#*^^*gg,aa@^^\n       #- @@^  _a@^^\n       @_  *g#b\n       ^@_   ^@_\n         ^@_   @\n          @(b (b\n         #b(b#^\n       _@_#@^\n    _a@a*^\n,a@*^"
  },
  {
    "path": "src/logo/ascii/salentos.txt",
    "content": "                 ``..``\n        .-:+oshdNMMMMMMNdhyo+:-.`\n  -oydmMMMMMMMMMMMMMMMMMMMMMMMMMMNdhs/\n$4 +hdddm$1NMMMMMMMMMMMMMMMMMMMMMMMMN$4mdddh+`\n$2`MMMMMN$4mdddddm$1MMMMMMMMMMMM$4mdddddm$3NMMMMM-\n$2 mMMMMMMMMMMMN$4ddddhyyhhddd$3NMMMMMMMMMMMM`\n$2 dMMMMMMMMMMMMMMMMM$4oo$3MMMMMMMMMMMMMMMMMN`\n$2 yMMMMMMMMMMMMMMMMM$4hh$3MMMMMMMMMMMMMMMMMd\n$2 +MMMMMMMMMMMMMMMMM$4hh$3MMMMMMMMMMMMMMMMMy\n$2 :MMMMMMMMMMMMMMMMM$4hh$3MMMMMMMMMMMMMMMMMo\n$2 .MMMMMMMMMMMMMMMMM$4hh$3MMMMMMMMMMMMMMMMM/\n$2 `NMMMMMMMMMMMMMMMM$4hh$3MMMMMMMMMMMMMMMMM-\n$2  mMMMMMMMMMMMMMMMM$4hh$3MMMMMMMMMMMMMMMMN`\n$2  hMMMMMMMMMMMMMMMM$4hh$3MMMMMMMMMMMMMMMMm\n$2  /MMMMMMMMMMMMMMMM$4hh$3MMMMMMMMMMMMMMMMy\n$2   .+hMMMMMMMMMMMMM$4hh$3MMMMMMMMMMMMMms:\n$2      `:smMMMMMMMMM$4hh$3MMMMMMMMMNh+.\n$2          .+hMMMMMM$4hh$3MMMMMMdo:\n$2             `:smMM$4yy$3MMNy/`\n                 $2.- $4`$3:."
  },
  {
    "path": "src/logo/ascii/salientos.txt",
    "content": "              00xxxx0\n          00xxxxxx0\n       0xxxxxxxxx         000000\n     0xxxxxxxxxx        xxxxxxxxxx0\n   0xxxxxxxxxxx0       xxxxxxxxxxxxx0\n  0xxxxxxxxxxxx0      0xxxxxxxxxxxxxx0\n 0xxxxxxxxxxxxx0      0xxxxxxxxxxxxxxx0\n0xxxxxxxxxxxxxxx       xxxxxxxxxxxxxxxx0\nxxxxxxxxxxxxxxxx0      0xxxxxxxxxxxxxxxx\nxxxxxxxxxxxxxxxxx       0xxxxxxxxxxxxxxx\nxxxxxxxxxxxxxxxxxx       xxxxxxxxxxxxxxx\nxxxxxxxxxxxxxxxxxx0       xxxxxxxxxxxxxx\n0xxxxxxxxxxxxxxxxxx0      0xxxxxxxxxxxx0\n 0xxxxxxxxxxxxxxxxxx       xxxxxxxxxxx0\n  0xxxxxxxxxxxxxxxxx       xxxxxxxxxx0\n   0xxxxxxxxxxxxxxxx       xxxxxxxxx0\n     0xxxxxxxxxxxx0       0xxxxxxx0\n        0xxxxxxx0         xxxxxx0\n                        0xxx00\n                      x00"
  },
  {
    "path": "src/logo/ascii/salix.txt",
    "content": "              __s_aaaaaaaaauuoXSSSSSSSS:\n          ._xSSSSSSSSSSSSSSSSSSSSSSSSSS:\n        _aSSSSSSSSSSSSSSSSSSSSSSSSSSSSS:\n      _xSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS:\n     <XSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS:\n    -\"^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^'\n  .ssssssssssssssssssssssssssssssssssss\n  {SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSl\n  oSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS;\n :XSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS;\n {SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS\n -\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"'\n <assssssssssssssssssssssssssssssss>\n nSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS}\n nSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS}`\n XSSSSSSSSSSSSSSSSSSSSSSSSSSSS\"`\n SSSSSSSSSSSSSSSSSSSSSSSSS!\"`\n-\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"`"
  },
  {
    "path": "src/logo/ascii/sambabox.txt",
    "content": "                    #\n               *////#####\n           /////////#########(\n      .((((((/////    ,####(#(((((\n  /#######(((*             (#(((((((((.\n//((#(#(#,        ((##(        ,((((((//\n//////        #(##########(       //////\n//////    ((#(#(#(#(##########(/////////\n/////(    (((((((#########(##((((((/////\n/(((#(                             ((((/\n####(#                             ((###\n#########(((/////////(((((((((,    (#(#(\n########(   /////////(((((((*      #####\n####///,        *////(((         (((((((\n.///////////                .//(((((((((\n     ///////////,       *(/////((((*\n         ,/(((((((((##########/.\n             .((((((#######\n                  ((##*"
  },
  {
    "path": "src/logo/ascii/sasanqua.txt",
    "content": "                     __,_\n             _╕⌐≡µ,√*    º≡,\n            ñ     \"'       ░\n           ╞)         _,   ▒     __\n     _,,,_ _Ñ╜^≡µ   ≡'  1µ╕º^el    \"%µ\n     ∩'     K      Yµ&          1l     ╞)\n  ▒     √\"        ^Ü          1\"     `1µ\n  Γ    ║h                  _¿▒∞√;,     ^≡,\n   K    ^u_              ⌐*      ╙¥     ╓Ñ\n  ⌠        º≡u,,                  ║I    Å\n  Ü     _∩\"                       ║µ_¿╝\"\n   Yu_ ▒               ╙º≡_       ║l1µ\n      ║l          ,        Y∞µ___≡ª  Γl\n       ╓hⁿ╖I     1l         Ñ        ╓Ñ\n       Ñ   ¥,___≡1l        ╓Ñ      ¿╕ª\n       Ü         ╙L     ¿¿∩ª     ╓P\n        ª≡,__      *ⁿ┤ⁿÑⁿ^µ     √ª\n              ⁿ≡,,__√╝*    \"ⁿⁿ*\""
  },
  {
    "path": "src/logo/ascii/scientific.txt",
    "content": "                 =/;;/-\n                +:    //\n               /;      /;\n              -X        H.\n.//;;;:;;-,   X=        :+   .-;:=;:;#;.\nM-       ,=;;;#:,      ,:#;;:=,       ,@\n:#           :#.=/++++/=.$=           #=\n ,#;         #/:+/;,,/++:+/         ;+.\n   ,+/.    ,;@+,        ,#H;,    ,/+,\n      ;+;;/= @.  $3.H$2#$3#X   $1-X :///+;\n      ;+=;;;.@,  $2.X$3M$2@$.  $1=X.//;=#/.\n   ,;:      :@#=        =$H:     .+#-\n ,#=         #;-///==///-//         =#,\n;+           :#-;;;:;;;;-X-           +:\n@-      .-;;;;M-        =M/;;;-.      -X\n :;;::;;-.    #-        :+    ,-;;-;:==\n              ,X        H.\n               ;/      #=\n                //    +;\n                 '////'"
  },
  {
    "path": "src/logo/ascii/secureblue.txt",
    "content": "              ==++++++++++\n         :========++++++++++++:\n       ===============+++++++++++\n     ====================++++++++++\n   :=============$3#%@@@%$1=====++++++++-\n  -============$3%@%$1====$3%@@$1========+++++\n -============$3%@#$1======$3@@$1==========+++-\n.=============$3%@+$1======$3@@$1==============.\n$2--$1=========$3+@@@@@@@@@@@@@@@%+$1==========-\n$2------$1=====$3%@@@@@@@@@@@@@@@@*$1===========\n$2---------$1==$3%@@@@@@@%%@@@@@@@*$1===========\n$2:----------$3%@@@@@#$1===$3+%@@@@@*$1==========-\n $2----------$3%@@@@@%$1===$3*@@@@@@*$1==========.\n $2:---------$3%@@@@@@@@@@@@@@@@*$1=========-\n  $2:--------$3%@@@@@@@@@@@@@@@@*$1========-\n   $2:--------$3+##############+$1========:\n     $2-------------------------$1====-\n       $2--------------------------\n         .--------------------.\n              ------------\n"
  },
  {
    "path": "src/logo/ascii/semc.txt",
    "content": "            /\\\n     ______/  \\\n    /      |()| $2E M C\n$1   |   (-- |  |\n    \\   \\  |  |\n.----)   | |__|\n|_______/ / $3\"$1  \\\n              $3\"\n            \""
  },
  {
    "path": "src/logo/ascii/septor.txt",
    "content": "ssssssssssssssssssssssssssssssssssssssss\nssssssssssssssssssssssssssssssssssssssss\nssssssssssssssssssssssssssssssssssssssss\nssssssssssssssssssssssssssssssssssssssss\nssssssssss$2;okOOOOOOOOOOOOOOko;$1ssssssssss\nsssssssss$2oNWWWWWWWWWWWWWWWWWWNo$1sssssssss\nssssssss$2:WWWWWWWWWWWWWWWWWWWWWW:$1ssssssss\nssssssss$2lWWWWWk$1ssssssssss$2lddddd:$1ssssssss\nssssssss$2cWWWWWNKKKKKKKKKKKKOx:$1ssssssssss\n$3yy$1sssssss$2OWWWWWWWWWWWWWWWWWWWWx$1sssssss$3yy\nyyyyyyyyyy$2:kKNNNNNNNNNNNNWWWWWW:$3yyyyyyyy\nyyyyyyyy$2sccccc;$3yyyyyyyyyy$2kWWWWW:$3yyyyyyyy\nyyyyyyyy$2:WWWWWWNNNNNNNNNNWWWWWW;$3yyyyyyyy\nyyyyyyyy$2.dWWWWWWWWWWWWWWWWWWWNd$3yyyyyyyyy\nyyyyyyyyyy$2sdO0KKKKKKKKKKKK0Od;$3yyyyyyyyyy\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
  },
  {
    "path": "src/logo/ascii/serene.txt",
    "content": "              __---''''''---__\n          .                      .\n        :                          :\n      -                       _______----_-\n     s               __----'''     __----\n __h_            _-'           _-'     h\n '-._''--.._    ;           _-'         y\n  :  ''-._  '-._/        _-'             :\n  y       ':_       _--''                y\n  m    .--'' '-._.;'                     m\n  m   :        :                         m\n  y    '.._     '-__                     y\n  :        '--._    '''----___           :\n   y            '--._         ''-- _    y\n    h                '--._          :  h\n     s                  __';         vs\n      -         __..--''             -\n        :_..--''                   :\n          .                     _ .\n            `''---______---''-``"
  },
  {
    "path": "src/logo/ascii/serpent_os.txt",
    "content": "            ,dKNWWNKOxo;.\n          ;xKXOdoloOWMWX0d:.\n        .dNNd'     cNMMMMNKOx;.\n       .dWNo       .:oxO000KXKl.\n       .OMNl             .....\n        oNMXd,.\n        .lKWMNKkdollccc:::;;;,'..\n          .;ox0KNWWMMMMMMMMWWWWNK0xl,.\n               ..,;:ccloodxO0KNWMMMMNOc.\n                             .';lxKWMMWk'\n      ..,:ccc:,.                  .oXMMMO\n   .:d0NWWWWWMWXOo,.     .,cc:;,,'..oWMMW\n .c0WW0xl:;,;cd0NMNO:'..ldl;.    .'.:XMMM\n xWMKc.        .;OWMWX0xl.         .dWMMK\n WMWd.           .oKWMMXx,        .dNMMXc\n NMMK:.      ..:oxo:oOXMMNOdc:;;cxKWMWO;\n ;kXWN0xooodkOOko;.   .lONMMMMWWMMWKx;.\n   ,okKXXXKOxc'.        .:kKWMMWXOl'\n"
  },
  {
    "path": "src/logo/ascii/sharklinux.txt",
    "content": "                             `:shd/\n                         `:yNMMMMs\n                      `-smMMMMMMN.\n                    .+dNMMMMMMMMs\n                  .smNNMMMMMMMMm`\n                .sNNNNNNNMMMMMM/\n              `omNNNNNNNMMMMMMm\n             /dNNNNNNNNMMMMMMM+\n           .yNNNNNNNNNMMMMMMMN`\n          +mNNNNNNNNNMMMMMMMMh\n        .hNNNNNNNNNNMMMMMMMMMs\n       +mMNNNNNNNNMMMMMMMMMMMs\n     .hNMMNNNNMMMMMMMMMMMMMMMd\n   .oNNNNNNNNNNMMMMMMMMMMMMMMMo\n`:+syyssoo++++ooooossssssssssso:"
  },
  {
    "path": "src/logo/ascii/shastraos.txt",
    "content": "               ..,;;,'.\n            ':oo.     ;o:\n          :o,           ol\n        .oo        ..';co:\n        ooo',;:looo;\n    .;lddl\n  cx   .xl     .c:'\n dd     xx     xx ,d;\n.xd     cx.    xx   dd.\n cx:    .xo    xx    ,x:\n  'xl    xx    cx'    .xl\n    xd,  xx    .xd     dx.\n     .xo:xx     xx    .xx\n        'c      xx:.'lx:\n           ..,;cxxxo\n  .';:codxxl   lxo\ncd.           'xo\n:o,         'ld\n .oc'...';lo"
  },
  {
    "path": "src/logo/ascii/shebang.txt",
    "content": "                   '\n                  '#'\n                 '#!#'\n                '#!#!#'\n               '#!#!#!#'\n              '#!#!#!#!#'\n             '#!#!#!#!#!#'\n            '#!#!#!#!#!#!#'\n               #!#!#!#!#!#!'\n          '#!      #!#!#!#!#'\n         '#!#!#!#!     #!#!#!'\n        '#!#!#!#!#!#!#!   #!#!'\n       '#!#!#!#!#!#!#!#!#!#!  #'\n      '#!#!#!#!#!#!#!#!#!#!#!#!\n     '#!#!#!#!#!#!#!#!#!#!     #!'\n    '#!#!#!#!#!#!#!#!       #!#!#!'\n   '#!#!#!#!#!#!         #!#!#!#!#!'\n  '#!#!#!#!                #!#!#!#!#'\n '#!#!                          #!#!#'\n'#                                  #!'\n"
  },
  {
    "path": "src/logo/ascii/siduction.txt",
    "content": "                _aass,\n               jQh: =$w\n               QWmwawQW\n               )$QQQQ@(   ..\n         _a_a.   ~??^  syDY?Sa,\n       _mW>-<$c       jWmi  imm.\n       ]QQwayQE       4QQmgwmQQ`\n        ?WWQWP'       -9QQQQQ@'._aas,\n _a%is.        .adYYs,. -\"?!` aQB*~^3$c\n_Qh;.nm       .QWc. {QL      ]QQp;..vmQ/\n\"QQmmQ@       -QQQggmQP      ]QQWmggmQQ(\n -???\"         \"$WQQQY`  __,  ?QQQQQQW!\n        _yZ!?q,   -   .yWY!!Sw, \"???^\n       .QQa_=qQ       mQm>..vmm\n        $QQWQQP       $QQQgmQQ@\n         \"???\"   _aa, -9WWQQWY`\n               _mB>~)$a  -~~\n               mQms_vmQ.\n               ]WQQQQQP\n                -?T??\""
  },
  {
    "path": "src/logo/ascii/skiffos.txt",
    "content": "$2             ,@@@@@@@@@@@w,_\n  $2====~~~,,.$2A@@@@@@@@@@@@@@@@@W,_\n  $1`||||||||||||||L{$2\"@$@@@@@@@@B\"\n   $1`|||||||||||||||||||||L{$2\"$D\n     $2@@@@@@@@@@@@@@@@@@@@@$1_||||}==,\n      $2*@@@@@@@@@@@@@@@@@@@@@@@@@p$1||||==,\n        $1`'||LLL{{\"\"$2@$B@@@@@@@@@@@@@@@p$1||\n            $1`~=|||||||||||L\"$2$@@@@@@@@@@@\n                   $1````'\"\"\"\"\"\"\"$2'\"\"\"\"\"\"\"\""
  },
  {
    "path": "src/logo/ascii/slackel.txt",
    "content": "              _aawmmmmmwwaaaaas,,,_.\n           .ammmmm###mmmmmmm###BQmm##mws\n         .am###mmBmBmBmBmBmBmmmmm#mmmm#2\n        <q###mmBmBmBmBmBmBmBmBmBmBmmBmZ`\n       um#mmmBmBm##U##mmBmBmBmWmmBmWm#(\n     .wm#mmBBmm#Y~   ~XmBmBmWmmmmmBm#e\n    .dm#mmWmm#Z'      ]#mBmBmmBZ!\"\"\"\"`\n   .dm#mmBmm#2`       ]mmmBmBm#2\n   jm#mmWmm#2`        dmmBmBmB#(\n  )m##mBmmWZ`        )##mBmBmmZ\n :dmmmBmBm#'        .d#mBmBmWZ(\n j#mmBmBmme         jmmmBmBm#2\n_m#mBmWmmm'        )mmmBmBmmZ`\n]##mBmmm#2        <m#mBmBmB#^\ndmmmBmWm#C       <m#mBmBmB#(\nZmmBmBmmmh.    _jm#mmBmBm#(\nXBmBmBmBmm6s_aum##mmBmBm&^\n3$mBmBmBmmm#mmmmmmBmBm#2'\n+ZmBmBmWmBmBmWmmBmBm##!\n )ZmBmBmmmBmBmmBmB##!`\n  -4U#mBmWmBmBm##2\"\n    -!!XU##US*?\"-"
  },
  {
    "path": "src/logo/ascii/slackware.txt",
    "content": "                  :::::::::\n            :::::::::::::::::::\n         :::::::::::::::::::::::::\n       ::::::::$2cllcccccllllllll$1::::::\n    :::::::::$2lc               dc$1:::::::\n   ::::::::$2cl   clllccllll    oc$1:::::::::\n  :::::::::$2o   lc$1::::::::$2co   oc$1::::::::::\n ::::::::::$2o    cccclc$1:::::$2clcc$1::::::::::::\n :::::::::::$2lc        cclccclc$1:::::::::::::\n::::::::::::::$2lcclcc          lc$1::::::::::::\n::::::::::$2cclcc$1:::::$2lccclc     oc$1:::::::::::\n::::::::::$2o    l$1::::::::::$2l    lc$1:::::::::::\n :::::$2cll$1:$2o     clcllcccll     o$1:::::::::::\n :::::$2occ$1:$2o                  clc$1:::::::::::\n  ::::$2ocl$1:$2ccslclccclclccclclc$1:::::::::::::\n   :::$2oclcccccccccccccllllllllllllll$1:::::\n    ::$2lcc1lcccccccccccccccccccccccco$1::::\n      ::::::::::::::::::::::::::::::::\n        ::::::::::::::::::::::::::::\n           ::::::::::::::::::::::\n                ::::::::::::"
  },
  {
    "path": "src/logo/ascii/slackware_small.txt",
    "content": "   ________\n  /  ______|\n  | |______\n  \\______  \\\n   ______| |\n| |________/\n|____________"
  },
  {
    "path": "src/logo/ascii/sleeperos.txt",
    "content": "  ______________\n/P$2aooooooooooooa$19\\\nH$2EEEEEEEEEEEEEEEE$1H\nH$2EEEEEEEEEEEEEEEE$1H\n\\baaaaaaap;$2ZZZZ$1,/*\n       x%\"$2ZZZZ$1,%^\n      >7'$2ZZZZ$1,%\"\n     /7'$2ZZZZ$1/>'\n   ,//$2ZZZZ$1,//\n  ,%<$2ZZZZ$1,%<\n >%^$2ZZZZ$1x%^_____\n4>'$2ZZZZ$1---------9b\nH$2EEEEEEEEEEEEEEEE$1H\nH$2EEEEEEEEEEEEEEEE$1H\nYq$2^************^$1pY\n  ``````````````\n"
  },
  {
    "path": "src/logo/ascii/sleeperos_small.txt",
    "content": " _____\n[$2BBBBB$1]\n`^^7$2&$1/`\n ,/$2&$1/`\n,/$2&$1Z__\n[$2BBBBB$1]\n`^^^^^`\n"
  },
  {
    "path": "src/logo/ascii/slitaz.txt",
    "content": "        @    @(               @\n      @@   @@                  @    @/\n     @@   @@                   @@   @@\n    @@  %@@                     @@   @@\n   @@  %@@@       @@@@@.       @@@@  @@\n  @@@    @@@@    @@@@@@@    &@@@    @@@\n   @@@@@@@ %@@@@@@@@@@@@ &@@@% @@@@@@@/\n       ,@@@@@@@@@@@@@@@@@@@@@@@@@\n  .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@/\n@@@@@@.  @@@@@@@@@@@@@@@@@@@@@  /@@@@@@\n@@    @@@@@  @@@@@@@@@@@@,  @@@@@   @@@\n@@ @@@@.    @@@@@@@@@@@@@%    #@@@@ @@.\n@@ ,@@      @@@@@@@@@@@@@      @@@  @@\n@   @@.     @@@@@@@@@@@@@     @@@  *@\n@    @@     @@@@@@@@@@@@      @@   @\n      @      @@@@@@@@@.     #@\n       @      ,@@@@@       @"
  },
  {
    "path": "src/logo/ascii/smartos.txt",
    "content": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\nyyyys             oyyyyyyyyyyyyyyyy\nyyyys  yyyyyyyyy  oyyyyyyyyyyyyyyyy\nyyyys  yyyyyyyyy  oyyyyyyyyyyyyyyyy\nyyyys  yyyyyyyyy  oyyyyyyyyyyyyyyyy\nyyyys  yyyyyyyyy  oyyyyyyyyyyyyyyyy\nyyyys  yyyyyyyyyyyyyyyyyyyyyyyyyyyy\nyyyyy                         syyyy\nyyyyyyyyyyyyyyyyyyyyyyyyyyyy  syyyy\nyyyyyyyyyyyyyyyy  syyyyyyyyy  syyyy\nyyyyyyyyyyyyyyyy  oyyyyyyyyy  syyyy\nyyyyyyyyyyyyyyyy  oyyyyyyyyy  syyyy\nyyyyyyyyyyyyyyyy  syyyyyyyyy  syyyy\nyyyyyyyyyyyyyyyy              yyyyy\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
  },
  {
    "path": "src/logo/ascii/snigdhaos.txt",
    "content": "                   WK0OO0X\n               WKOxk0XWNXkxN\n           WXOxk0N        kk\n        WKkxOXW          NxO\n     NX0ddk00OOOOkkkkkkkkk0\n WKkxO0dOXXNNNNWWW WXKK\nNxkN   kxW       XOxxdN\nKoX    WkxX    WkxK Xd0\nWxkW     XkkX  kkW   NkxX\n W0dK      XxOWkkW     XxkN\n   NkxX     XoKWkxK      Kd0\n     XkkN  WOdN  NkxX     NxO\n       OxNKxOW     XdO     kk\n       kdk0W        XoK WXkxN\n   WNNXKXXXXKKKK000OOdxkk0X\n  KdOO000KKKKXXXXX0xx0W\n XoK          N0kx0N\n Nd0      NKOxOKW\n  Nkk0K0kxOKN\n    WXXXN\n"
  },
  {
    "path": "src/logo/ascii/soda.txt",
    "content": "                $2@&&&&$1        *'*,\n              $2@@@@@@@&&&$1  **     '*,\n             $2@@@@@@@@@@@&&&&\n           @&@@@@@@@@@@@@@@@&&&\n          $1******$2@@@@@@@@@@@@@@@&&&&\n        $1************$2@@@@@@@@@@@@@@\n      $1*****************$2@@@@@@@@@\n     $1**********************$2@@@\n   @@@@@$1********************\n  $2@@@@@@@@@$1***************\n$2@@@@@@@@@@@@@@@$1*********\n$2@@@@@@@@@@@@@@@@@@$1****\n   $2@@@@@@@@@@@@@@@@@@\n       @@@@@@@@@@@@\n          @@@@@@@"
  },
  {
    "path": "src/logo/ascii/solaris.txt",
    "content": "                 `-     `\n          `--    `+-    .:\n           .+:  `++:  -/+-     .\n    `.::`  -++/``:::`./+/  `.-/.\n      `++/-`.`          ` /++:`\n  ``   ./:`                .: `..`.-\n``./+/:-                     -+++:-\n    -/+`                      :."
  },
  {
    "path": "src/logo/ascii/solaris_small.txt",
    "content": "       .   .;   .\n   .   :;  ::  ;:   .\n   .;. ..      .. .;.\n..  ..             ..  ..\n .;,                 ,;."
  },
  {
    "path": "src/logo/ascii/solus.txt",
    "content": "$2            -```````````\n          `-+/------------.`\n       .---:mNo---------------.\n     .-----yMMMy:---------------.\n   `------oMMMMMm/----------------`\n  .------/MMMMMMMN+----------------.\n .------/NMMMMMMMMm-+/--------------.\n`------/NMMMMMMMMMN-:mh/-------------`\n.-----/NMMMMMMMMMMM:-+MMd//oso/:-----.\n-----/NMMMMMMMMMMMM+--mMMMh::smMmyo:--\n----+NMMMMMMMMMMMMMo--yMMMMNo-:yMMMMd/.\n.--oMMMMMMMMMMMMMMMy--yMMMMMMh:-yMMMy-`\n`-sMMMMMMMMMMMMMMMMh--dMMMMMMMd:/Ny+y.\n`-/+osyhhdmmNNMMMMMm-/MMMMMMMmh+/ohm+\n  .------------:://+-/++++++$1oshddys:\n   -hhhhyyyyyyyyyyyhhhhddddhysssso-\n    `:ossssssyysssssssssssssssso:`\n      `:+ssssssssssssssssssss+-\n         `-/+ssssssssssso+/-`\n              `.-----..`"
  },
  {
    "path": "src/logo/ascii/source_mage.txt",
    "content": "       :ymNMNho.\n.+sdmNMMMMMMMMMMy`\n.-::/yMMMMMMMMMMMm-\n      sMMMMMMMMMMMm/\n     /NMMMMMMMMMMMMMm:\n    .MMMMMMMMMMMMMMMMM:\n    `MMMMMMMMMMMMMMMMMN.\n     NMMMMMMMMMMMMMMMMMd\n     mMMMMMMMMMMMMMMMMMMo\n     hhMMMMMMMMMMMMMMMMMM.\n     .`/MMMMMMMMMMMMMMMMMs\n        :mMMMMMMMMMMMMMMMN`\n         `sMMMMMMMMMMMMMMM+\n           /NMMMMMMMMMMMMMN`\n             oMMMMMMMMMMMMM+\n          ./sd.-hMMMMMMMMmmN`\n      ./+oyyyh- `MMMMMMMMMmNh\n                 sMMMMMMMMMmmo\n                 `NMMMMMMMMMd:\n                  -dMMMMMMMMMo\n                    -shmNMMms."
  },
  {
    "path": "src/logo/ascii/sparky.txt",
    "content": "           .            `-:-`\n          .o`       .-///-`\n         `oo`    .:/++:.\n         os+`  -/+++:` ``.........```\n        /ys+`./+++/-.-::::::----......``\n       `syyo`++o+--::::-::/+++/-``\n       -yyy+.+o+`:/:-:sdmmmmmmmmdy+-`\n::-`   :yyy/-oo.-+/`ymho++++++oyhdmdy/`\n`/yy+-`.syyo`+o..o--h..osyhhddhs+//osyy/`\n  -ydhs+-oyy/.+o.-: ` `  :/::+ydhy+```-os-\n   .sdddy::syo--/:.     `.:dy+-ohhho    ./:\n     :yddds/:+oo+//:-`- /+ +hy+.shhy:     ``\n      `:ydmmdysooooooo-.ss`/yss--oyyo\n        `./ossyyyyo+:-/oo:.osso- .oys\n       ``..-------::////.-oooo/   :so\n    `...----::::::::--.`/oooo:    .o:\n           ```````     ++o+:`     `:`\n                     ./+/-`        `\n                   `-:-.\n                   ``"
  },
  {
    "path": "src/logo/ascii/spoinkos.txt",
    "content": "    @..@\n   (----)\n  ( >__< )\n  ^^ ~~ ^^\n|  Spoink!  |"
  },
  {
    "path": "src/logo/ascii/star.txt",
    "content": "                   ./\n                  `yy-\n                 `y.`y`\n    ``           s-  .y            `\n    +h//:..`    +/    /o    ``..:/so\n     /o``.-::/:/+      o/://::-.`+o`\n      :s`     `.        .`     `s/\n       .y.                    .s-\n        `y-                  :s`\n      .-//.                  /+:.\n   .:/:.                       .:/:.\n-+o:.                             .:+:.\n-///++///:::`              .-::::///+so-\n       ``..o/              d-....```\n           s.     `/.      d\n           h    .+o-+o-    h.\n           h  -o/`   `/o:  s:\n          -s/o:`       `:o/+/\n          /s-             -yo"
  },
  {
    "path": "src/logo/ascii/steamdeck.txt",
    "content": "$2           .xXK0kdc'..\n           .OMMMMWNK0Odc'.\n           .OMMMMMMMMMMWNOl'.\n           .OMMMMMMMMMMMMMWXx;.\n           .:ddk0XWMMMMMMMMMMNx'\n                ..;oxONMMMMMMMWKc.\n$1      ..,;::::;,'.$2   .;xXMMMMMMMNo.\n$1   .':looooooooool:,..$2 .;OWMMMMMMNl.\n$1  .:oooooooooooooooooc'$2  .xWMMMMMMK:\n$1 'coooooooooooooooooool,$2  'OMMMMMMWx.\n$1.coooooooooooooooooooool.$2  cNMMMMMM0,\n$1'loooooooooooooooooooooo,$2  ,KMMMMMMX:\n$1'loooooooooooooooooooooo,$2  ,KMMMMMMX:\n$1.:oooooooooooooooooooooc.$2  lNMMMMMM0,\n$1 .coooooooooooooooooool'$2  ,0MMMMMMWd.\n$1  .:looooooooooooooolc.$2  'OWMMMMMMK;\n$1   ..;cloooooooooc;'..$2 .c0WMMMMMMXc.\n$1      ..',;;;;,'..$2  ..cONMMMMMMMXc.\n               ..,cxOKWMMMMMMMW0;.\n           .cxk0KNWMMMMMMMMMWXo.\n           .OMMMMMMMMMMMMMWKo'\n           .OMMMMMMMMMMWKx:.\n           .OMMMWNX0Oxl;.\n           .o0Oxoc,..\n"
  },
  {
    "path": "src/logo/ascii/steamdeck_small.txt",
    "content": " $2__\n   \\\n$1##  $2\\\n$1##  $2/\n __/"
  },
  {
    "path": "src/logo/ascii/steamos.txt",
    "content": "$1              .,,,,.\n        .,'onNMMMMMNNnn',.\n     .'oNMANKMMMMMMMMMMMNNn'.\n   .'ANMMMMMMMXKNNWWWPFFWNNMNn.\n  ;NNMMMMMMMMMMNWW'' ,.., 'WMMM,\n ;NMMMMV+##+VNWWW' .+;'':+, 'WMW,\n,VNNWP+$2######$1+WW,  $2+:    $1:+, +MMM,\n'$2+#############,   +.    ,+' $1+NMMM\n$2  '*#########*'     '*,,*' $1.+NMMMM.\n$2     `'*###*'          ,.,;###$1+WNM,\n$2         .,;;,      .;##########$1+W\n$2,',.         ';  ,+##############'\n '###+. :,. .,; ,###############'\n  '####.. `'' .,###############'\n    '#####+++################'\n      '*##################*'\n         ''*##########*''\n              ''''''"
  },
  {
    "path": "src/logo/ascii/stock_linux.txt",
    "content": "              #G5J5G#\n          &BPYJJJJJJJYPB&\n      &#G5JJJJJJY5YJJJJJJ5G#&\n   #G5YJJJJJY5G#& &#G5YJJJJJY5G#\nBPYJJJJJJJ5B&         &BPYJJJJJJYPB\nJJJJJJJJJJY5G#&           &BPYJJJJJ\nJJJJJJJJJJJJJJY5G#           &JJJJJ\nPYJJJJJJJJJJJJJJJJYPB&        GYJJJ\n  &BPYJJJJJJJJJJJJJJJJ5PB&      &BP\n      #G5YJJJJJJJJJJJJJJJY5G#\nPB&      &BP5JJJJJJJJJJJJJJJJYPB&\nJJJYG        &BPYJJJJJJJJJJJJJJJJYP\nJJJJJ&           #G5YJJJJJJJJJJJJJJ\nJJJJJYPB&           &#G5YJJJJJJJJJJ\nBPYJJJJJJYPB&         &B5JJJJJJJYPB\n   #G5YJJJJJY5G#& &#G5YJJJJJY5G#\n      &#G5JJJJJJY5YJJJJJJ5G#&\n          &BPYJJJJJJJYPB&\n              #G5J5G#"
  },
  {
    "path": "src/logo/ascii/sulin.txt",
    "content": "                         /\\          /\\\n                        ( \\\\        // )\n                         \\ \\\\      // /\n                          \\_\\\\||||//_/\n                           \\/ _  _ \\\n                          \\/|(O)(O)|\n                         \\/ |      |\n     ___________________\\/  \\      /\n    //                //     |____|\n   //                ||     /      \\\n  //|                \\|     \\ 0  0 /\n // \\       )         V    / \\____/\n//   \\     /        (     /\n      \\   /_________|  |_/\n      /  /\\   /     |  ||\n     /  / /  /      \\  ||\n     | |  | |        | ||\n     | |  | |        | ||\n     |_|  |_|        |_||\n     \\_\\  \\_\\        \\_\\"
  },
  {
    "path": "src/logo/ascii/summitos.txt",
    "content": "                :~^\n               ~5PPJ\n              :5555P7\n             .Y55555P~\n             ?P5555555^\n            7P55555555Y.\n           ~5555555555PJ$2       7YJ^\n          $1^555555555555P7$2     7GGGP^\n         $1.Y5555555555555P!$2   ~PPPPP5.\n         $1JP555555555555555^$2 ^PPPPPPGY\n        $17P5555555555555555Y$2^5PPPPPPPG?\n       $1~P55555555555555555$2^PPPPPPPPPPP!\n      $1^555555555555555555$2^PPPPPPPPPPPPP^\n     $1.Y555555555555P$3G$1P55$2^PPPPPPPPPPPPPP5:\n     $1JP55555555555P$3GGG$1P$2^PPPPPPPPPPPPPPPGY\n    $17P555555555555$3GGGGG$2PPPPPPPPPPPPPPPPPG?\n   $1!P555555555555$3GGGGGGG$2PPPPPPPPPPPPPPPPPG!\n  $1^5555555555555$3PGGGGGGGG$2PPPPPPPPPPPPPPPPPP^\n $1:Y555555555555$3PGGGGGGGGGG$2PPPPPPPPPPPPPPPPP5:\n$1.JP55555555555$3PGGGGGGGGGGGG$2PPPPPPPPPPPPPPPPGY.\n$1?PPPPPPPPPPPP$3GGBBBBBBBBBBBBB$2GGGGGGGGGGGGGGGGGJ\n$1^~~~~~~~~~~~$3!!!!!!!!!!!!!!!!!$2!!!!!!!!!!!!!!!!~\n"
  },
  {
    "path": "src/logo/ascii/suse.txt",
    "content": " kKKKKKd'  Okxol:;,.\nkKKKKKKK0kOKKKKKKKKKKOxo:,\nKKKKKKKKKKKKKKKKKKK0P^,,,^dx:\nKKKKKKKKKKKKKKKKKKk'.oOPPb.'0k.\nKKKKKKKKKKKKKKKKKK: kKx..dd lKd\nKKKKKKKKKKKKOx0KKKd ^0KKKO' kKKc\nKKKKKKKKKKKKK;.;oOKx,..^..;kKKK0.\nKKKKKKKKKKKKK0o;...^cdxxOK0O/^^'\nKKKKKKKKKKKKKKKKK0x;,,......,;od\nkKKKKKKKKKKKKKKKKKKKKKKK00KKOo^\n kKKKKKOxddxkOO00000Okxoc;''"
  },
  {
    "path": "src/logo/ascii/swagarch.txt",
    "content": "$2        .;ldkOKXXNNNNXXK0Oxoc,.\n   ,lkXMMNK0OkkxkkOKWMMMMMMMMMM;\n 'K0xo  ..,;:c:.     `'lKMMMMM0\n     .lONMMMMMM'         `lNMk'\n$2    ;WMMMMMMMMMO.              $1....::...\n$2    OMMMMMMMMMMMMKl.       $1.,;;;;;ccccccc,\n$2    `0MMMMMMMMMMMMMM0:         $1.. .ccccccc.\n$2      'kWMMMMMMMMMMMMMNo.   $1.,:'  .ccccccc.\n$2        `c0MMMMMMMMMMMMMN,$1,:c;    :cccccc:\n$2 ckl.      `lXMMMMMMMMMX$1occcc:.. ;ccccccc.\n$2dMMMMXd,     `OMMMMMMWk$1ccc;:''` ,ccccccc:\n$2XMMMMMMMWKkxxOWMMMMMNo$1ccc;     .cccccccc.\n$2 `':ldxO0KXXXXXK0Okdo$1cccc.     :cccccccc.\n                    :ccc:'     `cccccccc:,\n                                   ''"
  },
  {
    "path": "src/logo/ascii/t2.txt",
    "content": "                       $3//$2\n$1TTTTTTTTTTTTTTT$2       $3//$2\n$1TTTTTTTTTTTTTTT$2      $3//$2 $1..::$2  $4:-:.$2\n      $4ttt$2           $3//$2 $1.::.$2    $4.:-:$2\n      $4ttt$2          $3//$2  $1.:.$2       $4--$2\n      $4ttt$2         $3//$2            $4:-:$2\n      $4ttt$2        $3//$2           $4.:-:$2\n      $4ttt$2       $3//$2           $4:-:.$2\n      $4ttt$2      $3//$2          $4.--.$2\n      $4ttt$2     $3//$2         $4.:-:$2\n      $4ttt$2    $3//$2          $4...$2\n      $4ttt$2   $3//$2\n           $3//$2         $3.:::::::::::.$2\n          $3//$2\n"
  },
  {
    "path": "src/logo/ascii/t2_small.txt",
    "content": "$2TTTTTTTTTT\n    tt   $1222$2\n    tt  $12   2$2\n    tt     $12$2\n    tt    $12$2\n    tt  $122222$2"
  },
  {
    "path": "src/logo/ascii/tails.txt",
    "content": "      ``\n  ./yhNh\nsyy/Nshh         `:o/\nN:dsNshh  █   `ohNMMd\nN-/+Nshh      `yMMMMd\nN-yhMshh       yMMMMd\nN-s:hshh  █    yMMMMd so//.\nN-oyNsyh       yMMMMd d  Mms.\nN:hohhhd:.     yMMMMd  syMMM+\nNsyh+-..+y+-   yMMMMd   :mMM+\n+hy-      -ss/`yMMMM     `+d+\n  :sy/.     ./yNMMMMm      ``\n    .+ys- `:+hNMMMMMMy/`\n      `hNmmMMMMMMMMMMMMdo.\n       dMMMMMMMMMMMMMMMMMNh:\n       +hMMMMMMMMMMMMMMMMMmy.\n         -oNMMMMMMMMMMmy+.`\n           `:yNMMMds/.`\n              .//`"
  },
  {
    "path": "src/logo/ascii/tatra.txt",
    "content": "         . .:. .         .:.\n        .^^.!.:::.    .^!?J?^\n      .:^.^!!!~:~^.  .7??77!~~^.\n     .~^.!??77?!.^~:  ..:^^7JJJ7~~^.\n     .^~.^7???7~.~~.   .7??????????!\n      .:^:^^~^^:!^ ^:  .......^!:...\n    .!7~.::.!.::. ~BG~    :^  ^~:\n    :!!~   ~.    ?BBBB!  ^?J!.  .!~.\n     :!. .JBY. .Y#BBBY?~!???J7. :^^.\n     .. :5#B#P~P#BBP?7?55J?J7:\n       ^P#BBBBBBBB5?7J5555J!.....\n      !BBBBBBGBBGJ77::Y555J?77777^\n     ?BBBBG5JJ5PJ?!:  .?Y??????77?~.\n   .YBGPYJ??????Y?^^^^~7?????????7?!.\n   .^^:..::::::::.....:::::::::::..:."
  },
  {
    "path": "src/logo/ascii/tearch.txt",
    "content": "          @@@@@@@@@@@@@@\n      @@@@@@@@@              @@@@@@\n     @@@@@                     @@@@@\n     @@                           @@\n      @%                         @@\n       @                         @\n       @@@@@@@@@@@@@@@@@@@@@@@@ @@\n       .@@@@@@@@@@@@/@@@@@@@@@@@@\n       @@@@@@@@@@@@///@@@@@@@@@@@@\n      @@@@@@@@@@@@@((((@@@@@@@@@@@@\n     @@@@@@@@@@@#(((((((#@@@@@@@@@@@\n    @@@@@@@@@@@#//////////@@@@@@@@@@&\n    @@@@@@@@@@////@@@@@////@@@@@@@@@@\n    @@@@@@@@//////@@@@@/////@@@@@@@@@\n    @@@@@@@//@@@@@@@@@@@@@@@//@@@@@@@\n @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n@@     .@@@@@@@@@@@@@@@@@@@@@@@@@      @\n @@@@@@           @@@.           @@@@@@@\n   @@@@@@@&@@@@@@@#  #@@@@@@@@@@@@@@@@\n      @@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n          @@@@@@@@@@@@@@@@@@@@@"
  },
  {
    "path": "src/logo/ascii/templeos.txt",
    "content": "           $1aooooo$2,\n           $1aooooo$2``\n           $1aooooo$2``\n    $3(((((( $1aooooo$2``$3((((((\n $1oooo$3@@ @C@*;,./k@@ @@ @$1oooo$2\n $1oooooo$3@@ @@@@@@@@ @@k$1oooooo$2``\n $1oooooooo$3\\@ @/@@ @@$1ooooooooo$2``\n  `````````$3'*@*/'$2````````````\n    $3@@@(   $1aooooo$2`` $3@@@@\n    @@ @   $1aooooo$2`` $3@ @@\n     @@@   $1aooooo$2`` $3@@@,\n     @@    $1aooooo$2`` $3 @@\n      @    $1aooooo$2`` $3 (@\n           $1aooooo$2``\n           $1aooooo$2``\n           $1aooooo$2``\n           $1aooooo$2``\n           $1aooooo$2``\n            ```````\n              `````"
  },
  {
    "path": "src/logo/ascii/tileos.txt",
    "content": "$2                      ,.\n$2                  ##########\n$2             ###################.\n$2         ############################\n$2     ####################################\n$2    ######################################\n$1,(/$2     *############################(     $3.%$1\n$1((((((($2     (####################      $3%%####$1\n$1(((((((((((/$2     ############      $3%#########$1\n$1(((((((((((((((($2      ##/     $3(##############$1\n$1(((((((((((((((((((($2      $3###################$1\n$1((((((((((((((((((((($2   $3####################$1\n$1((((((((((((((((((((($2   $3####################$1\n$1((((((((((((((((((((($2   $3####################$1\n$1((((((((((((((((((((($2   $3####################$1\n$1 (((((((((((((((((((($2   $3###################%$1\n$1     (((((((((((((((($2   $3################/$1\n$1         (((((((((((($2   $3###########%$1\n$1              ((((((($2   $3#######*$1\n$1                  ((($2   $3##%*$1"
  },
  {
    "path": "src/logo/ascii/torizoncore.txt",
    "content": "$2                          `.::-.\n                       `-://////:-.\n                     `:////////////:.\n                `.-.`  `-:///////-.     `\n             `.://///:.`  `-::-.     `-://:.`\n           .:///////////:.        `-:////////:.`\n           `.://///////:-`       .:////////////:`\n    `.-/:-`   `.:///:-`    $3`-+o/.$2  `.://////-.     `.`\n `.:///////:-`   `.`    $3`-+sssssso/.$2  `.--.     `-:///:-`\n-/////////////:       $3`+sssssssssssso-$2       `-://///////:-`\n  .-///////:.`    ``    $3-/sssssssso/.$2    `   .:///////////-.\n     .-::.`    `-://:-.    $3-/sso/.$2    `.:/:-.  `.://///-.\n            `-:////////:-.    $3`$2    `.:////////-.  `.-.\n$1o-$2          -:///////////:`       .:////////////:`        $1-o$2\n$1hdho-$2         `-://///:.`    `..`   `-://////:.`       $1-ohdh$2\n$1/ydddho-$2         `--.`    `.:////:.`   `-::.`       $1-ohdddy/$2\n  $1./ydddho:`$2           `.://////////:.          $1`:ohdddy/.$2\n`    $1`/shddho:`$2        `.://////////:.       $1`:ohddds/`$2    `\n::-`    $1`/shddhs:`$2        `.:////:.`      $1`:shddhs/`$2    `-::\n:///:-`    $1`:shddhs/`$2        `..`      $1`/shddhs:`$2    `-:///-\n `.:///:-`    $1`:ohddhs/`$2            $1`/shddho:`$2    `-:///:.`\n    `.:///:-.     $1-ohdddy/.$2      $1./ydddho-$2     .-:///-.`\n       `.:///:-.     $1-+hdddy+//+ydddh+-$2     .-://:-.\n          `.-///:-.     $1-+yddddddy+-$2     .-://:-.\n              .-://:-.     $1./++/.$2     .-///:-.\n                 .-:///:.`        `.:///:-`\n                    `-:///:.````.:///:-`\n                       `-:////////:-`\n                          `-::::-`"
  },
  {
    "path": "src/logo/ascii/trisquel.txt",
    "content": "                         ,o$$$$$$o.\n                      ,o$$$$Y\"\"\"Y$$$$b\n    ,o$$$$$$$$$$$$o.       ,$$$$'   ,   Y$$$$b\n ,o$$$$$$$$$$$$$$$$$$$$$$$$o.    :$$      b   Y$$$$.\n,$$$$\"'      \"Y$$$$$$$$o.   'b.   ,b  d$$$$$$\n$$$$'  .d$$$$$$$$b  '$$$$$$$$o   'Y$$$$$$Y  d$$$$$$'\n$$$$'  q'    'b  '$$$$$$$$$$o._   _.o$$$$$$$$'\n.$$$$,_    _,d$$  ,$$$$$$$$$$$$$$$$$$$$$$$$$2$$$$$$$$Y'\n$1 '$$$$$$$$aaa$$$$$$' .$$$$$$$$$$$$$2$$$$$$$$$$$$$$$$'\n$1     \"\"\"\"     $2d$$$$$$$$\"'\n             d$$$$$$'   .d$$$$b.\n             $$$$$$$$  .$$\"   'a$$.\n             $$$$$$$$  $$b      $$$$.\n             '$$$$$$. '$$b,,.  $$$$$$\n              '$$$$$$.       .$$$$'\n               'a$$$$$$o._.o$$$$a'\n                  'a$$$$$$$$a'"
  },
  {
    "path": "src/logo/ascii/truenas_scale.txt",
    "content": "                      ++  $2++\n                  $1++++++  $2++++++\n               $1+++++++++  $2+++++++++\n               $1+++++++      $2+++++++\n        $1+++  $2+++  $1+  $3--------$1  $2+  $1+++  $2+++\n     $1++++++  $2++++++   $3------$1   ++++++  $2++++++\n   $1++++++++  $2++++++++++    $1++++++++++  $2++++++++\n  $1+++++++  $3--$1  $2+++++++      $1+++++++  $3--$1  $2+++++++\n$2+++  $1+  $3--------$1  $2+  $1+++  $2+++  $1+  $3--------$1  $2+  $1+++\n$2++++++  $3--------$1  ++++++  $2++++++  $3--------$1  ++++++\n$2++++++++++     $1+++++++++  $2+++++++++     $1++++++++++\n  $2+++++++++    $1+++++++  $3--$1  $2+++++++    $1+++++++++\n      $2+++++  +++     $3--------$1     +++  +++++\n         $2++  +++++++  $3------$1  +++++++  ++\n             $2++++++++++    $1++++++++++\n                $2++++++++  $1++++++++\n                   $2+++++  $1+++++\n                      $2++  $1++"
  },
  {
    "path": "src/logo/ascii/tuxedo_os.txt",
    "content": " #############################+\n.%#######################%@@@+\n            .           :#@%-\n           .#@%%%-     =@@#.\n             *@%@@=   +@@+\n              =@@%@+.#@%-\n               -@@%@@@#:\n                .#@%%@-\n                :#@@%@@=\n               =@@+=@@%@+\n             .#@@=  -@@%@#\n            -%@#:    :%@%@%:\n           *@@+       .#%%%%-\n         :%@%-              .\n        +@@@%#%%%%%%%%%%%%%%%%%%%%%%%.\n       +######################*******"
  },
  {
    "path": "src/logo/ascii/twister.txt",
    "content": "$3.......................$4.......$5.,:ccllllcc:.$4...\n$3.......................$5.,;cldxkO0KXNNNWNNNX0x:\n$3.................$5.,:ldkOOKK0OkkkkkOKWMMMMMMMNo\n$3.............$5,;coxO0Oxo::'$4.........$5,oXMMMMMM0'\n$3.........$5,:ldkOOkc;'$3...$4.............$5;OMMMMW0:$4.\n$3.......$5;ldkOxol:'$3......$4............$5,dNMMX0''$4..\n$3....$5;:oxkxl:'$3..........$4..........$5,lOWWXx:.,'$4..\n$3..$5,ldxkkd:'$3............$5.:c$4....$5':lkNMNOc,cdl'$4..\n$3.$5;oxxkkkc'$3.........$5.:clc;'.';lkKNNKk;;ck00l$4...\n$5.lxxxkkkxoooooddxkOOko'..cok0KOd':::o0NXd;'$4...\n$3.$5:odxxkkOOOOOOOkdoi'..:ddxdoc:::od0NWW0c'$4.....\n$3...$5':;gggggg;::''.......::::x0XWMMMNO.::'$4.....\n$3..............$5;......,;od0KNWMWNK0O:::do'$4.....\n$3...............$5'cclgggggggxdll\":::'XKoo,$4......\n$3.................$5',,,,,,,::::;ooNWMWOc'$4.......\n$3..................$5,:loxxxO0KXNNXK0ko:'$4........\n$3..................$5';::;oTcoggcoF\":::'$4.........\n$3..................$5':o,.:::::::::,p'$4...........\n$3..................$5;'ccdxkOOOdxlf'$4.............\n$3.................$5,l;;XgggggXP:'$4...............\n$3................$5;lco;::::::'$4..................\n$3..............$5.';ggggol'`$4.....................\n$3.............$5':oo:''$3...$4......................."
  },
  {
    "path": "src/logo/ascii/ublinux.txt",
    "content": "$1              UUU\n$1          UUUUUUUUUUU\n$1       UUUUUUUUUUUUUUUUU\n$1   UUUUUUUUUUUUUUUUUUUUUUUUU\n$1UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU$3#.\n$1UU$2BBBBB$1UUUUUU$2BBBBBBBBBBB.$1UUUUUU$3##\n$1UU$2BBBBB$1UUUUUU$2BBBBBBBBBBBBB:$1UUUU$3##\n$1UU$2BBBBB$1UUUUUU$2BBBBB$1UUUU$2BBBBB$1UUUU$3##\n$1UU$2BBBBB$1UUUUUU$2BBBBB$1UUUU$2BBBBB$1UUUU$3##\n$1UU$2BBBBB$1UUUUUU$2BBBBBBBBBBBB$1UUUUUU$3##\n$1UU$2BBBBB$1UUUUUU$2BBBBB$1UUUU,$2BBBB.$1UUU$3##\n$1UUU$2BBBBB$1UUUUU$2BBBBB$1UUUUU$2BBBBB$1UUU$3##\n$1UUU$2BBBBB$1UUUUU$2BBBBB$1UUUUU$2BBBBB$1UUU$3##\n$1UUU#$2BBBBBBBBBBBBBBBBBBBBBBB$1UUUU$3##\n$1UUUUUU$2'BBBBBBBBBBBBBBBB'$1UUUUUUU$3##\n$1   UUUUUUUUUUUUUUUUUUUUUUUUU$3##'\n$1       UUUUUUUUUUUUUUUUU$3###'\n$1          UUUUUUUUUUU$3###'\n$1              UUU$3###'\n"
  },
  {
    "path": "src/logo/ascii/ublinux_small.txt",
    "content": "$1       UUUU\n$1    UUUUUUUUUU\n$1 $2BBB$1UUUU$2BBBBBB$1UU$3#\n$1U$2BBB$1UUUU$2BBB$1UUU$2B$1UU$3#\n$1U$2BBB$1UUUU$2BBB$1UUU$2B$1UU$3#\n$1U$2BBB$1UUUU$2BBBBBB$1UUU$3#\n$1U$2BBB$1UUUU$2BBB$1UUU$2BB$1U$3#\n$1UU$2BBB$1UUU$2BBB$1UUU$2BB$1U$3#\n$1 UU$2BBBBBBBBBBB$1UU$3#\n$1    UUUUUUUUU$3#\n$1       UUU$3#\n"
  },
  {
    "path": "src/logo/ascii/ubuntu.txt",
    "content": "                             ....\n              $2.',:clooo:  $1.:looooo:.\n           $2.;looooooooc  $1.oooooooooo'\n        $2.;looooool:,''.  $1:ooooooooooc\n       $2;looool;.         $1'oooooooooo,\n      $2;clool'             $1.cooooooc.  $2,,\n         $2...                $1......  $2.:oo,\n  $1.;clol:,.                        $2.loooo'\n $1:ooooooooo,                        $2'ooool\n$1'ooooooooooo.                        $2loooo.\n$1'ooooooooool                         $2coooo.\n $1,loooooooc.                        $2.loooo.\n   $1.,;;;'.                          $2;ooooc\n       $2...                         $2,ooool.\n    $2.cooooc.              $1..',,'.  $2.cooo.\n      $2;ooooo:.           $1;oooooooc.  $2:l.\n       $2.coooooc,..      $1coooooooooo.\n         $2.:ooooooolc:. $1.ooooooooooo'\n           $2.':loooooo;  $1,oooooooooc\n               $2..';::c'  $1.;loooo:'"
  },
  {
    "path": "src/logo/ascii/ubuntu_budgie.txt",
    "content": "           ./oydmMMMMMMmdyo/.\n        :smMMMMMMMMMMMhs+:++yhs:\n     `omMMMMMMMMMMMN+`        `odo`\n    /NMMMMMMMMMMMMN-            `sN/\n  `hMMMMmhhmMMMMMMh               sMh`\n .mMmo-     /yMMMMm`              `MMm.\n mN/       yMMMMMMMd-              MMMm\noN-        oMMMMMMMMMms+//+o+:    :MMMMo\nm/          +NMMMMMMMMMMMMMMMMm. :NMMMMm\nM`           .NMMMMMMMMMMMMMMMNodMMMMMMM\nM-            sMMMMMMMMMMMMMMMMMMMMMMMMM\nmm`           mMMMMMMMMMNdhhdNMMMMMMMMMm\noMm/        .dMMMMMMMMh:      :dMMMMMMMo\n mMMNyo/:/sdMMMMMMMMM+          sMMMMMm\n .mMMMMMMMMMMMMMMMMMs           `NMMMm.\n  `hMMMMMMMMMMM.oo+.            `MMMh`\n    /NMMMMMMMMMo                sMN/\n     `omMMMMMMMMy.            :dmo`\n        :smMMMMMMMh+-`   `.:ohs:\n           ./oydmMMMMMMdhyo/."
  },
  {
    "path": "src/logo/ascii/ubuntu_cinnamon.txt",
    "content": "            .-/+oooooooo+/-.\n        `:+oooooooooooooooooo+:`\n      -+oooooooooooooooooooooooo+-\n    .ooooooooooooooooooo$2:ohNd$1oooooo.\n   /oooooooooooo$2:/+oo++:/ohNd$1ooooooo/\n  +oooooooooo$2:osNdhyyhdNNh+:+$1oooooooo+\n /ooooooooo$2/dN/$1ooooooooo$2/sNNo$1ooooooooo/\n.ooooooooo$2oMd:$1oooooooooooo$2:yMy$1ooooooooo.\n+ooooo$2:+o/Md$1oooooo$2:sm/$1oo/ooo$2yMo$1oooooooo+\nooo$2:sdMdosMo$1ooooo$2oNMd$1//$2dMd+$1o$2:so$1ooooooooo\noooo$2+ymdosMo$1ooo$2+mMm$1+/$2hMMMMMh+hs$1ooooooooo\n+oooooo$2:$1:$2/Nm:$1/$2hMNo$1:y$2MMMMMMMMMM+$1oooooooo+\n.ooooooooo$2/NNMNy$1:o$2NMMMMMMMMMMo$1ooooooooo.\n/oooooooooo$2:yh:$1+m$2MMMMMMMMMMd/$1ooooooooo/\n  +oooooooooo$2+$1/h$2mMMMMMMNds//o$1oooooooo+\n   /oooooooooooo$2+:////:o/ymMd$1ooooooo/\n    .oooooooooooooooooooo$2/sdh$1oooooo.\n      -+oooooooooooooooooooooooo+-\n        `:+oooooooooooooooooo+:`\n            .-/+oooooooo+/-."
  },
  {
    "path": "src/logo/ascii/ubuntu_gnome.txt",
    "content": "$3          ./o.\n        .oooooooo\n      .oooo```soooo\n    .oooo`     `soooo\n   .ooo`   $4.o.$3   `\\/ooo.\n   :ooo   $4:oooo.$3   `\\/ooo.\n    sooo    $4`ooooo$3    \\/oooo\n     \\/ooo    $4`soooo$3    `ooooo\n      `soooo    $4`\\/ooo$3    `soooo\n$4./oo    $3`\\/ooo    $4`/oooo.$3   `/ooo\n$4`\\/ooo.   $3`/oooo.   $4`/oooo.$3   ``\n$4  `\\/ooo.    $3/oooo     $4/ooo`\n$4     `ooooo    $3``    $4.oooo\n$4       `soooo.     .oooo`\n         `\\/oooooooooo`\n            ``\\/oo``"
  },
  {
    "path": "src/logo/ascii/ubuntu_kylin.txt",
    "content": "$1            .__=liiiiiii=__,\n        ._=liiliii|i|i|iiilii=_.\n      _=iiiii|ii|i|ii|i|inwwwzii=,\n    .=liiii|ii|ii|wwww|i$23QWWWW$1ziii=,\n   =lii|i|ii|i|$2QQQWWWWWm]QWWQD$1||iiii=\n  =lii|iiivw$2Qm$1>3$2WWWWWQWQQwwQw$1cii|i|ii=\n =lii|ii|n$2QWWWQ$1|)i$2|i$1%i|]$2TQWWWWm$1|ii|i|i=\n.li|i|i|m$2WWWQV$1|ii$2wmD$1|iiii|$2TWWWWm$1|i|iiii,\n=iii$2www|$WQWk$1|i$2aWWWD$1|i|i|ii]$2QQWQk$1|ii|i|=\niii$2QWWWQz$WW$1|i$2jQQWQm$1w|ii$2wW$1k|$2TTTTY$1i|i|iii\niiI$2QWQWWtyQQ$1|i|$2$WWWWWQWk$1||i|i|ii||i|ii|i\n<|i|$2TTT|mQQWz$1|i$23D$1]C|$2nD$W$1|ii$2vWWWWk$1||ii|i>\n-|ii|i|i$2WWWQw$1|$2Tt$1|i3$2T$1|$2T$1|i|$2nQWQWDk$1|ii|ii`\n <|i|iii|$2VWQWWQ$1|i|i|||ii$2wmWWQWD$1||ii|ii+\n  <|ii|i|i]$2$W@$1tv$2QQQWQQQWWTTHT$11|iii|i|>\n   <|i|ii|ii||v$2QWWWQWWW@vmWWWm$1|i|i|i>\n    -<|i|ii|ii|i|$2TTTTTT$1|]$2QQWWWC$1|ii>`\n      -<|i|ii|i|ii|i|i|ii3$2TTT$1t|i>`\n         ~<|ii|ii|iiiii|i|||i>~\n            -~~<|ii|i||i>~~`"
  },
  {
    "path": "src/logo/ascii/ubuntu_mate.txt",
    "content": "$1            .:/+oossssoo+/:.`\n        `:+ssssssssssssssssss+:`\n      -+sssssssssssssss$2y$1ssssssss+-\n    .osssssssssssss$2yy$1ss$2mMmh$1ssssssso.\n   /sssssssss$2ydmNNNmmd$1s$2mMMMMNdy$1sssss/\n `+ssssssss$2hNNdy$1sssssss$2mMMMMNdy$1ssssss+`\n +sssssss$2yNNh$1ss$2hmNNNNm$1s$2mMmh$1s$2ydy$1sssssss+\n-sssss$2y$1ss$2Nm$1ss$2hNNh$1ssssss$2y$1s$2hh$1ss$2mMy$1sssssss-\n+ssss$2yMNdy$1ss$2hMd$1ssssssssss$2hMd$1ss$2NN$1sssssss+\nsssss$2yMMMMMmh$1sssssssssssss$2NM$1ss$2dMy$1sssssss\nsssss$2yMMMMMmhy$1ssssssssssss$2NM$1ss$2dMy$1sssssss\n+ssss$2yMNdy$1ss$2hMd$1ssssssssss$2hMd$1ss$2NN$1sssssss+\n-sssss$2y$1ss$2Nm$1ss$2hNNh$1ssssssss$2dh$1ss$2mMy$1sssssss-\n +sssssss$2yNNh$1ss$2hmNNNNm$1s$2mNmh$1s$2ymy$1sssssss+\n  +ssssssss$2hNNdy$1sssssss$2mMMMMmhy$1ssssss+\n   /sssssssss$2ydmNNNNmd$1s$2mMMMMNdh$1sssss/\n    .osssssssssssss$2yy$1ss$2mMmdy$1sssssso.\n      -+sssssssssssssss$2y$1ssssssss+-\n        `:+ssssssssssssssssss+:`\n            .:/+oossssoo+/:."
  },
  {
    "path": "src/logo/ascii/ubuntu_old.txt",
    "content": "             --+oossssssoo+--\n         .:+ssssssssssssssssss+:.\n       -+ssssssssssssssssssyyssss+-\n     .ossssssssssssssssssd$2MMMNy$1sssso.\n   /sssssssssss$2hdmmNNmmyNMMMMh$1ssssss/\n  +sssssssss$2hm$1yd$2MMMMMMMNddddy$1ssssssss+\n /ssssssss$2hNMMM$1yh$2hyyyyhmNMMMNh$1ssssssss/\n.ssssssss$2dMMMNh$1ssssssssss$2hNMMMd$1ssssssss.\n+ssss$2hhhyNMMNy$1ssssssssssss$2yNMMMy$1sssssss+\noss$2yNMMMNyMMh$1ssssssssssssss$2hmmmh$1ssssssso\noss$2yNMMMNyMMh$1sssssssssssssshmmmhssssssso\n+ssss$2hhhyNMMNy$1ssssssssssss$2yNMMMy$1sssssss+\n.ssssssss$2dMMMNh$1ssssssssss$2hNMMMd$1ssssssss.\n /ssssssss$2hNMMM$1yh$2hyyyyhdNMMMNh$1ssssssss/\n  +sssssssss$2dm$1yd$2MMMMMMMMddddy$1ssssssss+\n   /sssssssssss$2hdmNNNNmyNMMMMh$1ssssss/\n    .ossssssssssssssssss$2dMMMNy$1sssso.\n     -+sssssssssssssssss$2yyy$1ssss+-\n       `:+ssssssssssssssssss+:´\n           --+oossssssoo+--"
  },
  {
    "path": "src/logo/ascii/ubuntu_old2.txt",
    "content": "                         ./+o+-\n$2                 yyyyy- $1-yyyyyy+\n$2              $2://+//////$1-yyyyyyo\n$3          .++ $2.:/++++++/-$1.+sss/`\n$3        .:++o:  $2/++++++++/:--:/-\n$3       o:+o+:++.$2`..```.-/oo+++++/\n$3      .:+o:+o/.$2          `+sssoo+/\n$2 .++/+:$3+oo+o:`$2             /sssooo.\n$2/+++//+:$3`oo+o$2               /::--:.\n$2+/+o+++$3`o++o$1               ++////.\n$2 .++.o+$3++oo+:`$1             /dddhhh.\n$3      .+.o+oo:.$1          `oddhhhh+\n$3       +.++o+o`$1`-````.:ohdhhhhh+\n$3        `:o+++ $1`ohhhhhhhhyo++os:\n$3          .o:$1`.syhhhhhhh/$3.oo++o`\n$1              /osyyyyyyo$3++ooo+++/\n$1                  ````` $3+oo+++o:\n$3                         `oo++."
  },
  {
    "path": "src/logo/ascii/ubuntu_old2_small.txt",
    "content": "         _\n     ---(_)\n _/  ---  \\\n(_) |   |\n  \\  --- _/\n     ---(_)"
  },
  {
    "path": "src/logo/ascii/ubuntu_small.txt",
    "content": "       $2..;,; $1.,;,.\n    $2.,lool: $1.ooooo,\n   $2;oo;:    $1.coool.\n $1....         $1''' $2,l;\n$1:oooo,            $2'oo.\n$1looooc            $2:oo'\n $1'::'             $2,oo:\n   $2,.,       $1.... $2co,\n    $2lo:;.   $1:oooo; $2.\n     $2':ooo; $1cooooc\n        $2'''  $1''''"
  },
  {
    "path": "src/logo/ascii/ubuntu_studio.txt",
    "content": "              ..-::::::-.`\n         `.:+++++++++++$2ooo$1++:.`\n       ./+++++++++++++$2sMMMNdyo$1+/.\n     .++++++++++++++++$2oyhmMMMMms$1++.\n   `/+++++++++$2osyhddddhys$1+$2osdMMMh$1++/`\n  `+++++++++$2ydMMMMNNNMMMMNds$1+$2oyyo$1++++`\n  +++++++++$2dMMNhso$1++++$2oydNMMmo$1++++++++`\n :+$2odmy$1+++$2ooysoohmNMMNmyoohMMNs$1+++++++:\n ++$2dMMm$1+$2oNMd$1++$2yMMMmhhmMMNs+yMMNo$1+++++++\n`++$2NMMy$1+$2hMMd$1+$2oMMMs$1++++$2sMMN$1++$2NMMs$1+++++++.\n`++$2NMMy$1+$2hMMd$1+$2oMMMo$1++++$2sMMN$1++$2mMMs$1+++++++.\n ++$2dMMd$1+$2oNMm$1++$2yMMNdhhdMMMs$1+y$2MMNo$1+++++++\n :+$2odmy$1++$2oo$1+$2ss$1+$2ohNMMMMmho$1+$2yMMMs$1+++++++:\n  +++++++++$2hMMmhs+ooo+oshNMMms$1++++++++\n  `++++++++$2oymMMMMNmmNMMMMmy+oys$1+++++`\n   `/+++++++++$2oyhdmmmmdhso+sdMMMs$1++/\n     ./+++++++++++++++$2oyhdNMMMms$1++.\n       ./+++++++++++++$2hMMMNdyo$1+/.\n         `.:+++++++++++$2sso$1++:.\n              ..-::::::-.."
  },
  {
    "path": "src/logo/ascii/ubuntu_sway.txt",
    "content": "            .-/+oossssoo+\\-.\n        ´:+ssssssssssssssssss+:`\n      -+ssssssssssssssssss$2yy$1ssss+-\n    .ossssssssssssssssss$2dMMMNyy$1ssso.\n   /sssssssssss$2hdmmNNmmyNMMMMh$1ssssss\\\n  +sssssssss$2hm$1ydMMMMMMMNdd$2ddy$1ssssssss+\n /ssssssss$2hN$1MM$2M$1yh$2hyyyyhmNM$1MM$2Nh$1ssssssss\\\n.ssssssss$2dM$1MM$2Nh$1ssssssssss$2hN$1MM$2Md$1ssssssss.\n+sss$2yyyyyN$1MM$2Ny$1ssssssssssss$2yN$1MM$2My$1sssssss+\nossy$2NMMMNy$1MM$2h$1ssssssssssssss$2hm$1mm$2h$1ssssssso\nossy$2NMMMNy$1MM$2h$1sssssssssssssshmmmh$1ssssssso\n+sss$2yyyyyN$1MM$2Ny$1ssssssssssss$2yN$1MM$2My$1sssssss+\n.ssssssss$2dM$1MM$2Nh$1ssssssssss$2hN$1MM$2Md$1ssssssss.\n \\ssssssss$2hN$1MM$2M$1yh$2hyyyyhdNM$1M$2MNh$1ssssssss/\n  +sssssssss$2dm$1ydMMMMMMMMdd$2ddy$1ssssssss+\n   \\sssssssssss$2hdmNNNNmyNMMMMh$1ssssss/\n    .ossssssssssssssssss$2dMMMNyy$1ssso.\n      -+sssssssssssssssss$2yy$1sss+-\n        `:+ssssssssssssssssss+:`\n            .-\\+oossssoo+/-."
  },
  {
    "path": "src/logo/ascii/ubuntu_touch.txt",
    "content": "   ###############\n ##               ##\n##  $2##$1         $2##$1  ##\n##  $2##$1  $2#$1   $2#$1  $2##$1  ##\n##       $2###$1       ##\n ##               ##\n   ###############"
  },
  {
    "path": "src/logo/ascii/ubuntu_unity.txt",
    "content": "               .-/+ooosssssssooo+\\-.\n            ,:+sssssssssssssssssssss+:.\n         -+ssssssssssssssssssss$2.....$1ssss+-\n       .ssssss$2,$1ss$2:cloooooo:$1ss$2.:loooo.$1ssssss.\n    .ssssssss$2loo$1ss$2oooooooc$1ss$2.ooooooooo$1ssssss.\n   .ssssss$2.;looooool:,''.$1ss$2:ooooooooooc$1ssssss.\n  +ssssss$2;loooool;.$1ssssssssss$2ooooooooo$1ssssssss+\n /ssssss$2;clool'$1sssssssssssssss$2ooooooc$1ssss$2,,$1sssss.\n.sssssssssssssssssssssssssssssssssssss$2.:oo,$1sssss.\n.sssss$2.;clol:,.$1ssssssssssssssssssssss$2.loooo'$1ssss.\n+ssss$2:ooooooooo,$1ssssssssssssssssssssss$2'ooool$1ssss+\nosss$2'ooooooooooo.$1ssssssssssssssssssssss$2loooo.$1ssso\nosss$2'ooooooooool$1sssssssssssssssssssssss$2co$1ssssssso\nossss$2,looooooc.$1sssssssssssssssssssssss$2.loooo.$1ssso\n+ssssss$2.,;;;'.$1ssssssssssssssssssssssss$2;ooooc$1ssss+\n.ssssssssssssssssssssssssssssssssssss$2,ooool.$1ssss.\n.sssssss$2.cooooc.$1sssssssssssss$2.,,,,.$1ss$2.cooo.$1sssss.\n `sssssss$2ooooo:.$1ssssssssssss$2oooooooc.$1ss$2:l.$1sssss'\n  +ssssss$2.coooooc,..$1sssssss$2cooooooooo.$1ssssssss+\n   `sssssss$2.:oo$1s$2ooooolc:.$1s$2.ooooooooooo'$1ssssss'\n    .sssssss$2.o$1ss$2`:loooo;$1sss$2,ooooooooc$1sssssss.\n     `sssssssss$2;oooo';:c'$1ssss$2`:ooo:'$1sssssss'\n       -+sssssssssssssssssssssssssssssss+-\n          `:+sssssssssssssssssssssss+:'\n               `-\\+ooosssssssooo+/-'"
  },
  {
    "path": "src/logo/ascii/ultramarine.txt",
    "content": "            .cd0NNNNNNNXOdc.\n        .:xKNNNNNNNNNNNNNNNNKd;.\n      ,dXNNNNNNNNNNNNNNNNNNNNNNNd,\n    'ONNNNNNNNNNNNNNNNNNNNNNNNNNNNO'\n  .xNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNk.\n .0NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN0.\n.0NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN0.\ndNNNNNNNNNNNNWWWWWWWWNNNNNNNNNNNNNNNNNNd\nNNNNNNNNNNNNNW$2MMMMMMMM$1WWNNNNNNNNNNNNNNNN\nNNNNNNNNNNNNNNW$2MMMMMMMMM$1WWNNNNNNNNNNNNNN\nNNNNNNNNNNNNNNW$2MMMMMMMMMMMM$1WNNNNNNNNNNNN\nNNNNNNNNNNWWW$2MMMMMMMMMMMMMMMM$1WWWNNNNNNNX\noNWWWW$2MMMMMMMMMMMMMMMMMMMMMMMMMMMM$1WWWNNo\n OW$2MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM$1WO\n .OW$2MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM$1WO.\n   lNW$2MMMMMMMMMMMMMMMMMMMMMMMMMMMM$1WNl\n    .dNW$2MMMMMMMMMMMMMMMMMMMMMMMM$1WNd.\n      .cKW$2MMMMMMMMMMMMMMMMMMMM$1WKc.\n         'oOXWWW$2MMMMMMMM$1WWWXOl.\n             ;lkXNNNNNNXkl'"
  },
  {
    "path": "src/logo/ascii/ultramarine_small.txt",
    "content": "       @@@@@@@@@@@@\n    @@@@@@@@@@@@@@@@@@\n  @@@@@@@@@@@@@@@@@@@@@@\n @@@@@@@@@@@@@@@@@@@@@@@@\n@@@@@@@@@@@@@@@@@@@@@@@@@@\n@@@@@@@@@*=+*%@@@@@@@@@@@@\n@@@@@@@@@@:   :#@@@@@@@@@@\n@@@@@@@@@#      -#@@@@@@@@\n@@%###*=.         :=*#@@@@\n@@=                    =@@\n @@=                  -@@\n  @@*:              :*@@\n    @%*=:        :=*@@\n       @@@%%##%%@@@\n"
  },
  {
    "path": "src/logo/ascii/unifi.txt",
    "content": "  ___ ___      .__________.__\n |   |   |____ |__\\_  ____/__|\n |   |   /    \\|  ||  __) |  |\n |   |  |   |  \\  ||  \\   |  |\n |______|___|  /__||__/   |__|\n            |_/"
  },
  {
    "path": "src/logo/ascii/univalent.txt",
    "content": "UUUUUUU                   UUUUUUU\nUUUUUUU                   UUUUUUU\nUUUUUUU         A         UUUUUUU\nUUUUUUU        A|A        UUUUUUU\nUUUUUUU       A | A       UUUUUUU\nUUUUUUU      A  |  A      UUUUUUU\nUUUUUUU     A|  |  |A     UUUUUUU\nUUUUUUU    A |  |  | A    UUUUUUU\nUUUUUUU    A |  |  | A    UUUUUUU\nUUUUUUU    A |  |  | A    UUUUUUU\nUUUUUUU    A |  |  | A    UUUUUUU\nUUUUUUU    A |  |  | A    UUUUUUU\nUUUUUUU    A |  |  | A    UUUUUUU\n UUUUUUU   A |  |  | A   UUUUUUU\n  UUUUUUU  A |  |  | A  UUUUUUU\n    UUUUUUUAAAAAAAAAAAUUUUUUU\n       UUUUUUUUUUUUUUUUUUU\n          UUUUUUUUUUUUU"
  },
  {
    "path": "src/logo/ascii/univention.txt",
    "content": "         ./osssssssssssssssssssssso+-\n       `ohhhhhhhhhhhhhhhhhhhhhhhhhhhhy:\n       shhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh-\n   `-//$2sssss$1/hhhhhhhhhhhhhh+$2s$1.hhhhhhhhh+\n .ohhhy$2sssss$1.hhhhhhhhhhhhhh.$2sss$1+hhhhhhh+\n.yhhhhy$2sssss$1.hhhhhhhhhhhhhh.$2ssss$1:hhhhhh+\n+hhhhhy$2sssss$1.hhhhhhhhhhhhhh.$2sssss$1yhhhhh+\n+hhhhhy$2sssss$1.hhhhhhhhhhhhhh.$2sssss$1yhhhhh+\n+hhhhhy$2sssss$1.hhhhhhhhhhhhhh.$2sssss$1yhhhhh+\n+hhhhhy$2sssss$1.hhhhhhhhhhhhhh.$2sssss$1yhhhhh+\n+hhhhhy$2sssss$1.hhhhhhhhhhhhhh.$2sssss$1yhhhhh+\n+hhhhhy$2sssss$1.hhhhhhhhhhhhhh.$2sssss$1yhhhhh+\n+hhhhhy$2sssss$1.hhhhhhhhhhhhhh.$2sssss$1yhhhhh+\n+hhhhhy$2ssssss$1+yhhhhhhhhhhy/$2ssssss$1yhhhhh+\n+hhhhhh:$2sssssss$1:hhhhhhh+$2.ssssssss$1yhhhhy.\n+hhhhhhh+`$2ssssssssssssssss$1hh$2sssss$1yhhho`\n+hhhhhhhhhs+$2ssssssssssss$1+hh+$2sssss$1/:-`\n-hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhho\n :yhhhhhhhhhhhhhhhhhhhhhhhhhhhh+`\n   -+ossssssssssssssssssssss+:`"
  },
  {
    "path": "src/logo/ascii/unknown.txt",
    "content": "       ________\n   _jgN########Ngg_\n _N##N@@\"\"  \"\"9NN##Np_\nd###P            N####p\n\"^^\"              T####\n                  d###P\n               _g###@F\n            _gN##@P\n          gN###F\"\n         d###F\n        0###F\n        0###F\n        0###F\n        \"NN@'\n\n         ___\n        q###r\n         \"\""
  },
  {
    "path": "src/logo/ascii/uos.txt",
    "content": "                      .......\n                  ..............\n              ......................\n           .............................\nuuuuuu    uuuuuu     ooooooooooo       ssssssssss\nu::::u    u::::u   oo:::::::::::oo   ss::::::::::s\nu::::u    u::::u  o:::::::::::::::oss:::::::::::::s\nu::::u    u::::u  o:::::ooooo:::::os::::::ssss:::::s\nu::::u    u::::u  o::::o     o::::o s:::::s  ssssss\nu::::u    u::::u  o::::o     o::::o   s::::::s\nu::::u    u::::u  o::::o     o::::o      s::::::s\nu:::::uuuu:::::u  o::::o     o::::ossssss   s:::::s\nu:::::::::::::::uuo:::::ooooo:::::os:::::ssss::::::s\nu:::::::::::::::uo:::::::::::::::os::::::::::::::s\nuu::::::::uu:::u oo:::::::::::oo  s:::::::::::ss\n    uuuuuuuu  uuuu   ooooooooooo     sssssssssss\n          .............................\n              .....................\n                  .............\n                      ......"
  },
  {
    "path": "src/logo/ascii/urukos.txt",
    "content": "         $3:$4::::::::::::::    $5.\n        $3=#$4*============.   $5:#:\n       $3=##%$4+----------.   $5.###:\n      $3=####.              $5.####:\n     $3=####.                $5.####:\n    $3=###*.   .=$4--------.     $5####:\n   $3=####.   .*#+$4======-       $5####:\n  $3=###*.   .*###+$4====-         $5####:\n $3=###*.   .*#####+$4==-           $5####:\n$3=###*.   .*#######+$4:             $5####:\n$3=###*.   .*#######+$4:             $5####:\n $3=###*.   .*#####+$4==-           $5####:\n  $3=###*.   .*###+$4====-         $5####:\n   $3=####.   .*#+$4======-       $5####:\n    $3=###*.   .=$4--------.    $5.####:\n     $3=####.                $5.####:\n      $3=####.              $5.####:\n       $3=###+$4--------------$5####:\n        $3=#+$4=================-$5:\n         $3:$4::::::::::::::::::."
  },
  {
    "path": "src/logo/ascii/uwuntu.txt",
    "content": "                                  &&\n                               &&&&&&&&\n   ,                  *&&&&&&  &&&&&&&&(\n    &%%%%&&&&     &&&&&&&&&&&&  ,&&&&&\n     %%$2%%%%&&$1&&&   ,&&&&&&&&&&&&&,   %&&&$&&&%%$%%%.\n     &%%%$2%&&&&&$1&&#   &,       &&&&&&$2&&&&&&&%%%$1%%\n      &%%&&$2&&&&$1&&&(               &&&$2&&&&&&%$1%%%\n       &&&&&$2&&&$1&%                  *&&$2&&&&&$1&&%\n    &&&/  &&&&$3\\$1&                    ,$3/$1*.**\n %&&&&&&&&  &&&$3⟩$1.,                *.$3⟨$1\n %&&&&&&&&  &&$3/$1..      $3/    \\$1      ..$3\\$1(&&&&&&\n   #&&&#%%%%.%%%(      $3\\_/\\_/$1      (%%%.%%%%/\n        /%%%%%%%&&*              ,&&&%%%%%%&\n           &&&&&&&&           &&&&&&&&&&&\n            (&&&&&    &&&&&&&&&&&\n            $2%%$1  &   &&&&&&&&&&&&  &&&&&&&\n           $2%%%$1        #&&&&&&#   &&&&&&&&&\n $2%%%%%     %%$1                     #&&&&&(\n$2&%.      %%%$1\n  $2%%%%%%%"
  },
  {
    "path": "src/logo/ascii/valhalla.txt",
    "content": "   .:~:\n  !55P&#:\n ^@G?G!&Y\n !GYYGP55P:      Y5J777!^:.\n.GY?J#G!Y?5. ....P#7Y!##Y??57\n.5JY5PPY5GBBBPYYPBBGBYYP5PJP5:\n^#J?Y@5?J5@@&!GG!&@@YJ?5@PJ7B?\n5PY55G5YY5P!55YY5P7P5JJY5YYJ5Y\n#G?J5@PJJP@~&55Y5@7@5JJG@5J?GB\n  .:.^:^^:!.!:!!:!:!:^^:^.:."
  },
  {
    "path": "src/logo/ascii/vanilla.txt",
    "content": "                .----: \n              .-------.\n             :---::----:\n            .----::-----.\n  .........  :----::-----: ..:::-::::..\n.-----------------::------------------:\n ----::-----------::----------::::---:\n  -----:::--------::-------:::-------\n   :------::::--::...:::::---------:\n    .---------::..    ..:---------.\n      .::-----::..    .::----::.\n        .:------:.......:-------:\n       .--------::::::::-:::-------.\n      .-------::-----.:-----::------.\n     -----::------:   :------::-----\n    :--::--------:     .-------::---:\n   :----------::         .:----------\n    :--------:             :--------:"
  },
  {
    "path": "src/logo/ascii/vanilla2.txt",
    "content": "              .\n            x/A\\x\n           Z#@#?P`.\n          /@$R/.:.',\n     _    ($@`.:::.)    _\n_-=t'''`-.g$(.::::.!-aZ#$#Kko,\nV$#6..::::.~l.::.<&#p***q##$p'\n'9#$b,:::::::::.%P~'.:::.`~v'\n \"<#$&b,.':::'./'.:::::::.>'\n   `~*q@#&b+=- -=+x.,''_.'\n     z+'.:::',).:.`~q@6x,\n    /.:::::',Z!.:::.`*q&x\n   i'.:::',<J?`.':::.\\#$&,\n   lo._,xd$#%' \\'::::.@$#)\n   V@#$##@%P'   ~+,''/#$#!\n   `~***~^'       `'\"****"
  },
  {
    "path": "src/logo/ascii/vanilla_small.txt",
    "content": "      ,x.\n     ;&?^$2.$1\n.-e~^+7'  $2)$1adbx,\n \\#\\$2.  $1`,*~ ~*/\n  `~*+-'-<ay,$2^$1 \n  $2,/  $1,%$2\\ $1`\\&,\n  !&UP*  $2+.$1/%?"
  },
  {
    "path": "src/logo/ascii/venom.txt",
    "content": "`-`\n -yys+/-`\n  `oyyyyy: /osyyyyso+:.\n    /yyyyy+`+yyyyyyyyyys/.\n    .-yyyyys.:+//+oyyyyyyyo.\n  `oy/`oyyyyy/      ./syyyyy:\n  syyys`:yyyyyo`       :yyyyy:\n /yyyyo  .syyyyy-       .yyyyy.\n yyyyy.    +yyyyy/       /yyyy/\n`yyyyy      :yyyyys`     -yyyyo\n yyyyy.      `syyyyy-    /yyyy/\n /yyyyo        /yyyyy+  .yyyyy.\n  syyyys.       -yyyyys.:yyyy:\n  `oyyyyyo-`     `oyyyyy:.sy:\n    :syyyyyyso+/++`/yyyyyo``\n      -oyyyyyyyyyyy-.syyyys.\n         -/+osyyyyso.`+yyyyy/\n                       .-/+syo`\n                             `."
  },
  {
    "path": "src/logo/ascii/venom_small.txt",
    "content": " ++**\n  *===**====+*\n   *====*   +===+\n *==*+===*    *===*\n*===* *===+    *===*\n*===*  +===+   *===*\n*===*   +===*  *===*\n *===*    *===+*==*\n   +===+   *===+=*\n      *+====**===*\n               **++"
  },
  {
    "path": "src/logo/ascii/vincentos.txt",
    "content": "                             __agggg\n                          _g@@@@@@@P\n                         a@@@@@@@@F\n                        _@@@@@@@@F\n   $2a@@@@@@_            _@@@@@@_$1`\n $2_a@@@~@@@@,           @@@@~M@@&_\n_@@@~  `@@@@          @@@@~  `@@@_\n@@@\"    ~@@@@        @@@@~    \"@@@\n@@@      ~@@@@      @@@@F      @@@\n@@@       4@@@@    @@@@F       @@@\n@@@_       4@@@b  @@@@F       _@@@\n\"@@@_       4@@@yy@@@F       _@@@\"\n \"R@@g_      5@@@@@@@      _g@@@\"\n   ~@@@gy_    @@@@@@    _yg@@@~\n     `T@@@@gyyy@@@@yyag@@@@F~\n         ~~FRR@@@@@@RPF~~"
  },
  {
    "path": "src/logo/ascii/vnux.txt",
    "content": "              `\n           ^[XOx~.\n        ^_nwdbbkp0ti'\n        <vJCZw0LQ0Uj>\n$2          _j>!vC1,,\n     $4,$2   ,CY$3O$2t$3O$21(l;\"\n`$4~-{r(1I$2 ^$1/zmwJuc:$2I^\n'$4?)|$1U$4/}-$2 ^$3f$1OCLLOw$3_$2,;\n ,$4i,``. $2\",$3k%ooW@$d\"$2I,'\n  '    ;^$3u$$$$$$$$$$$$$$$$^<$2:^\n   ` .>>$3($$$$$5$@@@@$$$$$3$nl$2[::\n    `!}?$3B$$$5%&WMMW&%$$$3$1}-$2}\":\n    ^?j$3Z$$$5WMMWWWWMMW$$$3ofc$2;;`\n    <~x&$3$$$5&MWWWWWWWWp$3-$5l>[<\n$1 'ljmwn$2~tk8$5MWWWWM8O$2X$1r$2+]nC$1[\n!JZqwwdX$2:^C8$5#MMMM@$2X$1Odpdpq0<\n<wwwwmmpO$21$30@%%%%8$2d$1nqmwmqqqJl\n?QOZmqqqpb$2t[run/?!$10pwqqQj-,\n ^:l<{nUUv>      ^x00J(\"\n                   ^\""
  },
  {
    "path": "src/logo/ascii/void.txt",
    "content": "                __.;=====;.__\n            _.=+==++=++=+=+===;.\n             -=+++=+===+=+=+++++=_\n        .     -=:``     `--==+=++==.\n       _vi,    `            --+=++++:\n      .uvnvi.       _._       -==+==+.\n     .vvnvnI`    .;==|==;.     :|=||=|.\n$2+QmQQm$1pvvnv;$2 _yYsyQQWUUQQQm #QmQ#$1:$2QQQWUV$QQm.\n $2-QQWQW$1pvvo$2wZ?.wQQQE$1==<$2QWWQ/QWQW.QQWW$1(:$2 jQWQE\n  $2-$QQQQmmU'  jQQQ$1@+=<$2QWQQ)mQQQ.mQQQC$1+;$2jWQQ@'\n   $2-$WQ8Y$1nI:$2   QWQQwgQQWV$1`$2mWQQ.jQWQQgyyWW@!\n     $1-1vvnvv.     `~+++`        ++|+++\n      +vnvnnv,                 `-|===\n       +vnvnvns.           .      :=-\n        -Invnvvnsi..___..=sv=.     `\n          +Invnvnvnnnnnnnnvvnn;.\n            ~|Invnvnvvnvvvnnv}+`\n               -~|{*l}*|~"
  },
  {
    "path": "src/logo/ascii/void2.txt",
    "content": "                        ..........\n                   .::::::::::::::::::..\n               ..:::::::::::::::::::::::::.\n                '::::::::::::::::::::::::::::.\n                  ':::::''      '':::::::::::::.\n$3         ..         $1'                '':::::::::.\n$3        .||.                            $1':::::::::\n$3       .|||||.                            $1'::::::::\n$3      .|||||||:                             $1::::::::\n$3      |||||||:          $1.::::::::.           ::::::::\n$2 ######$3||||||'   $2##^ v##########v$1::. $2#####  #############v\n$2  ######$3||||| $2##^ v####$1::::::$2####v$1::$2#####  #####$1:::::$2#####\n$2   ######$3||$2##^   #####$1::::::$2#####$1::$2#####  #####$1:::::$2######\n$2    ######^$3||    $2#####$1:::::$2####^$1::$2#####  #####$1:::::$2#####^\n$2     ##^$3|||||    $2^###########^$1:::$2#####  ##############^\n$3      |||||||:          $1'::::::::'          .::::::::\n$3      '|||||||:                            $1.::::::::'\n$3       '|||||||:.                           $1'::::::\n$3        '||||||||:.                           $1':::\n$3         ':|||||||||.                .          $1'\n$3           '|||||||||||:...    ...:||||.\n             ':||||||||||||||||||||||||||.\n                ':|||||||||||||||||||||||''\n                   '':||||||||||||||:''\n                          ''''''\n"
  },
  {
    "path": "src/logo/ascii/void2_small.txt",
    "content": "    _______\n _ \\______ -\n| \\  ___  \\ |\n| | /   \\ | |\n| | \\___/ | |\n| \\______ \\_|\n -_______\\"
  },
  {
    "path": "src/logo/ascii/void_small.txt",
    "content": "    ____\n  'pfPfp.%\n//  _._  \\\\\nUU |===| UU\n\\\\  ^~^  //\n `0PpppP'\n   `````"
  },
  {
    "path": "src/logo/ascii/vzlinux.txt",
    "content": "             $1.::::::::.$2\n`/////`      $1:zzzzzzzz$2        ://///-\n VVVVVu`         $1-zzz`$2       /VVVVV.\n `dVVVVV        $1.zzz`$2       :VVVVV:\n  `VVVVVo       $1zzz$2        -VVVVV/\n   .VVVVV\\     $1zzz/$2       .VVVVV+\n    -VVVVV:   $1zzzzzzzzz$2  .dVVVVs\n     \\VVVVV-  $1`````````$2  VVVVVV\n      +VVVVV.           sVVVVu`\n       oVVVVd`         +VVVVd`\n        VVVVVV        /VVVVV.\n        `uVVVVs      -VVVVV-\n         `dVVVV+    .VVVVV/\n          .VVVVV\\  `dVVVV+\n           -VVVVV-`uVVVVo\n            :VVVVVVVVVVV\n             \\VVVVVVVVu\n              oVVVVVVd`\n               sVVVVV.\n                ----."
  },
  {
    "path": "src/logo/ascii/wii_linux.txt",
    "content": "'''''''            `~;:`            -''''''   ~kQ@@g\\      ,EQ@@g/\nh@@@@@@'          o@@@@@9`         `@@@@@@D  `@@@@@@@=     @@@@@@@?\n'@@@@@@X         o@@@@@@@D         v@@@@@@:   R@@@@@@,     D@@@@@@_\n t@@@@@@'       _@@@@@@@@@;       `Q@@@@@U     ;fmo/-       ;fmo/-\n `Q@@@@@m       d@@@@@@@@@N       7@@@@@@'\n  L@@@@@@'     :@@@@@&@@@@@|     `Q@@@@@Z     :]]]]]v      :]]]]]v\n   Q@@@@@X     R@@@@Q`g@@@@Q     f@@@@@Q-     z@@@@@Q      v@@@@@Q\n   r@@@@@@~   ;@@@@@/ ;@@@@@L   `@@@@@@/      z@@@@@Q      v@@@@@Q\n    d@@@@@q   M@@@@#   H@@@@Q   ]@@@@@Q       z@@@@@Q      v@@@@@Q\n    ,@@@@@@, >@@@@@;   _@@@@@c `@@@@@@>       z@@@@@Q      v@@@@@Q\n     X@@@@@U Q@@@@R     Z@@@@Q`{@@@@@N        z@@@@@Q      v@@@@@Q\n     .@@@@@@S@@@@@:     -@@@@@e@@@@@@:        z@@@@@Q      v@@@@@Q\n      {@@@@@@@@@@U       t@@@@@@@@@@e         z@@@@@Q      v@@@@@Q\n      `Q@@@@@@@@@'       `Q@@@@@@@@@-         z@@@@@Q      v@@@@@Q\n       :@@@@@@@@|         ;@@@@@@@@=          z@@@@@Q      v@@@@@Q\n        '2#@@Q6:           ,eQ@@QZ~           /QQQQQg      \\QQQQQN"
  },
  {
    "path": "src/logo/ascii/windows.txt",
    "content": "$1        ,.=:!!t3Z3z.,\n       :tt:::tt333EE3\n$1       Et:::ztt33EEEL$2 @Ee.,      ..,\n$1      ;tt:::tt333EE7$2 ;EEEEEEttttt33#\n$1     :Et:::zt333EEQ.$2 $EEEEEttttt33QL\n$1     it::::tt333EEF$2 @EEEEEEttttt33F\n$1    ;3=*^```\"*4EEV$2 :EEEEEEttttt33@.\n$3    ,.=::::!t=., $1`$2 @EEEEEEtttz33QF\n$3   ;::::::::zt33)$2   \"4EEEtttji3P*\n$3  :t::::::::tt33.$4:Z3z..$2  ``$4 ,..g.\n$3  i::::::::zt33F$4 AEEEtttt::::ztF\n$3 ;:::::::::t33V$4 ;EEEttttt::::t3\n$3 E::::::::zt33L$4 @EEEtttt::::z3F\n$3{3=*^```\"*4E3)$4 ;EEEtttt:::::tZ`\n$3             `$4 :EEEEtttt::::z7\n                 \"VEzjt:;;z>*`"
  },
  {
    "path": "src/logo/ascii/windows_11.txt",
    "content": "$1/////////////////  $2/////////////////\n$1/////////////////  $2/////////////////\n$1/////////////////  $2/////////////////\n$1/////////////////  $2/////////////////\n$1/////////////////  $2/////////////////\n$1/////////////////  $2/////////////////\n$1/////////////////  $2/////////////////\n$1/////////////////  $2/////////////////\n\n$3/////////////////  $4/////////////////\n$3/////////////////  $4/////////////////\n$3/////////////////  $4/////////////////\n$3/////////////////  $4/////////////////\n$3/////////////////  $4/////////////////\n$3/////////////////  $4/////////////////\n$3/////////////////  $4/////////////////\n$3/////////////////  $4/////////////////"
  },
  {
    "path": "src/logo/ascii/windows_11_small.txt",
    "content": "$1lllllll  $2lllllll\n$1lllllll  $2lllllll\n$1lllllll  $2lllllll\n\n$3lllllll  $4lllllll\n$3lllllll  $4lllllll\n$3lllllll  $4lllllll"
  },
  {
    "path": "src/logo/ascii/windows_2025.txt",
    "content": "$1      ##%%%%%%%%%  $2%%%%%%%%%##\n$1    ###%%%%%%%%%%  $2%%%%%%%%%%###\n$1  ####%%%%%%%%%%%  $2%%%%%%%%%%%####\n$1 ##%%%%%%%%%%%%%%  $2%%%%%%%%%%%%%%##\n$1#%%%%%%%%%%%%%%%%  $2%%%%%%%%%%%%%%%%#\n$1%%%%%%%%%%%%%%%%%  $2%%%%%%%%%%%%%%%%%\n$1%%%%%%%%%%%%%%%%%  $2%%%%%%%%%%%%%%%%%\n$1%%%%%%%%%%%%%%%%%  $2#%%%%%%%%%%%%%%%%\n\n$3%%%%%%%%%%%%%%%%%  $4#%%%%%%%%%%%%%%%%\n$3%%%%%%%%%%%%%%%%%  $4%%%%%%%%%%%%%%%%%\n$3%%%%%%%%%%%%%%%%%  $4%%%%%%%%%%%%%%%%%\n$3%%%%%%%%%%%%%%%%%  $4%%%%%%%%%%%%%%%%#\n$3 ###%%%%%%%%%%%%%  $4%%%%%%%%%%%%%%%##\n$3  ####%%%%%%%%%%%  $4%%%%%%%%%%%#%####\n$3    ##%#%%%%%%%%%  $4%%%%%%%%%%%######\n$3      ##%%%%%%%%%  $4%%%%%%%%%########"
  },
  {
    "path": "src/logo/ascii/windows_8.txt",
    "content": "                                $2..,\n                    ....,,:;+ccllll\n$1      ...,,+:;  $2cllllllllllllllllll\n$1,cclllllllllll  $2lllllllllllllllllll\n$1llllllllllllll  $2lllllllllllllllllll\n$1llllllllllllll  $2lllllllllllllllllll\n$1llllllllllllll  $2lllllllllllllllllll\n$1llllllllllllll  $2lllllllllllllllllll\n$1llllllllllllll  $2lllllllllllllllllll\n\n$3llllllllllllll  $4lllllllllllllllllll\n$3llllllllllllll  $4lllllllllllllllllll\n$3llllllllllllll  $4lllllllllllllllllll\n$3llllllllllllll  $4lllllllllllllllllll\n$3llllllllllllll  $4lllllllllllllllllll\n$3`'ccllllllllll  $4lllllllllllllllllll\n$3       `' \\*::  $4:ccllllllllllllllll\n                       ````''*::cll\n                                 ``"
  },
  {
    "path": "src/logo/ascii/windows_95.txt",
    "content": "$6                        ___\n                   .--=+++++=-:.\n.              _ *%@@@@@@@@@@@@@@*\n *:+:.__ :+* @@@ @\"$5_*&%$6@@$4%&&&*$6\"@@@\n  \"+.-#+ +%* \" _ $5++&&&%$6@@$4%&&&&&#$6@@\n$5\"          , $6%@@ $5&&&&&%$6@@$4%&&&&&#$6@@\n$5   *  oo  *# $6\" _ $5&&&&&%$6@@$4%&&&&&#$6@@\n$5\"          , $6%@@ $5&&&&\"$6@@@@#*$4\"&&&$6@@\n.$5  *  oo  *# $6\" _ %@@@@@@@@@@@@@@@@\n *:+:.__ :=* %@@ @\"$1**&%$6@@$3%&&&*$6\"@@@\n  \"+.-#+ +%* \" _ $1&&&&&%$6@@$3%&&&&&#$6@@\n$1\"          , $6%@@ $1&&&&&%$6@@$3%&&&&&#$6@@\n$1   *  oo  *# $6\" _ $1&&&&&%$6@@$3%&&&&&#$6@@\n$1\"          , $6%@@ $1&&*\"$6%@@@@@@$3\"*%&$6@@\n.$1  *  oo  *# $6\" _ @@@@@@@@@@@@@@@@@\n *:+:.__ :+# @@@ @%#=+\"\"\"\"\"\"+==%#@\n  \"+.-#+ +%* %+\" \"             \":@\n       \" \""
  },
  {
    "path": "src/logo/ascii/wolfos.txt",
    "content": "__        __    _  __  ___  ____  \n\\ \\      / /__ | |/ _|/ _ \\/ ___| \n \\ \\ /\\ / / _ \\| | |_| | | \\___ \\ \n  \\ V  V / (_) | |  _| |_| |___) |\n   \\_/\\_/ \\___/|_|_|  \\___/|____/ \n"
  },
  {
    "path": "src/logo/ascii/xcp_ng.txt",
    "content": "                                $3%$4#\n                        $1(((($3%%%%%$4&&\n                     $1#(((((((((($4&&&&\n                  $1((((((((((($2#####$4&&\n                $1((((($5%.....$2%#######\n      $1((((    (((((($5%.......$2%####,\n    $1%((((((& (((((((($5%.....$2%####\n     $1(((((((((((((($2###########\n    $1((((  ((((((($2###########\n     $1(   /(((($2###########\n       $6,,*..$2##########\n     $6,,,******$2##   ######\n    $6,,,*,,,,    $2## ######\n$6.  .,,,,       $2###  ##\n $6..."
  },
  {
    "path": "src/logo/ascii/xenia.txt",
    "content": "                                      **\n                                 * ** */\n $2@$1/////           / *** ***   **** ***/\n    //////////////**** ** * ** *** **/\n  ,, ,,//////////* ****** ** ** **  /\n   , , ,,, /////******* *********  /\n      , ,,,,,  ** **  ***********\n            ., ,,,**** ******* *** ///\n             ,, *********** ** ////\n               ,,,,, ******* //// //\n                    ,,,,,,. ///// //\n                      ,,,  , ///////\n                         ,,,, /////\n                            ,, / /\n                                /\n"
  },
  {
    "path": "src/logo/ascii/xenia_old.txt",
    "content": "$2      ,c.                       .c;\n$2    .$1KMMMk$2....             ....$1kMMMK$2.\n$2   .$1WMMMMMX$2.....         .....$1KMMMMMW.\n$1   XMMMMMMM0$2.....        ....$1OMMMMMMMN\n$1  dMMMMMMMMM;$2.... ..... ....,$1MMMMMMMMMd\n$1  WMMMMMMMMMl;$3okKKKKKKKKKOo$1;cMMMMMMMMMM\n$1 'MMMMMMMNX$2K0$3KKKKKKKKKKKKKKK$20K$1XNMMMMMMM;\n$1 oMMMMMMM$2Oxo$3KKKKKKKKKKKKKKKKK$2oxO$1MMMMMMMd\n$1 dMMMMMMM$2dxxx$3KKKKKKKKKKKKKKK$2xxxd$1NMMMMMMk\n$1 :MMMMX0$2xxxxxx$30KKKKKKKK0KK0$2xxxxxx0$1XMMMMc\n$1  MMMO$2xxxxxxxxdx$3kdd$20x0$3ddk$2xdxxxxxxxx$1OMMM\n$1 ;$2xxkxddxxxxdodxxxxdxdxxxxdodxxxxddxkxx$1;\n$1dxd$2KMMMWXo$1'.....'$2cdxxxdc$1'.....'$2lXWMMMX$1dxd\n$1cxd$2XMMMN$1,..........$2dxd$1'.........'$2XMMMN$1dxl\n$1 .xx$2WMMl$1...''....'.;k:.'....''...$2lMMW$1xx.\n$1..:kXMMx..'....''..kMk..''....'..xMMXkc..\n$1 dMMMMMMd.....'...xMMMx...''....dMMMMMMx\n$1    kMMMMWOoc:coOkolllokOoc:coOWMMMMO\n$1         .MMMMMMMMl$2...$1lNMMMMMMM.\n$1            KMMMMMMX$2l$1KMMMMMMX\n$1               .MMMMMMMMM."
  },
  {
    "path": "src/logo/ascii/xeroarch.txt",
    "content": "$5                  ████\n$5                ███$2▓▓$5███\n$5               ███$2▓▓▓▓$5███\n$5              ███$2▓▓▓▓▓▓$5███\n$5             ███$2▓▓▓▓▓▓▓▓$5███\n$5            ███$2▓▓▓▓▓▓▓▓▓▓$5███\n$5           ███$2▓▓▓▓▓▓▓▓▓▓▓▓$5███\n$5          ███$2▓▓▓▓▓▓$5██$2▓▓▓▓▓▓$5▓██\n$5         ███$2▓▓▓▓▓▓$5████$2▓▓▓▓▓▓$5▓██\n$5        ██▓$2▓▓▓▓▓▓$5█▓$3▓▓$5▓█$2▓▓▓▓▓▓$5▓██\n$5       █████▓▒▒▒██$3▓$4▒▒$3▓$5██▒▒▒▓█████\n$5     ███$6▓▓▓▓▓$5████$3▓$4▒▒▒▒$3▓$5▓███$6▓▓▓▓▓$5███\n$5    ███$6▓▓▓▓▓▓▓▓▓$5███▓▓███$6▓▓▓▓▓▓▓▓▓$5███\n$5   ███$6▓▓▓▓▓▓▓$2▓▓$6▓▓▓▓▓$5█$6▓▓▓▓▓▓▓▓▓▓▓▓▓$5███\n$5  ███$6▓▓▓▓▓▓$2▓▓$5▒▒$6$2▓▓$6▓▓▓$5▓$6▓▓▓▓$4()$6▓▓▓▓▓▓▓▓$5███\n$5 ███$6▓▓▓▓▓$2▓▓$5▒▒▒▒▒▒$6$2▓▓$6▓$5▓$6▓▓$3()$6▓▓$3()$6▓▓▓▓▓▓▓$5███\n$5███$6▓▓▓▓▓▓▓▓$2▓▓$5▒▒$6$2▓▓$6▓▓▓$5▓$6▓▓▓▓$4()$6▓▓▓▓▓▓▓▓▓▓$5███\n$5██$6▓▓▓▓▓▓▓▓▓▓▓$2▓▓$6▓▓▓▓▓$5▓$6▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓$5██\n$5████████████████████████████████████████"
  },
  {
    "path": "src/logo/ascii/xferience.txt",
    "content": "           ``--:::::::-.`\n        .-/+++ooooooooo+++:-`\n     `-/+oooooooooooooooooo++:.\n    -/+oooooo/+ooooooooo+/ooo++:`\n  `/+oo++oo.   .+oooooo+.-: +:-o+-\n `/+o/.  -o.    :oooooo+ ```:.+oo+-\n`:+oo-    -/`   :oooooo+ .`-`+oooo/.\n.+ooo+.    .`   `://///+-+..oooooo+:`\n-+ooo:`                ``.-+oooooo+/`\n-+oo/`                       :+oooo/.\n.+oo:            ..-/. .      -+oo+/`\n`/++-         -:::++::/.      -+oo+-\n ./o:          `:///+-     `./ooo+:`\n  .++-         `` /-`   -:/+oooo+:`\n   .:+/:``          `-:ooooooo++-\n     ./+o+//:...../+oooooooo++:`\n       `:/++ooooooooooooo++/-`\n          `.-//++++++//:-.`\n               ``````"
  },
  {
    "path": "src/logo/ascii/xinux.txt",
    "content": "$1     * $2=======    =====       ======= $1*\n$1   **** $2=======  =======     ======= $1***\n$1  ****** $2=======  =======   ======= $1******\n$1  ******* $2======== ======= ======= $1*******\n$1   ******* $2======== ============= $1*******\n$1    ******* $2======== ==========  $1*******\n$1     ******* $2======== ========  $1*******\n$1     ******** $2======= ======= $1*********\n$1    *********** $2==== ======= $1***********\n$1   ************* $2== ======= $1*************\n$1  ******* ******* $2======== $1******* *******\n$1 *******   ******* $2====== $1*******   *******\n$1*******     ******* $2==== $1*******     *******\n$1 *****       ******* $2== $1*******       *****\n"
  },
  {
    "path": "src/logo/ascii/xray_os.txt",
    "content": "               $1rrrrrraaaaaaaaaaaayyyy\n           $1xxrrrrrrrraaaaaaaaaaaayyyyyyyyy\n        $1xxxxxrrrrrrrraaaaaaaaaaaayyyyyyy$3yyyyy$2yyyyyyyyyy\n      $1xxxxxxxrrrrrrrraaaaa $2aaaaayyyyyyyyyyyyyyyyyyy\n    $1xxxxxx$3xxx$1rrrrrrrraaaa $2aaaaaaayyyyyyyyyyyyyyyyy\n  $1xxxxxx$3xxxxxr$1rrrrrrraa $2aaaaaaaaay$3yyyyyyyyy$2yyyy $1yy\n $1xxxxxxx$3xxx$1xxrrrrrrrra $2aaaaaaaaa$3ayyyyyyyyyyyy$1yyyyyy\n $1xxxxxxxxxxxxrrrrrrrr $2aaaaaaaaaaa$3yyyyyyyyyyyy$1yyyyyyy\n$1xxxxxxxxxxxxxrrrrrr $2raaaaaaaaaaaayyy$3yyyyyyyy$1yyyyyy$1yyy\n$1xxxxxxxxxxxxxrrrrr $2rraaaaaaaaaaaayyyyy$3yy$2yyyyyy $1yyyyyy\n$1xxxxxxxx$3xxxx$1xrrrrr$2rr$3raaaaaaa$2aaaaayyyyyyyyyy $1yyyyyyyyy\n$1xxxxxxx$3xxxx$1xxrrrrrrr$3raaaaaa$2aaaaaayyyyyyy $1yyyyyyyyyyyy\n$1xxxxxxx$3xxx$1xxxrrrrrrrr$3aaaaaa$2aaaaaayyyy $1yyyyyyyyyyyyyy\n $1xxxxxxxxxxxxrrrrrrrra $2aaaaaaaaaay $1yyyyyyyyyyyyyyyy\n  $1xxxxxxxxxxxrrrrrrr $2aaaaaaaaaaaayyyy$1yyyyyyyyyyyyy\n   $1xxxxxxx$3xxxrr$1rrrr $2raaaaaaaaaaaa $1yyyyyyyyyyyyyyy\n     $1xxxxxxxxrrrr $2rrraaaaaaaaa $1aayyyyyyyyyyyyyy\n       $1xxxxxxrrrrrrr $2aaaaaa  $1aaaayyyyyyyyyyyy\n          $1xxxrrrrrr $2raaa  $1aaaaaaayyyyyyyyy\n              $1rrrr $2rr  $1aaaaaaaaaayyyyyy\n                 $2r   $1aaaaaaaaaa"
  },
  {
    "path": "src/logo/ascii/xubuntu.txt",
    "content": "             __ygg@@@@@@@@@ggy__\n         _yg@@@@@@@@@@@@@@@@@@@@@gy_\n      _a@@@@@@@@@@@@@@@@@@@@@@@@@@@@@y_\n    _a@@@@@@@@@@@@@@@@@@@@@@@$2#$1@@@@@@@@@g_\n   a@@@@@@@@@@@@@$2###$1@@@@@@@@$2##$1@@@@$2##$1@@@@@k\n  g@@@@@@@$2###$1@@@$2#####$1@@@@@@@$2##$1@@$2###$1@@@@@@@k\n a@@@@@@@@$2#####$1@$2#####$1@@@@@@$2##$1@@$2###$1@@@@@@@@@k\nj@@@@@@@@@$2############$1@@@@@$2##$1@$2###$1@@@@@@@@@@@k\ng@@@@@@@@@$2#####################$1@@@@@@@@@@@@@@\n@@@@@@@@@$2##########################$1@@@@@@@@@@\n0@@@@@@@@$2###########################$1@@@@@@@@@\n~@@@@@@@$2############################$1@@@@@@@@F\n 9@@@@@@$2##########################$1@@@@@@@@@P\n  4@@@@@@$2######################$1@@@@@@@@@@@P\n   ~@@@@@@$2################$1@@@@@@@@@@@@@@@F\n    `4@@@@@@$2#######$1@@@@@@@@@@@@@@@@@@@@P`\n      `~@@@@@@@@@@@@@@@@@@@@@@@@@@@@@F`\n         ~~4@@@@@@@@@@@@@@@@@@@@@P~~\n             `~~=R@@@@@@@@@P=~~~"
  },
  {
    "path": "src/logo/ascii/yiffos.txt",
    "content": "           NK\n            NxKXW\n            WOlcoXW\n            0olccloxNW\n            XxllllllloxOKNW\n            Nklllllllllllodk0XWW\n           N0dllllllllllllllodxOKNW\n         WKxlllllllllllllllllllooxOKN\n       WKdllllllllllllllooooooooooooo\n      Nxllllllllllllllloooooooooo$2oooo\n$1    XXNOolllllllllllloooooooooo$2oooooo\n$1  WX0xolllllllllllooooooooooo$2oooooooo\n$1XWN0xolllllllllloooooooo$2ooooooooooooo\n$1  Kkdolllllllooooddddd$2doooooooooooddo\n$1       K00XNW$2      WX0xdooooooddddddd\n                      WXOxdoooooodddd\n                         WXOxdddddddd\n                               NX0ddd\n                                 WN0d"
  },
  {
    "path": "src/logo/ascii/zorin.txt",
    "content": "        `osssssssssssssssssssso`\n       .osssssssssssssssssssssso.\n      .+oooooooooooooooooooooooo+.\n\n\n  `::::::::::::::::::::::.         .:`\n `+ssssssssssssssssss+:.`     `.:+ssso`\n.ossssssssssssssso/.       `-+ossssssso.\nssssssssssssso/-`      `-/osssssssssssss\n.ossssssso/-`      .-/ossssssssssssssso.\n `+sss+:.      `.:+ssssssssssssssssss+`\n  `:.         .::::::::::::::::::::::`\n\n\n      .+oooooooooooooooooooooooo+.\n       -osssssssssssssssssssssso-\n        `osssssssssssssssssssso`"
  },
  {
    "path": "src/logo/ascii/zos.txt",
    "content": "             //  OOOOOOO  SSSSSSS\n            //  OO    OO SS\n    zzzzzz //  OO    OO SS\n      zz  //  OO    OO   SSSS\n    zz   //  OO    OO       SS\n  zz    //  OO    OO       SS\nzzzzzz //   OOOOOOO  SSSSSSS"
  },
  {
    "path": "src/logo/ascii/zraxyl.txt",
    "content": "       __________\n     / __________ \\\n   / /             \\ \\\n / /   .-\"````\"-.   \\ \\\n| | |/            \\| | |\n| |    Z R A X Y L   | |\n| | \\ ¯¯¯¯¯¯¯¯¯¯¯¯ / | |\n \\ \\ `-.___..___.-' / /\n  '.'._         _.' .'\n    '-.| M - C | .-'"
  },
  {
    "path": "src/logo/builtin.c",
    "content": "#include \"logo.h\"\n#include \"logo_builtin.h\"\n#include \"common/color.h\"\n\nconst FFlogo ffLogoUnknown = {\n    .names = {\"unknown\"},\n    .lines = FASTFETCH_DATATEXT_LOGO_UNKNOWN,\n    .colors = {\n        FF_COLOR_FG_DEFAULT,\n    },\n};\n\nstatic const FFlogo A[] = {\n    // Adélie\n    {\n        .names = {\"Adélie\", \"Adelie\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ADELIE,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_CYAN,\n        },\n    },\n    // AerOS\n    {\n        .names = {\"aerOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_AEROS,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_CYAN,\n        },\n    },\n    // Aeon\n    {\n        .names = {\"Aeon\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_AEON,\n        .colors = {\n            FF_COLOR_FG_256 \"36\",\n            FF_COLOR_FG_256 \"36\",\n        },\n    },\n    // AerynOS\n    {\n        .names = {\"AerynOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_AERYNOS,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n            FF_COLOR_FG_MAGENTA,\n        },\n    },\n    // Afterglow\n    {\n        .names = {\"Afterglow\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_AFTERGLOW,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_BLUE,\n        },\n    },\n    // AIX\n    {\n        .names = {\"aix\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_AIX,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // AlmaLinux\n    {\n        .names = {\"Almalinux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ALMALINUX,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_LIGHT_YELLOW,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_LIGHT_GREEN,\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // Alpine\n    {\n        .names = {\"Alpine\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ALPINE,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Alpine2\n    {\n        .names = {\"Alpine2\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ALPINE2,\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // AlpineSmall\n    {\n        .names = {\"Alpine_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_ALPINE_SMALL,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Alpine2Small\n    {\n        .names = {\"alpine2_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT | FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_ALPINE2_SMALL,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Alpine3Small\n    {\n        .names = {\"alpine3_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT | FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_ALPINE3_SMALL,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Alter\n    {\n        .names = {\"Alter\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ALTER,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // ALTLinux\n    {\n        .names = {\"ALTLinux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ALTLINUX,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_BLACK,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // Amazon\n    {\n        .names = {\"Amazon\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_AMAZON,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_WHITE,\n        }\n    },\n    // AmazonLinux\n    {\n        .names = {\"Amazon Linux\", \"amzn\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_AMAZON_LINUX,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_256 \"178\",\n        }\n    },\n    // Amiga\n    {\n        .names = {\"Amiga\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_AMIGA,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_LIGHT_RED,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_LIGHT_YELLOW,\n            FF_COLOR_FG_GREEN,\n        }\n    },\n    // AmogOS\n    {\n        .names = {\"AmogOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_AMOGOS,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // Anarchy\n    {\n        .names = {\"Anarchy\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ANARCHY,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Android\n    {\n        .names = {\"android\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ANDROID,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // AndroidSmall\n    {\n        .names = {\"android_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_ANDROID_SMALL,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // AnduinOS\n    {\n        .names = {\"anduinos\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ANDUINOS,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Antergos\n    {\n        .names = {\"Antergos\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ANTERGOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // Antix\n    {\n        .names = {\"antiX\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ANTIX,\n        .colors = {\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // AnushOS\n    {\n        .names = {\"AnushOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ANUSHOS,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_BLACK,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // AoscOsRetro\n    {\n        .names = {\"Aosc OS/Retro\", \"aoscosretro\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_AOSCOSRETRO,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // AoscOsRetro_small\n    {\n        .names = {\"Aosc OS/Retro_small\", \"aoscosretro_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_AOSCOSRETRO_SMALL,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // AoscOS\n    {\n        .names = {\"Aosc OS\", \"aoscos\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_AOSCOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_BLACK,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_YELLOW,\n        },\n    },\n    // AoscOS_old\n    {\n        .names = {\"Aosc OS_old\", \"aoscos_old\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_AOSCOS_OLD,\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Aperture\n    {\n        .names = {\"Aperture\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_APERTURE,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Apple\n    {\n        .names = {\"Apple\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MACOS,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_LIGHT_RED,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // AppleSmall\n    {\n        .names = {\"Apple_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_MACOS_SMALL,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // Apricity\n    {\n        .names = {\"Apricity\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_APRICITY,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // ArchBox\n    {\n        .names = {\"ArchBox\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ARCHBOX,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Archcraft\n    {\n        .names = {\"Archcraft\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ARCHCRAFT,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // Archcraft2\n    {\n        .names = {\"Archcraft2\"},\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_ARCHCRAFT2,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // Arch\n    {\n        .names = {\"arch\", \"archmerge\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ARCH,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_CYAN,\n        },\n    },\n    // Arch2\n    {\n        .names = {\"arch2\"},\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_ARCH2,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_CYAN,\n        },\n    },\n    // Arch3\n    {\n        .names = {\"arch3\"},\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_ARCH3,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_CYAN,\n        },\n    },\n    // ArchSmall\n    {\n        .names = {\"arch_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_ARCH_SMALL,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_CYAN,\n        },\n    },\n    // ArchOld\n    {\n        .names = {\"arch_old\"},\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_ARCH_OLD,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n        .colorKeys = FF_COLOR_FG_BLUE,\n    },\n    // Archlabs\n    {\n        .names = {\"ARCHlabs\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ARCHLABS,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // ArchStrike\n    {\n        .names = {\"ArchStrike\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ARCHSTRIKE,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // Arkane\n    {\n        .names = {\"Arkane\", \"Arkane Linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ARKANE,\n        .colors = {\n            FF_COLOR_FG_256 \"237\",\n            FF_COLOR_FG_256 \"130\",\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_256 \"130\",\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Armbian\n    {\n        .names = {\"Armbian\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ARMBIAN,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // Armbian2\n    {\n        .names = {\"Armbian2\"},\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_ARMBIAN2,\n        .colors = {\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // Artix\n    {\n        .names = {\"artix\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ARTIX,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // ArtixSmall\n    {\n        .names = {\"artix_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_ARTIX_SMALL,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // Artix2Small\n    {\n        .names = {\"artix2_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT | FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_ARTIX2_SMALL,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // ArcoLinux (Discontinued)\n    {\n        .names = {\"arco\", \"arcolinux\"}, // ID=arcolinux\n        .lines = FASTFETCH_DATATEXT_LOGO_ARCO,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // ArcoLinuxSmall\n    {\n        .names = {\"arco_small\", \"arcolinux_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_ARCO_SMALL,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // ArseLinux\n    {\n        .names = {\"arse\", \"arselinux\", \"arse-linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ARSELINUX,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Arya\n    {\n        .names = {\"Arya\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ARYA,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // Asahi\n    {\n        .names = {\"asahi\", \"asahi-linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ASAHI,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_LIGHT_BLACK,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // Asahi2\n    {\n        .names = {\"asahi2\", \"asahi-linux2\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ASAHI2,\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .colors = {\n            FF_COLOR_FG_LIGHT_YELLOW,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_LIGHT_RED,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_BLACK,\n            FF_COLOR_FG_LIGHT_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // Aster\n    {\n        .names = {\"aster\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ASTER,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // AsteroidOS\n    {\n        .names = {\"AsteroidOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ASTEROIDOS,\n        .colors = {\n            FF_COLOR_FG_256 \"160\",\n            FF_COLOR_FG_256 \"208\",\n            FF_COLOR_FG_256 \"202\",\n            FF_COLOR_FG_256 \"214\"\n        },\n        .colorKeys = FF_COLOR_FG_256 \"160\",\n        .colorTitle = FF_COLOR_FG_256 \"208\",\n    },\n    // AstOS\n    {\n        .names = {\"astOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ASTOS,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Astra\n    {\n        .names = {\"Astra\", \"Astra Linux\", \"astralinux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ASTRA_LINUX,\n        .colors = {\n            FF_COLOR_FG_LIGHT_RED,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Ataraxia\n    {\n        .names = {\"Ataraxia Linux\", \"Ataraxia\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_JANUSLINUX,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_MAGENTA,\n    },\n    // AthenaOS\n    {\n        .names = {\"AthenaOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ATHENAOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_LIGHT_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_LIGHT_BLUE,\n    },\n    // AthenaOS_old\n    {\n        .names = {\"AthenaOS_old\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ATHENAOS_OLD,\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Aurora\n    {\n        .names = {\"Aurora\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_AURORA,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // AxOS\n    {\n        .names = {\"AxOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_AXOS,\n        .colors = {\n            FF_COLOR_FG_RGB \"222;6;255\",\n            FF_COLOR_FG_RGB \"222;6;255\",\n        },\n    },\n    // Azos\n    {\n        .names = {\"Azos\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_AZOS,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_RED,\n        }\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo B[] = {\n    // Bedrock\n    {\n        .names = {\"bedrock\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_BEDROCK,\n        .colors = {\n            FF_COLOR_FG_LIGHT_BLACK, //grey\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_LIGHT_BLACK, //grey\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // BedrockSmall\n    {\n        .names = {\"bedrock_small\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_BEDROCK_SMALL,\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .colors = {\n            FF_COLOR_FG_LIGHT_BLACK, //grey\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_LIGHT_BLACK, //grey\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // BigLinux\n    {\n        .names = {\"BigLinux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_BIGLINUX,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // Bitrig\n    {\n        .names = {\"Bitrig\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_BITRIG,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // BlackArch\n    {\n        .names = {\"Blackarch\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_BLACKARCH,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_LIGHT_RED,\n            FF_COLOR_FG_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_LIGHT_RED,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // BlackMesa\n    {\n        .names = {\"BlackMesa\", \"black-mesa\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_BLACKMESA,\n        .colors = {\n            FF_COLOR_FG_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_BLACK,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // BlackPanther\n    {\n        .names = {\"BlackPanther\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_BLACKPANTHER,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_LIGHT_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // BLAG\n    {\n        .names = {\"BLAG\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_BLAG,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // BlankOn\n    {\n        .names = {\"BlankOn\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_BLANKON,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // BlueLight\n    {\n        .names = {\"BlueLight\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_BLUELIGHT,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Bodhi\n    {\n        .names = {\"Bodhi\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_BODHI,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_LIGHT_YELLOW,\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // Bonsai\n    {\n        .names = {\"Bonsai\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_BONSAI,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // BredOS\n    {\n        .names = {\"Bredos\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_BREDOS,\n        .colors = {\n            FF_COLOR_FG_RGB \"198;151;66\", //grey\n        },\n        .colorKeys = FF_COLOR_FG_RGB \"198;151;66\",\n        .colorTitle = FF_COLOR_FG_RGB \"198;151;66\",\n    },\n    // BSD\n    {\n        .names = {\"BSD\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_BSD,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // BunsenLabs\n    {\n        .names = {\"BunsenLabs\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_BUNSENLABS,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo C[] = {\n    // CachyOS\n    {\n        .names = {\"CachyOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CACHYOS,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // CachyOSSmall\n    {\n        .names = {\"CachyOS_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_CACHYOS_SMALL,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // Calculate\n    {\n        .names = {\"Calculate\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CALCULATE,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // CalinixOS\n    {\n        .names = {\"Calinix\", \"calinixos\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CALINIXOS,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // CalinixOSSmall\n    {\n        .names = {\"Calinix_small\", \"calinixos_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_CALINIXOS_SMALL,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Carbs\n    {\n        .names = {\"Carbs\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CARBS,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // CBL-Mariner\n    {\n        .names = {\"CBL-Mariner\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CBL_MARINER,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // CelOS\n    {\n        .names = {\"Cel\", \"celos\", \"cel-linux\", \"celos-linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CELOS,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Center\n    {\n        .names = {\"Center\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CENTER,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // CentOS\n    {\n        .names = {\"CentOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CENTOS,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // CentOSSmall\n    {\n        .names = {\"CentOS_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_CENTOS_SMALL,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // Cereus\n    {\n        .names = {\"Cereus\", \"Cereus Linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CEREUS,\n        .colors = {\n            FF_COLOR_FG_256 \"173\",\n            FF_COLOR_FG_256 \"108\",\n            FF_COLOR_FG_256 \"71\",\n            FF_COLOR_FG_256 \"151\",\n            FF_COLOR_FG_256 \"72\"\n        },\n        .colorKeys = FF_COLOR_FG_256 \"108\",\n        .colorTitle = FF_COLOR_MODE_BOLD FF_COLOR_FG_WHITE,\n    },\n    // Chakra\n    {\n        .names = {\"Chakra\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CHAKRA,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_MAGENTA,\n    },\n    // ChaletOS\n    {\n        .names = {\"ChaletOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CHALETOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Chapeau\n    {\n        .names = {\"Chapeau\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CHAPEAU,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Chimera\n    {\n        .names = {\"Chimera\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CHIMERA_LINUX,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // ChonkySealOS\n    {\n        .names = {\"ChonkySealOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CHONKYSEALOS,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Chrom\n    {\n        .names = {\"Chrom\", \"ChromeOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CHROM,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // Cleanjaro\n    {\n        .names = {\"Cleanjaro\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CLEANJARO,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // CleanjaroSmall\n    {\n        .names = {\"Cleanjaro_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_CLEANJARO_SMALL,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // ClearLinux\n    {\n        .names = {\"Clear Linux\", \"clearlinux\", \"Clear Linux OS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CLEAR_LINUX,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // ClearOS\n    {\n        .names = {\"ClearOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CLEAROS,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // Clover\n    {\n        .names = {\"Clover\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CLOVER,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // Cobalt\n    {\n        .names = {\"Cobalt\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_COBALT,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_LIGHT_BLACK,\n            FF_COLOR_FG_LIGHT_BLUE,\n            FF_COLOR_FG_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Codex Linux (reMarkable OS)\n    {\n        .names = {\"Codex Linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CODEX,\n        .colors = {\n            FF_COLOR_FG_WHITE\n        },\n    },\n    // Condres\n    {\n        .names = {\"Condres\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CONDRES,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_CYAN\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // ContainerLinux\n    {\n        .names = {\"ContainerLinux\", \"Container Linux\", \"Container Linux by CoreOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_FEDORA_COREOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Common Torizon\n    {\n        .names = {\"common-torizon\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_TORIZONCORE,\n        .colors = {\n            FF_COLOR_FG_LIGHT_WHITE,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_BLUE\n        },\n    },\n    // Cosmic DE\n    {\n        .names = {\"Cosmic\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_COSMIC,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_LIGHT_YELLOW,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_LIGHT_RED,\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_LIGHT_RED,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // CRUX\n    {\n        .names = {\"CRUX\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CRUX,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // CRUXSmall\n    {\n        .names = {\"CRUX_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_CRUX_SMALL,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // CrystalLinux\n    {\n        .names = {\"Crystal\", \"Crystal\", \"crystal-linux\", \"Crystal-Linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CRYSTAL,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_MAGENTA,\n    },\n    // Cucumber\n    {\n        .names = {\"Cucumber\", \"CucumberOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CUCUMBER,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // CuerdOS\n    {\n        .names = {\"CuerdOS\", \"CuerdOS GNU/Linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CUERDOS,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // CutefishOS\n    {\n        .names = {\"CutefishOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CUTEFISHOS,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_BLUE,\n        },\n    },\n    // CuteOS\n    {\n        .names = {\"CuteOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CUTEOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_256 \"57\",\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // CyberOS\n    {\n        .names = {\"CyberOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CYBEROS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_256 \"57\",\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // cycledream\n    {\n        .names = {\"cycledream\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_CYCLEDREAM,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_MAGENTA,\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo D[] = {\n    // DahliaOS\n    {\n        .names = {\"dahliaOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_DAHLIA,\n        .colors = {\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // DarkOS\n    {\n        .names = {\"DarkOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_DARKOS,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // Debian\n    {\n        .names = {\"Debian\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_DEBIAN,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // DebianSmall\n    {\n        .names = {\"Debian_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_DEBIAN_SMALL,\n        .colors = {\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // Deepin\n    {\n        .names = {\"Deepin\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_DEEPIN,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // DesaOS\n    {\n        .names = {\"DesaOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_DESAOS,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Devuan\n    {\n        .names = {\"Devuan\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_DEVUAN,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_MAGENTA,\n    },\n    // DevuanSmall\n    {\n        .names = {\"Devuan_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_DEVUAN_SMALL,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_MAGENTA,\n    },\n    // DietPi\n    {\n        .names = {\"DietPi\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_DIETPI,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_LIGHT_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_LIGHT_BLACK,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // DracOS\n    {\n        .names = {\"DracOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_DRACOS,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // DragonFly\n    {\n        .names = {\"DragonFly\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_DRAGONFLY,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // DragonFlySmall\n    {\n        .names = {\"DragonFly_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_DRAGONFLY_SMALL,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // DragonFlyOld\n    {\n        .names = {\"DragonFly_old\"},\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_DRAGONFLY_OLD,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_DEFAULT,\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // DraugerOS\n    {\n        .names = {\"DraugerOS\", \"Drauger\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_DRAUGER,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Droidian\n    {\n        .names = {\"Droidian\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_DROIDIAN,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_LIGHT_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_LIGHT_GREEN,\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo E[] = {\n    // Elbrus\n    {\n        .names = {\"elbrus\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ELBRUS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Elementary\n    {\n        .names = {\"Elementary\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ELEMENTARY,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // ElementarySmall\n    {\n        .names = {\"Elementary_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_ELEMENTARY_SMALL,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Elive\n    {\n        .names = {\"Elive\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ELIVE,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_LIGHT_CYAN,\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // Emmabuntüs\n    {\n        .names = {\"Emmabuntus\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_EMMABUNTUS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_YELLOW,\n        },\n    },\n    // EmperorOS\n    {\n        .names = {\"Emperor\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_EMPEROROS,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_DEFAULT,\n        },\n    },\n    // EncryptOS\n    {\n        .names = {\"EncryptOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ENCRYPTOS,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // EndeavourOS\n    {\n        .names = {\"EndeavourOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ENDEAVOUROS,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // EndeavourOSSmall\n    {\n        .names = {\"EndeavourOS_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_ENDEAVOUROS_SMALL,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_BLUE,\n        },\n    },\n    // Endless\n    {\n        .names = {\"Endless\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ENDLESS,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Enso\n    {\n        .names = {\"Enso\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ENSO,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // EshanizedOS\n    {\n        .names = {\"EshanizedOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ESHANIZEDOS,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // EuroLinux\n    {\n        .names = {\"EuroLinux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_EUROLINUX,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // EvolutionOS\n    {\n        .names = {\"EvolutionOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_EVOLUTIONOS,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // EvolutionOSSmall\n    {\n        .names = {\"EvolutionOS_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_EVOLUTIONOS_SMALL,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // EvolutionOS_old\n    {\n        .names = {\"EvolutionOS_old\"},\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_EVOLUTIONOS_OLD,\n        .colors = {\n            FF_COLOR_FG_LIGHT_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // eweOS\n    {\n        .names = {\"eweOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_EWEOS,\n        .colors = {\n           FF_COLOR_FG_WHITE,\n           FF_COLOR_FG_LIGHT_YELLOW,\n           FF_COLOR_FG_LIGHT_RED,\n           FF_COLOR_FG_LIGHT_BLACK,\n           FF_COLOR_FG_RED,\n        },\n    },\n    // Exherbo\n    {\n        .names = {\"Exherbo\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_EXHERBO,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // ExodiaOS\n    {\n        .names = {\"Exodia\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_EXODIA_PREDATOR,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_MAGENTA,\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo F[] = {\n    // Fastfetch\n    {\n        .names = {\"Fastfetch\", \"FF\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_FASTFETCH,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_DEFAULT,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // Fedora\n    {\n        .names = {\"Fedora\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_FEDORA,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // FedoraAsahiRemix\n    {\n        .names = {\"fedora-asahi-remix\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ASAHI,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_LIGHT_BLACK,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // FedoraSmall\n    {\n        .names = {\"Fedora_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_FEDORA_SMALL,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    {\n        .names = {\"Fedora2_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT | FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_FEDORA2_SMALL,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // FedoraOld\n    {\n        .names = {\"Fedora_old\"},\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_FEDORA_OLD,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // FedoraSilverblue\n    {\n        .names = {\"Fedora-Silverblue\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_FEDORA_SILVERBLUE,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // FedoraKinoite\n    {\n        .names = {\"Fedora-Kinoite\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_FEDORA_KINOITE,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // FedoraSericea\n    {\n        .names = {\"Fedora-Sericea\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_FEDORA_SERICEA,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // FedoraCoreOS\n    {\n        .names = {\"Fedora-CoreOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_FEDORA_COREOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // FemboyOS\n    {\n        .names = {\"FemboyOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_FEMBOYOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Feren\n    {\n        .names = {\"Feren\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_FEREN,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Filotimo\n    {\n        .names = {\"filotimo\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_FILOTIMO,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Finnix\n    {\n        .names = {\"Finnix\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_FINNIX,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Floflis\n    {\n        .names = {\"Floflis\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_FLOFLIS,\n        .colors = {\n            FF_COLOR_FG_LIGHT_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // FreeBSD\n    {\n        .names = {\"Freebsd\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_FREEBSD,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // FreeBSDSmall\n    {\n        .names = {\"freebsd_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_FREEBSD_SMALL,\n        .colors = {\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // FreeMiNT\n    {\n        .names = {\"FreeMiNT\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_FREEMINT,\n        .colors = {\n            FF_COLOR_FG_WHITE\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Frugalware\n    {\n        .names = {\"Frugalware\", \"frugalware-linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_FRUGALWARE,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Funtoo\n    {\n        .names = {\"Funtoo\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_FUNTOO,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Furreto\n    {\n        .names = {\"Furreto\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_FURRETO,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_LIGHT_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo G[] = {\n    // GalliumOS\n    {\n        .names = {\"GalliumOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_GALLIUMOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Garuda\n    {\n        .names = {\"Garuda\", \"garuda-linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_GARUDA,\n        .colors = {\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // GarudaDragon\n    {\n        .names = {\"GarudaDragon\", \"garuda-dragon\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_GARUDA_DRAGON,\n        .colors = {\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // GarudaSmall\n    {\n        .names = {\"Garuda_small\", \"garuda-linux_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_GARUDA_SMALL,\n        .colors = {\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // Gentoo\n    {\n        .names = {\"Gentoo\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_GENTOO,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_MAGENTA,\n    },\n    // GentooSmall\n    {\n        .names = {\"Gentoo_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_GENTOO_SMALL,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_MAGENTA,\n    },\n    // GhostBSD\n    {\n        .names = {\"GhostBSD\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_GHOSTBSD,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // GhostFreak\n    {\n        .names = {\"GhostFreak\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_GHOSTFREAK,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Glaucus\n    {\n        .names = {\"Glaucus\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_GLAUCUS,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_MAGENTA,\n    },\n    // GNewSense\n    {\n        .names = {\"gNewSense\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_GNEWSENSE,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // GNOME\n    {\n        .names = {\"GNOME\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_GNOME,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // GNU\n    {\n        .names = {\"GNU\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_GNU,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // GoboLinux\n    {\n        .names = {\"GoboLinux\", \"Gobo\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_GOBOLINUX,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_MAGENTA,\n    },\n    // GoldenDogLinux\n    {\n        .names = {\"GoldenDog Linux\", \"GDL\", \"goldendoglinux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_GOLDENDOGLINUX,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // GrapheneOS\n    {\n        .names = {\"GrapheneOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_GRAPHENEOS,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Grombyang\n    {\n        .names = {\"Grombyang\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_GROMBYANG,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // Guix\n    {\n        .names = {\"Guix\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_GUIX,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // GuixSmall\n    {\n        .names = {\"Guix_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_GUIX_SMALL,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // GXDE\n    {\n        .names = {\"GXDE\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_GXDE,\n        .colors = {\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo H[] = {\n    // Haiku\n    {\n        .names = {\"Haiku\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_HAIKU,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // Haiku2\n    {\n        .names = {\"Haiku2\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_HAIKU2,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // HaikuSmall\n    {\n        .names = {\"Haiku_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_HAIKU_SMALL,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // HamoniKR\n    {\n        .names = {\"HamoniKR\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_HAMONIKR,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_256 \"99\"\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // HarDClanZ\n    {\n        .names = {\"HarDClanZ\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_HARDCLANZ,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // HardenedBSD\n    {\n        .names = {\"HardenedBSD\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_FREEBSD,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // HarmonyOS\n    {\n        .names = {\"HarmonyOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_HARMONYOS,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Hash\n    {\n        .names = {\"Hash\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_HASH,\n        .colors = {\n            FF_COLOR_FG_256 \"123\",\n            FF_COLOR_FG_256 \"123\",\n        },\n    },\n    // HeliumOS\n    {\n        .names = {\"HeliumOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_HELIUMOS,\n        .colors = {\n            FF_COLOR_FG_256 \"81\",\n        },\n        .colorKeys = FF_COLOR_FG_256 \"81\",\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Huawei Cloud EulerOS\n    {\n        .names = {\"Huawei Cloud EulerOS\", \"hce\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_HCE,\n        .colors = {\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // Huayra\n    {\n        .names = {\"Huayra\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_HUAYRA,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Hybrid\n    {\n        .names = {\"Hybrid\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_HYBRID,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_LIGHT_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_LIGHT_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // HydroOS\n    {\n        .names = {\"HydroOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_HYDROOS,\n        .colors = {\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // HyprOS\n    {\n        .names = {\"hypros\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_HYPROS,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_BLUE,\n        },\n    },\n    // Hyperbola\n    {\n        .names = {\"Hyperbola\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_HYPERBOLA,\n        .colors = {\n            FF_COLOR_FG_LIGHT_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_LIGHT_BLACK,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // HyperbolaSmall\n    {\n        .names = {\"Hyperbola_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_HYPERBOLA_SMALL,\n        .colors = {\n            FF_COLOR_FG_LIGHT_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_LIGHT_BLACK,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo I[] = {\n    // Iglunix\n    {\n        .names = {\"Iglunix\", \"Iglu\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_IGLUNIX,\n        .colors = {\n            FF_COLOR_FG_LIGHT_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_LIGHT_BLACK,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // InstantOS\n    {\n        .names = {\"InstantOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_INSTANTOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // Interix\n    {\n        .names = {\"Interix\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_INTERIX,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_BLACK,\n            FF_COLOR_FG_YELLOW,\n        },\n    },\n    // IRIX\n    {\n        .names = {\"IRIX\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_IRIX,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Ironclad\n    {\n        .names = {\"Ironclad\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_IRONCLAD,\n        .colors = {\n            FF_COLOR_FG_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_MAGENTA,\n    },\n    // Itc\n    {\n        .names = {\"Itc\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ITC,\n        .colors = {\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo J[] = {\n    // Januslinux\n    {\n        .names = {\"januslinux\", \"janus\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_JANUSLINUX,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_MAGENTA,\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo K[] = {\n    // Kaisen\n    {\n        .names = {\"Kaisen\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_KAISEN,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Kali\n    {\n        .names = {\"Kali\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_KALI,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_LIGHT_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // KaliSmall\n    {\n        .names = {\"Kali_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_KALI_SMALL,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_LIGHT_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Kalpa Desktop\n    {\n        .names = {\"kalpa-desktop\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_KALPA_DESKTOP,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // KaOS\n    {\n        .names = {\"KaOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_KAOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // KernelOS\n    {\n        .names = {\"KernelOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_KERNELOS,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_MAGENTA,\n        }\n    },\n    // KDELinux\n    {\n        .names = {\"kdelinux\", \"kde-linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_KDELINUX,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_WHITE\n        }\n    },\n    // KDE Neon\n    {\n        .names = {\"KDE Neon\"}, // Distro ID is \"neon\"; Distro name is \"KDE Neon\"\n        .lines = FASTFETCH_DATATEXT_LOGO_KDENEON,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_DEFAULT,\n        },\n    },\n    // Kibojoe\n    {\n        .names = {\"Kibojoe\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_KIBOJOE,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // KISSLinux\n    {\n        .names = {\"KISS\", \"kiss-linux\", \"kisslinux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_KISS,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // KISSLinux2\n    {\n        .names = {\"kiss2\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_KISS2,\n        .colors = {\n            FF_COLOR_FG_BLACK,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // Kogaion\n    {\n        .names = {\"Kogaion\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_KOGAION,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Korora\n    {\n        .names = {\"Korora\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_KORORA,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // KrassOS\n    {\n        .names = {\"KrassOS\", \"Krass\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_KRASSOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // KSLinux\n    {\n        .names = {\"KSLinux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_KSLINUX,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Kubuntu\n    {\n        .names = {\"Kubuntu\", \"kubuntu-linux\", \"kde-ubuntu\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_KUBUNTU,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Kylin\n    {\n        .names = {\"Kylin\", \"kylin\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_KYLIN,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_LIGHT_BLACK\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo L[] = {\n    // LainOS\n    {\n        .names = {\"LainOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_LAINOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_256 \"14\",\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // LangitKetujuh\n    {\n        .names = {\"langitketujuh\", \"l7\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_LANGITKETUJUH,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Laxeros\n    {\n        .names = {\"Laxeros\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_LAXEROS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // LEDE\n    {\n        .names = {\"LEDE\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_LEDE,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // LibreELEC\n    {\n        .names = {\"LibreELEC\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_LIBREELEC,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // Lilidog\n    {\n        .names = {\"Lilidog\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_LILIDOG,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Lingmo OS\n    {\n        .names = {\"Lingmo\", \"LingmoOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_LINGMO,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Linspire\n    {\n        .names = {\"Linspire\", \"Lindows\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_LINSPIRE,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // Linux\n    {\n        .names = {\"Linux\", \"linux-generic\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_LINUX,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_LIGHT_BLACK,\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // LinuxFromScratch\n    {\n        .names = {\"LinuxFromScratch\", \"lfs\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_LFS,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_BLACK,\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_BLACK,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // LinuxSmall\n    {\n        .names = {\"Linux_small\", \"linux-generic_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_LINUX_SMALL,\n        .colors = {\n            FF_COLOR_FG_BLACK,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // LinuxLite\n    {\n        .names = {\"LinuxLite\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_LINUXLITE,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // LinuxLiteSmall\n    {\n        .names = {\"LinuxLite_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_LINUXLITE_SMALL,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // LinuxMint\n    {\n        .names = {\"linuxmint\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_LINUXMINT,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // LinuxMintSmall\n    {\n        .names = {\"linuxmint_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_LINUXMINT_SMALL,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // LinuxMint2\n    {\n        .names = {\"linuxmint2\"},\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_LINUXMINT2,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // LinuxMintOld\n    {\n        .names = {\"linuxmint_old\"},\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_LINUXMINT_OLD,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // Live_Raizo\n    {\n        .names = {\"Live Raizo\", \"Live_Raizo\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_LIVE_RAIZO,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // LliureX\n    {\n        .names = {\"LliureX\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_LLIUREX,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // LMDE\n    {\n        .names = {\"LMDE\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_LMDE,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Loc-OS\n    {\n        .names = {\"locos\", \"loc-os\", \"Loc-OS Linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_LOCOS,\n        .colors = {\n            FF_COLOR_FG_BLACK,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // Lubuntu\n    {\n        .names = {\"lubuntu\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_LUBUNTU,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Lunar\n    {\n        .names = {\"Lunar\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_LUNAR,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo M[] = {\n    // Macaroni\n    {\n        .names = {\"Macaroni\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MACARONIOS,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // MacOS\n    {\n        .names = {\"macOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MACOS,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_LIGHT_RED,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // MacOSSmall\n    {\n        .names = {\"macOS_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_MACOS_SMALL,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // MacOS2\n    {\n        .names = {\"macOS2\"},\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_MACOS2,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_LIGHT_RED,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // MacOS2Small\n    {\n        .names = {\"macOS2_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT | FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_MACOS2_SMALL,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // MacOS3\n    {\n        .names = {\"macOS3\"},\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_MACOS3,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_LIGHT_RED,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // MainsailOS\n    {\n        .names = {\"MainsailOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MAINSAILOS,\n        .colors = {\n            FF_COLOR_FG_RED,\n        },\n    },\n    // MainsailOSSmall\n    {\n        .names = {\"MainsailOS_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_MAINSAILOS_SMALL,\n        .colors = {\n            FF_COLOR_FG_RED,\n        },\n    },\n    // Mageia\n    {\n        .names = {\"Mageia\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MAGEIA,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // MageiaSmall\n    {\n        .names = {\"Mageia_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_MAGEIA_SMALL,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Magix\n    {\n        .names = {\"Magix\", \"MagixOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MAGIX,\n        .colors = {\n            FF_COLOR_FG_LIGHT_MAGENTA,\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_LIGHT_MAGENTA,\n    },\n    // MagpieOS\n    {\n        .names = {\"MagpieOS\", \"Magpie\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MAGPIEOS,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // Mandriva\n    {\n        .names = {\"mandriva\", \"mandrake\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MANDRIVA,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // Manjaro\n    {\n        .names = {\"manjaro\", \"manjaro-arm\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MANJARO,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // ManjaroSmall\n    {\n        .names = {\"manjaro_small\", \"manjaro-arm_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_MANJARO_SMALL,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // MassOS\n    {\n        .names = {\"MassOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MASSOS,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // MatuusOS\n    {\n        .names = {\"MatuusOS\", \"Matuus\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MATUUSOS,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // MaUI\n    {\n        .names = {\"MaUI\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MAUI,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Mauna\n    {\n        .names = {\"Mauna\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MAUNA,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_CYAN,\n        },\n    },\n    // Meowix\n    {\n        .names = {\"Meowix\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MEOWIX,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // Mer\n    {\n        .names = {\"Mer\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MER,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // MidnightBSD\n    {\n        .names = {\"MidnightBSD\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MIDNIGHTBSD,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_LIGHT_BLACK,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // MidOS\n    {\n        .names = {\"MidOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MIDOS,\n        .colors = {\n            FF_COLOR_FG_LIGHT_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_LIGHT_BLACK,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // MidOSOld\n    {\n        .names = {\"MidOS_old\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MIDOS_OLD,\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .colors = {\n            FF_COLOR_FG_LIGHT_BLACK,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_LIGHT_BLACK,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Minimal System\n    {\n        .names = {\"Minimal_System\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MINIMAL,\n        .colors = {\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Minix\n    {\n        .names = {\"Minix\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MINIX,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // MiracleLinux\n    {\n        .names = {\"miraclelinux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MIRACLE_LINUX,\n        .colors = {\n            FF_COLOR_FG_256 \"29\",\n        },\n        .colorKeys = FF_COLOR_FG_256 \"29\",\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // MOS\n    {\n        .names = {\"MOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MOS,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_BLUE,\n        },\n    },\n    // Msys2\n    {\n        .names = {\"Msys2\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MSYS2,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // MX\n    {\n        .names = {\"MX\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MX,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // MXSmall\n    {\n        .names = {\"MX_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_MX_SMALL,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // MX2\n    {\n        .names = {\"MX2\"},\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_MX2,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo N[] = {\n    // Namib\n    {\n        .names = {\"Namib\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_NAMIB,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Nekos\n    {\n        .names = {\"Nekos\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_NEKOS,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_RED,\n        },\n    },\n    // Neptune\n    {\n        .names = {\"Neptune\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_NEPTUNE,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // NetRunner\n    {\n        .names = {\"NetRunner\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_NETRUNNER,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // NexaLinux\n    {\n        .names = {\"nexalinux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_NEXALINUX,\n        .colors = {\n            FF_COLOR_FG_LIGHT_BLUE,\n            FF_COLOR_FG_LIGHT_BLUE,\n        },\n    },\n    // Nitrux\n    {\n        .names = {\"Nitrux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_NITRUX,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // NixOS\n    {\n        .names = {\"NixOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_NIXOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_CYAN,\n        },\n    },\n    // NixOSSmall\n    {\n        .names = {\"NixOS_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_NIXOS_SMALL,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_CYAN,\n        },\n    },\n    // NixOSOld\n    {\n        .names = {\"nixos_old\"},\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_NIXOS_OLD,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_CYAN,\n        },\n    },\n    // NixOsOldSmall\n    {\n        .names = {\"nixos_old_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT | FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_NIXOS_OLD_SMALL,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_CYAN,\n        },\n    },\n    // NetBSD\n    {\n        .names = {\"NetBSD\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_NETBSD,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // NetBSD2\n    {\n        .names = {\"NetBSD2\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_NETBSD2,\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // NetBSD Small\n    {\n        .names = {\"NetBSD_small\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_NETBSD_SMALL,\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Nobara\n    {\n        .names = {\"nobara\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_NOBARA,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // NomadBSD\n    {\n        .names = {\"nomadbsd\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_NOMADBSD,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // NurOS\n    {\n        .names = {\"NurOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_NUROS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Nurunner\n    {\n        .names = {\"Nurunner\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_NURUNNER,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // NuTyX\n    {\n        .names = {\"NuTyX\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_NUTYX,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_RED,\n        },\n    },\n    // NetHydra\n    {\n        .names = {\"NetHydra\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_NETHYDRA,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_DEFAULT,\n        },\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo O[] = {\n    // Obarun\n    {\n        .names = {\"Obarun\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OBARUN,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // OBRevenge\n    {\n        .names = {\"OBRevenge\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OBREVENGE,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // ObsidianOS\n    {\n        .names = {\"ObsidianOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OBSIDIANOS,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_LIGHT_BLUE,\n        },\n    },\n    // OmniOS\n    {\n        .names = {\"OmniOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OMNIOS,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_LIGHT_BLACK,\n        }\n    },\n    // OpenKylin\n    {\n        .names = {\"openkylin\", \"open-kylin\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OPENKYLIN,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // OpenBSD\n    {\n        .names = {\"openbsd\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OPENBSD,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_LIGHT_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // OpenBSDSmall\n    {\n        .names = {\"openbsd_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_OPENBSD_SMALL,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // OpenEuler\n    {\n        .names = {\"OpenEuler\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OPENEULER,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // OpenIndiana\n    {\n        .names = {\"OpenIndiana\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OPENINDIANA,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_DEFAULT,\n        },\n    },\n    // OpenMamba\n    {\n        .names = {\"OpenMamba\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OPENMAMBA,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_GREEN,\n        },\n    },\n    // OpenStage\n    {\n        .names = {\"OpenStage\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OPENSTAGE,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // OpenSuse\n    {\n        .names = {\"opensuse\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // OpenSuseSmall\n    {\n        .names = {\"opensuse_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_SMALL,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // openSuseMicroOS\n    {\n        .names = {\"opensuse-microos\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_MICROOS,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n        },\n    },\n    // OpenSuseLeap\n    {\n        .names = {\"opensuse-leap\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_LEAP,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // OpenSuseLeapOld\n    {\n        .names = {\"opensuse-leap_old\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_LEAP_OLD,\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // OpenSuseTumbleweed\n    {\n        .names = {\"opensuse-tumbleweed\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_TUMBLEWEED,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // OpenSuseTumbleweedSmall\n    {\n        .names = {\"opensuse-tumbleweed_small\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_TUMBLEWEED_SMALL,\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // OpenSuseTumbleweedOld\n    {\n        .names = {\"opensuse-tumbleweed_old\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_TUMBLEWEED_OLD,\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // OpenSuseTumbleweed2\n    {\n        .names = {\"opensuse-tumbleweed2\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_TUMBLEWEED2,\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // openSUSESlowroll\n    {\n        .names = {\"opensuse-slowroll\", \"opensuse-tumbleweed-slowroll\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_SLOWROLL,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // OpenMandriva\n    {\n        .names = {\"openmandriva\", \"open-mandriva\", \"open_mandriva\", \"openmandriva lx\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OPENMANDRIVA,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // OpenWrt\n    {\n        .names = {\"openwrt\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OPENWRT,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // OPNsense\n    {\n        .names = {\"OPNsense\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OPNSENSE,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_256 \"202\",\n        },\n    },\n    // Oracle\n    {\n        .names = {\"oracle\", \"oracle linux\", \"oracle linux server\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ORACLE,\n        .colors = {\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Orchid\n    {\n        .names = {\"orchid\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ORCHID,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_MAGENTA,\n    },\n    // OrchidSmall\n    {\n        .names = {\"orchid_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_ORCHID_SMALL,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_MAGENTA,\n    },\n    // Oreon\n    {\n        .names = {\"Oreon\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OREON,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n            FF_COLOR_FG_DEFAULT,\n        },\n    },\n    // OS/2 Warp\n    {\n        .names = {\"OS2Warp\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OS2WARP,\n        .colors = {\n            FF_COLOR_FG_LIGHT_WHITE,\n            FF_COLOR_FG_LIGHT_BLUE,\n        }\n    },\n    // OS_Elbrus\n    {\n        .names = {\"OS Elbrus\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OS_ELBRUS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // OSMC\n    {\n        .names = {\"OSMC\", \"Open Source Media Center\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_OSMC,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // OSX\n    {\n        .names = {\"OSX\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_MACOS,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_LIGHT_RED,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // OSXSmall\n    {\n        .names = {\"OSX_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_MACOS_SMALL,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo P[] = {\n    // PacBSD\n    {\n        .names = {\"PacBSD\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PACBSD,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Panwah\n    {\n        .names = {\"Panwah\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PANWAH,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_BLACK,\n        },\n    },\n    // Parabola\n    {\n        .names = {\"parabola\", \"parabola-gnulinux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PARABOLA,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_MAGENTA,\n    },\n    // ParabolaSmall\n    {\n        .names = {\"parabola_small\", \"parabola-gnulinux_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_PARABOLA_SMALL,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_MAGENTA,\n    },\n    // Parch\n    {\n        .names = {\"Parch\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PARCH,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_RED,\n        },\n    },\n    // Pardus\n    {\n        .names = {\"Pardus\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PARDUS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_CYAN,\n        },\n    },\n    // Parrot\n    {\n        .names = {\"Parrot\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PARROT,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Parsix\n    {\n        .names = {\"Parsix\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PARSIX,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_LIGHT_BLACK,\n        },\n    },\n    // PCBSD\n    {\n        .names = {\"PCBSD\", \"TrueOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PCBSD,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // PCLinuxOS\n    {\n        .names = {\"PCLinuxOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PCLINUXOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // PearOS\n    {\n        .names = {\"PearOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PEAROS,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_BLUE,\n        },\n    },\n    // Pengwin\n    {\n        .names = {\"Pengwin\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PENGWIN,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_LIGHT_MAGENTA,\n            FF_COLOR_FG_MAGENTA,\n        },\n    },\n    // Pentoo\n    {\n        .names = {\"Pentoo\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PENTOO,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Peppermint\n    {\n        .names = {\"Peppermint\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PEPPERMINT,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Peropesis\n    {\n        .names = {\"Peropesis\", \"Peropesis Linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PEROPESIS,\n        .colors = {\n            FF_COLOR_FG_WHITE\n        },\n    },\n    // PhyOS\n    {\n        .names = {\"PhyOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PHYOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // PikaOS\n    {\n        .names = {\"PikaOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PIKAOS,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n        },\n    },\n    // PisiLinux\n    {\n        .names = {\"PisiLinux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PISI,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // PNMLinux\n    {\n        .names = {\"PNM Linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PNM_LINUX,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_256 \"202\"\n        },\n    },\n    // Pop\n    {\n        .names = {\"pop\", \"popos\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_POP,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // PopSmall\n    {\n        .names = {\"pop_small\", \"popos_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_POP_SMALL,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // Porteus\n    {\n        .names = {\"Porteus\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PORTEUS,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // PostMarketOS\n    {\n        .names = {\"PostMarketOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_POSTMARKETOS,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // PostMarketOSSmall\n    {\n        .names = {\"PostMarketOS_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_POSTMARKETOS_SMALL,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Proxmox\n    {\n        .names = {\"Proxmox\", \"pve\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PROXMOX,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_256 \"202\"\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_256 \"202\",\n    },\n    // PuffOS\n    {\n        .names = {\"PuffOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PUFFOS,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Puppy\n    {\n        .names = {\"Puppy\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PUPPY,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // PureOS\n    {\n        .names = {\"PureOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PUREOS,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // PureOSSmall\n    {\n        .names = {\"PureOS_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_PUREOS_SMALL,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // PrismLinux\n    {\n        .names = {\"PrismLinux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_PRISMLINUX,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_BLUE,\n        },\n    },\n    // PrismLinuxSmall\n    {\n        .names = {\"PrismLinux_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_PRISMLINUX_SMALL,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_BLUE,\n        },\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo Q[] = {\n    // QTS\n    {\n        .names = {\"qts\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_QTS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Q4OS\n    {\n        .names = {\"Q4OS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_Q4OS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_RED,\n        },\n    },\n    // Qubes\n    {\n        .names = {\"Qubes\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_QUBES,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_MAGENTA,\n        },\n    },\n    // Qubyt\n    {\n        .names = {\"Qubyt\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_QUBYT,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_BLACK,\n        },\n    },\n    // Quibian\n    {\n        .names = {\"Quibian\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_QUIBIAN,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Quirinux\n    {\n        .names = {\"Quirinux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_QUIRINUX,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_MAGENTA,\n        },\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo R[] = {\n    // Radix\n    {\n        .names = {\"Radix\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_RADIX,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_RED,\n        },\n    },\n    // Raspbian\n    {\n        .names = {\"raspbian\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_RASPBIAN,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // RaspbianSmall\n    {\n        .names = {\"raspbian_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_RASPBIAN_SMALL,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // RavynOS\n    {\n        .names = {\"RavynOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_RAVYNOS,\n        .colors = {\n            FF_COLOR_FG_256 \"15\",\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // RebornOS\n    {\n        .names = {\"RebornOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_REBORNOS,\n        .colors = {\n            FF_COLOR_FG_BLACK,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // RebornSmall\n    {\n        .names = {\"RebornOS_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_REBORNOS_SMALL,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // RedCore\n    {\n        .names = {\"RedCore\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_REDCORE,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // RedHatEnterpriseLinux\n    {\n        .names = {\"rhel\", \"redhat\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_RHEL,\n        .colors = {\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // RedHatEnterpriseLinux\n    {\n        .names = {\"rhel_small\", \"redhat_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_RHEL_SMALL,\n        .colors = {\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // RedHatEnterpriseLinux_old\n    {\n        .names = {\"rhel_old\", \"redhat_old\"},\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_RHEL_OLD,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // RedOS\n    {\n        .names = {\"RedOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_REDOS,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorTitle = FF_COLOR_FG_RED,\n        .colorKeys = FF_COLOR_FG_RED,\n    },\n    // RedOS small\n    {\n        .names = {\"RedOS_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_REDOS_SMALL,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorTitle = FF_COLOR_FG_RED,\n        .colorKeys = FF_COLOR_FG_RED,\n    },\n    // RedstarOS\n    {\n        .names = {\"redstar\", \"redstar-os\", \"redstaros\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_REDSTAR,\n        .colors = {\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // Refracta\n    {\n        .names = {\"Refracta\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_REFRACTA,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_LIGHT_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_LIGHT_BLACK,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Regata\n    {\n        .names = {\"Regata\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_REGATA,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_GREEN,\n        },\n    },\n    // Regolith\n    {\n        .names = {\"Regolith\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_REGOLITH,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // RhaymOS\n    {\n        .names = {\"RhaymOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_RHAYMOS,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // RockyLinux\n    {\n        .names = {\"rocky\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ROCKY,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // RockyLinuxSmall\n    {\n        .names = {\"rocky_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_ROCKY_SMALL,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // RosaLinux\n    {\n        .names = {\"rosa\", \"rosa-linux\", \"rosalinux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ROSA,\n        .colors = {\n            FF_COLOR_FG_RGB \"250;250;250\",\n            FF_COLOR_FG_RGB \"100;165;225\",\n        },\n        .colorKeys = FF_COLOR_FG_RGB \"100;165;225\",\n        .colorTitle = FF_COLOR_FG_RGB \"100;165;225\",\n    },\n    // RhinoLinux\n    {\n        .names = {\"Rhino Linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_RHINO,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_LIGHT_BLUE,\n            FF_COLOR_FG_LIGHT_MAGENTA,\n            FF_COLOR_FG_MAGENTA,\n        },\n        .colorKeys = FF_COLOR_FG_MAGENTA,\n        .colorTitle = FF_COLOR_FG_MAGENTA,\n    },\n    {\n        .names = {\"RengeOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_RENGEOS,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_MAGENTA,\n        },\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo S[] = {\n    // Sabayon\n    {\n        .names = {\"Sabayon\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SABAYON,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Sabotage\n    {\n        .names = {\"Sabotage\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SABOTAGE,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Sailfish\n    {\n        .names = {\"Sailfish\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SAILFISH,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_MAGENTA,\n        },\n    },\n    // SalentOS\n    {\n        .names = {\"SalentOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SALENTOS,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // SalientOS\n    {\n        .names = {\"Salient OS\", \"SalientOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SALIENTOS,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Salix\n    {\n        .names = {\"Salix\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SALIX,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_GREEN,\n        },\n    },\n    // SambaBOX\n    {\n        .names = {\"SambaBOX\", \"Profelis SambaBOX\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SAMBABOX,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_CYAN,\n        },\n    },\n    // Sasanqua\n    {\n        .names = {\"Sasanqua\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SASANQUA,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_RED,\n        },\n    },\n    // Scientific\n    {\n        .names = {\"Scientific\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SCIENTIFIC,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_RED,\n        },\n    },\n    // Secureblue\n    {\n        .names = {\"secureblue\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SECUREBLUE,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_DEFAULT,\n        },\n    },\n    // Serpent OS\n    {\n        .names = {\"Serpent OS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SERPENT_OS,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n    },\n    // Semc\n    {\n        .names = {\"semc\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SEMC,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_LIGHT_BLACK,\n            FF_COLOR_FG_RED,\n        },\n    },\n    // Septor\n    {\n        .names = {\"Septor\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SEPTOR,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_BLUE,\n        },\n    },\n    // Serene\n    {\n        .names = {\"Serene\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SERENE,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_CYAN,\n        },\n    },\n    // SharkLinux\n    {\n        .names = {\"SharkLinux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SHARKLINUX,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // ShastraOS\n    {\n        .names = {\"ShastraOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SHASTRAOS,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Shebang\n    {\n        .names = {\"Shebang\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SHEBANG,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Siduction\n    {\n        .names = {\"Siduction\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SIDUCTION,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        }\n    },\n    // SkiffOS\n    {\n        .names = {\"SkiffOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SKIFFOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // SleeperOS\n    {\n        .names = {\"SleeperOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SLEEPEROS,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_WHITE,\n        }\n    },\n    // SleeperOS\n    {\n        .names = {\"SleeperOS_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_SLEEPEROS_SMALL,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_WHITE,\n        }\n    },\n    // Slitaz\n    {\n        .names = {\"Slitaz\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SLITAZ,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_YELLOW,\n        },\n    },\n    // SpoinkOS\n    {\n        .names = {\"SpoinkOS\", \"spoink-os\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SPOINKOS,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n        },\n    },\n    // Slackel\n    {\n        .names = {\"Slackel\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SLACKEL,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_YELLOW,\n        },\n    },\n    // Slackware\n    {\n        .names = {\"Slackware\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SLACKWARE,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // SlackwareSmall\n    {\n        .names = {\"Slackware_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_SLACKWARE_SMALL,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // SmartOS\n    {\n        .names = {\"SmartOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SMARTOS,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // SnigdhaOS\n    {\n        .names = {\"SnigdhaOS\", \"Snigdha\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SNIGDHAOS,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Soda\n    {\n        .names = {\"Soda\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SODA,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Source Mage\n    {\n        .names = {\"Source Mage\", \"Source Mage GNU/Linux\", \"source_mage\", \"sourcemage\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SOURCE_MAGE,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Solaris\n    {\n        .names = {\"solaris\", \"sunos\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SOLARIS,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // SolarisSmall\n    {\n        .names = {\"solaris_small\", \"sunos_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_SOLARIS_SMALL,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Solus\n    {\n        .names = {\"Solus\", \"solus-linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SOLUS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Sparky\n    {\n        .names = {\"Sparky\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SPARKY,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE\n        },\n    },\n    // Star\n    {\n        .names = {\"Star\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_STAR,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // StockLinux\n    {\n        .names = {\"Stock Linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_STOCK_LINUX,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // SteamOS\n    {\n        .names = {\"SteamOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_STEAMOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Steam Deck\n    {\n        .names = {\"SteamDeck\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_STEAMDECK,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Steam Deck Small\n    {\n        .names = {\"SteamDeck_small\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_STEAMDECK_SMALL,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Steam Deck OLED\n    {\n        .names = {\"SteamDeckOled\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_STEAMDECK,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // Sulin\n    {\n        .names = {\"Sulin\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SULIN,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // SummitOS\n    {\n        .names = {\"SummitOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SUMMITOS,\n        .colors = {\n            FF_COLOR_FG_RGB \"143;191;80\",\n            FF_COLOR_FG_RGB \"160;205;102\",\n            FF_COLOR_FG_RGB \"181;225;102\",\n        },\n    },\n    // Suse\n    {\n        .names = {\"suse\", \"suse-linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SUSE,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_GREEN,\n        },\n    },\n    // SuseSmall\n    {\n        .names = {\"suse_small\", \"suse-linux_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_SMALL,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_GREEN,\n        },\n    },\n    // Swagarch\n    {\n        .names = {\"Swagarch\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_SWAGARCH,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo T[] = {\n    // T2\n    {\n        .names = {\"T2\", \"T2 SDE\", \"T2/Linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_T2,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_BLUE,\n        },\n    },\n    // T2Small\n    {\n        .names = {\"T2_small\", \"T2 SDE_small\", \"T2/Linux_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_T2_SMALL,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_BLUE,\n        },\n    },\n    // Tails\n    {\n        .names = {\"Tails\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_TAILS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Tatra\n    {\n        .names = {\"Tatra\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_TATRA,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_GREEN,\n        },\n    },\n    // TeArch\n    {\n        .names = {\"TeArch\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_TEARCH,\n        .colors = {\n            FF_COLOR_FG_256 \"39\",\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // TempleOS\n    {\n        .names = {\"TempleOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_TEMPLEOS,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_CYAN,\n        },\n    },\n    // TileOS\n    {\n        .names = {\"TileOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_TILEOS,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_GREEN,\n        },\n    },\n    // Torizon OS\n    {\n        .names = {\"Torizon OS\", \"TorizonCore\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_TORIZONCORE,\n        .colors = {\n            FF_COLOR_FG_LIGHT_WHITE,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_BLUE\n        },\n    },\n    // Trisquel\n    {\n        .names = {\"Trisquel\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_TRISQUEL,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_CYAN,\n        },\n    },\n    // TrueNAS Scale\n    {\n        .names = {\"TrueNAS-Scale\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_TRUENAS_SCALE,\n        .colors = {\n            FF_COLOR_FG_256 \"39\",\n            FF_COLOR_FG_256 \"32\",\n            FF_COLOR_FG_256 \"248\",\n        },\n        .colorKeys = FF_COLOR_FG_256 \"248\",\n        .colorTitle = FF_COLOR_FG_256 \"32\",\n    },\n    // TuxedoOS\n    {\n        .names = {\"Tuxedo OS\", \"tuxedo\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_TUXEDO_OS,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_RED,\n        },\n    },\n    // Twister\n    {\n        .names = {\"Twister\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_TWISTER,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo U[] = {\n    // UBLinux\n    {\n        .names = {\"UBLinux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_UBLINUX,\n        .colors = {\n            FF_COLOR_FG_256 \"38\",\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_LIGHT_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_256 \"38\",\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // UBLinuxSmall\n    {\n        .names = {\"UBLinux_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_UBLINUX_SMALL,\n        .colors = {\n            FF_COLOR_FG_256 \"38\",\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_LIGHT_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_256 \"38\",\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Ubuntu\n    {\n        .names = {\"ubuntu\", \"ubuntu-linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_RED,\n        },\n    },\n    // UbuntuSmall\n    {\n        .names = {\"ubuntu_small\", \"ubuntu-linux_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_SMALL,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_RED,\n        },\n    },\n    // UbuntuOld\n    {\n        .names = {\"ubuntu_old\", \"ubuntu-linux_old\"},\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_OLD,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // UbuntuOld2\n    {\n        .names = {\"ubuntu_old2\"},\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_OLD2,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // UbuntuOld2Small\n    {\n        .names = {\"ubuntu_old2_small\", \"ubuntu_old2_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT | FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_OLD2_SMALL,\n        .colors = {\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // UbuntuBudgie\n    {\n        .names = {\"ubuntu budgie\", \"ubuntu-budgie\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_BUDGIE,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_RED,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // UbuntuCinnamon\n    {\n        .names = {\"ubuntu cinnamon\", \"ubuntu-cinnamon\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_CINNAMON,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // UbuntuGNOME\n    {\n        .names = {\"ubuntu gnome\", \"ubuntu-gnome\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_GNOME,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_MAGENTA,\n    },\n    // UbuntuKylin\n    {\n        .names = {\"ubuntu kylin\", \"ubuntu-kylin\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_KYLIN,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_RED,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // UbuntuMate\n    {\n        .names = {\"ubuntu mate\", \"ubuntu-mate\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_MATE,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_GREEN,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // UbuntuKde\n    {\n        .names = {\"ubuntu kde\", \"ubuntu-kde\", \"ubuntu-plasma\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_KUBUNTU,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // UbuntuStudio\n    {\n        .names = {\"ubuntu studio\", \"ubuntu-studio\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_STUDIO,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // UbuntuSway\n    {\n        .names = {\"ubuntu sway\", \"ubuntu-sway\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_SWAY,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // UbuntuTouch\n    {\n        .names = {\"ubuntu touch\", \"ubuntu-touch\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_TOUCH,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // UbuntuUnity\n    {\n        .names = {\"ubuntu unity\", \"ubuntu-unity\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_UNITY,\n        .colors = {\n            FF_COLOR_FG_MAGENTA,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Ultramarine\n    {\n        .names = {\"Ultramarine\", \"Ultramarine Linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ULTRAMARINE,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Ultramarine Small\n    {\n        .names = {\"Ultramarine_small\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ULTRAMARINE_SMALL,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Unifi\n    {\n        .names = {\"Unifi\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_UNIFI,\n        .colors = {\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Univalent\n    {\n        .names = {\"Univalent\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_UNIVALENT,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_CYAN,\n        },\n    },\n    // Univention\n    {\n        .names = {\"Univention\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_UNIVENTION,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // UOS\n    {\n        .names = {\"UOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_UOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // UrukOS\n    {\n        .names = {\"UrukOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_URUKOS,\n        .colors = {\n            FF_COLOR_FG_LIGHT_BLUE,\n            FF_COLOR_FG_LIGHT_BLUE,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_LIGHT_BLUE,\n            FF_COLOR_FG_BLUE,\n        }\n    },\n    // Uwuntu\n    {\n        .names = {\"uwuntu\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_UWUNTU,\n        .colors = {\n            FF_COLOR_FG_256 \"225\",\n            FF_COLOR_FG_256 \"206\",\n            FF_COLOR_FG_256 \"52\",\n        },\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo V[] = {\n    // Valhalla\n    {\n        .names = {\"Valhalla\", \"valhallaos\", \"valhalla-linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_VALHALLA,\n        .colors = {\n            FF_COLOR_FG_DEFAULT,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Vanilla\n    {\n        .names = {\"vanilla\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_VANILLA,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // Vanilla2\n    {\n        .names = {\"vanilla2\"},\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_VANILLA2,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_YELLOW,\n    },\n    // VanillaSmall\n    {\n        .names = {\"vanilla_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_VANILLA_SMALL,\n        .colors = {\n            FF_COLOR_FG_LIGHT_YELLOW,\n            FF_COLOR_FG_YELLOW,\n        },\n    },\n    // Venom\n    {\n        .names = {\"Venom\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_VENOM,\n        .colors = {\n            FF_COLOR_FG_LIGHT_BLACK,\n            FF_COLOR_FG_BLUE,\n        },\n    },\n    // VenomSmall\n    {\n        .names = {\"Venom_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_VENOM_SMALL,\n        .colors = {\n            FF_COLOR_FG_LIGHT_BLACK,\n            FF_COLOR_FG_BLUE,\n        },\n    },\n    // VincentOS\n    {\n        .names = {\"VincentOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_VINCENTOS,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_DEFAULT,\n        },\n    },\n    // Vnux\n    {\n        .names = {\"Vnux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_VNUX,\n        .colors = {\n            FF_COLOR_FG_256 \"11\",\n            FF_COLOR_FG_256 \"8\",\n            FF_COLOR_FG_256 \"15\",\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Vzlinux\n    {\n        .names = {\"Vzlinux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_VZLINUX,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_WHITE,\n            FF_COLOR_FG_YELLOW,\n        },\n    },\n    // Void\n    {\n        .names = {\"void\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_VOID,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_LIGHT_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // VoidSmall\n    {\n        .names = {\"void_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_VOID_SMALL,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // Void2\n    {\n        .names = {\"void2\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_VOID2,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_DEFAULT,\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // Void2Small\n    {\n        .names = {\"void2_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT | FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_VOID2_SMALL,\n        .colors = {\n            FF_COLOR_FG_GREEN,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo W[] = {\n    // WiiLinux\n    {\n        .names = {\"WiiLinuxNgx\", \"WiiLinux\", \"Wii-Linux\", \"Wii Linux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_WII_LINUX,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_WHITE,\n        },\n    },\n    // Windows2025\n    {\n        .names = {\"Windows Server 2025\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_WINDOWS_2025,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // Windows11\n    {\n        .names = {\"Windows 11\", \"Windows Server 2022\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_WINDOWS_11,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // Windows11Small\n    {\n        .names = {\"Windows 11_small\"},\n        .type = FF_LOGO_LINE_TYPE_SMALL_BIT,\n        .lines = FASTFETCH_DATATEXT_LOGO_WINDOWS_11_SMALL,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_CYAN,\n    },\n    // Windows8\n    {\n        .names = {\"Windows 8\", \"Windows 8.1\", \"Windows 10\", \"Windows Server 2012\", \"Windows Server 2012 R2\", \"Windows Server 2016\", \"Windows Server 2019\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_WINDOWS_8,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_CYAN,\n        },\n        .colorKeys = FF_COLOR_FG_YELLOW,\n        .colorTitle = FF_COLOR_FG_DEFAULT,\n    },\n    // Windows\n    {\n        .names = {\"Windows\", \"Windows 7\", \"Windows Server 2008\", \"Windows Server 2008 R2\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_WINDOWS,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_YELLOW,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_GREEN,\n    },\n    // Windows95\n    {\n        .names = {\"Windows 95\", \"Windows 9x\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_WINDOWS_95,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_CYAN,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // WolfOS\n    {\n        .names = {\"WolfOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_WOLFOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_GREEN,\n        },\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo X[] = {\n    // XCP-ng\n    {\n        .names = {\"XCP-ng\", \"xenenterprise\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_XCP_NG,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_BLACK,\n            FF_COLOR_FG_BLACK,\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_YELLOW,\n        }\n    },\n    // Xenia\n    {\n        .names = {\"Xenia\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_XENIA,\n        .colors = {\n            FF_COLOR_FG_RED,\n            FF_COLOR_FG_LIGHT_BLACK,\n        },\n        .colorKeys = FF_COLOR_FG_DEFAULT,\n        .colorTitle = FF_COLOR_FG_RED,\n    },\n    // Xenia_old\n    {\n        .names = {\"Xenia_old\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_XENIA_OLD,\n        .type = FF_LOGO_LINE_TYPE_ALTER_BIT,\n        .colors = {\n            FF_COLOR_FG_YELLOW,\n            FF_COLOR_FG_GREEN,\n            FF_COLOR_FG_RED,\n        }\n    },\n    //XeroArch\n    {\n        .names = {\"XeroArch\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_XEROARCH,\n        .colors = {\n            FF_COLOR_FG_256 \"50\",\n            FF_COLOR_FG_256 \"14\",\n            FF_COLOR_FG_256 \"50\",\n            FF_COLOR_FG_256 \"93\",\n            FF_COLOR_FG_256 \"16\",\n            FF_COLOR_FG_256 \"15\",\n        }\n    },\n    // Xferience\n    {\n        .names = {\"Xferience\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_XFERIENCE,\n        .colors = {\n            FF_COLOR_FG_CYAN,\n            FF_COLOR_FG_CYAN,\n        },\n    },\n    // Xubuntu\n    {\n        .names = {\"Xubuntu\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_XUBUNTU,\n        .colors = {\n            FF_COLOR_FG_256 \"25\",\n            FF_COLOR_FG_DEFAULT,\n        },\n    },\n    //Xray_OS\n    {\n        .names = {\"Xray_OS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_XRAY_OS,\n        .colors = {\n            FF_COLOR_FG_256 \"15\",\n            FF_COLOR_FG_256 \"14\",\n            FF_COLOR_FG_256 \"16\",\n        }\n    },\n    // Xinux\n    {\n        .names = {\"Xinux\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_XINUX,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n            FF_COLOR_FG_CYAN,\n        }\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo Y[] = {\n    // YiffOS\n    {\n        .names = {\"YiffOS\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_YIFFOS,\n        .colors = {\n            FF_COLOR_FG_256 \"93\",\n            FF_COLOR_FG_256 \"92\",\n        },\n    },\n    // LAST\n    {},\n};\n\nstatic const FFlogo Z[] = {\n    // Zorin\n    {\n        .names = {\"Zorin\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ZORIN,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // Z/OS\n    {\n        .names = {\"z/OS\", \"zos\"},\n        .lines = FASTFETCH_DATATEXT_LOGO_ZOS,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n    },\n    // Zraxyl\n    {\n        .names = {\"Zraxyl\" },\n        .lines = FASTFETCH_DATATEXT_LOGO_ZRAXYL,\n        .colors = {\n            FF_COLOR_FG_BLUE,\n        },\n        .colorKeys = FF_COLOR_FG_BLUE,\n        .colorTitle = FF_COLOR_FG_BLUE,\n    },\n    // LAST\n    {},\n};\n\nconst FFlogo* ffLogoBuiltins[] = {\n    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z,\n};\n"
  },
  {
    "path": "src/logo/image/im6.c",
    "content": "#ifdef FF_HAVE_IMAGEMAGICK6\n\n#include \"image.h\"\n#include \"common/library.h\"\n\n#include <magick/MagickCore.h>\n\nstatic FF_LIBRARY_SYMBOL(ResizeImage)\n\nstatic void* logoResize(const void* image, size_t width, size_t height, void* exceptionInfo)\n{\n    return ffResizeImage(image, width, height, UndefinedFilter, 1.0, exceptionInfo);\n}\n\nFFLogoImageResult ffLogoPrintImageIM6(FFLogoRequestData* requestData)\n{\n    FF_LIBRARY_LOAD(imageMagick, FF_LOGO_IMAGE_RESULT_INIT_ERROR, \"libMagickCore-6.Q16HDRI\" FF_LIBRARY_EXTENSION, 8, \"libMagickCore-6.Q16\" FF_LIBRARY_EXTENSION, 8)\n    FF_LIBRARY_LOAD_SYMBOL_ADDRESS(imageMagick, ffResizeImage, ResizeImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR)\n\n    FFLogoImageResult result = ffLogoPrintImageImpl(requestData, &(FFIMData) {\n        .resizeFunc = logoResize,\n        .library = imageMagick,\n    });\n\n    imageMagick = NULL; // leak imageMagick to prevent fastfetch from crashing #552\n    return result;\n}\n\n#endif\n"
  },
  {
    "path": "src/logo/image/im7.c",
    "content": "#ifdef FF_HAVE_IMAGEMAGICK7\n\n#include \"image.h\"\n#include \"common/library.h\"\n\n#include <MagickCore/MagickCore.h>\n\nstatic FF_LIBRARY_SYMBOL(ResizeImage)\n\nstatic void* logoResize(const void* image, size_t width, size_t height, void* exceptionInfo)\n{\n    return ffResizeImage(image, width, height, UndefinedFilter, exceptionInfo);\n}\n\nFFLogoImageResult ffLogoPrintImageIM7(FFLogoRequestData* requestData)\n{\n    FF_LIBRARY_LOAD(imageMagick, FF_LOGO_IMAGE_RESULT_INIT_ERROR,\n        \"libMagickCore-7.Q16HDRI\" FF_LIBRARY_EXTENSION, 11,\n        \"libMagickCore-7.Q16\" FF_LIBRARY_EXTENSION, 11,\n        \"libMagickCore-7.Q16HDRI-10\" FF_LIBRARY_EXTENSION, -1 // Required for Windows\n    )\n    FF_LIBRARY_LOAD_SYMBOL_ADDRESS(imageMagick, ffResizeImage, ResizeImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR)\n\n    FFLogoImageResult result = ffLogoPrintImageImpl(requestData, &(FFIMData) {\n        .resizeFunc = logoResize,\n        .library = imageMagick,\n    });\n\n    imageMagick = NULL; // leak imageMagick to prevent fastfetch from crashing #552\n    return result;\n}\n\n#endif\n"
  },
  {
    "path": "src/logo/image/image.c",
    "content": "#include \"image.h\"\n#include \"common/io.h\"\n#include \"common/printing.h\"\n#include \"common/processing.h\"\n#include \"common/stringUtils.h\"\n#include \"common/base64.h\"\n#include \"detection/terminalsize/terminalsize.h\"\n\n#include <limits.h>\n#include <math.h>\n\n#ifdef __APPLE__\n    #include <sys/syslimits.h>\n#elif _WIN32\n    #include <windows.h>\n#elif __linux__\n    #include <sys/sendfile.h>\n#elif __sun\n    #include <sys/termios.h>\n#endif\n\nstatic bool printImageIterm(bool printError)\n{\n    const FFOptionsLogo* options = &instance.config.logo;\n    FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();\n    if(!ffAppendFileBuffer(options->source.chars, &buf))\n    {\n        if (printError)\n            fputs(\"Logo (iterm): Failed to load image file\\n\", stderr);\n        return false;\n    }\n\n    fflush(stdout);\n\n    bool inTmux = false;\n    {\n        const char* term = getenv(\"TERM\");\n        inTmux = term && (ffStrStartsWith(term, \"screen\") || ffStrStartsWith(term, \"tmux\"));\n    }\n\n    FF_STRBUF_AUTO_DESTROY base64 = ffBase64EncodeStrbuf(&buf);\n    ffStrbufClear(&buf);\n\n    if (!options->width || !options->height)\n    {\n        if (options->position == FF_LOGO_POSITION_LEFT)\n        {\n            ffStrbufAppendF(&buf, \"\\e[2J\\e[3J\\e[%u;%uH\",\n                (unsigned) options->paddingTop + 1,\n                (unsigned) options->paddingLeft + 1\n            );\n        }\n        else if (options->position == FF_LOGO_POSITION_TOP)\n        {\n            ffStrbufAppendNC(&buf, options->paddingTop, '\\n');\n            ffStrbufAppendNC(&buf, options->paddingLeft, ' ');\n        }\n        else if (options->position == FF_LOGO_POSITION_RIGHT)\n        {\n            if (!options->width)\n            {\n                if (printError)\n                    fputs(\"Logo (iterm): Must set logo width when using position right\\n\", stderr);\n                return false;\n            }\n            ffStrbufAppendF(&buf, \"\\e[2J\\e[3J\\e[%u;9999999H\\e[%uD\", (unsigned) options->paddingTop + 1, (unsigned) options->paddingRight + options->width);\n        }\n        if (inTmux)\n            ffStrbufAppendS(&buf, \"\\ePtmux;\\e\");\n\n        if (options->width)\n            ffStrbufAppendF(&buf, \"\\e]1337;File=inline=1;width=%u:%s\\a\", (unsigned) options->width, base64.chars);\n        else\n            ffStrbufAppendF(&buf, \"\\e]1337;File=inline=1:%s\\a\", base64.chars);\n        if (inTmux)\n            ffStrbufAppendS(&buf, \"\\e\\\\\");\n        ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf);\n\n        if (options->position == FF_LOGO_POSITION_LEFT || options->position == FF_LOGO_POSITION_RIGHT)\n        {\n            uint16_t X = 0, Y = 0;\n            const char* error = ffGetTerminalResponse(\"\\e[6n\", 2, \"%*[^0-9]%hu;%huR\", &Y, &X);\n            if (error)\n            {\n                fprintf(stderr, \"\\nLogo (iterm): fail to query cursor position: %s\\n\", error);\n                return true; // We already printed image logo, don't print ascii logo then\n            }\n            if (X < options->paddingLeft + options->width)\n                X = (uint16_t) (options->paddingLeft + options->width);\n            if (options->position == FF_LOGO_POSITION_LEFT)\n                instance.state.logoWidth = X + options->paddingRight - 1;\n            instance.state.logoHeight = Y;\n            fputs(\"\\e[H\", stdout);\n        }\n        else if (options->position == FF_LOGO_POSITION_TOP)\n        {\n            instance.state.logoWidth = instance.state.logoHeight = 0;\n            ffPrintCharTimes('\\n', options->paddingRight);\n        }\n    }\n    else\n    {\n        ffStrbufAppendNC(&buf, options->paddingTop, '\\n');\n        if (options->position == FF_LOGO_POSITION_RIGHT)\n            ffStrbufAppendF(&buf, \"\\e[9999999C\\e[%uD\", (unsigned) options->paddingRight + options->width);\n        else if (options->paddingLeft)\n            ffStrbufAppendF(&buf, \"\\e[%uC\", (unsigned) options->paddingLeft);\n        if (inTmux)\n            ffStrbufAppendS(&buf, \"\\ePtmux;\\e\");\n        ffStrbufAppendF(&buf, \"\\e]1337;File=inline=1;width=%u;height=%u;preserveAspectRatio=%u:%s\\a\",\n            (unsigned) options->width,\n            (unsigned) options->height,\n            (unsigned) options->preserveAspectRatio,\n            base64.chars\n        );\n        if (inTmux)\n            ffStrbufAppendS(&buf, \"\\e\\\\\");\n        ffStrbufAppendC(&buf, '\\n');\n\n        if (options->position == FF_LOGO_POSITION_LEFT)\n        {\n            instance.state.logoWidth = options->width + options->paddingLeft + options->paddingRight;\n            instance.state.logoHeight = options->paddingTop + options->height;\n            ffStrbufAppendF(&buf, \"\\e[%uA\", (unsigned) instance.state.logoHeight);\n        }\n        else if (options->position == FF_LOGO_POSITION_TOP)\n        {\n            instance.state.logoWidth = instance.state.logoHeight = 0;\n            ffStrbufAppendNC(&buf, options->paddingRight, '\\n');\n        }\n        else if (options->position == FF_LOGO_POSITION_RIGHT)\n        {\n            instance.state.logoWidth = instance.state.logoHeight = 0;\n            ffStrbufAppendF(&buf, \"\\e[1G\\e[%uA\", (unsigned) options->height);\n        }\n        ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf);\n    }\n\n    return true;\n}\n\nstatic bool printImageKittyIcat(bool printError)\n{\n    const FFOptionsLogo* options = &instance.config.logo;\n\n    if (!ffPathExists(options->source.chars, FF_PATHTYPE_FILE))\n    {\n        if (printError)\n            fputs(\"Logo (kitty-icat): Failed to load image file\\n\", stderr);\n        return false;\n    }\n\n    fflush(stdout);\n\n    FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();\n\n    if (options->position == FF_LOGO_POSITION_LEFT)\n    {\n        ffStrbufAppendF(&buf, \"\\e[2J\\e[3J\\e[%u;%uH\",\n            (unsigned) options->paddingTop + 1,\n            (unsigned) options->paddingLeft + 1\n        );\n    }\n    else if (options->position == FF_LOGO_POSITION_TOP)\n    {\n        if (!options->width)\n        {\n            ffStrbufAppendNC(&buf, options->paddingTop, '\\n');\n            ffStrbufAppendNC(&buf, options->paddingLeft, ' ');\n        }\n        else\n        {\n            if (printError)\n                fputs(\"Logo (kitty-icat): position top is not supported when logo width is set\\n\", stderr);\n            return false;\n        }\n    }\n    else if (options->position == FF_LOGO_POSITION_RIGHT)\n    {\n        if (printError)\n            fputs(\"Logo (kitty-icat): position right is not supported\\n\", stderr);\n        return false;\n    }\n\n    uint32_t prevLength = buf.length;\n\n    const char* error = NULL;\n\n    if (options->width)\n    {\n        char place[64];\n        snprintf(place,\n            ARRAY_SIZE(place),\n            \"--place=%ux%u@%ux%u\",\n            options->width,\n            options->height == 0 ? 9999 : options->height,\n            options->paddingLeft + 1,\n            options->paddingTop + 1);\n\n        error = ffProcessAppendStdOut(&buf, (char* []) {\n            \"kitten\",\n            \"icat\",\n            \"-n\",\n            \"--align=center\",\n            place,\n            \"--scale-up\",\n            options->source.chars,\n            NULL,\n        });\n    }\n    else\n    {\n        error = ffProcessAppendStdOut(&buf, (char* []) {\n            \"kitten\",\n            \"icat\",\n            \"-n\",\n            \"--align=left\",\n            options->source.chars,\n            NULL,\n        });\n    }\n    if (error)\n    {\n        if (printError)\n            fprintf(stderr, \"Logo (kitty-icat): running `kitten icat` failed %s\\n\", error);\n        return false;\n    }\n\n    if (buf.length == prevLength)\n    {\n        if (printError)\n            fputs(\"Logo (kitty-icat): `kitten icat` returned empty output\\n\", stderr);\n        return false;\n    }\n\n    ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf);\n\n    if (options->position == FF_LOGO_POSITION_LEFT || options->position == FF_LOGO_POSITION_RIGHT)\n    {\n        uint16_t X = 0, Y = 0;\n        const char* error = ffGetTerminalResponse(\"\\e[6n\", 2, \"%*[^0-9]%hu;%huR\", &Y, &X);\n        if (error)\n        {\n            fprintf(stderr, \"\\nLogo (kitty-icat): fail to query cursor position: %s\\n\", error);\n            return true; // We already printed image logo, don't print ascii logo then\n        }\n        if (X < options->paddingLeft + options->width)\n            X = (uint16_t) (options->paddingLeft + options->width);\n        if (options->position == FF_LOGO_POSITION_LEFT)\n            instance.state.logoWidth = X + options->paddingRight - 1;\n        instance.state.logoHeight = Y;\n        fputs(\"\\e[H\", stdout);\n    }\n    else if (options->position == FF_LOGO_POSITION_TOP)\n    {\n        instance.state.logoWidth = instance.state.logoHeight = 0;\n        ffPrintCharTimes('\\n', options->paddingRight);\n    }\n\n    return true;\n}\n\nstatic bool printImageKittyDirect(bool printError)\n{\n    const FFOptionsLogo* options = &instance.config.logo;\n\n    if (!ffPathExists(options->source.chars, FF_PATHTYPE_FILE))\n    {\n        if (printError)\n            fputs(\"Logo (kitty-direct): Failed to load image file\\n\", stderr);\n        return false;\n    }\n\n    fflush(stdout);\n\n    bool inTmux = false;\n    {\n        const char* term = getenv(\"TERM\");\n        inTmux = term && (ffStrStartsWith(term, \"screen\") || ffStrStartsWith(term, \"tmux\"));\n    }\n\n    FF_STRBUF_AUTO_DESTROY base64 = ffBase64EncodeStrbuf(&options->source);\n    FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();\n\n    if (!options->width || !options->height)\n    {\n        if (options->position == FF_LOGO_POSITION_LEFT)\n        {\n            // We must clear the entre screen to make sure that terminal buffer won't scroll up\n            ffStrbufAppendF(&buf, \"\\e[2J\\e[3J\\e[%u;%uH\",\n                (unsigned) options->paddingTop + 1,\n                (unsigned) options->paddingLeft + 1\n            );\n        }\n        else if (options->position == FF_LOGO_POSITION_TOP)\n        {\n            ffStrbufAppendNC(&buf, options->paddingTop, '\\n');\n            ffStrbufAppendNC(&buf, options->paddingLeft, ' ');\n        }\n        else if (options->position == FF_LOGO_POSITION_RIGHT)\n        {\n            if (!options->width)\n            {\n                if (printError)\n                    fputs(\"Logo (iterm): Must set logo width when using position right\\n\", stderr);\n                return false;\n            }\n            ffStrbufAppendF(&buf, \"\\e[2J\\e[3J\\e[%u;9999999H\\e[%uD\", (unsigned) options->paddingTop + 1, (unsigned) options->paddingRight + options->width);\n        }\n\n        if (inTmux)\n            ffStrbufAppendS(&buf, \"\\ePtmux;\\e\");\n        if (options->width)\n            ffStrbufAppendF(&buf, \"\\e_Ga=T,f=100,t=f,c=%u;%s\", (unsigned) options->width, base64.chars);\n        else\n            ffStrbufAppendF(&buf, \"\\e_Ga=T,f=100,t=f;%s\", base64.chars);\n        if (inTmux)\n            ffStrbufAppendC(&buf, '\\e');\n        ffStrbufAppendS(&buf, \"\\e\\\\\");\n        if (inTmux)\n            ffStrbufAppendS(&buf, \"\\e\\\\\");\n\n        ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf);\n\n        if (options->position == FF_LOGO_POSITION_LEFT || options->position == FF_LOGO_POSITION_RIGHT)\n        {\n            uint16_t X = 0, Y = 0;\n            const char* error = ffGetTerminalResponse(\"\\e[6n\", 2, \"%*[^0-9]%hu;%huR\", &Y, &X);\n            if (error)\n            {\n                if (printError)\n                    fprintf(stderr, \"\\nLogo (kitty-direct): fail to query cursor position: %s\\n\", error);\n                return true; // We already printed image logo, don't print ascii logo then\n            }\n            if (X < options->paddingLeft + options->width)\n                X = (uint16_t) (options->paddingLeft + options->width);\n            if (options->position == FF_LOGO_POSITION_LEFT)\n                instance.state.logoWidth = X + options->paddingRight - 1;\n            instance.state.logoHeight = Y;\n            fputs(\"\\e[H\", stdout);\n        }\n        else if (options->position == FF_LOGO_POSITION_TOP)\n        {\n            instance.state.logoWidth = instance.state.logoHeight = 0;\n            ffPrintCharTimes('\\n', options->paddingRight);\n        }\n    }\n    else\n    {\n        ffStrbufAppendNC(&buf, options->paddingTop, '\\n');\n\n        if (options->position == FF_LOGO_POSITION_RIGHT)\n            ffStrbufAppendF(&buf, \"\\e[9999999C\\e[%uD\", (unsigned) options->paddingRight + options->width);\n        else if (options->paddingLeft)\n            ffStrbufAppendF(&buf, \"\\e[%uC\", (unsigned) options->paddingLeft);\n\n        if (inTmux)\n            ffStrbufAppendS(&buf, \"\\ePtmux;\\e\");\n\n        ffStrbufAppendF(&buf, \"\\e_Ga=T,f=100,t=f,c=%u,r=%u;%s\\e\\\\\",\n            (unsigned) options->width,\n            (unsigned) options->height,\n            base64.chars\n        );\n        if (inTmux)\n            ffStrbufAppendS(&buf, \"\\e\\\\\");\n        ffStrbufAppendC(&buf, '\\n');\n        if (options->position == FF_LOGO_POSITION_LEFT)\n        {\n            instance.state.logoWidth = options->width + options->paddingLeft + options->paddingRight;\n            instance.state.logoHeight = options->paddingTop + options->height;\n            ffStrbufAppendF(&buf, \"\\e[%uA\", (unsigned) instance.state.logoHeight);\n        }\n        else if (options->position == FF_LOGO_POSITION_TOP)\n        {\n            instance.state.logoWidth = instance.state.logoHeight = 0;\n            ffStrbufAppendNC(&buf, options->paddingRight, '\\n');\n        }\n        else if (options->position == FF_LOGO_POSITION_RIGHT)\n        {\n            instance.state.logoWidth = instance.state.logoHeight = 0;\n            ffStrbufAppendF(&buf, \"\\e[1G\\e[%uA\", (unsigned) options->height);\n        }\n\n        ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf);\n    }\n\n    return true;\n}\n\n#if defined(FF_HAVE_IMAGEMAGICK7) || defined(FF_HAVE_IMAGEMAGICK6)\n\n#define FF_KITTY_MAX_CHUNK_SIZE 4096\n\n#define FF_CACHE_FILE_HEIGHT \"height\"\n#define FF_CACHE_FILE_WIDTH \"width\"\n#define FF_CACHE_FILE_SIXEL \"sixel\"\n#define FF_CACHE_FILE_KITTY_COMPRESSED \"kittyc\"\n#define FF_CACHE_FILE_KITTY_UNCOMPRESSED \"kittyu\"\n#define FF_CACHE_FILE_CHAFA \"chafa\"\n\n#include <string.h>\n#include <unistd.h>\n#include <fcntl.h>\n\n#ifndef _WIN32\n#include <sys/ioctl.h>\n#else\n#include <wincon.h>\n#include \"common/path.h\"\n#endif\n\n#ifdef FF_HAVE_ZLIB\n#include \"common/library.h\"\n#include <stdlib.h>\n#include <zlib.h>\n\nstatic bool compressBlob(void** blob, size_t* length)\n{\n    FF_LIBRARY_LOAD(zlib, false, \"libz\" FF_LIBRARY_EXTENSION, 2)\n    FF_LIBRARY_LOAD_SYMBOL(zlib, compressBound, false)\n    FF_LIBRARY_LOAD_SYMBOL(zlib, compress2, false)\n\n    uLong compressedLength = ffcompressBound(*length);\n    void* compressed = malloc(compressedLength);\n    if(compressed == NULL)\n        return false;\n\n    if(ffcompress2(compressed, &compressedLength, *blob, *length, Z_BEST_COMPRESSION) != Z_OK)\n    {\n        free(compressed);\n        return false;\n    }\n\n    free(*blob);\n\n    *length = (size_t) compressedLength;\n    *blob = compressed;\n    return true;\n}\n\n#endif // FF_HAVE_ZLIB\n\n//We use only the defines from here, that are exactly the same in both versions\n#ifdef FF_HAVE_IMAGEMAGICK7\n    #include <MagickCore/MagickCore.h>\n#else\n    #include <magick/MagickCore.h>\n#endif\n\ntypedef struct ImageData\n{\n    FF_LIBRARY_SYMBOL(CopyMagickString)\n    FF_LIBRARY_SYMBOL(ImageToBlob)\n    FF_LIBRARY_SYMBOL(Base64Encode)\n\n    ImageInfo* imageInfo;\n    Image* image;\n    ExceptionInfo* exceptionInfo;\n} ImageData;\n\nstatic inline bool checkAllocationResult(void* data, size_t length)\n{\n    if(data == NULL)\n        return false;\n\n    if(length == 0)\n    {\n        free(data);\n        return false;\n    }\n\n    return true;\n}\n\nstatic void writeCacheStrbuf(FFLogoRequestData* requestData, const FFstrbuf* value, const char* cacheFileName)\n{\n    uint32_t cacheDirLength = requestData->cacheDir.length;\n    ffStrbufAppendS(&requestData->cacheDir, cacheFileName);\n    ffWriteFileBuffer(requestData->cacheDir.chars, value);\n    ffStrbufSubstrBefore(&requestData->cacheDir, cacheDirLength);\n}\n\nstatic void writeCacheUint32(FFLogoRequestData* requestData, uint32_t value, const char* cacheFileName)\n{\n    FFstrbuf content;\n    content.chars = (char*) &value;\n    content.length = sizeof(value);\n    writeCacheStrbuf(requestData, &content, cacheFileName);\n}\n\nstatic void printImagePixels(FFLogoRequestData* requestData, const FFstrbuf* result, const char* cacheFileName)\n{\n    const FFOptionsLogo* options = &instance.config.logo;\n    //Calculate character dimensions\n    instance.state.logoWidth = requestData->logoCharacterWidth + options->paddingLeft + options->paddingRight;\n    instance.state.logoHeight = requestData->logoCharacterHeight + options->paddingTop - 1;\n\n    //Write cache files\n    writeCacheStrbuf(requestData, result, cacheFileName);\n\n    if(options->width == 0)\n        writeCacheUint32(requestData, requestData->logoCharacterWidth, FF_CACHE_FILE_WIDTH);\n\n    if(options->height == 0)\n        writeCacheUint32(requestData, requestData->logoCharacterHeight, FF_CACHE_FILE_HEIGHT);\n\n    //Write result to stdout\n    ffPrintCharTimes('\\n', options->paddingTop);\n    if (options->position == FF_LOGO_POSITION_RIGHT)\n        printf(\"\\e[9999999C\\e[%uD\", (unsigned) options->paddingRight + requestData->logoCharacterWidth);\n    else if (options->paddingLeft)\n        printf(\"\\e[%uC\", (unsigned) options->paddingLeft);\n    fflush(stdout);\n    ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), result);\n\n    if (options->position != FF_LOGO_POSITION_TOP)\n    {\n        //Go to upper left corner\n        printf(\"\\e[1G\\e[%uA\", instance.state.logoHeight);\n    }\n\n    if (options->position != FF_LOGO_POSITION_LEFT)\n        instance.state.logoWidth = instance.state.logoHeight = 0;\n}\n\nstatic bool printImageSixel(FFLogoRequestData* requestData, const ImageData* imageData)\n{\n    imageData->ffCopyMagickString(imageData->imageInfo->magick, \"SIXEL\", 6);\n\n    size_t length;\n    void* blob = imageData->ffImageToBlob(imageData->imageInfo, imageData->image, &length, imageData->exceptionInfo);\n    if(!checkAllocationResult(blob, length))\n        return false;\n\n    FFstrbuf result;\n    result.chars = (char*) blob;\n    result.length = (uint32_t) length;\n\n    printImagePixels(requestData, &result, FF_CACHE_FILE_SIXEL);\n\n    free(blob);\n    return true;\n}\n\nstatic void appendKittyChunk(FFstrbuf* result, const char** blob, size_t* length, bool printEscapeCode)\n{\n    uint32_t chunkSize = *length > FF_KITTY_MAX_CHUNK_SIZE ? FF_KITTY_MAX_CHUNK_SIZE : (uint32_t) *length;\n\n    if(printEscapeCode)\n        ffStrbufAppendS(result, \"\\033_G\");\n    else\n        ffStrbufAppendC(result, ',');\n\n    ffStrbufAppendS(result, chunkSize != *length ? \"m=1\" : \"m=0\");\n    ffStrbufAppendC(result, ';');\n    ffStrbufAppendNS(result, chunkSize, *blob);\n    ffStrbufAppendS(result, \"\\033\\\\\");\n    *length -= chunkSize;\n    *blob += chunkSize;\n}\n\nstatic bool printImageKitty(FFLogoRequestData* requestData, const ImageData* imageData)\n{\n    imageData->ffCopyMagickString(imageData->imageInfo->magick, \"RGBA\", 5);\n\n    size_t length;\n    void* blob = imageData->ffImageToBlob(imageData->imageInfo, imageData->image, &length, imageData->exceptionInfo);\n    if(!checkAllocationResult(blob, length))\n        return false;\n\n    #ifdef FF_HAVE_ZLIB\n        bool isCompressed = compressBlob(&blob, &length);\n    #else\n        bool isCompressed = false;\n    #endif\n\n    char* chars = imageData->ffBase64Encode(blob, length, &length);\n    free(blob);\n    if(!checkAllocationResult(chars, length))\n        return false;\n\n    FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateA((uint32_t) (length + 1024));\n\n    const char* currentPos = chars;\n    size_t remainingLength = length;\n\n    ffStrbufAppendF(&result, \"\\033_Ga=T,f=32,s=%u,v=%u\", requestData->logoPixelWidth, requestData->logoPixelHeight);\n    if(isCompressed)\n        ffStrbufAppendS(&result, \",o=z\");\n    appendKittyChunk(&result, &currentPos, &remainingLength, false);\n    while(remainingLength > 0)\n        appendKittyChunk(&result, &currentPos, &remainingLength, true);\n\n    printImagePixels(requestData, &result, isCompressed ? FF_CACHE_FILE_KITTY_COMPRESSED : FF_CACHE_FILE_KITTY_UNCOMPRESSED);\n\n    free(chars);\n    return true;\n}\n\n#ifdef FF_HAVE_CHAFA\n#include <chafa.h>\nstatic bool printImageChafa(FFLogoRequestData* requestData, const ImageData* imageData)\n{\n    FF_LIBRARY_LOAD(chafa, false,\n        \"libchafa\" FF_LIBRARY_EXTENSION, 1,\n        \"libchafa-0\" FF_LIBRARY_EXTENSION, -1 // Required for Windows\n    )\n    FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_symbol_map_new, false)\n    FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_symbol_map_apply_selectors, false)\n    FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_config_new, false)\n    FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_config_set_geometry, false)\n    FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_config_set_symbol_map, false)\n    FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_new, false)\n    FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_draw_all_pixels, false)\n    FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_print, false)\n    FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_unref, false)\n    FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_config_unref, false)\n    FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_symbol_map_unref, false)\n\n    imageData->ffCopyMagickString(imageData->imageInfo->magick, \"RGBA\", 5);\n    size_t length;\n    void* blob = imageData->ffImageToBlob(imageData->imageInfo, imageData->image, &length, imageData->exceptionInfo);\n    if(!checkAllocationResult(blob, length))\n        return false;\n\n    ChafaSymbolMap* symbolMap = ffchafa_symbol_map_new();\n    GError* error = NULL;\n    if(!ffchafa_symbol_map_apply_selectors(symbolMap, instance.config.logo.chafaSymbols.chars, &error))\n        fputs(error->message, stderr);\n\n    ChafaCanvasConfig* canvasConfig = ffchafa_canvas_config_new();\n    ffchafa_canvas_config_set_geometry(canvasConfig, (gint) requestData->logoCharacterWidth, (gint) requestData->logoCharacterHeight);\n    ffchafa_canvas_config_set_symbol_map(canvasConfig, symbolMap);\n\n    if(instance.config.logo.chafaFgOnly)\n    {\n        FF_LIBRARY_LOAD_SYMBOL_LAZY(chafa, chafa_canvas_config_set_fg_only_enabled);\n        if(ffchafa_canvas_config_set_fg_only_enabled)\n            ffchafa_canvas_config_set_fg_only_enabled(canvasConfig, true);\n    }\n    if(instance.config.logo.chafaCanvasMode < CHAFA_CANVAS_MODE_MAX)\n    {\n        FF_LIBRARY_LOAD_SYMBOL_LAZY(chafa, chafa_canvas_config_set_canvas_mode);\n        if(ffchafa_canvas_config_set_canvas_mode)\n            ffchafa_canvas_config_set_canvas_mode(canvasConfig, (ChafaCanvasMode) instance.config.logo.chafaCanvasMode);\n    }\n    if(instance.config.logo.chafaColorSpace < CHAFA_COLOR_SPACE_MAX)\n    {\n        FF_LIBRARY_LOAD_SYMBOL_LAZY(chafa, chafa_canvas_config_set_color_space)\n        if(ffchafa_canvas_config_set_color_space)\n            ffchafa_canvas_config_set_color_space(canvasConfig, (ChafaColorSpace) instance.config.logo.chafaColorSpace);\n    }\n    if(instance.config.logo.chafaDitherMode < CHAFA_DITHER_MODE_MAX)\n    {\n        FF_LIBRARY_LOAD_SYMBOL_LAZY(chafa, chafa_canvas_config_set_dither_mode)\n        if(ffchafa_canvas_config_set_dither_mode)\n            ffchafa_canvas_config_set_dither_mode(canvasConfig, (ChafaDitherMode) instance.config.logo.chafaDitherMode);\n    }\n\n    ChafaCanvas* canvas = ffchafa_canvas_new(canvasConfig);\n    ffchafa_canvas_draw_all_pixels(\n        canvas,\n        CHAFA_PIXEL_RGBA8_UNASSOCIATED,\n        blob,\n        (gint) imageData->image->columns,\n        (gint) imageData->image->rows,\n        (gint) imageData->image->columns * 4\n    );\n\n    GString* str = ffchafa_canvas_print(canvas, NULL);\n    FFstrbuf result;\n    result.allocated = (uint32_t) str->allocated_len;\n    result.length = (uint32_t) str->len;\n    result.chars = str->str;\n\n    ffLogoPrintChars(result.chars, false);\n    writeCacheStrbuf(requestData, &result, FF_CACHE_FILE_CHAFA);\n\n    // FIXME: These functions must be imported from `libglib` dlls on Windows\n    FF_LIBRARY_LOAD_SYMBOL_LAZY(chafa, g_string_free);\n    if(ffg_string_free)\n        ffg_string_free(str, TRUE);\n    if(error)\n    {\n        FF_LIBRARY_LOAD_SYMBOL_LAZY(chafa, g_error_free)\n        if(ffg_error_free)\n            ffg_error_free(error);\n    }\n\n    ffchafa_canvas_unref(canvas);\n    ffchafa_canvas_config_unref(canvasConfig);\n    ffchafa_symbol_map_unref(symbolMap);\n\n    return true;\n}\n#endif\n\nFFLogoImageResult ffLogoPrintImageImpl(FFLogoRequestData* requestData, const FFIMData* imData)\n{\n    FF_LIBRARY_LOAD_SYMBOL(imData->library, MagickCoreGenesis, FF_LOGO_IMAGE_RESULT_INIT_ERROR)\n    FF_LIBRARY_LOAD_SYMBOL(imData->library, MagickCoreTerminus, FF_LOGO_IMAGE_RESULT_INIT_ERROR)\n    FF_LIBRARY_LOAD_SYMBOL(imData->library, AcquireExceptionInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR)\n    FF_LIBRARY_LOAD_SYMBOL(imData->library, DestroyExceptionInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR)\n    FF_LIBRARY_LOAD_SYMBOL(imData->library, AcquireImageInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR)\n    FF_LIBRARY_LOAD_SYMBOL(imData->library, DestroyImageInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR)\n    FF_LIBRARY_LOAD_SYMBOL(imData->library, ReadImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR)\n    FF_LIBRARY_LOAD_SYMBOL(imData->library, DestroyImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR)\n\n    ImageData imageData;\n\n    FF_LIBRARY_LOAD_SYMBOL_VAR(imData->library, imageData, CopyMagickString, FF_LOGO_IMAGE_RESULT_INIT_ERROR)\n    FF_LIBRARY_LOAD_SYMBOL_VAR(imData->library, imageData, ImageToBlob, FF_LOGO_IMAGE_RESULT_INIT_ERROR)\n    FF_LIBRARY_LOAD_SYMBOL_VAR(imData->library, imageData, Base64Encode, FF_LOGO_IMAGE_RESULT_INIT_ERROR)\n\n    ffMagickCoreGenesis(NULL, MagickFalse);\n\n    imageData.exceptionInfo = ffAcquireExceptionInfo();\n    if(imageData.exceptionInfo == NULL)\n    {\n        ffMagickCoreTerminus();\n        return FF_LOGO_IMAGE_RESULT_RUN_ERROR;\n    }\n\n    ImageInfo* imageInfoIn = ffAcquireImageInfo();\n    if(imageInfoIn == NULL)\n    {\n        ffDestroyExceptionInfo(imageData.exceptionInfo);\n        ffMagickCoreTerminus();\n        return FF_LOGO_IMAGE_RESULT_RUN_ERROR;\n    }\n\n    //+1, because we need to copy the null byte too\n    imageData.ffCopyMagickString(imageInfoIn->filename, instance.config.logo.source.chars, instance.config.logo.source.length + 1);\n\n    imageData.image = ffReadImage(imageInfoIn, imageData.exceptionInfo);\n    ffDestroyImageInfo(imageInfoIn);\n    if(imageData.image == NULL)\n    {\n        ffDestroyExceptionInfo(imageData.exceptionInfo);\n        ffMagickCoreTerminus();\n        return FF_LOGO_IMAGE_RESULT_RUN_ERROR;\n    }\n\n    if(requestData->logoPixelWidth == 0 && requestData->logoPixelHeight == 0)\n    {\n        requestData->logoPixelWidth = (uint32_t) imageData.image->columns;\n        requestData->logoPixelHeight = (uint32_t) imageData.image->rows;\n    }\n    else if(requestData->logoPixelWidth == 0)\n        requestData->logoPixelWidth = (uint32_t) ((double) imageData.image->columns / (double) imageData.image->rows * requestData->logoPixelHeight);\n    else if(requestData->logoPixelHeight == 0)\n        requestData->logoPixelHeight = (uint32_t) ((double) imageData.image->rows / (double) imageData.image->columns * requestData->logoPixelWidth);\n\n    requestData->logoCharacterWidth = (uint32_t) ceil((double) requestData->logoPixelWidth / requestData->characterPixelWidth);\n    requestData->logoCharacterHeight = (uint32_t) ceil((double) requestData->logoPixelHeight / requestData->characterPixelHeight);\n\n    if(requestData->logoPixelWidth == 0 || requestData->logoPixelHeight == 0 || requestData->logoCharacterWidth == 0 || requestData->logoCharacterHeight == 0)\n    {\n        ffDestroyImage(imageData.image);\n        ffDestroyExceptionInfo(imageData.exceptionInfo);\n        ffMagickCoreTerminus();\n        return FF_LOGO_IMAGE_RESULT_RUN_ERROR;\n    }\n\n    Image* resized = imData->resizeFunc(imageData.image, requestData->logoPixelWidth, requestData->logoPixelHeight, imageData.exceptionInfo);\n    ffDestroyImage(imageData.image);\n    if(resized == NULL)\n    {\n        ffDestroyExceptionInfo(imageData.exceptionInfo);\n        ffMagickCoreTerminus();\n        return FF_LOGO_IMAGE_RESULT_RUN_ERROR;\n    }\n    imageData.image = resized;\n\n    imageData.imageInfo = ffAcquireImageInfo();\n    if(imageData.imageInfo == NULL)\n    {\n        ffDestroyImage(imageData.image);\n        ffDestroyExceptionInfo(imageData.exceptionInfo);\n        ffMagickCoreTerminus();\n        return FF_LOGO_IMAGE_RESULT_RUN_ERROR;\n    }\n\n    bool printSuccessful = false;\n    if(requestData->type == FF_LOGO_TYPE_IMAGE_CHAFA)\n    {\n        #ifdef FF_HAVE_CHAFA\n            printSuccessful = printImageChafa(requestData, &imageData);\n        #endif\n    }\n    else if(requestData->type == FF_LOGO_TYPE_IMAGE_KITTY)\n        printSuccessful = printImageKitty(requestData, &imageData);\n    else if(requestData->type == FF_LOGO_TYPE_IMAGE_SIXEL)\n        printSuccessful = printImageSixel(requestData, &imageData);\n\n    ffDestroyImageInfo(imageData.imageInfo);\n    ffDestroyImage(imageData.image);\n    ffDestroyExceptionInfo(imageData.exceptionInfo);\n    ffMagickCoreTerminus();\n\n    return printSuccessful ? FF_LOGO_IMAGE_RESULT_SUCCESS : FF_LOGO_IMAGE_RESULT_RUN_ERROR;\n}\n\nstatic FFNativeFD getCacheFD(FFLogoRequestData* requestData, const char* fileName)\n{\n    uint32_t cacheDirLength = requestData->cacheDir.length;\n    ffStrbufAppendS(&requestData->cacheDir, fileName);\n    #ifndef _WIN32\n    int fd = open(requestData->cacheDir.chars, O_RDONLY\n        #ifdef O_CLOEXEC\n            | O_CLOEXEC\n        #endif\n    );\n    #else\n    HANDLE fd = CreateFileA(requestData->cacheDir.chars, GENERIC_READ,\n        FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);\n    #endif\n    ffStrbufSubstrBefore(&requestData->cacheDir, cacheDirLength);\n    return fd;\n}\n\nstatic void readCachedStrbuf(FFLogoRequestData* requestData, FFstrbuf* result, const char* cacheFileName)\n{\n    uint32_t cacheDirLength = requestData->cacheDir.length;\n    ffStrbufAppendS(&requestData->cacheDir, cacheFileName);\n    ffAppendFileBuffer(requestData->cacheDir.chars, result);\n    ffStrbufSubstrBefore(&requestData->cacheDir, cacheDirLength);\n}\n\nstatic uint32_t readCachedUint32(FFLogoRequestData* requestData, const char* cacheFileName)\n{\n    FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate();\n    readCachedStrbuf(requestData, &content, cacheFileName);\n\n    uint32_t result = 0;\n\n    if(content.length != sizeof(result))\n        return 0;\n\n    memcpy(&result, content.chars, sizeof(result));\n\n    return result;\n}\n\nstatic bool printCachedChars(FFLogoRequestData* requestData)\n{\n    FF_STRBUF_AUTO_DESTROY content = ffStrbufCreateA(32768);\n\n    if(requestData->type == FF_LOGO_TYPE_IMAGE_CHAFA)\n        readCachedStrbuf(requestData, &content, FF_CACHE_FILE_CHAFA);\n\n    if(content.length == 0)\n        return false;\n\n    ffLogoPrintChars(content.chars, false);\n    return true;\n}\n\nstatic bool printCachedPixel(FFLogoRequestData* requestData)\n{\n    FFOptionsLogo* options = &instance.config.logo;\n\n    requestData->logoCharacterWidth = options->width;\n    if(requestData->logoCharacterWidth == 0)\n    {\n        requestData->logoCharacterWidth = readCachedUint32(requestData, FF_CACHE_FILE_WIDTH);\n        if(requestData->logoCharacterWidth == 0)\n            return false;\n    }\n\n    requestData->logoCharacterHeight = options->height;\n    if(requestData->logoCharacterHeight == 0)\n    {\n        requestData->logoCharacterHeight = readCachedUint32(requestData, FF_CACHE_FILE_HEIGHT);\n        if(requestData->logoCharacterHeight == 0)\n            return false;\n    }\n\n    FF_AUTO_CLOSE_FD FFNativeFD fd = FF_INVALID_FD;\n    if(requestData->type == FF_LOGO_TYPE_IMAGE_KITTY)\n    {\n        fd = getCacheFD(requestData, FF_CACHE_FILE_KITTY_COMPRESSED);\n        if(!ffIsValidNativeFD(fd))\n            fd = getCacheFD(requestData, FF_CACHE_FILE_KITTY_UNCOMPRESSED);\n    }\n    else if(requestData->type == FF_LOGO_TYPE_IMAGE_SIXEL)\n        fd = getCacheFD(requestData, FF_CACHE_FILE_SIXEL);\n\n    if(!ffIsValidNativeFD(fd))\n        return false;\n\n    ffPrintCharTimes('\\n', options->paddingTop);\n    if (options->position == FF_LOGO_POSITION_RIGHT)\n        printf(\"\\e[9999999C\\e[%uD\", (unsigned) options->paddingRight + requestData->logoCharacterWidth);\n    else if (options->paddingLeft)\n        printf(\"\\e[%uC\", (unsigned) options->paddingLeft);\n    fflush(stdout);\n\n    bool sent = false;\n    #ifdef __linux__\n    struct stat st;\n    if (fstat(fd, &st) >= 0)\n    {\n        while (st.st_size > 0)\n        {\n            ssize_t bytes = sendfile(STDOUT_FILENO, fd, NULL, (size_t) st.st_size);\n            if (bytes > 0)\n            {\n                sent = true;\n                st.st_size -= bytes;\n            }\n            else\n                break;\n        }\n    }\n    #endif\n\n    if (!sent)\n    {\n        char buffer[32768];\n        ssize_t readBytes;\n        while((readBytes = ffReadFDData(fd, sizeof(buffer), buffer)) > 0)\n            ffWriteFDData(FFUnixFD2NativeFD(STDOUT_FILENO), (size_t) readBytes, buffer);\n    }\n\n    instance.state.logoWidth = requestData->logoCharacterWidth + options->paddingLeft + options->paddingRight;\n    instance.state.logoHeight = requestData->logoCharacterHeight + options->paddingTop;\n\n    if (options->position != FF_LOGO_POSITION_TOP)\n    {\n        //Go to upper left corner\n        printf(\"\\e[1G\\e[%uA\", instance.state.logoHeight);\n    }\n\n    if (options->position != FF_LOGO_POSITION_LEFT)\n        instance.state.logoWidth = instance.state.logoHeight = 0;\n    return true;\n}\n\nstatic bool printCached(FFLogoRequestData* requestData)\n{\n    if(requestData->type == FF_LOGO_TYPE_IMAGE_CHAFA)\n        return printCachedChars(requestData);\n    else\n        return printCachedPixel(requestData);\n}\n\nstatic bool getCharacterPixelDimensions(FFLogoRequestData* requestData)\n{\n    #ifdef _WIN32\n\n    CONSOLE_FONT_INFOEX cfi;\n    if(GetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi)) // Only works for ConHost\n    {\n        requestData->characterPixelWidth = cfi.dwFontSize.X;\n        requestData->characterPixelHeight = cfi.dwFontSize.Y;\n    }\n    if (requestData->characterPixelWidth > 1.0 && requestData->characterPixelHeight > 1.0)\n        return true;\n    #endif\n\n    FFTerminalSizeResult termSize = {};\n    if (ffDetectTerminalSize(&termSize))\n    {\n        requestData->characterPixelWidth = termSize.width / (double) termSize.columns;\n        requestData->characterPixelHeight = termSize.height / (double) termSize.rows;\n    }\n\n    return requestData->characterPixelWidth > 1.0 && requestData->characterPixelHeight > 1.0;\n}\n\nstatic bool printImageIfExistsSlowPath(FFLogoType type, bool printError)\n{\n    FFLogoRequestData requestData;\n    requestData.type = type;\n    requestData.characterPixelWidth = 1;\n    requestData.characterPixelHeight = 1;\n\n    if(!getCharacterPixelDimensions(&requestData))\n    {\n        if(printError)\n            fputs(\"Logo: getCharacterPixelDimensions() failed\\n\", stderr);\n        return false;\n    }\n\n    requestData.logoPixelWidth = (uint32_t) ceil((double) instance.config.logo.width * requestData.characterPixelWidth);\n    requestData.logoPixelHeight = (uint32_t) ceil((double) instance.config.logo.height * requestData.characterPixelHeight);\n\n    ffStrbufInit(&requestData.cacheDir);\n    ffStrbufAppend(&requestData.cacheDir, &instance.state.platform.cacheDir);\n    ffStrbufAppendS(&requestData.cacheDir, \"fastfetch/images\");\n\n    ffStrbufEnsureFree(&requestData.cacheDir, PATH_MAX);\n    char* filePath = requestData.cacheDir.chars + requestData.cacheDir.length;\n    if(realpath(instance.config.logo.source.chars, filePath) == NULL)\n    {\n        //We can safely return here, because if realpath failed, we surely won't be able to read the file\n        ffStrbufDestroy(&requestData.cacheDir);\n        if(printError)\n            fputs(\"Logo: Querying realpath of the image source failed\\n\", stderr);\n        return false;\n    }\n\n    #ifdef _WIN32\n    filePath[1] = filePath[0]; // Drive Name\n    filePath[0] = '/';\n    #endif\n\n    ffStrbufRecalculateLength(&requestData.cacheDir);\n    ffStrbufEnsureEndsWithC(&requestData.cacheDir, '/');\n    ffStrbufAppendF(&requestData.cacheDir, \"%u*%u/\", requestData.logoPixelWidth, requestData.logoPixelHeight);\n\n    if(!instance.config.logo.recache && printCached(&requestData))\n    {\n        ffStrbufDestroy(&requestData.cacheDir);\n        return true;\n    }\n\n    FFLogoImageResult result = FF_LOGO_IMAGE_RESULT_INIT_ERROR;\n\n    #ifdef FF_HAVE_IMAGEMAGICK7\n        result = ffLogoPrintImageIM7(&requestData);\n    #endif\n\n    #ifdef FF_HAVE_IMAGEMAGICK6\n        if(result == FF_LOGO_IMAGE_RESULT_INIT_ERROR)\n            result = ffLogoPrintImageIM6(&requestData);\n    #endif\n\n    ffStrbufDestroy(&requestData.cacheDir);\n\n    if(result == FF_LOGO_IMAGE_RESULT_SUCCESS)\n        return true;\n\n    if(printError)\n    {\n        if(result == FF_LOGO_IMAGE_RESULT_INIT_ERROR)\n            fputs(\"Logo: Image Magick library not found\\n\", stderr);\n        else\n            fputs(\"Logo: Failed to load / convert the image source\\n\", stderr);\n    }\n\n    return false;\n}\n\n#endif //FF_HAVE_IMAGEMAGICK{6, 7}\n\nbool ffLogoPrintImageIfExists(FFLogoType type, bool printError)\n{\n    if(instance.config.display.pipe)\n    {\n        if(printError)\n            fputs(\"Logo: Image logo is not supported in pipe mode\\n\", stderr);\n        return false;\n    }\n\n    if(!ffPathExists(instance.config.logo.source.chars, FF_PATHTYPE_FILE))\n    {\n        if(printError)\n            fprintf(stderr, \"Logo: Image source \\\"%s\\\" does not exist\\n\", instance.config.logo.source.chars);\n        return false;\n    }\n\n    const char* term = getenv(\"TERM\");\n    if((term && ffStrEquals(term, \"screen\")) || getenv(\"ZELLIJ\"))\n    {\n        if(printError)\n            fputs(\"Logo: Image logo is not supported in terminal multiplexers\\n\", stderr);\n        return false;\n    }\n\n    if(type == FF_LOGO_TYPE_IMAGE_ITERM)\n        return printImageIterm(printError);\n\n    if(type == FF_LOGO_TYPE_IMAGE_KITTY_DIRECT)\n        return printImageKittyDirect(printError);\n\n    if(type == FF_LOGO_TYPE_IMAGE_KITTY_ICAT)\n        return printImageKittyIcat(printError);\n\n    #if !defined(FF_HAVE_CHAFA)\n        if(type == FF_LOGO_TYPE_IMAGE_CHAFA)\n        {\n            if(printError)\n                fputs(\"Logo: Chafa support is not compiled in\\n\", stderr);\n            return false;\n        }\n    #endif\n\n    #if !defined(FF_HAVE_IMAGEMAGICK7) && !defined(FF_HAVE_IMAGEMAGICK6)\n        if(printError)\n            fputs(\"Logo: Image Magick support is not compiled in\\n\", stderr);\n        return false;\n    #else\n        return printImageIfExistsSlowPath(type, printError);\n    #endif\n}\n"
  },
  {
    "path": "src/logo/image/image.h",
    "content": "#pragma once\n\n#include \"../logo.h\"\n\n#if defined(FF_HAVE_IMAGEMAGICK7) || defined(FF_HAVE_IMAGEMAGICK6)\n\ntypedef enum __attribute__((__packed__)) FFLogoImageResult\n{\n    FF_LOGO_IMAGE_RESULT_SUCCESS,    //Logo printed\n    FF_LOGO_IMAGE_RESULT_INIT_ERROR, //Failed to load library, try again with next IM version\n    FF_LOGO_IMAGE_RESULT_RUN_ERROR   //Failed to load / convert image, cancel whole sixel code\n} FFLogoImageResult;\n\ntypedef struct FFLogoRequestData\n{\n    FFLogoType type;\n    FFstrbuf cacheDir;\n\n    double characterPixelWidth;\n    double characterPixelHeight;\n\n    uint32_t logoPixelWidth;\n    uint32_t logoPixelHeight;\n\n    uint32_t logoCharacterHeight;\n    uint32_t logoCharacterWidth;\n} FFLogoRequestData;\n\ntypedef struct FFIMData\n{\n    void* library;\n    void*(*resizeFunc)(const void* image, size_t width, size_t height, void* exceptionInfo);\n} FFIMData;\n\nFFLogoImageResult ffLogoPrintImageImpl(FFLogoRequestData* requestData, const FFIMData* imData);\n\n#endif\n\n#ifdef FF_HAVE_IMAGEMAGICK7\nFFLogoImageResult ffLogoPrintImageIM7(FFLogoRequestData* requestData);\n#endif\n\n#ifdef FF_HAVE_IMAGEMAGICK6\n#include <math.h>\nFFLogoImageResult ffLogoPrintImageIM6(FFLogoRequestData* requestData);\n#endif\n"
  },
  {
    "path": "src/logo/logo.c",
    "content": "#include \"logo/logo.h\"\n#include \"common/io.h\"\n#include \"common/printing.h\"\n#include \"common/processing.h\"\n#include \"common/textModifier.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/media/media.h\"\n#include \"detection/os/os.h\"\n#include \"detection/terminalshell/terminalshell.h\"\n\n#include <ctype.h>\n#include <stdlib.h>\n#include <string.h>\n\nstatic bool ffLogoPrintCharsRaw(const char* data, size_t length, bool printError)\n{\n    FFOptionsLogo* options = &instance.config.logo;\n    FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();\n\n    if (!options->width || !options->height)\n    {\n        if (options->position == FF_LOGO_POSITION_LEFT)\n        {\n            ffStrbufAppendF(&buf, \"\\e[2J\\e[3J\\e[%u;%uH\",\n                (unsigned) options->paddingTop + 1,\n                (unsigned) options->paddingLeft + 1\n            );\n        }\n        else if (options->position == FF_LOGO_POSITION_TOP)\n        {\n            ffStrbufAppendNC(&buf, options->paddingTop, '\\n');\n            ffStrbufAppendNC(&buf, options->paddingLeft, ' ');\n        }\n        else if (options->position == FF_LOGO_POSITION_RIGHT)\n        {\n            if (!options->width)\n            {\n                if (printError)\n                    fputs(\"Logo (image-raw): Must set logo width when using position right\\n\", stderr);\n                return false;\n            }\n            ffStrbufAppendF(&buf, \"\\e[2J\\e[3J\\e[%u;9999999H\\e[%uD\", (unsigned) options->paddingTop + 1, (unsigned) options->paddingRight + options->width);\n        }\n        ffStrbufAppendNS(&buf, (uint32_t) length, data);\n        ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf);\n\n        if (options->position == FF_LOGO_POSITION_LEFT || options->position == FF_LOGO_POSITION_RIGHT)\n        {\n            uint16_t X = 0, Y = 0;\n            // Windows Terminal doesn't report `\\e` for some reason\n            const char* error = ffGetTerminalResponse(\"\\e[6n\", 2, \"%*[^0-9]%hu;%huR\", &Y, &X); // %*[^0-9]: ignore optional \\e[\n            if (error)\n            {\n                if (printError)\n                    fprintf(stderr, \"\\nLogo (image-raw): fail to query cursor position: %s\\n\", error);\n                return true;\n            }\n            if (options->position == FF_LOGO_POSITION_LEFT)\n            {\n                if (options->width + options->paddingLeft > X)\n                    X = (uint16_t) (options->width + options->paddingLeft);\n                instance.state.logoWidth = X + instance.config.logo.paddingRight - 1;\n            }\n            instance.state.logoHeight = Y;\n            fputs(\"\\e[H\", stdout);\n        }\n        else if (options->position == FF_LOGO_POSITION_TOP)\n        {\n            instance.state.logoWidth = instance.state.logoHeight = 0;\n            ffPrintCharTimes('\\n', options->paddingRight);\n        }\n    }\n    else\n    {\n        ffStrbufAppendNC(&buf, options->paddingTop, '\\n');\n\n        if (options->position == FF_LOGO_POSITION_RIGHT)\n            ffStrbufAppendF(&buf, \"\\e[9999999C\\e[%uD\", (unsigned) options->paddingRight + options->width);\n        else if (options->paddingLeft)\n            ffStrbufAppendF(&buf, \"\\e[%uC\", (unsigned) options->paddingLeft);\n\n        ffStrbufAppendNS(&buf, (uint32_t) length, data);\n        ffStrbufAppendC(&buf, '\\n');\n\n        if (options->position == FF_LOGO_POSITION_LEFT)\n        {\n            instance.state.logoWidth = options->width + options->paddingLeft + options->paddingRight;\n            instance.state.logoHeight = options->paddingTop + options->height;\n            ffStrbufAppendF(&buf, \"\\e[%uA\", (unsigned) instance.state.logoHeight);\n        }\n        else if (options->position == FF_LOGO_POSITION_TOP)\n        {\n            instance.state.logoWidth = instance.state.logoHeight = 0;\n            ffStrbufAppendNC(&buf, options->paddingRight, '\\n');\n        }\n        else if (options->position == FF_LOGO_POSITION_RIGHT)\n        {\n            instance.state.logoWidth = instance.state.logoHeight = 0;\n            ffStrbufAppendF(&buf, \"\\e[%uA\", (unsigned) options->height);\n        }\n        ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf);\n    }\n\n    return true;\n}\n\n// If result is NULL, calculate logo width\n// Returns logo height\nstatic uint32_t logoAppendChars(const char* data, bool doColorReplacement, FFstrbuf* result)\n{\n    FFOptionsLogo* options = &instance.config.logo;\n    uint32_t currentlineLength = options->type == FF_LOGO_TYPE_IMAGE_CHAFA ? 0 : options->width; // For chafa, unit of options->width is pixels\n    uint32_t logoHeight = 0;\n\n    if (result)\n    {\n        if (options->position != FF_LOGO_POSITION_RIGHT)\n            ffStrbufAppendNC(result, options->paddingLeft, ' ');\n        else\n            ffStrbufAppendF(result, \"\\e[9999999C\\e[%dD\", options->paddingRight + instance.state.logoWidth);\n    }\n\n    while(*data != '\\0')\n    {\n        //We are at the end of a line. Print paddings and update max line length\n        if(*data == '\\n' || (*data == '\\r' && *(data + 1) == '\\n'))\n        {\n            //We have \\r\\n, skip the \\r\n            if(*data == '\\r')\n                ++data;\n\n            if(result) ffStrbufAppendC(result, '\\n');\n            ++data;\n\n            if (result)\n            {\n                if (options->position != FF_LOGO_POSITION_RIGHT)\n                    ffStrbufAppendNC(result, options->paddingLeft, ' ');\n                else\n                    ffStrbufAppendF(result, \"\\e[9999999C\\e[%dD\", options->paddingRight + instance.state.logoWidth);\n            }\n\n            if(currentlineLength > instance.state.logoWidth)\n                instance.state.logoWidth = currentlineLength;\n\n            currentlineLength = 0;\n            ++logoHeight;\n            continue;\n        }\n\n        //Always print tabs as 4 spaces, to have consistent spacing\n        if(*data == '\\t')\n        {\n            if(result) ffStrbufAppendNC(result, 4, ' ');\n            ++data;\n            continue;\n        }\n\n        //We have an escape sequence directly as bytes. We print it, but don't increase the line length\n        if(*data == '\\e' && *(data + 1) == '[')\n        {\n            const char* start = data;\n\n            if(result) ffStrbufAppendS(result, \"\\e[\");\n            data += 2;\n\n            while(ffCharIsDigit(*data) || *data == ';')\n            {\n                if(result) ffStrbufAppendC(result, *data); // number\n                ++data;\n            }\n\n            //We have a valid control sequence, print it and continue with next char\n            if(isascii(*data))\n            {\n                if(result) ffStrbufAppendC(result, *data); // single letter, end of control sequence\n                ++data;\n                continue;\n            }\n\n            //Invalid control sequence, try to get most accurate length\n            currentlineLength += (uint32_t) (data - start - 1); //-1 for \\033 which for sure doesn't take any space\n\n            //Don't continue here, print the char after the letters with the unicode printing\n        }\n\n        //We have a fastfetch color placeholder. Replace it with the esacape sequence, don't increase the line length\n        if(doColorReplacement && *data == '$')\n        {\n            ++data;\n\n            //If we have $$, or $\\0, print it as single $\n            if(*data == '$' || *data == '\\0')\n            {\n                if(result) ffStrbufAppendC(result, '$');\n                ++currentlineLength;\n                ++data;\n                continue;\n            }\n\n            if(!instance.config.display.pipe)\n            {\n                //Map the number to an array index, so that '1' -> 0, '2' -> 1, etc.\n                int index = *data - '1';\n\n                //If the index is valid, print the color. Otherwise continue as normal\n                if(index < 0 || index >= FASTFETCH_LOGO_MAX_COLORS)\n                {\n                    if(result) ffStrbufAppendC(result, '$');\n                    ++currentlineLength;\n                    //Don't continue here, we want to print the current char as unicode\n                }\n                else\n                {\n                    if(result) ffStrbufAppendF(result, \"\\e[%sm\", options->colors[index].chars);\n                    ++data;\n                    continue;\n                }\n            }\n            else\n            {\n                ++data;\n                continue;\n            }\n        }\n\n        //Do the printing, respecting unicode\n\n        ++currentlineLength;\n\n        int codepoint = (unsigned char) *data;\n        uint8_t bytes;\n\n        if(codepoint <= 127)\n            bytes = 1;\n        else if((codepoint & 0xE0) == 0xC0)\n            bytes = 2;\n        else if((codepoint & 0xF0) == 0xE0)\n            bytes = 3;\n        else if((codepoint & 0xF8) == 0xF0)\n            bytes = 4;\n        else\n            bytes = 1; //Invalid utf8, print it as is, byte by byte\n\n        for(uint8_t i = 0; i < bytes; ++i)\n        {\n            if(*data == '\\0')\n                break;\n\n            if(result) ffStrbufAppendC(result, *data);\n            ++data;\n        }\n    }\n    //Happens if the last line is the longest\n    if(currentlineLength > instance.state.logoWidth)\n        instance.state.logoWidth = currentlineLength;\n\n    return options->type != FF_LOGO_TYPE_IMAGE_CHAFA && options->height > logoHeight ? options->height : logoHeight;\n}\n\nvoid ffLogoPrintChars(const char* data, bool doColorReplacement)\n{\n    FFOptionsLogo* options = &instance.config.logo;\n\n    if (options->position == FF_LOGO_POSITION_RIGHT)\n        logoAppendChars(data, doColorReplacement, NULL);\n\n    FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateA(2048);\n\n    if (!instance.config.display.pipe && instance.config.display.brightColor)\n        ffStrbufAppendS(&result, FASTFETCH_TEXT_MODIFIER_BOLT);\n\n    ffStrbufAppendNC(&result, options->paddingTop, '\\n');\n\n    //Use logoColor[0] as the default color\n    if(doColorReplacement && !instance.config.display.pipe)\n        ffStrbufAppendF(&result, \"\\e[%sm\", options->colors[0].chars);\n\n    instance.state.logoHeight = options->paddingTop + logoAppendChars(data, doColorReplacement, &result);\n\n    if(!instance.config.display.pipe)\n        ffStrbufAppendS(&result, FASTFETCH_TEXT_MODIFIER_RESET);\n\n    if(options->position == FF_LOGO_POSITION_LEFT)\n    {\n        instance.state.logoWidth += options->paddingLeft + options->paddingRight;\n\n        //Go to the leftmost position and go up the height\n        ffStrbufAppendF(&result, \"\\e[1G\\e[%uA\", instance.state.logoHeight);\n    }\n    else if(options->position == FF_LOGO_POSITION_RIGHT)\n    {\n        instance.state.logoWidth = 0;\n\n        //Go to the leftmost position and go up the height\n        ffStrbufAppendF(&result, \"\\e[1G\\e[%uA\", instance.state.logoHeight);\n    }\n    else if (options->position == FF_LOGO_POSITION_TOP)\n    {\n        instance.state.logoWidth = instance.state.logoHeight = 0;\n        ffStrbufAppendNC(&result, options->paddingRight, '\\n');\n    }\n\n    ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &result);\n}\n\nstatic void logoApplyColors(const FFlogo* logo, bool replacement)\n{\n    if(instance.config.display.colorTitle.length == 0)\n        ffStrbufAppendS(&instance.config.display.colorTitle, logo->colorTitle ?: logo->colors[0]);\n\n    if(instance.config.display.colorKeys.length == 0)\n        ffStrbufAppendS(&instance.config.display.colorKeys, logo->colorKeys ?: logo->colors[1]);\n\n    if (replacement)\n    {\n        FFOptionsLogo* options = &instance.config.logo;\n\n        const char* const* colors = logo->colors;\n        for(int i = 0; *colors != NULL && i < FASTFETCH_LOGO_MAX_COLORS; i++, colors++)\n        {\n            if(options->colors[i].length == 0)\n                ffStrbufAppendS(&options->colors[i], *colors);\n        }\n    }\n}\n\nstatic bool logoHasName(const FFlogo* logo, const FFstrbuf* name, bool small)\n{\n    for(\n        const char* const* logoName = logo->names;\n        *logoName != NULL && logoName <= &logo->names[FASTFETCH_LOGO_MAX_NAMES];\n        ++logoName\n    ) {\n        if(small)\n        {\n            uint32_t logoNameLength = (uint32_t) (strlen(*logoName) - strlen(\"_small\"));\n            if(name->length == logoNameLength && strncasecmp(*logoName, name->chars, logoNameLength) == 0) return true;\n        }\n        if(ffStrbufIgnCaseEqualS(name, *logoName))\n            return true;\n    }\n\n    return false;\n}\n\nstatic const FFlogo* logoGetBuiltin(const FFstrbuf* name, FFLogoSize size)\n{\n    if (name->length == 0 || !isalpha(name->chars[0]))\n        return NULL;\n\n    for(const FFlogo* logo = ffLogoBuiltins[toupper(name->chars[0]) - 'A']; *logo->names; ++logo)\n    {\n        switch (size)\n        {\n            // Never use alternate logos\n            case FF_LOGO_SIZE_NORMAL:\n                if(logo->type != FF_LOGO_LINE_TYPE_NORMAL) continue;\n                break;\n            case FF_LOGO_SIZE_SMALL:\n                if(logo->type != FF_LOGO_LINE_TYPE_SMALL_BIT) continue;\n                break;\n            default:\n                break;\n        }\n\n        if(logoHasName(logo, name, size == FF_LOGO_SIZE_SMALL))\n            return logo;\n    }\n\n    return NULL;\n}\n\nstatic const FFlogo* logoGetBuiltinDetected(FFLogoSize size)\n{\n    const FFOSResult* os = ffDetectOS();\n\n    const FFlogo* logo = logoGetBuiltin(&os->id, size);\n    if(logo != NULL)\n        return logo;\n\n    logo = logoGetBuiltin(&os->name, size);\n    if(logo != NULL)\n        return logo;\n\n    if (ffStrbufContainC(&os->idLike, ' '))\n    {\n        FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();\n        for (\n            uint32_t start = 0, end = ffStrbufFirstIndexC(&os->idLike, ' ');\n            true;\n            start = end + 1, end = ffStrbufNextIndexC(&os->idLike, start, ' ')\n        )\n        {\n            ffStrbufSetNS(&buf, end - start, os->idLike.chars + start);\n            logo = logoGetBuiltin(&buf, size);\n            if(logo != NULL)\n                return logo;\n\n            if (end >= os->idLike.length)\n                break;\n        }\n    }\n    else\n    {\n        logo = logoGetBuiltin(&os->idLike, size);\n        if(logo != NULL)\n            return logo;\n    }\n\n    logo = logoGetBuiltin(&instance.state.platform.sysinfo.name, size);\n    if(logo != NULL)\n        return logo;\n\n    return &ffLogoUnknown;\n}\n\nstatic void logoPrintStruct(const FFlogo* logo)\n{\n    logoApplyColors(logo, true);\n\n    ffLogoPrintChars(logo->lines, true);\n}\n\nstatic void logoPrintNone(void)\n{\n    if (!instance.config.display.pipe)\n        logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), false);\n    instance.state.logoHeight = 0;\n    instance.state.logoWidth = 0;\n}\n\nstatic bool logoPrintBuiltinIfExists(const FFstrbuf* name, FFLogoSize size)\n{\n    if(name->chars[0] == '~' || name->chars[0] == '.' || name->chars[0] == '/'\n        #if _WIN32\n        || (ffCharIsEnglishAlphabet(name->chars[0]) && name->chars[1] == ':') // Windows drive letter\n        #endif\n    )\n        return false; // Paths\n\n    if(ffStrbufIgnCaseEqualS(name, \"none\"))\n    {\n        logoPrintNone();\n        return true;\n    }\n\n    const FFlogo* logo = ffLogoGetBuiltinForName(name, size);\n    if(logo == NULL)\n        return false;\n\n    logoPrintStruct(logo);\n\n    return true;\n}\n\nstatic inline void logoPrintDetected(FFLogoSize size)\n{\n    logoPrintStruct(logoGetBuiltinDetected(size));\n}\n\nstatic bool logoPrintData(bool doColorReplacement, FFstrbuf* source)\n{\n    if(source->length == 0)\n        return false;\n\n    logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), doColorReplacement);\n    ffLogoPrintChars(source->chars, doColorReplacement);\n    return true;\n}\n\nstatic bool updateLogoPath(void)\n{\n    FFOptionsLogo* options = &instance.config.logo;\n\n    if(ffPathExists(options->source.chars, FF_PATHTYPE_FILE))\n        return true;\n\n    if (ffStrbufEqualS(&options->source, \"-\")) // stdin\n        return true;\n\n    if (ffStrbufIgnCaseEqualS(&options->source, \"media-cover\"))\n    {\n        const FFMediaResult* media = ffDetectMedia(true);\n        if (media->cover.length == 0)\n            return false;\n        ffStrbufSet(&options->source, &media->cover);\n        return true;\n    }\n\n    FF_STRBUF_AUTO_DESTROY fullPath = ffStrbufCreateA(128);\n    if (ffPathExpandEnv(options->source.chars, &fullPath) && ffPathExists(fullPath.chars, FF_PATHTYPE_FILE))\n    {\n        ffStrbufDestroy(&options->source);\n        ffStrbufInitMove(&options->source, &fullPath);\n        return true;\n    }\n\n    FF_LIST_FOR_EACH(FFstrbuf, dataDir, instance.state.platform.dataDirs)\n    {\n        //We need to copy it, because multiple threads might be using dataDirs at the same time\n        ffStrbufSet(&fullPath, dataDir);\n        ffStrbufAppendS(&fullPath, \"fastfetch/logos/\");\n        ffStrbufAppend(&fullPath, &options->source);\n\n        if(ffPathExists(fullPath.chars, FF_PATHTYPE_FILE))\n        {\n            ffStrbufDestroy(&options->source);\n            ffStrbufInitMove(&options->source, &fullPath);\n            return true;\n        }\n    }\n\n    return false;\n}\n\nstatic bool logoPrintFileIfExists(bool doColorReplacement, bool raw)\n{\n    FFOptionsLogo* options = &instance.config.logo;\n\n    FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate();\n\n    if(ffStrbufEqualS(&options->source, \"-\")\n        ? !ffAppendFDBuffer(FFUnixFD2NativeFD(STDIN_FILENO), &content)\n        : !ffAppendFileBuffer(options->source.chars, &content)\n    )\n    {\n        if (instance.config.display.showErrors)\n            fprintf(stderr, \"Logo: Failed to load file content from logo source: %s\\n\", options->source.chars);\n        return false;\n    }\n\n    logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), doColorReplacement);\n    if(raw)\n        return ffLogoPrintCharsRaw(content.chars, content.length, instance.config.display.showErrors);\n\n    ffLogoPrintChars(content.chars, doColorReplacement);\n    return true;\n}\n\nstatic bool logoPrintImageIfExists(FFLogoType logo, bool printError)\n{\n    if(!ffLogoPrintImageIfExists(logo, printError))\n        return false;\n\n    logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), false);\n    return true;\n}\n\nstatic bool logoTryKnownType(void)\n{\n    FFOptionsLogo* options = &instance.config.logo;\n\n    if(options->type == FF_LOGO_TYPE_NONE)\n    {\n        logoPrintNone();\n        return true;\n    }\n\n    if(options->type == FF_LOGO_TYPE_BUILTIN)\n        return logoPrintBuiltinIfExists(&options->source, FF_LOGO_SIZE_UNKNOWN);\n\n    if(options->type == FF_LOGO_TYPE_SMALL)\n        return logoPrintBuiltinIfExists(&options->source, FF_LOGO_SIZE_SMALL);\n\n    if(options->type == FF_LOGO_TYPE_DATA)\n        return logoPrintData(true, &options->source);\n\n    if(options->type == FF_LOGO_TYPE_DATA_RAW)\n        return logoPrintData(false, &options->source);\n\n    if(options->type == FF_LOGO_TYPE_COMMAND_RAW)\n    {\n        FF_STRBUF_AUTO_DESTROY source = ffStrbufCreate();\n\n        const char* error = ffProcessAppendStdOut(&source, (char* const[]){\n            #ifdef _WIN32\n            \"cmd.exe\", \"/c\",\n            #else\n            \"/bin/sh\", \"-c\",\n            #endif\n            options->source.chars,\n            NULL\n        });\n\n        if (error)\n        {\n            if (instance.config.display.showErrors)\n                fprintf(stderr, \"Logo: failed to execute command `%s`: %s\\n\", options->source.chars, error);\n            return false;\n        }\n\n        return logoPrintData(false, &source);\n    }\n\n    //We sure have a file, resolve relative paths\n    if (!updateLogoPath())\n    {\n        if (instance.config.display.showErrors)\n            fprintf(stderr, \"Logo: Failed to resolve logo source: %s\\n\", options->source.chars);\n        return false;\n    }\n\n    if(options->type == FF_LOGO_TYPE_FILE)\n        return logoPrintFileIfExists(true, false);\n\n    if(options->type == FF_LOGO_TYPE_FILE_RAW)\n        return logoPrintFileIfExists(false, false);\n\n    if(options->type == FF_LOGO_TYPE_IMAGE_RAW)\n        return logoPrintFileIfExists(false, true);\n\n    return logoPrintImageIfExists(options->type, instance.config.display.showErrors);\n}\n\nvoid ffLogoPrint(void)\n{\n    const FFOptionsLogo* options = &instance.config.logo;\n\n    if (options->type == FF_LOGO_TYPE_NONE)\n    {\n        logoPrintNone();\n        return;\n    }\n\n    //If the source is not set, we can directly print the detected logo.\n    if(options->source.length == 0)\n    {\n        logoPrintDetected(options->type == FF_LOGO_TYPE_SMALL ? FF_LOGO_SIZE_SMALL : FF_LOGO_SIZE_NORMAL);\n        return;\n    }\n\n    //If the source and source type is set to something else than auto, always print with the set type.\n    if(options->source.length > 0 && options->type != FF_LOGO_TYPE_AUTO)\n    {\n        if(!logoTryKnownType())\n        {\n            if (instance.config.display.showErrors)\n            {\n                // Image logo should have been handled\n                if(options->type == FF_LOGO_TYPE_BUILTIN || options->type == FF_LOGO_TYPE_SMALL)\n                    fprintf(stderr, \"Logo: Failed to load %s logo: %s\\n\", options->type == FF_LOGO_TYPE_BUILTIN ? \"builtin\" : \"builtin small\", options->source.chars);\n            }\n\n            logoPrintDetected(FF_LOGO_SIZE_UNKNOWN);\n        }\n        return;\n    }\n\n    //If source matches the name of a builtin logo, print it and return.\n    if(logoPrintBuiltinIfExists(&options->source, FF_LOGO_SIZE_UNKNOWN))\n        return;\n\n    //Make sure the logo path is set correctly.\n    if (updateLogoPath())\n    {\n        if (ffStrbufEndsWithIgnCaseS(&options->source, \".raw\"))\n        {\n            if(logoPrintFileIfExists(false, true))\n                return;\n        }\n\n        if (!ffStrbufEndsWithIgnCaseS(&options->source, \".txt\"))\n        {\n            const FFTerminalResult* terminal = ffDetectTerminal();\n\n            //Terminal emulators that support kitty graphics protocol.\n            bool supportsKitty =\n                ffStrbufIgnCaseEqualS(&terminal->processName, \"kitty\") ||\n                ffStrbufIgnCaseEqualS(&terminal->processName, \"konsole\") ||\n                ffStrbufIgnCaseEqualS(&terminal->processName, \"wezterm\") ||\n                ffStrbufIgnCaseEqualS(&terminal->processName, \"wayst\") ||\n                ffStrbufIgnCaseEqualS(&terminal->processName, \"ghostty\") ||\n                #ifdef __APPLE__\n                ffStrbufIgnCaseEqualS(&terminal->processName, \"WarpTerminal\") ||\n                #else\n                ffStrbufIgnCaseEqualS(&terminal->processName, \"warp\") ||\n                #endif\n                false;\n\n            //Try to load the logo as an image. If it succeeds, print it and return.\n            if(logoPrintImageIfExists(supportsKitty ? FF_LOGO_TYPE_IMAGE_KITTY : FF_LOGO_TYPE_IMAGE_CHAFA, false))\n                return;\n        }\n\n        //Try to load the logo as a file. If it succeeds, print it and return.\n        if(logoPrintFileIfExists(true, false))\n            return;\n    }\n    else\n    {\n        if (instance.config.display.showErrors)\n            fprintf(stderr, \"Logo: Failed to resolve logo source: %s\\n\", options->source.chars);\n    }\n\n    logoPrintDetected(FF_LOGO_SIZE_UNKNOWN);\n}\n\nvoid ffLogoPrintLine(void)\n{\n    if(instance.state.logoWidth > 0)\n        printf(\"\\033[%uC\", instance.state.logoWidth);\n\n    if (instance.state.dynamicInterval > 0)\n        fputs(\"\\033[K\", stdout); // Clear to the end of the line\n\n    ++instance.state.keysHeight;\n}\n\nvoid ffLogoPrintRemaining(void)\n{\n    if (instance.state.keysHeight <= instance.state.logoHeight)\n        ffPrintCharTimes('\\n', instance.state.logoHeight - instance.state.keysHeight + 1);\n    instance.state.keysHeight = instance.state.logoHeight + 1;\n}\n\nvoid ffLogoBuiltinPrint(void)\n{\n    FFOptionsLogo* options = &instance.config.logo;\n    options->position = FF_LOGO_POSITION_TOP;\n    options->paddingRight = 2; // empty line after logo printing\n    FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();\n\n    for(uint8_t ch = 0; ch < 26; ++ch)\n    {\n        for(const FFlogo* logo = ffLogoBuiltins[ch]; *logo->names; ++logo)\n        {\n            if (instance.config.display.pipe)\n                ffStrbufSetF(&buf, \"%s:\\n\", logo->names[0]);\n            else\n                ffStrbufSetF(&buf, \"\\e[%sm%s:\\e[0m\\n\", logo->colors[0], logo->names[0]);\n            ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf);\n            logoPrintStruct(logo);\n\n            for(uint8_t i = 0; i < FASTFETCH_LOGO_MAX_COLORS; i++)\n                ffStrbufClear(&options->colors[i]);\n        }\n    }\n}\n\nvoid ffLogoBuiltinList(void)\n{\n    uint32_t counter = 0;\n    for(uint8_t ch = 0; ch < 26; ++ch)\n    {\n        for(const FFlogo* logo = ffLogoBuiltins[ch]; *logo->names; ++logo)\n        {\n            ++counter;\n            printf(\"%u)%s \", counter, counter < 10 ? \" \" : \"\");\n\n            for(\n                const char* const* names = logo->names;\n                *names != NULL && names <= &logo->names[FASTFETCH_LOGO_MAX_NAMES];\n                ++names\n            )\n                printf(\"\\\"%s\\\" \", *names);\n\n            putchar('\\n');\n        }\n    }\n}\n\nvoid ffLogoBuiltinListAutocompletion(void)\n{\n    for(uint8_t ch = 0; ch < 26; ++ch)\n    {\n        for(const FFlogo* logo = ffLogoBuiltins[ch]; *logo->names; ++logo)\n            printf(\"%s\\n\", logo->names[0]);\n    }\n}\n\nconst FFlogo* ffLogoGetBuiltinForName(const FFstrbuf* name, FFLogoSize size)\n{\n    return ffStrbufEqualS(name, \"?\") ? &ffLogoUnknown : logoGetBuiltin(name, size);\n}\n\nconst FFlogo* ffLogoGetBuiltinDetected(FFLogoSize size)\n{\n    return logoGetBuiltinDetected(size);\n}\n"
  },
  {
    "path": "src/logo/logo.h",
    "content": "#pragma once\n\n#include \"fastfetch.h\"\n\ntypedef enum __attribute__((__packed__)) FFLogoLineType\n{\n    FF_LOGO_LINE_TYPE_NORMAL = 0,\n    FF_LOGO_LINE_TYPE_SMALL_BIT = 1 << 0, // The names of small logo must end with `_small` or `-small`\n    FF_LOGO_LINE_TYPE_ALTER_BIT = 1 << 1,\n    FF_LOGO_LINE_TYPE_FORCE_UNSIGNED = UINT8_MAX,\n} FFLogoLineType;\n\ntypedef enum __attribute__((__packed__)) FFLogoSize\n{\n    FF_LOGO_SIZE_UNKNOWN,\n    FF_LOGO_SIZE_NORMAL,\n    FF_LOGO_SIZE_SMALL,\n} FFLogoSize;\n\ntypedef struct FFlogo\n{\n    const char* lines;\n    const char* names[FASTFETCH_LOGO_MAX_NAMES];\n    const char* colors[FASTFETCH_LOGO_MAX_COLORS];\n    const char* colorKeys;\n    const char* colorTitle;\n    FFLogoLineType type;\n} FFlogo;\n\n//logo.c\nvoid ffLogoPrint(void);\nvoid ffLogoPrintChars(const char* data, bool doColorReplacement);\nvoid ffLogoPrintLine(void);\nvoid ffLogoPrintRemaining(void);\nvoid ffLogoBuiltinPrint(void);\nvoid ffLogoBuiltinList(void);\nvoid ffLogoBuiltinListAutocompletion(void);\nconst FFlogo* ffLogoGetBuiltinForName(const FFstrbuf* name, FFLogoSize size);\nconst FFlogo* ffLogoGetBuiltinDetected(FFLogoSize size);\n\n//builtin.c\nextern const FFlogo* ffLogoBuiltins[];\nextern const FFlogo ffLogoUnknown;\n\n//image/image.c\nbool ffLogoPrintImageIfExists(FFLogoType type, bool printError);\n"
  },
  {
    "path": "src/modules/battery/battery.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/percent.h\"\n#include \"common/duration.h\"\n#include \"common/temps.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/battery/battery.h\"\n#include \"modules/battery/battery.h\"\n\nstatic void printBattery(FFBatteryOptions* options, FFBatteryResult* result, uint8_t index)\n{\n    FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();\n    if (options->moduleArgs.key.length == 0)\n    {\n        if (result->modelName.length > 0)\n            ffStrbufSetF(&key, \"%s (%s)\", FF_BATTERY_MODULE_NAME, result->modelName.chars);\n        else\n            ffStrbufSetS(&key, FF_BATTERY_MODULE_NAME);\n    }\n    else\n    {\n        ffStrbufClear(&key);\n        FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) {\n            FF_ARG(index, \"index\"),\n            FF_ARG(result->modelName, \"name\"),\n            FF_ARG(options->moduleArgs.keyIcon, \"icon\"),\n        }));\n    }\n\n    FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);\n\n        FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate();\n        bool showStatus =\n            !(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT) &&\n            result->status.length > 0 &&\n            ffStrbufIgnCaseCompS(&result->status, \"Unknown\") != 0;\n\n        if(result->capacity >= 0)\n        {\n            if(percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            {\n                ffPercentAppendBar(&str, result->capacity, options->percent, &options->moduleArgs);\n            }\n\n            if(percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n            {\n                if(str.length > 0)\n                    ffStrbufAppendC(&str, ' ');\n\n                ffPercentAppendNum(&str, result->capacity, options->percent, str.length > 0, &options->moduleArgs);\n            }\n\n            if(result->timeRemaining > 0)\n            {\n                if(str.length > 0)\n                    ffStrbufAppendS(&str, \" (\");\n\n                ffDurationAppendNum((uint32_t) result->timeRemaining, &str);\n                ffStrbufAppendS(&str, \" remaining)\");\n            }\n        }\n\n        if(showStatus)\n        {\n            if(str.length > 0)\n                ffStrbufAppendF(&str, \" [%s]\", result->status.chars);\n            else\n                ffStrbufAppend(&str, &result->status);\n        }\n\n        if(result->temperature != FF_BATTERY_TEMP_UNSET)\n        {\n            if(str.length > 0)\n                ffStrbufAppendS(&str, \" - \");\n\n            ffTempsAppendNum(result->temperature, &str, options->tempConfig, &options->moduleArgs);\n        }\n\n        ffStrbufPutTo(&str, stdout);\n    }\n    else\n    {\n        uint32_t timeRemaining = result->timeRemaining < 0 ? 0 : (uint32_t) result->timeRemaining;\n        uint32_t seconds = timeRemaining % 60;\n        timeRemaining /= 60;\n        uint32_t minutes = timeRemaining % 60;\n        timeRemaining /= 60;\n        uint32_t hours = timeRemaining % 24;\n        timeRemaining /= 24;\n        uint32_t days = timeRemaining;\n\n        FF_STRBUF_AUTO_DESTROY capacityNum = ffStrbufCreate();\n        if(percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n            ffPercentAppendNum(&capacityNum, result->capacity, options->percent, false, &options->moduleArgs);\n        FF_STRBUF_AUTO_DESTROY capacityBar = ffStrbufCreate();\n        if(percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            ffPercentAppendBar(&capacityBar, result->capacity, options->percent, &options->moduleArgs);\n        FF_STRBUF_AUTO_DESTROY tempStr = ffStrbufCreate();\n        ffTempsAppendNum(result->temperature, &tempStr, options->tempConfig, &options->moduleArgs);\n        FF_STRBUF_AUTO_DESTROY timeStr = ffStrbufCreate();\n        if (result->timeRemaining > 0)\n            ffDurationAppendNum((uint32_t) result->timeRemaining, &timeStr);\n\n        FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) {\n            FF_ARG(result->manufacturer, \"manufacturer\"),\n            FF_ARG(result->modelName, \"model-name\"),\n            FF_ARG(result->technology, \"technology\"),\n            FF_ARG(capacityNum, \"capacity\"),\n            FF_ARG(result->status, \"status\"),\n            FF_ARG(tempStr, \"temperature\"),\n            FF_ARG(result->cycleCount, \"cycle-count\"),\n            FF_ARG(result->serial, \"serial\"),\n            FF_ARG(result->manufactureDate, \"manufacture-date\"),\n            FF_ARG(capacityBar, \"capacity-bar\"),\n            FF_ARG(days, \"time-days\"),\n            FF_ARG(hours, \"time-hours\"),\n            FF_ARG(minutes, \"time-minutes\"),\n            FF_ARG(seconds, \"time-seconds\"),\n            FF_ARG(timeStr, \"time-formatted\"),\n        }));\n    }\n}\n\nbool ffPrintBattery(FFBatteryOptions* options)\n{\n    FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFBatteryResult));\n\n    const char* error = ffDetectBattery(options, &results);\n\n    if (error)\n    {\n        ffPrintError(FF_BATTERY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n    if(results.length == 0)\n    {\n        ffPrintError(FF_BATTERY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", \"No batteries found\");\n        return false;\n    }\n\n    for(uint32_t i = 0; i < results.length; i++)\n    {\n        FFBatteryResult* result = FF_LIST_GET(FFBatteryResult, results, i);\n        printBattery(options, result, results.length == 1 ? 0 : (uint8_t) (i + 1));\n    }\n\n    FF_LIST_FOR_EACH(FFBatteryResult, result, results)\n    {\n        ffStrbufDestroy(&result->manufacturer);\n        ffStrbufDestroy(&result->modelName);\n        ffStrbufDestroy(&result->technology);\n        ffStrbufDestroy(&result->status);\n        ffStrbufDestroy(&result->serial);\n        ffStrbufDestroy(&result->manufactureDate);\n    }\n    return true;\n}\n\nvoid ffParseBatteryJsonObject(FFBatteryOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        #ifdef _WIN32\n        if (unsafe_yyjson_equals_str(key, \"useSetupApi\"))\n        {\n            options->useSetupApi = yyjson_get_bool(val);\n            continue;\n        }\n        #endif\n\n        if (ffTempsParseJsonObject(key, val, &options->temp, &options->tempConfig))\n            continue;\n\n        if (ffPercentParseJsonObject(key, val, &options->percent))\n            continue;\n\n        ffPrintError(FF_BATTERY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateBatteryJsonConfig(FFBatteryOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    #ifdef _WIN32\n        yyjson_mut_obj_add_bool(doc, module, \"useSetupApi\", options->useSetupApi);\n    #endif\n\n    ffTempsGenerateJsonConfig(doc, module, options->temp, options->tempConfig);\n    ffPercentGenerateJsonConfig(doc, module, options->percent);\n}\n\nbool ffGenerateBatteryJsonResult(FFBatteryOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFBatteryResult));\n\n    const char* error = ffDetectBattery(options, &results);\n    if (error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n\n    FF_LIST_FOR_EACH(FFBatteryResult, battery, results)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n        yyjson_mut_obj_add_real(doc, obj, \"capacity\", battery->capacity);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"manufacturer\", &battery->manufacturer);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"manufactureDate\", &battery->manufactureDate);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"modelName\", &battery->modelName);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"status\", &battery->status);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"technology\", &battery->technology);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"serial\", &battery->serial);\n        if (battery->temperature != FF_BATTERY_TEMP_UNSET)\n            yyjson_mut_obj_add_real(doc, obj, \"temperature\", battery->temperature);\n        else\n            yyjson_mut_obj_add_null(doc, obj, \"temperature\");\n        yyjson_mut_obj_add_uint(doc, obj, \"cycleCount\", battery->cycleCount);\n        if (battery->timeRemaining > 0)\n            yyjson_mut_obj_add_int(doc, obj, \"timeRemaining\", battery->timeRemaining);\n        else\n            yyjson_mut_obj_add_null(doc, obj, \"timeRemaining\");\n    }\n\n    FF_LIST_FOR_EACH(FFBatteryResult, battery, results)\n    {\n        ffStrbufDestroy(&battery->manufacturer);\n        ffStrbufDestroy(&battery->manufactureDate);\n        ffStrbufDestroy(&battery->modelName);\n        ffStrbufDestroy(&battery->technology);\n        ffStrbufDestroy(&battery->status);\n        ffStrbufDestroy(&battery->serial);\n    }\n\n    return true;\n}\n\nvoid ffInitBatteryOptions(FFBatteryOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n    options->temp = false;\n    options->tempConfig = (FFColorRangeConfig) { 60, 80 };\n    options->percent = (FFPercentageModuleConfig) { 50, 20, 0 };\n\n    #ifdef _WIN32\n        options->useSetupApi = false;\n    #endif\n}\n\nvoid ffDestroyBatteryOptions(FFBatteryOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffBatteryModuleInfo = {\n    .name = FF_BATTERY_MODULE_NAME,\n    .description = \"Print battery capacity, status, etc\",\n    .initOptions = (void*) ffInitBatteryOptions,\n    .destroyOptions = (void*) ffDestroyBatteryOptions,\n    .parseJsonObject = (void*) ffParseBatteryJsonObject,\n    .printModule = (void*) ffPrintBattery,\n    .generateJsonResult = (void*) ffGenerateBatteryJsonResult,\n    .generateJsonConfig = (void*) ffGenerateBatteryJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Battery manufacturer\", \"manufacturer\"},\n        {\"Battery model name\", \"model-name\"},\n        {\"Battery technology\", \"technology\"},\n        {\"Battery capacity (percentage num)\", \"capacity\"},\n        {\"Battery status\", \"status\"},\n        {\"Battery temperature (formatted)\", \"temperature\"},\n        {\"Battery cycle count\", \"cycle-count\"},\n        {\"Battery serial number\", \"serial\"},\n        {\"Battery manufactor date\", \"manufacture-date\"},\n        {\"Battery capacity (percentage bar)\", \"capacity-bar\"},\n        {\"Battery time remaining days\", \"time-days\"},\n        {\"Battery time remaining hours\", \"time-hours\"},\n        {\"Battery time remaining minutes\", \"time-minutes\"},\n        {\"Battery time remaining seconds\", \"time-seconds\"},\n        {\"Battery time remaining (formatted)\", \"time-formatted\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/battery/battery.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_BATTERY_MODULE_NAME \"Battery\"\n\nbool ffPrintBattery(FFBatteryOptions* options);\nvoid ffInitBatteryOptions(FFBatteryOptions* options);\nvoid ffDestroyBatteryOptions(FFBatteryOptions* options);\n\nextern FFModuleBaseInfo ffBatteryModuleInfo;\n"
  },
  {
    "path": "src/modules/battery/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n#include \"common/percent.h\"\n\ntypedef struct FFBatteryOptions\n{\n    FFModuleArgs moduleArgs;\n\n    bool temp;\n    FFColorRangeConfig tempConfig;\n    FFPercentageModuleConfig percent;\n\n    #ifdef _WIN32\n        bool useSetupApi;\n    #endif\n} FFBatteryOptions;\n\nstatic_assert(sizeof(FFBatteryOptions) <= FF_OPTION_MAX_SIZE, \"FFBatteryOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/bios/bios.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/bios/bios.h\"\n#include \"modules/bios/bios.h\"\n\nbool ffPrintBios(FFBiosOptions* options)\n{\n    bool success = false;\n    FFBiosResult bios;\n    ffStrbufInit(&bios.date);\n    ffStrbufInit(&bios.release);\n    ffStrbufInit(&bios.vendor);\n    ffStrbufInit(&bios.version);\n    ffStrbufInit(&bios.type);\n\n    const char* error = ffDetectBios(&bios);\n\n    FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();\n\n    if(error)\n    {\n        ffPrintError(FF_BIOS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        goto exit;\n    }\n\n    if(bios.version.length == 0)\n    {\n        ffPrintError(FF_BIOS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"bios_version is not set.\");\n        goto exit;\n    }\n\n    if(options->moduleArgs.key.length == 0)\n    {\n        if(bios.type.length == 0)\n            ffStrbufSetStatic(&bios.type, \"Unknown\");\n        else if (ffStrbufIgnCaseEqualS(&bios.type, \"BIOS\"))\n            ffStrbufSetStatic(&bios.type, \"Legacy\");\n\n        ffStrbufSetF(&key, FF_BIOS_MODULE_NAME \" (%s)\", bios.type.chars);\n    }\n    else\n    {\n        ffStrbufClear(&key);\n        FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) {\n            FF_ARG(bios.type, \"type\"),\n            FF_ARG(options->moduleArgs.keyIcon, \"icon\"),\n        }));\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);\n        ffStrbufWriteTo(&bios.version, stdout);\n        if (bios.release.length)\n            printf(\" (%s)\\n\", bios.release.chars);\n        else\n            putchar('\\n');\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) {\n            FF_ARG(bios.date, \"date\"),\n            FF_ARG(bios.release, \"release\"),\n            FF_ARG(bios.vendor, \"vendor\"),\n            FF_ARG(bios.version, \"version\"),\n            FF_ARG(bios.type, \"type\"),\n        }));\n    }\n    success = true;\n\nexit:\n    ffStrbufDestroy(&bios.date);\n    ffStrbufDestroy(&bios.release);\n    ffStrbufDestroy(&bios.vendor);\n    ffStrbufDestroy(&bios.version);\n    ffStrbufDestroy(&bios.type);\n\n    return success;\n}\n\nvoid ffParseBiosJsonObject(FFBiosOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_BIOS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateBiosJsonConfig(FFBiosOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateBiosJsonResult(FF_MAYBE_UNUSED FFBiosOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    bool success = false;\n    FFBiosResult bios;\n    ffStrbufInit(&bios.date);\n    ffStrbufInit(&bios.release);\n    ffStrbufInit(&bios.vendor);\n    ffStrbufInit(&bios.version);\n    ffStrbufInit(&bios.type);\n\n    const char* error = ffDetectBios(&bios);\n\n    if (error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        goto exit;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_strbuf(doc, obj, \"date\", &bios.date);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"release\", &bios.release);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"vendor\", &bios.vendor);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"version\", &bios.version);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"type\", &bios.type);\n    success = true;\n\nexit:\n    ffStrbufDestroy(&bios.date);\n    ffStrbufDestroy(&bios.release);\n    ffStrbufDestroy(&bios.vendor);\n    ffStrbufDestroy(&bios.version);\n    ffStrbufDestroy(&bios.type);\n    return success;\n}\n\nvoid ffInitBiosOptions(FFBiosOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n}\n\nvoid ffDestroyBiosOptions(FFBiosOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffBiosModuleInfo = {\n    .name = FF_BIOS_MODULE_NAME,\n    .description = \"Print information of 1st-stage bootloader (name, version, release date, etc)\",\n    .initOptions = (void*) ffInitBiosOptions,\n    .destroyOptions = (void*) ffDestroyBiosOptions,\n    .parseJsonObject = (void*) ffParseBiosJsonObject,\n    .printModule = (void*) ffPrintBios,\n    .generateJsonResult = (void*) ffGenerateBiosJsonResult,\n    .generateJsonConfig = (void*) ffGenerateBiosJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Bios date\", \"date\"},\n        {\"Bios release\", \"release\"},\n        {\"Bios vendor\", \"vendor\"},\n        {\"Bios version\", \"version\"},\n        {\"Firmware type\", \"type\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/bios/bios.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_BIOS_MODULE_NAME \"BIOS\"\n\nbool ffPrintBios(FFBiosOptions* options);\nvoid ffInitBiosOptions(FFBiosOptions* options);\nvoid ffDestroyBiosOptions(FFBiosOptions* options);\n\nextern FFModuleBaseInfo ffBiosModuleInfo;\n"
  },
  {
    "path": "src/modules/bios/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFBiosOptions\n{\n    FFModuleArgs moduleArgs;\n} FFBiosOptions;\n\nstatic_assert(sizeof(FFBiosOptions) <= FF_OPTION_MAX_SIZE, \"FFBiosOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/bluetooth/bluetooth.c",
    "content": "#include \"common/percent.h\"\n#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/bluetooth/bluetooth.h\"\n#include \"modules/bluetooth/bluetooth.h\"\n\nstatic void printDevice(FFBluetoothOptions* options, const FFBluetoothResult* device, uint8_t index)\n{\n    FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_BLUETOOTH_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n        FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n        bool showBatteryLevel = device->battery > 0 && device->battery <= 100;\n\n        if (showBatteryLevel && (percentType & FF_PERCENTAGE_TYPE_BAR_BIT))\n        {\n            ffPercentAppendBar(&buffer, device->battery, options->percent, &options->moduleArgs);\n            ffStrbufAppendC(&buffer, ' ');\n        }\n\n        if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT))\n            ffStrbufAppend(&buffer, &device->name);\n\n        if (showBatteryLevel && (percentType & FF_PERCENTAGE_TYPE_NUM_BIT))\n        {\n            if (buffer.length)\n                ffStrbufAppendC(&buffer, ' ');\n            ffPercentAppendNum(&buffer, device->battery, options->percent, buffer.length > 0, &options->moduleArgs);\n        }\n\n        if (!device->connected)\n            ffStrbufAppendS(&buffer, \" [disconnected]\");\n\n        ffStrbufPutTo(&buffer, stdout);\n    }\n    else\n    {\n        FF_STRBUF_AUTO_DESTROY percentageNum = ffStrbufCreate();\n        if(percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n            ffPercentAppendNum(&percentageNum, device->battery, options->percent, false, &options->moduleArgs);\n        FF_STRBUF_AUTO_DESTROY percentageBar = ffStrbufCreate();\n        if(percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            ffPercentAppendBar(&percentageBar, device->battery, options->percent, &options->moduleArgs);\n\n        FF_PRINT_FORMAT_CHECKED(FF_BLUETOOTH_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n            FF_ARG(device->name, \"name\"),\n            FF_ARG(device->address, \"address\"),\n            FF_ARG(device->type, \"type\"),\n            FF_ARG(percentageNum, \"battery-percentage\"),\n            FF_ARG(device->connected, \"connected\"),\n            FF_ARG(percentageBar, \"battery-percentage-bar\"),\n        }));\n    }\n}\n\nbool ffPrintBluetooth(FFBluetoothOptions* options)\n{\n    FF_LIST_AUTO_DESTROY devices = ffListCreate(sizeof (FFBluetoothResult));\n    const char* error = ffDetectBluetooth(options, &devices);\n\n    if(error)\n    {\n        ffPrintError(FF_BLUETOOTH_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    FF_LIST_AUTO_DESTROY filtered = ffListCreate(sizeof(FFBluetoothResult*));\n\n    FF_LIST_FOR_EACH(FFBluetoothResult, device, devices)\n    {\n        if(!device->connected && !options->showDisconnected)\n            continue;\n\n        *(FFBluetoothResult**)ffListAdd(&filtered) = device;\n    }\n\n    if(filtered.length == 0)\n    {\n        ffPrintError(FF_BLUETOOTH_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No bluetooth devices found\");\n    }\n\n    for(uint32_t i = 0; i < filtered.length; i++)\n    {\n        uint8_t index = (uint8_t) (filtered.length == 1 ? 0 : i + 1);\n        printDevice(options, *FF_LIST_GET(FFBluetoothResult*, filtered, i), index);\n    }\n\n    FF_LIST_FOR_EACH(FFBluetoothResult, device, devices)\n    {\n        ffStrbufDestroy(&device->name);\n        ffStrbufDestroy(&device->type);\n        ffStrbufDestroy(&device->address);\n    }\n    return true;\n}\n\nvoid ffParseBluetoothJsonObject(FFBluetoothOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"showDisconnected\"))\n        {\n            options->showDisconnected = yyjson_get_bool(val);\n            continue;\n        }\n\n        if (ffPercentParseJsonObject(key, val, &options->percent))\n            continue;\n\n        ffPrintError(FF_BLUETOOTH_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateBluetoothJsonConfig(FFBluetoothOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    yyjson_mut_obj_add_bool(doc, module, \"showDisconnected\", options->showDisconnected);\n\n    ffPercentGenerateJsonConfig(doc, module, options->percent);\n}\n\nbool ffGenerateBluetoothJsonResult(FFBluetoothOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFBluetoothResult));\n\n    const char* error = ffDetectBluetooth(options, &results);\n    if (error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n\n    FF_LIST_FOR_EACH(FFBluetoothResult, item, results)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"address\", &item->address);\n        yyjson_mut_obj_add_uint(doc, obj, \"battery\", item->battery);\n        yyjson_mut_obj_add_bool(doc, obj, \"connected\", item->connected);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &item->name);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"type\", &item->type);\n    }\n\n    FF_LIST_FOR_EACH(FFBluetoothResult, device, results)\n    {\n        ffStrbufDestroy(&device->name);\n        ffStrbufDestroy(&device->type);\n        ffStrbufDestroy(&device->address);\n    }\n    return true;\n}\n\nvoid ffInitBluetoothOptions(FFBluetoothOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n    options->showDisconnected = false;\n    options->percent = (FFPercentageModuleConfig) { 50, 20, 0 };\n}\n\nvoid ffDestroyBluetoothOptions(FFBluetoothOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffBluetoothModuleInfo = {\n    .name = FF_BLUETOOTH_MODULE_NAME,\n    .description = \"List (connected) bluetooth devices\",\n    .initOptions = (void*) ffInitBluetoothOptions,\n    .destroyOptions = (void*) ffDestroyBluetoothOptions,\n    .parseJsonObject = (void*) ffParseBluetoothJsonObject,\n    .printModule = (void*) ffPrintBluetooth,\n    .generateJsonResult = (void*) ffGenerateBluetoothJsonResult,\n    .generateJsonConfig = (void*) ffGenerateBluetoothJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Name\", \"name\"},\n        {\"Address\", \"address\"},\n        {\"Type\", \"type\"},\n        {\"Battery percentage number\", \"battery-percentage\"},\n        {\"Is connected\", \"connected\"},\n        {\"Battery percentage bar\", \"battery-percentage-bar\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/bluetooth/bluetooth.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_BLUETOOTH_MODULE_NAME \"Bluetooth\"\n\nbool ffPrintBluetooth(FFBluetoothOptions* options);\nvoid ffInitBluetoothOptions(FFBluetoothOptions* options);\nvoid ffDestroyBluetoothOptions(FFBluetoothOptions* options);\n\nextern FFModuleBaseInfo ffBluetoothModuleInfo;\n"
  },
  {
    "path": "src/modules/bluetooth/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n#include \"common/percent.h\"\n\ntypedef struct FFBluetoothOptions\n{\n    FFModuleArgs moduleArgs;\n\n    bool showDisconnected;\n    FFPercentageModuleConfig percent;\n} FFBluetoothOptions;\n\nstatic_assert(sizeof(FFBluetoothOptions) <= FF_OPTION_MAX_SIZE, \"FFBluetoothOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/bluetoothradio/bluetoothradio.c",
    "content": "#include \"common/percent.h\"\n#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/bluetoothradio/bluetoothradio.h\"\n#include \"modules/bluetoothradio/bluetoothradio.h\"\n\n#define FF_BLUETOOTHRADIO_DISPLAY_NAME \"Bluetooth Radio\"\n\nstatic void printDevice(FFBluetoothRadioOptions* options, const FFBluetoothRadioResult* radio, uint8_t index)\n{\n    FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();\n    if (options->moduleArgs.key.length == 0)\n    {\n        ffStrbufAppendF(&key, \"%s (%s)\", FF_BLUETOOTHRADIO_DISPLAY_NAME, radio->name.chars);\n    }\n    else\n    {\n        FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) {\n            FF_ARG(index, \"index\"),\n            FF_ARG(radio->name, \"name\"),\n            FF_ARG(options->moduleArgs.keyIcon, \"icon\"),\n        }));\n    }\n\n    const char* version = NULL;\n\n    switch (radio->lmpVersion < 0 ? -radio->lmpVersion : radio->lmpVersion)\n    {\n        case 0: version = \"1.0b\"; break;\n        case 1: version = \"1.1\"; break;\n        case 2: version = \"1.2\"; break;\n        case 3: version = \"2.0\"; break;\n        case 4: version = \"2.1\"; break;\n        case 5: version = \"3.0\"; break;\n        case 6: version = \"4.0\"; break;\n        case 7: version = \"4.1\"; break;\n        case 8: version = \"4.2\"; break;\n        case 9: version = \"5.0\"; break;\n        case 10: version = \"5.1\"; break;\n        case 11: version = \"5.2\"; break;\n        case 12: version = \"5.3\"; break;\n        case 13: version = \"5.4\"; break;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);\n\n        if (version)\n            printf(\"Bluetooth %s%s (%s)\\n\", version, (radio->lmpVersion < 0 ? \"+\" : \"\"), radio->vendor.chars);\n        else\n            ffStrbufPutTo(&radio->vendor, stdout);\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) {\n            FF_ARG(radio->name, \"name\"),\n            FF_ARG(radio->address, \"address\"),\n            FF_ARG(radio->lmpVersion, \"lmp-version\"),\n            FF_ARG(radio->lmpSubversion, \"lmp-subversion\"),\n            FF_ARG(version, \"version\"),\n            FF_ARG(radio->vendor, \"vendor\"),\n            FF_ARG(radio->discoverable, \"discoverable\"),\n            FF_ARG(radio->connectable, \"connectable\"),\n        }));\n    }\n}\n\nbool ffPrintBluetoothRadio(FFBluetoothRadioOptions* options)\n{\n    FF_LIST_AUTO_DESTROY radios = ffListCreate(sizeof (FFBluetoothRadioResult));\n    const char* error = ffDetectBluetoothRadio(&radios);\n\n    if(error)\n    {\n        ffPrintError(FF_BLUETOOTHRADIO_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    uint8_t index = 0;\n    FF_LIST_FOR_EACH(FFBluetoothRadioResult, radio, radios)\n    {\n        if (!radio->enabled)\n            continue;\n\n        index++;\n        printDevice(options, radio, index);\n    }\n\n    if (index == 0)\n    {\n        if (radios.length > 0)\n            ffPrintError(FF_BLUETOOTHRADIO_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Bluetooth radios found but none enabled\");\n        else\n            ffPrintError(FF_BLUETOOTHRADIO_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No devices detected\");\n    }\n\n    FF_LIST_FOR_EACH(FFBluetoothRadioResult, radio, radios)\n    {\n        ffStrbufDestroy(&radio->name);\n        ffStrbufDestroy(&radio->address);\n        ffStrbufDestroy(&radio->vendor);\n    }\n    return true;\n}\n\nvoid ffParseBluetoothRadioJsonObject(FFBluetoothRadioOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_BLUETOOTHRADIO_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateBluetoothRadioJsonConfig(FFBluetoothRadioOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateBluetoothRadioJsonResult(FF_MAYBE_UNUSED FFBluetoothRadioOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFBluetoothRadioResult));\n\n    const char* error = ffDetectBluetoothRadio(&results);\n    if (error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n\n    FF_LIST_FOR_EACH(FFBluetoothRadioResult, item, results)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &item->name);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"address\", &item->address);\n        if (item->lmpVersion == INT_MIN)\n            yyjson_mut_obj_add_null(doc, obj, \"lmpVersion\");\n        else\n            yyjson_mut_obj_add_int(doc, obj, \"lmpVersion\", item->lmpVersion);\n        if (item->lmpSubversion == INT_MIN)\n            yyjson_mut_obj_add_null(doc, obj, \"lmpSubversion\");\n        else\n            yyjson_mut_obj_add_int(doc, obj, \"lmpSubversion\", item->lmpSubversion);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"vendor\", &item->vendor);\n        yyjson_mut_obj_add_bool(doc, obj, \"enabled\", item->enabled);\n        yyjson_mut_obj_add_bool(doc, obj, \"discoverable\", item->discoverable);\n        yyjson_mut_obj_add_bool(doc, obj, \"connectable\", item->connectable);\n    }\n\n    FF_LIST_FOR_EACH(FFBluetoothRadioResult, radio, results)\n    {\n        ffStrbufDestroy(&radio->name);\n        ffStrbufDestroy(&radio->address);\n        ffStrbufDestroy(&radio->vendor);\n    }\n\n    return true;\n}\n\nvoid ffInitBluetoothRadioOptions(FFBluetoothRadioOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰐻\");\n}\n\nvoid ffDestroyBluetoothRadioOptions(FFBluetoothRadioOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffBluetoothRadioModuleInfo = {\n    .name = FF_BLUETOOTHRADIO_MODULE_NAME,\n    .description = \"List bluetooth radios width supported version and vendor\",\n    .initOptions = (void*) ffInitBluetoothRadioOptions,\n    .destroyOptions = (void*) ffDestroyBluetoothRadioOptions,\n    .parseJsonObject = (void*) ffParseBluetoothRadioJsonObject,\n    .printModule = (void*) ffPrintBluetoothRadio,\n    .generateJsonResult = (void*) ffGenerateBluetoothRadioJsonResult,\n    .generateJsonConfig = (void*) ffGenerateBluetoothRadioJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Radio name for discovering\", \"name\"},\n        {\"Address\", \"address\"},\n        {\"LMP version\", \"lmp-version\"},\n        {\"LMP subversion\", \"lmp-subversion\"},\n        {\"Bluetooth version\", \"version\"},\n        {\"Vendor\", \"vendor\"},\n        {\"Discoverable\", \"discoverable\"},\n        {\"Connectable / Pairable\", \"connectable\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/bluetoothradio/bluetoothradio.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_BLUETOOTHRADIO_MODULE_NAME \"BluetoothRadio\"\n\nbool ffPrintBluetoothRadio(FFBluetoothRadioOptions* options);\nvoid ffInitBluetoothRadioOptions(FFBluetoothRadioOptions* options);\nvoid ffDestroyBluetoothRadioOptions(FFBluetoothRadioOptions* options);\n\nextern FFModuleBaseInfo ffBluetoothRadioModuleInfo;\n"
  },
  {
    "path": "src/modules/bluetoothradio/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n#include \"common/percent.h\"\n\ntypedef struct FFBluetoothRadioOptions\n{\n    FFModuleArgs moduleArgs;\n} FFBluetoothRadioOptions;\n\nstatic_assert(sizeof(FFBluetoothRadioOptions) <= FF_OPTION_MAX_SIZE, \"FFBluetoothRadioOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/board/board.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/board/board.h\"\n#include \"modules/board/board.h\"\n\nbool ffPrintBoard(FFBoardOptions* options)\n{\n    bool success = false;\n    FFBoardResult result;\n    ffStrbufInit(&result.name);\n    ffStrbufInit(&result.vendor);\n    ffStrbufInit(&result.version);\n    ffStrbufInit(&result.serial);\n\n    const char* error = ffDetectBoard(&result);\n    if(error)\n    {\n        ffPrintError(FF_BOARD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        goto exit;\n    }\n\n    if(result.name.length == 0)\n    {\n        ffPrintError(FF_BOARD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"board_name is not set.\");\n        goto exit;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_BOARD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        ffStrbufWriteTo(&result.name, stdout);\n        if (result.version.length)\n            printf(\" (%s)\", result.version.chars);\n        putchar('\\n');\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_BOARD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n            FF_ARG(result.name, \"name\"),\n            FF_ARG(result.vendor, \"vendor\"),\n            FF_ARG(result.version, \"version\"),\n            FF_ARG(result.serial, \"serial\"),\n        }));\n    }\n    success = true;\n\nexit:\n    ffStrbufDestroy(&result.name);\n    ffStrbufDestroy(&result.vendor);\n    ffStrbufDestroy(&result.version);\n    ffStrbufDestroy(&result.serial);\n    return success;\n}\n\nvoid ffParseBoardJsonObject(FFBoardOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_BOARD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateBoardJsonConfig(FFBoardOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateBoardJsonResult(FF_MAYBE_UNUSED FFBoardOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    bool success = false;\n    FFBoardResult board;\n    ffStrbufInit(&board.name);\n    ffStrbufInit(&board.vendor);\n    ffStrbufInit(&board.version);\n    ffStrbufInit(&board.serial);\n\n    const char* error = ffDetectBoard(&board);\n\n    if (error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        goto exit;\n    }\n\n    if (board.name.length == 0)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", \"board_name is not set.\");\n        goto exit;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &board.name);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"vendor\", &board.vendor);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"version\", &board.version);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"serial\", &board.serial);\n    success = true;\n\nexit:\n    ffStrbufDestroy(&board.name);\n    ffStrbufDestroy(&board.vendor);\n    ffStrbufDestroy(&board.version);\n    ffStrbufDestroy(&board.serial);\n    return success;\n}\n\nvoid ffInitBoardOptions(FFBoardOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n}\n\nvoid ffDestroyBoardOptions(FFBoardOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffBoardModuleInfo = {\n    .name = FF_BOARD_MODULE_NAME,\n    .description = \"Print motherboard name and other info\",\n    .initOptions = (void*) ffInitBoardOptions,\n    .destroyOptions = (void*) ffDestroyBoardOptions,\n    .parseJsonObject = (void*) ffParseBoardJsonObject,\n    .printModule = (void*) ffPrintBoard,\n    .generateJsonResult = (void*) ffGenerateBoardJsonResult,\n    .generateJsonConfig = (void*) ffGenerateBoardJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Board name\", \"name\"},\n        {\"Board vendor\", \"vendor\"},\n        {\"Board version\", \"version\"},\n        {\"Board serial number\", \"serial\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/board/board.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_BOARD_MODULE_NAME \"Board\"\n\nbool ffPrintBoard(FFBoardOptions* options);\nvoid ffInitBoardOptions(FFBoardOptions* options);\nvoid ffDestroyBoardOptions(FFBoardOptions* options);\n\nextern FFModuleBaseInfo ffBoardModuleInfo;\n"
  },
  {
    "path": "src/modules/board/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFBoardOptions\n{\n    FFModuleArgs moduleArgs;\n} FFBoardOptions;\n\nstatic_assert(sizeof(FFBoardOptions) <= FF_OPTION_MAX_SIZE, \"FFBoardOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/bootmgr/bootmgr.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/bootmgr/bootmgr.h\"\n#include \"modules/bootmgr/bootmgr.h\"\n\nbool ffPrintBootmgr(FFBootmgrOptions* options)\n{\n    bool success = false;\n    FFBootmgrResult bootmgr = {\n        .name = ffStrbufCreate(),\n        .firmware = ffStrbufCreate(),\n    };\n\n    const char* error = ffDetectBootmgr(&bootmgr);\n\n    if(error)\n    {\n        ffPrintError(FF_BOOTMGR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        goto exit;\n    }\n    else\n    {\n        FF_STRBUF_AUTO_DESTROY firmwareName = ffStrbufCreateCopy(&bootmgr.firmware);\n        #ifndef __APPLE__\n        ffStrbufSubstrAfterLastC(&firmwareName, '\\\\');\n        #else\n        ffStrbufSubstrAfterLastC(&firmwareName, '/');\n        #endif\n\n        if(options->moduleArgs.outputFormat.length == 0)\n        {\n            ffPrintLogoAndKey(FF_BOOTMGR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n            ffStrbufWriteTo(&bootmgr.name, stdout);\n            if (firmwareName.length > 0)\n                printf(\" - %s\\n\", firmwareName.chars);\n            else\n                putchar('\\n');\n        }\n        else\n        {\n            FF_PRINT_FORMAT_CHECKED(FF_BOOTMGR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n                FF_ARG(bootmgr.name, \"name\"),\n                FF_ARG(bootmgr.firmware, \"firmware-path\"),\n                FF_ARG(firmwareName, \"firmware-name\"),\n                FF_ARG(bootmgr.secureBoot, \"secure-boot\"),\n                FF_ARG(bootmgr.order, \"order\"),\n            }));\n        }\n    }\n    success = true;\n\nexit:\n    ffStrbufDestroy(&bootmgr.name);\n    ffStrbufDestroy(&bootmgr.firmware);\n\n    return success;\n}\n\nvoid ffParseBootmgrJsonObject(FFBootmgrOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_BOOTMGR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateBootmgrJsonConfig(FFBootmgrOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateBootmgrJsonResult(FF_MAYBE_UNUSED FFBootmgrOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    bool success = false;\n    FFBootmgrResult bootmgr = {\n        .name = ffStrbufCreate(),\n        .firmware = ffStrbufCreate(),\n    };\n\n    const char* error = ffDetectBootmgr(&bootmgr);\n\n    if (error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        goto exit;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &bootmgr.name);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"firmware\", &bootmgr.firmware);\n    yyjson_mut_obj_add_uint(doc, obj, \"order\", bootmgr.order);\n    yyjson_mut_obj_add_bool(doc, obj, \"secureBoot\", bootmgr.secureBoot);\n    success = true;\n\nexit:\n    ffStrbufDestroy(&bootmgr.name);\n    ffStrbufDestroy(&bootmgr.firmware);\n    return success;\n}\n\nvoid ffInitBootmgrOptions(FFBootmgrOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n}\n\nvoid ffDestroyBootmgrOptions(FFBootmgrOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffBootmgrModuleInfo = {\n    .name = FF_BOOTMGR_MODULE_NAME,\n    .description = \"Print information of 2nd-stage bootloader (name, firmware, etc)\",\n    .initOptions = (void*) ffInitBootmgrOptions,\n    .destroyOptions = (void*) ffDestroyBootmgrOptions,\n    .parseJsonObject = (void*) ffParseBootmgrJsonObject,\n    .printModule = (void*) ffPrintBootmgr,\n    .generateJsonResult = (void*) ffGenerateBootmgrJsonResult,\n    .generateJsonConfig = (void*) ffGenerateBootmgrJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Name / description\", \"name\"},\n        {\"Firmware file path\", \"firmware-path\"},\n        {\"Firmware file name\", \"firmware-name\"},\n        {\"Is secure boot enabled\", \"secure-boot\"},\n        {\"Boot order\", \"order\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/bootmgr/bootmgr.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_BOOTMGR_MODULE_NAME \"Bootmgr\"\n\nbool ffPrintBootmgr(FFBootmgrOptions* options);\nvoid ffInitBootmgrOptions(FFBootmgrOptions* options);\nvoid ffDestroyBootmgrOptions(FFBootmgrOptions* options);\n\nextern FFModuleBaseInfo ffBootmgrModuleInfo;\n"
  },
  {
    "path": "src/modules/bootmgr/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFBootmgrOptions\n{\n    FFModuleArgs moduleArgs;\n} FFBootmgrOptions;\n\nstatic_assert(sizeof(FFBootmgrOptions) <= FF_OPTION_MAX_SIZE, \"FFBootmgrOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/break/break.c",
    "content": "#include \"common/printing.h\"\n#include \"logo/logo.h\"\n#include \"modules/break/break.h\"\n\nbool ffPrintBreak(FF_MAYBE_UNUSED FFBreakOptions* options)\n{\n    ffLogoPrintLine();\n    putchar('\\n');\n    return true;\n}\n\nvoid ffParseBreakJsonObject(FF_MAYBE_UNUSED FFBreakOptions* options, FF_MAYBE_UNUSED yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (unsafe_yyjson_equals_str(key, \"type\") || unsafe_yyjson_equals_str(key, \"condition\"))\n            continue;\n\n        ffPrintError(FF_BREAK_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffInitBreakOptions(FF_MAYBE_UNUSED FFBreakOptions* options)\n{\n}\n\nvoid ffDestroyBreakOptions(FF_MAYBE_UNUSED FFBreakOptions* options)\n{\n}\n\nFFModuleBaseInfo ffBreakModuleInfo = {\n    .name = FF_BREAK_MODULE_NAME,\n    .description = \"Print a empty line\",\n    .initOptions = (void*) ffInitBreakOptions,\n    .destroyOptions = (void*) ffDestroyBreakOptions,\n    .parseJsonObject = (void*) ffParseBreakJsonObject,\n    .printModule = (void*) ffPrintBreak,\n};\n"
  },
  {
    "path": "src/modules/break/break.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_BREAK_MODULE_NAME \"Break\"\n\nbool ffPrintBreak(FFBreakOptions* options);\nvoid ffInitBreakOptions(FFBreakOptions* options);\nvoid ffDestroyBreakOptions(FFBreakOptions* options);\n\nextern FFModuleBaseInfo ffBreakModuleInfo;\n"
  },
  {
    "path": "src/modules/break/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFBreakOptions\n{\n} FFBreakOptions;\n\nstatic_assert(sizeof(FFBreakOptions) <= FF_OPTION_MAX_SIZE, \"FFBreakOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/brightness/brightness.c",
    "content": "#include \"common/percent.h\"\n#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/brightness/brightness.h\"\n#include \"modules/brightness/brightness.h\"\n\nbool ffPrintBrightness(FFBrightnessOptions* options)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFBrightnessResult));\n\n    const char* error = ffDetectBrightness(options, &result);\n\n    if(error)\n    {\n        ffPrintError(FF_BRIGHTNESS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if(result.length == 0)\n    {\n        ffPrintError(FF_BRIGHTNESS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No result is detected.\");\n        return false;\n    }\n\n    FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;\n\n    if (options->compact)\n    {\n        FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate();\n\n        FF_LIST_FOR_EACH(FFBrightnessResult, item, result)\n        {\n            if(str.length > 0)\n                ffStrbufAppendC(&str, ' ');\n\n            const double percent = (item->current - item->min) / (item->max - item->min) * 100;\n            ffPercentAppendNum(&str, percent, options->percent, false, &options->moduleArgs);\n        }\n\n        ffPrintLogoAndKey(FF_BRIGHTNESS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        ffStrbufPutTo(&str, stdout);\n        return true;\n    }\n\n    FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();\n\n    uint32_t index = 0;\n    FF_LIST_FOR_EACH(FFBrightnessResult, item, result)\n    {\n        if (options->moduleArgs.key.length == 0)\n        {\n            ffStrbufAppendF(&key, \"%s (%s)\", FF_BRIGHTNESS_MODULE_NAME, item->name.chars);\n        }\n        else\n        {\n            uint32_t moduleIndex = result.length == 1 ? 0 : index + 1;\n            FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) {\n                FF_ARG(moduleIndex, \"index\"),\n                FF_ARG(item->name, \"name\"),\n                FF_ARG(options->moduleArgs.keyIcon, \"icon\"),\n            }));\n        }\n\n        const double percent = (item->current - item->min) / (item->max - item->min) * 100;\n\n        if (options->moduleArgs.outputFormat.length == 0)\n        {\n            FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate();\n            ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);\n\n            if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            {\n                ffPercentAppendBar(&str, percent, options->percent, &options->moduleArgs);\n            }\n\n            if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n            {\n                if(str.length > 0)\n                    ffStrbufAppendC(&str, ' ');\n\n                ffPercentAppendNum(&str, percent, options->percent, str.length > 0, &options->moduleArgs);\n            }\n\n            ffStrbufAppendS(&str, item->builtin ? \" [Built-in]\" : \" [External]\");\n\n            ffStrbufPutTo(&str, stdout);\n        }\n        else\n        {\n            FF_STRBUF_AUTO_DESTROY valueNum = ffStrbufCreate();\n            if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n                ffPercentAppendNum(&valueNum, percent, options->percent, false, &options->moduleArgs);\n            FF_STRBUF_AUTO_DESTROY valueBar = ffStrbufCreate();\n            if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n                ffPercentAppendBar(&valueBar, percent, options->percent, &options->moduleArgs);\n\n            FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) {\n                FF_ARG(valueNum, \"percentage\"),\n                FF_ARG(item->name, \"name\"),\n                FF_ARG(item->max, \"max\"),\n                FF_ARG(item->min, \"min\"),\n                FF_ARG(item->current, \"current\"),\n                FF_ARG(valueBar, \"percentage-bar\"),\n                FF_ARG(item->builtin, \"is-builtin\"),\n            }));\n        }\n\n        ffStrbufClear(&key);\n        ffStrbufDestroy(&item->name);\n        ++index;\n    }\n\n    return true;\n}\n\nvoid ffParseBrightnessJsonObject(FFBrightnessOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"ddcciSleep\"))\n        {\n            options->ddcciSleep = (uint32_t) yyjson_get_uint(val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"compact\"))\n        {\n            options->compact = (uint32_t) yyjson_get_bool(val);\n            continue;\n        }\n\n        if (ffPercentParseJsonObject(key, val, &options->percent))\n            continue;\n\n        ffPrintError(FF_BRIGHTNESS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateBrightnessJsonConfig(FFBrightnessOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    yyjson_mut_obj_add_uint(doc, module, \"ddcciSleep\", options->ddcciSleep);\n\n    ffPercentGenerateJsonConfig(doc, module, options->percent);\n\n    yyjson_mut_obj_add_bool(doc, module, \"compact\", options->compact);\n}\n\nbool ffGenerateBrightnessJsonResult(FF_MAYBE_UNUSED FFBrightnessOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFBrightnessResult));\n\n    const char* error = ffDetectBrightness(options, &result);\n\n    if (error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_arr(doc);\n    yyjson_mut_obj_add_val(doc, module, \"result\", arr);\n\n    FF_LIST_FOR_EACH(FFBrightnessResult, item, result)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &item->name);\n        yyjson_mut_obj_add_real(doc, obj, \"max\", item->max);\n        yyjson_mut_obj_add_real(doc, obj, \"min\", item->min);\n        yyjson_mut_obj_add_real(doc, obj, \"current\", item->current);\n        yyjson_mut_obj_add_bool(doc, obj, \"builtin\", item->builtin);\n    }\n\n    FF_LIST_FOR_EACH(FFBrightnessResult, item, result)\n    {\n        ffStrbufDestroy(&item->name);\n    }\n\n    return true;\n}\n\nvoid ffInitBrightnessOptions(FFBrightnessOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰯪\");\n\n    options->ddcciSleep = 10;\n    options->percent = (FFPercentageModuleConfig) { 100, 100, 0 };\n    options->compact = false;\n}\n\nvoid ffDestroyBrightnessOptions(FFBrightnessOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffBrightnessModuleInfo = {\n    .name = FF_BRIGHTNESS_MODULE_NAME,\n    .description = \"Print current brightness level of your monitors\",\n    .initOptions = (void*) ffInitBrightnessOptions,\n    .destroyOptions = (void*) ffDestroyBrightnessOptions,\n    .parseJsonObject = (void*) ffParseBrightnessJsonObject,\n    .printModule = (void*) ffPrintBrightness,\n    .generateJsonResult = (void*) ffGenerateBrightnessJsonResult,\n    .generateJsonConfig = (void*) ffGenerateBrightnessJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Screen brightness (percentage num)\", \"percentage\"},\n        {\"Screen name\", \"name\"},\n        {\"Maximum brightness value\", \"max\"},\n        {\"Minimum brightness value\", \"min\"},\n        {\"Current brightness value\", \"current\"},\n        {\"Screen brightness (percentage bar)\", \"percentage-bar\"},\n        {\"Is built-in screen\", \"is-builtin\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/brightness/brightness.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_BRIGHTNESS_MODULE_NAME \"Brightness\"\n\nbool ffPrintBrightness(FFBrightnessOptions* options);\nvoid ffInitBrightnessOptions(FFBrightnessOptions* options);\nvoid ffDestroyBrightnessOptions(FFBrightnessOptions* options);\n\nextern FFModuleBaseInfo ffBrightnessModuleInfo;\n"
  },
  {
    "path": "src/modules/brightness/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n#include \"common/percent.h\"\n\ntypedef struct FFBrightnessOptions\n{\n    FFModuleArgs moduleArgs;\n\n    uint32_t ddcciSleep; // ms\n    FFPercentageModuleConfig percent;\n    bool compact;\n} FFBrightnessOptions;\n\nstatic_assert(sizeof(FFBrightnessOptions) <= FF_OPTION_MAX_SIZE, \"FFBrightnessOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/btrfs/btrfs.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/percent.h\"\n#include \"common/size.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/btrfs/btrfs.h\"\n#include \"modules/btrfs/btrfs.h\"\n\nstatic void printBtrfs(FFBtrfsOptions* options, FFBtrfsResult* result, uint8_t index)\n{\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n    if (options->moduleArgs.key.length == 0)\n    {\n        if (result->name.length > 0)\n            ffStrbufSetF(&buffer, \"%s (%s)\", FF_BTRFS_MODULE_NAME, result->name.chars);\n        else\n            ffStrbufSetS(&buffer, FF_BTRFS_MODULE_NAME);\n    }\n    else\n    {\n        ffStrbufClear(&buffer);\n        FF_PARSE_FORMAT_STRING_CHECKED(&buffer, &options->moduleArgs.key, ((FFformatarg[]) {\n            FF_ARG(index, \"index\"),\n            FF_ARG(result->name, \"name\"),\n            FF_ARG(options->moduleArgs.keyIcon, \"icon\"),\n        }));\n    }\n\n    uint64_t used = 0, allocated = 0, total = result->totalSize;\n    for (uint32_t i = 0; i < ARRAY_SIZE(result->allocation); ++i)\n    {\n        uint64_t times = result->allocation[i].copies;\n        used += result->allocation[i].used * times;\n        allocated += result->allocation[i].total * times;\n    }\n\n    FF_STRBUF_AUTO_DESTROY usedPretty = ffStrbufCreate();\n    ffSizeAppendNum(used, &usedPretty);\n    FF_STRBUF_AUTO_DESTROY allocatedPretty = ffStrbufCreate();\n    ffSizeAppendNum(allocated, &allocatedPretty);\n    FF_STRBUF_AUTO_DESTROY totalPretty = ffStrbufCreate();\n    ffSizeAppendNum(total, &totalPretty);\n\n    double usedPercentage = total > 0 ? (double) used / (double) total * 100.0 : 0;\n    double allocatedPercentage = total > 0 ? (double) allocated / (double) total * 100.0 : 0;\n\n    FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(buffer.chars, index, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);\n\n        ffStrbufClear(&buffer);\n        ffStrbufSetF(&buffer, \"%s / %s (\", usedPretty.chars, totalPretty.chars);\n        ffPercentAppendNum(&buffer, usedPercentage, options->percent, false, &options->moduleArgs);\n        ffStrbufAppendS(&buffer, \", \");\n        ffPercentAppendNum(&buffer, allocatedPercentage, options->percent, false, &options->moduleArgs);\n        ffStrbufAppendF(&buffer, \" allocated)\");\n        ffStrbufPutTo(&buffer, stdout);\n    }\n    else\n    {\n        FF_STRBUF_AUTO_DESTROY usedPercentageNum = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n            ffPercentAppendNum(&usedPercentageNum, usedPercentage, options->percent, false, &options->moduleArgs);\n        FF_STRBUF_AUTO_DESTROY usedPercentageBar = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            ffPercentAppendBar(&usedPercentageBar, usedPercentage, options->percent, &options->moduleArgs);\n\n        FF_STRBUF_AUTO_DESTROY allocatedPercentageNum = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n            ffPercentAppendNum(&allocatedPercentageNum, allocatedPercentage, options->percent, false, &options->moduleArgs);\n        FF_STRBUF_AUTO_DESTROY allocatedPercentageBar = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            ffPercentAppendBar(&allocatedPercentageBar, allocatedPercentage, options->percent, &options->moduleArgs);\n\n        FF_STRBUF_AUTO_DESTROY nodeSizePretty = ffStrbufCreate();\n        ffSizeAppendNum(result->nodeSize, &nodeSizePretty);\n        FF_STRBUF_AUTO_DESTROY sectorSizePretty = ffStrbufCreate();\n        ffSizeAppendNum(result->sectorSize, &sectorSizePretty);\n\n        FF_PRINT_FORMAT_CHECKED(buffer.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) {\n            FF_ARG(result->name, \"name\"),\n            FF_ARG(result->uuid, \"uuid\"),\n            FF_ARG(result->devices, \"devices\"),\n            FF_ARG(result->features, \"features\"),\n            FF_ARG(usedPretty, \"used\"),\n            FF_ARG(allocatedPretty, \"allocated\"),\n            FF_ARG(totalPretty, \"total\"),\n            FF_ARG(usedPercentageNum, \"used-percentage\"),\n            FF_ARG(allocatedPercentageNum, \"allocated-percentage\"),\n            FF_ARG(usedPercentageBar, \"used-percentage-bar\"),\n            FF_ARG(allocatedPercentageBar, \"allocated-percentage-bar\"),\n            FF_ARG(nodeSizePretty, \"node-size\"),\n            FF_ARG(sectorSizePretty, \"sector-size\"),\n        }));\n    }\n}\n\nbool ffPrintBtrfs(FFBtrfsOptions* options)\n{\n    FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFBtrfsResult));\n\n    const char* error = ffDetectBtrfs(&results);\n\n    if (error)\n    {\n        ffPrintError(FF_BTRFS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n    if(results.length == 0)\n    {\n        ffPrintError(FF_BTRFS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", \"No btrfs drive found\");\n        return false;\n    }\n\n    for(uint32_t i = 0; i < results.length; i++)\n    {\n        FFBtrfsResult* result = FF_LIST_GET(FFBtrfsResult, results, i);\n        uint8_t index = results.length == 1 ? 0 : (uint8_t) (i + 1);\n        printBtrfs(options, result, index);\n    }\n\n    FF_LIST_FOR_EACH(FFBtrfsResult, result, results)\n    {\n        ffStrbufDestroy(&result->name);\n        ffStrbufDestroy(&result->uuid);\n        ffStrbufDestroy(&result->devices);\n        ffStrbufDestroy(&result->features);\n    }\n\n    return true;\n}\n\nvoid ffParseBtrfsJsonObject(FFBtrfsOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (ffPercentParseJsonObject(key, val, &options->percent))\n            continue;\n\n        ffPrintError(FF_BTRFS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateBtrfsJsonConfig(FFBtrfsOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    ffPercentGenerateJsonConfig(doc, module, options->percent);\n}\n\nbool ffGenerateBtrfsJsonResult(FF_MAYBE_UNUSED FFBtrfsOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFBtrfsResult));\n\n    const char* error = ffDetectBtrfs(&results);\n    if (error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n\n    FF_LIST_FOR_EACH(FFBtrfsResult, btrfs, results)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &btrfs->name);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"uuid\", &btrfs->uuid);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"devices\", &btrfs->devices);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"features\", &btrfs->features);\n        yyjson_mut_obj_add_uint(doc, obj, \"generation\", btrfs->generation);\n        yyjson_mut_obj_add_uint(doc, obj, \"nodeSize\", btrfs->nodeSize);\n        yyjson_mut_obj_add_uint(doc, obj, \"sectorSize\", btrfs->sectorSize);\n        yyjson_mut_obj_add_uint(doc, obj, \"totalSize\", btrfs->totalSize);\n        yyjson_mut_val* allocation = yyjson_mut_obj_add_arr(doc, obj, \"allocation\");\n        for (uint32_t i = 0; i < ARRAY_SIZE(btrfs->allocation); ++i)\n        {\n            yyjson_mut_val* item = yyjson_mut_arr_add_obj(doc, allocation);\n            yyjson_mut_obj_add_str(doc, item, \"type\", btrfs->allocation[i].type);\n            yyjson_mut_obj_add_str(doc, item, \"profile\", btrfs->allocation[i].profile);\n            yyjson_mut_obj_add_uint(doc, item, \"copies\", btrfs->allocation[i].copies);\n            yyjson_mut_obj_add_uint(doc, item, \"used\", btrfs->allocation[i].used);\n            yyjson_mut_obj_add_uint(doc, item, \"total\", btrfs->allocation[i].total);\n        }\n    }\n\n    FF_LIST_FOR_EACH(FFBtrfsResult, btrfs, results)\n    {\n        ffStrbufDestroy(&btrfs->name);\n        ffStrbufDestroy(&btrfs->uuid);\n        ffStrbufDestroy(&btrfs->devices);\n        ffStrbufDestroy(&btrfs->features);\n    }\n\n    return true;\n}\n\nvoid ffInitBtrfsOptions(FFBtrfsOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󱑛\");\n    options->percent = (FFPercentageModuleConfig) { 50, 80, 0 };\n}\n\nvoid ffDestroyBtrfsOptions(FFBtrfsOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffBtrfsModuleInfo = {\n    .name = FF_BTRFS_MODULE_NAME,\n    .description = \"Print Linux BTRFS volumes\",\n    .initOptions = (void*) ffInitBtrfsOptions,\n    .destroyOptions = (void*) ffDestroyBtrfsOptions,\n    .parseJsonObject = (void*) ffParseBtrfsJsonObject,\n    .printModule = (void*) ffPrintBtrfs,\n    .generateJsonResult = (void*) ffGenerateBtrfsJsonResult,\n    .generateJsonConfig = (void*) ffGenerateBtrfsJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Name / Label\", \"name\"},\n        {\"UUID\", \"uuid\"},\n        {\"Associated devices\", \"devices\"},\n        {\"Enabled features\", \"features\"},\n        {\"Size used\", \"used\"},\n        {\"Size allocated\", \"allocated\"},\n        {\"Size total\", \"total\"},\n        {\"Used percentage num\", \"used-percentage\"},\n        {\"Allocated percentage num\", \"allocated-percentage\"},\n        {\"Used percentage bar\", \"used-percentage-bar\"},\n        {\"Allocated percentage bar\", \"allocated-percentage-bar\"},\n        {\"Node size\", \"node-size\"},\n        {\"Sector size\", \"sector-size\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/btrfs/btrfs.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_BTRFS_MODULE_NAME \"Btrfs\"\n\nbool ffPrintBtrfs(FFBtrfsOptions* options);\nvoid ffInitBtrfsOptions(FFBtrfsOptions* options);\nvoid ffDestroyBtrfsOptions(FFBtrfsOptions* options);\n\nextern FFModuleBaseInfo ffBtrfsModuleInfo;\n"
  },
  {
    "path": "src/modules/btrfs/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n#include \"common/percent.h\"\n\ntypedef struct FFBtrfsOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFPercentageModuleConfig percent;\n} FFBtrfsOptions;\n\nstatic_assert(sizeof(FFBtrfsOptions) <= FF_OPTION_MAX_SIZE, \"FFBtrfsOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/camera/camera.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/libc/libc.h\"\n#include \"detection/camera/camera.h\"\n#include \"modules/camera/camera.h\"\n\nstatic void printDevice(FFCameraOptions* options, const FFCameraResult* device, uint8_t index)\n{\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_CAMERA_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n        ffStrbufWriteTo(&device->name, stdout);\n        if (device->colorspace.length > 0)\n        {\n            fputs(\" - \", stdout);\n            ffStrbufWriteTo(&device->colorspace, stdout);\n        }\n\n        if (device->width > 0 && device->height > 0)\n            printf(\" (%ux%u px)\\n\", (unsigned) device->width, (unsigned) device->height);\n        else\n            putchar('\\n');\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_CAMERA_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, (((FFformatarg[]) {\n            FF_ARG(device->name, \"name\"),\n            FF_ARG(device->vendor, \"vendor\"),\n            FF_ARG(device->colorspace, \"colorspace\"),\n            FF_ARG(device->id, \"id\"),\n            FF_ARG(device->width, \"width\"),\n            FF_ARG(device->height, \"height\"),\n        })));\n    }\n}\n\nbool ffPrintCamera(FFCameraOptions* options)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFCameraResult));\n    const char* error = ffDetectCamera(&result);\n\n    if (error)\n    {\n        ffPrintError(FF_CAMERA_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if (result.length == 0)\n    {\n        ffPrintError(FF_CAMERA_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No camera found\");\n        return false;\n    }\n\n    for(uint32_t i = 0; i < result.length; i++)\n    {\n        uint8_t index = (uint8_t) (result.length == 1 ? 0 : i + 1);\n        printDevice(options, FF_LIST_GET(FFCameraResult, result, i), index);\n    }\n\n    FF_LIST_FOR_EACH(FFCameraResult, dev, result)\n    {\n        ffStrbufDestroy(&dev->name);\n        ffStrbufDestroy(&dev->id);\n        ffStrbufDestroy(&dev->colorspace);\n    }\n\n    return true;\n}\n\nvoid ffParseCameraJsonObject(FFCameraOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_CAMERA_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateCameraJsonConfig(FFCameraOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateCameraJsonResult(FF_MAYBE_UNUSED FFCameraOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFCameraResult));\n    const char* error = ffDetectCamera(&result);\n\n    if (error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n\n    FF_LIST_FOR_EACH(FFCameraResult, dev, result)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &dev->name);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"vendor\", &dev->vendor);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"colorSpace\", &dev->colorspace);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"id\", &dev->id);\n        yyjson_mut_obj_add_uint(doc, obj, \"width\", dev->width);\n        yyjson_mut_obj_add_uint(doc, obj, \"height\", dev->height);\n    }\n\n    FF_LIST_FOR_EACH(FFCameraResult, dev, result)\n    {\n        ffStrbufDestroy(&dev->name);\n        ffStrbufDestroy(&dev->id);\n        ffStrbufDestroy(&dev->colorspace);\n    }\n\n    return true;\n}\n\nvoid ffInitCameraOptions(FFCameraOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰄀\");\n}\n\nvoid ffDestroyCameraOptions(FFCameraOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffCameraModuleInfo = {\n    .name = FF_CAMERA_MODULE_NAME,\n    .description = \"Print available cameras\",\n    .initOptions = (void*) ffInitCameraOptions,\n    .destroyOptions = (void*) ffDestroyCameraOptions,\n    .parseJsonObject = (void*) ffParseCameraJsonObject,\n    .printModule = (void*) ffPrintCamera,\n    .generateJsonResult = (void*) ffGenerateCameraJsonResult,\n    .generateJsonConfig = (void*) ffGenerateCameraJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Device name\", \"name\"},\n        {\"Vendor\", \"vendor\"},\n        {\"Color space\", \"colorspace\"},\n        {\"Identifier\", \"id\"},\n        {\"Width (in px)\", \"width\"},\n        {\"Height (in px)\", \"height\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/camera/camera.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_CAMERA_MODULE_NAME \"Camera\"\n\nbool ffPrintCamera(FFCameraOptions* options);\nvoid ffInitCameraOptions(FFCameraOptions* options);\nvoid ffDestroyCameraOptions(FFCameraOptions* options);\n\nextern FFModuleBaseInfo ffCameraModuleInfo;\n"
  },
  {
    "path": "src/modules/camera/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFCameraOptions\n{\n    FFModuleArgs moduleArgs;\n} FFCameraOptions;\n\nstatic_assert(sizeof(FFCameraOptions) <= FF_OPTION_MAX_SIZE, \"FFCameraOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/chassis/chassis.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/chassis/chassis.h\"\n#include \"modules/chassis/chassis.h\"\n\nbool ffPrintChassis(FFChassisOptions* options)\n{\n    bool success = false;\n\n    FFChassisResult result;\n    ffStrbufInit(&result.type);\n    ffStrbufInit(&result.vendor);\n    ffStrbufInit(&result.version);\n    ffStrbufInit(&result.serial);\n\n    const char* error = ffDetectChassis(&result);\n\n    if(error)\n    {\n        ffPrintError(FF_CHASSIS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        goto exit;\n    }\n\n    if(result.type.length == 0)\n    {\n        ffPrintError(FF_CHASSIS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"chassis_type is not set by O.E.M.\");\n        goto exit;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_CHASSIS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        ffStrbufWriteTo(&result.type, stdout);\n        if (result.version.length)\n            printf(\" (%s)\", result.version.chars);\n        putchar('\\n');\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_CHASSIS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n            FF_ARG(result.type, \"type\"),\n            FF_ARG(result.vendor, \"vendor\"),\n            FF_ARG(result.version, \"version\"),\n            FF_ARG(result.serial, \"serial\"),\n        }));\n    }\n    success = true;\n\nexit:\n    ffStrbufDestroy(&result.type);\n    ffStrbufDestroy(&result.vendor);\n    ffStrbufDestroy(&result.version);\n    ffStrbufDestroy(&result.serial);\n    return success;\n}\n\nvoid ffParseChassisJsonObject(FFChassisOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_CHASSIS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateChassisJsonConfig(FFChassisOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateChassisJsonResult(FF_MAYBE_UNUSED FFChassisOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    bool success = false;\n    FFChassisResult result;\n    ffStrbufInit(&result.type);\n    ffStrbufInit(&result.vendor);\n    ffStrbufInit(&result.version);\n    ffStrbufInit(&result.serial);\n\n    const char* error = ffDetectChassis(&result);\n\n    if (error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        goto exit;\n    }\n\n    if(result.type.length == 0)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", \"chassis_type is not set by O.E.M.\");\n        goto exit;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_strbuf(doc, obj, \"type\", &result.type);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"vendor\", &result.vendor);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"version\", &result.version);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"serial\", &result.serial);\n    success = true;\n\nexit:\n    ffStrbufDestroy(&result.type);\n    ffStrbufDestroy(&result.vendor);\n    ffStrbufDestroy(&result.version);\n    ffStrbufDestroy(&result.serial);\n    return success;\n}\n\nvoid ffInitChassisOptions(FFChassisOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n}\n\nvoid ffDestroyChassisOptions(FFChassisOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffChassisModuleInfo = {\n    .name = FF_CHASSIS_MODULE_NAME,\n    .description = \"Print chassis type (desktop, laptop, etc)\",\n    .initOptions = (void*) ffInitChassisOptions,\n    .destroyOptions = (void*) ffDestroyChassisOptions,\n    .parseJsonObject = (void*) ffParseChassisJsonObject,\n    .printModule = (void*) ffPrintChassis,\n    .generateJsonResult = (void*) ffGenerateChassisJsonResult,\n    .generateJsonConfig = (void*) ffGenerateChassisJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Chassis type\", \"type\"},\n        {\"Chassis vendor\", \"vendor\"},\n        {\"Chassis version\", \"version\"},\n        {\"Chassis serial number\", \"serial\"},\n    })),\n};\n"
  },
  {
    "path": "src/modules/chassis/chassis.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_CHASSIS_MODULE_NAME \"Chassis\"\n\nbool ffPrintChassis(FFChassisOptions* options);\nvoid ffInitChassisOptions(FFChassisOptions* options);\nvoid ffDestroyChassisOptions(FFChassisOptions* options);\n\nextern FFModuleBaseInfo ffChassisModuleInfo;\n"
  },
  {
    "path": "src/modules/chassis/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFChassisOptions\n{\n    FFModuleArgs moduleArgs;\n} FFChassisOptions;\n\nstatic_assert(sizeof(FFChassisOptions) <= FF_OPTION_MAX_SIZE, \"FFChassisOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/colors/colors.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/textModifier.h\"\n#include \"common/stringUtils.h\"\n#include \"logo/logo.h\"\n#include \"modules/colors/colors.h\"\n\nstatic inline uint8_t min(uint8_t a, uint8_t b)\n{\n    return a < b ? a : b;\n}\n\nstatic inline uint8_t max(uint8_t a, uint8_t b)\n{\n    return a > b ? a : b;\n}\n\nbool ffPrintColors(FFColorsOptions* options)\n{\n    bool flag = false;\n\n    FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateA(128);\n\n    if (options->symbol == FF_COLORS_SYMBOL_BLOCK || options->symbol == FF_COLORS_SYMBOL_BACKGROUND)\n    {\n        // 3%d: Set the foreground color\n        for(uint8_t i = options->block.range[0]; i <= min(options->block.range[1], 7); i++)\n        {\n            if (options->symbol == FF_COLORS_SYMBOL_BLOCK)\n            {\n                if (!instance.config.display.pipe)\n                    ffStrbufAppendF(&result, \"\\e[3%dm\", i);\n                for (uint8_t j = 0; j < options->block.width; j++)\n                    ffStrbufAppendS(&result, \"█\");\n            }\n            else\n            {\n                ffStrbufAppendF(&result, \"\\e[4%dm\", i);\n                ffStrbufAppendNC(&result, options->block.width, ' ');\n            }\n        }\n        if (result.length > 0)\n        {\n            ffPrintLogoAndKey(FF_COLORS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n            flag = true;\n\n            if (options->paddingLeft > 0)\n                ffPrintCharTimes(' ', options->paddingLeft);\n\n            if (!instance.config.display.pipe || options->symbol == FF_COLORS_SYMBOL_BACKGROUND)\n                ffStrbufAppendS(&result, FASTFETCH_TEXT_MODIFIER_RESET);\n            ffStrbufPutTo(&result, stdout);\n            ffStrbufClear(&result);\n        }\n\n        #ifdef __linux__\n        // Required by Linux Console for light background to work\n        if (options->symbol == FF_COLORS_SYMBOL_BACKGROUND)\n        {\n            const char* term = getenv(\"TERM\");\n            // Should be \"linux\", however some terminal mulitplexer overrides $TERM\n            if (term && !ffStrStartsWith(term, \"xterm\"))\n                ffStrbufAppendS(&result, \"\\e[5m\");\n        }\n        #endif\n\n        // 9%d: Set the foreground to the bright color\n        for(uint8_t i = max(options->block.range[0], 8); i <= options->block.range[1]; i++)\n        {\n            if (options->symbol == FF_COLORS_SYMBOL_BLOCK)\n            {\n                if(!instance.config.display.pipe)\n                    ffStrbufAppendF(&result, \"\\e[9%dm\", i - 8);\n                for (uint8_t j = 0; j < options->block.width; j++)\n                    ffStrbufAppendS(&result, \"█\");\n            }\n            else\n            {\n                ffStrbufAppendF(&result, \"\\e[10%dm\", i - 8);\n                ffStrbufAppendNC(&result, options->block.width, ' ');\n            }\n        }\n    }\n    else\n    {\n        const char* symbol;\n        switch (options->symbol)\n        {\n            case FF_COLORS_SYMBOL_CIRCLE: symbol = \"● \"; break;\n            case FF_COLORS_SYMBOL_DIAMOND: symbol = \"◆ \"; break;\n            case FF_COLORS_SYMBOL_TRIANGLE: symbol = \"▲ \"; break;\n            case FF_COLORS_SYMBOL_SQUARE: symbol = \"■ \"; break;\n            case FF_COLORS_SYMBOL_STAR: symbol = \"★ \"; break;\n            default: symbol = \"███ \"; break;\n        }\n        for (int i = 8; i >= 1; --i)\n        {\n            if (!instance.config.display.pipe)\n                ffStrbufAppendF(&result, \"\\e[3%dm\", i);\n            ffStrbufAppendS(&result, symbol);\n        }\n        ffStrbufTrimRight(&result, ' ');\n    }\n\n    if (result.length > 0)\n    {\n        if (flag)\n            ffLogoPrintLine();\n        else\n        {\n            ffPrintLogoAndKey(FF_COLORS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n            flag = true;\n        }\n\n        if(options->paddingLeft > 0)\n            ffPrintCharTimes(' ', options->paddingLeft);\n        if(!instance.config.display.pipe || options->symbol == FF_COLORS_SYMBOL_BACKGROUND)\n            ffStrbufAppendS(&result, FASTFETCH_TEXT_MODIFIER_RESET);\n        ffStrbufPutTo(&result, stdout);\n    }\n\n    if (!flag)\n    {\n        ffPrintError(FF_COLORS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", \"Nothing to print\");\n        return false;\n    }\n\n    return true;\n}\n\nvoid ffParseColorsJsonObject(FFColorsOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"symbol\"))\n        {\n            int value;\n            const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {\n                { \"block\", FF_COLORS_SYMBOL_BLOCK },\n                { \"background\", FF_COLORS_SYMBOL_BACKGROUND },\n                { \"circle\", FF_COLORS_SYMBOL_CIRCLE },\n                { \"diamond\", FF_COLORS_SYMBOL_DIAMOND },\n                { \"triangle\", FF_COLORS_SYMBOL_TRIANGLE },\n                { \"square\", FF_COLORS_SYMBOL_SQUARE },\n                { \"star\", FF_COLORS_SYMBOL_STAR },\n                {},\n            });\n            if (error)\n                ffPrintError(FF_COLORS_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, \"Invalid %s value: %s\", unsafe_yyjson_get_str(key), error);\n            else\n                options->symbol = (FFColorsSymbol) value;\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"paddingLeft\"))\n        {\n            options->paddingLeft = (uint32_t) yyjson_get_uint(val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"block\"))\n        {\n            if (!yyjson_is_obj(val))\n                ffPrintError(FF_COLORS_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, \"Invalid %s value: must be an object\", unsafe_yyjson_get_str(key));\n            else\n            {\n                yyjson_val* width = yyjson_obj_get(val, \"width\");\n                if (width)\n                    options->block.width = (uint8_t) yyjson_get_uint(width);\n\n                yyjson_val* range = yyjson_obj_get(val, \"range\");\n                if (range)\n                {\n                    if (!yyjson_is_arr(range) || yyjson_arr_size(range) != 2)\n                        ffPrintError(FF_COLORS_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, \"Invalid %s.range value: must be an array of 2 elements\", unsafe_yyjson_get_str(key));\n                    else\n                    {\n                        uint8_t start = (uint8_t) yyjson_get_uint(yyjson_arr_get(range, 0));\n                        uint8_t end = (uint8_t) yyjson_get_uint(yyjson_arr_get(range, 1));\n                        if (start > end)\n                            ffPrintError(FF_COLORS_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, \"Invalid %s.range value: range[0] > range[1]\", unsafe_yyjson_get_str(key));\n                        else if (end > 15)\n                            ffPrintError(FF_COLORS_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, \"Invalid %s.range value: range[1] > 15\", unsafe_yyjson_get_str(key));\n                        else\n                        {\n                            options->block.range[0] = start;\n                            options->block.range[1] = end;\n                        }\n                    }\n                }\n            }\n            continue;\n        }\n\n        ffPrintError(FF_COLORS_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateColorsJsonConfig(FFColorsOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    yyjson_mut_obj_remove_key(module, \"format\"); // Not supported\n\n    switch (options->symbol)\n    {\n        case FF_COLORS_SYMBOL_CIRCLE: yyjson_mut_obj_add_str(doc, module, \"symbol\", \"circle\"); break;\n        case FF_COLORS_SYMBOL_DIAMOND: yyjson_mut_obj_add_str(doc, module, \"symbol\", \"diamond\"); break;\n        case FF_COLORS_SYMBOL_TRIANGLE: yyjson_mut_obj_add_str(doc, module, \"symbol\", \"triangle\"); break;\n        case FF_COLORS_SYMBOL_SQUARE: yyjson_mut_obj_add_str(doc, module, \"symbol\", \"square\"); break;\n        case FF_COLORS_SYMBOL_STAR: yyjson_mut_obj_add_str(doc, module, \"symbol\", \"star\"); break;\n        default: yyjson_mut_obj_add_str(doc, module, \"symbol\", \"block\"); break;\n    }\n\n    yyjson_mut_obj_add_uint(doc, module, \"paddingLeft\", options->paddingLeft);\n\n    {\n        yyjson_mut_val* block = yyjson_mut_obj_add_obj(doc, module, \"block\");\n\n        yyjson_mut_obj_add_uint(doc, block, \"width\", options->block.width);\n\n        yyjson_mut_val* range = yyjson_mut_obj_add_arr(doc, block, \"range\");\n        for (uint8_t i = 0; i < 2; i++)\n            yyjson_mut_arr_add_uint(doc, range, options->block.range[i]);\n    }\n}\n\nvoid ffInitColorsOptions(FFColorsOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n    ffStrbufSetStatic(&options->moduleArgs.key, \" \");\n    options->symbol = FF_COLORS_SYMBOL_BACKGROUND;\n    options->paddingLeft = 0;\n    options->block = (FFBlockConfig) {\n        .width = 3,\n        .range = { 0, 15 },\n    };\n}\n\nvoid ffDestroyColorsOptions(FFColorsOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffColorsModuleInfo = {\n    .name = FF_COLORS_MODULE_NAME,\n    .description = \"Display the terminal's 16-color palette\",\n    .initOptions = (void*) ffInitColorsOptions,\n    .destroyOptions = (void*) ffDestroyColorsOptions,\n    .parseJsonObject = (void*) ffParseColorsJsonObject,\n    .printModule = (void*) ffPrintColors,\n    .generateJsonConfig = (void*) ffGenerateColorsJsonConfig,\n};\n"
  },
  {
    "path": "src/modules/colors/colors.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_COLORS_MODULE_NAME \"Colors\"\n\nbool ffPrintColors(FFColorsOptions* options);\nvoid ffInitColorsOptions(FFColorsOptions* options);\nvoid ffDestroyColorsOptions(FFColorsOptions* options);\n\nextern FFModuleBaseInfo ffColorsModuleInfo;\n"
  },
  {
    "path": "src/modules/colors/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef enum __attribute__((__packed__)) FFColorsSymbol\n{\n    FF_COLORS_SYMBOL_BLOCK,\n    FF_COLORS_SYMBOL_BACKGROUND,\n    FF_COLORS_SYMBOL_CIRCLE,\n    FF_COLORS_SYMBOL_DIAMOND,\n    FF_COLORS_SYMBOL_SQUARE,\n    FF_COLORS_SYMBOL_TRIANGLE,\n    FF_COLORS_SYMBOL_STAR,\n} FFColorsSymbol;\n\ntypedef struct FFBlockConfig\n{\n    uint8_t width;\n    uint8_t range[2];\n} FFBlockConfig;\n\ntypedef struct FFColorsOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFColorsSymbol symbol;\n    uint32_t paddingLeft;\n    FFBlockConfig block;\n} FFColorsOptions;\n\nstatic_assert(sizeof(FFColorsOptions) <= FF_OPTION_MAX_SIZE, \"FFColorsOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/command/command.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"modules/command/command.h\"\n#include \"detection/command/command.h\"\n\nbool ffPrintCommand(FFCommandOptions* options)\n{\n    FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();\n    const char* error = ffDetectCommand(options, &result);\n\n    if (error)\n    {\n        ffPrintError(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if (!result.length)\n    {\n        ffPrintError(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No result generated\");\n        return false;\n    }\n\n    if (options->splitLines)\n    {\n        uint8_t index = 0;\n        char* line = NULL;\n        size_t len = 0;\n        while (ffStrbufGetline(&line, &len, &result))\n        {\n            if (options->moduleArgs.outputFormat.length == 0)\n            {\n                ffPrintLogoAndKey(FF_COMMAND_MODULE_NAME, ++index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n                puts(line);\n            }\n            else\n            {\n                FF_PRINT_FORMAT_CHECKED(FF_COMMAND_MODULE_NAME, ++index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n                    FF_ARG(line, \"result\")\n                }));\n            }\n        }\n    }\n    else\n    {\n        if (options->moduleArgs.outputFormat.length == 0)\n        {\n            ffPrintLogoAndKey(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n            ffStrbufPutTo(&result, stdout);\n        }\n        else\n        {\n            FF_PRINT_FORMAT_CHECKED(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n                FF_ARG(result, \"result\")\n            }));\n        }\n    }\n\n    return true;\n}\n\nvoid ffParseCommandJsonObject(FFCommandOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"shell\"))\n        {\n            ffStrbufSetJsonVal(&options->shell, val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"param\"))\n        {\n            ffStrbufSetJsonVal(&options->param, val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"text\"))\n        {\n            ffStrbufSetJsonVal(&options->text, val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"useStdErr\"))\n        {\n            options->useStdErr = yyjson_get_bool(val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"parallel\"))\n        {\n            options->parallel = yyjson_get_bool(val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"splitLines\"))\n        {\n            options->splitLines = yyjson_get_bool(val);\n            continue;\n        }\n\n        ffPrintError(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateCommandJsonConfig(FFCommandOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    yyjson_mut_obj_add_strbuf(doc, module, \"shell\", &options->shell);\n    yyjson_mut_obj_add_strbuf(doc, module, \"param\", &options->param);\n    yyjson_mut_obj_add_strbuf(doc, module, \"text\", &options->text);\n    yyjson_mut_obj_add_bool(doc, module, \"useStdErr\", options->useStdErr);\n    yyjson_mut_obj_add_bool(doc, module, \"parallel\", options->parallel);\n    yyjson_mut_obj_add_bool(doc, module, \"splitLines\", options->splitLines);\n}\n\nbool ffGenerateCommandJsonResult(FF_MAYBE_UNUSED FFCommandOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();\n    const char* error = ffDetectCommand(options, &result);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    if(!result.length)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", \"No result generated\");\n        return false;\n    }\n\n    if (options->splitLines)\n    {\n        yyjson_mut_val* jsonArray = yyjson_mut_obj_add_arr(doc, module, \"result\");\n        char* line = NULL;\n        size_t len = 0;\n        while (ffStrbufGetline(&line, &len, &result))\n            yyjson_mut_arr_add_strncpy(doc, jsonArray, line, len);\n    }\n    else\n        yyjson_mut_obj_add_strbuf(doc, module, \"result\", &result);\n\n    return true;\n}\n\nvoid ffInitCommandOptions(FFCommandOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n\n    ffStrbufInitStatic(&options->shell,\n        #ifdef _WIN32\n        \"cmd.exe\"\n        #else\n        \"/bin/sh\"\n        #endif\n    );\n    ffStrbufInitStatic(&options->param,\n        #ifdef _WIN32\n        \"/c\"\n        #else\n        \"-c\"\n        #endif\n    );\n    ffStrbufInit(&options->text);\n    options->useStdErr = false;\n    options->parallel = true;\n    options->splitLines = false;\n}\n\nvoid ffDestroyCommandOptions(FFCommandOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n    ffStrbufDestroy(&options->shell);\n    ffStrbufDestroy(&options->param);\n    ffStrbufDestroy(&options->text);\n}\n\nFFModuleBaseInfo ffCommandModuleInfo = {\n    .name = FF_COMMAND_MODULE_NAME,\n    .description = \"Run custom shell scripts\",\n    .initOptions = (void*) ffInitCommandOptions,\n    .destroyOptions = (void*) ffDestroyCommandOptions,\n    .parseJsonObject = (void*) ffParseCommandJsonObject,\n    .printModule = (void*) ffPrintCommand,\n    .generateJsonResult = (void*) ffGenerateCommandJsonResult,\n    .generateJsonConfig = (void*) ffGenerateCommandJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Command result\", \"result\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/command/command.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_COMMAND_MODULE_NAME \"Command\"\n\nbool ffPrepareCommand(FFCommandOptions* options);\n\nbool ffPrintCommand(FFCommandOptions* options);\nvoid ffInitCommandOptions(FFCommandOptions* options);\nvoid ffDestroyCommandOptions(FFCommandOptions* options);\n\nextern FFModuleBaseInfo ffCommandModuleInfo;\n"
  },
  {
    "path": "src/modules/command/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFCommandOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFstrbuf shell;\n    FFstrbuf param;\n    FFstrbuf text;\n    bool useStdErr;\n    bool parallel;\n    bool splitLines;\n} FFCommandOptions;\n\nstatic_assert(sizeof(FFCommandOptions) <= FF_OPTION_MAX_SIZE, \"FFCommandOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/cpu/cpu.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/parsing.h\"\n#include \"common/temps.h\"\n#include \"common/frequency.h\"\n#include \"detection/cpu/cpu.h\"\n#include \"modules/cpu/cpu.h\"\n\nstatic int sortCores(const FFCPUCore* a, const FFCPUCore* b)\n{\n    return (int)b->freq - (int)a->freq;\n}\n\nbool ffPrintCPU(FFCPUOptions* options)\n{\n    bool success = false;\n    FFCPUResult cpu = {\n        .temperature = FF_CPU_TEMP_UNSET,\n        .frequencyMax = 0,\n        .frequencyBase = 0,\n        .name = ffStrbufCreate(),\n        .vendor = ffStrbufCreate(),\n    };\n\n    const char* error = ffDetectCPU(options, &cpu);\n\n    if(error)\n    {\n        ffPrintError(FF_CPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n    }\n    else if(cpu.vendor.length == 0 && cpu.name.length == 0 && cpu.coresOnline <= 1)\n    {\n        ffPrintError(FF_CPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No CPU detected\");\n    }\n    else\n    {\n        FF_STRBUF_AUTO_DESTROY coreTypes = ffStrbufCreate();\n        if (options->showPeCoreCount)\n        {\n            uint32_t typeCount = 0;\n            while (cpu.coreTypes[typeCount].count != 0 && typeCount < ARRAY_SIZE(cpu.coreTypes)) typeCount++;\n            if (typeCount > 0)\n            {\n                qsort(cpu.coreTypes, typeCount, sizeof(cpu.coreTypes[0]), (void*) sortCores);\n\n                for (uint32_t i = 0; i < typeCount; i++)\n                    ffStrbufAppendF(&coreTypes, \"%s%u\", i == 0 ? \"\" : \"+\", cpu.coreTypes[i].count);\n            }\n        }\n\n        if(options->moduleArgs.outputFormat.length == 0)\n        {\n            ffPrintLogoAndKey(FF_CPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n            FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate();\n\n            if(cpu.packages > 1)\n                ffStrbufAppendF(&str, \"%u x \", cpu.packages);\n\n            if(cpu.name.length > 0)\n                ffStrbufAppend(&str, &cpu.name);\n            else if(cpu.vendor.length > 0)\n            {\n                ffStrbufAppend(&str, &cpu.vendor);\n                ffStrbufAppendS(&str, \" CPU\");\n            }\n            else\n                ffStrbufAppendS(&str, \"Unknown\");\n\n            if(coreTypes.length > 0)\n                ffStrbufAppendF(&str, \" (%s)\", coreTypes.chars);\n            else if(cpu.coresOnline > 1)\n                ffStrbufAppendF(&str, \" (%u)\", cpu.coresOnline);\n\n            uint32_t freq = cpu.frequencyMax;\n            if(freq == 0)\n                freq = cpu.frequencyBase;\n            if(freq > 0)\n            {\n                ffStrbufAppendS(&str, \" @ \");\n                ffFreqAppendNum(freq, &str);\n            }\n\n            if(cpu.temperature != FF_CPU_TEMP_UNSET)\n            {\n                ffStrbufAppendS(&str, \" - \");\n                ffTempsAppendNum(cpu.temperature, &str, options->tempConfig, &options->moduleArgs);\n            }\n\n            ffStrbufPutTo(&str, stdout);\n        }\n        else\n        {\n            FF_STRBUF_AUTO_DESTROY freqBase = ffStrbufCreate();\n            ffFreqAppendNum(cpu.frequencyBase, &freqBase);\n            FF_STRBUF_AUTO_DESTROY freqMax = ffStrbufCreate();\n            ffFreqAppendNum(cpu.frequencyMax, &freqMax);\n\n            FF_STRBUF_AUTO_DESTROY tempStr = ffStrbufCreate();\n            ffTempsAppendNum(cpu.temperature, &tempStr, options->tempConfig, &options->moduleArgs);\n            FF_PRINT_FORMAT_CHECKED(FF_CPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n                FF_ARG(cpu.name, \"name\"),\n                FF_ARG(cpu.vendor, \"vendor\"),\n                FF_ARG(cpu.coresPhysical, \"cores-physical\"),\n                FF_ARG(cpu.coresLogical, \"cores-logical\"),\n                FF_ARG(cpu.coresOnline, \"cores-online\"),\n                FF_ARG(freqBase, \"freq-base\"),\n                FF_ARG(freqMax, \"freq-max\"),\n                FF_ARG(tempStr, \"temperature\"),\n                FF_ARG(coreTypes, \"core-types\"),\n                FF_ARG(cpu.packages, \"packages\"),\n                FF_ARG(cpu.march, \"march\"),\n                FF_ARG(cpu.numaNodes, \"numa-nodes\"),\n            }));\n        }\n        success = true;\n    }\n\n    ffStrbufDestroy(&cpu.name);\n    ffStrbufDestroy(&cpu.vendor);\n\n    return success;\n}\n\nvoid ffParseCPUJsonObject(FFCPUOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (ffTempsParseJsonObject(key, val, &options->temp, &options->tempConfig))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"tempSensor\"))\n        {\n            ffStrbufSetJsonVal(&options->tempSensor, val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"freqNdigits\"))\n        {\n            ffPrintError(FF_CPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"modules.CPU.freqNdigits has been moved to display.freq.ndigits\");\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"showPeCoreCount\"))\n        {\n            options->showPeCoreCount = yyjson_get_bool(val);\n            continue;\n        }\n\n        ffPrintError(FF_CPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateCPUJsonConfig(FFCPUOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    ffTempsGenerateJsonConfig(doc, module, options->temp, options->tempConfig);\n\n    yyjson_mut_obj_add_bool(doc, module, \"showPeCoreCount\", options->showPeCoreCount);\n}\n\nbool ffGenerateCPUJsonResult(FFCPUOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    bool success = false;\n    FFCPUResult cpu = {\n        .temperature = FF_CPU_TEMP_UNSET,\n        .frequencyMax = 0,\n        .frequencyBase = 0,\n        .name = ffStrbufCreate(),\n        .vendor = ffStrbufCreate(),\n    };\n\n    const char* error = ffDetectCPU(options, &cpu);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n    }\n    else if(cpu.vendor.length == 0 && cpu.name.length == 0 && cpu.coresOnline <= 1)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", \"No CPU detected\");\n    }\n    else\n    {\n        yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n        yyjson_mut_obj_add_strbuf(doc, obj, \"cpu\", &cpu.name);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"vendor\", &cpu.vendor);\n        if (cpu.packages == 0)\n            yyjson_mut_obj_add_null(doc, obj, \"packages\");\n        else\n            yyjson_mut_obj_add_uint(doc, obj, \"packages\", cpu.packages);\n\n        yyjson_mut_val* cores = yyjson_mut_obj_add_obj(doc, obj, \"cores\");\n        yyjson_mut_obj_add_uint(doc, cores, \"physical\", cpu.coresPhysical);\n        yyjson_mut_obj_add_uint(doc, cores, \"logical\", cpu.coresLogical);\n        yyjson_mut_obj_add_uint(doc, cores, \"online\", cpu.coresOnline);\n\n        yyjson_mut_val* frequency = yyjson_mut_obj_add_obj(doc, obj, \"frequency\");\n        yyjson_mut_obj_add_uint(doc, frequency, \"base\", cpu.frequencyBase);\n        yyjson_mut_obj_add_uint(doc, frequency, \"max\", cpu.frequencyMax);\n\n        yyjson_mut_val* coreTypes = yyjson_mut_obj_add_arr(doc, obj, \"coreTypes\");\n        for (uint32_t i = 0; i < ARRAY_SIZE(cpu.coreTypes) && cpu.coreTypes[i].count > 0; i++)\n        {\n            yyjson_mut_val* core = yyjson_mut_arr_add_obj(doc, coreTypes);\n            yyjson_mut_obj_add_uint(doc, core, \"count\", cpu.coreTypes[i].count);\n            yyjson_mut_obj_add_uint(doc, core, \"freq\", cpu.coreTypes[i].freq);\n        }\n\n        if (cpu.temperature != FF_CPU_TEMP_UNSET)\n            yyjson_mut_obj_add_real(doc, obj, \"temperature\", cpu.temperature);\n        else\n            yyjson_mut_obj_add_null(doc, obj, \"temperature\");\n\n        if (cpu.march)\n            yyjson_mut_obj_add_str(doc, obj, \"march\", cpu.march);\n        else\n            yyjson_mut_obj_add_null(doc, obj, \"march\");\n\n        if (cpu.numaNodes > 0)\n            yyjson_mut_obj_add_uint(doc, obj, \"numaNodes\", cpu.numaNodes);\n        else\n            yyjson_mut_obj_add_null(doc, obj, \"numaNodes\");\n\n        success = true;\n    }\n\n    ffStrbufDestroy(&cpu.name);\n    ffStrbufDestroy(&cpu.vendor);\n\n    return success;\n}\n\nvoid ffInitCPUOptions(FFCPUOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n    ffStrbufInit(&options->tempSensor);\n    options->temp = false;\n    options->tempConfig = (FFColorRangeConfig) { 60, 80 };\n    options->showPeCoreCount = false;\n}\n\nvoid ffDestroyCPUOptions(FFCPUOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n    ffStrbufDestroy(&options->tempSensor);\n}\n\nFFModuleBaseInfo ffCPUModuleInfo = {\n    .name = FF_CPU_MODULE_NAME,\n    .description = \"Print CPU name, frequency, etc\",\n    .initOptions = (void*) ffInitCPUOptions,\n    .destroyOptions = (void*) ffDestroyCPUOptions,\n    .parseJsonObject = (void*) ffParseCPUJsonObject,\n    .printModule = (void*) ffPrintCPU,\n    .generateJsonResult = (void*) ffGenerateCPUJsonResult,\n    .generateJsonConfig = (void*) ffGenerateCPUJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Name\", \"name\"},\n        {\"Vendor\", \"vendor\"},\n        {\"Physical core count\", \"cores-physical\"},\n        {\"Logical core count\", \"cores-logical\"},\n        {\"Online core count\", \"cores-online\"},\n        {\"Base frequency (formatted)\", \"freq-base\"},\n        {\"Max frequency (formatted)\", \"freq-max\"},\n        {\"Temperature (formatted)\", \"temperature\"},\n        {\"Logical core count grouped by frequency\", \"core-types\"},\n        {\"Processor package count\", \"packages\"},\n        {\"CPU microarchitecture\", \"march\"},\n        {\"NUMA node count\", \"numa-nodes\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/cpu/cpu.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_CPU_MODULE_NAME \"CPU\"\n\nbool ffPrintCPU(FFCPUOptions* options);\nvoid ffInitCPUOptions(FFCPUOptions* options);\nvoid ffDestroyCPUOptions(FFCPUOptions* options);\n\nextern FFModuleBaseInfo ffCPUModuleInfo;\n"
  },
  {
    "path": "src/modules/cpu/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFCPUOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFstrbuf tempSensor;\n    bool temp;\n    FFColorRangeConfig tempConfig;\n    bool showPeCoreCount;\n} FFCPUOptions;\n\nstatic_assert(sizeof(FFCPUOptions) <= FF_OPTION_MAX_SIZE, \"FFCPUOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/cpucache/cpucache.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/size.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/cpucache/cpucache.h\"\n#include \"modules/cpucache/cpucache.h\"\n\n#define FF_CPUCACHE_DISPLAY_NAME \"CPU Cache\"\n\nstatic void printCPUCacheNormal(const FFCPUCacheResult* result, FFCPUCacheOptions* options)\n{\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();\n\n    char levelStr[4] = \"L\";\n    for (uint32_t i = 0; i < ARRAY_SIZE(result->caches) && result->caches[i].length > 0; i++)\n    {\n        ffStrbufClear(&key);\n        levelStr[1] = (char) ('1' + i);\n        if (options->moduleArgs.key.length == 0)\n            ffStrbufAppendF(&key, \"%s (%s)\", FF_CPUCACHE_DISPLAY_NAME, levelStr);\n        else\n        {\n            uint32_t index = i + 1;\n            FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) {\n                FF_ARG(index, \"index\"),\n                FF_ARG(levelStr, \"level\"),\n                FF_ARG(options->moduleArgs.keyIcon, \"icon\"),\n            }));\n        }\n\n        ffStrbufClear(&buffer);\n\n        uint32_t sum = 0;\n        FF_LIST_FOR_EACH(FFCPUCache, src, result->caches[i])\n        {\n            char typeStr = '?';\n            switch (src->type)\n            {\n                case FF_CPU_CACHE_TYPE_DATA: typeStr = 'D'; break;\n                case FF_CPU_CACHE_TYPE_INSTRUCTION: typeStr = 'I'; break;\n                case FF_CPU_CACHE_TYPE_UNIFIED: typeStr = 'U'; break;\n                case FF_CPU_CACHE_TYPE_TRACE: typeStr = 'T'; break;\n            }\n            if (buffer.length)\n                ffStrbufAppendS(&buffer, \", \");\n            if (src->num > 1)\n                ffStrbufAppendF(&buffer, \"%ux\", src->num);\n            ffSizeAppendNum(src->size, &buffer);\n            ffStrbufAppendF(&buffer, \" (%c)\", typeStr);\n\n            sum += src->size * src->num;\n        }\n\n        if(options->moduleArgs.outputFormat.length == 0)\n        {\n            ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);\n            ffStrbufPutTo(&buffer, stdout);\n        }\n        else\n        {\n            FF_STRBUF_AUTO_DESTROY buffer2 = ffStrbufCreate();\n            ffSizeAppendNum(sum, &buffer2);\n            FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) {\n                FF_ARG(buffer, \"result\"),\n                FF_ARG(buffer2, \"sum\"),\n            }));\n        }\n    }\n}\n\nstatic void printCPUCacheCompact(const FFCPUCacheResult* result, FFCPUCacheOptions* options)\n{\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n    uint64_t sum = 0;\n    for (uint32_t i = 0; i < ARRAY_SIZE(result->caches) && result->caches[i].length > 0; i++)\n    {\n        if (buffer.length)\n            ffStrbufAppendS(&buffer, \", \");\n        uint32_t value = 0;\n        FF_LIST_FOR_EACH(FFCPUCache, src, result->caches[i])\n            value += src->size * src->num;\n        ffSizeAppendNum(value, &buffer);\n        ffStrbufAppendF(&buffer, \" (L%u)\", i + 1);\n        sum += value;\n    }\n\n    if (options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_CPUCACHE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        ffStrbufPutTo(&buffer, stdout);\n    }\n    else\n    {\n        FF_STRBUF_AUTO_DESTROY buffer2 = ffStrbufCreate();\n        ffSizeAppendNum(sum, &buffer2);\n        FF_PRINT_FORMAT_CHECKED(FF_CPUCACHE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n            FF_ARG(buffer, \"result\"),\n            FF_ARG(buffer2, \"sum\"),\n        }));\n    }\n}\n\nbool ffPrintCPUCache(FFCPUCacheOptions* options)\n{\n    bool success = false;\n    FFCPUCacheResult result = {\n        .caches = {\n            ffListCreate(sizeof(FFCPUCache)),\n            ffListCreate(sizeof(FFCPUCache)),\n            ffListCreate(sizeof(FFCPUCache)),\n            ffListCreate(sizeof(FFCPUCache)),\n        },\n    };\n\n    const char* error = ffDetectCPUCache(&result);\n\n    if(error)\n    {\n        ffPrintError(FF_CPUCACHE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        goto exit;\n    }\n\n    if (!options->compact)\n        printCPUCacheNormal(&result, options);\n    else\n        printCPUCacheCompact(&result, options);\n    success = true;\n\nexit:\n    ffListDestroy(&result.caches[0]);\n    ffListDestroy(&result.caches[1]);\n    ffListDestroy(&result.caches[2]);\n    ffListDestroy(&result.caches[3]);\n\n    return success;\n}\n\nvoid ffParseCPUCacheJsonObject(FFCPUCacheOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"compact\"))\n        {\n            options->compact = yyjson_get_bool(val);\n            continue;\n        }\n\n        ffPrintError(FF_CPUCACHE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateCPUCacheJsonConfig(FFCPUCacheOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    yyjson_mut_obj_add_bool(doc, module, \"compact\", options->compact);\n}\n\nbool ffGenerateCPUCacheJsonResult(FF_MAYBE_UNUSED FFCPUCacheOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    bool success = false;\n    FFCPUCacheResult result = {\n        .caches = {\n            ffListCreate(sizeof(FFCPUCache)),\n            ffListCreate(sizeof(FFCPUCache)),\n            ffListCreate(sizeof(FFCPUCache)),\n            ffListCreate(sizeof(FFCPUCache)),\n        },\n    };\n\n    const char* error = ffDetectCPUCache(&result);\n\n    if (error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        goto exit;\n    }\n\n    yyjson_mut_val* caches = yyjson_mut_obj_add_obj(doc, module, \"result\");\n\n    for (uint32_t i = 0; i < ARRAY_SIZE(result.caches) && result.caches[i].length > 0; i++)\n    {\n        yyjson_mut_val* level = yyjson_mut_obj_add_arr(doc, caches, &\"l1\\0l2\\0l3\\0l4\\0\"[i * 3]);\n        FF_LIST_FOR_EACH(FFCPUCache, src, result.caches[i])\n        {\n            yyjson_mut_val* item = yyjson_mut_arr_add_obj(doc, level);\n            yyjson_mut_obj_add_uint(doc, item, \"size\", src->size);\n            yyjson_mut_obj_add_uint(doc, item, \"num\", src->num);\n            const char* typeStr = \"unknown\";\n            switch (src->type)\n            {\n                case FF_CPU_CACHE_TYPE_DATA: typeStr = \"data\"; break;\n                case FF_CPU_CACHE_TYPE_INSTRUCTION: typeStr = \"instruction\"; break;\n                case FF_CPU_CACHE_TYPE_UNIFIED: typeStr = \"unified\"; break;\n                case FF_CPU_CACHE_TYPE_TRACE: typeStr = \"trace\"; break;\n            }\n            yyjson_mut_obj_add_uint(doc, item, \"lineSize\", src->lineSize);\n            yyjson_mut_obj_add_str(doc, item, \"type\", typeStr);\n        }\n    }\n    success = true;\n\nexit:\n    ffListDestroy(&result.caches[0]);\n    ffListDestroy(&result.caches[1]);\n    ffListDestroy(&result.caches[2]);\n    ffListDestroy(&result.caches[3]);\n    return success;\n}\n\nvoid ffInitCPUCacheOptions(FFCPUCacheOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n\n    options->compact = false;\n}\n\nvoid ffDestroyCPUCacheOptions(FFCPUCacheOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffCPUCacheModuleInfo = {\n    .name = FF_CPUCACHE_MODULE_NAME,\n    .description = \"Print CPU cache sizes\",\n    .initOptions = (void*) ffInitCPUCacheOptions,\n    .destroyOptions = (void*) ffDestroyCPUCacheOptions,\n    .parseJsonObject = (void*) ffParseCPUCacheJsonObject,\n    .printModule = (void*) ffPrintCPUCache,\n    .generateJsonResult = (void*) ffGenerateCPUCacheJsonResult,\n    .generateJsonConfig = (void*) ffGenerateCPUCacheJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Separate result\", \"result\"},\n        {\"Sum result\", \"sum\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/cpucache/cpucache.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_CPUCACHE_MODULE_NAME \"CPUCache\"\n\nbool ffPrintCPUCache(FFCPUCacheOptions* options);\nvoid ffInitCPUCacheOptions(FFCPUCacheOptions* options);\nvoid ffDestroyCPUCacheOptions(FFCPUCacheOptions* options);\n\nextern FFModuleBaseInfo ffCPUCacheModuleInfo;\n"
  },
  {
    "path": "src/modules/cpucache/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFCPUCacheOptions\n{\n    FFModuleArgs moduleArgs;\n\n    bool compact;\n} FFCPUCacheOptions;\n\nstatic_assert(sizeof(FFCPUCacheOptions) <= FF_OPTION_MAX_SIZE, \"FFCPUCacheOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/cpuusage/cpuusage.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/percent.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/cpuusage/cpuusage.h\"\n#include \"modules/cpuusage/cpuusage.h\"\n\n#define FF_CPUUSAGE_DISPLAY_NAME \"CPU Usage\"\n\nbool ffPrintCPUUsage(FFCPUUsageOptions* options)\n{\n    FF_LIST_AUTO_DESTROY percentages = ffListCreate(sizeof(double));\n    const char* error = ffGetCpuUsageResult(options, &percentages);\n\n    if(error)\n    {\n        ffPrintError(FF_CPUUSAGE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    double maxValue = -999, minValue = 999, sumValue = 0;\n    uint32_t maxIndex = 999, minIndex = 999;\n\n    uint32_t index = 0, valueCount = 0;\n    FF_LIST_FOR_EACH(double, percent, percentages)\n    {\n        if (*percent == *percent)\n        {\n            sumValue += *percent;\n\n            #if _WIN32\n            // Windows may return values greater than 100%, cap them to 100%\n            if (*percent > 100) *percent = 100;\n            #endif\n\n            if (*percent > maxValue)\n            {\n                maxValue = *percent;\n                maxIndex = index;\n            }\n            if (*percent < minValue)\n            {\n                minValue = *percent;\n                minIndex = index;\n            }\n            ++valueCount;\n        }\n        ++index;\n    }\n    double avgValue = sumValue / (double) valueCount;\n    #if _WIN32\n    // See above comment\n    if (avgValue > 100) avgValue = 100;\n    #endif\n\n    FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_CPUUSAGE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n        FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate();\n        if (!options->separate)\n        {\n            if(percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n                ffPercentAppendBar(&str, avgValue, options->percent, &options->moduleArgs);\n            if(percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n            {\n                if(str.length > 0)\n                    ffStrbufAppendC(&str, ' ');\n                ffPercentAppendNum(&str, avgValue, options->percent, str.length > 0, &options->moduleArgs);\n            }\n        }\n        else\n        {\n            FF_LIST_FOR_EACH(double, percent, percentages)\n            {\n                if(str.length > 0)\n                    ffStrbufAppendC(&str, ' ');\n                ffPercentAppendNum(&str, *percent, options->percent, false, &options->moduleArgs);\n            }\n        }\n        ffStrbufPutTo(&str, stdout);\n    }\n    else\n    {\n        FF_STRBUF_AUTO_DESTROY avgNum = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n            ffPercentAppendNum(&avgNum, avgValue, options->percent, false, &options->moduleArgs);\n        FF_STRBUF_AUTO_DESTROY avgBar = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            ffPercentAppendBar(&avgBar, avgValue, options->percent, &options->moduleArgs);\n        FF_STRBUF_AUTO_DESTROY minNum = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n            ffPercentAppendNum(&minNum, minValue, options->percent, false, &options->moduleArgs);\n        FF_STRBUF_AUTO_DESTROY minBar = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            ffPercentAppendBar(&minBar, minValue, options->percent, &options->moduleArgs);\n        FF_STRBUF_AUTO_DESTROY maxNum = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n            ffPercentAppendNum(&maxNum, maxValue, options->percent, false, &options->moduleArgs);\n        FF_STRBUF_AUTO_DESTROY maxBar = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            ffPercentAppendBar(&maxBar, maxValue, options->percent, &options->moduleArgs);\n\n        FF_PRINT_FORMAT_CHECKED(FF_CPUUSAGE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(avgNum, \"avg\"),\n            FF_ARG(maxNum, \"max\"),\n            FF_ARG(maxIndex, \"max-index\"),\n            FF_ARG(minNum, \"min\"),\n            FF_ARG(minIndex, \"min-index\"),\n            FF_ARG(avgBar, \"avg-bar\"),\n            FF_ARG(maxBar, \"max-bar\"),\n            FF_ARG(minBar, \"min-bar\"),\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseCPUUsageJsonObject(FFCPUUsageOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"separate\"))\n        {\n            options->separate = yyjson_get_bool(val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"waitTime\"))\n        {\n            options->waitTime = (uint32_t) yyjson_get_uint(val);\n            continue;\n        }\n\n        if (ffPercentParseJsonObject(key, val, &options->percent))\n            continue;\n\n        ffPrintError(FF_CPUUSAGE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateCPUUsageJsonConfig(FFCPUUsageOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    yyjson_mut_obj_add_bool(doc, module, \"separate\", options->separate);\n\n    ffPercentGenerateJsonConfig(doc, module, options->percent);\n\n    yyjson_mut_obj_add_uint(doc, module, \"waitTime\", options->waitTime);\n}\n\nbool ffGenerateCPUUsageJsonResult(FFCPUUsageOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY percentages = ffListCreate(sizeof(double));\n    const char* error = ffGetCpuUsageResult(options, &percentages);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n    yyjson_mut_val* result = yyjson_mut_obj_add_arr(doc, module, \"result\");\n    FF_LIST_FOR_EACH(double, percent, percentages)\n    {\n        yyjson_mut_arr_add_real(doc, result, *percent);\n    }\n\n    return true;\n}\n\nvoid ffInitCPUUsageOptions(FFCPUUsageOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰓅\");\n    options->separate = false;\n    options->percent = (FFPercentageModuleConfig) { 50, 80, 0 };\n    options->waitTime = 200;\n}\n\nvoid ffDestroyCPUUsageOptions(FFCPUUsageOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffCPUUsageModuleInfo = {\n    .name = FF_CPUUSAGE_MODULE_NAME,\n    .description = \"Print CPU usage. Costs some time to collect data\",\n    .initOptions = (void*) ffInitCPUUsageOptions,\n    .destroyOptions = (void*) ffDestroyCPUUsageOptions,\n    .parseJsonObject = (void*) ffParseCPUUsageJsonObject,\n    .printModule = (void*) ffPrintCPUUsage,\n    .generateJsonResult = (void*) ffGenerateCPUUsageJsonResult,\n    .generateJsonConfig = (void*) ffGenerateCPUUsageJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"CPU usage (percentage num, average)\", \"avg\"},\n        {\"CPU usage (percentage num, maximum)\", \"max\"},\n        {\"CPU core index of maximum usage\", \"max-index\"},\n        {\"CPU usage (percentage num, minimum)\", \"min\"},\n        {\"CPU core index of minimum usage\", \"min-index\"},\n        {\"CPU usage (percentage bar, average)\", \"avg-bar\"},\n        {\"CPU usage (percentage bar, maximum)\", \"max-bar\"},\n        {\"CPU usage (percentage bar, minimum)\", \"min-bar\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/cpuusage/cpuusage.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_CPUUSAGE_MODULE_NAME \"CPUUsage\"\n\nvoid ffPrepareCPUUsage();\n\nbool ffPrintCPUUsage(FFCPUUsageOptions* options);\nvoid ffInitCPUUsageOptions(FFCPUUsageOptions* options);\nvoid ffDestroyCPUUsageOptions(FFCPUUsageOptions* options);\n\nextern FFModuleBaseInfo ffCPUUsageModuleInfo;\n"
  },
  {
    "path": "src/modules/cpuusage/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n#include \"common/percent.h\"\n\ntypedef struct FFCPUUsageOptions\n{\n    FFModuleArgs moduleArgs;\n\n    bool separate;\n    FFPercentageModuleConfig percent;\n    uint32_t waitTime; // in ms\n} FFCPUUsageOptions;\n\nstatic_assert(sizeof(FFCPUUsageOptions) <= FF_OPTION_MAX_SIZE, \"FFCPUUsageOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/cursor/cursor.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/cursor/cursor.h\"\n#include \"modules/cursor/cursor.h\"\n\nbool ffPrintCursor(FFCursorOptions* options)\n{\n    bool success = false;\n    FFCursorResult result;\n    ffStrbufInit(&result.error);\n    ffStrbufInit(&result.theme);\n    ffStrbufInit(&result.size);\n\n    ffDetectCursor(&result);\n\n    if(result.error.length)\n        ffPrintError(FF_CURSOR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", result.error.chars);\n    else\n    {\n        ffStrbufRemoveIgnCaseEndS(&result.theme, \"cursors\");\n        ffStrbufRemoveIgnCaseEndS(&result.theme, \"cursor\");\n        ffStrbufTrimRight(&result.theme, '_');\n        ffStrbufTrimRight(&result.theme, '-');\n        if(result.theme.length == 0)\n            ffStrbufAppendS(&result.theme, \"default\");\n\n        if(options->moduleArgs.outputFormat.length == 0)\n        {\n            ffPrintLogoAndKey(FF_CURSOR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n            ffStrbufWriteTo(&result.theme, stdout);\n\n            if(result.size.length > 0 && !ffStrbufEqualS(&result.size, \"0\"))\n                printf(\" (%spx)\", result.size.chars);\n\n            putchar('\\n');\n        }\n        else\n        {\n            FF_PRINT_FORMAT_CHECKED(FF_CURSOR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n                FF_ARG(result.theme, \"theme\"),\n                FF_ARG(result.size, \"size\"),\n            }));\n        }\n\n        success = true;\n    }\n\n    ffStrbufDestroy(&result.error);\n    ffStrbufDestroy(&result.theme);\n    ffStrbufDestroy(&result.size);\n\n    return success;\n}\n\nvoid ffParseCursorJsonObject(FFCursorOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_CURSOR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateCursorJsonConfig(FFCursorOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateCursorJsonResult(FF_MAYBE_UNUSED FFCursorOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    bool success = false;\n    FFCursorResult result;\n    ffStrbufInit(&result.error);\n    ffStrbufInit(&result.theme);\n    ffStrbufInit(&result.size);\n\n    ffDetectCursor(&result);\n\n    if (result.error.length)\n    {\n        yyjson_mut_obj_add_strbuf(doc, module, \"error\", &result.error);\n    }\n    else\n    {\n        yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n        yyjson_mut_obj_add_strbuf(doc, obj, \"theme\", &result.theme);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"size\", &result.size);\n        success = true;\n    }\n\n    ffStrbufDestroy(&result.error);\n    ffStrbufDestroy(&result.theme);\n    ffStrbufDestroy(&result.size);\n\n    return success;\n}\n\nvoid ffInitCursorOptions(FFCursorOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰆿\");\n}\n\nvoid ffDestroyCursorOptions(FFCursorOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffCursorModuleInfo = {\n    .name = FF_CURSOR_MODULE_NAME,\n    .description = \"Print cursor style name\",\n    .initOptions = (void*) ffInitCursorOptions,\n    .destroyOptions = (void*) ffDestroyCursorOptions,\n    .parseJsonObject = (void*) ffParseCursorJsonObject,\n    .printModule = (void*) ffPrintCursor,\n    .generateJsonResult = (void*) ffGenerateCursorJsonResult,\n    .generateJsonConfig = (void*) ffGenerateCursorJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Cursor theme\", \"theme\"},\n        {\"Cursor size\", \"size\"},\n    })),\n};\n"
  },
  {
    "path": "src/modules/cursor/cursor.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_CURSOR_MODULE_NAME \"Cursor\"\n\nbool ffPrintCursor(FFCursorOptions* options);\nvoid ffInitCursorOptions(FFCursorOptions* options);\nvoid ffDestroyCursorOptions(FFCursorOptions* options);\n\nextern FFModuleBaseInfo ffCursorModuleInfo;\n"
  },
  {
    "path": "src/modules/cursor/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFCursorOptions\n{\n    FFModuleArgs moduleArgs;\n} FFCursorOptions;\n\nstatic_assert(sizeof(FFCursorOptions) <= FF_OPTION_MAX_SIZE, \"FFCursorOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/custom/custom.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/textModifier.h\"\n#include \"common/stringUtils.h\"\n#include \"modules/custom/custom.h\"\n\nbool ffPrintCustom(FFCustomOptions* options)\n{\n    ffPrintFormat(FF_CUSTOM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, 0, ((FFformatarg[]) {}));\n    return true;\n}\n\nvoid ffGenerateCustomJsonConfig(FFCustomOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nvoid ffParseCustomJsonObject(FFCustomOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_CUSTOM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffInitCustomOptions(FFCustomOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n    ffStrbufSetStatic(&options->moduleArgs.key, \" \");\n}\n\nvoid ffDestroyCustomOptions(FFCustomOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffCustomModuleInfo = {\n    .name = FF_CUSTOM_MODULE_NAME,\n    .description = \"Print a custom string, with or without key\",\n    .initOptions = (void*) ffInitCustomOptions,\n    .destroyOptions = (void*) ffDestroyCustomOptions,\n    .parseJsonObject = (void*) ffParseCustomJsonObject,\n    .printModule = (void*) ffPrintCustom,\n    .generateJsonConfig = (void*) ffGenerateCustomJsonConfig,\n};\n"
  },
  {
    "path": "src/modules/custom/custom.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_CUSTOM_MODULE_NAME \"Custom\"\n\nbool ffPrintCustom(FFCustomOptions* options);\nvoid ffInitCustomOptions(FFCustomOptions* options);\nvoid ffDestroyCustomOptions(FFCustomOptions* options);\n\nextern FFModuleBaseInfo ffCustomModuleInfo;\n"
  },
  {
    "path": "src/modules/custom/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFCustomOptions\n{\n    FFModuleArgs moduleArgs;\n} FFCustomOptions;\n\nstatic_assert(sizeof(FFCustomOptions) <= FF_OPTION_MAX_SIZE, \"FFCustomOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/datetime/datetime.c",
    "content": "#include \"common/time.h\"\n#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"modules/datetime/datetime.h\"\n\n#include <time.h>\n\n#pragma GCC diagnostic ignored \"-Wformat\" // warning: unknown conversion type character 'F' in format\n\n#define FF_DATETIME_DISPLAY_NAME \"Date & Time\"\n\ntypedef struct FFDateTimeResult\n{\n    //Examples for 21.02.2022 - 15:18:37\n    uint16_t year; //2022\n    uint8_t yearShort; //22\n    uint8_t month; //2\n    char monthPretty[FASTFETCH_STRBUF_DEFAULT_ALLOC]; //02\n    char monthName[FASTFETCH_STRBUF_DEFAULT_ALLOC]; //February\n    char monthNameShort[FASTFETCH_STRBUF_DEFAULT_ALLOC]; //Feb\n    uint8_t week; //8\n    char weekday[FASTFETCH_STRBUF_DEFAULT_ALLOC]; //Monday\n    char weekdayShort[FASTFETCH_STRBUF_DEFAULT_ALLOC]; //Mon\n    uint16_t dayInYear; //52\n    uint8_t dayInMonth; //21\n    uint8_t dayInWeek; //1\n    char dayPretty[FASTFETCH_STRBUF_DEFAULT_ALLOC]; //01\n    uint8_t hour; //15\n    char hourPretty[FASTFETCH_STRBUF_DEFAULT_ALLOC]; //15\n    uint8_t hour12; //3\n    char hour12Pretty[FASTFETCH_STRBUF_DEFAULT_ALLOC]; //03\n    uint8_t minute; //18\n    char minutePretty[FASTFETCH_STRBUF_DEFAULT_ALLOC]; //18\n    uint8_t second; //37\n    char secondPretty[FASTFETCH_STRBUF_DEFAULT_ALLOC]; //37\n    char offsetFromUtc[FASTFETCH_STRBUF_DEFAULT_ALLOC];\n    char timezoneName[FASTFETCH_STRBUF_DEFAULT_ALLOC];\n} FFDateTimeResult;\n\nstatic void printDateTimeFormat(struct tm* tm, const FFModuleArgs* moduleArgs)\n{\n    FFDateTimeResult result;\n\n    result.year = (uint16_t) (tm->tm_year + 1900);\n    result.yearShort = (uint8_t) (result.year % 100);\n    result.month = (uint8_t) (tm->tm_mon + 1);\n    strftime(result.monthPretty, sizeof(result.monthPretty), \"%m\", tm);\n    strftime(result.monthName, sizeof(result.monthName), \"%B\", tm);\n    strftime(result.monthNameShort, sizeof(result.monthNameShort), \"%b\", tm);\n    result.week = (uint8_t) (tm->tm_yday / 7 + 1);\n    strftime(result.weekday, sizeof(result.weekday), \"%A\", tm);\n    strftime(result.weekdayShort, sizeof(result.weekdayShort), \"%a\", tm);\n    result.dayInYear = (uint8_t) (tm->tm_yday + 1);\n    result.dayInMonth = (uint8_t) tm->tm_mday;\n    result.dayInWeek = tm->tm_wday == 0 ? 7 : (uint8_t) tm->tm_wday;\n    strftime(result.dayPretty, sizeof(result.dayPretty), \"%d\", tm);\n    result.hour = (uint8_t) tm->tm_hour;\n    strftime(result.hourPretty, sizeof(result.hourPretty), \"%H\", tm);\n    result.hour12 = (uint8_t) (result.hour % 12);\n    strftime(result.hour12Pretty, sizeof(result.hour12Pretty), \"%I\", tm);\n    result.minute = (uint8_t) tm->tm_min;\n    strftime(result.minutePretty, sizeof(result.minutePretty), \"%M\", tm);\n    result.second = (uint8_t) tm->tm_sec;\n    strftime(result.secondPretty, sizeof(result.secondPretty), \"%S\", tm);\n    strftime(result.offsetFromUtc, sizeof(result.offsetFromUtc), \"%z\", tm);\n    strftime(result.timezoneName, sizeof(result.timezoneName), \"%Z\", tm);\n\n    FF_PRINT_FORMAT_CHECKED(FF_DATETIME_DISPLAY_NAME, 0, moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n        FF_ARG(result.year, \"year\"), // 1\n        FF_ARG(result.yearShort, \"year-short\"), // 2\n        FF_ARG(result.month, \"month\"), // 3\n        FF_ARG(result.monthPretty, \"month-pretty\"), // 4\n        FF_ARG(result.monthName, \"month-name\"), // 5\n        FF_ARG(result.monthNameShort, \"month-name-short\"), // 6\n        FF_ARG(result.week, \"week\"), // 7\n        FF_ARG(result.weekday, \"weekday\"), // 8\n        FF_ARG(result.weekdayShort, \"weekday-short\"), // 9\n        FF_ARG(result.dayInYear, \"day-in-year\"), // 10\n        FF_ARG(result.dayInMonth, \"day-in-month\"), // 11\n        FF_ARG(result.dayInWeek, \"day-in-week\"), // 12\n        FF_ARG(result.hour, \"hour\"), // 13\n        FF_ARG(result.hourPretty, \"hour-pretty\"), // 14\n        FF_ARG(result.hour12, \"hour-12\"), // 15\n        FF_ARG(result.hour12Pretty, \"hour-12-pretty\"), // 16\n        FF_ARG(result.minute, \"minute\"), // 17\n        FF_ARG(result.minutePretty, \"minute-pretty\"), // 18\n        FF_ARG(result.second, \"second\"), // 19\n        FF_ARG(result.secondPretty, \"second-pretty\"), // 20\n        FF_ARG(result.offsetFromUtc, \"offset-from-utc\"), // 21\n        FF_ARG(result.timezoneName, \"timezone-name\"), // 22\n        FF_ARG(result.dayPretty, \"day-pretty\"), // 23\n    }));\n}\n\nbool ffPrintDateTime(FFDateTimeOptions* options)\n{\n    uint64_t msNow = ffTimeGetNow();\n    time_t sNow = (time_t) (msNow / 1000);\n    struct tm* tm = localtime(&sNow);\n\n    if(options->moduleArgs.outputFormat.length > 0)\n    {\n        printDateTimeFormat(tm, &options->moduleArgs);\n        return true;\n    }\n\n    char buffer[32];\n    if (strftime(buffer, ARRAY_SIZE(buffer), \"%F %T\", tm) == 0) //yyyy-MM-dd HH:mm:ss\n    {\n        ffPrintError(FF_DATETIME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"strftime() failed\");\n        return false;\n    }\n\n    ffPrintLogoAndKey(FF_DATETIME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n    puts(buffer);\n    return true;\n}\n\nvoid ffParseDateTimeJsonObject(FFDateTimeOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_DATETIME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateDateTimeJsonConfig(FFDateTimeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateDateTimeJsonResult(FF_MAYBE_UNUSED FFDateTimeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    yyjson_mut_obj_add_strcpy(doc, module, \"result\", ffTimeToFullStr(ffTimeGetNow()));\n    return true;\n}\n\nvoid ffInitDateTimeOptions(FFDateTimeOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n}\n\nvoid ffDestroyDateTimeOptions(FFDateTimeOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffDateTimeModuleInfo = {\n    .name = FF_DATETIME_MODULE_NAME,\n    .description = \"Print current date and time\",\n    .initOptions = (void*) ffInitDateTimeOptions,\n    .destroyOptions = (void*) ffDestroyDateTimeOptions,\n    .parseJsonObject = (void*) ffParseDateTimeJsonObject,\n    .printModule = (void*) ffPrintDateTime,\n    .generateJsonResult = (void*) ffGenerateDateTimeJsonResult,\n    .generateJsonConfig = (void*) ffGenerateDateTimeJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Year\", \"year\"},\n        {\"Last two digits of year\", \"year-short\"},\n        {\"Month\", \"month\"},\n        {\"Month with leading zero\", \"month-pretty\"},\n        {\"Month name\", \"month-name\"},\n        {\"Month name short\", \"month-name-short\"},\n        {\"Week number on year\", \"week\"},\n        {\"Weekday\", \"weekday\"},\n        {\"Weekday short\", \"weekday-short\"},\n        {\"Day in year\", \"day-in-year\"},\n        {\"Day in month\", \"day-in-month\"},\n        {\"Day in week\", \"day-in-week\"},\n        {\"Hour\", \"hour\"},\n        {\"Hour with leading zero\", \"hour-pretty\"},\n        {\"Hour 12h format\", \"hour-12\"},\n        {\"Hour 12h format with leading zero\", \"hour-12-pretty\"},\n        {\"Minute\", \"minute\"},\n        {\"Minute with leading zero\", \"minute-pretty\"},\n        {\"Second\", \"second\"},\n        {\"Second with leading zero\", \"second-pretty\"},\n        {\"Offset from UTC in the ISO 8601 format\", \"offset-from-utc\"},\n        {\"Locale-dependent timezone name or abbreviation\", \"timezone-name\"},\n        {\"Day in month with leading zero\", \"day-pretty\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/datetime/datetime.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_DATETIME_MODULE_NAME \"DateTime\"\n\nbool ffPrintDateTime(FFDateTimeOptions* options);\nvoid ffInitDateTimeOptions(FFDateTimeOptions* options);\nvoid ffDestroyDateTimeOptions(FFDateTimeOptions* options);\n\nextern FFModuleBaseInfo ffDateTimeModuleInfo;\n"
  },
  {
    "path": "src/modules/datetime/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFDateTimeOptions\n{\n    FFModuleArgs moduleArgs;\n} FFDateTimeOptions;\n\nstatic_assert(sizeof(FFDateTimeOptions) <= FF_OPTION_MAX_SIZE, \"FFDateTimeOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/de/de.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/displayserver/displayserver.h\"\n#include \"detection/de/de.h\"\n#include \"modules/de/de.h\"\n\nbool ffPrintDE(FFDEOptions* options)\n{\n    const FFDisplayServerResult* result = ffConnectDisplayServer();\n\n    if(result->dePrettyName.length == 0)\n    {\n        ffPrintError(FF_DE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No DE found\");\n        return false;\n    }\n\n    FF_STRBUF_AUTO_DESTROY version = ffStrbufCreate();\n    ffDetectDEVersion(&result->dePrettyName, &version, options);\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_DE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n        ffStrbufWriteTo(&result->dePrettyName, stdout);\n\n        if(version.length > 0)\n        {\n            putchar(' ');\n            ffStrbufWriteTo(&version, stdout);\n        }\n\n        putchar('\\n');\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_DE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(result->deProcessName, \"process-name\"),\n            FF_ARG(result->dePrettyName, \"pretty-name\"),\n            FF_ARG(version, \"version\")\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseDEJsonObject(FFDEOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"slowVersionDetection\"))\n        {\n            ffPrintError(FF_DE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Key `slowVersionDetection` is deprecated, it's always true\");\n            continue;\n        }\n\n        ffPrintError(FF_DE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateDEJsonConfig(FFDEOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateDEJsonResult(FF_MAYBE_UNUSED FFDEOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    const FFDisplayServerResult* result = ffConnectDisplayServer();\n\n    if(result->dePrettyName.length == 0)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", \"No DE found\");\n        return false;\n    }\n\n    FF_STRBUF_AUTO_DESTROY version = ffStrbufCreate();\n    ffDetectDEVersion(&result->dePrettyName, &version, options);\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_strbuf(doc, obj, \"processName\", &result->deProcessName);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"prettyName\", &result->dePrettyName);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"version\", &version);\n    return true;\n}\n\nvoid ffInitDEOptions(FFDEOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n}\n\nvoid ffDestroyDEOptions(FFDEOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffDEModuleInfo = {\n    .name = FF_DE_MODULE_NAME,\n    .description = \"Print desktop environment name\",\n    .initOptions = (void*) ffInitDEOptions,\n    .destroyOptions = (void*) ffDestroyDEOptions,\n    .parseJsonObject = (void*) ffParseDEJsonObject,\n    .printModule = (void*) ffPrintDE,\n    .generateJsonResult = (void*) ffGenerateDEJsonResult,\n    .generateJsonConfig = (void*) ffGenerateDEJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"DE process name\", \"process-name\"},\n        {\"DE pretty name\", \"pretty-name\"},\n        {\"DE version\", \"version\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/de/de.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_DE_MODULE_NAME \"DE\"\n\nbool ffPrintDE(FFDEOptions* options);\nvoid ffInitDEOptions(FFDEOptions* options);\nvoid ffDestroyDEOptions(FFDEOptions* options);\n\nextern FFModuleBaseInfo ffDEModuleInfo;\n"
  },
  {
    "path": "src/modules/de/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFDEOptions\n{\n    FFModuleArgs moduleArgs;\n} FFDEOptions;\n\nstatic_assert(sizeof(FFDEOptions) <= FF_OPTION_MAX_SIZE, \"FFDEOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/disk/disk.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/percent.h\"\n#include \"common/size.h\"\n#include \"common/time.h\"\n#include \"detection/disk/disk.h\"\n#include \"modules/disk/disk.h\"\n\n#ifndef _WIN32\n    #include <fnmatch.h>\n#endif\n\n#pragma GCC diagnostic ignored \"-Wsign-conversion\"\n\nstatic void printDisk(FFDiskOptions* options, const FFDisk* disk, uint32_t index)\n{\n    FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();\n\n    if(options->moduleArgs.key.length == 0)\n    {\n        ffStrbufSetF(&key, \"%s (%s)\", FF_DISK_MODULE_NAME, disk->mountpoint.chars);\n    }\n    else\n    {\n        FF_STRBUF_AUTO_DESTROY mountpointLink = ffStrbufCreate();\n        FF_STRBUF_AUTO_DESTROY nameLink = ffStrbufCreate();\n        #ifdef __linux__\n        if (getenv(\"WSL_DISTRO_NAME\") != NULL && getenv(\"WT_SESSION\") != NULL)\n        {\n            if (ffStrbufEqualS(&disk->filesystem, \"9p\") && ffStrbufStartsWithS(&disk->mountpoint, \"/mnt/\"))\n            {\n                ffStrbufSetF(&mountpointLink, \"\\e]8;;file:///%c:/\\e\\\\%s\\e]8;;\\e\\\\\", disk->mountpoint.chars[5], disk->mountpoint.chars);\n                ffStrbufSetF(&nameLink, \"\\e]8;;file:///%c:/\\e\\\\%s\\e]8;;\\e\\\\\", disk->mountpoint.chars[5], disk->name.chars);\n            }\n            else\n            {\n                ffStrbufSetF(&mountpointLink, \"\\e]8;;file:////wsl.localhost/%s%s\\e\\\\%s\\e]8;;\\e\\\\\", getenv(\"WSL_DISTRO_NAME\"), disk->mountpoint.chars, disk->mountpoint.chars);\n                ffStrbufSetF(&nameLink, \"\\e]8;;file:////wsl.localhost/%s%s\\e\\\\%s\\e]8;;\\e\\\\\", getenv(\"WSL_DISTRO_NAME\"), disk->mountpoint.chars, disk->name.chars);\n            }\n        }\n        else\n        #endif\n        {\n            ffStrbufSetF(&mountpointLink, \"\\e]8;;file://%s\\e\\\\%s\\e]8;;\\e\\\\\", disk->mountpoint.chars, disk->mountpoint.chars);\n            ffStrbufSetF(&nameLink, \"\\e]8;;file://%s\\e\\\\%s\\e]8;;\\e\\\\\", disk->mountpoint.chars, disk->name.chars);\n        }\n\n        FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) {\n            FF_ARG(disk->mountpoint, \"mountpoint\"),\n            FF_ARG(disk->name, \"name\"),\n            FF_ARG(disk->mountFrom, \"mount-from\"),\n            FF_ARG(options->moduleArgs.keyIcon, \"icon\"),\n            FF_ARG(index, \"index\"),\n            FF_ARG(disk->filesystem, \"filesystem\"),\n            FF_ARG(mountpointLink, \"mountpoint-link\"),\n            FF_ARG(nameLink, \"name-link\"),\n        }));\n    }\n\n    FF_STRBUF_AUTO_DESTROY usedPretty = ffStrbufCreate();\n    ffSizeAppendNum(disk->bytesUsed, &usedPretty);\n\n    FF_STRBUF_AUTO_DESTROY totalPretty = ffStrbufCreate();\n    ffSizeAppendNum(disk->bytesTotal, &totalPretty);\n\n    double bytesPercentage = disk->bytesTotal > 0 ? (double) disk->bytesUsed / (double) disk->bytesTotal * 100.0 : 0;\n    FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);\n\n        FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate();\n\n        if(disk->bytesTotal > 0)\n        {\n            if(percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            {\n                ffPercentAppendBar(&str, bytesPercentage, options->percent, &options->moduleArgs);\n                ffStrbufAppendC(&str, ' ');\n            }\n\n            if(!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT))\n                ffStrbufAppendF(&str, \"%s / %s \", usedPretty.chars, totalPretty.chars);\n\n            if(percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n            {\n                ffPercentAppendNum(&str, bytesPercentage, options->percent, str.length > 0, &options->moduleArgs);\n                ffStrbufAppendC(&str, ' ');\n            }\n        }\n        else\n            ffStrbufAppendS(&str, \"Unknown \");\n\n        if(!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT))\n        {\n            if(disk->filesystem.length)\n                ffStrbufAppendF(&str, \"- %s \", disk->filesystem.chars);\n\n            ffStrbufAppendC(&str, '[');\n            if(disk->type & FF_DISK_VOLUME_TYPE_EXTERNAL_BIT)\n                ffStrbufAppendS(&str, \"External, \");\n            if(disk->type & FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT)\n                ffStrbufAppendS(&str, \"Subvolume, \");\n            if(disk->type & FF_DISK_VOLUME_TYPE_HIDDEN_BIT)\n                ffStrbufAppendS(&str, \"Hidden, \");\n            if(disk->type & FF_DISK_VOLUME_TYPE_READONLY_BIT)\n                ffStrbufAppendS(&str, \"Read-only, \");\n            if (str.chars[str.length - 1] == '[')\n                ffStrbufSubstrBefore(&str, str.length - 1);\n            else\n            {\n                ffStrbufTrimRight(&str, ' ');\n                str.chars[str.length - 1] = ']';\n            }\n        }\n\n        ffStrbufTrimRight(&str, ' ');\n        ffStrbufPutTo(&str, stdout);\n    }\n    else\n    {\n        FF_STRBUF_AUTO_DESTROY bytesPercentageNum = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n            ffPercentAppendNum(&bytesPercentageNum, bytesPercentage, options->percent, false, &options->moduleArgs);\n        FF_STRBUF_AUTO_DESTROY bytesPercentageBar = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            ffPercentAppendBar(&bytesPercentageBar, bytesPercentage, options->percent, &options->moduleArgs);\n\n        double filesPercentage = disk->filesTotal > 0 ? ((double) disk->filesUsed / (double) disk->filesTotal) * 100.0 : 0;\n        FF_STRBUF_AUTO_DESTROY filesPercentageNum = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n            ffPercentAppendNum(&filesPercentageNum, filesPercentage, options->percent, false, &options->moduleArgs);\n        FF_STRBUF_AUTO_DESTROY filesPercentageBar = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            ffPercentAppendBar(&filesPercentageBar, filesPercentage, options->percent, &options->moduleArgs);\n\n        bool isExternal = !!(disk->type & FF_DISK_VOLUME_TYPE_EXTERNAL_BIT);\n        bool isHidden = !!(disk->type & FF_DISK_VOLUME_TYPE_HIDDEN_BIT);\n        bool isReadOnly = !!(disk->type & FF_DISK_VOLUME_TYPE_READONLY_BIT);\n\n        FF_STRBUF_AUTO_DESTROY freePretty = ffStrbufCreate();\n        ffSizeAppendNum(disk->bytesFree, &freePretty);\n\n        FF_STRBUF_AUTO_DESTROY availPretty = ffStrbufCreate();\n        ffSizeAppendNum(disk->bytesAvailable, &availPretty);\n\n        uint64_t now = ffTimeGetNow();\n        uint64_t duration = now - disk->createTime;\n        uint32_t milliseconds = (uint32_t) (duration % 1000);\n        duration /= 1000;\n        uint32_t seconds = (uint32_t) (duration % 60);\n        duration /= 60;\n        uint32_t minutes = (uint32_t) (duration % 60);\n        duration /= 60;\n        uint32_t hours = (uint32_t) (duration % 24);\n        duration /= 24;\n        uint32_t days = (uint32_t) duration;\n\n        FFTimeGetAgeResult age = ffTimeGetAge(disk->createTime, now);\n        FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) {\n            FF_ARG(usedPretty, \"size-used\"),\n            FF_ARG(totalPretty, \"size-total\"),\n            FF_ARG(bytesPercentageNum, \"size-percentage\"),\n            FF_ARG(disk->filesUsed, \"files-used\"),\n            FF_ARG(disk->filesTotal, \"files-total\"),\n            FF_ARG(filesPercentageNum, \"files-percentage\"),\n            FF_ARG(isExternal, \"is-external\"),\n            FF_ARG(isHidden, \"is-hidden\"),\n            FF_ARG(disk->filesystem, \"filesystem\"),\n            FF_ARG(disk->name, \"name\"),\n            FF_ARG(isReadOnly, \"is-readonly\"),\n            {FF_ARG_TYPE_STRING, ffTimeToShortStr(disk->createTime), \"create-time\"},\n            FF_ARG(bytesPercentageBar, \"size-percentage-bar\"),\n            FF_ARG(filesPercentageBar, \"files-percentage-bar\"),\n            FF_ARG(days, \"days\"),\n            FF_ARG(hours, \"hours\"),\n            FF_ARG(minutes, \"minutes\"),\n            FF_ARG(seconds, \"seconds\"),\n            FF_ARG(milliseconds, \"milliseconds\"),\n            FF_ARG(disk->mountpoint, \"mountpoint\"),\n            FF_ARG(disk->mountFrom, \"mount-from\"),\n            FF_ARG(age.years, \"years\"),\n            FF_ARG(age.daysOfYear, \"days-of-year\"),\n            FF_ARG(age.yearsFraction, \"years-fraction\"),\n            FF_ARG(freePretty, \"size-free\"),\n            FF_ARG(availPretty, \"size-available\"),\n        }));\n    }\n}\n\nbool ffPrintDisk(FFDiskOptions* options)\n{\n    FF_LIST_AUTO_DESTROY disks = ffListCreate(sizeof (FFDisk));\n    const char* error = ffDetectDisks(options, &disks);\n\n    if(error)\n    {\n        ffPrintError(FF_DISK_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if(disks.length == 0)\n    {\n        ffPrintError(FF_DISK_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No disks found\");\n        return false;\n    }\n\n    uint32_t index = 0;\n    FF_LIST_FOR_EACH(FFDisk, disk, disks)\n    {\n        if(__builtin_expect(options->folders.length == 0, 1) && (disk->type & ~options->showTypes))\n            continue;\n\n        printDisk(options, disk, ++index);\n    }\n\n    FF_LIST_FOR_EACH(FFDisk, disk, disks)\n    {\n        ffStrbufDestroy(&disk->mountFrom);\n        ffStrbufDestroy(&disk->mountpoint);\n        ffStrbufDestroy(&disk->filesystem);\n        ffStrbufDestroy(&disk->name);\n    }\n\n    return true;\n}\n\nstatic bool setSeparatedList(FFstrbuf* strbuf, yyjson_val* val, char separator)\n{\n    if (yyjson_is_str(val))\n    {\n        ffStrbufSetJsonVal(strbuf, val);\n        return true;\n    }\n    if (yyjson_is_arr(val))\n    {\n        ffStrbufClear(strbuf);\n        yyjson_val *elem;\n        size_t eidx, emax;\n        yyjson_arr_foreach(val, eidx, emax, elem)\n        {\n            if (yyjson_is_str(elem))\n            {\n                if (strbuf->length > 0)\n                    ffStrbufAppendC(strbuf, separator);\n                ffStrbufAppendJsonVal(strbuf, elem);\n            }\n        }\n        return true;\n    }\n    return false;\n}\n\nvoid ffParseDiskJsonObject(FFDiskOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"folders\"))\n        {\n            setSeparatedList(&options->folders, val, FF_DISK_FOLDER_SEPARATOR);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"hideFolders\"))\n        {\n            setSeparatedList(&options->hideFolders, val, FF_DISK_FOLDER_SEPARATOR);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"hideFS\"))\n        {\n            setSeparatedList(&options->hideFS, val, ':');\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"showRegular\"))\n        {\n            if (yyjson_get_bool(val))\n                options->showTypes |= FF_DISK_VOLUME_TYPE_REGULAR_BIT;\n            else\n                options->showTypes &= ~FF_DISK_VOLUME_TYPE_REGULAR_BIT;\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"showExternal\"))\n        {\n            if (yyjson_get_bool(val))\n                options->showTypes |= FF_DISK_VOLUME_TYPE_EXTERNAL_BIT;\n            else\n                options->showTypes &= ~FF_DISK_VOLUME_TYPE_EXTERNAL_BIT;\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"showHidden\"))\n        {\n            if (yyjson_get_bool(val))\n                options->showTypes |= FF_DISK_VOLUME_TYPE_HIDDEN_BIT;\n            else\n                options->showTypes &= ~FF_DISK_VOLUME_TYPE_HIDDEN_BIT;\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"showSubvolumes\"))\n        {\n            if (yyjson_get_bool(val))\n                options->showTypes |= FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT;\n            else\n                options->showTypes &= ~FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT;\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"showReadOnly\"))\n        {\n            if (yyjson_get_bool(val))\n                options->showTypes |= FF_DISK_VOLUME_TYPE_READONLY_BIT;\n            else\n                options->showTypes &= ~FF_DISK_VOLUME_TYPE_READONLY_BIT;\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"showUnknown\"))\n        {\n            if (yyjson_get_bool(val))\n                options->showTypes |= FF_DISK_VOLUME_TYPE_UNKNOWN_BIT;\n            else\n                options->showTypes &= ~FF_DISK_VOLUME_TYPE_UNKNOWN_BIT;\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"useAvailable\"))\n        {\n            if (yyjson_get_bool(val))\n                options->calcType = FF_DISK_CALC_TYPE_AVAILABLE;\n            else\n                options->calcType = FF_DISK_CALC_TYPE_FREE;\n            continue;\n        }\n\n        if (ffPercentParseJsonObject(key, val, &options->percent))\n            continue;\n\n        ffPrintError(FF_DISK_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateDiskJsonConfig(FFDiskOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    yyjson_mut_obj_add_bool(doc, module, \"showRegular\", !!(options->showTypes & FF_DISK_VOLUME_TYPE_REGULAR_BIT));\n\n    yyjson_mut_obj_add_bool(doc, module, \"showExternal\", !!(options->showTypes & FF_DISK_VOLUME_TYPE_EXTERNAL_BIT));\n\n    yyjson_mut_obj_add_bool(doc, module, \"showHidden\", !!(options->showTypes & FF_DISK_VOLUME_TYPE_HIDDEN_BIT));\n\n    yyjson_mut_obj_add_bool(doc, module, \"showSubvolumes\", !!(options->showTypes & FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT));\n\n    yyjson_mut_obj_add_bool(doc, module, \"showReadOnly\", !!(options->showTypes & FF_DISK_VOLUME_TYPE_READONLY_BIT));\n\n    yyjson_mut_obj_add_bool(doc, module, \"showUnknown\", !!(options->showTypes & FF_DISK_VOLUME_TYPE_UNKNOWN_BIT));\n\n    yyjson_mut_obj_add_strbuf(doc, module, \"folders\", &options->folders);\n\n    yyjson_mut_obj_add_strbuf(doc, module, \"hideFolders\", &options->hideFolders);\n\n    yyjson_mut_obj_add_strbuf(doc, module, \"hideFS\", &options->hideFS);\n\n    yyjson_mut_obj_add_bool(doc, module, \"useAvailable\", options->calcType == FF_DISK_CALC_TYPE_AVAILABLE);\n\n    ffPercentGenerateJsonConfig(doc, module, options->percent);\n}\n\nbool ffGenerateDiskJsonResult(FFDiskOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY disks = ffListCreate(sizeof (FFDisk));\n    const char* error = ffDetectDisks(options, &disks);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n\n    FF_LIST_FOR_EACH(FFDisk, item, disks)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n\n        yyjson_mut_val* bytes = yyjson_mut_obj_add_obj(doc, obj, \"bytes\");\n        yyjson_mut_obj_add_uint(doc, bytes, \"available\", item->bytesAvailable);\n        yyjson_mut_obj_add_uint(doc, bytes, \"free\", item->bytesFree);\n        yyjson_mut_obj_add_uint(doc, bytes, \"total\", item->bytesTotal);\n        yyjson_mut_obj_add_uint(doc, bytes, \"used\", item->bytesUsed);\n\n        yyjson_mut_val* files = yyjson_mut_obj_add_obj(doc, obj, \"files\");\n        if (item->filesTotal == 0 && item->filesUsed == 0)\n        {\n            yyjson_mut_obj_add_null(doc, files, \"total\");\n            yyjson_mut_obj_add_null(doc, files, \"used\");\n        }\n        else\n        {\n            yyjson_mut_obj_add_uint(doc, files, \"total\", item->filesTotal);\n            yyjson_mut_obj_add_uint(doc, files, \"used\", item->filesUsed);\n        }\n\n        yyjson_mut_obj_add_strbuf(doc, obj, \"filesystem\", &item->filesystem);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"mountpoint\", &item->mountpoint);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"mountFrom\", &item->mountFrom);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &item->name);\n        yyjson_mut_val* typeArr = yyjson_mut_obj_add_arr(doc, obj, \"volumeType\");\n        if(item->type & FF_DISK_VOLUME_TYPE_REGULAR_BIT)\n            yyjson_mut_arr_add_str(doc, typeArr, \"Regular\");\n        if(item->type & FF_DISK_VOLUME_TYPE_EXTERNAL_BIT)\n            yyjson_mut_arr_add_str(doc, typeArr, \"External\");\n        if(item->type & FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT)\n            yyjson_mut_arr_add_str(doc, typeArr, \"Subvolume\");\n        if(item->type & FF_DISK_VOLUME_TYPE_HIDDEN_BIT)\n            yyjson_mut_arr_add_str(doc, typeArr, \"Hidden\");\n        if(item->type & FF_DISK_VOLUME_TYPE_READONLY_BIT)\n            yyjson_mut_arr_add_str(doc, typeArr, \"Read-only\");\n        if(item->type & FF_DISK_VOLUME_TYPE_UNKNOWN_BIT)\n            yyjson_mut_arr_add_str(doc, typeArr, \"Unknown\");\n\n        const char* pstr = ffTimeToFullStr(item->createTime);\n        if (*pstr)\n            yyjson_mut_obj_add_strcpy(doc, obj, \"createTime\", pstr);\n        else\n            yyjson_mut_obj_add_null(doc, obj, \"createTime\");\n    }\n\n    FF_LIST_FOR_EACH(FFDisk, item, disks)\n    {\n        ffStrbufDestroy(&item->mountpoint);\n        ffStrbufDestroy(&item->mountFrom);\n        ffStrbufDestroy(&item->filesystem);\n        ffStrbufDestroy(&item->name);\n    }\n\n    return true;\n}\n\nvoid ffInitDiskOptions(FFDiskOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n\n    ffStrbufInit(&options->folders);\n    #if _WIN32 || __APPLE__ || __ANDROID__\n    ffStrbufInit(&options->hideFolders);\n    #else\n    ffStrbufInitS(&options->hideFolders, \"/efi:/boot:/boot/*\");\n    #endif\n    ffStrbufInit(&options->hideFS);\n    options->showTypes = FF_DISK_VOLUME_TYPE_REGULAR_BIT | FF_DISK_VOLUME_TYPE_EXTERNAL_BIT | FF_DISK_VOLUME_TYPE_READONLY_BIT;\n    options->calcType = FF_DISK_CALC_TYPE_FREE;\n    options->percent = (FFPercentageModuleConfig) { 50, 80, 0 };\n}\n\nvoid ffDestroyDiskOptions(FFDiskOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n    ffStrbufDestroy(&options->folders);\n    ffStrbufDestroy(&options->hideFolders);\n    ffStrbufDestroy(&options->hideFS);\n}\n\nFFModuleBaseInfo ffDiskModuleInfo = {\n    .name = FF_DISK_MODULE_NAME,\n    .description = \"Print partitions, space usage, file system, etc\",\n    .initOptions = (void*) ffInitDiskOptions,\n    .destroyOptions = (void*) ffDestroyDiskOptions,\n    .parseJsonObject = (void*) ffParseDiskJsonObject,\n    .printModule = (void*) ffPrintDisk,\n    .generateJsonResult = (void*) ffGenerateDiskJsonResult,\n    .generateJsonConfig = (void*) ffGenerateDiskJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Size used\", \"size-used\"},\n        {\"Size total\", \"size-total\"},\n        {\"Size percentage num\", \"size-percentage\"},\n        {\"Files used\", \"files-used\"},\n        {\"Files total\", \"files-total\"},\n        {\"Files percentage num\", \"files-percentage\"},\n        {\"True if external volume\", \"is-external\"},\n        {\"True if hidden volume\", \"is-hidden\"},\n        {\"Filesystem\", \"filesystem\"},\n        {\"Label / name\", \"name\"},\n        {\"True if read-only\", \"is-readonly\"},\n        {\"Create time in local timezone\", \"create-time\"},\n        {\"Size percentage bar\", \"size-percentage-bar\"},\n        {\"Files percentage bar\", \"files-percentage-bar\"},\n        {\"Days after creation\", \"days\"},\n        {\"Hours after creation\", \"hours\"},\n        {\"Minutes after creation\", \"minutes\"},\n        {\"Seconds after creation\", \"seconds\"},\n        {\"Milliseconds after creation\", \"milliseconds\"},\n        {\"Mount point / drive letter\", \"mountpoint\"},\n        {\"Mount from (device path)\", \"mount-from\"},\n        {\"Years integer after creation\", \"years\"},\n        {\"Days of year after creation\", \"days-of-year\"},\n        {\"Years fraction after creation\", \"years-fraction\"},\n        {\"Size free\", \"size-free\"},\n        {\"Size available\", \"size-available\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/disk/disk.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_DISK_MODULE_NAME \"Disk\"\n\nbool ffPrintDisk(FFDiskOptions* options);\nvoid ffInitDiskOptions(FFDiskOptions* options);\nvoid ffDestroyDiskOptions(FFDiskOptions* options);\n\nextern FFModuleBaseInfo ffDiskModuleInfo;\n"
  },
  {
    "path": "src/modules/disk/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n#include \"common/percent.h\"\n\ntypedef enum __attribute__((__packed__)) FFDiskVolumeType\n{\n    FF_DISK_VOLUME_TYPE_NONE = 0,\n    FF_DISK_VOLUME_TYPE_REGULAR_BIT = 1 << 0,\n    FF_DISK_VOLUME_TYPE_HIDDEN_BIT = 1 << 1,\n    FF_DISK_VOLUME_TYPE_EXTERNAL_BIT = 1 << 2,\n    FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT = 1 << 3,\n    FF_DISK_VOLUME_TYPE_UNKNOWN_BIT = 1 << 4,\n    FF_DISK_VOLUME_TYPE_READONLY_BIT = 1 << 5,\n    FF_DISK_VOLUME_TYPE_FORCE_UNSIGNED = UINT8_MAX,\n} FFDiskVolumeType;\n\ntypedef enum __attribute__((__packed__)) FFDiskCalcType\n{\n    FF_DISK_CALC_TYPE_FREE,\n    FF_DISK_CALC_TYPE_AVAILABLE,\n} FFDiskCalcType;\n\ntypedef struct FFDiskOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFstrbuf folders;\n    FFstrbuf hideFolders;\n    FFstrbuf hideFS;\n    FFDiskVolumeType showTypes;\n    FFDiskCalcType calcType;\n    FFPercentageModuleConfig percent;\n} FFDiskOptions;\n\nstatic_assert(sizeof(FFDiskOptions) <= FF_OPTION_MAX_SIZE, \"FFDiskOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/diskio/diskio.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/size.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/diskio/diskio.h\"\n#include \"modules/diskio/diskio.h\"\n\n#define FF_DISKIO_DISPLAY_NAME \"Disk IO\"\n\nstatic int sortDevices(const FFDiskIOResult* left, const FFDiskIOResult* right)\n{\n    return ffStrbufComp(&left->name, &right->name);\n}\n\nstatic void formatKey(const FFDiskIOOptions* options, FFDiskIOResult* dev, uint32_t index, FFstrbuf* key)\n{\n    if(options->moduleArgs.key.length == 0)\n    {\n        ffStrbufSetF(key, FF_DISKIO_DISPLAY_NAME \" (%s)\", dev->name.length ? dev->name.chars : dev->devPath.chars);\n    }\n    else\n    {\n        ffStrbufClear(key);\n        FF_PARSE_FORMAT_STRING_CHECKED(key, &options->moduleArgs.key, ((FFformatarg[]){\n            FF_ARG(index, \"index\"),\n            FF_ARG(dev->name, \"name\"),\n            FF_ARG(dev->devPath, \"dev-path\"),\n            FF_ARG(options->moduleArgs.keyIcon, \"icon\"),\n        }));\n    }\n}\n\nbool ffPrintDiskIO(FFDiskIOOptions* options)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFDiskIOResult));\n    const char* error = ffDetectDiskIO(&result, options);\n\n    if(error)\n    {\n        ffPrintError(FF_DISKIO_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, \"%s\", error);\n        return false;\n    }\n\n    ffListSort(&result, (const void*) sortDevices);\n\n    uint32_t index = 0;\n    FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY buffer2 = ffStrbufCreate();\n\n    FF_LIST_FOR_EACH(FFDiskIOResult, dev, result)\n    {\n        formatKey(options, dev, result.length == 1 ? 0 : index + 1, &key);\n        ffStrbufClear(&buffer);\n\n        if(options->moduleArgs.outputFormat.length == 0)\n        {\n            ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);\n\n            ffSizeAppendNum(dev->bytesRead, &buffer);\n            if (!options->detectTotal) ffStrbufAppendS(&buffer, \"/s\");\n            ffStrbufAppendS(&buffer, \" (R) - \");\n\n            ffSizeAppendNum(dev->bytesWritten, &buffer);\n            if (!options->detectTotal) ffStrbufAppendS(&buffer, \"/s\");\n            ffStrbufAppendS(&buffer, \" (W)\");\n            ffStrbufPutTo(&buffer, stdout);\n        }\n        else\n        {\n            ffSizeAppendNum(dev->bytesRead, &buffer);\n            if (!options->detectTotal) ffStrbufAppendS(&buffer, \"/s\");\n            ffStrbufClear(&buffer2);\n            ffSizeAppendNum(dev->bytesWritten, &buffer2);\n            if (!options->detectTotal) ffStrbufAppendS(&buffer2, \"/s\");\n\n            FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]){\n                FF_ARG(buffer, \"size-read\"),\n                FF_ARG(buffer2, \"size-written\"),\n                FF_ARG(dev->name, \"name\"),\n                FF_ARG(dev->devPath, \"dev-path\"),\n                FF_ARG(dev->bytesRead, \"bytes-read\"),\n                FF_ARG(dev->bytesWritten, \"bytes-written\"),\n                FF_ARG(dev->readCount, \"read-count\"),\n                FF_ARG(dev->writeCount, \"write-count\"),\n            }));\n        }\n        ++index;\n    }\n\n    FF_LIST_FOR_EACH(FFDiskIOResult, dev, result)\n    {\n        ffStrbufDestroy(&dev->name);\n        ffStrbufDestroy(&dev->devPath);\n    }\n\n    return true;\n}\n\nvoid ffParseDiskIOJsonObject(FFDiskIOOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"namePrefix\"))\n        {\n            ffStrbufSetJsonVal(&options->namePrefix, val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"detectTotal\"))\n        {\n            options->detectTotal = yyjson_get_bool(val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"waitTime\"))\n        {\n            options->waitTime = (uint32_t) yyjson_get_uint(val);\n            continue;\n        }\n\n        ffPrintError(FF_DISKIO_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateDiskIOJsonConfig(FFDiskIOOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    yyjson_mut_obj_add_strbuf(doc, module, \"namePrefix\", &options->namePrefix);\n\n    yyjson_mut_obj_add_bool(doc, module, \"detectTotal\", options->detectTotal);\n\n    yyjson_mut_obj_add_uint(doc, module, \"waitTime\", options->waitTime);\n}\n\nbool ffGenerateDiskIOJsonResult(FFDiskIOOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFDiskIOResult));\n    const char* error = ffDetectDiskIO(&result, options);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n    FF_LIST_FOR_EACH(FFDiskIOResult, dev, result)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &dev->name);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"devPath\", &dev->devPath);\n        yyjson_mut_obj_add_uint(doc, obj, \"bytesRead\", dev->bytesRead);\n        yyjson_mut_obj_add_uint(doc, obj, \"bytesWritten\", dev->bytesWritten);\n        yyjson_mut_obj_add_uint(doc, obj, \"readCount\", dev->readCount);\n        yyjson_mut_obj_add_uint(doc, obj, \"writeCount\", dev->writeCount);\n    }\n\n    FF_LIST_FOR_EACH(FFDiskIOResult, dev, result)\n    {\n        ffStrbufDestroy(&dev->name);\n        ffStrbufDestroy(&dev->devPath);\n    }\n\n    return true;\n}\n\nvoid ffInitDiskIOOptions(FFDiskIOOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰓅\");\n\n    ffStrbufInit(&options->namePrefix);\n    options->detectTotal = false;\n    options->waitTime = 1000;\n}\n\nvoid ffDestroyDiskIOOptions(FFDiskIOOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n    ffStrbufDestroy(&options->namePrefix);\n}\n\nFFModuleBaseInfo ffDiskIOModuleInfo = {\n    .name = FF_DISKIO_MODULE_NAME,\n    .description = \"Print physical disk I/O throughput\",\n    .initOptions = (void*) ffInitDiskIOOptions,\n    .destroyOptions = (void*) ffDestroyDiskIOOptions,\n    .parseJsonObject = (void*) ffParseDiskIOJsonObject,\n    .printModule = (void*) ffPrintDiskIO,\n    .generateJsonResult = (void*) ffGenerateDiskIOJsonResult,\n    .generateJsonConfig = (void*) ffGenerateDiskIOJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Size of data read [per second] (formatted)\", \"size-read\"},\n        {\"Size of data written [per second] (formatted)\", \"size-written\"},\n        {\"Device name\", \"name\"},\n        {\"Device raw file path\", \"dev-path\"},\n        {\"Size of data read [per second] (in bytes)\", \"bytes-read\"},\n        {\"Size of data written [per second] (in bytes)\", \"bytes-written\"},\n        {\"Number of reads\", \"read-count\"},\n        {\"Number of writes\", \"write-count\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/diskio/diskio.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_DISKIO_MODULE_NAME \"DiskIO\"\n\nvoid ffPrepareDiskIO(FFDiskIOOptions* options);\n\nbool ffPrintDiskIO(FFDiskIOOptions* options);\nvoid ffInitDiskIOOptions(FFDiskIOOptions* options);\nvoid ffDestroyDiskIOOptions(FFDiskIOOptions* options);\n\nextern FFModuleBaseInfo ffDiskIOModuleInfo;\n"
  },
  {
    "path": "src/modules/diskio/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFDiskIOOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFstrbuf namePrefix;\n    uint32_t waitTime;\n    bool detectTotal;\n} FFDiskIOOptions;\n\nstatic_assert(sizeof(FFDiskIOOptions) <= FF_OPTION_MAX_SIZE, \"FFDiskIOOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/display/display.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/size.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/displayserver/displayserver.h\"\n#include \"modules/display/display.h\"\n\n#include <math.h>\n\nstatic int sortByNameAsc(FFDisplayResult* a, FFDisplayResult* b)\n{\n    return ffStrbufComp(&a->name, &b->name);\n}\n\nstatic int sortByNameDesc(FFDisplayResult* a, FFDisplayResult* b)\n{\n    return -ffStrbufComp(&a->name, &b->name);\n}\n\nbool ffPrintDisplay(FFDisplayOptions* options)\n{\n    const FFDisplayServerResult* dsResult = ffConnectDisplayServer();\n\n    if(dsResult->displays.length == 0)\n    {\n        ffPrintError(FF_DISPLAY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Couldn't detect display\");\n        return false;\n    }\n\n    if (options->order != FF_DISPLAY_ORDER_NONE)\n    {\n        ffListSort((FFlist*) &dsResult->displays, (void*) (options->order == FF_DISPLAY_ORDER_ASC ? sortByNameAsc : sortByNameDesc));\n    }\n\n    if (options->compactType != FF_DISPLAY_COMPACT_TYPE_NONE)\n    {\n        ffPrintLogoAndKey(FF_DISPLAY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n        FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n        FF_LIST_FOR_EACH(FFDisplayResult, result, dsResult->displays)\n        {\n            if (options->compactType & FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT)\n            {\n                ffStrbufAppendF(&buffer, \"%ix%i\", result->width, result->height);\n            }\n            else\n            {\n                uint32_t scaledWidth = result->width * 96 / result->dpi;\n                uint32_t scaledHeight = result->height * 96 / result->dpi;\n                ffStrbufAppendF(&buffer, \"%ix%i\", scaledWidth, scaledHeight);\n            }\n\n            if (options->compactType & FF_DISPLAY_COMPACT_TYPE_REFRESH_RATE_BIT)\n            {\n                if (result->refreshRate > 0)\n                {\n                    const char* space = instance.config.display.freqSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_ALWAYS ? \" \" : \"\";\n                    if (options->preciseRefreshRate)\n                        ffStrbufAppendF(&buffer, \" @ %g%sHz\", result->refreshRate, space);\n                    else\n                        ffStrbufAppendF(&buffer, \" @ %i%sHz\", (uint32_t) (result->refreshRate + 0.5), space);\n                }\n                ffStrbufAppendS(&buffer, \", \");\n            }\n            else\n            {\n                ffStrbufAppendC(&buffer, ' ');\n            }\n        }\n        ffStrbufTrimRight(&buffer, ' ');\n        ffStrbufTrimRight(&buffer, ',');\n        ffStrbufPutTo(&buffer, stdout);\n        return true;\n    }\n\n    FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();\n\n    for(uint32_t i = 0; i < dsResult->displays.length; i++)\n    {\n        FFDisplayResult* result = FF_LIST_GET(FFDisplayResult, dsResult->displays, i);\n        uint32_t moduleIndex = dsResult->displays.length == 1 ? 0 : i + 1;\n        const char* displayType = result->type == FF_DISPLAY_TYPE_UNKNOWN ? NULL : result->type == FF_DISPLAY_TYPE_BUILTIN ? \"Built-in\" : \"External\";\n\n        ffStrbufClear(&key);\n        if(options->moduleArgs.key.length == 0)\n        {\n            if (result->name.length)\n                ffStrbufAppendF(&key, \"%s (%s)\", FF_DISPLAY_MODULE_NAME, result->name.chars);\n            else if (moduleIndex > 0)\n                ffStrbufAppendF(&key, \"%s (%d)\", FF_DISPLAY_MODULE_NAME, moduleIndex);\n            else\n                ffStrbufAppendS(&key, FF_DISPLAY_MODULE_NAME);\n        }\n        else\n        {\n            FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) {\n                FF_ARG(moduleIndex, \"index\"),\n                FF_ARG(result->name, \"name\"),\n                FF_ARG(displayType, \"type\"),\n                FF_ARG(options->moduleArgs.keyIcon, \"icon\"),\n            }));\n        }\n\n        FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n        double inch = sqrt(result->physicalWidth * result->physicalWidth + result->physicalHeight * result->physicalHeight) / 25.4;\n        uint32_t scaledWidth = result->width * 96 / result->dpi;\n        uint32_t scaledHeight = result->height * 96 / result->dpi;\n        double scaleFactor = (double) result->dpi / 96.;\n\n        if(options->moduleArgs.outputFormat.length == 0)\n        {\n            ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);\n\n            ffStrbufAppendF(&buffer, \"%ix%i\", result->width, result->height);\n\n            if(result->dpi != 96)\n            {\n                ffStrbufAppendS(&buffer, \" @ \");\n                ffStrbufAppendDouble(&buffer, scaleFactor, instance.config.display.fractionNdigits, instance.config.display.fractionTrailingZeros == FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS);\n                ffStrbufAppendC(&buffer, 'x');\n            }\n\n            if (inch > 1)\n                ffStrbufAppendF(&buffer, \" in %i\\\"\", (uint32_t) (inch + 0.5));\n\n            if(result->refreshRate > 0)\n            {\n                ffStrbufAppendS(&buffer, \", \");\n                if(options->preciseRefreshRate)\n                    ffStrbufAppendDouble(&buffer, result->refreshRate, 3, false);\n                else\n                    ffStrbufAppendSInt(&buffer, (int) (result->refreshRate + 0.5));\n                ffStrbufAppendS(&buffer, instance.config.display.freqSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_NEVER ? \"Hz\" : \" Hz\");\n            }\n\n            bool flag = false;\n            if (result->type != FF_DISPLAY_TYPE_UNKNOWN)\n            {\n                ffStrbufAppendS(&buffer, result->type == FF_DISPLAY_TYPE_BUILTIN ? \" [Built-in\" : \" [External\");\n                flag = true;\n            }\n\n            if (result->hdrStatus == FF_DISPLAY_HDR_STATUS_ENABLED)\n            {\n                ffStrbufAppendS(&buffer, flag ? \", HDR\" : \" [HDR\");\n                flag = true;\n            }\n\n            if (flag)\n                ffStrbufAppendS(&buffer, \"]\");\n\n            if(moduleIndex > 0 && result->primary)\n                ffStrbufAppendS(&buffer, \" *\");\n\n            ffStrbufPutTo(&buffer, stdout);\n            ffStrbufClear(&buffer);\n        }\n        else\n        {\n            double ppi = inch == 0 ? 0 : sqrt(result->width * result->width + result->height * result->height) / inch;\n            bool hdrEnabled = result->hdrStatus == FF_DISPLAY_HDR_STATUS_ENABLED;\n            bool hdrCompatible = result->hdrStatus == FF_DISPLAY_HDR_STATUS_SUPPORTED || result->hdrStatus == FF_DISPLAY_HDR_STATUS_ENABLED;\n            uint32_t iInch = (uint32_t) (inch + 0.5), iPpi = (uint32_t) (ppi + 0.5);\n\n            char refreshRate[16];\n            if(result->refreshRate > 0)\n            {\n                if(options->preciseRefreshRate)\n                    snprintf(refreshRate, ARRAY_SIZE(refreshRate), \"%g\", ((int) (result->refreshRate * 1000 + 0.5)) / 1000.0);\n                else\n                    snprintf(refreshRate, ARRAY_SIZE(refreshRate), \"%i\", (uint32_t) (result->refreshRate + 0.5));\n            }\n            else\n                refreshRate[0] = 0;\n\n            char preferredRefreshRate[16];\n            if(result->preferredRefreshRate > 0)\n            {\n                if(options->preciseRefreshRate)\n                    snprintf(preferredRefreshRate, ARRAY_SIZE(preferredRefreshRate), \"%g\", ((int) (result->preferredRefreshRate * 1000 + 0.5)) / 1000.0);\n                else\n                    snprintf(preferredRefreshRate, ARRAY_SIZE(preferredRefreshRate), \"%i\", (uint32_t) (result->preferredRefreshRate + 0.5));\n            }\n            else\n                preferredRefreshRate[0] = 0;\n\n            char buf[32];\n            if (result->serial)\n            {\n                const uint8_t* nums = (uint8_t*) &result->serial;\n                snprintf(buf, ARRAY_SIZE(buf), \"%2X-%2X-%2X-%2X\", nums[0], nums[1], nums[2], nums[3]);\n            }\n            else\n                buf[0] = '\\0';\n\n            FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) {\n                FF_ARG(result->width, \"width\"),\n                FF_ARG(result->height, \"height\"),\n                FF_ARG(refreshRate, \"refresh-rate\"),\n                FF_ARG(scaledWidth, \"scaled-width\"),\n                FF_ARG(scaledHeight, \"scaled-height\"),\n                FF_ARG(result->name, \"name\"),\n                FF_ARG(displayType, \"type\"),\n                FF_ARG(result->rotation, \"rotation\"),\n                FF_ARG(result->primary, \"is-primary\"),\n                FF_ARG(result->physicalWidth, \"physical-width\"),\n                FF_ARG(result->physicalHeight, \"physical-height\"),\n                FF_ARG(iInch, \"inch\"),\n                FF_ARG(iPpi, \"ppi\"),\n                FF_ARG(result->bitDepth, \"bit-depth\"),\n                FF_ARG(hdrEnabled, \"hdr-enabled\"),\n                FF_ARG(result->manufactureYear, \"manufacture-year\"),\n                FF_ARG(result->manufactureWeek, \"manufacture-week\"),\n                FF_ARG(buf, \"serial\"),\n                FF_ARG(result->platformApi, \"platform-api\"),\n                FF_ARG(hdrCompatible, \"hdr-compatible\"),\n                FF_ARG(scaleFactor, \"scale-factor\"),\n                FF_ARG(result->preferredWidth, \"preferred-width\"),\n                FF_ARG(result->preferredHeight, \"preferred-height\"),\n                FF_ARG(preferredRefreshRate, \"preferred-refresh-rate\"),\n                FF_ARG(result->dpi, \"dpi\"),\n            }));\n        }\n    }\n\n    return true;\n}\n\nvoid ffParseDisplayJsonObject(FFDisplayOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"compactType\"))\n        {\n            if (yyjson_is_null(val))\n                options->compactType = FF_DISPLAY_COMPACT_TYPE_NONE;\n            else\n            {\n                int value;\n                const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {\n                    { \"none\", FF_DISPLAY_COMPACT_TYPE_NONE },\n                    { \"original\", FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT },\n                    { \"scaled\", FF_DISPLAY_COMPACT_TYPE_SCALED_BIT },\n                    { \"original-with-refresh-rate\", FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT | FF_DISPLAY_COMPACT_TYPE_REFRESH_RATE_BIT },\n                    { \"scaled-with-refresh-rate\", FF_DISPLAY_COMPACT_TYPE_SCALED_BIT | FF_DISPLAY_COMPACT_TYPE_REFRESH_RATE_BIT },\n                    {},\n                });\n                if (error)\n                    ffPrintError(FF_DISPLAY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Invalid %s value: %s\", unsafe_yyjson_get_str(key), error);\n                else\n                    options->compactType = (FFDisplayCompactType) value;\n            }\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"preciseRefreshRate\"))\n        {\n            options->preciseRefreshRate = yyjson_get_bool(val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"order\"))\n        {\n            if (yyjson_is_null(val))\n                options->order = FF_DISPLAY_ORDER_NONE;\n            else\n            {\n                int value;\n                const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {\n                    { \"asc\", FF_DISPLAY_ORDER_ASC },\n                    { \"desc\", FF_DISPLAY_ORDER_DESC },\n                    { \"none\", FF_DISPLAY_ORDER_NONE },\n                    {},\n                });\n                if (error)\n                    ffPrintError(FF_DISPLAY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Invalid %s value: %s\", unsafe_yyjson_get_str(key), error);\n                else\n                    options->order = (FFDisplayOrder) value;\n            }\n            continue;\n        }\n\n        ffPrintError(FF_DISPLAY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateDisplayJsonConfig(FFDisplayOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    switch ((int) options->compactType)\n    {\n        case FF_DISPLAY_COMPACT_TYPE_NONE:\n            yyjson_mut_obj_add_str(doc, module, \"compactType\", \"none\");\n            break;\n        case FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT:\n            yyjson_mut_obj_add_str(doc, module, \"compactType\", \"original\");\n            break;\n        case FF_DISPLAY_COMPACT_TYPE_SCALED_BIT:\n            yyjson_mut_obj_add_str(doc, module, \"compactType\", \"scaled\");\n            break;\n        case FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT | FF_DISPLAY_COMPACT_TYPE_REFRESH_RATE_BIT:\n            yyjson_mut_obj_add_str(doc, module, \"compactType\", \"original-with-refresh-rate\");\n            break;\n        case FF_DISPLAY_COMPACT_TYPE_SCALED_BIT | FF_DISPLAY_COMPACT_TYPE_REFRESH_RATE_BIT:\n            yyjson_mut_obj_add_str(doc, module, \"compactType\", \"scaled-with-refresh-rate\");\n            break;\n    }\n\n    yyjson_mut_obj_add_bool(doc, module, \"preciseRefreshRate\", options->preciseRefreshRate);\n\n    switch (options->order)\n    {\n        case FF_DISPLAY_ORDER_NONE:\n            yyjson_mut_obj_add_null(doc, module, \"order\");\n            break;\n        case FF_DISPLAY_ORDER_ASC:\n            yyjson_mut_obj_add_str(doc, module, \"order\", \"asc\");\n            break;\n        case FF_DISPLAY_ORDER_DESC:\n            yyjson_mut_obj_add_str(doc, module, \"order\", \"desc\");\n            break;\n    }\n}\n\nbool ffGenerateDisplayJsonResult(FF_MAYBE_UNUSED FFDisplayOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    const FFDisplayServerResult* dsResult = ffConnectDisplayServer();\n\n    if(dsResult->displays.length == 0)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", \"Couldn't detect display\");\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n    FF_LIST_FOR_EACH(FFDisplayResult, item, dsResult->displays)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n        yyjson_mut_obj_add_uint(doc, obj, \"id\", item->id);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &item->name);\n        yyjson_mut_obj_add_bool(doc, obj, \"primary\", item->primary);\n\n        yyjson_mut_val* output = yyjson_mut_obj_add_obj(doc, obj, \"output\");\n        yyjson_mut_obj_add_uint(doc, output, \"width\", item->width);\n        yyjson_mut_obj_add_uint(doc, output, \"height\", item->height);\n        yyjson_mut_obj_add_real(doc, output, \"refreshRate\", item->refreshRate);\n\n        if (item->drrStatus == FF_DISPLAY_DRR_STATUS_UNKNOWN)\n            yyjson_mut_obj_add_null(doc, output, \"drrStatus\");\n        else switch (item->drrStatus)\n        {\n            case FF_DISPLAY_DRR_STATUS_DISABLED:\n                yyjson_mut_obj_add_str(doc, output, \"drrStatus\", \"Disabled\");\n                break;\n            case FF_DISPLAY_DRR_STATUS_ENABLED:\n                yyjson_mut_obj_add_str(doc, output, \"drrStatus\", \"Enabled\");\n                break;\n            default:\n                yyjson_mut_obj_add_str(doc, output, \"drrStatus\", \"Unknown\");\n                break;\n        }\n        yyjson_mut_obj_add_uint(doc, output, \"dpi\", item->dpi);\n\n        uint32_t scaledWidth = item->width * 96 / item->dpi;\n        uint32_t scaledHeight = item->height * 96 / item->dpi;\n        yyjson_mut_val* scaled = yyjson_mut_obj_add_obj(doc, obj, \"scaled\");\n        yyjson_mut_obj_add_uint(doc, scaled, \"width\", scaledWidth);\n        yyjson_mut_obj_add_uint(doc, scaled, \"height\", scaledHeight);\n\n        yyjson_mut_val* preferred = yyjson_mut_obj_add_obj(doc, obj, \"preferred\");\n        yyjson_mut_obj_add_uint(doc, preferred, \"width\", item->preferredWidth);\n        yyjson_mut_obj_add_uint(doc, preferred, \"height\", item->preferredHeight);\n        yyjson_mut_obj_add_real(doc, preferred, \"refreshRate\", item->preferredRefreshRate);\n\n        yyjson_mut_val* physical = yyjson_mut_obj_add_obj(doc, obj, \"physical\");\n        yyjson_mut_obj_add_uint(doc, physical, \"width\", item->physicalWidth);\n        yyjson_mut_obj_add_uint(doc, physical, \"height\", item->physicalHeight);\n\n        yyjson_mut_obj_add_uint(doc, obj, \"rotation\", item->rotation);\n        yyjson_mut_obj_add_uint(doc, obj, \"bitDepth\", item->bitDepth);\n        if (item->hdrStatus == FF_DISPLAY_HDR_STATUS_UNKNOWN)\n            yyjson_mut_obj_add_null(doc, obj, \"hdrStatus\");\n        else switch (item->hdrStatus)\n        {\n            case FF_DISPLAY_HDR_STATUS_UNSUPPORTED:\n                yyjson_mut_obj_add_str(doc, obj, \"hdrStatus\", \"Unsupported\");\n                break;\n            case FF_DISPLAY_HDR_STATUS_SUPPORTED:\n                yyjson_mut_obj_add_str(doc, obj, \"hdrStatus\", \"Supported\");\n                break;\n            case FF_DISPLAY_HDR_STATUS_ENABLED:\n                yyjson_mut_obj_add_str(doc, obj, \"hdrStatus\", \"Enabled\");\n                break;\n            default:\n                yyjson_mut_obj_add_str(doc, obj, \"hdrStatus\", \"Unknown\");\n                break;\n        }\n\n        switch (item->type)\n        {\n            case FF_DISPLAY_TYPE_BUILTIN:\n                yyjson_mut_obj_add_str(doc, obj, \"type\", \"Builtin\");\n                break;\n            case FF_DISPLAY_TYPE_EXTERNAL:\n                yyjson_mut_obj_add_str(doc, obj, \"type\", \"External\");\n                break;\n            default:\n                yyjson_mut_obj_add_str(doc, obj, \"type\", \"Unknown\");\n                break;\n        }\n\n        if (item->manufactureYear)\n        {\n            yyjson_mut_val* manufactureDate = yyjson_mut_obj_add_obj(doc, obj, \"manufactureDate\");\n            yyjson_mut_obj_add_uint(doc, manufactureDate, \"year\", item->manufactureYear);\n            yyjson_mut_obj_add_uint(doc, manufactureDate, \"week\", item->manufactureWeek);\n        }\n        else\n        {\n            yyjson_mut_obj_add_null(doc, obj, \"manufactureDate\");\n        }\n\n        if (item->serial)\n            yyjson_mut_obj_add_uint(doc, obj, \"serial\", item->serial);\n        else\n            yyjson_mut_obj_add_null(doc, obj, \"serial\");\n\n        yyjson_mut_obj_add_str(doc, obj, \"platformApi\", item->platformApi);\n    }\n\n    return true;\n}\n\nvoid ffInitDisplayOptions(FFDisplayOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰍹\");\n    options->compactType = FF_DISPLAY_COMPACT_TYPE_NONE;\n    options->preciseRefreshRate = false;\n    options->order = FF_DISPLAY_ORDER_NONE;\n}\n\nvoid ffDestroyDisplayOptions(FFDisplayOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffDisplayModuleInfo = {\n    .name = FF_DISPLAY_MODULE_NAME,\n    .description = \"Print resolutions, refresh rates, etc\",\n    .initOptions = (void*) ffInitDisplayOptions,\n    .destroyOptions = (void*) ffDestroyDisplayOptions,\n    .parseJsonObject = (void*) ffParseDisplayJsonObject,\n    .printModule = (void*) ffPrintDisplay,\n    .generateJsonResult = (void*) ffGenerateDisplayJsonResult,\n    .generateJsonConfig = (void*) ffGenerateDisplayJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Screen configured width (in pixels)\", \"width\"},\n        {\"Screen configured height (in pixels)\", \"height\"},\n        {\"Screen configured refresh rate (in Hz)\", \"refresh-rate\"},\n        {\"Screen scaled width (in pixels)\", \"scaled-width\"},\n        {\"Screen scaled height (in pixels)\", \"scaled-height\"},\n        {\"Screen name\", \"name\"},\n        {\"Screen type (Built-in or External)\", \"type\"},\n        {\"Screen rotation (in degrees)\", \"rotation\"},\n        {\"True if being the primary screen\", \"is-primary\"},\n        {\"Screen physical width (in millimeters)\", \"physical-width\"},\n        {\"Screen physical height (in millimeters)\", \"physical-height\"},\n        {\"Physical diagonal length in inches\", \"inch\"},\n        {\"Pixels per inch (PPI)\", \"ppi\"},\n        {\"Bits per color channel\", \"bit-depth\"},\n        {\"True if high dynamic range (HDR) mode is enabled\", \"hdr-enabled\"},\n        {\"Year of manufacturing\", \"manufacture-year\"},\n        {\"Nth week of manufacturing in the year\", \"manufacture-week\"},\n        {\"Serial number\", \"serial\"},\n        {\"The platform API used when detecting the display\", \"platform-api\"},\n        {\"True if the display is HDR compatible\", \"hdr-compatible\"},\n        {\"HiDPI scale factor\", \"scale-factor\"},\n        {\"Screen preferred width (in pixels)\", \"preferred-width\"},\n        {\"Screen preferred height (in pixels)\", \"preferred-height\"},\n        {\"Screen preferred refresh rate (in Hz)\", \"preferred-refresh-rate\"},\n        {\"DPI\", \"dpi\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/display/display.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_DISPLAY_MODULE_NAME \"Display\"\n\nbool ffPrintDisplay(FFDisplayOptions* options);\nvoid ffInitDisplayOptions(FFDisplayOptions* options);\nvoid ffDestroyDisplayOptions(FFDisplayOptions* options);\n\nextern FFModuleBaseInfo ffDisplayModuleInfo;\n"
  },
  {
    "path": "src/modules/display/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef enum __attribute__((__packed__)) FFDisplayCompactType\n{\n    FF_DISPLAY_COMPACT_TYPE_NONE = 0,\n    FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT = 1 << 0,\n    FF_DISPLAY_COMPACT_TYPE_SCALED_BIT = 1 << 1,\n    FF_DISPLAY_COMPACT_TYPE_REFRESH_RATE_BIT = 1 << 2,\n    FF_DISPLAY_COMPACT_TYPE_UNSIGNED = UINT8_MAX,\n} FFDisplayCompactType;\n\ntypedef enum __attribute__((__packed__)) FFDisplayOrder\n{\n    FF_DISPLAY_ORDER_NONE,\n    FF_DISPLAY_ORDER_ASC,\n    FF_DISPLAY_ORDER_DESC,\n} FFDisplayOrder;\n\ntypedef struct FFDisplayOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFDisplayCompactType compactType;\n    bool preciseRefreshRate;\n    FFDisplayOrder order;\n} FFDisplayOptions;\n\nstatic_assert(sizeof(FFDisplayOptions) <= FF_OPTION_MAX_SIZE, \"FFDisplayOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/dns/dns.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/dns/dns.h\"\n#include \"modules/dns/dns.h\"\n\nbool ffPrintDNS(FFDNSOptions* options)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFstrbuf));\n\n    const char* error = ffDetectDNS(options, &result);\n\n    if (error)\n    {\n        ffPrintError(FF_DNS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if (result.length == 0)\n    {\n        ffPrintError(FF_DNS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"NO DNS servers detected\");\n        return false;\n    }\n\n    FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();\n    FF_LIST_FOR_EACH(FFstrbuf, item, result)\n    {\n        if (!ffStrbufContainC(item, '.')) continue; // IPv4\n        if (buf.length)\n            ffStrbufAppendC(&buf, ' ');\n        ffStrbufAppend(&buf, item);\n    }\n    FF_LIST_FOR_EACH(FFstrbuf, item, result)\n    {\n        if (!ffStrbufContainC(item, ':')) continue; // IPv6\n        if (buf.length)\n            ffStrbufAppendC(&buf, ' ');\n        ffStrbufAppend(&buf, item);\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_DNS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n        ffStrbufPutTo(&buf, stdout);\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_DNS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n            FF_ARG(buf, \"result\"),\n        }));\n    }\n\n    FF_LIST_FOR_EACH(FFstrbuf, item, result)\n    {\n        ffStrbufDestroy(item);\n    }\n\n    return true;\n}\n\nvoid ffParseDNSJsonObject(FFDNSOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"showType\"))\n        {\n            int value;\n            const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {\n                { \"both\", FF_DNS_TYPE_BOTH },\n                { \"ipv4\", FF_DNS_TYPE_IPV4_BIT },\n                { \"ipv6\", FF_DNS_TYPE_IPV6_BIT },\n                {},\n            });\n            if (error)\n                ffPrintError(FF_DNS_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, \"Invalid %s value: %s\", unsafe_yyjson_get_str(key), error);\n            else\n                options->showType = (FFDNSShowType) value;\n            continue;\n        }\n\n        ffPrintError(FF_DNS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateDNSJsonConfig(FFDNSOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    switch ((uint8_t) options->showType)\n    {\n        case FF_DNS_TYPE_IPV4_BIT:\n            yyjson_mut_obj_add_str(doc, module, \"showType\", \"ipv4\");\n            break;\n        case FF_DNS_TYPE_IPV6_BIT:\n            yyjson_mut_obj_add_str(doc, module, \"showType\", \"ipv6\");\n            break;\n        case FF_DNS_TYPE_BOTH:\n            yyjson_mut_obj_add_str(doc, module, \"showType\", \"both\");\n            break;\n    }\n}\n\nbool ffGenerateDNSJsonResult(FFDNSOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFstrbuf));\n\n    const char* error = ffDetectDNS(options, &result);\n\n    if (error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n\n    FF_LIST_FOR_EACH(FFstrbuf, item, result)\n    {\n        yyjson_mut_arr_add_strbuf(doc, arr, item);\n    }\n\n    FF_LIST_FOR_EACH(FFstrbuf, item, result)\n    {\n        ffStrbufDestroy(item);\n    }\n\n    return true;\n}\n\nvoid ffInitDNSOptions(FFDNSOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰇖\");\n\n    options->showType = FF_DNS_TYPE_BOTH;\n}\n\nvoid ffDestroyDNSOptions(FFDNSOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffDNSModuleInfo = {\n    .name = FF_DNS_MODULE_NAME,\n    .description = \"Print configured DNS servers\",\n    .initOptions = (void*) ffInitDNSOptions,\n    .destroyOptions = (void*) ffDestroyDNSOptions,\n    .parseJsonObject = (void*) ffParseDNSJsonObject,\n    .printModule = (void*) ffPrintDNS,\n    .generateJsonResult = (void*) ffGenerateDNSJsonResult,\n    .generateJsonConfig = (void*) ffGenerateDNSJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"DNS result\", \"result\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/dns/dns.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_DNS_MODULE_NAME \"DNS\"\n\nbool ffPrintDNS(FFDNSOptions* options);\nvoid ffInitDNSOptions(FFDNSOptions* options);\nvoid ffDestroyDNSOptions(FFDNSOptions* options);\n\nextern FFModuleBaseInfo ffDNSModuleInfo;\n"
  },
  {
    "path": "src/modules/dns/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef enum __attribute__((__packed__)) FFDNSShowType {\n    FF_DNS_TYPE_IPV4_BIT = 1,\n    FF_DNS_TYPE_IPV6_BIT = 2,\n    FF_DNS_TYPE_BOTH = FF_DNS_TYPE_IPV4_BIT | FF_DNS_TYPE_IPV6_BIT,\n    FF_DNS_TYPE_FORCE_UNSIGNED = UINT8_MAX,\n} FFDNSShowType;\n\ntypedef struct FFDNSOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFDNSShowType showType;\n} FFDNSOptions;\n\nstatic_assert(sizeof(FFDNSOptions) <= FF_OPTION_MAX_SIZE, \"FFDNSOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/editor/editor.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/libc/libc.h\"\n#include \"detection/editor/editor.h\"\n#include \"modules/editor/editor.h\"\n\nbool ffPrintEditor(FFEditorOptions* options)\n{\n    FFEditorResult result = {\n        .type = \"Unknown\",\n        .name = ffStrbufCreate(),\n        .path = ffStrbufCreate(),\n        .exe = ffStrbufCreate(),\n        .version = ffStrbufCreate(),\n    };\n    const char* error = ffDetectEditor(&result);\n\n    if (error)\n    {\n        ffPrintError(FF_EDITOR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if (options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_EDITOR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        if (result.exe.length)\n        {\n            ffStrbufWriteTo(&result.exe, stdout);\n            if (result.version.length)\n                printf(\" %s\", result.version.chars);\n        }\n        else\n        {\n            ffStrbufWriteTo(&result.name, stdout);\n        }\n        putchar('\\n');\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_EDITOR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(result.type, \"type\"),\n            FF_ARG(result.name, \"name\"),\n            FF_ARG(result.exe, \"exe-name\"),\n            FF_ARG(result.path, \"path\"),\n            FF_ARG(result.version, \"version\"),\n        }));\n    }\n\n    ffStrbufDestroy(&result.name);\n    ffStrbufDestroy(&result.path);\n    ffStrbufDestroy(&result.exe);\n    ffStrbufDestroy(&result.version);\n\n    return true;\n}\n\nvoid ffParseEditorJsonObject(FFEditorOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_EDITOR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateEditorJsonConfig(FFEditorOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateEditorJsonResult(FF_MAYBE_UNUSED FFEditorOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FFEditorResult result = {\n        .name = ffStrbufCreate(),\n        .path = ffStrbufCreate(),\n        .version = ffStrbufCreate(),\n    };\n\n    const char* error = ffDetectEditor(&result);\n\n    if (error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_str(doc, obj, \"type\", result.type);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &result.name);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"path\", &result.path);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"exe\", &result.exe);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"version\", &result.version);\n\n    ffStrbufDestroy(&result.name);\n    ffStrbufDestroy(&result.path);\n    ffStrbufDestroy(&result.exe);\n    ffStrbufDestroy(&result.version);\n\n    return true;\n}\n\nvoid ffInitEditorOptions(FFEditorOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󱞎\");\n}\n\nvoid ffDestroyEditorOptions(FFEditorOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffEditorModuleInfo = {\n    .name = FF_EDITOR_MODULE_NAME,\n    .description = \"Print information of the default editor ($VISUAL or $EDITOR)\",\n    .initOptions = (void*) ffInitEditorOptions,\n    .destroyOptions = (void*) ffDestroyEditorOptions,\n    .parseJsonObject = (void*) ffParseEditorJsonObject,\n    .printModule = (void*) ffPrintEditor,\n    .generateJsonResult = (void*) ffGenerateEditorJsonResult,\n    .generateJsonConfig = (void*) ffGenerateEditorJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Type (Visual / Editor)\", \"type\"},\n        {\"Name\", \"name\"},\n        {\"Exe name of real path\", \"exe-name\"},\n        {\"Full path of real path\", \"path\"},\n        {\"Version\", \"version\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/editor/editor.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_EDITOR_MODULE_NAME \"Editor\"\n\nbool ffPrintEditor(FFEditorOptions* options);\nvoid ffInitEditorOptions(FFEditorOptions* options);\nvoid ffDestroyEditorOptions(FFEditorOptions* options);\n\nextern FFModuleBaseInfo ffEditorModuleInfo;\n"
  },
  {
    "path": "src/modules/editor/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFEditorOptions\n{\n    FFModuleArgs moduleArgs;\n} FFEditorOptions;\n\nstatic_assert(sizeof(FFEditorOptions) <= FF_OPTION_MAX_SIZE, \"FFEditorOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/font/font.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/font/font.h\"\n#include \"modules/font/font.h\"\n\nbool ffPrintFont(FFFontOptions* options)\n{\n    bool success = false;\n    FFFontResult font;\n    for(uint32_t i = 0; i < FF_DETECT_FONT_NUM_FONTS; ++i)\n        ffStrbufInit(&font.fonts[i]);\n    ffStrbufInit(&font.display);\n\n    const char* error = ffDetectFont(&font);\n\n    if(error)\n    {\n        ffPrintError(FF_FONT_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n    }\n    else\n    {\n        if(options->moduleArgs.outputFormat.length == 0)\n        {\n            ffPrintLogoAndKey(FF_FONT_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n            ffStrbufPutTo(&font.display, stdout);\n        }\n        else\n        {\n            FF_PRINT_FORMAT_CHECKED(FF_FONT_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n                FF_ARG(font.fonts[0], \"font1\"),\n                FF_ARG(font.fonts[1], \"font2\"),\n                FF_ARG(font.fonts[2], \"font3\"),\n                FF_ARG(font.fonts[3], \"font4\"),\n                FF_ARG(font.display, \"combined\"),\n            }));\n        }\n\n        success = true;\n    }\n\n    ffStrbufDestroy(&font.display);\n    for (uint32_t i = 0; i < FF_DETECT_FONT_NUM_FONTS; ++i)\n        ffStrbufDestroy(&font.fonts[i]);\n\n    return success;\n}\n\nvoid ffParseFontJsonObject(FFFontOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_FONT_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateFontJsonConfig(FFFontOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateFontJsonResult(FF_MAYBE_UNUSED FFFontOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    bool success = false;\n    FFFontResult font;\n    for(uint32_t i = 0; i < FF_DETECT_FONT_NUM_FONTS; ++i)\n        ffStrbufInit(&font.fonts[i]);\n    ffStrbufInit(&font.display);\n\n    const char* error = ffDetectFont(&font);\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n    }\n    else\n    {\n        yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n        yyjson_mut_obj_add_strbuf(doc, obj, \"display\", &font.display);\n        yyjson_mut_val* fontsArr = yyjson_mut_obj_add_arr(doc, obj, \"fonts\");\n        for (uint32_t i = 0; i < FF_DETECT_FONT_NUM_FONTS; ++i)\n            yyjson_mut_arr_add_strbuf(doc, fontsArr, &font.fonts[i]);\n        success = true;\n    }\n\n    ffStrbufDestroy(&font.display);\n    for (uint32_t i = 0; i < FF_DETECT_FONT_NUM_FONTS; ++i)\n        ffStrbufDestroy(&font.fonts[i]);\n\n    return success;\n}\n\nvoid ffInitFontOptions(FFFontOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n}\n\nvoid ffDestroyFontOptions(FFFontOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffFontModuleInfo = {\n    .name = FF_FONT_MODULE_NAME,\n    .description = \"Print system font names\",\n    .initOptions = (void*) ffInitFontOptions,\n    .destroyOptions = (void*) ffDestroyFontOptions,\n    .parseJsonObject = (void*) ffParseFontJsonObject,\n    .printModule = (void*) ffPrintFont,\n    .generateJsonResult = (void*) ffGenerateFontJsonResult,\n    .generateJsonConfig = (void*) ffGenerateFontJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Font 1\", \"font1\"},\n        {\"Font 2\", \"font2\"},\n        {\"Font 3\", \"font3\"},\n        {\"Font 4\", \"font4\"},\n        {\"Combined fonts for display\", \"combined\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/font/font.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_FONT_MODULE_NAME \"Font\"\n\nbool ffPrintFont(FFFontOptions* options);\nvoid ffInitFontOptions(FFFontOptions* options);\nvoid ffDestroyFontOptions(FFFontOptions* options);\n\nextern FFModuleBaseInfo ffFontModuleInfo;\n"
  },
  {
    "path": "src/modules/font/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFFontOptions\n{\n    FFModuleArgs moduleArgs;\n} FFFontOptions;\n\nstatic_assert(sizeof(FFFontOptions) <= FF_OPTION_MAX_SIZE, \"FFFontOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/gamepad/gamepad.c",
    "content": "#include \"common/percent.h\"\n#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/gamepad/gamepad.h\"\n#include \"modules/gamepad/gamepad.h\"\n\nstatic void printDevice(FFGamepadOptions* options, const FFGamepadDevice* device, uint8_t index)\n{\n    FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_GAMEPAD_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n        FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n        bool showBatteryLevel = device->battery > 0 && device->battery <= 100;\n\n        if (showBatteryLevel && (percentType & FF_PERCENTAGE_TYPE_BAR_BIT))\n        {\n            ffPercentAppendBar(&buffer, device->battery, options->percent, &options->moduleArgs);\n            ffStrbufAppendC(&buffer, ' ');\n        }\n\n        if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT))\n            ffStrbufAppend(&buffer, &device->name);\n\n        if (showBatteryLevel && (percentType & FF_PERCENTAGE_TYPE_NUM_BIT))\n        {\n            if (buffer.length)\n                ffStrbufAppendC(&buffer, ' ');\n            ffPercentAppendNum(&buffer, device->battery, options->percent, buffer.length > 0, &options->moduleArgs);\n        }\n        ffStrbufPutTo(&buffer, stdout);\n    }\n    else\n    {\n        FF_STRBUF_AUTO_DESTROY percentageNum = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n            ffPercentAppendNum(&percentageNum, device->battery, options->percent, false, &options->moduleArgs);\n        FF_STRBUF_AUTO_DESTROY percentageBar = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            ffPercentAppendBar(&percentageBar, device->battery, options->percent, &options->moduleArgs);\n\n        FF_PRINT_FORMAT_CHECKED(FF_GAMEPAD_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n            FF_ARG(device->name, \"name\"),\n            FF_ARG(device->serial, \"serial\"),\n            FF_ARG(percentageNum, \"battery-percentage\"),\n            FF_ARG(percentageBar, \"battery-percentage-bar\"),\n        }));\n    }\n}\n\nbool ffPrintGamepad(FFGamepadOptions* options)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFGamepadDevice));\n\n    const char* error = ffDetectGamepad(&result);\n\n    if(error)\n    {\n        ffPrintError(FF_GAMEPAD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if(!result.length)\n    {\n        ffPrintError(FF_GAMEPAD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No devices detected\");\n        return false;\n    }\n\n    FF_LIST_AUTO_DESTROY filtered = ffListCreate(sizeof(FFGamepadDevice*));\n    FF_LIST_FOR_EACH(FFGamepadDevice, device, result)\n    {\n        bool ignored = false;\n        FF_LIST_FOR_EACH(FFstrbuf, ignore, options->ignores)\n        {\n            if(ffStrbufStartsWithIgnCase(&device->name, ignore))\n            {\n                ignored = true;\n                break;\n            }\n        }\n        if(!ignored)\n        {\n            FFGamepadDevice** ptr = ffListAdd(&filtered);\n            *ptr = device;\n        }\n    }\n\n    bool ret = true;\n    if(!filtered.length)\n    {\n        ffPrintError(FF_GAMEPAD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"All devices are ignored\");\n        ret = false;\n    }\n    else\n    {\n        uint8_t index = 0;\n        FF_LIST_FOR_EACH(FFGamepadDevice*, pdevice, filtered)\n        {\n            FFGamepadDevice* device = *pdevice;\n            printDevice(options, device, filtered.length > 1 ? ++index : 0);\n        }\n\n        FF_LIST_FOR_EACH(FFGamepadDevice, device, result)\n        {\n            ffStrbufDestroy(&device->serial);\n            ffStrbufDestroy(&device->name);\n        }\n    }\n\n    return ret;\n}\n\nvoid ffParseGamepadJsonObject(FFGamepadOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"ignores\"))\n        {\n            yyjson_val *elem;\n            size_t eidx, emax;\n            yyjson_arr_foreach(val, eidx, emax, elem)\n            {\n                if (yyjson_is_str(elem))\n                {\n                    FFstrbuf* strbuf = ffListAdd(&options->ignores);\n                    ffStrbufInitJsonVal(strbuf, elem);\n                }\n            }\n            continue;\n        }\n\n        if (ffPercentParseJsonObject(key, val, &options->percent))\n            continue;\n\n        ffPrintError(FF_GAMEPAD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateGamepadJsonConfig(FFGamepadOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    if (options->ignores.length > 0)\n    {\n        yyjson_mut_val* ignores = yyjson_mut_obj_add_arr(doc, module, \"ignores\");\n        FF_LIST_FOR_EACH(FFstrbuf, strbuf, options->ignores)\n            yyjson_mut_arr_append(ignores, yyjson_mut_strncpy(doc, strbuf->chars, strbuf->length));\n    }\n    ffPercentGenerateJsonConfig(doc, module, options->percent);\n}\n\nbool ffGenerateGamepadJsonResult(FF_MAYBE_UNUSED FFGamepadOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFGamepadDevice));\n\n    const char* error = ffDetectGamepad(&result);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n    FF_LIST_FOR_EACH(FFGamepadDevice, device, result)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"serial\", &device->serial);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &device->name);\n\n        bool ignored = false;\n        FF_LIST_FOR_EACH(FFstrbuf, ignore, options->ignores)\n        {\n            if(ffStrbufStartsWithIgnCase(&device->name, ignore))\n            {\n                ignored = true;\n                break;\n            }\n        }\n        yyjson_mut_obj_add_bool(doc, obj, \"ignored\", ignored);\n    }\n\n    FF_LIST_FOR_EACH(FFGamepadDevice, device, result)\n    {\n        ffStrbufDestroy(&device->serial);\n        ffStrbufDestroy(&device->name);\n    }\n\n    return true;\n}\n\nvoid ffInitGamepadOptions(FFGamepadOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰺵\");\n\n    ffListInit(&options->ignores, sizeof(FFstrbuf));\n    options->percent = (FFPercentageModuleConfig) { 50, 20, 0 };\n}\n\nvoid ffDestroyGamepadOptions(FFGamepadOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n\n    FF_LIST_FOR_EACH(FFstrbuf, str, options->ignores)\n        ffStrbufDestroy(str);\n    ffListDestroy(&options->ignores);\n}\n\nFFModuleBaseInfo ffGamepadModuleInfo = {\n    .name = FF_GAMEPAD_MODULE_NAME,\n    .description = \"List (connected) gamepads\",\n    .initOptions = (void*) ffInitGamepadOptions,\n    .destroyOptions = (void*) ffDestroyGamepadOptions,\n    .parseJsonObject = (void*) ffParseGamepadJsonObject,\n    .printModule = (void*) ffPrintGamepad,\n    .generateJsonResult = (void*) ffGenerateGamepadJsonResult,\n    .generateJsonConfig = (void*) ffGenerateGamepadJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Name\", \"name\"},\n        {\"Serial number\", \"serial\"},\n        {\"Battery percentage num\", \"battery-percentage\"},\n        {\"Battery percentage bar\", \"battery-percentage-bar\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/gamepad/gamepad.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_GAMEPAD_MODULE_NAME \"Gamepad\"\n\nbool ffPrintGamepad(FFGamepadOptions* options);\nvoid ffInitGamepadOptions(FFGamepadOptions* options);\nvoid ffDestroyGamepadOptions(FFGamepadOptions* options);\n\nextern FFModuleBaseInfo ffGamepadModuleInfo;\n"
  },
  {
    "path": "src/modules/gamepad/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n#include \"common/FFlist.h\"\n\ntypedef struct FFGamepadOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFlist ignores; // List of FFstrbuf\n    FFPercentageModuleConfig percent;\n} FFGamepadOptions;\n\nstatic_assert(sizeof(FFGamepadOptions) <= FF_OPTION_MAX_SIZE, \"FFGamepadOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/gpu/gpu.c",
    "content": "#include \"common/percent.h\"\n#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/temps.h\"\n#include \"common/size.h\"\n#include \"common/frequency.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/host/host.h\"\n#include \"detection/gpu/gpu.h\"\n#include \"modules/gpu/gpu.h\"\n\n#include <stdlib.h>\n\nstatic void printGPUResult(FFGPUOptions* options, uint8_t index, const FFGPUResult* gpu)\n{\n    const char* type;\n    switch (gpu->type)\n    {\n        case FF_GPU_TYPE_INTEGRATED: type = \"Integrated\"; break;\n        case FF_GPU_TYPE_DISCRETE: type = \"Discrete\"; break;\n        default: type = \"Unknown\"; break;\n    }\n\n    FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_GPU_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n        FF_STRBUF_AUTO_DESTROY output = ffStrbufCreate();\n\n        if(gpu->vendor.length > 0 && !ffStrbufStartsWithIgnCase(&gpu->name, &gpu->vendor))\n        {\n            ffStrbufAppend(&output, &gpu->vendor);\n            ffStrbufAppendC(&output, ' ');\n        }\n\n        ffStrbufAppend(&output, &gpu->name);\n\n        if(gpu->coreCount != FF_GPU_CORE_COUNT_UNSET)\n            ffStrbufAppendF(&output, \" (%d)\", gpu->coreCount);\n\n        if(gpu->frequency > 0)\n        {\n            ffStrbufAppendS(&output, \" @ \");\n            ffFreqAppendNum(gpu->frequency, &output);\n        }\n\n        if(gpu->temperature != FF_GPU_TEMP_UNSET)\n        {\n            ffStrbufAppendS(&output, \" - \");\n            ffTempsAppendNum(gpu->temperature, &output, options->tempConfig, &options->moduleArgs);\n        }\n\n        if(gpu->dedicated.total != FF_GPU_VMEM_SIZE_UNSET && gpu->dedicated.total != 0)\n        {\n            ffStrbufAppendS(&output, \" (\");\n\n            if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT))\n            {\n                if(gpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET)\n                {\n                    ffSizeAppendNum(gpu->dedicated.used, &output);\n                    ffStrbufAppendS(&output, \" / \");\n                }\n                ffSizeAppendNum(gpu->dedicated.total, &output);\n            }\n            if(gpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET)\n            {\n                double percent = (double) gpu->dedicated.used / (double) gpu->dedicated.total * 100.0;\n                if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n                {\n                    ffStrbufAppendS(&output, \", \");\n                    ffPercentAppendNum(&output, percent, options->percent, false, &options->moduleArgs);\n                }\n                if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n                {\n                    ffStrbufAppendS(&output, \" \");\n                    ffPercentAppendBar(&output, percent, options->percent, &options->moduleArgs);\n                }\n            }\n            ffStrbufAppendC(&output, ')');\n        }\n\n        if (gpu->type != FF_GPU_TYPE_UNKNOWN)\n            ffStrbufAppendF(&output, \" [%s]\", type);\n\n        ffStrbufPutTo(&output, stdout);\n    }\n    else\n    {\n        FF_STRBUF_AUTO_DESTROY tempStr = ffStrbufCreate();\n        ffTempsAppendNum(gpu->temperature, &tempStr, options->tempConfig, &options->moduleArgs);\n        FF_STRBUF_AUTO_DESTROY dTotal = ffStrbufCreate();\n        FF_STRBUF_AUTO_DESTROY dUsed = ffStrbufCreate();\n        FF_STRBUF_AUTO_DESTROY dPercentNum = ffStrbufCreate();\n        FF_STRBUF_AUTO_DESTROY dPercentBar = ffStrbufCreate();\n        if (gpu->dedicated.total != FF_GPU_VMEM_SIZE_UNSET) ffSizeAppendNum(gpu->dedicated.total, &dTotal);\n        if (gpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET) ffSizeAppendNum(gpu->dedicated.used, &dUsed);\n        if (gpu->dedicated.total != FF_GPU_VMEM_SIZE_UNSET && gpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET)\n        {\n            double percent = (double) gpu->dedicated.used / (double) gpu->dedicated.total * 100.0;\n            if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n                ffPercentAppendNum(&dPercentNum, percent, options->percent, false, &options->moduleArgs);\n            if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n                ffPercentAppendBar(&dPercentBar, percent, options->percent, &options->moduleArgs);\n        }\n\n        FF_STRBUF_AUTO_DESTROY sTotal = ffStrbufCreate();\n        FF_STRBUF_AUTO_DESTROY sUsed = ffStrbufCreate();\n        FF_STRBUF_AUTO_DESTROY sPercentNum = ffStrbufCreate();\n        FF_STRBUF_AUTO_DESTROY sPercentBar = ffStrbufCreate();\n        if (gpu->shared.total != FF_GPU_VMEM_SIZE_UNSET) ffSizeAppendNum(gpu->shared.total, &sTotal);\n        if (gpu->shared.used != FF_GPU_VMEM_SIZE_UNSET) ffSizeAppendNum(gpu->shared.used, &sUsed);\n        if (gpu->shared.total != FF_GPU_VMEM_SIZE_UNSET && gpu->shared.used != FF_GPU_VMEM_SIZE_UNSET)\n        {\n            double percent = (double) gpu->shared.used / (double) gpu->shared.total * 100.0;\n            if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n                ffPercentAppendNum(&sPercentNum, percent, options->percent, false, &options->moduleArgs);\n            if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n                ffPercentAppendBar(&sPercentBar, percent, options->percent, &options->moduleArgs);\n        }\n\n        FF_STRBUF_AUTO_DESTROY frequency = ffStrbufCreate();\n        ffFreqAppendNum(gpu->frequency, &frequency);\n\n        FF_STRBUF_AUTO_DESTROY coreUsageNum = ffStrbufCreate();\n        FF_STRBUF_AUTO_DESTROY coreUsageBar = ffStrbufCreate();\n        if (gpu->coreUsage != FF_GPU_CORE_USAGE_UNSET)\n        {\n            if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n                ffPercentAppendNum(&coreUsageNum, gpu->coreUsage, options->percent, false, &options->moduleArgs);\n            if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n                ffPercentAppendBar(&coreUsageBar, gpu->coreUsage, options->percent, &options->moduleArgs);\n        }\n\n        FF_PRINT_FORMAT_CHECKED(FF_GPU_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n            FF_ARG(gpu->vendor, \"vendor\"),\n            FF_ARG(gpu->name, \"name\"),\n            FF_ARG(gpu->driver, \"driver\"),\n            FF_ARG(tempStr, \"temperature\"),\n            FF_ARG(gpu->coreCount, \"core-count\"),\n            FF_ARG(type, \"type\"),\n            FF_ARG(dTotal, \"dedicated-total\"),\n            FF_ARG(dUsed, \"dedicated-used\"),\n            FF_ARG(sTotal, \"shared-total\"),\n            FF_ARG(sUsed, \"shared-used\"),\n            FF_ARG(gpu->platformApi, \"platform-api\"),\n            FF_ARG(frequency, \"frequency\"),\n            FF_ARG(index, \"index\"),\n            FF_ARG(dPercentNum, \"dedicated-percentage-num\"),\n            FF_ARG(dPercentBar, \"dedicated-percentage-bar\"),\n            FF_ARG(sPercentNum, \"shared-percentage-num\"),\n            FF_ARG(sPercentBar, \"shared-percentage-bar\"),\n            FF_ARG(coreUsageNum, \"core-usage-num\"),\n            FF_ARG(coreUsageBar, \"core-usage-bar\"),\n            FF_ARG(gpu->memoryType, \"memory-type\"),\n        }));\n    }\n}\n\nbool ffPrintGPU(FFGPUOptions* options)\n{\n    FF_LIST_AUTO_DESTROY gpus = ffListCreate(sizeof (FFGPUResult));\n    const char* error = ffDetectGPU(options, &gpus);\n    if (error)\n    {\n        ffPrintError(FF_GPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    FF_LIST_AUTO_DESTROY selectedGPUs;\n    ffListInitA(&selectedGPUs, sizeof(const FFGPUResult*), gpus.length);\n\n    FF_LIST_FOR_EACH(FFGPUResult, gpu, gpus)\n    {\n        if(gpu->type == FF_GPU_TYPE_UNKNOWN && options->hideType == FF_GPU_TYPE_UNKNOWN)\n            continue;\n\n        if(gpu->type == FF_GPU_TYPE_INTEGRATED && options->hideType == FF_GPU_TYPE_INTEGRATED)\n            continue;\n\n        if(gpu->type == FF_GPU_TYPE_DISCRETE && options->hideType == FF_GPU_TYPE_DISCRETE)\n            continue;\n\n        * (const FFGPUResult**) ffListAdd(&selectedGPUs) = gpu;\n    }\n\n    for(uint32_t i = 0; i < selectedGPUs.length; i++)\n        printGPUResult(options, selectedGPUs.length == 1 ? 0 : (uint8_t) (i + 1), *FF_LIST_GET(const FFGPUResult*, selectedGPUs, i));\n\n    if(selectedGPUs.length == 0)\n        ffPrintError(FF_GPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No GPUs found\");\n\n    FF_LIST_FOR_EACH(FFGPUResult, gpu, gpus)\n    {\n        ffStrbufDestroy(&gpu->vendor);\n        ffStrbufDestroy(&gpu->name);\n        ffStrbufDestroy(&gpu->driver);\n        ffStrbufDestroy(&gpu->platformApi);\n        ffStrbufDestroy(&gpu->memoryType);\n    }\n\n    return true;\n}\n\nvoid ffParseGPUJsonObject(FFGPUOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (ffTempsParseJsonObject(key, val, &options->temp, &options->tempConfig))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"driverSpecific\"))\n        {\n            options->driverSpecific = yyjson_get_bool(val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"detectionMethod\"))\n        {\n            int value;\n            const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {\n                { \"auto\", FF_GPU_DETECTION_METHOD_AUTO },\n                { \"pci\", FF_GPU_DETECTION_METHOD_PCI },\n                { \"vulkan\", FF_GPU_DETECTION_METHOD_VULKAN },\n                { \"opencl\", FF_GPU_DETECTION_METHOD_OPENCL },\n                { \"opengl\", FF_GPU_DETECTION_METHOD_OPENGL },\n                {},\n            });\n            if (error)\n                ffPrintError(FF_GPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Invalid %s value: %s\", unsafe_yyjson_get_str(key), error);\n            else\n                options->detectionMethod = (FFGPUDetectionMethod) value;\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"hideType\"))\n        {\n            if (yyjson_is_null(val))\n                options->hideType = FF_GPU_TYPE_NONE;\n            else\n            {\n                int value;\n                const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {\n                    { \"none\", FF_GPU_TYPE_NONE },\n                    { \"unknown\", FF_GPU_TYPE_UNKNOWN },\n                    { \"integrated\", FF_GPU_TYPE_INTEGRATED },\n                    { \"discrete\", FF_GPU_TYPE_DISCRETE },\n                    {},\n                });\n                if (error)\n                    ffPrintError(FF_GPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Invalid %s value: %s\", unsafe_yyjson_get_str(key), error);\n                else\n                    options->hideType = (FFGPUType) value;\n            }\n            continue;\n        }\n\n        if (ffPercentParseJsonObject(key, val, &options->percent))\n            continue;\n\n        ffPrintError(FF_GPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateGPUJsonConfig(FFGPUOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    yyjson_mut_obj_add_bool(doc, module, \"driverSpecific\", options->driverSpecific);\n\n    switch (options->detectionMethod)\n    {\n        case FF_GPU_DETECTION_METHOD_AUTO:\n            yyjson_mut_obj_add_str(doc, module, \"detectionMethod\", \"auto\");\n            break;\n        case FF_GPU_DETECTION_METHOD_PCI:\n            yyjson_mut_obj_add_str(doc, module, \"detectionMethod\", \"pci\");\n            break;\n        case FF_GPU_DETECTION_METHOD_VULKAN:\n            yyjson_mut_obj_add_str(doc, module, \"detectionMethod\", \"vulkan\");\n            break;\n        case FF_GPU_DETECTION_METHOD_OPENCL:\n            yyjson_mut_obj_add_str(doc, module, \"detectionMethod\", \"opencl\");\n            break;\n        case FF_GPU_DETECTION_METHOD_OPENGL:\n            yyjson_mut_obj_add_str(doc, module, \"detectionMethod\", \"opengl\");\n            break;\n    }\n\n    ffTempsGenerateJsonConfig(doc, module, options->temp, options->tempConfig);\n\n    switch (options->hideType)\n    {\n        case FF_GPU_TYPE_NONE:\n            yyjson_mut_obj_add_str(doc, module, \"hideType\", \"none\");\n            break;\n        case FF_GPU_TYPE_UNKNOWN:\n            yyjson_mut_obj_add_str(doc, module, \"hideType\", \"unknown\");\n            break;\n        case FF_GPU_TYPE_INTEGRATED:\n            yyjson_mut_obj_add_str(doc, module, \"hideType\", \"integrated\");\n            break;\n        case FF_GPU_TYPE_DISCRETE:\n            yyjson_mut_obj_add_str(doc, module, \"hideType\", \"discrete\");\n            break;\n    }\n\n    ffPercentGenerateJsonConfig(doc, module, options->percent);\n}\n\nbool ffGenerateGPUJsonResult(FFGPUOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY gpus = ffListCreate(sizeof (FFGPUResult));\n    const char* error = ffDetectGPU(options, &gpus);\n    if (error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n    FF_LIST_FOR_EACH(FFGPUResult, gpu, gpus)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n\n        if (gpu->index != FF_GPU_INDEX_UNSET)\n            yyjson_mut_obj_add_uint(doc, obj, \"index\", gpu->index);\n        else\n            yyjson_mut_obj_add_null(doc, obj, \"index\");\n\n        if (gpu->coreCount != FF_GPU_CORE_COUNT_UNSET)\n            yyjson_mut_obj_add_int(doc, obj, \"coreCount\", gpu->coreCount);\n        else\n            yyjson_mut_obj_add_null(doc, obj, \"coreCount\");\n\n        if (gpu->coreUsage != FF_GPU_CORE_USAGE_UNSET)\n            yyjson_mut_obj_add_real(doc, obj, \"coreUsage\", gpu->coreUsage);\n        else\n            yyjson_mut_obj_add_null(doc, obj, \"coreUsage\");\n\n        yyjson_mut_val* memoryObj = yyjson_mut_obj_add_obj(doc, obj, \"memory\");\n\n        yyjson_mut_val* dedicatedObj = yyjson_mut_obj_add_obj(doc, memoryObj, \"dedicated\");\n        if (gpu->dedicated.total != FF_GPU_VMEM_SIZE_UNSET)\n            yyjson_mut_obj_add_uint(doc, dedicatedObj, \"total\", gpu->dedicated.total);\n        else\n            yyjson_mut_obj_add_null(doc, dedicatedObj, \"total\");\n\n        if (gpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET)\n            yyjson_mut_obj_add_uint(doc, dedicatedObj, \"used\", gpu->dedicated.used);\n        else\n            yyjson_mut_obj_add_null(doc, dedicatedObj, \"used\");\n\n        yyjson_mut_val* sharedObj = yyjson_mut_obj_add_obj(doc, memoryObj, \"shared\");\n        if (gpu->shared.total != FF_GPU_VMEM_SIZE_UNSET)\n            yyjson_mut_obj_add_uint(doc, sharedObj, \"total\", gpu->shared.total);\n        else\n            yyjson_mut_obj_add_null(doc, sharedObj, \"total\");\n        if (gpu->shared.used != FF_GPU_VMEM_SIZE_UNSET)\n            yyjson_mut_obj_add_uint(doc, sharedObj, \"used\", gpu->shared.used);\n        else\n            yyjson_mut_obj_add_null(doc, sharedObj, \"used\");\n\n        if (gpu->memoryType.length)\n            yyjson_mut_obj_add_strbuf(doc, memoryObj, \"type\", &gpu->memoryType);\n        else\n            yyjson_mut_obj_add_null(doc, memoryObj, \"type\");\n\n        yyjson_mut_obj_add_strbuf(doc, obj, \"driver\", &gpu->driver);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &gpu->name);\n\n        if(gpu->temperature != FF_GPU_TEMP_UNSET)\n            yyjson_mut_obj_add_real(doc, obj, \"temperature\", gpu->temperature);\n        else\n            yyjson_mut_obj_add_null(doc, obj, \"temperature\");\n\n        const char* type;\n        switch (gpu->type)\n        {\n            case FF_GPU_TYPE_INTEGRATED: type = \"Integrated\"; break;\n            case FF_GPU_TYPE_DISCRETE: type = \"Discrete\"; break;\n            default: type = NULL; break;\n        }\n        if (type)\n            yyjson_mut_obj_add_str(doc, obj, \"type\", type);\n        else\n            yyjson_mut_obj_add_null(doc, obj, \"type\");\n\n        yyjson_mut_obj_add_strbuf(doc, obj, \"vendor\", &gpu->vendor);\n\n        yyjson_mut_obj_add_strbuf(doc, obj, \"platformApi\", &gpu->platformApi);\n\n        if (gpu->frequency != FF_GPU_FREQUENCY_UNSET)\n            yyjson_mut_obj_add_uint(doc, obj, \"frequency\", gpu->frequency);\n        else\n            yyjson_mut_obj_add_null(doc, obj, \"frequency\");\n\n        yyjson_mut_obj_add_uint(doc, obj, \"deviceId\", gpu->deviceId);\n    }\n\n    FF_LIST_FOR_EACH(FFGPUResult, gpu, gpus)\n    {\n        ffStrbufDestroy(&gpu->vendor);\n        ffStrbufDestroy(&gpu->name);\n        ffStrbufDestroy(&gpu->driver);\n        ffStrbufDestroy(&gpu->platformApi);\n        ffStrbufDestroy(&gpu->memoryType);\n    }\n\n    return true;\n}\n\nvoid ffInitGPUOptions(FFGPUOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰾲\");\n\n    options->driverSpecific = false;\n    options->detectionMethod =\n        #if defined(__x86_64__) || defined(__i386__)\n        FF_GPU_DETECTION_METHOD_PCI\n        #else\n        FF_GPU_DETECTION_METHOD_AUTO\n        #endif\n    ;\n    options->temp = false;\n    options->hideType = FF_GPU_TYPE_NONE;\n    options->tempConfig = (FFColorRangeConfig) { 60, 80 };\n    options->percent = (FFPercentageModuleConfig) { 50, 80, 0 };\n}\n\nvoid ffDestroyGPUOptions(FFGPUOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffGPUModuleInfo = {\n    .name = FF_GPU_MODULE_NAME,\n    .description = \"Print GPU names, graphic memory size, type, etc\",\n    .initOptions = (void*) ffInitGPUOptions,\n    .destroyOptions = (void*) ffDestroyGPUOptions,\n    .parseJsonObject = (void*) ffParseGPUJsonObject,\n    .printModule = (void*) ffPrintGPU,\n    .generateJsonResult = (void*) ffGenerateGPUJsonResult,\n    .generateJsonConfig = (void*) ffGenerateGPUJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"GPU vendor\", \"vendor\"},\n        {\"GPU name\", \"name\"},\n        {\"GPU driver\", \"driver\"},\n        {\"GPU temperature\", \"temperature\"},\n        {\"GPU core count\", \"core-count\"},\n        {\"GPU type\", \"type\"},\n        {\"GPU total dedicated memory\", \"dedicated-total\"},\n        {\"GPU used dedicated memory\", \"dedicated-used\"},\n        {\"GPU total shared memory\", \"shared-total\"},\n        {\"GPU used shared memory\", \"shared-used\"},\n        {\"The platform API used when detecting the GPU\", \"platform-api\"},\n        {\"Current frequency in GHz\", \"frequency\"},\n        {\"GPU vendor specific index\", \"index\"},\n        {\"Dedicated memory usage percentage num\", \"dedicated-percentage-num\"},\n        {\"Dedicated memory usage percentage bar\", \"dedicated-percentage-bar\"},\n        {\"Shared memory usage percentage num\", \"shared-percentage-num\"},\n        {\"Shared memory usage percentage bar\", \"shared-percentage-bar\"},\n        {\"Core usage percentage num\", \"core-usage-num\"},\n        {\"Core usage percentage bar\", \"core-usage-bar\"},\n        {\"Memory type (Windows only)\", \"memory-type\"},\n    })),\n};\n"
  },
  {
    "path": "src/modules/gpu/gpu.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_GPU_MODULE_NAME \"GPU\"\n\nbool ffPrintGPU(FFGPUOptions* options);\nvoid ffInitGPUOptions(FFGPUOptions* options);\nvoid ffDestroyGPUOptions(FFGPUOptions* options);\n\nextern FFModuleBaseInfo ffGPUModuleInfo;\n"
  },
  {
    "path": "src/modules/gpu/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n#include \"common/percent.h\"\n\ntypedef enum __attribute__((__packed__)) FFGPUType\n{\n    FF_GPU_TYPE_NONE,      // Indicates no specific GPU type. Useful as a hide filter only.\n    FF_GPU_TYPE_UNKNOWN,   // Indicates an unknown or unrecognized GPU type.\n    FF_GPU_TYPE_INTEGRATED,\n    FF_GPU_TYPE_DISCRETE,\n} FFGPUType;\n\ntypedef enum __attribute__((__packed__)) FFGPUDetectionMethod\n{\n    FF_GPU_DETECTION_METHOD_AUTO,\n    FF_GPU_DETECTION_METHOD_PCI,\n    FF_GPU_DETECTION_METHOD_VULKAN,\n    FF_GPU_DETECTION_METHOD_OPENCL,\n    FF_GPU_DETECTION_METHOD_OPENGL,\n} FFGPUDetectionMethod;\n\ntypedef struct FFGPUOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFGPUType hideType;\n    FFGPUDetectionMethod detectionMethod;\n    bool temp;\n    bool driverSpecific;\n    FFColorRangeConfig tempConfig;\n    FFPercentageModuleConfig percent;\n} FFGPUOptions;\n\nstatic_assert(sizeof(FFGPUOptions) <= FF_OPTION_MAX_SIZE, \"FFGPUOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/host/host.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/host/host.h\"\n#include \"modules/host/host.h\"\n\nbool ffPrintHost(FFHostOptions* options)\n{\n    bool success = false;\n    FFHostResult host;\n    ffStrbufInit(&host.family);\n    ffStrbufInit(&host.name);\n    ffStrbufInit(&host.version);\n    ffStrbufInit(&host.sku);\n    ffStrbufInit(&host.serial);\n    ffStrbufInit(&host.uuid);\n    ffStrbufInit(&host.vendor);\n\n    const char* error = ffDetectHost(&host);\n    if(error)\n    {\n        ffPrintError(FF_HOST_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        goto exit;\n    }\n\n    if(host.name.length == 0 && host.family.length == 0)\n    {\n        ffPrintError(FF_HOST_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"neither product_family nor product_name is set by O.E.M.\");\n        goto exit;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_HOST_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n        FF_STRBUF_AUTO_DESTROY output = ffStrbufCreate();\n\n        if(host.name.length > 0)\n            ffStrbufAppend(&output, &host.name);\n        else\n            ffStrbufAppend(&output, &host.family);\n\n        if(host.version.length > 0)\n            ffStrbufAppendF(&output, \" (%s)\", host.version.chars);\n\n        ffStrbufPutTo(&output, stdout);\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_HOST_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n            FF_ARG(host.family, \"family\"),\n            FF_ARG(host.name, \"name\"),\n            FF_ARG(host.version, \"version\"),\n            FF_ARG(host.sku, \"sku\"),\n            FF_ARG(host.vendor, \"vendor\"),\n            FF_ARG(host.serial, \"serial\"),\n            FF_ARG(host.uuid, \"uuid\"),\n        }));\n    }\n    success = true;\n\nexit:\n    ffStrbufDestroy(&host.family);\n    ffStrbufDestroy(&host.name);\n    ffStrbufDestroy(&host.version);\n    ffStrbufDestroy(&host.sku);\n    ffStrbufDestroy(&host.serial);\n    ffStrbufDestroy(&host.uuid);\n    ffStrbufDestroy(&host.vendor);\n\n    return success;\n}\n\nvoid ffParseHostJsonObject(FFHostOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_HOST_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateHostJsonConfig(FFHostOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateHostJsonResult(FF_MAYBE_UNUSED FFHostOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    bool success = false;\n    FFHostResult host;\n    ffStrbufInit(&host.family);\n    ffStrbufInit(&host.name);\n    ffStrbufInit(&host.version);\n    ffStrbufInit(&host.sku);\n    ffStrbufInit(&host.serial);\n    ffStrbufInit(&host.uuid);\n    ffStrbufInit(&host.vendor);\n\n    const char* error = ffDetectHost(&host);\n    if (error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        goto exit;\n    }\n\n    if (host.family.length == 0 && host.name.length == 0)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", \"neither product_family nor product_name is set by O.E.M.\");\n        goto exit;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_strbuf(doc, obj, \"family\", &host.family);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &host.name);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"version\", &host.version);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"sku\", &host.sku);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"vendor\", &host.vendor);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"serial\", &host.serial);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"uuid\", &host.uuid);\n    success = true;\n\nexit:\n    ffStrbufDestroy(&host.family);\n    ffStrbufDestroy(&host.name);\n    ffStrbufDestroy(&host.version);\n    ffStrbufDestroy(&host.sku);\n    ffStrbufDestroy(&host.serial);\n    ffStrbufDestroy(&host.uuid);\n    ffStrbufDestroy(&host.vendor);\n\n    return success;\n}\n\nvoid ffInitHostOptions(FFHostOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰌢\");\n}\n\nvoid ffDestroyHostOptions(FFHostOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffHostModuleInfo = {\n    .name = FF_HOST_MODULE_NAME,\n    .description = \"Print product name of your computer\",\n    .initOptions = (void*) ffInitHostOptions,\n    .destroyOptions = (void*) ffDestroyHostOptions,\n    .parseJsonObject = (void*) ffParseHostJsonObject,\n    .printModule = (void*) ffPrintHost,\n    .generateJsonResult = (void*) ffGenerateHostJsonResult,\n    .generateJsonConfig = (void*) ffGenerateHostJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Product family\", \"family\"},\n        {\"Product name\", \"name\"},\n        {\"Product version\", \"version\"},\n        {\"Product sku\", \"sku\"},\n        {\"Product vendor\", \"vendor\"},\n        {\"Product serial number\", \"serial\"},\n        {\"Product uuid\", \"uuid\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/host/host.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_HOST_MODULE_NAME \"Host\"\n\nbool ffPrintHost(FFHostOptions* options);\nvoid ffInitHostOptions(FFHostOptions* options);\nvoid ffDestroyHostOptions(FFHostOptions* options);\n\nextern FFModuleBaseInfo ffHostModuleInfo;\n"
  },
  {
    "path": "src/modules/host/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFHostOptions\n{\n    FFModuleArgs moduleArgs;\n} FFHostOptions;\n\nstatic_assert(sizeof(FFHostOptions) <= FF_OPTION_MAX_SIZE, \"FFHostOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/icons/icons.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/icons/icons.h\"\n#include \"modules/icons/icons.h\"\n\nbool ffPrintIcons(FFIconsOptions* options)\n{\n    bool success = false;\n    FFIconsResult result = {\n        .icons1 = ffStrbufCreate(),\n        .icons2 = ffStrbufCreate(),\n    };\n    const char* error = ffDetectIcons(&result);\n\n    if(error)\n    {\n        ffPrintError(FF_ICONS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        goto exit;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_ICONS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        if (result.icons1.length)\n            ffStrbufWriteTo(&result.icons1, stdout);\n        if (result.icons2.length)\n        {\n            if (result.icons1.length)\n                fputs(\", \", stdout);\n            ffStrbufWriteTo(&result.icons2, stdout);\n        }\n        putchar('\\n');\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_ICONS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(result.icons1, \"icons1\"),\n            FF_ARG(result.icons2, \"icons2\"),\n        }));\n    }\n    success = true;\n\nexit:\n    ffStrbufDestroy(&result.icons1);\n    ffStrbufDestroy(&result.icons2);\n\n    return success;\n}\n\nvoid ffParseIconsJsonObject(FFIconsOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_ICONS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateIconsJsonConfig(FFIconsOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateIconsJsonResult(FF_MAYBE_UNUSED FFIconsOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    bool success = false;\n    FFIconsResult result = {\n        .icons1 = ffStrbufCreate(),\n        .icons2 = ffStrbufCreate()\n    };\n    const char* error = ffDetectIcons(&result);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        goto exit;\n    }\n\n    yyjson_mut_val* icons = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_strbuf(doc, icons, \"icons1\", &result.icons1);\n    yyjson_mut_obj_add_strbuf(doc, icons, \"icons2\", &result.icons2);\n    success = true;\n\nexit:\n    ffStrbufDestroy(&result.icons1);\n    ffStrbufDestroy(&result.icons2);\n\n    return success;\n}\n\nvoid ffInitIconsOptions(FFIconsOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n}\n\nvoid ffDestroyIconsOptions(FFIconsOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffIconsModuleInfo = {\n    .name = FF_ICONS_MODULE_NAME,\n    .description = \"Print icon style name\",\n    .initOptions = (void*) ffInitIconsOptions,\n    .destroyOptions = (void*) ffDestroyIconsOptions,\n    .parseJsonObject = (void*) ffParseIconsJsonObject,\n    .printModule = (void*) ffPrintIcons,\n    .generateJsonResult = (void*) ffGenerateIconsJsonResult,\n    .generateJsonConfig = (void*) ffGenerateIconsJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Icons part 1\", \"icons1\"},\n        {\"Icons part 2\", \"icons2\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/icons/icons.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_ICONS_MODULE_NAME \"Icons\"\n\nbool ffPrintIcons(FFIconsOptions* options);\nvoid ffInitIconsOptions(FFIconsOptions* options);\nvoid ffDestroyIconsOptions(FFIconsOptions* options);\n\nextern FFModuleBaseInfo ffIconsModuleInfo;\n"
  },
  {
    "path": "src/modules/icons/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFIconsOptions\n{\n    FFModuleArgs moduleArgs;\n} FFIconsOptions;\n\nstatic_assert(sizeof(FFIconsOptions) <= FF_OPTION_MAX_SIZE, \"FFIconsOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/initsystem/initsystem.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/initsystem/initsystem.h\"\n#include \"modules/initsystem/initsystem.h\"\n\n#define FF_INITSYSTEM_DISPLAY_NAME \"Init System\"\n\nbool ffPrintInitSystem(FFInitSystemOptions* options)\n{\n    bool success = false;\n    FFInitSystemResult result = {\n        .name = ffStrbufCreate(),\n        .exe = ffStrbufCreate(),\n        .version = ffStrbufCreate(),\n        .pid = 1,\n    };\n\n    const char* error = ffDetectInitSystem(&result);\n\n    if(error)\n    {\n        ffPrintError(FF_INITSYSTEM_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        goto exit;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_INITSYSTEM_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        ffStrbufWriteTo(&result.name, stdout);\n        if (result.version.length)\n            printf(\" %s\\n\", result.version.chars);\n        else\n            putchar('\\n');\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_INITSYSTEM_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n            FF_ARG(result.name, \"name\"),\n            FF_ARG(result.exe, \"exe\"),\n            FF_ARG(result.version, \"version\"),\n            FF_ARG(result.pid, \"pid\"),\n        }));\n    }\n    success = true;\n\nexit:\n    ffStrbufDestroy(&result.name);\n    ffStrbufDestroy(&result.exe);\n    ffStrbufDestroy(&result.version);\n\n    return success;\n}\n\nvoid ffParseInitSystemJsonObject(FFInitSystemOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_INITSYSTEM_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateInitSystemJsonConfig(FFInitSystemOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateInitSystemJsonResult(FF_MAYBE_UNUSED FFInitSystemOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    bool success = false;\n    FFInitSystemResult result = {\n        .name = ffStrbufCreate(),\n        .exe = ffStrbufCreate(),\n        .version = ffStrbufCreate(),\n        .pid = 1,\n    };\n\n    const char* error = ffDetectInitSystem(&result);\n\n    if (error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        goto exit;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &result.name);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"exe\", &result.exe);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"version\", &result.version);\n    yyjson_mut_obj_add_uint(doc, obj, \"pid\", result.pid);\n    success = true;\n\nexit:\n    ffStrbufDestroy(&result.name);\n    ffStrbufDestroy(&result.exe);\n    ffStrbufDestroy(&result.version);\n    return success;\n}\n\nvoid ffInitInitSystemOptions(FFInitSystemOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰿄\");\n}\n\nvoid ffDestroyInitSystemOptions(FFInitSystemOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffInitSystemModuleInfo = {\n    .name = FF_INITSYSTEM_MODULE_NAME,\n    .description = \"Print init system (pid 1) name and version\",\n    .initOptions = (void*) ffInitInitSystemOptions,\n    .destroyOptions = (void*) ffDestroyInitSystemOptions,\n    .parseJsonObject = (void*) ffParseInitSystemJsonObject,\n    .printModule = (void*) ffPrintInitSystem,\n    .generateJsonResult = (void*) ffGenerateInitSystemJsonResult,\n    .generateJsonConfig = (void*) ffGenerateInitSystemJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Init system name\", \"name\"},\n        {\"Init system exe path\", \"exe\"},\n        {\"Init system version path\", \"version\"},\n        {\"Init system pid\", \"pid\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/initsystem/initsystem.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_INITSYSTEM_MODULE_NAME \"InitSystem\"\n\nbool ffPrintInitSystem(FFInitSystemOptions* options);\nvoid ffInitInitSystemOptions(FFInitSystemOptions* options);\nvoid ffDestroyInitSystemOptions(FFInitSystemOptions* options);\n\nextern FFModuleBaseInfo ffInitSystemModuleInfo;\n"
  },
  {
    "path": "src/modules/initsystem/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFInitSystemOptions\n{\n    FFModuleArgs moduleArgs;\n} FFInitSystemOptions;\n\nstatic_assert(sizeof(FFInitSystemOptions) <= FF_OPTION_MAX_SIZE, \"FFInitSystemOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/kernel/kernel.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/size.h\"\n#include \"common/stringUtils.h\"\n#include \"modules/kernel/kernel.h\"\n\nbool ffPrintKernel(FFKernelOptions* options)\n{\n    const FFPlatformSysinfo* info = &instance.state.platform.sysinfo;\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_KERNEL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        printf(\"%s %s\\n\", info->name.chars, info->release.chars);\n    }\n    else\n    {\n        FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate();\n        ffSizeAppendNum(info->pageSize, &str);\n        FF_PRINT_FORMAT_CHECKED(FF_KERNEL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(info->name, \"sysname\"),\n            FF_ARG(info->release, \"release\"),\n            FF_ARG(info->version, \"version\"),\n            FF_ARG(info->architecture, \"arch\"),\n            FF_ARG(str, \"page-size\"),\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseKernelJsonObject(FFKernelOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_KERNEL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateKernelJsonConfig(FFKernelOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateKernelJsonResult(FF_MAYBE_UNUSED FFKernelOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    const FFPlatformSysinfo* info = &instance.state.platform.sysinfo;\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_strbuf(doc, obj, \"architecture\", &info->architecture);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &info->name);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"release\", &info->release);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"version\", &info->version);\n    yyjson_mut_obj_add_uint(doc, obj, \"pageSize\", info->pageSize);\n\n    return true;\n}\n\nvoid ffInitKernelOptions(FFKernelOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n}\n\nvoid ffDestroyKernelOptions(FFKernelOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffKernelModuleInfo = {\n    .name = FF_KERNEL_MODULE_NAME,\n    .description = \"Print system kernel version\",\n    .initOptions = (void*) ffInitKernelOptions,\n    .destroyOptions = (void*) ffDestroyKernelOptions,\n    .parseJsonObject = (void*) ffParseKernelJsonObject,\n    .printModule = (void*) ffPrintKernel,\n    .generateJsonResult = (void*) ffGenerateKernelJsonResult,\n    .generateJsonConfig = (void*) ffGenerateKernelJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Sysname\", \"sysname\"},\n        {\"Release\", \"release\"},\n        {\"Version\", \"version\"},\n        {\"Architecture\", \"arch\"},\n        {\"Display version\", \"display-version\"},\n        {\"Page size\", \"page-size\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/kernel/kernel.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_KERNEL_MODULE_NAME \"Kernel\"\n\nbool ffPrintKernel(FFKernelOptions* options);\nvoid ffInitKernelOptions(FFKernelOptions* options);\nvoid ffDestroyKernelOptions(FFKernelOptions* options);\n\nextern FFModuleBaseInfo ffKernelModuleInfo;\n"
  },
  {
    "path": "src/modules/kernel/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFKernelOptions\n{\n    FFModuleArgs moduleArgs;\n} FFKernelOptions;\n\nstatic_assert(sizeof(FFKernelOptions) <= FF_OPTION_MAX_SIZE, \"FFKernelOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/keyboard/keyboard.c",
    "content": "#include \"common/percent.h\"\n#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/keyboard/keyboard.h\"\n#include \"modules/keyboard/keyboard.h\"\n\nstatic void printDevice(FFKeyboardOptions* options, const FFKeyboardDevice* device, uint8_t index)\n{\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_KEYBOARD_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        ffStrbufPutTo(&device->name, stdout);\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_KEYBOARD_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n            FF_ARG(device->name, \"name\"),\n            FF_ARG(device->serial, \"serial\"),\n        }));\n    }\n}\n\nbool ffPrintKeyboard(FFKeyboardOptions* options)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFKeyboardDevice));\n\n    const char* error = ffDetectKeyboard(&result);\n\n    if(error)\n    {\n        ffPrintError(FF_KEYBOARD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if(!result.length)\n    {\n        ffPrintError(FF_KEYBOARD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No devices detected\");\n        return false;\n    }\n\n    FF_LIST_AUTO_DESTROY filtered = ffListCreate(sizeof(FFKeyboardDevice*));\n    FF_LIST_FOR_EACH(FFKeyboardDevice, device, result)\n    {\n        bool ignored = false;\n        FF_LIST_FOR_EACH(FFstrbuf, ignore, options->ignores)\n        {\n            if(ffStrbufStartsWithIgnCase(&device->name, ignore))\n            {\n                ignored = true;\n                break;\n            }\n        }\n        if(!ignored)\n        {\n            FFKeyboardDevice** ptr = ffListAdd(&filtered);\n            *ptr = device;\n        }\n    }\n\n    bool ret = true;\n    if(!filtered.length)\n    {\n        ffPrintError(FF_KEYBOARD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"All devices are ignored\");\n        ret = false;\n    }\n    else\n    {\n        uint8_t index = 0;\n        FF_LIST_FOR_EACH(FFKeyboardDevice*, pdevice, filtered)\n        {\n            FFKeyboardDevice* device = *pdevice;\n            printDevice(options, device, filtered.length > 1 ? ++index : 0);\n            ffStrbufDestroy(&device->serial);\n            ffStrbufDestroy(&device->name);\n        }\n    }\n\n    return ret;\n}\n\nvoid ffParseKeyboardJsonObject(FFKeyboardOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"ignores\"))\n        {\n            yyjson_val *elem;\n            size_t eidx, emax;\n            yyjson_arr_foreach(val, eidx, emax, elem)\n            {\n                if (yyjson_is_str(elem))\n                {\n                    FFstrbuf* strbuf = ffListAdd(&options->ignores);\n                    ffStrbufInitJsonVal(strbuf, elem);\n                }\n            }\n            continue;\n        }\n\n        ffPrintError(FF_KEYBOARD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateKeyboardJsonConfig(FFKeyboardOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    if (options->ignores.length > 0)\n    {\n        yyjson_mut_val* ignores = yyjson_mut_obj_add_arr(doc, module, \"ignores\");\n        FF_LIST_FOR_EACH(FFstrbuf, strbuf, options->ignores)\n            yyjson_mut_arr_append(ignores, yyjson_mut_strncpy(doc, strbuf->chars, strbuf->length));\n    }\n}\n\nbool ffGenerateKeyboardJsonResult(FF_MAYBE_UNUSED FFKeyboardOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFKeyboardDevice));\n\n    const char* error = ffDetectKeyboard(&result);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n    FF_LIST_FOR_EACH(FFKeyboardDevice, device, result)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"serial\", &device->serial);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &device->name);\n\n        bool ignored = false;\n        FF_LIST_FOR_EACH(FFstrbuf, ignore, options->ignores)\n        {\n            if(ffStrbufStartsWithIgnCase(&device->name, ignore))\n            {\n                ignored = true;\n                break;\n            }\n        }\n        yyjson_mut_obj_add_bool(doc, obj, \"ignored\", ignored);\n    }\n\n    FF_LIST_FOR_EACH(FFKeyboardDevice, device, result)\n    {\n        ffStrbufDestroy(&device->serial);\n        ffStrbufDestroy(&device->name);\n    }\n\n    return true;\n}\n\nvoid ffInitKeyboardOptions(FFKeyboardOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n\n    ffListInit(&options->ignores, sizeof(FFstrbuf));\n}\n\nvoid ffDestroyKeyboardOptions(FFKeyboardOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n\n    FF_LIST_FOR_EACH(FFstrbuf, str, options->ignores)\n        ffStrbufDestroy(str);\n    ffListDestroy(&options->ignores);\n}\n\nFFModuleBaseInfo ffKeyboardModuleInfo = {\n    .name = FF_KEYBOARD_MODULE_NAME,\n    .description = \"List (connected) keyboards\",\n    .initOptions = (void*) ffInitKeyboardOptions,\n    .destroyOptions = (void*) ffDestroyKeyboardOptions,\n    .parseJsonObject = (void*) ffParseKeyboardJsonObject,\n    .printModule = (void*) ffPrintKeyboard,\n    .generateJsonResult = (void*) ffGenerateKeyboardJsonResult,\n    .generateJsonConfig = (void*) ffGenerateKeyboardJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Name\", \"name\"},\n        {\"Serial number\", \"serial\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/keyboard/keyboard.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_KEYBOARD_MODULE_NAME \"Keyboard\"\n\nbool ffPrintKeyboard(FFKeyboardOptions* options);\nvoid ffInitKeyboardOptions(FFKeyboardOptions* options);\nvoid ffDestroyKeyboardOptions(FFKeyboardOptions* options);\n\nextern FFModuleBaseInfo ffKeyboardModuleInfo;\n"
  },
  {
    "path": "src/modules/keyboard/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n#include \"common/FFlist.h\"\n\ntypedef struct FFKeyboardOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFlist ignores; // List of FFstrbuf\n} FFKeyboardOptions;\n\nstatic_assert(sizeof(FFKeyboardOptions) <= FF_OPTION_MAX_SIZE, \"FFKeyboardOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/lm/lm.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/lm/lm.h\"\n#include \"modules/lm/lm.h\"\n\nbool ffPrintLM(FFLMOptions* options)\n{\n    bool success = false;\n    FFLMResult result;\n    ffStrbufInit(&result.service);\n    ffStrbufInit(&result.type);\n    ffStrbufInit(&result.version);\n    const char* error = ffDetectLM(&result);\n\n    if(error)\n    {\n        ffPrintError(FF_LM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        goto exit;\n    }\n\n    if(result.service.length == 0)\n    {\n        ffPrintError(FF_LM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No LM service found\");\n        goto exit;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_LM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        ffStrbufWriteTo(&result.service, stdout);\n        if(result.version.length)\n            printf(\" %s\", result.version.chars);\n        if(result.type.length)\n            printf(\" (%s)\", result.type.chars);\n        putchar('\\n');\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_LM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(result.service, \"service\"),\n            FF_ARG(result.type, \"type\"),\n            FF_ARG(result.version, \"version\"),\n        }));\n    }\n    success = true;\n\nexit:\n    ffStrbufDestroy(&result.service);\n    ffStrbufDestroy(&result.type);\n    ffStrbufDestroy(&result.version);\n\n    return success;\n}\n\nvoid ffParseLMJsonObject(FFLMOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_LM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateLMJsonConfig(FFLMOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateLMJsonResult(FF_MAYBE_UNUSED FFLMOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    bool success = false;\n    FFLMResult result;\n    ffStrbufInit(&result.service);\n    ffStrbufInit(&result.type);\n    ffStrbufInit(&result.version);\n    const char* error = ffDetectLM(&result);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        goto exit;\n    }\n\n    if(result.service.length == 0)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", \"No LM service found\");\n        goto exit;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_strbuf(doc, obj, \"service\", &result.service);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"type\", &result.type);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"version\", &result.version);\n    success = true;\n\nexit:\n    ffStrbufDestroy(&result.service);\n    ffStrbufDestroy(&result.type);\n    ffStrbufDestroy(&result.version);\n\n    return success;\n}\n\nvoid ffInitLMOptions(FFLMOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰧨\");\n}\n\nvoid ffDestroyLMOptions(FFLMOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffLMModuleInfo = {\n    .name = FF_LM_MODULE_NAME,\n    .description = \"Print login manager (desktop manager) name and version\",\n    .initOptions = (void*) ffInitLMOptions,\n    .destroyOptions = (void*) ffDestroyLMOptions,\n    .parseJsonObject = (void*) ffParseLMJsonObject,\n    .printModule = (void*) ffPrintLM,\n    .generateJsonResult = (void*) ffGenerateLMJsonResult,\n    .generateJsonConfig = (void*) ffGenerateLMJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"LM service\", \"service\"},\n        {\"LM type\", \"type\"},\n        {\"LM version\", \"version\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/lm/lm.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_LM_MODULE_NAME \"LM\"\n\nbool ffPrintLM(FFLMOptions* options);\nvoid ffInitLMOptions(FFLMOptions* options);\nvoid ffDestroyLMOptions(FFLMOptions* options);\n\nextern FFModuleBaseInfo ffLMModuleInfo;\n"
  },
  {
    "path": "src/modules/lm/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFLMOptions\n{\n    FFModuleArgs moduleArgs;\n} FFLMOptions;\n\nstatic_assert(sizeof(FFLMOptions) <= FF_OPTION_MAX_SIZE, \"FFLMOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/loadavg/loadavg.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/percent.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/loadavg/loadavg.h\"\n#include \"detection/cpu/cpu.h\"\n#include \"modules/loadavg/loadavg.h\"\n\nbool ffPrintLoadavg(FFLoadavgOptions* options)\n{\n    double result[3] = { 0.0 / 0.0, 0.0 / 0.0, 0.0 / 0.0 };\n\n    const char* error = ffDetectLoadavg(result);\n    if(error)\n    {\n        ffPrintError(FF_LOADAVG_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        if (options->compact)\n        {\n            ffPrintLogoAndKey(FF_LOADAVG_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n            printf(\"%.*f, %.*f, %.*f\\n\", options->ndigits, result[0], options->ndigits, result[1], options->ndigits, result[2]);\n        }\n        else\n        {\n            FFCPUResult cpu = {\n                .temperature = FF_CPU_TEMP_UNSET,\n                .frequencyMax = 0,\n                .frequencyBase = 0,\n                .name = ffStrbufCreate(),\n                .vendor = ffStrbufCreate(),\n            };\n            ffDetectCPU(&(FFCPUOptions) {}, &cpu);\n\n            FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n            for (uint32_t index = 0; index < 3; index++)\n            {\n                uint32_t duration = index == 0 ? 1 : index == 1 ? 5 : 15;\n                if (options->moduleArgs.key.length == 0)\n                {\n                    ffStrbufSetF(&buffer, \"%s (%d min)\", FF_LOADAVG_MODULE_NAME, duration);\n                }\n                else\n                {\n                    ffStrbufClear(&buffer);\n                    FF_PARSE_FORMAT_STRING_CHECKED(&buffer, &options->moduleArgs.key, ((FFformatarg[]) {\n                        FF_ARG(index, \"index\"),\n                        FF_ARG(duration, \"duration\"),\n                        FF_ARG(options->moduleArgs.keyIcon, \"icon\"),\n                    }));\n                }\n\n                ffPrintLogoAndKey(buffer.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);\n\n                ffStrbufClear(&buffer);\n                double percent = result[index] * 100 / cpu.coresOnline;\n                FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;\n\n                if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n                    ffPercentAppendBar(&buffer, percent, options->percent, &options->moduleArgs);\n\n                if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT))\n                {\n                    if (buffer.length > 0)\n                        ffStrbufAppendC(&buffer, ' ');\n                    ffStrbufAppendF(&buffer, \"%.*f\", options->ndigits, result[index]);\n                }\n\n                if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n                {\n                    if (buffer.length > 0)\n                        ffStrbufAppendC(&buffer, ' ');\n                    ffPercentAppendNum(&buffer, percent, options->percent, buffer.length > 0, &options->moduleArgs);\n                }\n\n                ffStrbufPutTo(&buffer, stdout);\n            }\n        }\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_LOADAVG_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(result[0], \"loadavg1\"),\n            FF_ARG(result[1], \"loadavg2\"),\n            FF_ARG(result[2], \"loadavg3\"),\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseLoadavgJsonObject(FFLoadavgOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"ndigits\"))\n        {\n            options->ndigits = (uint8_t) yyjson_get_uint(val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"compact\"))\n        {\n            options->compact = yyjson_get_bool(val);\n            continue;\n        }\n\n        if (ffPercentParseJsonObject(key, val, &options->percent))\n            continue;\n\n        ffPrintError(FF_LOADAVG_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateLoadavgJsonConfig(FFLoadavgOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    yyjson_mut_obj_add_uint(doc, module, \"ndigits\", options->ndigits);\n\n    yyjson_mut_obj_add_bool(doc, module, \"compact\", options->compact);\n\n    ffPercentGenerateJsonConfig(doc, module, options->percent);\n}\n\nbool ffGenerateLoadavgJsonResult(FF_MAYBE_UNUSED FFLoadavgOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    double result[3] = { 0.0 / 0.0, 0.0 / 0.0, 0.0 / 0.0 };\n\n    const char* error = ffDetectLoadavg(result);\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n    for (size_t i = 0; i < 3; i++)\n        yyjson_mut_arr_add_real(doc, arr, result[i]);\n\n    return true;\n}\n\nvoid ffInitLoadavgOptions(FFLoadavgOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n\n    options->percent = (FFPercentageModuleConfig) { 50, 80, 0 };\n    options->ndigits = 2;\n    options->compact = true;\n}\n\nvoid ffDestroyLoadavgOptions(FFLoadavgOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffLoadavgModuleInfo = {\n    .name = FF_LOADAVG_MODULE_NAME,\n    .description = \"Print system load averages\",\n    .initOptions = (void*) ffInitLoadavgOptions,\n    .destroyOptions = (void*) ffDestroyLoadavgOptions,\n    .parseJsonObject = (void*) ffParseLoadavgJsonObject,\n    .printModule = (void*) ffPrintLoadavg,\n    .generateJsonResult = (void*) ffGenerateLoadavgJsonResult,\n    .generateJsonConfig = (void*) ffGenerateLoadavgJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Load average over 1min\", \"loadavg1\"},\n        {\"Load average over 5min\", \"loadavg2\"},\n        {\"Load average over 15min\", \"loadavg3\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/loadavg/loadavg.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_LOADAVG_MODULE_NAME \"Loadavg\"\n\nbool ffPrintLoadavg(FFLoadavgOptions* options);\nvoid ffInitLoadavgOptions(FFLoadavgOptions* options);\nvoid ffDestroyLoadavgOptions(FFLoadavgOptions* options);\n\nextern FFModuleBaseInfo ffLoadavgModuleInfo;\n"
  },
  {
    "path": "src/modules/loadavg/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFLoadavgOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFPercentageModuleConfig percent;\n    uint8_t ndigits;\n    bool compact;\n} FFLoadavgOptions;\n\nstatic_assert(sizeof(FFLoadavgOptions) <= FF_OPTION_MAX_SIZE, \"FFLoadavgOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/locale/locale.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"detection/locale/locale.h\"\n#include \"modules/locale/locale.h\"\n\nbool ffPrintLocale(FFLocaleOptions* options)\n{\n    FF_STRBUF_AUTO_DESTROY locale = ffStrbufCreate();\n\n    const char* error = ffDetectLocale(&locale);\n    if(error)\n    {\n        ffPrintError(FF_LOCALE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_LOCALE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        ffStrbufPutTo(&locale, stdout);\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_LOCALE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(locale, \"result\")\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseLocaleJsonObject(FFLocaleOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_LOCALE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateLocaleJsonConfig(FFLocaleOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateLocaleJsonResult(FF_MAYBE_UNUSED FFLocaleOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_STRBUF_AUTO_DESTROY locale = ffStrbufCreate();\n\n    const char* error = ffDetectLocale(&locale);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    if(locale.length == 0)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", \"No locale found\");\n        return false;\n    }\n\n    yyjson_mut_obj_add_strbuf(doc, module, \"result\", &locale);\n\n    return true;\n}\n\nvoid ffInitLocaleOptions(FFLocaleOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n}\n\nvoid ffDestroyLocaleOptions(FFLocaleOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffLocaleModuleInfo = {\n    .name = FF_LOCALE_MODULE_NAME,\n    .description = \"Print system locale name\",\n    .initOptions = (void*) ffInitLocaleOptions,\n    .destroyOptions = (void*) ffDestroyLocaleOptions,\n    .parseJsonObject = (void*) ffParseLocaleJsonObject,\n    .printModule = (void*) ffPrintLocale,\n    .generateJsonResult = (void*) ffGenerateLocaleJsonResult,\n    .generateJsonConfig = (void*) ffGenerateLocaleJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Locale code\", \"result\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/locale/locale.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_LOCALE_MODULE_NAME \"Locale\"\n\nbool ffPrintLocale(FFLocaleOptions* options);\nvoid ffInitLocaleOptions(FFLocaleOptions* options);\nvoid ffDestroyLocaleOptions(FFLocaleOptions* options);\n\nextern FFModuleBaseInfo ffLocaleModuleInfo;\n"
  },
  {
    "path": "src/modules/locale/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFLocaleOptions\n{\n    FFModuleArgs moduleArgs;\n} FFLocaleOptions;\n\nstatic_assert(sizeof(FFLocaleOptions) <= FF_OPTION_MAX_SIZE, \"FFLocaleOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/localip/localip.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/localip/localip.h\"\n#include \"modules/localip/localip.h\"\n\n#define FF_LOCALIP_DISPLAY_NAME \"Local IP\"\n#pragma GCC diagnostic ignored \"-Wsign-conversion\"\n\nstatic int sortIps(const FFLocalIpResult* left, const FFLocalIpResult* right)\n{\n    return ffStrbufComp(&left->name, &right->name);\n}\n\nstatic void formatKey(const FFLocalIpOptions* options, FFLocalIpResult* ip, uint32_t index, FFstrbuf* key)\n{\n    if(options->moduleArgs.key.length == 0)\n    {\n        if(!ip->name.length)\n            ffStrbufSetF(&ip->name, \"unknown %u\", (unsigned) index);\n\n        ffStrbufSetF(key, FF_LOCALIP_DISPLAY_NAME \" (%s)\", ip->name.chars);\n    }\n    else\n    {\n        ffStrbufClear(key);\n        FF_PARSE_FORMAT_STRING_CHECKED(key, &options->moduleArgs.key, ((FFformatarg[]) {\n            FF_ARG(index, \"index\"),\n            FF_ARG(ip->name, \"ifname\"),\n            FF_ARG(ip->mac, \"mac\"),\n            FF_ARG(options->moduleArgs.keyIcon, \"icon\"),\n        }));\n    }\n}\n\nstatic void appendSpeed(FFLocalIpResult* ip, FFstrbuf* strbuf)\n{\n    if (ip->speed >= 1000000)\n    {\n        ffStrbufAppendDouble(strbuf, ip->speed / 1e6, instance.config.display.fractionNdigits, instance.config.display.fractionTrailingZeros == FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS);\n        ffStrbufAppendS(strbuf, \" Tbps\");\n    }\n    else if (ip->speed >= 1000)\n    {\n        ffStrbufAppendDouble(strbuf, ip->speed / 1e3, instance.config.display.fractionNdigits, instance.config.display.fractionTrailingZeros == FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS);\n        ffStrbufAppendS(strbuf, \" Gbps\");\n    }\n    else\n        ffStrbufAppendF(strbuf, \"%u Mbps\", (unsigned) ip->speed);\n}\n\nstatic void printIp(FFLocalIpResult* ip, bool markDefaultRoute, FFstrbuf* buffer)\n{\n    if (ip->ipv4.length)\n    {\n        ffStrbufAppend(buffer, &ip->ipv4);\n    }\n    if (ip->ipv6.length)\n    {\n        if (buffer->length) ffStrbufAppendC(buffer, ' ');\n        ffStrbufAppend(buffer, &ip->ipv6);\n    }\n    if (ip->mac.length)\n    {\n        if (buffer->length)\n            ffStrbufAppendF(buffer, \" (%s)\", ip->mac.chars);\n        else\n            ffStrbufAppend(buffer, &ip->mac);\n    }\n    if (ip->mtu > 0 || ip->speed > 0)\n    {\n        bool flag = buffer->length > 0;\n        if (flag)\n            ffStrbufAppendS(buffer, \" [\");\n        if (ip->speed > 0)\n        {\n            if (ip->mtu > 0)\n                ffStrbufAppendS(buffer, \"Speed \");\n            appendSpeed(ip, buffer);\n            if (ip->mtu > 0)\n                ffStrbufAppendS(buffer, \" / MTU \");\n        }\n        if (ip->mtu > 0)\n            ffStrbufAppendF(buffer, \"%u\", (unsigned) ip->mtu);\n        if (flag)\n            ffStrbufAppendC(buffer, ']');\n    }\n    if (ip->flags.length)\n    {\n        bool flag = buffer->length > 0;\n        if (flag) ffStrbufAppendS(buffer, \" <\");\n        ffStrbufAppend(buffer, &ip->flags);\n        if (flag)\n            ffStrbufAppendC(buffer, '>');\n    }\n    if (markDefaultRoute && ip->defaultRoute)\n        ffStrbufAppendS(buffer, \" *\");\n}\n\nbool ffPrintLocalIp(FFLocalIpOptions* options)\n{\n    FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFLocalIpResult));\n\n    const char* error = ffDetectLocalIps(options, &results);\n\n    if(error)\n    {\n        ffPrintError(FF_LOCALIP_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if(results.length == 0)\n    {\n        ffPrintError(FF_LOCALIP_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Failed to detect any IPs\");\n        return false;\n    }\n\n    ffListSort(&results, (const void*) sortIps);\n\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n\n    if (options->showType & FF_LOCALIP_TYPE_COMPACT_BIT)\n    {\n        ffPrintLogoAndKey(FF_LOCALIP_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n        FF_LIST_FOR_EACH(FFLocalIpResult, ip, results)\n        {\n            if (buffer.length)\n                ffStrbufAppendS(&buffer, \" - \");\n            printIp(ip, false, &buffer);\n        }\n        ffStrbufPutTo(&buffer, stdout);\n        ffStrbufClear(&buffer);\n    }\n    else\n    {\n        FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();\n        uint32_t index = 0;\n\n        FF_LIST_FOR_EACH(FFLocalIpResult, ip, results)\n        {\n            formatKey(options, ip, results.length == 1 ? 0 : index + 1, &key);\n            if(options->moduleArgs.outputFormat.length == 0)\n            {\n                ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);\n                printIp(ip, !(options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT), &buffer);\n                ffStrbufPutTo(&buffer, stdout);\n            }\n            else\n            {\n                if (ip->speed > 0)\n                    appendSpeed(ip, &buffer);\n                FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]){\n                    FF_ARG(ip->ipv4, \"ipv4\"),\n                    FF_ARG(ip->ipv6, \"ipv6\"),\n                    FF_ARG(ip->mac, \"mac\"),\n                    FF_ARG(ip->name, \"ifname\"),\n                    FF_ARG(ip->defaultRoute, \"is-default-route\"),\n                    FF_ARG(ip->mtu, \"mtu\"),\n                    FF_ARG(buffer, \"speed\"),\n                    FF_ARG(ip->flags, \"flags\"),\n                }));\n            }\n            ++index;\n            ffStrbufClear(&buffer);\n        }\n    }\n\n    FF_LIST_FOR_EACH(FFLocalIpResult, ip, results)\n    {\n        ffStrbufDestroy(&ip->name);\n        ffStrbufDestroy(&ip->ipv4);\n        ffStrbufDestroy(&ip->ipv6);\n        ffStrbufDestroy(&ip->mac);\n        ffStrbufDestroy(&ip->flags);\n    }\n\n    return true;\n}\n\nvoid ffParseLocalIpJsonObject(FFLocalIpOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"showIpv4\"))\n        {\n            if (yyjson_get_bool(val))\n                options->showType |= FF_LOCALIP_TYPE_IPV4_BIT;\n            else\n                options->showType &= ~FF_LOCALIP_TYPE_IPV4_BIT;\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"showIpv6\"))\n        {\n            if (yyjson_is_bool(val))\n            {\n                options->ipv6Type = FF_LOCALIP_IPV6_TYPE_AUTO;\n                if (unsafe_yyjson_get_bool(val))\n                    options->showType |= FF_LOCALIP_TYPE_IPV6_BIT;\n                else\n                    options->showType &= ~FF_LOCALIP_TYPE_IPV6_BIT;\n            }\n            else if (yyjson_is_str(val))\n            {\n                int value;\n                const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {\n                    { \"auto\", FF_LOCALIP_IPV6_TYPE_AUTO },\n                    { \"gua\", FF_LOCALIP_IPV6_TYPE_GUA_BIT },\n                    { \"ula\", FF_LOCALIP_IPV6_TYPE_ULA_BIT },\n                    { \"lla\", FF_LOCALIP_IPV6_TYPE_LLA_BIT },\n                    { \"unknown\", FF_LOCALIP_IPV6_TYPE_UNKNOWN_BIT },\n                    {},\n                });\n                if (error)\n                    ffPrintError(FF_LOCALIP_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Invalid %s value: %s\", unsafe_yyjson_get_str(key), error);\n                else\n                {\n                    options->showType |= FF_LOCALIP_TYPE_IPV6_BIT;\n                    options->ipv6Type = (FFLocalIpIpv6Type) value;\n                }\n            }\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"showMac\"))\n        {\n            if (yyjson_get_bool(val))\n                options->showType |= FF_LOCALIP_TYPE_MAC_BIT;\n            else\n                options->showType &= ~FF_LOCALIP_TYPE_MAC_BIT;\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"showLoop\"))\n        {\n            if (yyjson_get_bool(val))\n                options->showType |= FF_LOCALIP_TYPE_LOOP_BIT;\n            else\n                options->showType &= ~FF_LOCALIP_TYPE_LOOP_BIT;\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"showPrefixLen\"))\n        {\n            if (yyjson_get_bool(val))\n                options->showType |= FF_LOCALIP_TYPE_PREFIX_LEN_BIT;\n            else\n                options->showType &= ~FF_LOCALIP_TYPE_PREFIX_LEN_BIT;\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"showMtu\"))\n        {\n            if (yyjson_get_bool(val))\n                options->showType |= FF_LOCALIP_TYPE_MTU_BIT;\n            else\n                options->showType &= ~FF_LOCALIP_TYPE_MTU_BIT;\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"showSpeed\"))\n        {\n            if (yyjson_get_bool(val))\n                options->showType |= FF_LOCALIP_TYPE_SPEED_BIT;\n            else\n                options->showType &= ~FF_LOCALIP_TYPE_SPEED_BIT;\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"showFlags\"))\n        {\n            if (yyjson_get_bool(val))\n                options->showType |= FF_LOCALIP_TYPE_FLAGS_BIT;\n            else\n                options->showType &= ~FF_LOCALIP_TYPE_FLAGS_BIT;\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"compact\"))\n        {\n            if (yyjson_get_bool(val))\n                options->showType |= FF_LOCALIP_TYPE_COMPACT_BIT;\n            else\n                options->showType &= ~FF_LOCALIP_TYPE_COMPACT_BIT;\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"defaultRouteOnly\"))\n        {\n            if (yyjson_get_bool(val))\n                options->showType |= FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT;\n            else\n                options->showType &= ~FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT;\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"showAllIps\"))\n        {\n            if (yyjson_get_bool(val))\n                options->showType |= FF_LOCALIP_TYPE_ALL_IPS_BIT;\n            else\n                options->showType &= ~FF_LOCALIP_TYPE_ALL_IPS_BIT;\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"namePrefix\"))\n        {\n            ffStrbufSetJsonVal(&options->namePrefix, val);\n            continue;\n        }\n\n        ffPrintError(FF_LOCALIP_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateLocalIpJsonConfig(FFLocalIpOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    yyjson_mut_obj_add_bool(doc, module, \"showIpv4\", !!(options->showType & FF_LOCALIP_TYPE_IPV4_BIT));\n\n    if (options->ipv6Type == FF_LOCALIP_IPV6_TYPE_AUTO)\n        yyjson_mut_obj_add_bool(doc, module, \"showIpv6\", !!(options->showType & FF_LOCALIP_TYPE_IPV6_BIT));\n    else\n    {\n        const char* str = NULL;\n        switch (options->ipv6Type)\n        {\n            case FF_LOCALIP_IPV6_TYPE_GUA_BIT:     str = \"gua\"; break;\n            case FF_LOCALIP_IPV6_TYPE_ULA_BIT:     str = \"ula\"; break;\n            case FF_LOCALIP_IPV6_TYPE_LLA_BIT:     str = \"lla\"; break;\n            case FF_LOCALIP_IPV6_TYPE_UNKNOWN_BIT: str = \"unknown\"; break;\n            default:                               str = \"auto\"; break;\n        }\n        yyjson_mut_obj_add_str(doc, module, \"showIpv6\", str);\n    }\n\n    yyjson_mut_obj_add_bool(doc, module, \"showMac\", !!(options->showType & FF_LOCALIP_TYPE_MAC_BIT));\n\n    yyjson_mut_obj_add_bool(doc, module, \"showLoop\", !!(options->showType & FF_LOCALIP_TYPE_LOOP_BIT));\n\n    yyjson_mut_obj_add_bool(doc, module, \"showPrefixLen\", !!(options->showType & FF_LOCALIP_TYPE_PREFIX_LEN_BIT));\n\n    yyjson_mut_obj_add_bool(doc, module, \"showMtu\", !!(options->showType & FF_LOCALIP_TYPE_MTU_BIT));\n\n    yyjson_mut_obj_add_bool(doc, module, \"showSpeed\", !!(options->showType & FF_LOCALIP_TYPE_SPEED_BIT));\n\n    yyjson_mut_obj_add_bool(doc, module, \"showFlags\", !!(options->showType & FF_LOCALIP_TYPE_FLAGS_BIT));\n\n    yyjson_mut_obj_add_bool(doc, module, \"compact\", !!(options->showType & FF_LOCALIP_TYPE_COMPACT_BIT));\n\n    yyjson_mut_obj_add_bool(doc, module, \"defaultRouteOnly\", !!(options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT));\n\n    yyjson_mut_obj_add_bool(doc, module, \"showAllIps\", !!(options->showType & FF_LOCALIP_TYPE_ALL_IPS_BIT));\n\n    yyjson_mut_obj_add_strbuf(doc, module, \"namePrefix\", &options->namePrefix);\n}\n\nbool ffGenerateLocalIpJsonResult(FF_MAYBE_UNUSED FFLocalIpOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFLocalIpResult));\n\n    const char* error = ffDetectLocalIps(options, &results);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n    FF_LIST_FOR_EACH(FFLocalIpResult, ip, results)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &ip->name);\n        if (options->showType & (FF_LOCALIP_TYPE_IPV4_BIT | FF_LOCALIP_TYPE_IPV6_BIT))\n        {\n            yyjson_mut_val* defaultRoute = yyjson_mut_obj_add_obj(doc, obj, \"defaultRoute\");\n            if (options->showType & FF_LOCALIP_TYPE_IPV4_BIT)\n                yyjson_mut_obj_add_bool(doc, defaultRoute, \"ipv4\", !!(ip->defaultRoute & FF_LOCALIP_TYPE_IPV4_BIT));\n            if (options->showType & FF_LOCALIP_TYPE_IPV6_BIT)\n                yyjson_mut_obj_add_bool(doc, defaultRoute, \"ipv6\", !!(ip->defaultRoute & FF_LOCALIP_TYPE_IPV6_BIT));\n        }\n        if (options->showType & FF_LOCALIP_TYPE_IPV4_BIT)\n            yyjson_mut_obj_add_strbuf(doc, obj, \"ipv4\", &ip->ipv4);\n        if (options->showType & FF_LOCALIP_TYPE_IPV6_BIT)\n            yyjson_mut_obj_add_strbuf(doc, obj, \"ipv6\", &ip->ipv6);\n        if (options->showType & FF_LOCALIP_TYPE_MAC_BIT)\n            yyjson_mut_obj_add_strbuf(doc, obj, \"mac\", &ip->mac);\n        if (options->showType & FF_LOCALIP_TYPE_MTU_BIT)\n            yyjson_mut_obj_add_int(doc, obj, \"mtu\", ip->mtu);\n        if (options->showType & FF_LOCALIP_TYPE_SPEED_BIT)\n            yyjson_mut_obj_add_int(doc, obj, \"speed\", ip->speed);\n        if (options->showType & FF_LOCALIP_TYPE_FLAGS_BIT)\n            yyjson_mut_obj_add_strbuf(doc, obj, \"flags\", &ip->flags);\n    }\n\n    FF_LIST_FOR_EACH(FFLocalIpResult, ip, results)\n    {\n        ffStrbufDestroy(&ip->name);\n        ffStrbufDestroy(&ip->ipv4);\n        ffStrbufDestroy(&ip->ipv6);\n        ffStrbufDestroy(&ip->mac);\n        ffStrbufDestroy(&ip->flags);\n    }\n\n    return true;\n}\n\nvoid ffInitLocalIpOptions(FFLocalIpOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰩟\");\n\n    options->showType = FF_LOCALIP_TYPE_IPV4_BIT | FF_LOCALIP_TYPE_PREFIX_LEN_BIT\n        #if !__ANDROID__ /*Permission denied*/\n            | FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT\n        #endif\n    ;\n    options->ipv6Type = FF_LOCALIP_IPV6_TYPE_AUTO;\n    ffStrbufInit(&options->namePrefix);\n}\n\nvoid ffDestroyLocalIpOptions(FFLocalIpOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n    ffStrbufDestroy(&options->namePrefix);\n}\n\nFFModuleBaseInfo ffLocalIPModuleInfo = {\n    .name = FF_LOCALIP_MODULE_NAME,\n    .description = \"List local IP addresses (v4 or v6), MAC addresses, etc\",\n    .initOptions = (void*) ffInitLocalIpOptions,\n    .destroyOptions = (void*) ffDestroyLocalIpOptions,\n    .parseJsonObject = (void*) ffParseLocalIpJsonObject,\n    .printModule = (void*) ffPrintLocalIp,\n    .generateJsonResult = (void*) ffGenerateLocalIpJsonResult,\n    .generateJsonConfig = (void*) ffGenerateLocalIpJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"IPv4 address\", \"ipv4\"},\n        {\"IPv6 address\", \"ipv6\"},\n        {\"MAC address\", \"mac\"},\n        {\"Interface name\", \"ifname\"},\n        {\"Is default route\", \"is-default-route\"},\n        {\"MTU size in bytes\", \"mtu\"},\n        {\"Link speed (formatted)\", \"speed\"},\n        {\"Interface flags\", \"flags\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/localip/localip.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_LOCALIP_MODULE_NAME \"LocalIp\"\n\nbool ffPrintLocalIp(FFLocalIpOptions* options);\nvoid ffInitLocalIpOptions(FFLocalIpOptions* options);\nvoid ffDestroyLocalIpOptions(FFLocalIpOptions* options);\n\nextern FFModuleBaseInfo ffLocalIPModuleInfo;\n"
  },
  {
    "path": "src/modules/localip/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef enum __attribute__((__packed__)) FFLocalIpType\n{\n    FF_LOCALIP_TYPE_NONE,\n    FF_LOCALIP_TYPE_LOOP_BIT        = 1 << 0,\n    FF_LOCALIP_TYPE_IPV4_BIT        = 1 << 1,\n    FF_LOCALIP_TYPE_IPV6_BIT        = 1 << 2,\n    FF_LOCALIP_TYPE_MAC_BIT         = 1 << 3,\n    FF_LOCALIP_TYPE_PREFIX_LEN_BIT  = 1 << 4,\n    FF_LOCALIP_TYPE_MTU_BIT  = 1 << 5,\n    FF_LOCALIP_TYPE_SPEED_BIT  = 1 << 6,\n    FF_LOCALIP_TYPE_FLAGS_BIT  = 1 << 7,\n\n    FF_LOCALIP_TYPE_COMPACT_BIT            = 1 << 10,\n    FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT = 1 << 11,\n    FF_LOCALIP_TYPE_ALL_IPS_BIT            = 1 << 12,\n    FF_LOCALIP_TYPE_FORCE_UNSIGNED         = UINT16_MAX,\n} FFLocalIpType;\nstatic_assert(sizeof(FFLocalIpType) == sizeof(uint16_t), \"\");\n\ntypedef enum __attribute__((__packed__)) FFLocalIpIpv6Type\n{\n    FF_LOCALIP_IPV6_TYPE_NONE          = 0b00000000,\n    FF_LOCALIP_IPV6_TYPE_GUA_BIT       = 0b00000001,\n    FF_LOCALIP_IPV6_TYPE_ULA_BIT       = 0b00000010,\n    FF_LOCALIP_IPV6_TYPE_LLA_BIT       = 0b00000100,\n    FF_LOCALIP_IPV6_TYPE_UNKNOWN_BIT   = 0b00001000, // IPv4-mapped, loopback, etc.\n    FF_LOCALIP_IPV6_TYPE_SECONDARY_BIT = 0b01000000, // Temporary, duplicated, etc.\n    FF_LOCALIP_IPV6_TYPE_PREFERRED_BIT = 0b10000000, // PREFER_SOURCE (manually set)\n    FF_LOCALIP_IPV6_TYPE_TYPE_MASK     = 0b00011111,\n    FF_LOCALIP_IPV6_TYPE_AUTO          = 0b11111111, // Used for detect option\n} FFLocalIpIpv6Type;\nstatic_assert(sizeof(FFLocalIpIpv6Type) == sizeof(uint8_t), \"\");\n\ntypedef struct FFLocalIpOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFLocalIpType showType;\n    FFLocalIpIpv6Type ipv6Type;\n    FFstrbuf namePrefix;\n} FFLocalIpOptions;\n\nstatic_assert(sizeof(FFLocalIpOptions) <= FF_OPTION_MAX_SIZE, \"FFLocalIpOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/logo/logo.c",
    "content": "#include \"common/printing.h\"\n#include \"logo/logo.h\"\n#include \"modules/logo/logo.h\"\n#include \"options/logo.h\"\n\nbool ffPrintLogo(FF_MAYBE_UNUSED FFLogoOptions* options)\n{\n    ffPrintError(FF_LOGO_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_DEFAULT, \"Supported in JSON format only\");\n    return false;\n}\n\nvoid ffParseLogoJsonObject(FF_MAYBE_UNUSED FFLogoOptions* options, FF_MAYBE_UNUSED yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (unsafe_yyjson_equals_str(key, \"type\") || unsafe_yyjson_equals_str(key, \"condition\"))\n            continue;\n\n        ffPrintError(FF_LOGO_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nbool ffGenerateLogoJsonResult(FF_MAYBE_UNUSED FFLogoOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FFLogoSize size = FF_LOGO_SIZE_UNKNOWN;\n    FFOptionsLogo* logoOptions = &instance.config.logo;\n    if (logoOptions->type == FF_LOGO_TYPE_SMALL)\n        size = FF_LOGO_SIZE_SMALL;\n    else if (logoOptions->type != FF_LOGO_TYPE_BUILTIN && logoOptions->type != FF_LOGO_TYPE_AUTO)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", \"Only 'builtin' and 'small' logo types are supported\");\n        return false;\n    }\n\n    const FFlogo* logo = logoOptions->source.length > 0\n        ? ffLogoGetBuiltinForName(&logoOptions->source, size)\n        : ffLogoGetBuiltinDetected(size);\n\n    if (!logo)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", \"No built-in logo found for the specified name/size\");\n        return false;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n\n    yyjson_mut_obj_add_str(doc, obj, \"lines\", logo->lines);\n\n    yyjson_mut_val* namesArr = yyjson_mut_obj_add_arr(doc, obj, \"names\");\n    for (size_t i = 0; i < FASTFETCH_LOGO_MAX_NAMES && logo->names[i]; i++)\n        yyjson_mut_arr_add_str(doc, namesArr, logo->names[i]);\n\n    yyjson_mut_val* colorsArr = yyjson_mut_obj_add_arr(doc, obj, \"colors\");\n    for (size_t i = 0; i < FASTFETCH_LOGO_MAX_COLORS && logo->colors[i]; i++)\n        yyjson_mut_arr_add_str(doc, colorsArr, logo->colors[i]);\n\n    yyjson_mut_obj_add_str(doc, obj, \"colorKeys\", logo->colorKeys);\n    yyjson_mut_obj_add_str(doc, obj, \"colorTitle\", logo->colorTitle);\n\n    yyjson_mut_val* typeArr = yyjson_mut_obj_add_arr(doc, obj, \"type\");\n    if (logo->type & FF_LOGO_LINE_TYPE_NORMAL)\n        yyjson_mut_arr_add_str(doc, typeArr, \"normal\");\n    if (logo->type & FF_LOGO_LINE_TYPE_SMALL_BIT)\n        yyjson_mut_arr_add_str(doc, typeArr, \"small\");\n    if (logo->type & FF_LOGO_LINE_TYPE_ALTER_BIT)\n        yyjson_mut_arr_add_str(doc, typeArr, \"alter\");\n\n    return true;\n}\n\nvoid ffInitLogoOptions(FF_MAYBE_UNUSED FFLogoOptions* options)\n{\n}\n\nvoid ffDestroyLogoOptions(FF_MAYBE_UNUSED FFLogoOptions* options)\n{\n}\n\nFFModuleBaseInfo ffLogoModuleInfo = {\n    .name = FF_LOGO_MODULE_NAME,\n    .description = \"Query built-in logo for JSON output\",\n    .initOptions = (void*) ffInitLogoOptions,\n    .destroyOptions = (void*) ffDestroyLogoOptions,\n    .parseJsonObject = (void*) ffParseLogoJsonObject,\n    .printModule = (void*) ffPrintLogo,\n    .generateJsonResult = (void*) ffGenerateLogoJsonResult,\n};\n"
  },
  {
    "path": "src/modules/logo/logo.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_LOGO_MODULE_NAME \"Logo\"\n\nbool ffPrintLogo(FFLogoOptions* options);\nvoid ffInitLogoOptions(FFLogoOptions* options);\nvoid ffDestroyLogoOptions(FFLogoOptions* options);\n\nextern FFModuleBaseInfo ffLogoModuleInfo;\n"
  },
  {
    "path": "src/modules/logo/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFLogoOptions\n{\n} FFLogoOptions;\n\nstatic_assert(sizeof(FFLogoOptions) <= FF_OPTION_MAX_SIZE, \"FFLogoOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/media/media.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/media/media.h\"\n#include \"modules/media/media.h\"\n\n#include <ctype.h>\n\nstatic inline bool shouldIgnoreChar(char c)\n{\n    return isblank(c) || c == '-' || c == '.';\n}\n\nstatic bool artistInSongTitle(const FFstrbuf* song, const FFstrbuf* artist)\n{\n    uint32_t artistIndex = 0;\n    uint32_t songIndex = 0;\n\n    while(true)\n    {\n        while(shouldIgnoreChar(song->chars[songIndex]))\n            ++songIndex;\n\n        while(shouldIgnoreChar(artist->chars[artistIndex]))\n            ++artistIndex;\n\n        if(artist->chars[artistIndex] == '\\0')\n            return true;\n\n        if(song->chars[songIndex] == '\\0')\n            return false;\n\n        if(tolower(song->chars[songIndex]) != tolower(artist->chars[artistIndex]))\n            return false;\n\n        ++artistIndex;\n        ++songIndex;\n    }\n\n    //Unreachable\n    return false;\n}\n\nbool ffPrintMedia(FFMediaOptions* options)\n{\n    const FFMediaResult* media = ffDetectMedia(false);\n\n    if(media->error.length > 0)\n    {\n        ffPrintError(FF_MEDIA_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", media->error.chars);\n        return false;\n    }\n\n    FF_STRBUF_AUTO_DESTROY songPretty = ffStrbufCreateCopy(&media->song);\n    const char* removeStrings[] = {\n        \"(Official Music Video)\", \"(Official Video)\", \"(Music Video)\", \"(Official HD Video)\",\n        \"[Official Music Video]\", \"[Official Video]\", \"[Music Video]\", \"[Official HD Video]\",\n        \"| Official Music Video\", \"| Official Video\", \"| Music Video\", \"| Official HD Video\",\n        \"[Official Audio]\", \"[Audio]\", \"(Audio)\", \"| Official Audio\", \"| Audio\", \"| OFFICIAL AUDIO\",\n        \"(Lyric Video)\", \"(Official Lyric Video)\", \"(Lyrics)\",\n        \"[Lyric Video]\", \"[Official Lyric Video]\", \"[Lyrics]\",\n        \"| Lyric Video\", \"| Official Lyric Video\", \"| Lyrics\",\n    };\n    ffStrbufRemoveStrings(&songPretty, ARRAY_SIZE(removeStrings), removeStrings);\n    ffStrbufTrimRight(&songPretty, ' ');\n\n    if(songPretty.length == 0)\n        ffStrbufAppend(&songPretty, &media->song);\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        //We don't expose artistPretty to the format, as it might be empty (when the think that the artist is already in the song title)\n        FF_STRBUF_AUTO_DESTROY artistPretty = ffStrbufCreateCopy(&media->artist);\n        ffStrbufRemoveIgnCaseEndS(&artistPretty, \" - Topic\");\n        ffStrbufRemoveIgnCaseEndS(&artistPretty, \"VEVO\");\n        ffStrbufTrimRight(&artistPretty, ' ');\n\n        if(artistInSongTitle(&songPretty, &artistPretty))\n            ffStrbufClear(&artistPretty);\n\n        ffPrintLogoAndKey(FF_MEDIA_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n        if(artistPretty.length > 0)\n        {\n            ffStrbufWriteTo(&artistPretty, stdout);\n            fputs(\" - \", stdout);\n        }\n\n        if (media->status.length > 0)\n            ffStrbufAppendF(&songPretty, \" (%s)\", media->status.chars);\n\n        ffStrbufPutTo(&songPretty, stdout);\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_MEDIA_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n            FF_ARG(songPretty, \"combined\"),\n            FF_ARG(media->song, \"title\"),\n            FF_ARG(media->artist, \"artist\"),\n            FF_ARG(media->album, \"album\"),\n            FF_ARG(media->status, \"status\"),\n            FF_ARG(media->player, \"player-name\"),\n            FF_ARG(media->playerId, \"player-id\"),\n            FF_ARG(media->url, \"url\"),\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseMediaJsonObject(FFMediaOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_MEDIA_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateMediaJsonConfig(FFMediaOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateMediaJsonResult(FF_MAYBE_UNUSED FFMediaOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    const FFMediaResult* media = ffDetectMedia(false);\n\n    if(media->error.length > 0)\n    {\n        yyjson_mut_obj_add_strbuf(doc, module, \"error\", &media->error);\n        return false;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n\n    yyjson_mut_val* song = yyjson_mut_obj_add_obj(doc, obj, \"song\");\n    yyjson_mut_obj_add_strbuf(doc, song, \"name\", &media->song);\n    yyjson_mut_obj_add_strbuf(doc, song, \"artist\", &media->artist);\n    yyjson_mut_obj_add_strbuf(doc, song, \"album\", &media->album);\n    yyjson_mut_obj_add_strbuf(doc, song, \"status\", &media->status);\n    if (media->cover.length > 0)\n        yyjson_mut_obj_add_strbuf(doc, song, \"cover\", &media->cover);\n    else\n        yyjson_mut_obj_add_null(doc, song, \"cover\");\n\n    yyjson_mut_val* player = yyjson_mut_obj_add_obj(doc, obj, \"player\");\n    yyjson_mut_obj_add_strbuf(doc, player, \"name\", &media->player);\n    yyjson_mut_obj_add_strbuf(doc, player, \"id\", &media->playerId);\n    yyjson_mut_obj_add_strbuf(doc, player, \"url\", &media->url);\n\n    return true;\n}\n\nvoid ffInitMediaOptions(FFMediaOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n}\n\nvoid ffDestroyMediaOptions(FFMediaOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffMediaModuleInfo = {\n    .name = FF_MEDIA_MODULE_NAME,\n    .description = \"Print playing song name\",\n    .initOptions = (void*) ffInitMediaOptions,\n    .destroyOptions = (void*) ffDestroyMediaOptions,\n    .parseJsonObject = (void*) ffParseMediaJsonObject,\n    .printModule = (void*) ffPrintMedia,\n    .generateJsonResult = (void*) ffGenerateMediaJsonResult,\n    .generateJsonConfig = (void*) ffGenerateMediaJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Pretty media name\", \"combined\"},\n        {\"Media name\", \"title\"},\n        {\"Artist name\", \"artist\"},\n        {\"Album name\", \"album\"},\n        {\"Status\", \"status\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/media/media.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_MEDIA_MODULE_NAME \"Media\"\n\nbool ffPrintMedia(FFMediaOptions* options);\nvoid ffInitMediaOptions(FFMediaOptions* options);\nvoid ffDestroyMediaOptions(FFMediaOptions* options);\n\nextern FFModuleBaseInfo ffMediaModuleInfo;\n"
  },
  {
    "path": "src/modules/media/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFMediaOptions\n{\n    FFModuleArgs moduleArgs;\n} FFMediaOptions;\n\nstatic_assert(sizeof(FFMediaOptions) <= FF_OPTION_MAX_SIZE, \"FFMediaOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/memory/memory.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/percent.h\"\n#include \"common/size.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/memory/memory.h\"\n#include \"modules/memory/memory.h\"\n\nbool ffPrintMemory(FFMemoryOptions* options)\n{\n    FFMemoryResult storage = {};\n    const char* error = ffDetectMemory(&storage);\n\n    if(error)\n    {\n        ffPrintError(FF_MEMORY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    FF_STRBUF_AUTO_DESTROY usedPretty = ffStrbufCreate();\n    ffSizeAppendNum(storage.bytesUsed, &usedPretty);\n\n    FF_STRBUF_AUTO_DESTROY totalPretty = ffStrbufCreate();\n    ffSizeAppendNum(storage.bytesTotal, &totalPretty);\n\n    double percentage = storage.bytesTotal == 0\n        ? 0\n        : (double) storage.bytesUsed / (double) storage.bytesTotal * 100.0;\n\n    FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_MEMORY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        if (storage.bytesTotal == 0)\n            puts(\"Disabled\");\n        else\n        {\n            FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate();\n\n            if(percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            {\n                ffPercentAppendBar(&str, percentage, options->percent, &options->moduleArgs);\n                ffStrbufAppendC(&str, ' ');\n            }\n\n            if(!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT))\n                ffStrbufAppendF(&str, \"%s / %s \", usedPretty.chars, totalPretty.chars);\n\n            if(percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n                ffPercentAppendNum(&str, percentage, options->percent, str.length > 0, &options->moduleArgs);\n\n            ffStrbufTrimRight(&str, ' ');\n            ffStrbufPutTo(&str, stdout);\n        }\n    }\n    else\n    {\n        FF_STRBUF_AUTO_DESTROY percentageNum = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n            ffPercentAppendNum(&percentageNum, percentage, options->percent, false, &options->moduleArgs);\n        FF_STRBUF_AUTO_DESTROY percentageBar = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            ffPercentAppendBar(&percentageBar, percentage, options->percent, &options->moduleArgs);\n\n        FF_PRINT_FORMAT_CHECKED(FF_MEMORY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(usedPretty, \"used\"),\n            FF_ARG(totalPretty, \"total\"),\n            FF_ARG(percentageNum, \"percentage\"),\n            FF_ARG(percentageBar, \"percentage-bar\"),\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseMemoryJsonObject(FFMemoryOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (ffPercentParseJsonObject(key, val, &options->percent))\n            continue;\n\n        ffPrintError(FF_MEMORY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateMemoryJsonConfig(FFMemoryOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    ffPercentGenerateJsonConfig(doc, module, options->percent);\n}\n\nbool ffGenerateMemoryJsonResult(FF_MAYBE_UNUSED FFMemoryOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FFMemoryResult storage = {};\n    const char* error = ffDetectMemory(&storage);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_uint(doc, obj, \"total\", storage.bytesTotal);\n    yyjson_mut_obj_add_uint(doc, obj, \"used\", storage.bytesUsed);\n\n    return true;\n}\n\nvoid ffInitMemoryOptions(FFMemoryOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n    options->percent = (FFPercentageModuleConfig) { 50, 80, 0 };\n}\n\nvoid ffDestroyMemoryOptions(FFMemoryOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffMemoryModuleInfo = {\n    .name = FF_MEMORY_MODULE_NAME,\n    .description = \"Print system memory usage info\",\n    .initOptions = (void*) ffInitMemoryOptions,\n    .destroyOptions = (void*) ffDestroyMemoryOptions,\n    .parseJsonObject = (void*) ffParseMemoryJsonObject,\n    .printModule = (void*) ffPrintMemory,\n    .generateJsonResult = (void*) ffGenerateMemoryJsonResult,\n    .generateJsonConfig = (void*) ffGenerateMemoryJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Used size\", \"used\"},\n        {\"Total size\", \"total\"},\n        {\"Percentage used (num)\", \"percentage\"},\n        {\"Percentage used (bar)\", \"percentage-bar\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/memory/memory.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_MEMORY_MODULE_NAME \"Memory\"\n\nbool ffPrintMemory(FFMemoryOptions* options);\nvoid ffInitMemoryOptions(FFMemoryOptions* options);\nvoid ffDestroyMemoryOptions(FFMemoryOptions* options);\n\nextern FFModuleBaseInfo ffMemoryModuleInfo;\n"
  },
  {
    "path": "src/modules/memory/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n#include \"common/percent.h\"\n\ntypedef struct FFMemoryOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFPercentageModuleConfig percent;\n} FFMemoryOptions;\n\nstatic_assert(sizeof(FFMemoryOptions) <= FF_OPTION_MAX_SIZE, \"FFMemoryOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/modules.c",
    "content": "#include \"modules/modules.h\"\n\nstatic FFModuleBaseInfo* A[] = {\n    NULL,\n};\n\nstatic FFModuleBaseInfo* B[] = {\n    &ffBatteryModuleInfo,\n    &ffBiosModuleInfo,\n    &ffBluetoothModuleInfo,\n    &ffBluetoothRadioModuleInfo,\n    &ffBoardModuleInfo,\n    &ffBootmgrModuleInfo,\n    &ffBreakModuleInfo,\n    &ffBrightnessModuleInfo,\n    &ffBtrfsModuleInfo,\n    NULL,\n};\n\nstatic FFModuleBaseInfo* C[] = {\n    &ffCameraModuleInfo,\n    &ffChassisModuleInfo,\n    &ffCommandModuleInfo,\n    &ffColorsModuleInfo,\n    &ffCPUModuleInfo,\n    &ffCPUCacheModuleInfo,\n    &ffCPUUsageModuleInfo,\n    &ffCursorModuleInfo,\n    &ffCustomModuleInfo,\n    NULL,\n};\n\nstatic FFModuleBaseInfo* D[] = {\n    &ffDateTimeModuleInfo,\n    &ffDEModuleInfo,\n    &ffDisplayModuleInfo,\n    &ffDiskModuleInfo,\n    &ffDiskIOModuleInfo,\n    &ffDNSModuleInfo,\n    NULL,\n};\n\nstatic FFModuleBaseInfo* E[] = {\n    &ffEditorModuleInfo,\n    NULL,\n};\n\nstatic FFModuleBaseInfo* F[] = {\n    &ffFontModuleInfo,\n    NULL,\n};\n\nstatic FFModuleBaseInfo* G[] = {\n    &ffGamepadModuleInfo,\n    &ffGPUModuleInfo,\n    NULL,\n};\n\nstatic FFModuleBaseInfo* H[] = {\n    &ffHostModuleInfo,\n    NULL,\n};\n\nstatic FFModuleBaseInfo* I[] = {\n    &ffIconsModuleInfo,\n    &ffInitSystemModuleInfo,\n    NULL,\n};\n\nstatic FFModuleBaseInfo* J[] = {\n    NULL,\n};\n\nstatic FFModuleBaseInfo* K[] = {\n    &ffKernelModuleInfo,\n    &ffKeyboardModuleInfo,\n    NULL,\n};\n\nstatic FFModuleBaseInfo* L[] = {\n    &ffLMModuleInfo,\n    &ffLoadavgModuleInfo,\n    &ffLocaleModuleInfo,\n    &ffLocalIPModuleInfo,\n    &ffLogoModuleInfo,\n    NULL,\n};\n\nstatic FFModuleBaseInfo* M[] = {\n    &ffMediaModuleInfo,\n    &ffMemoryModuleInfo,\n    &ffMonitorModuleInfo,\n    &ffMouseModuleInfo,\n    NULL,\n};\n\nstatic FFModuleBaseInfo* N[] = {\n    &ffNetIOModuleInfo,\n    NULL,\n};\n\nstatic FFModuleBaseInfo* O[] = {\n    &ffOpenCLModuleInfo,\n    &ffOpenGLModuleInfo,\n    &ffOSModuleInfo,\n    NULL,\n};\n\nstatic FFModuleBaseInfo* P[] = {\n    &ffPackagesModuleInfo,\n    &ffPhysicalDiskModuleInfo,\n    &ffPhysicalMemoryModuleInfo,\n    &ffPlayerModuleInfo,\n    &ffPowerAdapterModuleInfo,\n    &ffProcessesModuleInfo,\n    &ffPublicIPModuleInfo,\n    NULL,\n};\n\nstatic FFModuleBaseInfo* Q[] = {\n    NULL,\n};\n\nstatic FFModuleBaseInfo* R[] = {\n    NULL,\n};\n\nstatic FFModuleBaseInfo* S[] = {\n    &ffSeparatorModuleInfo,\n    &ffShellModuleInfo,\n    &ffSoundModuleInfo,\n    &ffSwapModuleInfo,\n    NULL,\n};\n\nstatic FFModuleBaseInfo* T[] = {\n    &ffTerminalModuleInfo,\n    &ffTerminalFontModuleInfo,\n    &ffTerminalSizeModuleInfo,\n    &ffTerminalThemeModuleInfo,\n    &ffTitleModuleInfo,\n    &ffThemeModuleInfo,\n    &ffTPMModuleInfo,\n    NULL,\n};\n\nstatic FFModuleBaseInfo* U[] = {\n    &ffUptimeModuleInfo,\n    &ffUsersModuleInfo,\n    NULL,\n};\n\nstatic FFModuleBaseInfo* V[] = {\n    &ffVersionModuleInfo,\n    &ffVulkanModuleInfo,\n    NULL,\n};\n\nstatic FFModuleBaseInfo* W[] = {\n    &ffWallpaperModuleInfo,\n    &ffWeatherModuleInfo,\n    &ffWMModuleInfo,\n    &ffWifiModuleInfo,\n    &ffWMThemeModuleInfo,\n    NULL,\n};\n\nstatic FFModuleBaseInfo* X[] = {\n    NULL,\n};\n\nstatic FFModuleBaseInfo* Y[] = {\n    NULL,\n};\n\nstatic FFModuleBaseInfo* Z[] = {\n    &ffZpoolModuleInfo,\n    NULL,\n};\n\nFFModuleBaseInfo** ffModuleInfos[] = {\n    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z,\n};\n"
  },
  {
    "path": "src/modules/modules.h",
    "content": "#pragma once\n\n#include \"modules/battery/battery.h\"\n#include \"modules/bios/bios.h\"\n#include \"modules/bluetooth/bluetooth.h\"\n#include \"modules/bluetoothradio/bluetoothradio.h\"\n#include \"modules/brightness/brightness.h\"\n#include \"modules/board/board.h\"\n#include \"modules/bootmgr/bootmgr.h\"\n#include \"modules/break/break.h\"\n#include \"modules/btrfs/btrfs.h\"\n#include \"modules/camera/camera.h\"\n#include \"modules/chassis/chassis.h\"\n#include \"modules/cpu/cpu.h\"\n#include \"modules/cpucache/cpucache.h\"\n#include \"modules/cpuusage/cpuusage.h\"\n#include \"modules/command/command.h\"\n#include \"modules/colors/colors.h\"\n#include \"modules/cursor/cursor.h\"\n#include \"modules/custom/custom.h\"\n#include \"modules/datetime/datetime.h\"\n#include \"modules/disk/disk.h\"\n#include \"modules/diskio/diskio.h\"\n#include \"modules/display/display.h\"\n#include \"modules/de/de.h\"\n#include \"modules/dns/dns.h\"\n#include \"modules/editor/editor.h\"\n#include \"modules/font/font.h\"\n#include \"modules/gamepad/gamepad.h\"\n#include \"modules/gpu/gpu.h\"\n#include \"modules/host/host.h\"\n#include \"modules/icons/icons.h\"\n#include \"modules/initsystem/initsystem.h\"\n#include \"modules/kernel/kernel.h\"\n#include \"modules/keyboard/keyboard.h\"\n#include \"modules/lm/lm.h\"\n#include \"modules/loadavg/loadavg.h\"\n#include \"modules/locale/locale.h\"\n#include \"modules/localip/localip.h\"\n#include \"modules/logo/logo.h\"\n#include \"modules/media/media.h\"\n#include \"modules/memory/memory.h\"\n#include \"modules/monitor/monitor.h\"\n#include \"modules/mouse/mouse.h\"\n#include \"modules/netio/netio.h\"\n#include \"modules/opengl/opengl.h\"\n#include \"modules/opencl/opencl.h\"\n#include \"modules/os/os.h\"\n#include \"modules/packages/packages.h\"\n#include \"modules/physicaldisk/physicaldisk.h\"\n#include \"modules/physicalmemory/physicalmemory.h\"\n#include \"modules/player/player.h\"\n#include \"modules/poweradapter/poweradapter.h\"\n#include \"modules/processes/processes.h\"\n#include \"modules/publicip/publicip.h\"\n#include \"modules/separator/separator.h\"\n#include \"modules/shell/shell.h\"\n#include \"modules/sound/sound.h\"\n#include \"modules/swap/swap.h\"\n#include \"modules/terminal/terminal.h\"\n#include \"modules/terminalfont/terminalfont.h\"\n#include \"modules/terminalsize/terminalsize.h\"\n#include \"modules/terminaltheme/terminaltheme.h\"\n#include \"modules/theme/theme.h\"\n#include \"modules/title/title.h\"\n#include \"modules/tpm/tpm.h\"\n#include \"modules/uptime/uptime.h\"\n#include \"modules/users/users.h\"\n#include \"modules/version/version.h\"\n#include \"modules/vulkan/vulkan.h\"\n#include \"modules/wallpaper/wallpaper.h\"\n#include \"modules/weather/weather.h\"\n#include \"modules/wifi/wifi.h\"\n#include \"modules/wm/wm.h\"\n#include \"modules/wmtheme/wmtheme.h\"\n#include \"modules/zpool/zpool.h\"\n"
  },
  {
    "path": "src/modules/monitor/monitor.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/displayserver/displayserver.h\"\n#include \"modules/monitor/monitor.h\"\n\n#include <math.h>\n\nbool ffPrintMonitor(FFMonitorOptions* options)\n{\n    const FFDisplayServerResult* result = ffConnectDisplayServer();\n\n    if(!result->displays.length)\n    {\n        ffPrintError(FF_MONITOR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No display detected\");\n        return false;\n    }\n\n    FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();\n    uint32_t index = 0;\n    FF_LIST_FOR_EACH(FFDisplayResult, display, result->displays)\n    {\n        double inch = sqrt(display->physicalWidth * display->physicalWidth + display->physicalHeight * display->physicalHeight) / 25.4;\n        double ppi = sqrt(display->width * display->width + display->height * display->height) / inch;\n        bool hdrCompatible = display->hdrStatus == FF_DISPLAY_HDR_STATUS_SUPPORTED || display->hdrStatus == FF_DISPLAY_HDR_STATUS_ENABLED;\n\n        ffStrbufClear(&key);\n        if(options->moduleArgs.key.length == 0)\n        {\n            ffStrbufAppendS(&key, FF_MONITOR_MODULE_NAME);\n            if (display->name.length > 0)\n                ffStrbufAppendF(&key, \" (%s)\", display->name.chars);\n        }\n        else\n        {\n            uint32_t moduleIndex = result->displays.length == 1 ? 0 : index + 1;\n            FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) {\n                FF_ARG(moduleIndex, \"index\"),\n                FF_ARG(display->name, \"name\"),\n                FF_ARG(options->moduleArgs.keyIcon, \"icon\"),\n            }));\n        }\n\n        if(options->moduleArgs.outputFormat.length == 0)\n        {\n            ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);\n\n            printf(\"%ux%u px\", display->width, display->height);\n            if (display->refreshRate > 0)\n                printf(\" @ %g Hz\", ((int) (display->refreshRate * 1000 + 0.5)) / 1000.0);\n            if (inch > 0)\n                printf(\" - %ux%u mm (%.2f inches, %.2f ppi)\", display->physicalWidth, display->physicalHeight, inch, ppi);\n            if (hdrCompatible)\n                fputs(\" [HDR Compatible]\", stdout);\n            putchar('\\n');\n        }\n        else\n        {\n            char buf[32];\n            if (display->serial)\n            {\n                const uint8_t* nums = (uint8_t*) &display->serial;\n                snprintf(buf, sizeof(buf), \"%2X-%2X-%2X-%2X\", nums[0], nums[1], nums[2], nums[3]);\n            }\n            else\n                buf[0] = '\\0';\n\n            FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) {\n                FF_ARG(display->name, \"name\"),\n                FF_ARG(display->width, \"width\"),\n                FF_ARG(display->height, \"height\"),\n                FF_ARG(display->physicalWidth, \"physical-width\"),\n                FF_ARG(display->physicalHeight, \"physical-height\"),\n                FF_ARG(inch, \"inch\"),\n                FF_ARG(ppi, \"ppi\"),\n                FF_ARG(display->manufactureYear, \"manufacture-year\"),\n                FF_ARG(display->manufactureWeek, \"manufacture-week\"),\n                FF_ARG(buf, \"serial\"),\n                FF_ARG(display->refreshRate, \"refresh-rate\"),\n                FF_ARG(hdrCompatible, \"hdr-compatible\"),\n            }));\n        }\n\n        ffStrbufDestroy(&display->name);\n        ++index;\n    }\n\n    return true;\n}\n\nvoid ffParseMonitorJsonObject(FFMonitorOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_MONITOR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateMonitorJsonConfig(FFMonitorOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateMonitorJsonResult(FF_MAYBE_UNUSED FFMonitorOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    yyjson_mut_obj_add_str(doc, module, \"error\", \"Monitor module is an alias of Display module\");\n    return false;\n}\n\nvoid ffInitMonitorOptions(FFMonitorOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰹑\");\n}\n\nvoid ffDestroyMonitorOptions(FFMonitorOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffMonitorModuleInfo = {\n    .name = FF_MONITOR_MODULE_NAME,\n    .description = \"Alias of Display module\",\n    .initOptions = (void*) ffInitMonitorOptions,\n    .destroyOptions = (void*) ffDestroyMonitorOptions,\n    .parseJsonObject = (void*) ffParseMonitorJsonObject,\n    .printModule = (void*) ffPrintMonitor,\n    .generateJsonResult = (void*) ffGenerateMonitorJsonResult,\n    .generateJsonConfig = (void*) ffGenerateMonitorJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Display name\", \"name\"},\n        {\"Native resolution width in pixels\", \"width\"},\n        {\"Native resolution height in pixels\", \"height\"},\n        {\"Physical width in millimeters\", \"physical-width\"},\n        {\"Physical height in millimeters\", \"physical-height\"},\n        {\"Physical diagonal length in inches\", \"inch\"},\n        {\"Pixels per inch (PPI)\", \"ppi\"},\n        {\"Year of manufacturing\", \"manufacture-year\"},\n        {\"Nth week of manufacturing in the year\", \"manufacture-week\"},\n        {\"Serial number\", \"serial\"},\n        {\"Maximum refresh rate in Hz\", \"refresh-rate\"},\n        {\"True if the display is HDR compatible\", \"hdr-compatible\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/monitor/monitor.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_MONITOR_MODULE_NAME \"Monitor\"\n\nbool ffPrintMonitor(FFMonitorOptions* options);\nvoid ffInitMonitorOptions(FFMonitorOptions* options);\nvoid ffDestroyMonitorOptions(FFMonitorOptions* options);\n\nextern FFModuleBaseInfo ffMonitorModuleInfo;\n"
  },
  {
    "path": "src/modules/monitor/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFMonitorOptions\n{\n    FFModuleArgs moduleArgs;\n} FFMonitorOptions;\n\nstatic_assert(sizeof(FFMonitorOptions) <= FF_OPTION_MAX_SIZE, \"FFMonitorOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/mouse/mouse.c",
    "content": "#include \"common/percent.h\"\n#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/mouse/mouse.h\"\n#include \"modules/mouse/mouse.h\"\n\nstatic void printDevice(FFMouseOptions* options, const FFMouseDevice* device, uint8_t index)\n{\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_MOUSE_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        ffStrbufPutTo(&device->name, stdout);\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_MOUSE_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n            FF_ARG(device->name, \"name\"),\n            FF_ARG(device->serial, \"serial\"),\n        }));\n    }\n}\n\nbool ffPrintMouse(FFMouseOptions* options)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFMouseDevice));\n\n    const char* error = ffDetectMouse(&result);\n\n    if(error)\n    {\n        ffPrintError(FF_MOUSE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if(!result.length)\n    {\n        ffPrintError(FF_MOUSE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No devices detected\");\n        return false;\n    }\n\n    FF_LIST_AUTO_DESTROY filtered = ffListCreate(sizeof(FFMouseDevice*));\n    FF_LIST_FOR_EACH(FFMouseDevice, device, result)\n    {\n        bool ignored = false;\n        FF_LIST_FOR_EACH(FFstrbuf, ignore, options->ignores)\n        {\n            if(ffStrbufStartsWithIgnCase(&device->name, ignore))\n            {\n                ignored = true;\n                break;\n            }\n        }\n        if(!ignored)\n        {\n            FFMouseDevice** ptr = ffListAdd(&filtered);\n            *ptr = device;\n        }\n    }\n\n    bool ret = true;\n    if(!filtered.length)\n    {\n        ffPrintError(FF_MOUSE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"All devices are ignored\");\n        ret = false;\n    }\n    else\n    {\n        uint8_t index = 0;\n        FF_LIST_FOR_EACH(FFMouseDevice*, pdevice, filtered)\n        {\n            FFMouseDevice* device = *pdevice;\n            printDevice(options, device, filtered.length > 1 ? ++index : 0);\n        }\n    }\n\n    FF_LIST_FOR_EACH(FFMouseDevice, device, result)\n    {\n        ffStrbufDestroy(&device->serial);\n        ffStrbufDestroy(&device->name);\n    }\n\n    return ret;\n}\n\nvoid ffParseMouseJsonObject(FFMouseOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"ignores\"))\n        {\n            yyjson_val *elem;\n            size_t eidx, emax;\n            yyjson_arr_foreach(val, eidx, emax, elem)\n            {\n                if (yyjson_is_str(elem))\n                {\n                    FFstrbuf* strbuf = ffListAdd(&options->ignores);\n                    ffStrbufInitJsonVal(strbuf, elem);\n                }\n            }\n            continue;\n        }\n\n        ffPrintError(FF_MOUSE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateMouseJsonConfig(FFMouseOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    if (options->ignores.length > 0)\n    {\n        yyjson_mut_val* ignores = yyjson_mut_obj_add_arr(doc, module, \"ignores\");\n        FF_LIST_FOR_EACH(FFstrbuf, strbuf, options->ignores)\n            yyjson_mut_arr_append(ignores, yyjson_mut_strncpy(doc, strbuf->chars, strbuf->length));\n    }\n}\n\nbool ffGenerateMouseJsonResult(FF_MAYBE_UNUSED FFMouseOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFMouseDevice));\n\n    const char* error = ffDetectMouse(&result);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n    FF_LIST_FOR_EACH(FFMouseDevice, device, result)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"serial\", &device->serial);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &device->name);\n\n        bool ignored = false;\n        FF_LIST_FOR_EACH(FFstrbuf, ignore, options->ignores)\n        {\n            if(ffStrbufStartsWithIgnCase(&device->name, ignore))\n            {\n                ignored = true;\n                break;\n            }\n        }\n        yyjson_mut_obj_add_bool(doc, obj, \"ignored\", ignored);\n    }\n\n    FF_LIST_FOR_EACH(FFMouseDevice, device, result)\n    {\n        ffStrbufDestroy(&device->serial);\n        ffStrbufDestroy(&device->name);\n    }\n\n    return true;\n}\n\nvoid ffInitMouseOptions(FFMouseOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰍽\");\n\n    ffListInit(&options->ignores, sizeof(FFstrbuf));\n}\n\nvoid ffDestroyMouseOptions(FFMouseOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n\n    FF_LIST_FOR_EACH(FFstrbuf, str, options->ignores)\n        ffStrbufDestroy(str);\n    ffListDestroy(&options->ignores);\n}\n\nFFModuleBaseInfo ffMouseModuleInfo = {\n    .name = FF_MOUSE_MODULE_NAME,\n    .description = \"List connected mouses\",\n    .initOptions = (void*) ffInitMouseOptions,\n    .destroyOptions = (void*) ffDestroyMouseOptions,\n    .parseJsonObject = (void*) ffParseMouseJsonObject,\n    .printModule = (void*) ffPrintMouse,\n    .generateJsonResult = (void*) ffGenerateMouseJsonResult,\n    .generateJsonConfig = (void*) ffGenerateMouseJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Mouse name\", \"name\"},\n        {\"Mouse serial number\", \"serial\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/mouse/mouse.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_MOUSE_MODULE_NAME \"Mouse\"\n\nbool ffPrintMouse(FFMouseOptions* options);\nvoid ffInitMouseOptions(FFMouseOptions* options);\nvoid ffDestroyMouseOptions(FFMouseOptions* options);\n\nextern FFModuleBaseInfo ffMouseModuleInfo;\n"
  },
  {
    "path": "src/modules/mouse/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n#include \"common/FFlist.h\"\n\ntypedef struct FFMouseOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFlist ignores; // List of FFstrbuf\n} FFMouseOptions;\n\nstatic_assert(sizeof(FFMouseOptions) <= FF_OPTION_MAX_SIZE, \"FFMouseOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/netio/netio.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/size.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/netio/netio.h\"\n#include \"modules/netio/netio.h\"\n\n#define FF_NETIO_DISPLAY_NAME \"Network IO\"\n\nstatic int sortInfs(const FFNetIOResult* left, const FFNetIOResult* right)\n{\n    return ffStrbufComp(&left->name, &right->name);\n}\n\nstatic void formatKey(const FFNetIOOptions* options, FFNetIOResult* inf, uint32_t index, FFstrbuf* key)\n{\n    if(options->moduleArgs.key.length == 0)\n    {\n        if(!inf->name.length)\n            ffStrbufSetF(&inf->name, \"unknown %u\", (unsigned) index);\n\n        ffStrbufSetF(key, FF_NETIO_DISPLAY_NAME \" (%s)\", inf->name.chars);\n    }\n    else\n    {\n        ffStrbufClear(key);\n        FF_PARSE_FORMAT_STRING_CHECKED(key, &options->moduleArgs.key, ((FFformatarg[]){\n            FF_ARG(index, \"index\"),\n            FF_ARG(inf->name, \"name\"),\n            FF_ARG(options->moduleArgs.keyIcon, \"icon\"),\n        }));\n    }\n}\n\nbool ffPrintNetIO(FFNetIOOptions* options)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFNetIOResult));\n    const char* error = ffDetectNetIO(&result, options);\n\n    if(error)\n    {\n        ffPrintError(FF_NETIO_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    ffListSort(&result, (const void*) sortInfs);\n\n    uint32_t index = 0;\n    FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY buffer2 = ffStrbufCreate();\n\n    FF_LIST_FOR_EACH(FFNetIOResult, inf, result)\n    {\n        formatKey(options, inf, result.length == 1 ? 0 : index + 1, &key);\n        ffStrbufClear(&buffer);\n\n        if(options->moduleArgs.outputFormat.length == 0)\n        {\n            ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);\n\n            ffSizeAppendNum(inf->rxBytes, &buffer);\n            if (!options->detectTotal) ffStrbufAppendS(&buffer, \"/s\");\n            ffStrbufAppendS(&buffer, \" (IN) - \");\n\n            ffSizeAppendNum(inf->txBytes, &buffer);\n            if (!options->detectTotal) ffStrbufAppendS(&buffer, \"/s\");\n            ffStrbufAppendS(&buffer, \" (OUT)\");\n\n            if (inf->defaultRoute && !options->defaultRouteOnly)\n                ffStrbufAppendS(&buffer, \" *\");\n            ffStrbufPutTo(&buffer, stdout);\n        }\n        else\n        {\n            ffStrbufClear(&buffer2);\n            ffSizeAppendNum(inf->rxBytes, &buffer);\n            if (!options->detectTotal) ffStrbufAppendS(&buffer, \"/s\");\n            ffSizeAppendNum(inf->txBytes, &buffer2);\n            if (!options->detectTotal) ffStrbufAppendS(&buffer2, \"/s\");\n\n            FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]){\n                FF_ARG(buffer, \"rx-size\"),\n                FF_ARG(buffer2, \"tx-size\"),\n                FF_ARG(inf->name, \"ifname\"),\n                FF_ARG(inf->defaultRoute, \"is-default-route\"),\n                FF_ARG(inf->txBytes, \"tx-bytes\"),\n                FF_ARG(inf->rxBytes, \"rx-bytes\"),\n                FF_ARG(inf->txPackets, \"tx-packets\"),\n                FF_ARG(inf->rxPackets, \"rx-packets\"),\n                FF_ARG(inf->rxErrors, \"rx-errors\"),\n                FF_ARG(inf->txErrors, \"tx-errors\"),\n                FF_ARG(inf->rxDrops, \"rx-drops\"),\n                FF_ARG(inf->txDrops, \"tx-drops\"),\n            }));\n        }\n        ++index;\n    }\n\n    FF_LIST_FOR_EACH(FFNetIOResult, inf, result)\n    {\n        ffStrbufDestroy(&inf->name);\n    }\n\n    return true;\n}\n\nvoid ffParseNetIOJsonObject(FFNetIOOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"namePrefix\"))\n        {\n            ffStrbufSetJsonVal(&options->namePrefix, val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"defaultRouteOnly\"))\n        {\n            options->defaultRouteOnly = yyjson_get_bool(val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"detectTotal\"))\n        {\n            options->detectTotal = yyjson_get_bool(val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"waitTime\"))\n        {\n            options->waitTime = (uint32_t) yyjson_get_uint(val);\n            continue;\n        }\n\n        ffPrintError(FF_NETIO_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateNetIOJsonConfig(FFNetIOOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    yyjson_mut_obj_add_strbuf(doc, module, \"namePrefix\", &options->namePrefix);\n\n    yyjson_mut_obj_add_bool(doc, module, \"defaultRouteOnly\", options->defaultRouteOnly);\n\n    yyjson_mut_obj_add_bool(doc, module, \"detectTotal\", options->detectTotal);\n\n    yyjson_mut_obj_add_uint(doc, module, \"waitTime\", options->waitTime);\n}\n\nbool ffGenerateNetIOJsonResult(FFNetIOOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFNetIOResult));\n    const char* error = ffDetectNetIO(&result, options);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n    FF_LIST_FOR_EACH(FFNetIOResult, counter, result)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &counter->name);\n        yyjson_mut_obj_add_bool(doc, obj, \"defaultRoute\", counter->defaultRoute);\n        yyjson_mut_obj_add_uint(doc, obj, \"txBytes\", counter->txBytes);\n        yyjson_mut_obj_add_uint(doc, obj, \"rxBytes\", counter->rxBytes);\n        yyjson_mut_obj_add_uint(doc, obj, \"txPackets\", counter->txPackets);\n        yyjson_mut_obj_add_uint(doc, obj, \"rxPackets\", counter->rxPackets);\n        yyjson_mut_obj_add_uint(doc, obj, \"rxErrors\", counter->rxErrors);\n        yyjson_mut_obj_add_uint(doc, obj, \"txErrors\", counter->txErrors);\n        yyjson_mut_obj_add_uint(doc, obj, \"rxDrops\", counter->rxDrops);\n        yyjson_mut_obj_add_uint(doc, obj, \"txDrops\", counter->txDrops);\n    }\n\n    FF_LIST_FOR_EACH(FFNetIOResult, inf, result)\n    {\n        ffStrbufDestroy(&inf->name);\n    }\n\n    return true;\n}\n\nvoid ffInitNetIOOptions(FFNetIOOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰾆\");\n\n    ffStrbufInit(&options->namePrefix);\n    options->defaultRouteOnly =\n        #if __ANDROID__\n            false\n        #else\n            true\n        #endif\n    ;\n    options->detectTotal = false;\n    options->waitTime = 1000;\n}\n\nvoid ffDestroyNetIOOptions(FFNetIOOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n    ffStrbufDestroy(&options->namePrefix);\n}\n\nFFModuleBaseInfo ffNetIOModuleInfo = {\n    .name = FF_NETIO_MODULE_NAME,\n    .description = \"Print network I/O throughput\",\n    .initOptions = (void*) ffInitNetIOOptions,\n    .destroyOptions = (void*) ffDestroyNetIOOptions,\n    .parseJsonObject = (void*) ffParseNetIOJsonObject,\n    .printModule = (void*) ffPrintNetIO,\n    .generateJsonResult = (void*) ffGenerateNetIOJsonResult,\n    .generateJsonConfig = (void*) ffGenerateNetIOJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Size of data received [per second] (formatted)\", \"rx-size\"},\n        {\"Size of data sent [per second] (formatted)\", \"tx-size\"},\n        {\"Interface name\", \"ifname\"},\n        {\"Is default route\", \"is-default-route\"},\n        {\"Size of data received [per second] (in bytes)\", \"rx-bytes\"},\n        {\"Size of data sent [per second] (in bytes)\", \"tx-bytes\"},\n        {\"Number of packets received [per second]\", \"rx-packets\"},\n        {\"Number of packets sent [per second]\", \"tx-packets\"},\n        {\"Number of errors received [per second]\", \"rx-errors\"},\n        {\"Number of errors sent [per second]\", \"tx-errors\"},\n        {\"Number of packets dropped when receiving [per second]\", \"rx-drops\"},\n        {\"Number of packets dropped when sending [per second]\", \"tx-drops\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/netio/netio.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_NETIO_MODULE_NAME \"NetIO\"\n\nvoid ffPrepareNetIO(FFNetIOOptions* options);\n\nbool ffPrintNetIO(FFNetIOOptions* options);\nvoid ffInitNetIOOptions(FFNetIOOptions* options);\nvoid ffDestroyNetIOOptions(FFNetIOOptions* options);\n\nextern FFModuleBaseInfo ffNetIOModuleInfo;\n"
  },
  {
    "path": "src/modules/netio/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFNetIOOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFstrbuf namePrefix;\n    uint32_t waitTime;\n    bool defaultRouteOnly;\n    bool detectTotal;\n} FFNetIOOptions;\n\nstatic_assert(sizeof(FFNetIOOptions) <= FF_OPTION_MAX_SIZE, \"FFNetIOOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/opencl/opencl.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/opencl/opencl.h\"\n#include \"detection/gpu/gpu.h\"\n#include \"modules/opencl/opencl.h\"\n\nbool ffPrintOpenCL(FFOpenCLOptions* options)\n{\n    FFOpenCLResult* result = ffDetectOpenCL();\n\n    if(result->error != NULL)\n    {\n        ffPrintError(FF_OPENCL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", result->error);\n        return false;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_OPENCL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        ffStrbufPutTo(&result->version, stdout);\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_OPENCL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n            FF_ARG(result->version, \"version\"),\n            FF_ARG(result->name, \"name\"),\n            FF_ARG(result->vendor, \"vendor\"),\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseOpenCLJsonObject(FFOpenCLOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_OPENCL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateOpenCLJsonConfig(FFOpenCLOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateOpenCLJsonResult(FF_MAYBE_UNUSED FFOpenCLOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FFOpenCLResult* result = ffDetectOpenCL();\n\n    if(result->error != NULL)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", result->error);\n        return false;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_strbuf(doc, obj, \"version\", &result->version);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &result->name);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"vendor\", &result->vendor);\n\n    yyjson_mut_val* gpus = yyjson_mut_obj_add_arr(doc, obj, \"gpus\");\n    FF_LIST_FOR_EACH(FFGPUResult, gpu, result->gpus)\n    {\n        yyjson_mut_val* gpuObj = yyjson_mut_arr_add_obj(doc, gpus);\n        yyjson_mut_obj_add_str(doc, gpuObj, \"type\", gpu->type == FF_GPU_TYPE_UNKNOWN ? \"Unknown\" : gpu->type == FF_GPU_TYPE_INTEGRATED ? \"Integrated\" : \"Discrete\");\n        yyjson_mut_obj_add_strbuf(doc, gpuObj, \"vendor\", &gpu->vendor);\n        yyjson_mut_obj_add_strbuf(doc, gpuObj, \"name\", &gpu->name);\n        yyjson_mut_obj_add_strbuf(doc, gpuObj, \"driver\", &gpu->driver);\n        yyjson_mut_obj_add_strbuf(doc, gpuObj, \"platformApi\", &gpu->platformApi);\n        if (gpu->coreCount != FF_GPU_CORE_COUNT_UNSET)\n            yyjson_mut_obj_add_int(doc, gpuObj, \"coreCount\", gpu->coreCount);\n        else\n            yyjson_mut_obj_add_null(doc, gpuObj, \"coreCount\");\n\n        yyjson_mut_obj_add_uint(doc, gpuObj, \"frequency\", gpu->frequency);\n\n        yyjson_mut_val* memoryObj = yyjson_mut_obj_add_obj(doc, gpuObj, \"memory\");\n\n        {\n            yyjson_mut_val* dedicatedMemory = yyjson_mut_obj_add_obj(doc, memoryObj, \"dedicated\");\n            if (gpu->dedicated.total != FF_GPU_VMEM_SIZE_UNSET)\n                yyjson_mut_obj_add_uint(doc, dedicatedMemory, \"total\", gpu->dedicated.total);\n            else\n                yyjson_mut_obj_add_null(doc, dedicatedMemory, \"total\");\n\n            if (gpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET)\n                yyjson_mut_obj_add_uint(doc, dedicatedMemory, \"used\", gpu->dedicated.total);\n            else\n                yyjson_mut_obj_add_null(doc, dedicatedMemory, \"used\");\n        }\n\n        {\n            yyjson_mut_val* sharedMemory = yyjson_mut_obj_add_obj(doc, memoryObj, \"shared\");\n            if (gpu->shared.total != FF_GPU_VMEM_SIZE_UNSET)\n                yyjson_mut_obj_add_uint(doc, sharedMemory, \"total\", gpu->shared.total);\n            else\n                yyjson_mut_obj_add_null(doc, sharedMemory, \"total\");\n\n            if (gpu->shared.used != FF_GPU_VMEM_SIZE_UNSET)\n                yyjson_mut_obj_add_uint(doc, sharedMemory, \"used\", gpu->shared.used);\n            else\n                yyjson_mut_obj_add_null(doc, sharedMemory, \"used\");\n        }\n\n        yyjson_mut_obj_add_uint(doc, gpuObj, \"deviceId\", gpu->deviceId);\n    }\n\n    return true;\n}\n\nvoid ffInitOpenCLOptions(FFOpenCLOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n}\n\nvoid ffDestroyOpenCLOptions(FFOpenCLOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffOpenCLModuleInfo = {\n    .name = FF_OPENCL_MODULE_NAME,\n    .description = \"Print highest OpenCL version supported by the GPU\",\n    .initOptions = (void*) ffInitOpenCLOptions,\n    .destroyOptions = (void*) ffDestroyOpenCLOptions,\n    .parseJsonObject = (void*) ffParseOpenCLJsonObject,\n    .printModule = (void*) ffPrintOpenCL,\n    .generateJsonResult = (void*) ffGenerateOpenCLJsonResult,\n    .generateJsonConfig = (void*) ffGenerateOpenCLJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Platform version\", \"version\"},\n        {\"Platform name\", \"name\"},\n        {\"Platform vendor\", \"vendor\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/opencl/opencl.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_OPENCL_MODULE_NAME \"OpenCL\"\n\nbool ffPrintOpenCL(FFOpenCLOptions* options);\nvoid ffInitOpenCLOptions(FFOpenCLOptions* options);\nvoid ffDestroyOpenCLOptions(FFOpenCLOptions* options);\n\nextern FFModuleBaseInfo ffOpenCLModuleInfo;\n"
  },
  {
    "path": "src/modules/opencl/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFOpenCLOptions\n{\n    FFModuleArgs moduleArgs;\n} FFOpenCLOptions;\n\nstatic_assert(sizeof(FFOpenCLOptions) <= FF_OPTION_MAX_SIZE, \"FFOpenCLOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/opengl/opengl.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/opengl/opengl.h\"\n#include \"modules/opengl/opengl.h\"\n\nbool ffPrintOpenGL(FFOpenGLOptions* options)\n{\n    bool success = false;\n    FFOpenGLResult result;\n    ffStrbufInit(&result.version);\n    ffStrbufInit(&result.renderer);\n    ffStrbufInit(&result.vendor);\n    ffStrbufInit(&result.slv);\n    ffStrbufInit(&result.library);\n\n    const char* error = ffDetectOpenGL(options, &result);\n    if(error)\n    {\n        ffPrintError(FF_OPENGL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n    }\n    else\n    {\n        if(options->moduleArgs.outputFormat.length == 0)\n        {\n            ffPrintLogoAndKey(FF_OPENGL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n            puts(result.version.chars);\n        }\n        else\n        {\n            FF_PRINT_FORMAT_CHECKED(FF_OPENGL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n                FF_ARG(result.version, \"version\"),\n                FF_ARG(result.renderer, \"renderer\"),\n                FF_ARG(result.vendor, \"vendor\"),\n                FF_ARG(result.slv, \"slv\"),\n                FF_ARG(result.library, \"library\"),\n            }));\n        }\n        success = true;\n    }\n\n    ffStrbufDestroy(&result.version);\n    ffStrbufDestroy(&result.renderer);\n    ffStrbufDestroy(&result.vendor);\n    ffStrbufDestroy(&result.slv);\n    ffStrbufDestroy(&result.library);\n\n    return success;\n}\n\nvoid ffParseOpenGLJsonObject(FFOpenGLOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"library\"))\n        {\n            int value;\n            const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {\n                { \"auto\", FF_OPENGL_LIBRARY_AUTO },\n                { \"egl\", FF_OPENGL_LIBRARY_EGL },\n                { \"glx\", FF_OPENGL_LIBRARY_GLX },\n                {},\n            });\n            if (error)\n                ffPrintError(FF_OPENGL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Invalid %s value: %s\", unsafe_yyjson_get_str(key), error);\n            else\n                options->library = (FFOpenGLLibrary) value;\n            continue;\n        }\n\n        ffPrintError(FF_OPENGL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateOpenGLJsonConfig(FFOpenGLOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    switch (options->library)\n    {\n    case FF_OPENGL_LIBRARY_AUTO:\n        yyjson_mut_obj_add_str(doc, module, \"library\", \"auto\");\n        break;\n    case FF_OPENGL_LIBRARY_EGL:\n        yyjson_mut_obj_add_str(doc, module, \"library\", \"egl\");\n        break;\n    case FF_OPENGL_LIBRARY_GLX:\n        yyjson_mut_obj_add_str(doc, module, \"library\", \"glx\");\n        break;\n    }\n}\n\nbool ffGenerateOpenGLJsonResult(FF_MAYBE_UNUSED FFOpenGLOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    bool success = false;\n    FFOpenGLResult result;\n    ffStrbufInit(&result.version);\n    ffStrbufInit(&result.renderer);\n    ffStrbufInit(&result.vendor);\n    ffStrbufInit(&result.slv);\n    ffStrbufInit(&result.library);\n\n    const char* error = ffDetectOpenGL(options, &result);\n    if(error != NULL)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n    }\n    else\n    {\n        yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n        yyjson_mut_obj_add_strbuf(doc, obj, \"version\", &result.version);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"renderer\", &result.renderer);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"vendor\", &result.vendor);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"slv\", &result.slv);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"library\", &result.library);\n        success = true;\n    }\n\n    ffStrbufDestroy(&result.version);\n    ffStrbufDestroy(&result.renderer);\n    ffStrbufDestroy(&result.vendor);\n    ffStrbufDestroy(&result.slv);\n    ffStrbufDestroy(&result.library);\n\n    return success;\n}\n\nvoid ffInitOpenGLOptions(FFOpenGLOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n\n    options->library = FF_OPENGL_LIBRARY_AUTO;\n}\n\nvoid ffDestroyOpenGLOptions(FFOpenGLOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffOpenGLModuleInfo = {\n    .name = FF_OPENGL_MODULE_NAME,\n    .description = \"Print highest OpenGL version supported by the GPU\",\n    .initOptions = (void*) ffInitOpenGLOptions,\n    .destroyOptions = (void*) ffDestroyOpenGLOptions,\n    .parseJsonObject = (void*) ffParseOpenGLJsonObject,\n    .printModule = (void*) ffPrintOpenGL,\n    .generateJsonResult = (void*) ffGenerateOpenGLJsonResult,\n    .generateJsonConfig = (void*) ffGenerateOpenGLJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"OpenGL version\", \"version\"},\n        {\"OpenGL renderer\", \"renderer\"},\n        {\"OpenGL vendor\", \"vendor\"},\n        {\"OpenGL shading language version\", \"slv\"},\n        {\"OpenGL library used\", \"library\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/opengl/opengl.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_OPENGL_MODULE_NAME \"OpenGL\"\n\nbool ffPrintOpenGL(FFOpenGLOptions* options);\nvoid ffInitOpenGLOptions(FFOpenGLOptions* options);\nvoid ffDestroyOpenGLOptions(FFOpenGLOptions* options);\n\nextern FFModuleBaseInfo ffOpenGLModuleInfo;\n"
  },
  {
    "path": "src/modules/opengl/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef enum __attribute__((__packed__)) FFOpenGLLibrary\n{\n    FF_OPENGL_LIBRARY_AUTO,\n    FF_OPENGL_LIBRARY_EGL,\n    FF_OPENGL_LIBRARY_GLX,\n} FFOpenGLLibrary;\n\ntypedef struct FFOpenGLOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFOpenGLLibrary library;\n} FFOpenGLOptions;\n\nstatic_assert(sizeof(FFOpenGLOptions) <= FF_OPTION_MAX_SIZE, \"FFOpenGLOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/options.h",
    "content": "#pragma once\n\n// For \"fastfetch.h\"\n\n#include \"modules/battery/option.h\"\n#include \"modules/bios/option.h\"\n#include \"modules/bluetooth/option.h\"\n#include \"modules/bluetoothradio/option.h\"\n#include \"modules/board/option.h\"\n#include \"modules/bootmgr/option.h\"\n#include \"modules/break/option.h\"\n#include \"modules/brightness/option.h\"\n#include \"modules/btrfs/option.h\"\n#include \"modules/camera/option.h\"\n#include \"modules/chassis/option.h\"\n#include \"modules/cpu/option.h\"\n#include \"modules/cpucache/option.h\"\n#include \"modules/cpuusage/option.h\"\n#include \"modules/colors/option.h\"\n#include \"modules/cursor/option.h\"\n#include \"modules/custom/option.h\"\n#include \"modules/command/option.h\"\n#include \"modules/datetime/option.h\"\n#include \"modules/de/option.h\"\n#include \"modules/disk/option.h\"\n#include \"modules/diskio/option.h\"\n#include \"modules/display/option.h\"\n#include \"modules/dns/option.h\"\n#include \"modules/editor/option.h\"\n#include \"modules/font/option.h\"\n#include \"modules/host/option.h\"\n#include \"modules/gamepad/option.h\"\n#include \"modules/gpu/option.h\"\n#include \"modules/icons/option.h\"\n#include \"modules/initsystem/option.h\"\n#include \"modules/kernel/option.h\"\n#include \"modules/keyboard/option.h\"\n#include \"modules/loadavg/option.h\"\n#include \"modules/locale/option.h\"\n#include \"modules/lm/option.h\"\n#include \"modules/localip/option.h\"\n#include \"modules/media/option.h\"\n#include \"modules/memory/option.h\"\n#include \"modules/monitor/option.h\"\n#include \"modules/mouse/option.h\"\n#include \"modules/netio/option.h\"\n#include \"modules/opengl/option.h\"\n#include \"modules/opencl/option.h\"\n#include \"modules/os/option.h\"\n#include \"modules/packages/option.h\"\n#include \"modules/physicaldisk/option.h\"\n#include \"modules/physicalmemory/option.h\"\n#include \"modules/player/option.h\"\n#include \"modules/poweradapter/option.h\"\n#include \"modules/processes/option.h\"\n#include \"modules/publicip/option.h\"\n#include \"modules/separator/option.h\"\n#include \"modules/shell/option.h\"\n#include \"modules/sound/option.h\"\n#include \"modules/swap/option.h\"\n#include \"modules/terminal/option.h\"\n#include \"modules/terminalfont/option.h\"\n#include \"modules/terminalsize/option.h\"\n#include \"modules/terminaltheme/option.h\"\n#include \"modules/theme/option.h\"\n#include \"modules/title/option.h\"\n#include \"modules/tpm/option.h\"\n#include \"modules/uptime/option.h\"\n#include \"modules/users/option.h\"\n#include \"modules/version/option.h\"\n#include \"modules/vulkan/option.h\"\n#include \"modules/wallpaper/option.h\"\n#include \"modules/weather/option.h\"\n#include \"modules/wifi/option.h\"\n#include \"modules/wm/option.h\"\n#include \"modules/wmtheme/option.h\"\n#include \"modules/zpool/option.h\"\n"
  },
  {
    "path": "src/modules/os/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFOSOptions\n{\n    FFModuleArgs moduleArgs;\n} FFOSOptions;\n\nstatic_assert(sizeof(FFOSOptions) <= FF_OPTION_MAX_SIZE, \"FFOSOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/os/os.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/option.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/os/os.h\"\n#include \"modules/os/os.h\"\n\n#include <ctype.h>\n\nstatic void buildOutputDefault(const FFOSResult* os, FFstrbuf* result)\n{\n    //Create the basic output\n    if(os->name.length > 0)\n        ffStrbufAppend(result, &os->name);\n    else if(os->prettyName.length > 0)\n        ffStrbufAppend(result, &os->prettyName);\n    else if(os->id.length > 0)\n        ffStrbufAppend(result, &os->id);\n    else\n        ffStrbufAppend(result, &instance.state.platform.sysinfo.name);\n\n    //Append code name if it is missing\n    if(os->codename.length > 0 && !ffStrbufContainIgnCase(result, &os->codename))\n    {\n        ffStrbufAppendC(result, ' ');\n        ffStrbufAppend(result, &os->codename);\n    }\n\n    //Append version if it is missing\n    if(os->versionID.length > 0 && !ffStrbufContainIgnCase(result, &os->versionID))\n    {\n        ffStrbufAppendC(result, ' ');\n        ffStrbufAppend(result, &os->versionID);\n    }\n    else if(os->versionID.length == 0 && os->version.length > 0 && !ffStrbufContainIgnCase(result, &os->version))\n    {\n        ffStrbufAppendC(result, ' ');\n        ffStrbufAppend(result, &os->version);\n    }\n\n    //Append variant if it is missing\n    if(os->variant.length > 0 && !ffStrbufContainIgnCase(result, &os->variant))\n    {\n        ffStrbufAppendS(result, \" (\");\n        ffStrbufAppend(result, &os->variant);\n        ffStrbufAppendC(result, ')');\n    }\n    else if(os->variant.length == 0 && os->variantID.length > 0 && !ffStrbufContainIgnCase(result, &os->variantID))\n    {\n        ffStrbufAppendS(result, \" (\");\n        ffStrbufAppend(result, &os->variantID);\n        ffStrbufAppendC(result, ')');\n    }\n}\n\nbool ffPrintOS(FFOSOptions* options)\n{\n    const FFOSResult* os = ffDetectOS();\n\n    if(os->name.length == 0 && os->prettyName.length == 0 && os->id.length == 0)\n    {\n        ffPrintError(FF_OS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Could not detect OS\");\n        return false;\n    }\n\n    FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();\n\n    if(options->moduleArgs.key.length == 0)\n        ffStrbufSetStatic(&key, FF_OS_MODULE_NAME);\n    else\n    {\n        FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) {\n            FF_ARG(instance.state.platform.sysinfo.name, \"sysname\"),\n            FF_ARG(os->name, \"name\"),\n            FF_ARG(options->moduleArgs.keyIcon, \"icon\"),\n        }));\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();\n\n        if(os->prettyName.length > 0)\n            ffStrbufAppend(&result, &os->prettyName);\n        else\n            buildOutputDefault(os, &result);\n\n        //Append architecture if it is missing\n        if(!ffStrbufContainIgnCase(&result, &instance.state.platform.sysinfo.architecture))\n        {\n            ffStrbufAppendC(&result, ' ');\n            ffStrbufAppend(&result, &instance.state.platform.sysinfo.architecture);\n        }\n\n        ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);\n        ffStrbufPutTo(&result, stdout);\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]){\n            FF_ARG(instance.state.platform.sysinfo.name, \"sysname\"),\n            FF_ARG(os->name, \"name\"),\n            FF_ARG(os->prettyName, \"pretty-name\"),\n            FF_ARG(os->id, \"id\"),\n            FF_ARG(os->idLike, \"id-like\"),\n            FF_ARG(os->variant, \"variant\"),\n            FF_ARG(os->variantID, \"variant-id\"),\n            FF_ARG(os->version, \"version\"),\n            FF_ARG(os->versionID, \"version-id\"),\n            FF_ARG(os->codename, \"codename\"),\n            FF_ARG(os->buildID, \"build-id\"),\n            FF_ARG(instance.state.platform.sysinfo.architecture, \"arch\"),\n            FF_ARG(instance.state.platform.sysinfo.release, \"kernel-release\"),\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseOSJsonObject(FFOSOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_OS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateOSJsonConfig(FFOSOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateOSJsonResult(FF_MAYBE_UNUSED FFOSOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    const FFOSResult* os = ffDetectOS();\n\n    if(os->name.length == 0 && os->prettyName.length == 0 && os->id.length == 0)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", \"Could not detect OS\");\n        return false;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_strbuf(doc, obj, \"buildID\", &os->buildID);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"codename\", &os->codename);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"id\", &os->id);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"idLike\", &os->idLike);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &os->name);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"prettyName\", &os->prettyName);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"variant\", &os->variant);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"variantID\", &os->variantID);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"version\", &os->version);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"versionID\", &os->versionID);\n\n    return true;\n}\n\nvoid ffInitOSOptions(FFOSOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs,\n        #ifdef _WIN32\n            \"\"\n        #elif __APPLE__\n            \"\"\n        #elif __FreeBSD__\n            \"󰣠\"\n        #elif __ANDROID__\n            \"\"\n        #elif __linux__\n            \"\"\n        #elif __sun\n            \"\"\n        #elif __OpenBSD__\n            \"\"\n        #elif __Haiku__\n            \"\"\n        #else\n            \"󰢻\"\n        #endif\n    );\n}\n\nvoid ffDestroyOSOptions(FFOSOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffOSModuleInfo = {\n    .name = FF_OS_MODULE_NAME,\n    .description = \"Print operating system name and version\",\n    .initOptions = (void*) ffInitOSOptions,\n    .destroyOptions = (void*) ffDestroyOSOptions,\n    .parseJsonObject = (void*) ffParseOSJsonObject,\n    .printModule = (void*) ffPrintOS,\n    .generateJsonResult = (void*) ffGenerateOSJsonResult,\n    .generateJsonConfig = (void*) ffGenerateOSJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Name of the kernel\", \"sysname\"},\n        {\"Name of the OS\", \"name\"},\n        {\"Pretty name of the OS, if available\", \"pretty-name\"},\n        {\"ID of the OS\", \"id\"},\n        {\"ID like of the OS\", \"id-like\"},\n        {\"Variant of the OS\", \"variant\"},\n        {\"Variant ID of the OS\", \"variant-id\"},\n        {\"Version of the OS\", \"version\"},\n        {\"Version ID of the OS\", \"version-id\"},\n        {\"Version codename of the OS\", \"codename\"},\n        {\"Build ID of the OS\", \"build-id\"},\n        {\"Architecture of the OS\", \"arch\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/os/os.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_OS_MODULE_NAME \"OS\"\n\nbool ffPrintOS(FFOSOptions* options);\nvoid ffInitOSOptions(FFOSOptions* options);\nvoid ffDestroyOSOptions(FFOSOptions* options);\n\nextern FFModuleBaseInfo ffOSModuleInfo;\n"
  },
  {
    "path": "src/modules/packages/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef enum __attribute__((__packed__)) FFPackagesFlags\n{\n    FF_PACKAGES_FLAG_NONE = 0,\n    FF_PACKAGES_FLAG_APK_BIT = 1ULL << 0,\n    FF_PACKAGES_FLAG_BREW_BIT = 1ULL << 1,\n    FF_PACKAGES_FLAG_CHOCO_BIT = 1ULL << 2,\n    FF_PACKAGES_FLAG_DPKG_BIT = 1ULL << 3,\n    FF_PACKAGES_FLAG_EMERGE_BIT = 1ULL << 4,\n    FF_PACKAGES_FLAG_EOPKG_BIT = 1ULL << 5,\n    FF_PACKAGES_FLAG_FLATPAK_BIT = 1ULL << 6,\n    FF_PACKAGES_FLAG_NIX_BIT = 1ULL << 7,\n    FF_PACKAGES_FLAG_OPKG_BIT = 1ULL << 8,\n    FF_PACKAGES_FLAG_PACMAN_BIT = 1ULL << 9,\n    FF_PACKAGES_FLAG_PALUDIS_BIT = 1ULL << 10,\n    FF_PACKAGES_FLAG_PKG_BIT = 1ULL << 11,\n    FF_PACKAGES_FLAG_PKGTOOL_BIT = 1ULL << 12,\n    FF_PACKAGES_FLAG_MACPORTS_BIT = 1ULL << 13,\n    FF_PACKAGES_FLAG_RPM_BIT = 1ULL << 14,\n    FF_PACKAGES_FLAG_SCOOP_BIT = 1ULL << 15,\n    FF_PACKAGES_FLAG_SNAP_BIT = 1ULL << 16,\n    FF_PACKAGES_FLAG_WINGET_BIT = 1ULL << 17,\n    FF_PACKAGES_FLAG_XBPS_BIT = 1ULL << 18,\n    FF_PACKAGES_FLAG_AM_BIT = 1ULL << 19,\n    FF_PACKAGES_FLAG_SORCERY_BIT = 1ULL << 20,\n    FF_PACKAGES_FLAG_LPKG_BIT = 1ULL << 21,\n    FF_PACKAGES_FLAG_LPKGBUILD_BIT = 1ULL << 22,\n    FF_PACKAGES_FLAG_GUIX_BIT = 1ULL << 23,\n    FF_PACKAGES_FLAG_LINGLONG_BIT = 1ULL << 24,\n    FF_PACKAGES_FLAG_PACSTALL_BIT = 1ULL << 25,\n    FF_PACKAGES_FLAG_MPORT_BIT = 1ULL << 26,\n    FF_PACKAGES_FLAG_PKGSRC_BIT = 1ULL << 27,\n    FF_PACKAGES_FLAG_HPKG_BIT = 1ULL << 28,\n    FF_PACKAGES_FLAG_PISI_BIT = 1ULL << 29,\n    FF_PACKAGES_FLAG_SOAR_BIT = 1ULL << 30,\n    FF_PACKAGES_FLAG_KISS_BIT = 1ULL << 31,\n    FF_PACKAGES_FLAG_MOSS_BIT = 1ULL << 32,\n    FF_PACKAGES_FLAG_FORCE_UNSIGNED = UINT64_MAX,\n} FFPackagesFlags;\nstatic_assert(sizeof(FFPackagesFlags) == sizeof(uint64_t), \"\");\n\ntypedef struct FFPackagesOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFPackagesFlags disabled;\n    bool combined;\n} FFPackagesOptions;\n\nstatic_assert(sizeof(FFPackagesOptions) <= FF_OPTION_MAX_SIZE, \"FFPackagesOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/packages/packages.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/packages/packages.h\"\n#include \"modules/packages/packages.h\"\n\nbool ffPrintPackages(FFPackagesOptions* options)\n{\n    FFPackagesResult counts = {};\n    ffStrbufInit(&counts.pacmanBranch);\n\n    const char* error = ffDetectPackages(&counts, options);\n\n    if(error)\n    {\n        ffPrintError(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    uint32_t nixAll = counts.nixDefault + counts.nixSystem + counts.nixUser;\n    uint32_t flatpakAll = counts.flatpakSystem + counts.flatpakUser;\n    uint32_t brewAll = counts.brew + counts.brewCask;\n    uint32_t guixAll = counts.guixSystem + counts.guixUser + counts.guixHome;\n    uint32_t hpkgAll = counts.hpkgSystem + counts.hpkgUser;\n    uint32_t amAll = counts.amSystem + counts.amUser;\n    uint32_t scoopAll = counts.scoopUser + counts.scoopGlobal;\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n        #define FF_PRINT_PACKAGE_NAME(var, name) {\\\n            if(counts.var > 0) \\\n            { \\\n                printf(\"%u (%s)\", counts.var, (name)); \\\n                if((all -= counts.var) > 0) \\\n                    fputs(\", \", stdout); \\\n            } \\\n        }\n\n        #define FF_PRINT_PACKAGE(name) FF_PRINT_PACKAGE_NAME(name, #name)\n\n        #define FF_PRINT_PACKAGE_ALL(name) {\\\n            if(name ## All > 0) \\\n            { \\\n                printf(\"%u (%s)\", name ## All, #name); \\\n                if((all -= name ## All) > 0) \\\n                    fputs(\", \", stdout); \\\n            } \\\n        }\n\n        uint32_t all = counts.all;\n        if(counts.pacman > 0)\n        {\n            printf(\"%u (pacman)\", counts.pacman);\n            if(counts.pacmanBranch.length > 0)\n                printf(\"[%s]\", counts.pacmanBranch.chars);\n            if((all -= counts.pacman) > 0)\n                printf(\", \");\n        };\n        FF_PRINT_PACKAGE(dpkg)\n        FF_PRINT_PACKAGE(rpm)\n        FF_PRINT_PACKAGE(emerge)\n        FF_PRINT_PACKAGE(eopkg)\n        FF_PRINT_PACKAGE(xbps)\n        if (options->combined)\n        {\n            FF_PRINT_PACKAGE_ALL(nix);\n        }\n        else\n        {\n            FF_PRINT_PACKAGE_NAME(nixSystem, \"nix-system\")\n            FF_PRINT_PACKAGE_NAME(nixUser, \"nix-user\")\n            FF_PRINT_PACKAGE_NAME(nixDefault, \"nix-default\")\n        }\n        FF_PRINT_PACKAGE(apk)\n        FF_PRINT_PACKAGE(pkg)\n        FF_PRINT_PACKAGE(pkgsrc)\n        FF_PRINT_PACKAGE(kiss)\n        if (options->combined)\n        {\n            FF_PRINT_PACKAGE_ALL(hpkg)\n        }\n        else\n        {\n            FF_PRINT_PACKAGE_NAME(hpkgSystem, counts.hpkgUser ? \"hpkg-system\" : \"hpkg\")\n            FF_PRINT_PACKAGE_NAME(hpkgUser, \"hpkg-user\")\n        }\n        if (options->combined)\n        {\n            FF_PRINT_PACKAGE_ALL(flatpak);\n        }\n        else\n        {\n            FF_PRINT_PACKAGE_NAME(flatpakSystem, counts.flatpakUser ? \"flatpak-system\" : \"flatpak\")\n            FF_PRINT_PACKAGE_NAME(flatpakUser, \"flatpak-user\")\n        }\n        FF_PRINT_PACKAGE(snap)\n        if (options->combined)\n        {\n            FF_PRINT_PACKAGE_ALL(brew);\n        }\n        else\n        {\n            FF_PRINT_PACKAGE_NAME(brew, \"brew\")\n            FF_PRINT_PACKAGE_NAME(brewCask, \"brew-cask\")\n        }\n        FF_PRINT_PACKAGE(macports)\n        if (options->combined)\n        {\n            FF_PRINT_PACKAGE_ALL(scoop);\n        }\n        else\n        {\n            FF_PRINT_PACKAGE_NAME(scoopUser, counts.scoopGlobal ? \"scoop-user\" : \"scoop\")\n            FF_PRINT_PACKAGE_NAME(scoopGlobal, \"scoop-global\")\n        }\n        FF_PRINT_PACKAGE(choco)\n        FF_PRINT_PACKAGE(pkgtool)\n        FF_PRINT_PACKAGE(paludis)\n        FF_PRINT_PACKAGE(winget)\n        FF_PRINT_PACKAGE(opkg)\n        if (options->combined)\n        {\n            FF_PRINT_PACKAGE_ALL(am);\n        }\n        else\n        {\n            FF_PRINT_PACKAGE_NAME(amSystem, \"am\")\n            FF_PRINT_PACKAGE_NAME(amUser, \"appman\")\n        }\n        FF_PRINT_PACKAGE(sorcery)\n        FF_PRINT_PACKAGE(lpkg)\n        FF_PRINT_PACKAGE(lpkgbuild)\n        if (options->combined)\n        {\n            FF_PRINT_PACKAGE_ALL(guix);\n        }\n        else\n        {\n            FF_PRINT_PACKAGE_NAME(guixSystem, \"guix-system\")\n            FF_PRINT_PACKAGE_NAME(guixUser, \"guix-user\")\n            FF_PRINT_PACKAGE_NAME(guixHome, \"guix-home\")\n        }\n        FF_PRINT_PACKAGE(linglong)\n        FF_PRINT_PACKAGE(pacstall)\n        FF_PRINT_PACKAGE(mport)\n        FF_PRINT_PACKAGE(pisi)\n        FF_PRINT_PACKAGE(soar)\n        FF_PRINT_PACKAGE(moss)\n\n        putchar('\\n');\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(counts.all, \"all\"),\n            FF_ARG(counts.pacman, \"pacman\"),\n            FF_ARG(counts.pacmanBranch, \"pacman-branch\"),\n            FF_ARG(counts.dpkg, \"dpkg\"),\n            FF_ARG(counts.rpm, \"rpm\"),\n            FF_ARG(counts.emerge, \"emerge\"),\n            FF_ARG(counts.eopkg, \"eopkg\"),\n            FF_ARG(counts.xbps, \"xbps\"),\n            FF_ARG(counts.nixSystem, \"nix-system\"),\n            FF_ARG(counts.nixUser, \"nix-user\"),\n            FF_ARG(counts.nixDefault, \"nix-default\"),\n            FF_ARG(counts.apk, \"apk\"),\n            FF_ARG(counts.pkg, \"pkg\"),\n            FF_ARG(counts.flatpakSystem, \"flatpak-system\"),\n            FF_ARG(counts.flatpakUser, \"flatpak-user\"),\n            FF_ARG(counts.snap, \"snap\"),\n            FF_ARG(counts.brew, \"brew\"),\n            FF_ARG(counts.brewCask, \"brew-cask\"),\n            FF_ARG(counts.macports, \"macports\"),\n            FF_ARG(counts.scoopUser, \"scoop-user\"),\n            FF_ARG(counts.scoopGlobal, \"scoop-global\"),\n            FF_ARG(counts.choco, \"choco\"),\n            FF_ARG(counts.pkgtool, \"pkgtool\"),\n            FF_ARG(counts.paludis, \"paludis\"),\n            FF_ARG(counts.winget, \"winget\"),\n            FF_ARG(counts.opkg, \"opkg\"),\n            FF_ARG(counts.amSystem, \"am-system\"),\n            FF_ARG(counts.sorcery, \"sorcery\"),\n            FF_ARG(counts.lpkg, \"lpkg\"),\n            FF_ARG(counts.lpkgbuild, \"lpkgbuild\"),\n            FF_ARG(counts.guixSystem, \"guix-system\"),\n            FF_ARG(counts.guixUser, \"guix-user\"),\n            FF_ARG(counts.guixHome, \"guix-home\"),\n            FF_ARG(counts.linglong, \"linglong\"),\n            FF_ARG(counts.pacstall, \"pacstall\"),\n            FF_ARG(counts.mport, \"mport\"),\n            FF_ARG(counts.amUser, \"am-user\"),\n            FF_ARG(counts.pkgsrc, \"pkgsrc\"),\n            FF_ARG(counts.hpkgSystem, \"hpkg-system\"),\n            FF_ARG(counts.hpkgUser, \"hpkg-user\"),\n            FF_ARG(counts.pisi, \"pisi\"),\n            FF_ARG(counts.soar, \"soar\"),\n            FF_ARG(counts.kiss, \"kiss\"),\n            FF_ARG(counts.moss, \"moss\"),\n            FF_ARG(nixAll, \"nix-all\"),\n            FF_ARG(flatpakAll, \"flatpak-all\"),\n            FF_ARG(brewAll, \"brew-all\"),\n            FF_ARG(guixAll, \"guix-all\"),\n            FF_ARG(hpkgAll, \"hpkg-all\"),\n        }));\n    }\n\n    ffStrbufDestroy(&counts.pacmanBranch);\n\n    return true;\n}\n\nvoid ffParsePackagesJsonObject(FFPackagesOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"disabled\"))\n        {\n            if (!yyjson_is_null(val) && !yyjson_is_arr(val))\n            {\n                ffPrintError(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Invalid JSON value for %s\", unsafe_yyjson_get_str(key));\n                continue;\n            }\n\n            options->disabled = FF_PACKAGES_FLAG_NONE;\n\n            if (yyjson_is_arr(val))\n            {\n                yyjson_val* flagObj;\n                size_t flagIdx, flagMax;\n                yyjson_arr_foreach(val, flagIdx, flagMax, flagObj)\n                {\n                    if (!yyjson_is_str(flagObj))\n                    {\n                        ffPrintError(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Invalid JSON value for %s\", unsafe_yyjson_get_str(key));\n                        continue;\n                    }\n                    const char* flag = unsafe_yyjson_get_str(flagObj);\n\n                    #define FF_TEST_PACKAGE_NAME(name) else if (ffStrEqualsIgnCase(flag, #name)) { options->disabled |= FF_PACKAGES_FLAG_ ## name ## _BIT; }\n                    switch (toupper(flag[0]))\n                    {\n                        case 'A': if (false);\n                            FF_TEST_PACKAGE_NAME(APK)\n                            FF_TEST_PACKAGE_NAME(AM)\n                            break;\n                        case 'B': if (false);\n                            FF_TEST_PACKAGE_NAME(BREW)\n                            break;\n                        case 'C': if (false);\n                            FF_TEST_PACKAGE_NAME(CHOCO)\n                            break;\n                        case 'D': if (false);\n                            FF_TEST_PACKAGE_NAME(DPKG)\n                            break;\n                        case 'E': if (false);\n                            FF_TEST_PACKAGE_NAME(EMERGE)\n                            FF_TEST_PACKAGE_NAME(EOPKG)\n                            break;\n                        case 'F': if (false);\n                            FF_TEST_PACKAGE_NAME(FLATPAK)\n                            break;\n                        case 'G': if (false);\n                            FF_TEST_PACKAGE_NAME(GUIX)\n                            break;\n                        case 'H': if (false);\n                            FF_TEST_PACKAGE_NAME(HPKG)\n                            break;\n                        case 'K': if (false);\n                            FF_TEST_PACKAGE_NAME(KISS)\n                            break;\n                        case 'L': if (false);\n                            FF_TEST_PACKAGE_NAME(LPKG)\n                            FF_TEST_PACKAGE_NAME(LPKGBUILD)\n                            FF_TEST_PACKAGE_NAME(LINGLONG)\n                            break;\n                        case 'M': if (false);\n                            FF_TEST_PACKAGE_NAME(MACPORTS)\n                            FF_TEST_PACKAGE_NAME(MPORT)\n                            FF_TEST_PACKAGE_NAME(MOSS)\n                            break;\n                        case 'N': if (false);\n                            FF_TEST_PACKAGE_NAME(NIX)\n                            break;\n                        case 'O': if (false);\n                            FF_TEST_PACKAGE_NAME(OPKG)\n                            break;\n                        case 'P': if (false);\n                            FF_TEST_PACKAGE_NAME(PACMAN)\n                            FF_TEST_PACKAGE_NAME(PACSTALL)\n                            FF_TEST_PACKAGE_NAME(PALUDIS)\n                            FF_TEST_PACKAGE_NAME(PISI)\n                            FF_TEST_PACKAGE_NAME(PKG)\n                            FF_TEST_PACKAGE_NAME(PKGTOOL)\n                            FF_TEST_PACKAGE_NAME(PKGSRC)\n                            break;\n                        case 'R': if (false);\n                            FF_TEST_PACKAGE_NAME(RPM)\n                            break;\n                        case 'S': if (false);\n                            FF_TEST_PACKAGE_NAME(SCOOP)\n                            FF_TEST_PACKAGE_NAME(SNAP)\n                            FF_TEST_PACKAGE_NAME(SOAR)\n                            FF_TEST_PACKAGE_NAME(SORCERY)\n                            break;\n                        case 'W': if (false);\n                            FF_TEST_PACKAGE_NAME(WINGET)\n                            break;\n                        case 'X': if (false);\n                            FF_TEST_PACKAGE_NAME(XBPS)\n                            break;\n                    }\n                    #undef FF_TEST_PACKAGE_NAME\n                }\n                continue;\n            }\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"combined\"))\n        {\n            options->combined = yyjson_get_bool(val);\n            continue;\n        }\n\n        ffPrintError(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGeneratePackagesJsonConfig(FFPackagesOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"disabled\");\n    #define FF_TEST_PACKAGE_NAME(name) else if ((options->disabled & FF_PACKAGES_FLAG_ ## name ## _BIT)) { \\\n        ffStrbufSetS(&buf, #name); \\\n        ffStrbufLowerCase(&buf); \\\n        yyjson_mut_arr_add_strbuf(doc, arr, &buf); \\\n    }\n    if (false);\n    FF_TEST_PACKAGE_NAME(AM)\n    FF_TEST_PACKAGE_NAME(APK)\n    FF_TEST_PACKAGE_NAME(BREW)\n    FF_TEST_PACKAGE_NAME(CHOCO)\n    FF_TEST_PACKAGE_NAME(DPKG)\n    FF_TEST_PACKAGE_NAME(EMERGE)\n    FF_TEST_PACKAGE_NAME(EOPKG)\n    FF_TEST_PACKAGE_NAME(FLATPAK)\n    FF_TEST_PACKAGE_NAME(GUIX)\n    FF_TEST_PACKAGE_NAME(HPKG)\n    FF_TEST_PACKAGE_NAME(KISS)\n    FF_TEST_PACKAGE_NAME(LINGLONG)\n    FF_TEST_PACKAGE_NAME(LPKG)\n    FF_TEST_PACKAGE_NAME(LPKGBUILD)\n    FF_TEST_PACKAGE_NAME(MACPORTS)\n    FF_TEST_PACKAGE_NAME(MPORT)\n    FF_TEST_PACKAGE_NAME(MOSS)\n    FF_TEST_PACKAGE_NAME(NIX)\n    FF_TEST_PACKAGE_NAME(OPKG)\n    FF_TEST_PACKAGE_NAME(PACMAN)\n    FF_TEST_PACKAGE_NAME(PACSTALL)\n    FF_TEST_PACKAGE_NAME(PALUDIS)\n    FF_TEST_PACKAGE_NAME(PISI)\n    FF_TEST_PACKAGE_NAME(PKG)\n    FF_TEST_PACKAGE_NAME(PKGTOOL)\n    FF_TEST_PACKAGE_NAME(PKGSRC)\n    FF_TEST_PACKAGE_NAME(RPM)\n    FF_TEST_PACKAGE_NAME(SCOOP)\n    FF_TEST_PACKAGE_NAME(SNAP)\n    FF_TEST_PACKAGE_NAME(SOAR)\n    FF_TEST_PACKAGE_NAME(SORCERY)\n    FF_TEST_PACKAGE_NAME(WINGET)\n    FF_TEST_PACKAGE_NAME(XBPS)\n    #undef FF_TEST_PACKAGE_NAME\n\n    yyjson_mut_obj_add_bool(doc, module, \"combined\", options->combined);\n}\n\nbool ffGeneratePackagesJsonResult(FF_MAYBE_UNUSED FFPackagesOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FFPackagesResult counts = {};\n    ffStrbufInit(&counts.pacmanBranch);\n\n    const char* error = ffDetectPackages(&counts, options);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n\n    #define FF_APPEND_PACKAGE_COUNT(name) yyjson_mut_obj_add_uint(doc, obj, #name, counts.name);\n\n    FF_APPEND_PACKAGE_COUNT(all)\n    FF_APPEND_PACKAGE_COUNT(amSystem)\n    FF_APPEND_PACKAGE_COUNT(amUser)\n    FF_APPEND_PACKAGE_COUNT(apk)\n    FF_APPEND_PACKAGE_COUNT(brew)\n    FF_APPEND_PACKAGE_COUNT(brewCask)\n    FF_APPEND_PACKAGE_COUNT(choco)\n    FF_APPEND_PACKAGE_COUNT(dpkg)\n    FF_APPEND_PACKAGE_COUNT(emerge)\n    FF_APPEND_PACKAGE_COUNT(eopkg)\n    FF_APPEND_PACKAGE_COUNT(flatpakSystem)\n    FF_APPEND_PACKAGE_COUNT(flatpakUser)\n    FF_APPEND_PACKAGE_COUNT(guixSystem)\n    FF_APPEND_PACKAGE_COUNT(guixUser)\n    FF_APPEND_PACKAGE_COUNT(guixHome)\n    FF_APPEND_PACKAGE_COUNT(hpkgSystem)\n    FF_APPEND_PACKAGE_COUNT(hpkgUser)\n    FF_APPEND_PACKAGE_COUNT(kiss)\n    FF_APPEND_PACKAGE_COUNT(linglong)\n    FF_APPEND_PACKAGE_COUNT(macports)\n    FF_APPEND_PACKAGE_COUNT(mport)\n    FF_APPEND_PACKAGE_COUNT(moss)\n    FF_APPEND_PACKAGE_COUNT(nixDefault)\n    FF_APPEND_PACKAGE_COUNT(nixSystem)\n    FF_APPEND_PACKAGE_COUNT(nixUser)\n    FF_APPEND_PACKAGE_COUNT(opkg)\n    FF_APPEND_PACKAGE_COUNT(pacman)\n    FF_APPEND_PACKAGE_COUNT(pacstall)\n    FF_APPEND_PACKAGE_COUNT(paludis)\n    FF_APPEND_PACKAGE_COUNT(pisi)\n    FF_APPEND_PACKAGE_COUNT(pkg)\n    FF_APPEND_PACKAGE_COUNT(pkgtool)\n    FF_APPEND_PACKAGE_COUNT(pkgsrc)\n    FF_APPEND_PACKAGE_COUNT(rpm)\n    FF_APPEND_PACKAGE_COUNT(scoopUser)\n    FF_APPEND_PACKAGE_COUNT(scoopGlobal)\n    FF_APPEND_PACKAGE_COUNT(snap)\n    FF_APPEND_PACKAGE_COUNT(soar)\n    FF_APPEND_PACKAGE_COUNT(sorcery)\n    FF_APPEND_PACKAGE_COUNT(winget)\n    FF_APPEND_PACKAGE_COUNT(xbps)\n    yyjson_mut_obj_add_strbuf(doc, obj, \"pacmanBranch\", &counts.pacmanBranch);\n\n    return true;\n}\n\nvoid ffInitPackagesOptions(FFPackagesOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰏖\");\n\n    options->disabled = FF_PACKAGES_DISABLE_LIST;\n    options->combined = false;\n}\n\nvoid ffDestroyPackagesOptions(FFPackagesOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffPackagesModuleInfo = {\n    .name = FF_PACKAGES_MODULE_NAME,\n    .description = \"List installed package managers and count of installed packages\",\n    .initOptions = (void*) ffInitPackagesOptions,\n    .destroyOptions = (void*) ffDestroyPackagesOptions,\n    .parseJsonObject = (void*) ffParsePackagesJsonObject,\n    .printModule = (void*) ffPrintPackages,\n    .generateJsonResult = (void*) ffGeneratePackagesJsonResult,\n    .generateJsonConfig = (void*) ffGeneratePackagesJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Number of all packages\", \"all\"},\n        {\"Number of pacman packages\", \"pacman\"},\n        {\"Pacman branch on manjaro\", \"pacman-branch\"},\n        {\"Number of dpkg packages\", \"dpkg\"},\n        {\"Number of rpm packages\", \"rpm\"},\n        {\"Number of emerge packages\", \"emerge\"},\n        {\"Number of eopkg packages\", \"eopkg\"},\n        {\"Number of xbps packages\", \"xbps\"},\n        {\"Number of nix-system packages\", \"nix-system\"},\n        {\"Number of nix-user packages\", \"nix-user\"},\n        {\"Number of nix-default packages\", \"nix-default\"},\n        {\"Number of apk packages\", \"apk\"},\n        {\"Number of pkg packages\", \"pkg\"},\n        {\"Number of flatpak-system app packages\", \"flatpak-system\"},\n        {\"Number of flatpak-user app packages\", \"flatpak-user\"},\n        {\"Number of snap packages\", \"snap\"},\n        {\"Number of brew packages\", \"brew\"},\n        {\"Number of brew-cask packages\", \"brew-cask\"},\n        {\"Number of macports packages\", \"macports\"},\n        {\"Number of scoop-user packages\", \"scoop-user\"},\n        {\"Number of scoop-global packages\", \"scoop-global\"},\n        {\"Number of choco packages\", \"choco\"},\n        {\"Number of pkgtool packages\", \"pkgtool\"},\n        {\"Number of paludis packages\", \"paludis\"},\n        {\"Number of winget packages\", \"winget\"},\n        {\"Number of opkg packages\", \"opkg\"},\n        {\"Number of am-system packages\", \"am-system\"},\n        {\"Number of sorcery packages\", \"sorcery\"},\n        {\"Number of lpkg packages\", \"lpkg\"},\n        {\"Number of lpkgbuild packages\", \"lpkgbuild\"},\n        {\"Number of guix-system packages\", \"guix-system\"},\n        {\"Number of guix-user packages\", \"guix-user\"},\n        {\"Number of guix-home packages\", \"guix-home\"},\n        {\"Number of linglong packages\", \"linglong\"},\n        {\"Number of pacstall packages\", \"pacstall\"},\n        {\"Number of mport packages\", \"mport\"},\n        {\"Number of am-user (aka appman) packages\", \"am-user\"},\n        {\"Number of pkgsrc packages\", \"pkgsrc\"},\n        {\"Number of hpkg-system packages\", \"hpkg-system\"},\n        {\"Number of hpkg-user packages\", \"hpkg-user\"},\n        {\"Number of pisi packages\", \"pisi\"},\n        {\"Number of soar packages\", \"soar\"},\n        {\"Number of kiss packages\", \"kiss\"},\n        {\"Number of moss packages\", \"moss\"},\n        {\"Total number of all nix packages\", \"nix-all\"},\n        {\"Total number of all flatpak app packages\", \"flatpak-all\"},\n        {\"Total number of all brew packages\", \"brew-all\"},\n        {\"Total number of all guix packages\", \"guix-all\"},\n        {\"Total number of all hpkg packages\", \"hpkg-all\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/packages/packages.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_PACKAGES_MODULE_NAME \"Packages\"\n\nbool ffPrintPackages(FFPackagesOptions* options);\nvoid ffInitPackagesOptions(FFPackagesOptions* options);\nvoid ffDestroyPackagesOptions(FFPackagesOptions* options);\n\nextern FFModuleBaseInfo ffPackagesModuleInfo;\n"
  },
  {
    "path": "src/modules/physicaldisk/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFPhysicalDiskOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFstrbuf namePrefix;\n    bool temp;\n    FFColorRangeConfig tempConfig;\n} FFPhysicalDiskOptions;\n\nstatic_assert(sizeof(FFPhysicalDiskOptions) <= FF_OPTION_MAX_SIZE, \"FFPhysicalDiskOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/physicaldisk/physicaldisk.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/temps.h\"\n#include \"common/size.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/physicaldisk/physicaldisk.h\"\n#include \"modules/physicaldisk/physicaldisk.h\"\n\n#define FF_PHYSICALDISK_DISPLAY_NAME \"Physical Disk\"\n\nstatic int sortDevices(const FFPhysicalDiskResult* left, const FFPhysicalDiskResult* right)\n{\n    return ffStrbufComp(&left->name, &right->name);\n}\n\nstatic void formatKey(const FFPhysicalDiskOptions* options, FFPhysicalDiskResult* dev, uint32_t index, FFstrbuf* key)\n{\n    if(options->moduleArgs.key.length == 0)\n    {\n        ffStrbufSetF(key, FF_PHYSICALDISK_DISPLAY_NAME \" (%s)\", dev->name.length ? dev->name.chars : dev->devPath.chars);\n    }\n    else\n    {\n        ffStrbufClear(key);\n        FF_PARSE_FORMAT_STRING_CHECKED(key, &options->moduleArgs.key, ((FFformatarg[]){\n            FF_ARG(index, \"index\"),\n            FF_ARG(dev->name, \"name\"),\n            FF_ARG(dev->devPath, \"dev-path\"),\n            FF_ARG(options->moduleArgs.keyIcon, \"icon\"),\n        }));\n    }\n}\n\nbool ffPrintPhysicalDisk(FFPhysicalDiskOptions* options)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFPhysicalDiskResult));\n    const char* error = ffDetectPhysicalDisk(&result, options);\n\n    if(error)\n    {\n        ffPrintError(FF_PHYSICALDISK_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    ffListSort(&result, (const void*) sortDevices);\n\n    uint32_t index = 0;\n    FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n\n    FF_LIST_FOR_EACH(FFPhysicalDiskResult, dev, result)\n    {\n        formatKey(options, dev, result.length == 1 ? 0 : index + 1, &key);\n        ffStrbufClear(&buffer);\n        ffSizeAppendNum(dev->size, &buffer);\n\n        const char* physicalType = dev->type & FF_PHYSICALDISK_TYPE_HDD\n            ? \"HDD\"\n            : dev->type & FF_PHYSICALDISK_TYPE_SSD\n                ? \"SSD\"\n                : \"\";\n        const char* removableType = dev->type & FF_PHYSICALDISK_TYPE_REMOVABLE\n            ? \"Removable\"\n            : dev->type & FF_PHYSICALDISK_TYPE_FIXED\n                ? \"Fixed\"\n                : \"\";\n        const char* readOnlyType = dev->type & FF_PHYSICALDISK_TYPE_READONLY\n            ? \"Read-only\"\n            : \"\";\n\n        if(options->moduleArgs.outputFormat.length == 0)\n        {\n            ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);\n\n            if (physicalType[0] || removableType[0] || readOnlyType[0])\n            {\n                ffStrbufAppendS(&buffer, \" [\");\n                if (physicalType[0])\n                    ffStrbufAppendS(&buffer, physicalType);\n                if (removableType[0])\n                {\n                    if (buffer.chars[buffer.length - 1] != '[')\n                        ffStrbufAppendS(&buffer, \", \");\n                    ffStrbufAppendS(&buffer, removableType);\n                }\n                if (readOnlyType[0])\n                {\n                    if (buffer.chars[buffer.length - 1] != '[')\n                        ffStrbufAppendS(&buffer, \", \");\n                    ffStrbufAppendS(&buffer, readOnlyType);\n                }\n                ffStrbufAppendC(&buffer, ']');\n            }\n\n            if (dev->temperature != FF_PHYSICALDISK_TEMP_UNSET)\n            {\n                if(buffer.length > 0)\n                    ffStrbufAppendS(&buffer, \" - \");\n\n                ffTempsAppendNum(dev->temperature, &buffer, options->tempConfig, &options->moduleArgs);\n            }\n            ffStrbufPutTo(&buffer, stdout);\n        }\n        else\n        {\n            FF_STRBUF_AUTO_DESTROY tempStr = ffStrbufCreate();\n            ffTempsAppendNum(dev->temperature, &tempStr, options->tempConfig, &options->moduleArgs);\n            if (dev->type & FF_PHYSICALDISK_TYPE_READWRITE)\n                readOnlyType = \"Read-write\";\n            FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]){\n                FF_ARG(buffer, \"size\"),\n                FF_ARG(dev->name, \"name\"),\n                FF_ARG(dev->interconnect, \"interconnect\"),\n                FF_ARG(dev->devPath, \"dev-path\"),\n                FF_ARG(dev->serial, \"serial\"),\n                FF_ARG(physicalType, \"physical-type\"),\n                FF_ARG(removableType, \"removable-type\"),\n                FF_ARG(readOnlyType, \"readonly-type\"),\n                FF_ARG(dev->revision, \"revision\"),\n                FF_ARG(tempStr, \"temperature\"),\n            }));\n        }\n        ++index;\n    }\n\n    FF_LIST_FOR_EACH(FFPhysicalDiskResult, dev, result)\n    {\n        ffStrbufDestroy(&dev->name);\n        ffStrbufDestroy(&dev->interconnect);\n        ffStrbufDestroy(&dev->devPath);\n        ffStrbufDestroy(&dev->serial);\n        ffStrbufDestroy(&dev->revision);\n    }\n\n    return true;\n}\n\nvoid ffParsePhysicalDiskJsonObject(FFPhysicalDiskOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"namePrefix\"))\n        {\n            ffStrbufSetJsonVal(&options->namePrefix, val);\n            continue;\n        }\n\n        if (ffTempsParseJsonObject(key, val, &options->temp, &options->tempConfig))\n            continue;\n\n        ffPrintError(FF_PHYSICALDISK_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGeneratePhysicalDiskJsonConfig(FFPhysicalDiskOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    yyjson_mut_obj_add_strbuf(doc, module, \"namePrefix\", &options->namePrefix);\n\n    ffTempsGenerateJsonConfig(doc, module, options->temp, options->tempConfig);\n}\n\nbool ffGeneratePhysicalDiskJsonResult(FFPhysicalDiskOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFPhysicalDiskResult));\n    const char* error = ffDetectPhysicalDisk(&result, options);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n    FF_LIST_FOR_EACH(FFPhysicalDiskResult, dev, result)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &dev->name);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"devPath\", &dev->devPath);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"interconnect\", &dev->interconnect);\n\n        if (dev->type & FF_PHYSICALDISK_TYPE_HDD)\n            yyjson_mut_obj_add_str(doc, obj, \"kind\", \"HDD\");\n        else if (dev->type & FF_PHYSICALDISK_TYPE_SSD)\n            yyjson_mut_obj_add_str(doc, obj, \"kind\", \"SSD\");\n        else\n            yyjson_mut_obj_add_null(doc, obj, \"kind\");\n\n        yyjson_mut_obj_add_uint(doc, obj, \"size\", dev->size);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"serial\", &dev->serial);\n\n        if (dev->type & FF_PHYSICALDISK_TYPE_REMOVABLE)\n            yyjson_mut_obj_add_bool(doc, obj, \"removable\", true);\n        else if (dev->type & FF_PHYSICALDISK_TYPE_FIXED)\n            yyjson_mut_obj_add_bool(doc, obj, \"removable\", false);\n        else\n            yyjson_mut_obj_add_null(doc, obj, \"removable\");\n\n        if (dev->type & FF_PHYSICALDISK_TYPE_READONLY)\n            yyjson_mut_obj_add_bool(doc, obj, \"readOnly\", true);\n        else if (dev->type & FF_PHYSICALDISK_TYPE_READWRITE)\n            yyjson_mut_obj_add_bool(doc, obj, \"readOnly\", false);\n        else\n            yyjson_mut_obj_add_null(doc, obj, \"readOnly\");\n\n        yyjson_mut_obj_add_strbuf(doc, obj, \"revision\", &dev->revision);\n\n        if (dev->temperature != FF_PHYSICALDISK_TEMP_UNSET)\n            yyjson_mut_obj_add_real(doc, obj, \"temperature\", dev->temperature);\n        else\n            yyjson_mut_obj_add_null(doc, obj, \"temperature\");\n    }\n\n    FF_LIST_FOR_EACH(FFPhysicalDiskResult, dev, result)\n    {\n        ffStrbufDestroy(&dev->name);\n        ffStrbufDestroy(&dev->interconnect);\n        ffStrbufDestroy(&dev->devPath);\n        ffStrbufDestroy(&dev->serial);\n        ffStrbufDestroy(&dev->revision);\n    }\n\n    return true;\n}\n\nvoid ffInitPhysicalDiskOptions(FFPhysicalDiskOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰋊\");\n\n    ffStrbufInit(&options->namePrefix);\n    options->temp = false;\n    options->tempConfig = (FFColorRangeConfig) { 50, 70 };\n}\n\nvoid ffDestroyPhysicalDiskOptions(FFPhysicalDiskOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n    ffStrbufDestroy(&options->namePrefix);\n}\n\nFFModuleBaseInfo ffPhysicalDiskModuleInfo = {\n    .name = FF_PHYSICALDISK_MODULE_NAME,\n    .description = \"Print physical disk information\",\n    .initOptions = (void*) ffInitPhysicalDiskOptions,\n    .destroyOptions = (void*) ffDestroyPhysicalDiskOptions,\n    .parseJsonObject = (void*) ffParsePhysicalDiskJsonObject,\n    .printModule = (void*) ffPrintPhysicalDisk,\n    .generateJsonResult = (void*) ffGeneratePhysicalDiskJsonResult,\n    .generateJsonConfig = (void*) ffGeneratePhysicalDiskJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Device size (formatted)\", \"size\"},\n        {\"Device name\", \"name\"},\n        {\"Device interconnect type\", \"interconnect\"},\n        {\"Device raw file path\", \"dev-path\"},\n        {\"Serial number\", \"serial\"},\n        {\"Device kind (SSD or HDD)\", \"physical-type\"},\n        {\"Device kind (Removable or Fixed)\", \"removable-type\"},\n        {\"Device kind (Read-only or Read-write)\", \"readonly-type\"},\n        {\"Product revision\", \"revision\"},\n        {\"Device temperature (formatted)\", \"temperature\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/physicaldisk/physicaldisk.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_PHYSICALDISK_MODULE_NAME \"PhysicalDisk\"\n\nbool ffPrintPhysicalDisk(FFPhysicalDiskOptions* options);\nvoid ffInitPhysicalDiskOptions(FFPhysicalDiskOptions* options);\nvoid ffDestroyPhysicalDiskOptions(FFPhysicalDiskOptions* options);\n\nextern FFModuleBaseInfo ffPhysicalDiskModuleInfo;\n"
  },
  {
    "path": "src/modules/physicalmemory/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFPhysicalMemoryOptions\n{\n    FFModuleArgs moduleArgs;\n    bool showEmptySlots;\n} FFPhysicalMemoryOptions;\n\nstatic_assert(sizeof(FFPhysicalMemoryOptions) <= FF_OPTION_MAX_SIZE, \"FFPhysicalMemoryOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/physicalmemory/physicalmemory.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/size.h\"\n#include \"detection/physicalmemory/physicalmemory.h\"\n#include \"modules/physicalmemory/physicalmemory.h\"\n\n#define FF_PHYSICALMEMORY_DISPLAY_NAME \"Physical Memory\"\n\nbool ffPrintPhysicalMemory(FFPhysicalMemoryOptions* options)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFPhysicalMemoryResult));\n    const char* error = ffDetectPhysicalMemory(&result);\n\n    if(error)\n    {\n        ffPrintError(FF_PHYSICALMEMORY_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if (result.length == 0)\n    {\n        ffPrintError(FF_PHYSICALMEMORY_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No physical memory detected\");\n        return false;\n    }\n\n    FF_LIST_AUTO_DESTROY filtered = ffListCreate(sizeof(FFPhysicalMemoryResult*));\n    FF_LIST_FOR_EACH(FFPhysicalMemoryResult, device, result)\n    {\n        if (!options->showEmptySlots && !device->installed)\n            continue;\n\n        *(FFPhysicalMemoryResult**) ffListAdd(&filtered) = device;\n    }\n\n    if (filtered.length == 0)\n    {\n        ffPrintError(FF_PHYSICALMEMORY_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No installed physical memory detected\");\n        return false;\n    }\n\n    FF_STRBUF_AUTO_DESTROY prettySize = ffStrbufCreate();\n\n    for (uint32_t i = 0; i < filtered.length; ++i)\n    {\n        FFPhysicalMemoryResult* device = *FF_LIST_GET(FFPhysicalMemoryResult*, filtered, i);\n        ffStrbufClear(&prettySize);\n        if (device->installed)\n            ffSizeAppendNum(device->size, &prettySize);\n\n        if (options->moduleArgs.outputFormat.length == 0)\n        {\n            ffPrintLogoAndKey(FF_PHYSICALMEMORY_DISPLAY_NAME, filtered.length == 1 ? 0 : (uint8_t) (i + 1), &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n            if (device->installed)\n            {\n                fputs(prettySize.chars, stdout);\n                fputs(\" - \", stdout);\n                ffStrbufWriteTo(&device->type, stdout);\n                if (device->maxSpeed > 0)\n                    printf(\"-%u\", device->maxSpeed);\n                if (device->runningSpeed > 0 && device->runningSpeed != device->maxSpeed)\n                    printf(\" @ %u MT/s\", device->runningSpeed);\n                if (device->vendor.length > 0)\n                    printf(\" (%s)\", device->vendor.chars);\n                if (device->ecc)\n                    fputs(\" - ECC\", stdout);\n            }\n            else\n            {\n                fputs(\"Empty\", stdout);\n                if (device->formFactor.length > 0)\n                    printf(\" - %s\", device->formFactor.chars);\n                if (device->locator.length > 0)\n                    printf(\" (%s)\", device->locator.chars);\n            }\n            putchar('\\n');\n        }\n        else\n        {\n            FF_PRINT_FORMAT_CHECKED(FF_PHYSICALMEMORY_DISPLAY_NAME, (uint8_t) (i + 1), &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n                FF_ARG(device->size, \"bytes\"),\n                FF_ARG(prettySize, \"size\"),\n                FF_ARG(device->maxSpeed, \"max-speed\"),\n                FF_ARG(device->runningSpeed, \"running-speed\"),\n                FF_ARG(device->type, \"type\"),\n                FF_ARG(device->formFactor, \"form-factor\"),\n                FF_ARG(device->locator, \"locator\"),\n                FF_ARG(device->vendor, \"vendor\"),\n                FF_ARG(device->serial, \"serial\"),\n                FF_ARG(device->partNumber, \"part-number\"),\n                FF_ARG(device->ecc, \"is-ecc-enabled\"),\n                FF_ARG(device->installed, \"is-installed\"),\n            }));\n        }\n    }\n\n    FF_LIST_FOR_EACH(FFPhysicalMemoryResult, device, result)\n    {\n        ffStrbufDestroy(&device->type);\n        ffStrbufDestroy(&device->locator);\n        ffStrbufDestroy(&device->formFactor);\n        ffStrbufDestroy(&device->vendor);\n        ffStrbufDestroy(&device->serial);\n        ffStrbufDestroy(&device->partNumber);\n    }\n\n    return true;\n}\n\nvoid ffParsePhysicalMemoryJsonObject(FFPhysicalMemoryOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"showEmptySlots\"))\n        {\n            options->showEmptySlots = yyjson_get_bool(val);\n            continue;\n        }\n\n        ffPrintError(FF_PHYSICALMEMORY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGeneratePhysicalMemoryJsonConfig(FFPhysicalMemoryOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n    yyjson_mut_obj_add_bool(doc, module, \"showEmptySlots\", options->showEmptySlots);\n}\n\nbool ffGeneratePhysicalMemoryJsonResult(FF_MAYBE_UNUSED FFPhysicalMemoryOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFPhysicalMemoryResult));\n    const char* error = ffDetectPhysicalMemory(&result);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n    FF_LIST_FOR_EACH(FFPhysicalMemoryResult, device, result)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n        yyjson_mut_obj_add_uint(doc, obj, \"size\", device->size);\n        yyjson_mut_obj_add_bool(doc, obj, \"installed\", device->installed);\n        yyjson_mut_obj_add_uint(doc, obj, \"maxSpeed\", device->maxSpeed);\n        yyjson_mut_obj_add_uint(doc, obj, \"runningSpeed\", device->runningSpeed);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"type\", &device->type);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"locator\", &device->locator);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"formFactor\", &device->formFactor);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"vendor\", &device->vendor);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"serial\", &device->serial);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"partNumber\", &device->partNumber);\n        yyjson_mut_obj_add_bool(doc, obj, \"ecc\", device->ecc);\n    }\n\n    FF_LIST_FOR_EACH(FFPhysicalMemoryResult, device, result)\n    {\n        ffStrbufDestroy(&device->type);\n        ffStrbufDestroy(&device->locator);\n        ffStrbufDestroy(&device->formFactor);\n        ffStrbufDestroy(&device->vendor);\n        ffStrbufDestroy(&device->serial);\n        ffStrbufDestroy(&device->partNumber);\n    }\n\n    return true;\n}\n\nvoid ffInitPhysicalMemoryOptions(FFPhysicalMemoryOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰑭\");\n    options->showEmptySlots = false;\n}\n\nvoid ffDestroyPhysicalMemoryOptions(FFPhysicalMemoryOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffPhysicalMemoryModuleInfo = {\n    .name = FF_PHYSICALMEMORY_MODULE_NAME,\n    .description = \"Print system physical memory devices\",\n    .initOptions = (void*) ffInitPhysicalMemoryOptions,\n    .destroyOptions = (void*) ffDestroyPhysicalMemoryOptions,\n    .parseJsonObject = (void*) ffParsePhysicalMemoryJsonObject,\n    .printModule = (void*) ffPrintPhysicalMemory,\n    .generateJsonConfig = (void*) ffGeneratePhysicalMemoryJsonConfig,\n    .generateJsonResult = (void*) ffGeneratePhysicalMemoryJsonResult,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Size (in bytes)\", \"bytes\"},\n        {\"Size formatted\", \"size\"},\n        {\"Max speed (in MT/s)\", \"max-speed\"},\n        {\"Running speed (in MT/s)\", \"running-speed\"},\n        {\"Type (DDR4, DDR5, etc.)\", \"type\"},\n        {\"Form factor (SODIMM, DIMM, etc.)\", \"form-factor\"},\n        {\"Bank/Device Locator (BANK0/SIMM0, BANK0/SIMM1, etc.)\", \"locator\"},\n        {\"Vendor\", \"vendor\"},\n        {\"Serial number\", \"serial\"},\n        {\"Part number\", \"part-number\"},\n        {\"True if ECC enabled\", \"is-ecc-enabled\"},\n        {\"True if a memory module is installed in the slot\", \"is-installed\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/physicalmemory/physicalmemory.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_PHYSICALMEMORY_MODULE_NAME \"PhysicalMemory\"\n\nbool ffPrintPhysicalMemory(FFPhysicalMemoryOptions* options);\nvoid ffInitPhysicalMemoryOptions(FFPhysicalMemoryOptions* options);\nvoid ffDestroyPhysicalMemoryOptions(FFPhysicalMemoryOptions* options);\n\nextern FFModuleBaseInfo ffPhysicalMemoryModuleInfo;\n"
  },
  {
    "path": "src/modules/player/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFPlayerOptions\n{\n    FFModuleArgs moduleArgs;\n} FFPlayerOptions;\n\nstatic_assert(sizeof(FFPlayerOptions) <= FF_OPTION_MAX_SIZE, \"FFPlayerOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/player/player.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/media/media.h\"\n#include \"modules/player/player.h\"\n\n#include <ctype.h>\n\n#define FF_PLAYER_DISPLAY_NAME \"Media Player\"\n\nbool ffPrintPlayer(FFPlayerOptions* options)\n{\n    const FFMediaResult* media = ffDetectMedia(false);\n\n    if(media->error.length > 0)\n    {\n        ffPrintError(FF_PLAYER_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", media->error.chars);\n        return false;\n    }\n\n    if (media->player.length == 0)\n    {\n        ffPrintError(FF_PLAYER_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No media player detected\");\n        return false;\n    }\n\n    FF_STRBUF_AUTO_DESTROY playerPretty = ffStrbufCreate();\n\n    if (media->url.length > 0)\n    {\n        //If we are on a website, prepend the website name\n        if(\n            ffStrbufIgnCaseEqualS(&media->playerId, \"spotify\") ||\n            ffStrbufIgnCaseEqualS(&media->playerId, \"vlc\")\n        ) {} // do noting, surely not a website, even if the url is set\n        else if(ffStrbufStartsWithS(&media->url, \"https://www.\"))\n            ffStrbufAppendS(&playerPretty, media->url.chars + 12);\n        else if(ffStrbufStartsWithS(&media->url, \"http://www.\"))\n            ffStrbufAppendS(&playerPretty, media->url.chars + 11);\n        else if(ffStrbufStartsWithS(&media->url, \"https://\"))\n            ffStrbufAppendS(&playerPretty, media->url.chars + 8);\n        else if(ffStrbufStartsWithS(&media->url, \"http://\"))\n            ffStrbufAppendS(&playerPretty, media->url.chars + 7);\n    }\n\n    //If we found a website name, make it more pretty\n    if(playerPretty.length > 0)\n    {\n        ffStrbufSubstrBeforeFirstC(&playerPretty, '/'); //Remove the path\n        ffStrbufSubstrBeforeLastC(&playerPretty, '.'); //Remove the TLD\n    }\n\n    //Check again for length, as we may have removed everything.\n    bool playerPrettyIsCustom = playerPretty.length > 0;\n\n    //If we don't have subdomains, it is usually more pretty to capitalize the first letter.\n    if(playerPrettyIsCustom && ffStrbufFirstIndexC(&playerPretty, '.') == playerPretty.length)\n        playerPretty.chars[0] = (char) toupper(playerPretty.chars[0]);\n\n    if(playerPrettyIsCustom)\n        ffStrbufAppendS(&playerPretty, \" (\");\n\n    ffStrbufAppend(&playerPretty, &media->player);\n\n    if(playerPrettyIsCustom)\n        ffStrbufAppendC(&playerPretty, ')');\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_PLAYER_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        ffStrbufPutTo(&playerPretty, stdout);\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_PLAYER_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(playerPretty, \"player\"),\n            FF_ARG(media->player, \"name\"),\n            FF_ARG(media->playerId, \"id\"),\n            FF_ARG(media->url, \"url\"),\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParsePlayerJsonObject(FFPlayerOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_PLAYER_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGeneratePlayerJsonConfig(FFPlayerOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGeneratePlayerJsonResult(FF_MAYBE_UNUSED FFMediaOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    yyjson_mut_obj_add_str(doc, module, \"error\", \"Player module is an alias of Media module\");\n    return false;\n}\n\nvoid ffInitPlayerOptions(FFPlayerOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰥠\");\n}\n\nvoid ffDestroyPlayerOptions(FFPlayerOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffPlayerModuleInfo = {\n    .name = FF_PLAYER_MODULE_NAME,\n    .description = \"Print music player name\",\n    .initOptions = (void*) ffInitPlayerOptions,\n    .destroyOptions = (void*) ffDestroyPlayerOptions,\n    .parseJsonObject = (void*) ffParsePlayerJsonObject,\n    .printModule = (void*) ffPrintPlayer,\n    .generateJsonResult = (void*) ffGeneratePlayerJsonResult,\n    .generateJsonConfig = (void*) ffGeneratePlayerJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Pretty player name\", \"player\"},\n        {\"Player name\", \"name\"},\n        {\"Player Identifier\", \"id\"},\n        {\"URL name\", \"url\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/player/player.h",
    "content": "\n#pragma once\n\n#include \"option.h\"\n\n#define FF_PLAYER_MODULE_NAME \"Player\"\n\nbool ffPrintPlayer(FFPlayerOptions* options);\nvoid ffInitPlayerOptions(FFPlayerOptions* options);\nvoid ffDestroyPlayerOptions(FFPlayerOptions* options);\n\nextern FFModuleBaseInfo ffPlayerModuleInfo;\n"
  },
  {
    "path": "src/modules/poweradapter/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFPowerAdapterOptions\n{\n    FFModuleArgs moduleArgs;\n} FFPowerAdapterOptions;\n\nstatic_assert(sizeof(FFPowerAdapterOptions) <= FF_OPTION_MAX_SIZE, \"FFPowerAdapterOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/poweradapter/poweradapter.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/poweradapter/poweradapter.h\"\n#include \"modules/poweradapter/poweradapter.h\"\n\n#define FF_POWERADAPTER_DISPLAY_NAME \"Power Adapter\"\n\nbool ffPrintPowerAdapter(FFPowerAdapterOptions* options)\n{\n    FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFPowerAdapterResult));\n\n    const char* error = ffDetectPowerAdapter(&results);\n\n    if (error)\n    {\n        ffPrintError(FF_POWERADAPTER_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if(results.length == 0)\n    {\n        ffPrintError(FF_POWERADAPTER_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No power adapters found\");\n        return false;\n    }\n\n    for(uint8_t i = 0; i < (uint8_t) results.length; i++)\n    {\n        FFPowerAdapterResult* result = FF_LIST_GET(FFPowerAdapterResult, results, i);\n\n        if(options->moduleArgs.outputFormat.length == 0)\n        {\n            ffPrintLogoAndKey(FF_POWERADAPTER_DISPLAY_NAME, i, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n            if(result->name.length > 0)\n                puts(result->name.chars);\n            else\n                printf(\"%dW\\n\", result->watts);\n        }\n        else\n        {\n            FF_PRINT_FORMAT_CHECKED(FF_POWERADAPTER_DISPLAY_NAME, i, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n                FF_ARG(result->watts, \"watts\"),\n                FF_ARG(result->name, \"name\"),\n                FF_ARG(result->manufacturer, \"manufacturer\"),\n                FF_ARG(result->modelName, \"model-name\"),\n                FF_ARG(result->description, \"description\"),\n                FF_ARG(result->serial, \"serial\"),\n            }));\n        }\n\n        ffStrbufDestroy(&result->manufacturer);\n        ffStrbufDestroy(&result->description);\n        ffStrbufDestroy(&result->modelName);\n        ffStrbufDestroy(&result->name);\n        ffStrbufDestroy(&result->serial);\n    }\n\n    return true;\n}\n\nvoid ffGeneratePowerAdapterJsonConfig(FFPowerAdapterOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nvoid ffParsePowerAdapterJsonObject(FFPowerAdapterOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_POWERADAPTER_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nbool ffGeneratePowerAdapterJsonResult(FF_MAYBE_UNUSED FFPowerAdapterOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFPowerAdapterResult));\n\n    const char* error = ffDetectPowerAdapter(&results);\n\n    if (error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n    FF_LIST_FOR_EACH(FFPowerAdapterResult, item, results)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"description\", &item->description);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"manufacturer\", &item->manufacturer);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"modelName\", &item->modelName);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &item->name);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"serial\", &item->serial);\n        yyjson_mut_obj_add_int(doc, obj, \"watts\", item->watts);\n    }\n\n    FF_LIST_FOR_EACH(FFPowerAdapterResult, item, results)\n    {\n        ffStrbufDestroy(&item->manufacturer);\n        ffStrbufDestroy(&item->description);\n        ffStrbufDestroy(&item->modelName);\n        ffStrbufDestroy(&item->name);\n        ffStrbufDestroy(&item->serial);\n    }\n\n    return true;\n}\n\nvoid ffInitPowerAdapterOptions(FFPowerAdapterOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰚥\");\n}\n\nvoid ffDestroyPowerAdapterOptions(FFPowerAdapterOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffPowerAdapterModuleInfo = {\n    .name = FF_POWERADAPTER_MODULE_NAME,\n    .description = \"Print power adapter name and charging watts\",\n    .initOptions = (void*) ffInitPowerAdapterOptions,\n    .destroyOptions = (void*) ffDestroyPowerAdapterOptions,\n    .parseJsonObject = (void*) ffParsePowerAdapterJsonObject,\n    .printModule = (void*) ffPrintPowerAdapter,\n    .generateJsonResult = (void*) ffGeneratePowerAdapterJsonResult,\n    .generateJsonConfig = (void*) ffGeneratePowerAdapterJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Power adapter watts\", \"watts\"},\n        {\"Power adapter name\", \"name\"},\n        {\"Power adapter manufacturer\", \"manufacturer\"},\n        {\"Power adapter model\", \"model\"},\n        {\"Power adapter description\", \"description\"},\n        {\"Power adapter serial number\", \"serial\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/poweradapter/poweradapter.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_POWERADAPTER_MODULE_NAME \"PowerAdapter\"\n\nbool ffPrintPowerAdapter(FFPowerAdapterOptions* options);\nvoid ffInitPowerAdapterOptions(FFPowerAdapterOptions* options);\nvoid ffDestroyPowerAdapterOptions(FFPowerAdapterOptions* options);\n\nextern FFModuleBaseInfo ffPowerAdapterModuleInfo;\n"
  },
  {
    "path": "src/modules/processes/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFProcessesOptions\n{\n    FFModuleArgs moduleArgs;\n} FFProcessesOptions;\n\nstatic_assert(sizeof(FFProcessesOptions) <= FF_OPTION_MAX_SIZE, \"FFProcessesOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/processes/processes.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/processes/processes.h\"\n#include \"modules/processes/processes.h\"\n\nbool ffPrintProcesses(FFProcessesOptions* options)\n{\n    uint32_t numProcesses = 0;\n    const char* error = ffDetectProcesses(&numProcesses);\n\n    if(error)\n    {\n        ffPrintError(FF_PROCESSES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_PROCESSES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n        printf(\"%u\\n\", numProcesses);\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_PROCESSES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(numProcesses, \"result\")\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseProcessesJsonObject(FFProcessesOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_PROCESSES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateProcessesJsonConfig(FFProcessesOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateProcessesJsonResult(FF_MAYBE_UNUSED FFProcessesOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    uint32_t result;\n    const char* error = ffDetectProcesses(&result);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_obj_add_uint(doc, module, \"result\", result);\n\n    return true;\n}\n\nvoid ffInitProcessesOptions(FFProcessesOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n}\n\nvoid ffDestroyProcessesOptions(FFProcessesOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffProcessesModuleInfo = {\n    .name = FF_PROCESSES_MODULE_NAME,\n    .description = \"Print number of running processes\",\n    .initOptions = (void*) ffInitProcessesOptions,\n    .destroyOptions = (void*) ffDestroyProcessesOptions,\n    .parseJsonObject = (void*) ffParseProcessesJsonObject,\n    .printModule = (void*) ffPrintProcesses,\n    .generateJsonResult = (void*) ffGenerateProcessesJsonResult,\n    .generateJsonConfig = (void*) ffGenerateProcessesJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Process count\", \"result\"}\n    }))\n};\n"
  },
  {
    "path": "src/modules/processes/processes.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_PROCESSES_MODULE_NAME \"Processes\"\n\nbool ffPrintProcesses(FFProcessesOptions* options);\nvoid ffInitProcessesOptions(FFProcessesOptions* options);\nvoid ffDestroyProcessesOptions(FFProcessesOptions* options);\n\nextern FFModuleBaseInfo ffProcessesModuleInfo;\n"
  },
  {
    "path": "src/modules/publicip/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFPublicIPOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFstrbuf url;\n    uint32_t timeout;\n    bool ipv6;\n} FFPublicIPOptions;\n\nstatic_assert(sizeof(FFPublicIPOptions) <= FF_OPTION_MAX_SIZE, \"FFPublicIPOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/publicip/publicip.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"modules/publicip/publicip.h\"\n#include \"detection/publicip/publicip.h\"\n\n#define FF_PUBLICIP_DISPLAY_NAME \"Public IP\"\n\nbool ffPrintPublicIp(FFPublicIPOptions* options)\n{\n    FFPublicIpResult result;\n    ffStrbufInit(&result.ip);\n    ffStrbufInit(&result.location);\n    const char* error = ffDetectPublicIp(options, &result);\n\n    if (error)\n    {\n        ffPrintError(FF_PUBLICIP_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if (options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_PUBLICIP_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        if (result.location.length)\n            printf(\"%s (%s)\\n\", result.ip.chars, result.location.chars);\n        else\n            ffStrbufPutTo(&result.ip, stdout);\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_PUBLICIP_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n            FF_ARG(result.ip, \"ip\"),\n            FF_ARG(result.location, \"location\"),\n        }));\n    }\n\n    ffStrbufDestroy(&result.ip);\n    ffStrbufDestroy(&result.location);\n\n    return true;\n}\n\nvoid ffParsePublicIpJsonObject(FFPublicIPOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"url\"))\n        {\n            ffStrbufSetJsonVal(&options->url, val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"timeout\"))\n        {\n            options->timeout = (uint32_t) yyjson_get_uint(val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"ipv6\"))\n        {\n            options->ipv6 = yyjson_get_bool(val);\n            continue;\n        }\n\n        ffPrintError(FF_PUBLICIP_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGeneratePublicIpJsonConfig(FFPublicIPOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    yyjson_mut_obj_add_strbuf(doc, module, \"url\", &options->url);\n\n    yyjson_mut_obj_add_uint(doc, module, \"timeout\", options->timeout);\n\n    yyjson_mut_obj_add_bool(doc, module, \"ipv6\", options->ipv6);\n}\n\nbool ffGeneratePublicIpJsonResult(FFPublicIPOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FFPublicIpResult result;\n    ffStrbufInit(&result.ip);\n    ffStrbufInit(&result.location);\n    const char* error = ffDetectPublicIp(options, &result);\n\n    if (error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_strbuf(doc, obj, \"ip\", &result.ip);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"location\", &result.location);\n\n    ffStrbufDestroy(&result.ip);\n    ffStrbufDestroy(&result.location);\n\n    return true;\n}\n\nvoid ffInitPublicIpOptions(FFPublicIPOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰩠\");\n\n    ffStrbufInit(&options->url);\n    options->timeout = 0;\n    options->ipv6 = false;\n}\n\nvoid ffDestroyPublicIpOptions(FFPublicIPOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n\n    ffStrbufDestroy(&options->url);\n}\n\nFFModuleBaseInfo ffPublicIPModuleInfo = {\n    .name = FF_PUBLICIP_MODULE_NAME,\n    .description = \"Print your public IP address, etc\",\n    .initOptions = (void*) ffInitPublicIpOptions,\n    .destroyOptions = (void*) ffDestroyPublicIpOptions,\n    .parseJsonObject = (void*) ffParsePublicIpJsonObject,\n    .printModule = (void*) ffPrintPublicIp,\n    .generateJsonResult = (void*) ffGeneratePublicIpJsonResult,\n    .generateJsonConfig = (void*) ffGeneratePublicIpJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Public IP address\", \"ip\"},\n        {\"Location\", \"location\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/publicip/publicip.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_PUBLICIP_MODULE_NAME \"PublicIp\"\n\nvoid ffPreparePublicIp(FFPublicIPOptions* options);\n\nbool ffPrintPublicIp(FFPublicIPOptions* options);\nvoid ffInitPublicIpOptions(FFPublicIPOptions* options);\nvoid ffDestroyPublicIpOptions(FFPublicIPOptions* options);\n\nextern FFModuleBaseInfo ffPublicIPModuleInfo;\n"
  },
  {
    "path": "src/modules/separator/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFSeparatorOptions\n{\n    FFstrbuf string;\n    FFstrbuf outputColor;\n    uint32_t times;\n} FFSeparatorOptions;\n\nstatic_assert(sizeof(FFSeparatorOptions) <= FF_OPTION_MAX_SIZE, \"FFSeparatorOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/separator/separator.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"common/mallocHelper.h\"\n#include \"common/wcwidth.h\"\n#include \"common/textModifier.h\"\n#include \"logo/logo.h\"\n#include \"modules/separator/separator.h\"\n\n#include <locale.h>\n\n#if __SIZEOF_WCHAR_T__ == 4\n    static inline size_t mbrtoc32(uint32_t* restrict pc32, const char* restrict s, size_t n, mbstate_t* restrict ps)\n    {\n        return mbrtowc((wchar_t*) pc32, s, n, ps);\n    }\n#else\n    #include <uchar.h>\n#endif\n\nstatic uint8_t getMbrWidth(const char* mbstr, uint32_t length, const char** next, mbstate_t* state)\n{\n    if (__builtin_expect((uint8_t) *mbstr < 0x80, true)) // ASCII fast path\n    {\n        if (next) *next = mbstr + 1;\n        return 1;\n    }\n\n    uint32_t c32;\n    uint32_t len = (uint32_t) mbrtoc32(&c32, mbstr, length, state);\n    if (len >= (uint32_t) -3)\n    {\n        // Invalid or incomplete multibyte sequence\n        if (next) *next = mbstr + 1;\n        return 1;\n    }\n\n    if (next) *next = mbstr + len;\n    int width = mk_wcwidth(c32);\n    return width < 0 ? 0 : (uint8_t) width;\n}\n\nstatic uint32_t getWcsWidth(const FFstrbuf* mbstr)\n{\n    mbstate_t state = {};\n    uint32_t remainLength = mbstr->length;\n    uint32_t result = 0;\n\n    const char* ptr = mbstr->chars;\n    while (remainLength > 0 && *ptr != '\\0')\n    {\n        const char* lastPtr = NULL;\n        result += getMbrWidth(ptr, remainLength, &lastPtr, &state);\n        remainLength -= (uint32_t)(lastPtr - ptr);\n        ptr = lastPtr;\n    }\n\n    return result > 0 ? (uint32_t) result : mbstr->length;\n}\n\nbool ffPrintSeparator(FFSeparatorOptions* options)\n{\n    ffLogoPrintLine();\n\n    if(options->outputColor.length && !instance.config.display.pipe)\n        ffPrintColor(&options->outputColor);\n\n    if (options->times > 0)\n    {\n        if(__builtin_expect(options->string.length == 1, 1))\n            ffPrintCharTimes(options->string.chars[0], options->times);\n        else\n        {\n            for (uint32_t i = 0; i < options->times; i++)\n            {\n                fputs(options->string.chars, stdout);\n            }\n        }\n    }\n    else\n    {\n        setlocale(LC_CTYPE, \"\");\n        const FFPlatform* platform = &instance.state.platform;\n\n        uint32_t titleLength = 1 // @\n            + getWcsWidth(&platform->userName) // user name\n            + (instance.state.titleFqdn ? platform->hostName.length : ffStrbufFirstIndexC(&platform->hostName, '.')); // host name\n\n        if(__builtin_expect(options->string.length == 1, 1))\n        {\n            ffPrintCharTimes(options->string.chars[0], titleLength);\n        }\n        else\n        {\n            uint32_t wcsLength = getWcsWidth(&options->string);\n\n            int remaining = (int) titleLength;\n            //Write the whole separator as often as it fits fully into titleLength\n            for (; remaining >= (int) wcsLength; remaining -= (int) wcsLength)\n                ffStrbufWriteTo(&options->string, stdout);\n\n            if (remaining > 0)\n            {\n                //Write as much of the separator as needed to fill titleLength\n                if (wcsLength != options->string.length)\n                {\n                    // Unicode chars\n                    const char* ptr = options->string.chars;\n                    mbstate_t state = {};\n                    const char* next = NULL;\n                    while (remaining > 0 && *ptr != '\\0')\n                    {\n                        remaining -= (int) getMbrWidth(ptr, (uint32_t)(options->string.length - (ptr - options->string.chars)), &next, &state);\n                        ptr = next;\n                    }\n                    fwrite(options->string.chars, (size_t) (ptr - options->string.chars), 1, stdout);\n                }\n                else\n                {\n                    fwrite(options->string.chars, (size_t) remaining, 1, stdout);\n                }\n            }\n        }\n        setlocale(LC_CTYPE, \"C\");\n    }\n\n    if(options->outputColor.length && !instance.config.display.pipe)\n        fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout);\n    putchar('\\n');\n\n    return true;\n}\n\nvoid ffParseSeparatorJsonObject(FFSeparatorOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (unsafe_yyjson_equals_str(key, \"type\") || unsafe_yyjson_equals_str(key, \"condition\"))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"string\"))\n        {\n            ffStrbufSetJsonVal(&options->string, val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"outputColor\"))\n        {\n            ffOptionParseColor(yyjson_get_str(val), &options->outputColor);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"times\"))\n        {\n            options->times = (uint32_t) yyjson_get_uint(val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"length\"))\n        {\n            ffPrintError(FF_SEPARATOR_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, \"The option length has been renamed to times.\");\n            continue;\n        }\n\n        ffPrintError(FF_SEPARATOR_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateSeparatorJsonConfig(FFSeparatorOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    yyjson_mut_obj_add_strbuf(doc, module, \"string\", &options->string);\n    yyjson_mut_obj_add_strbuf(doc, module, \"outputColor\", &options->outputColor);\n    yyjson_mut_obj_add_uint(doc, module, \"times\", options->times);\n}\n\nvoid ffInitSeparatorOptions(FFSeparatorOptions* options)\n{\n    ffStrbufInitStatic(&options->string, \"-\");\n    ffStrbufInit(&options->outputColor);\n    options->times = 0;\n}\n\nvoid ffDestroySeparatorOptions(FFSeparatorOptions* options)\n{\n    ffStrbufDestroy(&options->string);\n}\n\nFFModuleBaseInfo ffSeparatorModuleInfo = {\n    .name = FF_SEPARATOR_MODULE_NAME,\n    .description = \"Print a separator line\",\n    .initOptions = (void*) ffInitSeparatorOptions,\n    .destroyOptions = (void*) ffDestroySeparatorOptions,\n    .parseJsonObject = (void*) ffParseSeparatorJsonObject,\n    .printModule = (void*) ffPrintSeparator,\n    .generateJsonConfig = (void*) ffGenerateSeparatorJsonConfig,\n};\n"
  },
  {
    "path": "src/modules/separator/separator.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_SEPARATOR_MODULE_NAME \"Separator\"\n\nbool ffPrintSeparator(FFSeparatorOptions* options);\nvoid ffInitSeparatorOptions(FFSeparatorOptions* options);\nvoid ffDestroySeparatorOptions(FFSeparatorOptions* options);\n\nextern FFModuleBaseInfo ffSeparatorModuleInfo;\n"
  },
  {
    "path": "src/modules/shell/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFShellOptions\n{\n    FFModuleArgs moduleArgs;\n} FFShellOptions;\n\nstatic_assert(sizeof(FFShellOptions) <= FF_OPTION_MAX_SIZE, \"FFShellOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/shell/shell.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/terminalshell/terminalshell.h\"\n#include \"modules/shell/shell.h\"\n\nbool ffPrintShell(FFShellOptions* options)\n{\n    const FFShellResult* result = ffDetectShell();\n\n    if(result->processName.length == 0)\n    {\n        ffPrintError(FF_SHELL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Couldn't detect shell\");\n        return false;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_SHELL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        ffStrbufWriteTo(&result->prettyName, stdout);\n\n        if(result->version.length > 0)\n        {\n            putchar(' ');\n            ffStrbufWriteTo(&result->version, stdout);\n        }\n\n        putchar('\\n');\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_SHELL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n            FF_ARG(result->processName, \"process-name\"),\n            FF_ARG(result->exe, \"exe\"),\n            FF_ARG(result->exeName, \"exe-name\"),\n            FF_ARG(result->version, \"version\"),\n            FF_ARG(result->pid, \"pid\"),\n            FF_ARG(result->prettyName, \"pretty-name\"),\n            FF_ARG(result->exePath, \"exe-path\"),\n            FF_ARG(result->tty, \"tty\"),\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseShellJsonObject(FFShellOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_SHELL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateShellJsonConfig(FFShellOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateShellJsonResult(FF_MAYBE_UNUSED FFShellOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    const FFShellResult* result = ffDetectShell();\n\n    if(result->processName.length == 0)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", \"Couldn't detect shell\");\n        return false;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_strbuf(doc, obj, \"exe\", &result->exe);\n    yyjson_mut_obj_add_strcpy(doc, obj, \"exeName\", result->exeName);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"exePath\", &result->exePath);\n    yyjson_mut_obj_add_uint(doc, obj, \"pid\", result->pid);\n    yyjson_mut_obj_add_uint(doc, obj, \"ppid\", result->ppid);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"processName\", &result->processName);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"prettyName\", &result->prettyName);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"version\", &result->version);\n    if (result->tty >= 0)\n        yyjson_mut_obj_add_int(doc, obj, \"tty\", result->tty);\n    else\n        yyjson_mut_obj_add_null(doc, obj, \"tty\");\n\n    return true;\n}\n\nvoid ffInitShellOptions(FFShellOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n}\n\nvoid ffDestroyShellOptions(FFShellOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffShellModuleInfo = {\n    .name = FF_SHELL_MODULE_NAME,\n    .description = \"Print current shell name and version\",\n    .initOptions = (void*) ffInitShellOptions,\n    .destroyOptions = (void*) ffDestroyShellOptions,\n    .parseJsonObject = (void*) ffParseShellJsonObject,\n    .printModule = (void*) ffPrintShell,\n    .generateJsonResult = (void*) ffGenerateShellJsonResult,\n    .generateJsonConfig = (void*) ffGenerateShellJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Shell process name\", \"process-name\"},\n        {\"The first argument of the command line when running the shell\", \"exe\"},\n        {\"Shell base name of arg0\", \"exe-name\"},\n        {\"Shell version\", \"version\"},\n        {\"Shell pid\", \"pid\"},\n        {\"Shell pretty name\", \"pretty-name\"},\n        {\"Shell full exe path\", \"exe-path\"},\n        {\"Shell tty used\", \"tty\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/shell/shell.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_SHELL_MODULE_NAME \"Shell\"\n\nbool ffPrintShell(FFShellOptions* options);\nvoid ffInitShellOptions(FFShellOptions* options);\nvoid ffDestroyShellOptions(FFShellOptions* options);\n\nextern FFModuleBaseInfo ffShellModuleInfo;\n"
  },
  {
    "path": "src/modules/sound/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n#include \"common/percent.h\"\n\ntypedef enum __attribute__((__packed__)) FFSoundType\n{\n    FF_SOUND_TYPE_MAIN,\n    FF_SOUND_TYPE_ACTIVE,\n    FF_SOUND_TYPE_ALL,\n} FFSoundType;\n\ntypedef struct FFSoundOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFSoundType soundType;\n    FFPercentageModuleConfig percent;\n} FFSoundOptions;\n\nstatic_assert(sizeof(FFSoundOptions) <= FF_OPTION_MAX_SIZE, \"FFSoundOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/sound/sound.c",
    "content": "#include \"common/percent.h\"\n#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/sound/sound.h\"\n#include \"modules/sound/sound.h\"\n\nstatic void printDevice(FFSoundOptions* options, const FFSoundDevice* device, uint8_t index)\n{\n    FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_SOUND_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n        FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate();\n        if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT))\n            ffStrbufAppend(&str, &device->name);\n\n        if(device->volume != FF_SOUND_VOLUME_UNKNOWN)\n        {\n            if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            {\n                if (str.length)\n                    ffStrbufAppendC(&str, ' ');\n\n                ffPercentAppendBar(&str, device->volume, options->percent, &options->moduleArgs);\n            }\n\n            if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n            {\n                if (str.length)\n                    ffStrbufAppendC(&str, ' ');\n\n                ffPercentAppendNum(&str, device->volume, options->percent, str.length > 0, &options->moduleArgs);\n            }\n        }\n\n        if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT))\n        {\n            if (device->main && index > 0)\n                ffStrbufAppendS(&str, \" (*)\");\n        }\n\n        ffStrbufPutTo(&str, stdout);\n    }\n    else\n    {\n        FF_STRBUF_AUTO_DESTROY percentageNum = ffStrbufCreate();\n        FF_STRBUF_AUTO_DESTROY percentageBar = ffStrbufCreate();\n        if(device->volume != FF_SOUND_VOLUME_UNKNOWN)\n        {\n            if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n                ffPercentAppendNum(&percentageNum, device->volume, options->percent, false, &options->moduleArgs);\n            if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n                ffPercentAppendBar(&percentageBar, device->volume, options->percent, &options->moduleArgs);\n        }\n\n        FF_PRINT_FORMAT_CHECKED(FF_SOUND_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n            FF_ARG(device->main, \"is-main\"),\n            FF_ARG(device->name, \"name\"),\n            FF_ARG(percentageNum, \"volume-percentage\"),\n            FF_ARG(device->identifier, \"identifier\"),\n            FF_ARG(percentageBar, \"volume-percentage-bar\"),\n            FF_ARG(device->platformApi, \"platform-api\"),\n        }));\n    }\n}\n\nbool ffPrintSound(FFSoundOptions* options)\n{\n    bool success = false;\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFSoundDevice));\n\n    const char* error = ffDetectSound(&result);\n\n    if(error)\n    {\n        ffPrintError(FF_SOUND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        goto exit;\n    }\n\n    {\n        FF_LIST_AUTO_DESTROY filtered = ffListCreate(sizeof(FFSoundDevice*));\n\n        FF_LIST_FOR_EACH(FFSoundDevice, device, result)\n        {\n            switch (options->soundType)\n            {\n                case FF_SOUND_TYPE_MAIN: if (!device->main) continue; break;\n                case FF_SOUND_TYPE_ACTIVE: if (!device->active) continue; break;\n                case FF_SOUND_TYPE_ALL: break;\n            }\n\n            *(FFSoundDevice**)ffListAdd(&filtered) = device;\n        }\n\n        if(filtered.length == 0)\n        {\n            ffPrintError(FF_SOUND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No active sound devices found\");\n            goto exit;\n        }\n\n        uint8_t index = 1;\n        FF_LIST_FOR_EACH(FFSoundDevice*, device, filtered)\n        {\n            printDevice(options, *device, filtered.length == 1 ? 0 : index++);\n        }\n    }\n    success = true;\n\nexit:\n    FF_LIST_FOR_EACH(FFSoundDevice, device, result)\n    {\n        ffStrbufDestroy(&device->identifier);\n        ffStrbufDestroy(&device->name);\n        ffStrbufDestroy(&device->platformApi);\n    }\n\n    return success;\n}\n\nvoid ffParseSoundJsonObject(FFSoundOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"soundType\"))\n        {\n            int value;\n            const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {\n                { \"main\", FF_SOUND_TYPE_MAIN },\n                { \"active\", FF_SOUND_TYPE_ACTIVE },\n                { \"all\", FF_SOUND_TYPE_ALL },\n                {},\n            });\n            if (error)\n                ffPrintError(FF_SOUND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Invalid %s value: %s\", unsafe_yyjson_get_str(key), error);\n            else\n                options->soundType = (FFSoundType) value;\n            continue;\n        }\n\n        if (ffPercentParseJsonObject(key, val, &options->percent))\n            continue;\n\n        ffPrintError(FF_SOUND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateSoundJsonConfig(FFSoundOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    switch (options->soundType)\n    {\n        case FF_SOUND_TYPE_MAIN:\n            yyjson_mut_obj_add_str(doc, module, \"soundType\", \"main\");\n            break;\n        case FF_SOUND_TYPE_ACTIVE:\n            yyjson_mut_obj_add_str(doc, module, \"soundType\", \"active\");\n            break;\n        case FF_SOUND_TYPE_ALL:\n            yyjson_mut_obj_add_str(doc, module, \"soundType\", \"all\");\n            break;\n    }\n\n    ffPercentGenerateJsonConfig(doc, module, options->percent);\n}\n\nbool ffGenerateSoundJsonResult(FF_MAYBE_UNUSED FFSoundOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFSoundDevice));\n    const char* error = ffDetectSound(&result);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n    FF_LIST_FOR_EACH(FFSoundDevice, item, result)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n        yyjson_mut_obj_add_bool(doc, obj, \"active\", item->active);\n        yyjson_mut_obj_add_bool(doc, obj, \"main\", item->main);\n\n        if (item->volume != FF_SOUND_VOLUME_UNKNOWN)\n            yyjson_mut_obj_add_uint(doc, obj, \"volume\", item->volume);\n        else\n            yyjson_mut_obj_add_null(doc, obj, \"volume\");\n\n        yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &item->name);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"identifier\", &item->identifier);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"platformApi\", &item->platformApi);\n    }\n\n    FF_LIST_FOR_EACH(FFSoundDevice, device, result)\n    {\n        ffStrbufDestroy(&device->identifier);\n        ffStrbufDestroy(&device->name);\n        ffStrbufDestroy(&device->platformApi);\n    }\n\n    return true;\n}\n\nvoid ffInitSoundOptions(FFSoundOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n\n    options->soundType = FF_SOUND_TYPE_MAIN;\n    options->percent = (FFPercentageModuleConfig) { 80, 90, 0 };\n}\n\nvoid ffDestroySoundOptions(FFSoundOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffSoundModuleInfo = {\n    .name = FF_SOUND_MODULE_NAME,\n    .description = \"Print sound devices, volume, etc\",\n    .initOptions = (void*) ffInitSoundOptions,\n    .destroyOptions = (void*) ffDestroySoundOptions,\n    .parseJsonObject = (void*) ffParseSoundJsonObject,\n    .printModule = (void*) ffPrintSound,\n    .generateJsonResult = (void*) ffGenerateSoundJsonResult,\n    .generateJsonConfig = (void*) ffGenerateSoundJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Is main sound device\", \"is-main\"},\n        {\"Device name\", \"name\"},\n        {\"Volume (in percentage num)\", \"volume-percentage\"},\n        {\"Identifier\", \"identifier\"},\n        {\"Volume (in percentage bar)\", \"volume-percentage-bar\"},\n        {\"Platform API used\", \"platform-api\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/sound/sound.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_SOUND_MODULE_NAME \"Sound\"\n\nbool ffPrintSound(FFSoundOptions* options);\nvoid ffInitSoundOptions(FFSoundOptions* options);\nvoid ffDestroySoundOptions(FFSoundOptions* options);\n\nextern FFModuleBaseInfo ffSoundModuleInfo;\n"
  },
  {
    "path": "src/modules/swap/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n#include \"common/percent.h\"\n\ntypedef struct FFSwapOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFPercentageModuleConfig percent;\n    bool separate;\n} FFSwapOptions;\n\nstatic_assert(sizeof(FFSwapOptions) <= FF_OPTION_MAX_SIZE, \"FFSwapOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/swap/swap.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/percent.h\"\n#include \"common/size.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/swap/swap.h\"\n#include \"modules/swap/swap.h\"\n\nvoid printSwap(FFSwapOptions* options, uint8_t index, FFSwapResult* storage)\n{\n    FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();\n\n    if (options->moduleArgs.key.length == 0)\n    {\n        if (storage->name.length > 0)\n            ffStrbufSetF(&key, \"%s (%s)\", FF_SWAP_MODULE_NAME, storage->name.chars);\n        else\n            ffStrbufSetS(&key, FF_SWAP_MODULE_NAME);\n    }\n    else\n    {\n        ffStrbufClear(&key);\n        FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) {\n            FF_ARG(index, \"index\"),\n            FF_ARG(storage->name, \"name\"),\n            FF_ARG(options->moduleArgs.keyIcon, \"icon\"),\n        }));\n    }\n\n    FF_STRBUF_AUTO_DESTROY usedPretty = ffStrbufCreate();\n    ffSizeAppendNum(storage->bytesUsed, &usedPretty);\n\n    FF_STRBUF_AUTO_DESTROY totalPretty = ffStrbufCreate();\n    ffSizeAppendNum(storage->bytesTotal, &totalPretty);\n\n    double percentage = storage->bytesTotal == 0\n        ? 0\n        : (double) storage->bytesUsed / (double) storage->bytesTotal * 100.0;\n\n    FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);\n        FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate();\n\n        if (storage->bytesTotal == 0)\n        {\n            if(percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            {\n                ffPercentAppendBar(&str, 0, options->percent, &options->moduleArgs);\n                ffStrbufAppendC(&str, ' ');\n            }\n            if(!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT))\n                ffStrbufAppendS(&str, \"Disabled\");\n            else\n                ffPercentAppendNum(&str, 0, options->percent, str.length > 0, &options->moduleArgs);\n        }\n        else\n        {\n            if(percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            {\n                ffPercentAppendBar(&str, percentage, options->percent, &options->moduleArgs);\n                ffStrbufAppendC(&str, ' ');\n            }\n\n            if(!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT))\n                ffStrbufAppendF(&str, \"%s / %s \", usedPretty.chars, totalPretty.chars);\n\n            if(percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n                ffPercentAppendNum(&str, percentage, options->percent, str.length > 0, &options->moduleArgs);\n        }\n\n        ffStrbufTrimRight(&str, ' ');\n        ffStrbufPutTo(&str, stdout);\n    }\n    else\n    {\n        FF_STRBUF_AUTO_DESTROY percentageNum = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n            ffPercentAppendNum(&percentageNum, percentage, options->percent, false, &options->moduleArgs);\n        FF_STRBUF_AUTO_DESTROY percentageBar = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            ffPercentAppendBar(&percentageBar, percentage, options->percent, &options->moduleArgs);\n        FF_PRINT_FORMAT_CHECKED(key.chars, index, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]){\n            FF_ARG(usedPretty, \"used\"),\n            FF_ARG(totalPretty, \"total\"),\n            FF_ARG(percentageNum, \"percentage\"),\n            FF_ARG(percentageBar, \"percentage-bar\"),\n            FF_ARG(storage->name, \"name\"),\n        }));\n    }\n}\n\nbool ffPrintSwap(FFSwapOptions* options)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFSwapResult));\n    const char* error = ffDetectSwap(&result);\n\n    if(error)\n    {\n        ffPrintError(FF_SWAP_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if (options->separate)\n    {\n        uint8_t index = 0;\n        FF_LIST_FOR_EACH(FFSwapResult, storage, result)\n        {\n            ++index;\n            printSwap(options, index, storage);\n        }\n    }\n    else\n    {\n        FFSwapResult total = {\n            .name = ffStrbufCreate(),\n        };\n        FF_LIST_FOR_EACH(FFSwapResult, storage, result)\n        {\n            total.bytesUsed += storage->bytesUsed;\n            total.bytesTotal += storage->bytesTotal;\n        }\n        printSwap(options, 0, &total);\n        ffStrbufDestroy(&total.name);\n    }\n\n    FF_LIST_FOR_EACH(FFSwapResult, storage, result)\n    {\n        ffStrbufDestroy(&storage->name);\n    }\n\n    return true;\n}\n\nvoid ffParseSwapJsonObject(FFSwapOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (ffPercentParseJsonObject(key, val, &options->percent))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"separate\"))\n        {\n            options->separate = yyjson_get_bool(val);\n            continue;\n        }\n\n        ffPrintError(FF_SWAP_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateSwapJsonConfig(FFSwapOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffPercentGenerateJsonConfig(doc, module, options->percent);\n\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n    yyjson_mut_obj_add_bool(doc, module, \"separate\", options->separate);\n}\n\nbool ffGenerateSwapJsonResult(FF_MAYBE_UNUSED FFSwapOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFSwapResult));\n    const char* error = ffDetectSwap(&result);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n    FF_LIST_FOR_EACH(FFSwapResult, storage, result)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &storage->name);\n        yyjson_mut_obj_add_uint(doc, obj, \"used\", storage->bytesUsed);\n        yyjson_mut_obj_add_uint(doc, obj, \"total\", storage->bytesTotal);\n    }\n\n    FF_LIST_FOR_EACH(FFSwapResult, storage, result)\n    {\n        ffStrbufDestroy(&storage->name);\n    }\n\n    return true;\n}\n\nvoid ffInitSwapOptions(FFSwapOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰓡\");\n    options->percent = (FFPercentageModuleConfig) { 50, 80, 0 };\n    options->separate = false;\n}\n\nvoid ffDestroySwapOptions(FFSwapOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffSwapModuleInfo = {\n    .name = FF_SWAP_MODULE_NAME,\n    .description = \"Print swap (paging file) space usage\",\n    .initOptions = (void*) ffInitSwapOptions,\n    .destroyOptions = (void*) ffDestroySwapOptions,\n    .parseJsonObject = (void*) ffParseSwapJsonObject,\n    .printModule = (void*) ffPrintSwap,\n    .generateJsonResult = (void*) ffGenerateSwapJsonResult,\n    .generateJsonConfig = (void*) ffGenerateSwapJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Used size\", \"used\"},\n        {\"Total size\", \"total\"},\n        {\"Percentage used (num)\", \"percentage\"},\n        {\"Percentage used (bar)\", \"percentage-bar\"},\n        {\"Name\", \"name\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/swap/swap.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_SWAP_MODULE_NAME \"Swap\"\n\nbool ffPrintSwap(FFSwapOptions* options);\nvoid ffInitSwapOptions(FFSwapOptions* options);\nvoid ffDestroySwapOptions(FFSwapOptions* options);\n\nextern FFModuleBaseInfo ffSwapModuleInfo;\n"
  },
  {
    "path": "src/modules/terminal/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFTerminalOptions\n{\n    FFModuleArgs moduleArgs;\n} FFTerminalOptions;\n\nstatic_assert(sizeof(FFTerminalOptions) <= FF_OPTION_MAX_SIZE, \"FFTerminalOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/terminal/terminal.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/terminalshell/terminalshell.h\"\n#include \"modules/terminal/terminal.h\"\n\nbool ffPrintTerminal(FFTerminalOptions* options)\n{\n    const FFTerminalResult* result = ffDetectTerminal();\n\n    if(result->processName.length == 0)\n    {\n        ffPrintError(FF_TERMINAL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Couldn't detect terminal\");\n        return false;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_TERMINAL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n        if(result->version.length)\n            printf(\"%s %s\\n\", result->prettyName.chars, result->version.chars);\n        else\n            ffStrbufPutTo(&result->prettyName, stdout);\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_TERMINAL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(result->processName, \"process-name\"),\n            FF_ARG(result->exe, \"exe\"),\n            FF_ARG(result->exeName, \"exe-name\"),\n            FF_ARG(result->pid, \"pid\"),\n            FF_ARG(result->prettyName, \"pretty-name\"),\n            FF_ARG(result->version, \"version\"),\n            FF_ARG(result->exePath, \"exe-path\"),\n            FF_ARG(result->tty, \"tty\"),\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseTerminalJsonObject(FFTerminalOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_TERMINAL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateTerminalJsonConfig(FFTerminalOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateTerminalJsonResult(FF_MAYBE_UNUSED FFTerminalOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    const FFTerminalResult* result = ffDetectTerminal();\n\n    if(result->processName.length == 0)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", \"Couldn't detect terminal\");\n        return false;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_strbuf(doc, obj, \"processName\", &result->processName);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"exe\", &result->exe);\n    yyjson_mut_obj_add_strcpy(doc, obj, \"exeName\", result->exeName);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"exePath\", &result->exePath);\n    yyjson_mut_obj_add_uint(doc, obj, \"pid\", result->pid);\n    yyjson_mut_obj_add_uint(doc, obj, \"ppid\", result->ppid);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"prettyName\", &result->prettyName);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"version\", &result->version);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"tty\", &result->tty);\n\n    return true;\n}\n\nvoid ffInitTerminalOptions(FFTerminalOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n}\n\nvoid ffDestroyTerminalOptions(FFTerminalOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffTerminalModuleInfo = {\n    .name = FF_TERMINAL_MODULE_NAME,\n    .description = \"Print current terminal name and version\",\n    .initOptions = (void*) ffInitTerminalOptions,\n    .destroyOptions = (void*) ffDestroyTerminalOptions,\n    .parseJsonObject = (void*) ffParseTerminalJsonObject,\n    .printModule = (void*) ffPrintTerminal,\n    .generateJsonResult = (void*) ffGenerateTerminalJsonResult,\n    .generateJsonConfig = (void*) ffGenerateTerminalJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Terminal process name\", \"process-name\"},\n        {\"The first argument of the command line when running the terminal\", \"exe\"},\n        {\"Terminal base name of arg0\", \"exe-name\"},\n        {\"Terminal pid\", \"pid\"},\n        {\"Terminal pretty name\", \"pretty-name\"},\n        {\"Terminal version\", \"version\"},\n        {\"Terminal full exe path\", \"exe-path\"},\n        {\"Terminal tty / pts used\", \"tty\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/terminal/terminal.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_TERMINAL_MODULE_NAME \"Terminal\"\n\nbool ffPrintTerminal(FFTerminalOptions* options);\nvoid ffInitTerminalOptions(FFTerminalOptions* options);\nvoid ffDestroyTerminalOptions(FFTerminalOptions* options);\n\nextern FFModuleBaseInfo ffTerminalModuleInfo;\n"
  },
  {
    "path": "src/modules/terminalfont/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFTerminalFontOptions\n{\n    FFModuleArgs moduleArgs;\n} FFTerminalFontOptions;\n\nstatic_assert(sizeof(FFTerminalFontOptions) <= FF_OPTION_MAX_SIZE, \"FFTerminalFontOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/terminalfont/terminalfont.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/terminalfont/terminalfont.h\"\n#include \"modules/terminalfont/terminalfont.h\"\n\n#define FF_TERMINALFONT_DISPLAY_NAME \"Terminal Font\"\n\nbool ffPrintTerminalFont(FFTerminalFontOptions* options)\n{\n    bool success = false;\n    FFTerminalFontResult terminalFont;\n    ffFontInit(&terminalFont.font);\n    ffFontInit(&terminalFont.fallback);\n    ffStrbufInit(&terminalFont.error);\n\n    if(!ffDetectTerminalFont(&terminalFont))\n    {\n        ffPrintError(FF_TERMINALFONT_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", terminalFont.error.chars);\n    }\n    else\n    {\n        if(options->moduleArgs.outputFormat.length == 0)\n        {\n            ffPrintLogoAndKey(FF_TERMINALFONT_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n            ffStrbufWriteTo(&terminalFont.font.pretty, stdout);\n            if(terminalFont.fallback.pretty.length)\n            {\n                fputs(\" / \", stdout);\n                ffStrbufWriteTo(&terminalFont.fallback.pretty, stdout);\n            }\n            putchar('\\n');\n        }\n        else\n        {\n            FF_PRINT_FORMAT_CHECKED(FF_TERMINALFONT_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n                FF_ARG(terminalFont.font.pretty, \"combined\"),\n                FF_ARG(terminalFont.font.name, \"name\"),\n                FF_ARG(terminalFont.font.size, \"size\"),\n                FF_ARG(terminalFont.font.styles, \"styles\"),\n            }));\n        }\n        success = true;\n    }\n\n    ffStrbufDestroy(&terminalFont.error);\n    ffFontDestroy(&terminalFont.font);\n    ffFontDestroy(&terminalFont.fallback);\n\n    return success;\n}\n\nvoid ffParseTerminalFontJsonObject(FFTerminalFontOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_TERMINALFONT_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateTerminalFontJsonConfig(FFTerminalFontOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateTerminalFontJsonResult(FF_MAYBE_UNUSED FFTerminalFontOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    bool success = false;\n    FFTerminalFontResult result;\n    ffFontInit(&result.font);\n    ffFontInit(&result.fallback);\n    ffStrbufInit(&result.error);\n\n    if(!ffDetectTerminalFont(&result))\n        yyjson_mut_obj_add_strbuf(doc, module, \"error\", &result.error);\n    else\n    {\n        yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n\n        yyjson_mut_val* font = yyjson_mut_obj_add_obj(doc, obj, \"font\");\n        yyjson_mut_obj_add_strbuf(doc, font, \"name\", &result.font.name);\n        yyjson_mut_obj_add_strbuf(doc, font, \"size\", &result.font.size);\n        yyjson_mut_val* fontStyles = yyjson_mut_obj_add_arr(doc, font, \"styles\");\n        FF_LIST_FOR_EACH(FFstrbuf, style, result.font.styles)\n        {\n            yyjson_mut_arr_add_strbuf(doc, fontStyles, style);\n        }\n        yyjson_mut_obj_add_strbuf(doc, font, \"pretty\", &result.font.pretty);\n\n        yyjson_mut_val* fallback = yyjson_mut_obj_add_obj(doc, obj, \"fallback\");\n        yyjson_mut_obj_add_strbuf(doc, fallback, \"name\", &result.fallback.name);\n        yyjson_mut_obj_add_strbuf(doc, fallback, \"size\", &result.fallback.size);\n        yyjson_mut_val* fallbackStyles = yyjson_mut_obj_add_arr(doc, fallback, \"styles\");\n        FF_LIST_FOR_EACH(FFstrbuf, style, result.fallback.styles)\n        {\n            yyjson_mut_arr_add_strbuf(doc, fallbackStyles, style);\n        }\n        yyjson_mut_obj_add_strbuf(doc, fallback, \"pretty\", &result.fallback.pretty);\n        success = true;\n    }\n\n    ffStrbufDestroy(&result.error);\n    ffFontDestroy(&result.font);\n    ffFontDestroy(&result.fallback);\n    return success;\n}\n\nvoid ffInitTerminalFontOptions(FFTerminalFontOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n}\n\nvoid ffDestroyTerminalFontOptions(FFTerminalFontOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffTerminalFontModuleInfo = {\n    .name = FF_TERMINALFONT_MODULE_NAME,\n    .description = \"Print font name and size used by current terminal\",\n    .initOptions = (void*) ffInitTerminalFontOptions,\n    .destroyOptions = (void*) ffDestroyTerminalFontOptions,\n    .parseJsonObject = (void*) ffParseTerminalFontJsonObject,\n    .printModule = (void*) ffPrintTerminalFont,\n    .generateJsonResult = (void*) ffGenerateTerminalFontJsonResult,\n    .generateJsonConfig = (void*) ffGenerateTerminalFontJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Terminal font combined\", \"combined\"},\n        {\"Terminal font name\", \"name\"},\n        {\"Terminal font size\", \"size\"},\n        {\"Terminal font styles\", \"styles\"},\n    })),\n};\n"
  },
  {
    "path": "src/modules/terminalfont/terminalfont.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_TERMINALFONT_MODULE_NAME \"TerminalFont\"\n\nbool ffPrintTerminalFont(FFTerminalFontOptions* options);\nvoid ffInitTerminalFontOptions(FFTerminalFontOptions* options);\nvoid ffDestroyTerminalFontOptions(FFTerminalFontOptions* options);\n\nextern FFModuleBaseInfo ffTerminalFontModuleInfo;\n"
  },
  {
    "path": "src/modules/terminalsize/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFTerminalSizeOptions\n{\n    FFModuleArgs moduleArgs;\n} FFTerminalSizeOptions;\n\nstatic_assert(sizeof(FFTerminalSizeOptions) <= FF_OPTION_MAX_SIZE, \"FFTerminalSizeOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/terminalsize/terminalsize.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/terminalsize/terminalsize.h\"\n#include \"modules/terminalsize/terminalsize.h\"\n\n#define FF_TERMINALSIZE_DISPLAY_NAME \"Terminal Size\"\n\nbool ffPrintTerminalSize(FFTerminalSizeOptions* options)\n{\n    FFTerminalSizeResult result = {};\n\n    if(!ffDetectTerminalSize(&result))\n    {\n        ffPrintError(FF_TERMINALSIZE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Failed to detect terminal size\");\n        return false;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_TERMINALSIZE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        printf(\"%u columns x %u rows\", result.columns, result.rows);\n\n        if (result.width != 0 && result.height != 0)\n            printf(\" (%upx x %upx)\", result.width, result.height);\n\n        putchar('\\n');\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_TERMINALSIZE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(result.rows, \"rows\"),\n            FF_ARG(result.columns, \"columns\"),\n            FF_ARG(result.width, \"width\"),\n            FF_ARG(result.height, \"height\"),\n        }));\n    }\n    return true;\n}\n\nvoid ffParseTerminalSizeJsonObject(FFTerminalSizeOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_TERMINALSIZE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateTerminalSizeJsonConfig(FFTerminalSizeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateTerminalSizeJsonResult(FF_MAYBE_UNUSED FFTerminalSizeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FFTerminalSizeResult result;\n\n    if(!ffDetectTerminalSize(&result))\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", \"Failed to detect terminal size\");\n        return false;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_uint(doc, obj, \"columns\", result.columns);\n    yyjson_mut_obj_add_uint(doc, obj, \"rows\", result.rows);\n    yyjson_mut_obj_add_uint(doc, obj, \"width\", result.width);\n    yyjson_mut_obj_add_uint(doc, obj, \"height\", result.height);\n\n    return true;\n}\n\nvoid ffInitTerminalSizeOptions(FFTerminalSizeOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰲎\");\n}\n\nvoid ffDestroyTerminalSizeOptions(FFTerminalSizeOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffTerminalSizeModuleInfo = {\n    .name = FF_TERMINALSIZE_MODULE_NAME,\n    .description = \"Print current terminal size\",\n    .initOptions = (void*) ffInitTerminalSizeOptions,\n    .destroyOptions = (void*) ffDestroyTerminalSizeOptions,\n    .parseJsonObject = (void*) ffParseTerminalSizeJsonObject,\n    .printModule = (void*) ffPrintTerminalSize,\n    .generateJsonResult = (void*) ffGenerateTerminalSizeJsonResult,\n    .generateJsonConfig = (void*) ffGenerateTerminalSizeJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Terminal rows\", \"rows\"},\n        {\"Terminal columns\", \"columns\"},\n        {\"Terminal width (in pixels)\", \"width\"},\n        {\"Terminal height (in pixels)\", \"height\"},\n    })),\n};\n"
  },
  {
    "path": "src/modules/terminalsize/terminalsize.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_TERMINALSIZE_MODULE_NAME \"TerminalSize\"\n\nbool ffPrintTerminalSize(FFTerminalSizeOptions* options);\nvoid ffInitTerminalSizeOptions(FFTerminalSizeOptions* options);\nvoid ffDestroyTerminalSizeOptions(FFTerminalSizeOptions* options);\n\nextern FFModuleBaseInfo ffTerminalSizeModuleInfo;\n"
  },
  {
    "path": "src/modules/terminaltheme/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFTerminalThemeOptions\n{\n    FFModuleArgs moduleArgs;\n} FFTerminalThemeOptions;\n\nstatic_assert(sizeof(FFTerminalThemeOptions) <= FF_OPTION_MAX_SIZE, \"FFTerminalThemeOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/terminaltheme/terminaltheme.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/terminaltheme/terminaltheme.h\"\n#include \"modules/terminaltheme/terminaltheme.h\"\n\n#include <inttypes.h>\n\n#define FF_TERMINALTHEME_DISPLAY_NAME \"Terminal Theme\"\n\nbool ffPrintTerminalTheme(FFTerminalThemeOptions* options)\n{\n    FFTerminalThemeResult result = {};\n\n    if(!ffDetectTerminalTheme(&result, false))\n    {\n        ffPrintError(FF_TERMINALTHEME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Failed to detect terminal theme\");\n        return false;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_TERMINALTHEME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        printf(\"#%02\" PRIX16 \"%02\" PRIX16 \"%02\" PRIX16 \" (FG) - #%02\" PRIX16 \"%02\" PRIX16 \"%02\" PRIX16 \" (BG) [%s]\\n\",\n            result.fg.r, result.fg.g, result.fg.b,\n            result.bg.r, result.bg.g, result.bg.b,\n            result.bg.dark ? \"Dark\" : \"Light\");\n    }\n    else\n    {\n        char fg[32], bg[32];\n        const char* fgType = result.fg.dark ? \"Dark\" : \"Light\";\n        const char* bgType = result.bg.dark ? \"Dark\" : \"Light\";\n        snprintf(fg, ARRAY_SIZE(fg), \"#%02\" PRIX16 \"%02\" PRIX16 \"%02\" PRIX16, result.fg.r, result.fg.g, result.fg.b);\n        snprintf(bg, ARRAY_SIZE(bg), \"#%02\" PRIX16 \"%02\" PRIX16 \"%02\" PRIX16, result.bg.r, result.bg.g, result.bg.b);\n        FF_PRINT_FORMAT_CHECKED(FF_TERMINALTHEME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(fg, \"fg-color\"),\n            FF_ARG(fgType, \"fg-type\"),\n            FF_ARG(bg, \"bg-color\"),\n            FF_ARG(bgType, \"bg-type\"),\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseTerminalThemeJsonObject(FFTerminalThemeOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_TERMINALTHEME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateTerminalThemeJsonConfig(FFTerminalThemeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateTerminalThemeJsonResult(FF_MAYBE_UNUSED FFTerminalThemeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FFTerminalThemeResult result = {};\n\n    if(!ffDetectTerminalTheme(&result, false))\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", \"Failed to detect terminal theme\");\n        return false;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n\n    yyjson_mut_val* fg = yyjson_mut_obj_add_obj(doc, obj, \"fg\");\n    yyjson_mut_obj_add_uint(doc, fg, \"r\", result.fg.r);\n    yyjson_mut_obj_add_uint(doc, fg, \"g\", result.fg.g);\n    yyjson_mut_obj_add_uint(doc, fg, \"b\", result.fg.b);\n    yyjson_mut_obj_add_bool(doc, fg, \"dark\", result.fg.dark);\n\n    yyjson_mut_val* bg = yyjson_mut_obj_add_obj(doc, obj, \"bg\");\n    yyjson_mut_obj_add_uint(doc, bg, \"r\", result.bg.r);\n    yyjson_mut_obj_add_uint(doc, bg, \"g\", result.bg.g);\n    yyjson_mut_obj_add_uint(doc, bg, \"b\", result.bg.b);\n    yyjson_mut_obj_add_bool(doc, bg, \"dark\", result.bg.dark);\n\n    return true;\n}\n\nvoid ffInitTerminalThemeOptions(FFTerminalThemeOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰔎\");\n}\n\nvoid ffDestroyTerminalThemeOptions(FFTerminalThemeOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffTerminalThemeModuleInfo = {\n    .name = FF_TERMINALTHEME_MODULE_NAME,\n    .description = \"Print current terminal theme (foreground and background colors)\",\n    .initOptions = (void*) ffInitTerminalThemeOptions,\n    .destroyOptions = (void*) ffDestroyTerminalThemeOptions,\n    .parseJsonObject = (void*) ffParseTerminalThemeJsonObject,\n    .printModule = (void*) ffPrintTerminalTheme,\n    .generateJsonResult = (void*) ffGenerateTerminalThemeJsonResult,\n    .generateJsonConfig = (void*) ffGenerateTerminalThemeJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Terminal foreground color\", \"fg-color\"},\n        {\"Terminal foreground type (Dark / Light)\", \"fg-type\"},\n        {\"Terminal background color\", \"bg-color\"},\n        {\"Terminal background type (Dark / Light)\", \"bg-type\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/terminaltheme/terminaltheme.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_TERMINALTHEME_MODULE_NAME \"TerminalTheme\"\n\nbool ffPrintTerminalTheme(FFTerminalThemeOptions* options);\nvoid ffInitTerminalThemeOptions(FFTerminalThemeOptions* options);\nvoid ffDestroyTerminalThemeOptions(FFTerminalThemeOptions* options);\n\nextern FFModuleBaseInfo ffTerminalThemeModuleInfo;\n"
  },
  {
    "path": "src/modules/theme/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFThemeOptions\n{\n    FFModuleArgs moduleArgs;\n} FFThemeOptions;\n\nstatic_assert(sizeof(FFThemeOptions) <= FF_OPTION_MAX_SIZE, \"FFThemeOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/theme/theme.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/theme/theme.h\"\n#include \"modules/theme/theme.h\"\n\nbool ffPrintTheme(FFThemeOptions* options)\n{\n    FFThemeResult result = {\n        .theme1 = ffStrbufCreate(),\n        .theme2 = ffStrbufCreate()\n    };\n    const char* error = ffDetectTheme(&result);\n\n    if(error)\n    {\n        ffPrintError(FF_THEME_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_THEME_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        if (result.theme1.length)\n            ffStrbufWriteTo(&result.theme1, stdout);\n        if (result.theme2.length)\n        {\n            if (result.theme1.length)\n                fputs(\", \", stdout);\n            ffStrbufWriteTo(&result.theme2, stdout);\n        }\n        putchar('\\n');\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_THEME_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(result.theme1, \"theme1\"),\n            FF_ARG(result.theme2, \"theme2\"),\n        }));\n    }\n\n    ffStrbufDestroy(&result.theme1);\n    ffStrbufDestroy(&result.theme2);\n    return true;\n}\n\nvoid ffParseThemeJsonObject(FFThemeOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_THEME_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateThemeJsonConfig(FFThemeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateThemeJsonResult(FF_MAYBE_UNUSED FFThemeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FFThemeResult result = {\n        .theme1 = ffStrbufCreate(),\n        .theme2 = ffStrbufCreate()\n    };\n    const char* error = ffDetectTheme(&result);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* theme = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_strbuf(doc, theme, \"theme1\", &result.theme1);\n    yyjson_mut_obj_add_strbuf(doc, theme, \"theme2\", &result.theme2);\n\n    ffStrbufDestroy(&result.theme1);\n    ffStrbufDestroy(&result.theme2);\n\n    return true;\n}\n\nvoid ffInitThemeOptions(FFThemeOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰉼\");\n}\n\nvoid ffDestroyThemeOptions(FFThemeOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffThemeModuleInfo = {\n    .name = FF_THEME_MODULE_NAME,\n    .description = \"Print current theme of desktop environment\",\n    .initOptions = (void*) ffInitThemeOptions,\n    .destroyOptions = (void*) ffDestroyThemeOptions,\n    .parseJsonObject = (void*) ffParseThemeJsonObject,\n    .printModule = (void*) ffPrintTheme,\n    .generateJsonResult = (void*) ffGenerateThemeJsonResult,\n    .generateJsonConfig = (void*) ffGenerateThemeJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Theme part 1\", \"theme1\"},\n        {\"Theme part 2\", \"theme2\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/theme/theme.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_THEME_MODULE_NAME \"Theme\"\n\nbool ffPrintTheme(FFThemeOptions* options);\nvoid ffInitThemeOptions(FFThemeOptions* options);\nvoid ffDestroyThemeOptions(FFThemeOptions* options);\n\nextern FFModuleBaseInfo ffThemeModuleInfo;\n"
  },
  {
    "path": "src/modules/title/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFTitleOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFstrbuf colorUser;\n    FFstrbuf colorAt;\n    FFstrbuf colorHost;\n    bool fqdn;\n} FFTitleOptions;\n\nstatic_assert(sizeof(FFTitleOptions) <= FF_OPTION_MAX_SIZE, \"FFTitleOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/title/title.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/textModifier.h\"\n#include \"modules/title/title.h\"\n\nstatic void appendText(FFstrbuf* output, const FFstrbuf* text, const FFstrbuf* color)\n{\n    if (!instance.config.display.pipe)\n    {\n        if (instance.config.display.brightColor)\n            ffStrbufAppendS(output, FASTFETCH_TEXT_MODIFIER_BOLT);\n        if (color->length > 0)\n            ffStrbufAppendF(output, \"\\e[%sm\", color->chars);\n        else if (instance.config.display.colorTitle.length > 0)\n            ffStrbufAppendF(output, \"\\e[%sm\", instance.config.display.colorTitle.chars);\n    }\n\n    ffStrbufAppend(output, text);\n\n    if(!instance.config.display.pipe)\n        ffStrbufAppendS(output, FASTFETCH_TEXT_MODIFIER_RESET);\n}\n\nbool ffPrintTitle(FFTitleOptions* options)\n{\n    FF_STRBUF_AUTO_DESTROY userNameColored = ffStrbufCreate();\n    appendText(&userNameColored, &instance.state.platform.userName, &options->colorUser);\n\n    FF_STRBUF_AUTO_DESTROY hostName = ffStrbufCreateCopy(&instance.state.platform.hostName);\n    if (!options->fqdn)\n        ffStrbufSubstrBeforeFirstC(&hostName, '.');\n    instance.state.titleFqdn = options->fqdn;\n\n    FF_STRBUF_AUTO_DESTROY hostNameColored = ffStrbufCreate();\n    appendText(&hostNameColored, &hostName, &options->colorHost);\n\n    FF_STRBUF_AUTO_DESTROY atColored = ffStrbufCreate();\n    if (!instance.config.display.pipe && options->colorAt.length > 0)\n    {\n        ffStrbufAppendF(&atColored, \"\\e[%sm\", options->colorAt.chars);\n        ffStrbufAppendC(&atColored, '@');\n        ffStrbufAppendS(&atColored, FASTFETCH_TEXT_MODIFIER_RESET);\n    }\n    else\n        ffStrbufAppendC(&atColored, '@');\n\n    if (options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_TITLE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n        ffStrbufWriteTo(&userNameColored, stdout);\n        ffStrbufWriteTo(&atColored, stdout);\n        ffStrbufPutTo(&hostNameColored, stdout);\n    }\n    else\n    {\n        FF_STRBUF_AUTO_DESTROY cwdTilde = ffStrbufCreate();\n        if (\n            #if _WIN32\n                ffStrbufStartsWithIgnCase\n            #else\n                ffStrbufStartsWith\n            #endif\n            (&instance.state.platform.cwd, &instance.state.platform.homeDir))\n        {\n            ffStrbufAppendS(&cwdTilde, \"~/\");\n            ffStrbufAppendNS(&cwdTilde, instance.state.platform.cwd.length - instance.state.platform.homeDir.length, &instance.state.platform.cwd.chars[instance.state.platform.homeDir.length]);\n        }\n        else\n            ffStrbufSet(&cwdTilde, &instance.state.platform.cwd);\n        if (cwdTilde.length > 1) ffStrbufTrimRight(&cwdTilde, '/');\n\n        FF_PRINT_FORMAT_CHECKED(FF_TITLE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(instance.state.platform.userName, \"user-name\"),\n            FF_ARG(hostName, \"host-name\"),\n            FF_ARG(instance.state.platform.homeDir, \"home-dir\"),\n            FF_ARG(instance.state.platform.exePath, \"exe-path\"),\n            FF_ARG(instance.state.platform.userShell, \"user-shell\"),\n            FF_ARG(userNameColored, \"user-name-colored\"),\n            FF_ARG(atColored, \"at-symbol-colored\"),\n            FF_ARG(hostNameColored, \"host-name-colored\"),\n            FF_ARG(instance.state.platform.fullUserName, \"full-user-name\"),\n            #ifndef _WIN32\n            FF_ARG(instance.state.platform.uid, \"user-id\"),\n            #else\n            FF_ARG(instance.state.platform.sid, \"user-id\"),\n            #endif\n            FF_ARG(instance.state.platform.pid, \"pid\"),\n            FF_ARG(cwdTilde, \"cwd\"),\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseTitleJsonObject(FFTitleOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"fqdn\"))\n        {\n            options->fqdn = yyjson_get_bool(val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"color\"))\n        {\n            if (!yyjson_is_obj(val))\n                continue;\n\n            yyjson_val* color = yyjson_obj_get(val, \"user\");\n            if (color)\n                ffOptionParseColor(yyjson_get_str(color), &options->colorUser);\n            color = yyjson_obj_get(val, \"at\");\n            if (color)\n                ffOptionParseColor(yyjson_get_str(color), &options->colorAt);\n            color = yyjson_obj_get(val, \"host\");\n            if (color)\n                ffOptionParseColor(yyjson_get_str(color), &options->colorHost);\n            continue;\n        }\n\n        ffPrintError(FF_TITLE_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateTitleJsonConfig(FFTitleOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    yyjson_mut_obj_add_bool(doc, module, \"fqdn\", options->fqdn);\n\n    yyjson_mut_val* color = yyjson_mut_obj_add_obj(doc, module, \"color\");\n    yyjson_mut_obj_add_strbuf(doc, color, \"user\", &options->colorUser);\n    yyjson_mut_obj_add_strbuf(doc, color, \"at\", &options->colorAt);\n    yyjson_mut_obj_add_strbuf(doc, color, \"host\", &options->colorHost);\n}\n\nbool ffGenerateTitleJsonResult(FF_MAYBE_UNUSED FFTitleOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    #ifdef _WIN32\n    yyjson_mut_obj_add_strbuf(doc, obj, \"userId\", &instance.state.platform.sid);\n    #else\n    yyjson_mut_obj_add_uint(doc, obj, \"userId\", instance.state.platform.uid);\n    #endif\n    yyjson_mut_obj_add_strbuf(doc, obj, \"userName\", &instance.state.platform.userName);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"fullUserName\", &instance.state.platform.fullUserName);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"hostName\", &instance.state.platform.hostName);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"homeDir\", &instance.state.platform.homeDir);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"exePath\", &instance.state.platform.exePath);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"userShell\", &instance.state.platform.userShell);\n    yyjson_mut_obj_add_uint(doc, obj, \"pid\", instance.state.platform.pid);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"cwd\", &instance.state.platform.cwd);\n\n    return true;\n}\n\nvoid ffInitTitleOptions(FFTitleOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n    ffStrbufSetStatic(&options->moduleArgs.key, \" \");\n\n    options->fqdn = false;\n    ffStrbufInit(&options->colorUser);\n    ffStrbufInit(&options->colorAt);\n    ffStrbufInit(&options->colorHost);\n}\n\nvoid ffDestroyTitleOptions(FFTitleOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n    ffStrbufDestroy(&options->colorUser);\n    ffStrbufDestroy(&options->colorAt);\n    ffStrbufDestroy(&options->colorHost);\n}\n\nFFModuleBaseInfo ffTitleModuleInfo = {\n    .name = FF_TITLE_MODULE_NAME,\n    .description = \"Print title, which contains your user name, hostname\",\n    .initOptions = (void*) ffInitTitleOptions,\n    .destroyOptions = (void*) ffDestroyTitleOptions,\n    .parseJsonObject = (void*) ffParseTitleJsonObject,\n    .printModule = (void*) ffPrintTitle,\n    .generateJsonResult = (void*) ffGenerateTitleJsonResult,\n    .generateJsonConfig = (void*) ffGenerateTitleJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"User name\", \"user-name\"},\n        {\"Host name\", \"host-name\"},\n        {\"Home directory\", \"home-dir\"},\n        {\"Executable path of current process\", \"exe-path\"},\n        {\"User's default shell\", \"user-shell\"},\n        {\"User name (colored)\", \"user-name-colored\"},\n        {\"@ symbol (colored)\", \"at-symbol-colored\"},\n        {\"Host name (colored)\", \"host-name-colored\"},\n        {\"Full user name\", \"full-user-name\"},\n        {\"UID (*nix) / SID (Windows)\", \"user-id\"},\n        {\"PID of current process\", \"pid\"},\n        {\"CWD with home dir replaced by `~`\", \"cwd\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/title/title.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_TITLE_MODULE_NAME \"Title\"\n\nbool ffPrintTitle(FFTitleOptions* options);\nvoid ffInitTitleOptions(FFTitleOptions* options);\nvoid ffDestroyTitleOptions(FFTitleOptions* options);\n\nextern FFModuleBaseInfo ffTitleModuleInfo;\n"
  },
  {
    "path": "src/modules/tpm/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFTPMOptions\n{\n    FFModuleArgs moduleArgs;\n} FFTPMOptions;\n\nstatic_assert(sizeof(FFTPMOptions) <= FF_OPTION_MAX_SIZE, \"FFTPMOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/tpm/tpm.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/tpm/tpm.h\"\n#include \"modules/tpm/tpm.h\"\n\nbool ffPrintTPM(FFTPMOptions* options)\n{\n    FFTPMResult result = {\n        .version = ffStrbufCreate(),\n        .description = ffStrbufCreate()\n    };\n    const char* error = ffDetectTPM(&result);\n\n    if(error)\n    {\n        ffPrintError(FF_TPM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_TPM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        if (result.description.length > 0)\n            ffStrbufPutTo(&result.description, stdout);\n        else\n            ffStrbufPutTo(&result.version, stdout);\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_TPM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(result.version, \"version\"),\n            FF_ARG(result.description, \"description\"),\n        }));\n    }\n\n    ffStrbufDestroy(&result.version);\n    ffStrbufDestroy(&result.description);\n\n    return true;\n}\n\nvoid ffParseTPMJsonObject(FFTPMOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_TPM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateTPMJsonConfig(FFTPMOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateTPMJsonResult(FF_MAYBE_UNUSED FFTPMOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FFTPMResult result = {\n        .version = ffStrbufCreate(),\n        .description = ffStrbufCreate()\n    };\n    const char* error = ffDetectTPM(&result);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_strbuf(doc, obj, \"version\", &result.version);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"description\", &result.description);\n\n    ffStrbufDestroy(&result.version);\n    ffStrbufDestroy(&result.description);\n\n    return true;\n}\n\nvoid ffInitTPMOptions(FFTPMOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n}\n\nvoid ffDestroyTPMOptions(FFTPMOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffTPMModuleInfo = {\n    .name = FF_TPM_MODULE_NAME,\n    .description = \"Print info of Trusted Platform Module (TPM) Security Device\",\n    .initOptions = (void*) ffInitTPMOptions,\n    .destroyOptions = (void*) ffDestroyTPMOptions,\n    .parseJsonObject = (void*) ffParseTPMJsonObject,\n    .printModule = (void*) ffPrintTPM,\n    .generateJsonResult = (void*) ffGenerateTPMJsonResult,\n    .generateJsonConfig = (void*) ffGenerateTPMJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"TPM device version\", \"version\"},\n        {\"TPM general description\", \"description\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/tpm/tpm.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_TPM_MODULE_NAME \"TPM\"\n\nbool ffPrintTPM(FFTPMOptions* options);\nvoid ffInitTPMOptions(FFTPMOptions* options);\nvoid ffDestroyTPMOptions(FFTPMOptions* options);\n\nextern FFModuleBaseInfo ffTPMModuleInfo;\n"
  },
  {
    "path": "src/modules/uptime/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFUptimeOptions\n{\n    FFModuleArgs moduleArgs;\n} FFUptimeOptions;\n\nstatic_assert(sizeof(FFUptimeOptions) <= FF_OPTION_MAX_SIZE, \"FFUptimeOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/uptime/uptime.c",
    "content": "#include \"common/duration.h\"\n#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/time.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/uptime/uptime.h\"\n#include \"modules/uptime/uptime.h\"\n\nbool ffPrintUptime(FFUptimeOptions* options)\n{\n    FFUptimeResult result = {};\n\n    const char* error = ffDetectUptime(&result);\n\n    if(error)\n    {\n        ffPrintError(FF_UPTIME_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    uint64_t uptime = result.uptime;\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n    ffDurationAppendNum((uptime + 500) / 1000, &buffer);\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_UPTIME_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        ffStrbufPutTo(&buffer, stdout);\n    }\n    else\n    {\n        uint32_t milliseconds = (uint32_t) (uptime % 1000);\n        uptime /= 1000;\n        uint32_t seconds = (uint32_t) (uptime % 60);\n        uptime /= 60;\n        uint32_t minutes = (uint32_t) (uptime % 60);\n        uptime /= 60;\n        uint32_t hours = (uint32_t) (uptime % 24);\n        uptime /= 24;\n        uint32_t days = (uint32_t) uptime;\n\n        FFTimeGetAgeResult age = ffTimeGetAge(result.bootTime, ffTimeGetNow());\n\n        FF_PRINT_FORMAT_CHECKED(FF_UPTIME_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(days, \"days\"),\n            FF_ARG(hours, \"hours\"),\n            FF_ARG(minutes, \"minutes\"),\n            FF_ARG(seconds, \"seconds\"),\n            FF_ARG(milliseconds, \"milliseconds\"),\n            {FF_ARG_TYPE_STRING, ffTimeToShortStr(result.bootTime), \"boot-time\"},\n            FF_ARG(age.years, \"years\"),\n            FF_ARG(age.daysOfYear, \"days-of-year\"),\n            FF_ARG(age.yearsFraction, \"years-fraction\"),\n            FF_ARG(buffer, \"formatted\")\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseUptimeJsonObject(FFUptimeOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_UPTIME_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateUptimeJsonConfig(FFUptimeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateUptimeJsonResult(FF_MAYBE_UNUSED FFUptimeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FFUptimeResult result;\n    const char* error = ffDetectUptime(&result);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_uint(doc, obj, \"uptime\", result.uptime);\n    yyjson_mut_obj_add_strcpy(doc, obj, \"bootTime\", ffTimeToFullStr(result.bootTime));\n\n    return true;\n}\n\nvoid ffInitUptimeOptions(FFUptimeOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n}\n\nvoid ffDestroyUptimeOptions(FFUptimeOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffUptimeModuleInfo = {\n    .name = FF_UPTIME_MODULE_NAME,\n    .description = \"Print how long system has been running\",\n    .initOptions = (void*) ffInitUptimeOptions,\n    .destroyOptions = (void*) ffDestroyUptimeOptions,\n    .parseJsonObject = (void*) ffParseUptimeJsonObject,\n    .printModule = (void*) ffPrintUptime,\n    .generateJsonResult = (void*) ffGenerateUptimeJsonResult,\n    .generateJsonConfig = (void*) ffGenerateUptimeJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Days after boot\", \"days\"},\n        {\"Hours after boot\", \"hours\"},\n        {\"Minutes after boot\", \"minutes\"},\n        {\"Seconds after boot\", \"seconds\"},\n        {\"Milliseconds after boot\", \"milliseconds\"},\n        {\"Boot time in local timezone\", \"boot-time\"},\n        {\"Years integer after boot\", \"years\"},\n        {\"Days of year after boot\", \"days-of-year\"},\n        {\"Years fraction after boot\", \"years-fraction\"},\n        {\"Formatted uptime\", \"formatted\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/uptime/uptime.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_UPTIME_MODULE_NAME \"Uptime\"\n\nbool ffPrintUptime(FFUptimeOptions* options);\nvoid ffInitUptimeOptions(FFUptimeOptions* options);\nvoid ffDestroyUptimeOptions(FFUptimeOptions* options);\n\nextern FFModuleBaseInfo ffUptimeModuleInfo;\n"
  },
  {
    "path": "src/modules/users/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFUsersOptions\n{\n    FFModuleArgs moduleArgs;\n\n    bool compact;\n    bool myselfOnly;\n} FFUsersOptions;\n\nstatic_assert(sizeof(FFUsersOptions) <= FF_OPTION_MAX_SIZE, \"FFUsersOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/users/users.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/time.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/users/users.h\"\n#include \"modules/users/users.h\"\n\n#pragma GCC diagnostic ignored \"-Wformat\" // warning: unknown conversion type character 'F' in format\n\nbool ffPrintUsers(FFUsersOptions* options)\n{\n    FF_LIST_AUTO_DESTROY users = ffListCreate(sizeof(FFUserResult));\n\n    const char* error = ffDetectUsers(options, &users);\n\n    if(error)\n    {\n        ffPrintError(FF_USERS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if(users.length == 0)\n    {\n        ffPrintError(FF_USERS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", \"Unable to detect any users\");\n        return false;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        if(options->compact)\n        {\n            ffPrintLogoAndKey(FF_USERS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n            FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();\n            for(uint32_t i = 0; i < users.length; ++i)\n            {\n                if(i > 0)\n                    ffStrbufAppendS(&result, \", \");\n                FFUserResult* user = FF_LIST_GET(FFUserResult, users, i);\n                ffStrbufAppend(&result, &user->name);\n            }\n            ffStrbufPutTo(&result, stdout);\n        }\n        else\n        {\n            for(uint32_t i = 0; i < users.length; ++i)\n            {\n                FFUserResult* user = FF_LIST_GET(FFUserResult, users, i);\n\n                ffPrintLogoAndKey(FF_USERS_MODULE_NAME, users.length == 1 ? 0 : (uint8_t) (i + 1), &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n                FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateCopy(&user->name);\n                if(user->hostName.length)\n                    ffStrbufAppendF(&result, \"@%s\", user->hostName.chars);\n\n                if(user->loginTime)\n                    ffStrbufAppendF(&result, \" - login time %s\", ffTimeToShortStr(user->loginTime));\n\n                ffStrbufPutTo(&result, stdout);\n            }\n        }\n    }\n    else\n    {\n        uint64_t now = ffTimeGetNow();\n        for(uint32_t i = 0; i < users.length; ++i)\n        {\n            FFUserResult* user = FF_LIST_GET(FFUserResult, users, i);\n\n            uint64_t duration = now - user->loginTime;\n            uint32_t milliseconds = (uint32_t) (duration % 1000);\n            duration /= 1000;\n            uint32_t seconds = (uint32_t) (duration % 60);\n            duration /= 60;\n            uint32_t minutes = (uint32_t) (duration % 60);\n            duration /= 60;\n            uint32_t hours = (uint32_t) (duration % 24);\n            duration /= 24;\n            uint32_t days = (uint32_t) duration;\n\n            FFTimeGetAgeResult age = ffTimeGetAge(user->loginTime, ffTimeGetNow());\n\n            FF_PRINT_FORMAT_CHECKED(FF_USERS_MODULE_NAME, users.length == 1 ? 0 : (uint8_t) (i + 1), &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n                FF_ARG(user->name, \"name\"),\n                FF_ARG(user->hostName, \"host-name\"),\n                FF_ARG(user->sessionName, \"session-name\"),\n                FF_ARG(user->clientIp, \"client-ip\"),\n                {FF_ARG_TYPE_STRING, ffTimeToShortStr(user->loginTime), \"login-time\"},\n                FF_ARG(days, \"days\"),\n                FF_ARG(hours, \"hours\"),\n                FF_ARG(minutes, \"minutes\"),\n                FF_ARG(seconds, \"seconds\"),\n                FF_ARG(milliseconds, \"milliseconds\"),\n                FF_ARG(age.years, \"years\"),\n                FF_ARG(age.daysOfYear, \"days-of-year\"),\n                FF_ARG(age.yearsFraction, \"years-fraction\"),\n            }));\n        }\n    }\n\n    FF_LIST_FOR_EACH(FFUserResult, user, users)\n    {\n        ffStrbufDestroy(&user->clientIp);\n        ffStrbufDestroy(&user->hostName);\n        ffStrbufDestroy(&user->sessionName);\n        ffStrbufDestroy(&user->name);\n    }\n\n    return true;\n}\n\nvoid ffParseUsersJsonObject(FFUsersOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (unsafe_yyjson_equals_str(key, \"type\") || unsafe_yyjson_equals_str(key, \"condition\"))\n            continue;\n\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"compact\"))\n        {\n            options->compact = yyjson_get_bool(val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"myselfOnly\"))\n        {\n            options->myselfOnly = yyjson_get_bool(val);\n            continue;\n        }\n\n        ffPrintError(FF_USERS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateUsersJsonConfig(FFUsersOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    yyjson_mut_obj_add_bool(doc, module, \"compact\", options->compact);\n\n    yyjson_mut_obj_add_bool(doc, module, \"myselfOnly\", options->myselfOnly);\n}\n\nbool ffGenerateUsersJsonResult(FFUsersOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFUserResult));\n\n    const char* error = ffDetectUsers(options, &results);\n\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n    FF_LIST_FOR_EACH(FFUserResult, user, results)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &user->name);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"hostName\", &user->hostName);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"sessionName\", &user->sessionName);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"clientIp\", &user->clientIp);\n        const char* pstr = ffTimeToFullStr(user->loginTime);\n        if (*pstr)\n            yyjson_mut_obj_add_strcpy(doc, obj, \"loginTime\", pstr);\n        else\n            yyjson_mut_obj_add_null(doc, obj, \"loginTime\");\n    }\n\n    FF_LIST_FOR_EACH(FFUserResult, user, results)\n    {\n        ffStrbufDestroy(&user->clientIp);\n        ffStrbufDestroy(&user->hostName);\n        ffStrbufDestroy(&user->sessionName);\n        ffStrbufDestroy(&user->name);\n    }\n\n    return true;\n}\n\nvoid ffInitUsersOptions(FFUsersOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n\n    options->compact = false;\n    options->myselfOnly = false;\n}\n\nvoid ffDestroyUsersOptions(FFUsersOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffUsersModuleInfo = {\n    .name = FF_USERS_MODULE_NAME,\n    .description = \"Print users currently logged in\",\n    .initOptions = (void*) ffInitUsersOptions,\n    .destroyOptions = (void*) ffDestroyUsersOptions,\n    .parseJsonObject = (void*) ffParseUsersJsonObject,\n    .printModule = (void*) ffPrintUsers,\n    .generateJsonResult = (void*) ffGenerateUsersJsonResult,\n    .generateJsonConfig = (void*) ffGenerateUsersJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"User name\", \"name\"},\n        {\"Host name\", \"host-name\"},\n        {\"Session name\", \"session-name\"},\n        {\"Client IP\", \"client-ip\"},\n        {\"Login Time in local timezone\", \"login-time\"},\n        {\"Days after login\", \"days\"},\n        {\"Hours after login\", \"hours\"},\n        {\"Minutes after login\", \"minutes\"},\n        {\"Seconds after login\", \"seconds\"},\n        {\"Milliseconds after login\", \"milliseconds\"},\n        {\"Years integer after login\", \"years\"},\n        {\"Days of year after login\", \"days-of-year\"},\n        {\"Years fraction after login\", \"years-fraction\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/users/users.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_USERS_MODULE_NAME \"Users\"\n\nbool ffPrintUsers(FFUsersOptions* options);\nvoid ffInitUsersOptions(FFUsersOptions* options);\nvoid ffDestroyUsersOptions(FFUsersOptions* options);\n\nextern FFModuleBaseInfo ffUsersModuleInfo;\n"
  },
  {
    "path": "src/modules/version/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFVersionOptions\n{\n    FFModuleArgs moduleArgs;\n} FFVersionOptions;\n\nstatic_assert(sizeof(FFVersionOptions) <= FF_OPTION_MAX_SIZE, \"FFVersionOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/version/version.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/libc/libc.h\"\n#include \"detection/version/version.h\"\n#include \"modules/version/version.h\"\n\nbool ffPrintVersion(FFVersionOptions* options)\n{\n    FFVersionResult* result = &ffVersionResult;\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_VERSION_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        printf(\"%s %s%s%s (%s)\\n\", result->projectName, result->version, result->versionTweak, result->debugMode ? \"-debug\" : \"\", result->architecture);\n    }\n    else\n    {\n        FFLibcResult libcResult;\n        FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();\n        if (!ffDetectLibc(&libcResult))\n        {\n            ffStrbufSetS(&buf, libcResult.name);\n            if (libcResult.version)\n            {\n                ffStrbufAppendC(&buf, ' ');\n                ffStrbufAppendS(&buf, libcResult.version);\n            }\n        }\n\n        const char* buildType = result->debugMode ? \"debug\" : \"release\";\n        FF_PRINT_FORMAT_CHECKED(FF_VERSION_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(result->projectName, \"project-name\"),\n            FF_ARG(result->version, \"version\"),\n            FF_ARG(result->versionTweak, \"version-tweak\"),\n            FF_ARG(buildType, \"build-type\"),\n            FF_ARG(result->sysName, \"sysname\"),\n            FF_ARG(result->architecture, \"arch\"),\n            FF_ARG(result->cmakeBuiltType, \"cmake-built-type\"),\n            FF_ARG(result->compileTime, \"compile-time\"),\n            FF_ARG(result->compiler, \"compiler\"),\n            FF_ARG(buf, \"libc\"),\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseVersionJsonObject(FFVersionOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_VERSION_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateVersionJsonConfig(FFVersionOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateVersionJsonResult(FF_MAYBE_UNUSED FFVersionOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FFVersionResult* result = &ffVersionResult;\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_str(doc, obj, \"projectName\", result->projectName);\n    yyjson_mut_obj_add_str(doc, obj, \"sysName\", result->sysName);\n    yyjson_mut_obj_add_str(doc, obj, \"architecture\", result->architecture);\n    yyjson_mut_obj_add_str(doc, obj, \"version\", result->version);\n    yyjson_mut_obj_add_str(doc, obj, \"versionGit\", result->versionGit);\n    yyjson_mut_obj_add_str(doc, obj, \"cmakeBuiltType\", result->cmakeBuiltType);\n    yyjson_mut_obj_add_str(doc, obj, \"compileTime\", result->compileTime);\n    yyjson_mut_obj_add_str(doc, obj, \"compiler\", result->compiler);\n    yyjson_mut_obj_add_bool(doc, obj, \"debugMode\", result->debugMode);\n\n    FFLibcResult libcResult;\n    if (ffDetectLibc(&libcResult))\n    {\n        yyjson_mut_obj_add_null(doc, obj, \"libc\");\n    }\n    else\n    {\n        FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreateS(libcResult.name);\n        if (libcResult.version)\n        {\n            ffStrbufAppendC(&buf, ' ');\n            ffStrbufAppendS(&buf, libcResult.version);\n        }\n        yyjson_mut_obj_add_strbuf(doc, obj, \"libc\", &buf);\n    }\n\n    return true;\n}\n\nvoid ffInitVersionOptions(FFVersionOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n}\n\nvoid ffDestroyVersionOptions(FFVersionOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffVersionModuleInfo = {\n    .name = FF_VERSION_MODULE_NAME,\n    .description = \"Print Fastfetch version\",\n    .initOptions = (void*) ffInitVersionOptions,\n    .destroyOptions = (void*) ffDestroyVersionOptions,\n    .parseJsonObject = (void*) ffParseVersionJsonObject,\n    .printModule = (void*) ffPrintVersion,\n    .generateJsonResult = (void*) ffGenerateVersionJsonResult,\n    .generateJsonConfig = (void*) ffGenerateVersionJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Project name\", \"project-name\"},\n        {\"Version\", \"version\"},\n        {\"Version tweak\", \"version-tweak\"},\n        {\"Build type (debug or release)\", \"build-type\"},\n        {\"System name\", \"sysname\"},\n        {\"Architecture\", \"arch\"},\n        {\"CMake build type when compiling (Debug, Release, RelWithDebInfo, MinSizeRel)\", \"cmake-built-type\"},\n        {\"Date time when compiling\", \"compile-time\"},\n        {\"Compiler used when compiling\", \"compiler\"},\n        {\"Libc used when compiling\", \"libc\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/version/version.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_VERSION_MODULE_NAME \"Version\"\n\nbool ffPrintVersion(FFVersionOptions* options);\nvoid ffInitVersionOptions(FFVersionOptions* options);\nvoid ffDestroyVersionOptions(FFVersionOptions* options);\n\nextern FFModuleBaseInfo ffVersionModuleInfo;\n"
  },
  {
    "path": "src/modules/vulkan/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFVulkanOptions\n{\n    FFModuleArgs moduleArgs;\n} FFVulkanOptions;\n\nstatic_assert(sizeof(FFVulkanOptions) <= FF_OPTION_MAX_SIZE, \"FFVulkanOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/vulkan/vulkan.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/gpu/gpu.h\"\n#include \"detection/vulkan/vulkan.h\"\n#include \"modules/vulkan/vulkan.h\"\n\nbool ffPrintVulkan(FFVulkanOptions* options)\n{\n    const FFVulkanResult* vulkan = ffDetectVulkan();\n\n    if(vulkan->error)\n    {\n        ffPrintError(FF_VULKAN_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", vulkan->error);\n        return false;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_VULKAN_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n        if (vulkan->apiVersion.length == 0 && vulkan->driver.length == 0)\n        {\n            ffStrbufWriteTo(&vulkan->instanceVersion, stdout);\n            puts(\" [Software only]\");\n        }\n        else\n        {\n            if(vulkan->apiVersion.length > 0)\n            {\n                ffStrbufWriteTo(&vulkan->apiVersion, stdout);\n\n                if(vulkan->driver.length > 0)\n                    fputs(\" - \", stdout);\n            }\n\n            if(vulkan->driver.length > 0)\n                ffStrbufWriteTo(&vulkan->driver, stdout);\n\n            putchar('\\n');\n        }\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_VULKAN_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n            FF_ARG(vulkan->driver, \"driver\"),\n            FF_ARG(vulkan->apiVersion, \"api-version\"),\n            FF_ARG(vulkan->conformanceVersion, \"conformance-version\"),\n            FF_ARG(vulkan->instanceVersion, \"instance-version\"),\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseVulkanJsonObject(FFVulkanOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_VULKAN_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateVulkanJsonConfig(FFVulkanOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateVulkanJsonResult(FF_MAYBE_UNUSED FFVulkanOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    const FFVulkanResult* result = ffDetectVulkan();\n\n    if(result->error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", result->error);\n        return false;\n    }\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_strbuf(doc, obj, \"apiVersion\", &result->apiVersion);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"conformanceVersion\", &result->conformanceVersion);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"driver\", &result->driver);\n    yyjson_mut_val* gpus = yyjson_mut_obj_add_arr(doc, obj, \"gpus\");\n    FF_LIST_FOR_EACH(FFGPUResult, vulkanGpu, result->gpus)\n    {\n        yyjson_mut_val* gpuObj = yyjson_mut_arr_add_obj(doc, gpus);\n        yyjson_mut_obj_add_str(doc, gpuObj, \"type\", vulkanGpu->type == FF_GPU_TYPE_UNKNOWN ? \"Unknown\" : vulkanGpu->type == FF_GPU_TYPE_INTEGRATED ? \"Integrated\" : \"Discrete\");\n        yyjson_mut_obj_add_strbuf(doc, gpuObj, \"vendor\", &vulkanGpu->vendor);\n        yyjson_mut_obj_add_strbuf(doc, gpuObj, \"name\", &vulkanGpu->name);\n        yyjson_mut_obj_add_strbuf(doc, gpuObj, \"driver\", &vulkanGpu->driver);\n        yyjson_mut_obj_add_strbuf(doc, gpuObj, \"platformApi\", &vulkanGpu->platformApi);\n        yyjson_mut_obj_add_uint(doc, gpuObj, \"deviceId\", vulkanGpu->deviceId);\n\n        yyjson_mut_val* memoryObj = yyjson_mut_obj_add_obj(doc, gpuObj, \"memory\");\n\n        {\n            yyjson_mut_val* dedicatedMemory = yyjson_mut_obj_add_obj(doc, memoryObj, \"dedicated\");\n            if (vulkanGpu->dedicated.total != FF_GPU_VMEM_SIZE_UNSET)\n                yyjson_mut_obj_add_uint(doc, dedicatedMemory, \"total\", vulkanGpu->dedicated.total);\n            else\n                yyjson_mut_obj_add_null(doc, dedicatedMemory, \"total\");\n\n            if (vulkanGpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET)\n                yyjson_mut_obj_add_uint(doc, dedicatedMemory, \"used\", vulkanGpu->dedicated.total);\n            else\n                yyjson_mut_obj_add_null(doc, dedicatedMemory, \"used\");\n        }\n\n        {\n            yyjson_mut_val* sharedMemory = yyjson_mut_obj_add_obj(doc, memoryObj, \"shared\");\n            if (vulkanGpu->shared.total != FF_GPU_VMEM_SIZE_UNSET)\n                yyjson_mut_obj_add_uint(doc, sharedMemory, \"total\", vulkanGpu->shared.total);\n            else\n                yyjson_mut_obj_add_null(doc, sharedMemory, \"total\");\n\n            if (vulkanGpu->shared.used != FF_GPU_VMEM_SIZE_UNSET)\n                yyjson_mut_obj_add_uint(doc, sharedMemory, \"used\", vulkanGpu->shared.used);\n            else\n                yyjson_mut_obj_add_null(doc, sharedMemory, \"used\");\n        }\n\n        yyjson_mut_obj_add_uint(doc, gpuObj, \"deviceId\", vulkanGpu->deviceId);\n    }\n\n    return true;\n}\n\nvoid ffInitVulkanOptions(FFVulkanOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n}\n\nvoid ffDestroyVulkanOptions(FFVulkanOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffVulkanModuleInfo = {\n    .name = FF_VULKAN_MODULE_NAME,\n    .description = \"Print highest Vulkan version supported by the GPU\",\n    .initOptions = (void*) ffInitVulkanOptions,\n    .destroyOptions = (void*) ffDestroyVulkanOptions,\n    .parseJsonObject = (void*) ffParseVulkanJsonObject,\n    .printModule = (void*) ffPrintVulkan,\n    .generateJsonResult = (void*) ffGenerateVulkanJsonResult,\n    .generateJsonConfig = (void*) ffGenerateVulkanJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Driver name\", \"driver\"},\n        {\"API version\", \"api-version\"},\n        {\"Conformance version\", \"conformance-version\"},\n        {\"Instance version\", \"instance-version\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/vulkan/vulkan.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_VULKAN_MODULE_NAME \"Vulkan\"\n\nbool ffPrintVulkan(FFVulkanOptions* options);\nvoid ffInitVulkanOptions(FFVulkanOptions* options);\nvoid ffDestroyVulkanOptions(FFVulkanOptions* options);\n\nextern FFModuleBaseInfo ffVulkanModuleInfo;\n"
  },
  {
    "path": "src/modules/wallpaper/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFWallpaperOptions\n{\n    FFModuleArgs moduleArgs;\n} FFWallpaperOptions;\n\nstatic_assert(sizeof(FFWallpaperOptions) <= FF_OPTION_MAX_SIZE, \"FFWallpaperOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/wallpaper/wallpaper.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/wallpaper/wallpaper.h\"\n#include \"modules/wallpaper/wallpaper.h\"\n\nbool ffPrintWallpaper(FFWallpaperOptions* options)\n{\n    FF_STRBUF_AUTO_DESTROY fullpath = ffStrbufCreate();\n    const char* error = ffDetectWallpaper(&fullpath);\n\n    const uint32_t index = ffStrbufLastIndexC(&fullpath,\n        #ifndef _WIN32\n        '/'\n        #else\n        '\\\\'\n        #endif\n    ) + 1;\n    const char* filename = index >= fullpath.length\n        ? fullpath.chars\n        : fullpath.chars + index;\n\n    if(error)\n    {\n        ffPrintError(FF_WALLPAPER_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_WALLPAPER_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        puts(filename);\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_WALLPAPER_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(filename, \"file-name\"),\n            FF_ARG(fullpath, \"full-path\"),\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseWallpaperJsonObject(FFWallpaperOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_WALLPAPER_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateWallpaperJsonConfig(FFWallpaperOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateWallpaperJsonResult(FF_MAYBE_UNUSED FFWallpaperOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_STRBUF_AUTO_DESTROY fullpath = ffStrbufCreate();\n    const char* error = ffDetectWallpaper(&fullpath);\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n    yyjson_mut_obj_add_strbuf(doc, module, \"result\", &fullpath);\n\n    return true;\n}\n\nvoid ffInitWallpaperOptions(FFWallpaperOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰸉\");\n}\n\nvoid ffDestroyWallpaperOptions(FFWallpaperOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffWallpaperModuleInfo = {\n    .name = FF_WALLPAPER_MODULE_NAME,\n    .description = \"Print image file path of current wallpaper\",\n    .initOptions = (void*) ffInitWallpaperOptions,\n    .destroyOptions = (void*) ffDestroyWallpaperOptions,\n    .parseJsonObject = (void*) ffParseWallpaperJsonObject,\n    .printModule = (void*) ffPrintWallpaper,\n    .generateJsonResult = (void*) ffGenerateWallpaperJsonResult,\n    .generateJsonConfig = (void*) ffGenerateWallpaperJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"File name\", \"file-name\"},\n        {\"Full path\", \"full-path\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/wallpaper/wallpaper.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_WALLPAPER_MODULE_NAME \"Wallpaper\"\n\nbool ffPrintWallpaper(FFWallpaperOptions* options);\nvoid ffInitWallpaperOptions(FFWallpaperOptions* options);\nvoid ffDestroyWallpaperOptions(FFWallpaperOptions* options);\n\nextern FFModuleBaseInfo ffWallpaperModuleInfo;\n"
  },
  {
    "path": "src/modules/weather/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFWeatherOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFstrbuf location;\n    FFstrbuf outputFormat;\n    uint32_t timeout;\n} FFWeatherOptions;\n\nstatic_assert(sizeof(FFWeatherOptions) <= FF_OPTION_MAX_SIZE, \"FFWeatherOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/weather/weather.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/weather/weather.h\"\n#include \"modules/weather/weather.h\"\n\nbool ffPrintWeather(FFWeatherOptions* options)\n{\n    FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();\n    const char* error = ffDetectWeather(options, &result);\n\n    if(error)\n    {\n        ffPrintError(FF_WEATHER_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_WEATHER_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        ffStrbufPutTo(&result, stdout);\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_WEATHER_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {\n            FF_ARG(result, \"result\"),\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseWeatherJsonObject(FFWeatherOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"location\"))\n        {\n            ffStrbufSetJsonVal(&options->location, val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"outputFormat\"))\n        {\n            ffStrbufSetJsonVal(&options->outputFormat, val);\n            continue;\n        }\n\n        if (unsafe_yyjson_equals_str(key, \"timeout\"))\n        {\n            options->timeout = (uint32_t) yyjson_get_uint(val);\n            continue;\n        }\n\n        ffPrintError(FF_WEATHER_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateWeatherJsonConfig(FFWeatherOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    yyjson_mut_obj_add_strbuf(doc, module, \"location\", &options->location);\n\n    yyjson_mut_obj_add_strbuf(doc, module, \"outputFormat\", &options->outputFormat);\n\n    yyjson_mut_obj_add_uint(doc, module, \"timeout\", options->timeout);\n}\n\nbool ffGenerateWeatherJsonResult(FFWeatherOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();\n    const char* error = ffDetectWeather(options, &result);\n\n    if (error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_obj_add_strbuf(doc, module, \"result\", &result);\n\n    return true;\n}\n\nvoid ffInitWeatherOptions(FFWeatherOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰖙\");\n\n    ffStrbufInit(&options->location);\n    ffStrbufInitStatic(&options->outputFormat, \"%t+-+%C+(%l)\");\n    options->timeout = 0;\n}\n\nvoid ffDestroyWeatherOptions(FFWeatherOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n\n    ffStrbufDestroy(&options->outputFormat);\n}\n\nFFModuleBaseInfo ffWeatherModuleInfo = {\n    .name = FF_WEATHER_MODULE_NAME,\n    .description = \"Print weather information\",\n    .initOptions = (void*) ffInitWeatherOptions,\n    .destroyOptions = (void*) ffDestroyWeatherOptions,\n    .parseJsonObject = (void*) ffParseWeatherJsonObject,\n    .printModule = (void*) ffPrintWeather,\n    .generateJsonResult = (void*) ffGenerateWeatherJsonResult,\n    .generateJsonConfig = (void*) ffGenerateWeatherJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Weather result\", \"result\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/weather/weather.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_WEATHER_MODULE_NAME \"Weather\"\n\nvoid ffPrepareWeather(FFWeatherOptions* options);\n\nbool ffPrintWeather(FFWeatherOptions* options);\nvoid ffInitWeatherOptions(FFWeatherOptions* options);\nvoid ffDestroyWeatherOptions(FFWeatherOptions* options);\n\nextern FFModuleBaseInfo ffWeatherModuleInfo;\n"
  },
  {
    "path": "src/modules/wifi/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFWifiOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFPercentageModuleConfig percent;\n} FFWifiOptions;\n\nstatic_assert(sizeof(FFWifiOptions) <= FF_OPTION_MAX_SIZE, \"FFWifiOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/wifi/wifi.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/wifi/wifi.h\"\n#include \"modules/wifi/wifi.h\"\n\nbool ffPrintWifi(FFWifiOptions* options)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFWifiResult));\n\n    const char* error = ffDetectWifi(&result);\n    if(error)\n    {\n        ffPrintError(FF_WIFI_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n    if(!result.length)\n    {\n        ffPrintError(FF_WIFI_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No Wifi interfaces found\");\n        return false;\n    }\n\n    FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;\n\n    for(uint32_t index = 0; index < result.length; ++index)\n    {\n        FFWifiResult* item = FF_LIST_GET(FFWifiResult, result, index);\n        uint8_t moduleIndex = result.length == 1 ? 0 : (uint8_t)(index + 1);\n\n        // https://en.wikipedia.org/wiki/List_of_WLAN_channels\n        char bandStr[8];\n        if (item->conn.frequency > 58000)\n            strcpy(bandStr, \"60\");\n        if (item->conn.frequency > 40000)\n            strcpy(bandStr, \"45\");\n        else if (item->conn.frequency > 5900)\n            strcpy(bandStr, \"6\");\n        else if (item->conn.frequency > 5100)\n            strcpy(bandStr, \"5\");\n        else if (item->conn.frequency > 4900)\n            strcpy(bandStr, \"4.9\");\n        else if (item->conn.frequency > 3600)\n            strcpy(bandStr, \"3.65\");\n        else if (item->conn.frequency > 2000)\n            strcpy(bandStr, \"2.4\");\n        else if (item->conn.frequency > 800)\n            strcpy(bandStr, \"0.9\");\n        else\n            bandStr[0] = '\\0';\n\n        if(options->moduleArgs.outputFormat.length == 0)\n        {\n            ffPrintLogoAndKey(FF_WIFI_MODULE_NAME, moduleIndex, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n            FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n            if(item->conn.ssid.length)\n            {\n                if(item->conn.signalQuality != -DBL_MAX)\n                {\n                    if(percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n                    {\n                        ffPercentAppendBar(&buffer, item->conn.signalQuality, options->percent, &options->moduleArgs);\n                        ffStrbufAppendC(&buffer, ' ');\n                    }\n                }\n\n                if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT))\n                {\n                    ffStrbufAppend(&buffer, &item->conn.ssid);\n\n                    if(item->conn.protocol.length)\n                    {\n                        ffStrbufAppendS(&buffer, \" - \");\n                        ffStrbufAppend(&buffer, &item->conn.protocol);\n                    }\n                    if (bandStr[0])\n                    {\n                        ffStrbufAppendF(&buffer, \" - %s%sGHz\", bandStr,\n                            instance.config.display.freqSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_NEVER ? \"\" : \" \");\n                    }\n                    if(item->conn.security.length)\n                    {\n                        ffStrbufAppendS(&buffer, \" - \");\n                        ffStrbufAppend(&buffer, &item->conn.security);\n                    }\n                    ffStrbufAppendC(&buffer, ' ');\n                }\n\n                if(item->conn.signalQuality != -DBL_MAX)\n                {\n                    if(percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n                        ffPercentAppendNum(&buffer, item->conn.signalQuality, options->percent, buffer.length > 0, &options->moduleArgs);\n                }\n\n                ffStrbufTrimRight(&buffer, ' ');\n            }\n            else\n            {\n                ffStrbufAppend(&buffer, &item->inf.status);\n            }\n            ffStrbufPutTo(&buffer, stdout);\n        }\n        else\n        {\n            FF_STRBUF_AUTO_DESTROY percentNum = ffStrbufCreate();\n            if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n                ffPercentAppendNum(&percentNum, item->conn.signalQuality, options->percent, false, &options->moduleArgs);\n            FF_STRBUF_AUTO_DESTROY percentBar = ffStrbufCreate();\n            if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n                ffPercentAppendBar(&percentBar, item->conn.signalQuality, options->percent, &options->moduleArgs);\n\n            FF_PRINT_FORMAT_CHECKED(FF_WIFI_MODULE_NAME, moduleIndex, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n                FF_ARG(item->inf.description, \"inf-desc\"),\n                FF_ARG(item->inf.status, \"inf-status\"),\n                FF_ARG(item->conn.status, \"status\"),\n                FF_ARG(item->conn.ssid, \"ssid\"),\n                FF_ARG(item->conn.bssid, \"bssid\"),\n                FF_ARG(item->conn.protocol, \"protocol\"),\n                FF_ARG(percentNum, \"signal-quality\"),\n                FF_ARG(item->conn.rxRate, \"rx-rate\"),\n                FF_ARG(item->conn.txRate, \"tx-rate\"),\n                FF_ARG(item->conn.security, \"security\"),\n                FF_ARG(percentBar, \"signal-quality-bar\"),\n                FF_ARG(item->conn.channel, \"channel\"),\n                FF_ARG(bandStr, \"band\"),\n            }));\n        }\n    }\n\n    FF_LIST_FOR_EACH(FFWifiResult, item, result)\n    {\n        ffStrbufDestroy(&item->inf.description);\n        ffStrbufDestroy(&item->inf.status);\n        ffStrbufDestroy(&item->conn.status);\n        ffStrbufDestroy(&item->conn.ssid);\n        ffStrbufDestroy(&item->conn.bssid);\n        ffStrbufDestroy(&item->conn.protocol);\n        ffStrbufDestroy(&item->conn.security);\n    }\n\n    return true;\n}\n\nvoid ffParseWifiJsonObject(FFWifiOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (ffPercentParseJsonObject(key, val, &options->percent))\n            continue;\n\n        ffPrintError(FF_WIFI_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateWifiJsonConfig(FFWifiOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    ffPercentGenerateJsonConfig(doc, module, options->percent);\n}\n\nbool ffGenerateWifiJsonResult(FF_MAYBE_UNUSED FFWifiOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFWifiResult));\n    const char* error = ffDetectWifi(&result);\n    if(error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n    FF_LIST_FOR_EACH(FFWifiResult, wifi, result)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n\n        yyjson_mut_val* inf = yyjson_mut_obj_add_obj(doc, obj, \"inf\");\n        yyjson_mut_obj_add_strbuf(doc, inf, \"description\", &wifi->inf.description);\n        yyjson_mut_obj_add_strbuf(doc, inf, \"status\", &wifi->inf.status);\n\n        yyjson_mut_val* conn = yyjson_mut_obj_add_obj(doc, obj, \"conn\");\n        yyjson_mut_obj_add_strbuf(doc, conn, \"status\", &wifi->conn.status);\n        yyjson_mut_obj_add_strbuf(doc, conn, \"ssid\", &wifi->conn.ssid);\n        yyjson_mut_obj_add_strbuf(doc, conn, \"bssid\", &wifi->conn.bssid);\n        yyjson_mut_obj_add_strbuf(doc, conn, \"protocol\", &wifi->conn.protocol);\n        yyjson_mut_obj_add_strbuf(doc, conn, \"security\", &wifi->conn.security);\n        if (wifi->conn.signalQuality != -DBL_MAX)\n            yyjson_mut_obj_add_real(doc, conn, \"signalQuality\", wifi->conn.signalQuality);\n        else\n            yyjson_mut_obj_add_null(doc, conn, \"signalQuality\");\n        if (wifi->conn.rxRate != -DBL_MAX)\n            yyjson_mut_obj_add_real(doc, conn, \"rxRate\", wifi->conn.rxRate);\n        else\n            yyjson_mut_obj_add_null(doc, conn, \"rxRate\");\n        if (wifi->conn.txRate != -DBL_MAX)\n            yyjson_mut_obj_add_real(doc, conn, \"txRate\", wifi->conn.txRate);\n        else\n            yyjson_mut_obj_add_null(doc, conn, \"txRate\");\n        yyjson_mut_obj_add_uint(doc, conn, \"channel\", wifi->conn.channel);\n        yyjson_mut_obj_add_uint(doc, conn, \"frequency\", wifi->conn.frequency);\n    }\n\n    FF_LIST_FOR_EACH(FFWifiResult, item, result)\n    {\n        ffStrbufDestroy(&item->inf.description);\n        ffStrbufDestroy(&item->inf.status);\n        ffStrbufDestroy(&item->conn.status);\n        ffStrbufDestroy(&item->conn.ssid);\n        ffStrbufDestroy(&item->conn.bssid);\n        ffStrbufDestroy(&item->conn.protocol);\n        ffStrbufDestroy(&item->conn.security);\n    }\n\n    return true;\n}\n\nvoid ffInitWifiOptions(FFWifiOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n\n    options->percent = (FFPercentageModuleConfig) { 75, 50, 0 };\n}\n\nvoid ffDestroyWifiOptions(FFWifiOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffWifiModuleInfo = {\n    .name = FF_WIFI_MODULE_NAME,\n    .description = \"Print connected Wi-Fi info (SSID, connection and security protocol)\",\n    .initOptions = (void*) ffInitWifiOptions,\n    .destroyOptions = (void*) ffDestroyWifiOptions,\n    .parseJsonObject = (void*) ffParseWifiJsonObject,\n    .printModule = (void*) ffPrintWifi,\n    .generateJsonResult = (void*) ffGenerateWifiJsonResult,\n    .generateJsonConfig = (void*) ffGenerateWifiJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Interface description\", \"inf-desc\"},\n        {\"Interface status\", \"inf-status\"},\n        {\"Connection status\", \"status\"},\n        {\"Connection SSID\", \"ssid\"},\n        {\"Connection BSSID\", \"bssid\"},\n        {\"Connection protocol\", \"protocol\"},\n        {\"Connection signal quality (percentage num)\", \"signal-quality\"},\n        {\"Connection RX rate\", \"rx-rate\"},\n        {\"Connection TX rate\", \"tx-rate\"},\n        {\"Connection Security algorithm\", \"security\"},\n        {\"Connection signal quality (percentage bar)\", \"signal-quality-bar\"},\n        {\"Connection channel number\", \"channel\"},\n        {\"Connection channel band in GHz\", \"band\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/wifi/wifi.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_WIFI_MODULE_NAME \"Wifi\"\n\nbool ffPrintWifi(FFWifiOptions* options);\nvoid ffInitWifiOptions(FFWifiOptions* options);\nvoid ffDestroyWifiOptions(FFWifiOptions* options);\n\nextern FFModuleBaseInfo ffWifiModuleInfo;\n"
  },
  {
    "path": "src/modules/wm/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFWMOptions\n{\n    FFModuleArgs moduleArgs;\n\n    bool detectPlugin;\n} FFWMOptions;\n\nstatic_assert(sizeof(FFWMOptions) <= FF_OPTION_MAX_SIZE, \"FFWMOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/wm/wm.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/displayserver/displayserver.h\"\n#include \"detection/wm/wm.h\"\n#include \"modules/wm/wm.h\"\n\nbool ffPrintWM(FFWMOptions* options)\n{\n    const FFDisplayServerResult* result = ffConnectDisplayServer();\n\n    if(result->wmPrettyName.length == 0)\n    {\n        ffPrintError(FF_WM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"No WM found\");\n        return false;\n    }\n\n    FF_STRBUF_AUTO_DESTROY pluginName = ffStrbufCreate();\n    if(options->detectPlugin)\n        ffDetectWMPlugin(&pluginName);\n\n    FF_STRBUF_AUTO_DESTROY version = ffStrbufCreate();\n    if (instance.config.general.detectVersion)\n        ffDetectWMVersion(&result->wmProcessName, &version, options);\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_WM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n\n        ffStrbufWriteTo(&result->wmPrettyName, stdout);\n\n        if(version.length > 0)\n        {\n            putchar(' ');\n            ffStrbufWriteTo(&version, stdout);\n        }\n\n        if(result->wmProtocolName.length > 0)\n        {\n            fputs(\" (\", stdout);\n            ffStrbufWriteTo(&result->wmProtocolName, stdout);\n            putchar(')');\n        }\n\n        if(pluginName.length > 0)\n        {\n            fputs(\" (with \", stdout);\n            ffStrbufWriteTo(&pluginName, stdout);\n            putchar(')');\n        }\n\n        putchar('\\n');\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_WM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(result->wmProcessName, \"process-name\"),\n            FF_ARG(result->wmPrettyName, \"pretty-name\"),\n            FF_ARG(result->wmProtocolName, \"protocol-name\"),\n            FF_ARG(pluginName, \"plugin-name\"),\n            FF_ARG(version, \"version\"),\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseWMJsonObject(FFWMOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (unsafe_yyjson_equals_str(key, \"detectPlugin\"))\n        {\n            options->detectPlugin = yyjson_get_bool(val);\n            continue;\n        }\n\n        ffPrintError(FF_WM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateWMJsonConfig(FFWMOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    yyjson_mut_obj_add_bool(doc, module, \"detectPlugin\", options->detectPlugin);\n}\n\nbool ffGenerateWMJsonResult(FF_MAYBE_UNUSED FFWMOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    const FFDisplayServerResult* result = ffConnectDisplayServer();\n\n    if(result->wmPrettyName.length == 0)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", \"No WM found\");\n        return false;\n    }\n\n    FF_STRBUF_AUTO_DESTROY pluginName = ffStrbufCreate();\n    if(options->detectPlugin)\n        ffDetectWMPlugin(&pluginName);\n\n    FF_STRBUF_AUTO_DESTROY version = ffStrbufCreate();\n    if (instance.config.general.detectVersion)\n        ffDetectWMVersion(&result->wmProcessName, &version, options);\n\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, \"result\");\n    yyjson_mut_obj_add_strbuf(doc, obj, \"processName\", &result->wmProcessName);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"prettyName\", &result->wmPrettyName);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"protocolName\", &result->wmProtocolName);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"pluginName\", &pluginName);\n    yyjson_mut_obj_add_strbuf(doc, obj, \"version\", &version);\n\n    return true;\n}\n\nvoid ffInitWMOptions(FFWMOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"\");\n    options->detectPlugin = true;\n}\n\nvoid ffDestroyWMOptions(FFWMOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffWMModuleInfo = {\n    .name = FF_WM_MODULE_NAME,\n    .description = \"Print window manager name and version\",\n    .initOptions = (void*) ffInitWMOptions,\n    .destroyOptions = (void*) ffDestroyWMOptions,\n    .parseJsonObject = (void*) ffParseWMJsonObject,\n    .printModule = (void*) ffPrintWM,\n    .generateJsonResult = (void*) ffGenerateWMJsonResult,\n    .generateJsonConfig = (void*) ffGenerateWMJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"WM process name\", \"process-name\"},\n        {\"WM pretty name\", \"pretty-name\"},\n        {\"WM protocol name\", \"protocol-name\"},\n        {\"WM plugin name\", \"plugin-name\"},\n        {\"WM version\", \"version\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/wm/wm.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_WM_MODULE_NAME \"WM\"\n\nbool ffPrintWM(FFWMOptions* options);\nvoid ffInitWMOptions(FFWMOptions* options);\nvoid ffDestroyWMOptions(FFWMOptions* options);\n\nextern FFModuleBaseInfo ffWMModuleInfo;\n"
  },
  {
    "path": "src/modules/wmtheme/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n\ntypedef struct FFWMThemeOptions\n{\n    FFModuleArgs moduleArgs;\n} FFWMThemeOptions;\n\nstatic_assert(sizeof(FFWMThemeOptions) <= FF_OPTION_MAX_SIZE, \"FFWMThemeOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/wmtheme/wmtheme.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/wmtheme/wmtheme.h\"\n#include \"modules/wmtheme/wmtheme.h\"\n\n#define FF_WMTHEME_DISPLAY_NAME \"WM Theme\"\n\nbool ffPrintWMTheme(FFWMThemeOptions* options)\n{\n    FF_STRBUF_AUTO_DESTROY themeOrError = ffStrbufCreate();\n    if(!ffDetectWmTheme(&themeOrError))\n    {\n        ffPrintError(FF_WMTHEME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", themeOrError.chars);\n        return false;\n    }\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(FF_WMTHEME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);\n        puts(themeOrError.chars);\n    }\n    else\n    {\n        FF_PRINT_FORMAT_CHECKED(FF_WMTHEME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){\n            FF_ARG(themeOrError, \"result\"),\n        }));\n    }\n\n    return true;\n}\n\nvoid ffParseWMThemeJsonObject(FFWMThemeOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        ffPrintError(FF_WMTHEME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateWMThemeJsonConfig(FFWMThemeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n}\n\nbool ffGenerateWMThemeJsonResult(FF_MAYBE_UNUSED FFWMThemeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_STRBUF_AUTO_DESTROY themeOrError = ffStrbufCreate();\n    if(!ffDetectWmTheme(&themeOrError))\n    {\n        yyjson_mut_obj_add_strbuf(doc, module, \"error\", &themeOrError);\n        return false;\n    }\n\n    yyjson_mut_obj_add_strbuf(doc, module, \"result\", &themeOrError);\n    return true;\n}\n\nvoid ffInitWMThemeOptions(FFWMThemeOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󰓸\");\n}\n\nvoid ffDestroyWMThemeOptions(FFWMThemeOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffWMThemeModuleInfo = {\n    .name = FF_WMTHEME_MODULE_NAME,\n    .description = \"Print current theme of window manager\",\n    .initOptions = (void*) ffInitWMThemeOptions,\n    .destroyOptions = (void*) ffDestroyWMThemeOptions,\n    .parseJsonObject = (void*) ffParseWMThemeJsonObject,\n    .printModule = (void*) ffPrintWMTheme,\n    .generateJsonResult = (void*) ffGenerateWMThemeJsonResult,\n    .generateJsonConfig = (void*) ffGenerateWMThemeJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"WM theme\", \"result\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/wmtheme/wmtheme.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_WMTHEME_MODULE_NAME \"WMTheme\"\n\nbool ffPrintWMTheme(FFWMThemeOptions* options);\nvoid ffInitWMThemeOptions(FFWMThemeOptions* options);\nvoid ffDestroyWMThemeOptions(FFWMThemeOptions* options);\n\nextern FFModuleBaseInfo ffWMThemeModuleInfo;\n"
  },
  {
    "path": "src/modules/zpool/option.h",
    "content": "#pragma once\n\n#include \"common/option.h\"\n#include \"common/percent.h\"\n\ntypedef struct FFZpoolOptions\n{\n    FFModuleArgs moduleArgs;\n\n    FFPercentageModuleConfig percent;\n} FFZpoolOptions;\n\nstatic_assert(sizeof(FFZpoolOptions) <= FF_OPTION_MAX_SIZE, \"FFZpoolOptions size exceeds maximum allowed size\");\n"
  },
  {
    "path": "src/modules/zpool/zpool.c",
    "content": "#include \"common/printing.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/percent.h\"\n#include \"common/size.h\"\n#include \"common/FFstrbuf.h\"\n#include \"common/stringUtils.h\"\n#include \"detection/zpool/zpool.h\"\n#include \"modules/zpool/zpool.h\"\n\nstatic void printZpool(FFZpoolOptions* options, FFZpoolResult* result, uint8_t index)\n{\n    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();\n    if (options->moduleArgs.key.length == 0)\n    {\n        if (result->name.length > 0)\n            ffStrbufSetF(&buffer, \"%s (%s)\", FF_ZPOOL_MODULE_NAME, result->name.chars);\n        else\n            ffStrbufSetS(&buffer, FF_ZPOOL_MODULE_NAME);\n    }\n    else\n    {\n        ffStrbufClear(&buffer);\n        FF_PARSE_FORMAT_STRING_CHECKED(&buffer, &options->moduleArgs.key, ((FFformatarg[]) {\n            FF_ARG(index, \"index\"),\n            FF_ARG(result->name, \"name\"),\n            FF_ARG(result->guid, \"guid\"),\n            FF_ARG(options->moduleArgs.keyIcon, \"icon\"),\n        }));\n    }\n\n    FF_STRBUF_AUTO_DESTROY usedPretty = ffStrbufCreate();\n    ffSizeAppendNum(result->used, &usedPretty);\n\n    FF_STRBUF_AUTO_DESTROY allocatedPretty = ffStrbufCreate();\n    ffSizeAppendNum(result->allocated, &allocatedPretty);\n\n    FF_STRBUF_AUTO_DESTROY totalPretty = ffStrbufCreate();\n    ffSizeAppendNum(result->total, &totalPretty);\n\n    double usedPercentage = result->total > 0 ? (double) result->used / (double) result->total * 100.0 : 0;\n    double allocatedPercentage = result->total > 0 ? (double) result->allocated / (double) result->total * 100.0 : 0;\n    FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;\n\n    if(options->moduleArgs.outputFormat.length == 0)\n    {\n        ffPrintLogoAndKey(buffer.chars, index, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);\n\n        ffStrbufClear(&buffer);\n        ffStrbufSetF(&buffer, \"%s / %s (\", usedPretty.chars, totalPretty.chars);\n        ffPercentAppendNum(&buffer, usedPercentage, options->percent, false, &options->moduleArgs);\n        ffStrbufAppendS(&buffer, \", \");\n        ffPercentAppendNum(&buffer, allocatedPercentage, options->percent, false, &options->moduleArgs);\n        ffStrbufAppendS(&buffer, \" allocated, \");\n        ffPercentAppendNum(&buffer, result->fragmentation, options->percent, false, &options->moduleArgs);\n        ffStrbufAppendF(&buffer, \" frag) - %s\", result->state.chars);\n        if (result->readOnly)\n            ffStrbufAppendS(&buffer, \" [Read-only]\");\n        ffStrbufPutTo(&buffer, stdout);\n    }\n    else\n    {\n        FF_STRBUF_AUTO_DESTROY usedPercentageNum = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n            ffPercentAppendNum(&usedPercentageNum, usedPercentage, options->percent, false, &options->moduleArgs);\n        FF_STRBUF_AUTO_DESTROY usedPercentageBar = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            ffPercentAppendBar(&usedPercentageBar, usedPercentage, options->percent, &options->moduleArgs);\n\n        FF_STRBUF_AUTO_DESTROY allocatedPercentageNum = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n            ffPercentAppendNum(&allocatedPercentageNum, allocatedPercentage, options->percent, false, &options->moduleArgs);\n        FF_STRBUF_AUTO_DESTROY allocatedPercentageBar = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            ffPercentAppendBar(&allocatedPercentageBar, allocatedPercentage, options->percent, &options->moduleArgs);\n\n        FF_STRBUF_AUTO_DESTROY fragPercentageNum = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n            ffPercentAppendNum(&fragPercentageNum, result->fragmentation, options->percent, false, &options->moduleArgs);\n        FF_STRBUF_AUTO_DESTROY fragPercentageBar = ffStrbufCreate();\n        if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n            ffPercentAppendBar(&fragPercentageBar, result->fragmentation, options->percent, &options->moduleArgs);\n\n        FF_PRINT_FORMAT_CHECKED(buffer.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) {\n            FF_ARG(result->name, \"name\"),\n            FF_ARG(result->guid, \"guid\"),\n            FF_ARG(result->state, \"state\"),\n            FF_ARG(usedPretty, \"size-used\"),\n            FF_ARG(allocatedPretty, \"size-allocated\"),\n            FF_ARG(totalPretty, \"size-total\"),\n            FF_ARG(usedPercentageNum, \"used-percentage\"),\n            FF_ARG(allocatedPercentageNum, \"allocated-percentage\"),\n            FF_ARG(fragPercentageNum, \"frag-percentage\"),\n            FF_ARG(usedPercentageBar, \"used-percentage-bar\"),\n            FF_ARG(allocatedPercentageBar, \"allocated-percentage-bar\"),\n            FF_ARG(fragPercentageBar, \"frag-percentage-bar\"),\n            FF_ARG(result->readOnly, \"is-readonly\"),\n        }));\n    }\n}\n\nbool ffPrintZpool(FFZpoolOptions* options)\n{\n    FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFZpoolResult));\n\n    const char* error = ffDetectZpool(&results);\n\n    if (error)\n    {\n        ffPrintError(FF_ZPOOL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", error);\n        return false;\n    }\n    if(results.length == 0)\n    {\n        ffPrintError(FF_ZPOOL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"%s\", \"No zpool found\");\n        return false;\n    }\n\n    for(uint32_t i = 0; i < results.length; i++)\n    {\n        FFZpoolResult* result = FF_LIST_GET(FFZpoolResult, results, i);\n        uint8_t index = results.length == 1 ? 0 : (uint8_t) (i + 1);\n        printZpool(options, result, index);\n    }\n\n    FF_LIST_FOR_EACH(FFZpoolResult, result, results)\n    {\n        ffStrbufDestroy(&result->name);\n        ffStrbufDestroy(&result->state);\n    }\n    return true;\n}\n\nvoid ffParseZpoolJsonObject(FFZpoolOptions* options, yyjson_val* module)\n{\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(module, idx, max, key, val)\n    {\n        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))\n            continue;\n\n        if (ffPercentParseJsonObject(key, val, &options->percent))\n            continue;\n\n        ffPrintError(FF_ZPOOL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, \"Unknown JSON key %s\", unsafe_yyjson_get_str(key));\n    }\n}\n\nvoid ffGenerateZpoolJsonConfig(FFZpoolOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);\n\n    ffPercentGenerateJsonConfig(doc, module, options->percent);\n}\n\nbool ffGenerateZpoolJsonResult(FF_MAYBE_UNUSED FFZpoolOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)\n{\n    FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFZpoolResult));\n\n    const char* error = ffDetectZpool(&results);\n    if (error)\n    {\n        yyjson_mut_obj_add_str(doc, module, \"error\", error);\n        return false;\n    }\n\n    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, \"result\");\n\n    FF_LIST_FOR_EACH(FFZpoolResult, zpool, results)\n    {\n        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"name\", &zpool->name);\n        yyjson_mut_obj_add_strbuf(doc, obj, \"state\", &zpool->state);\n        yyjson_mut_obj_add_uint(doc, obj, \"guid\", zpool->guid);\n        yyjson_mut_obj_add_uint(doc, obj, \"used\", zpool->used);\n        yyjson_mut_obj_add_uint(doc, obj, \"allocated\", zpool->allocated);\n        yyjson_mut_obj_add_uint(doc, obj, \"total\", zpool->total);\n        if (zpool->fragmentation != -DBL_MAX)\n            yyjson_mut_obj_add_real(doc, obj, \"fragmentation\", zpool->fragmentation);\n        else\n            yyjson_mut_obj_add_null(doc, obj, \"fragmentation\");\n        yyjson_mut_obj_add_bool(doc, obj, \"readOnly\", zpool->readOnly);\n    }\n\n    FF_LIST_FOR_EACH(FFZpoolResult, zpool, results)\n    {\n        ffStrbufDestroy(&zpool->name);\n        ffStrbufDestroy(&zpool->state);\n    }\n    return true;\n}\n\nvoid ffInitZpoolOptions(FFZpoolOptions* options)\n{\n    ffOptionInitModuleArg(&options->moduleArgs, \"󱑛\");\n    options->percent = (FFPercentageModuleConfig) { 50, 80, 0 };\n}\n\nvoid ffDestroyZpoolOptions(FFZpoolOptions* options)\n{\n    ffOptionDestroyModuleArg(&options->moduleArgs);\n}\n\nFFModuleBaseInfo ffZpoolModuleInfo = {\n    .name = FF_ZPOOL_MODULE_NAME,\n    .description = \"Print ZFS storage pools\",\n    .initOptions = (void*) ffInitZpoolOptions,\n    .destroyOptions = (void*) ffDestroyZpoolOptions,\n    .parseJsonObject = (void*) ffParseZpoolJsonObject,\n    .printModule = (void*) ffPrintZpool,\n    .generateJsonResult = (void*) ffGenerateZpoolJsonResult,\n    .generateJsonConfig = (void*) ffGenerateZpoolJsonConfig,\n    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {\n        {\"Zpool name\", \"name\"},\n        {\"Zpool guid\", \"guid\"},\n        {\"Zpool state\", \"state\"},\n        {\"Size used\", \"used\"},\n        {\"Size allocated\", \"allocated\"},\n        {\"Size total\", \"total\"},\n        {\"Size used percentage num\", \"used-percentage\"},\n        {\"Size allocated percentage num\", \"allocated-percentage\"},\n        {\"Fragmentation percentage num\", \"fragmentation-percentage\"},\n        {\"Size used percentage bar\", \"used-percentage-bar\"},\n        {\"Size allocated percentage bar\", \"allocated-percentage-bar\"},\n        {\"Fragmentation percentage bar\", \"fragmentation-percentage-bar\"},\n        {\"Is read-only\", \"is-readonly\"},\n    }))\n};\n"
  },
  {
    "path": "src/modules/zpool/zpool.h",
    "content": "#pragma once\n\n#include \"option.h\"\n\n#define FF_ZPOOL_MODULE_NAME \"Zpool\"\n\nbool ffPrintZpool(FFZpoolOptions* options);\nvoid ffInitZpoolOptions(FFZpoolOptions* options);\nvoid ffDestroyZpoolOptions(FFZpoolOptions* options);\n\nextern FFModuleBaseInfo ffZpoolModuleInfo;\n"
  },
  {
    "path": "src/options/display.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/color.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/percent.h\"\n#include \"common/stringUtils.h\"\n#include \"options/display.h\"\n\n#include <unistd.h>\n\nconst char* ffOptionsParseDisplayJsonConfig(FFOptionsDisplay* options, yyjson_val* root)\n{\n    yyjson_val* object = yyjson_obj_get(root, \"display\");\n    if (!object) return NULL;\n    if (!yyjson_is_obj(object)) return \"Property 'display' must be an object\";\n\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(object, idx, max, key, val)\n    {\n        if (unsafe_yyjson_equals_str(key, \"stat\"))\n        {\n            if (yyjson_is_bool(val))\n            {\n                if (yyjson_get_bool(val))\n                {\n                    options->stat = 0;\n                    options->showErrors = true;\n                }\n                else\n                    options->stat = -1;\n            }\n            else if (yyjson_is_uint(val))\n            {\n                options->stat = (int) yyjson_get_uint(val);\n                options->showErrors = true;\n            }\n            else\n                return \"display.stat must be a boolean or a positive integer\";\n        }\n        else if (unsafe_yyjson_equals_str(key, \"pipe\"))\n            options->pipe = yyjson_get_bool(val);\n        else if (unsafe_yyjson_equals_str(key, \"showErrors\"))\n            options->showErrors = yyjson_get_bool(val);\n        else if (unsafe_yyjson_equals_str(key, \"disableLinewrap\"))\n            options->disableLinewrap = yyjson_get_bool(val);\n        else if (unsafe_yyjson_equals_str(key, \"hideCursor\"))\n            options->hideCursor = yyjson_get_bool(val);\n        else if (unsafe_yyjson_equals_str(key, \"separator\"))\n            ffStrbufSetJsonVal(&options->keyValueSeparator, val);\n        else if (unsafe_yyjson_equals_str(key, \"color\"))\n        {\n            if (yyjson_is_str(val))\n            {\n                ffOptionParseColor(unsafe_yyjson_get_str(val), &options->colorKeys);\n                ffStrbufSet(&options->colorTitle, &options->colorKeys);\n            }\n            else if (yyjson_is_obj(val))\n            {\n                yyjson_val* colorKeys = yyjson_obj_get(val, \"keys\");\n                if (colorKeys)\n                    ffOptionParseColor(yyjson_get_str(colorKeys), &options->colorKeys);\n                yyjson_val* colorTitle = yyjson_obj_get(val, \"title\");\n                if (colorTitle)\n                    ffOptionParseColor(yyjson_get_str(colorTitle), &options->colorTitle);\n                yyjson_val* colorOutput = yyjson_obj_get(val, \"output\");\n                if (colorOutput)\n                    ffOptionParseColor(yyjson_get_str(colorOutput), &options->colorOutput);\n                yyjson_val* colorSeparator = yyjson_obj_get(val, \"separator\");\n                if (colorSeparator)\n                    ffOptionParseColor(yyjson_get_str(colorSeparator), &options->colorSeparator);\n            }\n            else\n                return \"display.color must be either a string or an object\";\n        }\n        else if (unsafe_yyjson_equals_str(key, \"brightColor\"))\n            options->brightColor = yyjson_get_bool(val);\n        else if (unsafe_yyjson_equals_str(key, \"duration\"))\n        {\n            if (!yyjson_is_obj(val))\n                return \"display.duration must be an object\";\n\n            yyjson_val* abbreviation = yyjson_obj_get(val, \"abbreviation\");\n            if (abbreviation) options->durationAbbreviation = yyjson_get_bool(abbreviation);\n\n            yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, \"spaceBeforeUnit\");\n            if (spaceBeforeUnit)\n            {\n                int value;\n                const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) {\n                    { \"default\", FF_SPACE_BEFORE_UNIT_DEFAULT },\n                    { \"always\", FF_SPACE_BEFORE_UNIT_ALWAYS },\n                    { \"never\", FF_SPACE_BEFORE_UNIT_NEVER },\n                    {},\n                });\n                if (error) return error;\n                options->durationSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;\n            }\n        }\n        else if (unsafe_yyjson_equals_str(key, \"size\"))\n        {\n            if (!yyjson_is_obj(val))\n                return \"display.size must be an object\";\n\n            yyjson_val* maxPrefix = yyjson_obj_get(val, \"maxPrefix\");\n            if (maxPrefix)\n            {\n                int value;\n                const char* error = ffJsonConfigParseEnum(maxPrefix, &value, (FFKeyValuePair[]) {\n                    { \"B\", 0 },\n                    { \"kB\", 1 },\n                    { \"MB\", 2 },\n                    { \"GB\", 3 },\n                    { \"TB\", 4 },\n                    { \"PB\", 5 },\n                    { \"EB\", 6 },\n                    { \"ZB\", 7 },\n                    { \"YB\", 8 },\n                    {}\n                });\n                if (error) return error;\n                options->sizeMaxPrefix = (uint8_t) value;\n            }\n\n            yyjson_val* binaryPrefix = yyjson_obj_get(val, \"binaryPrefix\");\n            if (binaryPrefix)\n            {\n                int value;\n                const char* error = ffJsonConfigParseEnum(binaryPrefix, &value, (FFKeyValuePair[]) {\n                    { \"iec\", FF_SIZE_BINARY_PREFIX_TYPE_IEC },\n                    { \"si\", FF_SIZE_BINARY_PREFIX_TYPE_SI },\n                    { \"jedec\", FF_SIZE_BINARY_PREFIX_TYPE_JEDEC },\n                    {},\n                });\n                if (error) return error;\n                options->sizeBinaryPrefix = (FFSizeBinaryPrefixType) value;\n            }\n\n            yyjson_val* ndigits = yyjson_obj_get(val, \"ndigits\");\n            if (ndigits)\n            {\n                if (!yyjson_is_uint(ndigits))\n                    return \"display.size.ndigits must be an unsigned integer\";\n                uint64_t val = yyjson_get_uint(ndigits);\n                if (val > 9)\n                    return \"display.size.ndigits must be between 0 and 9\";\n                options->sizeNdigits = (uint8_t) val;\n            }\n\n            yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, \"spaceBeforeUnit\");\n            if (spaceBeforeUnit)\n            {\n                int value;\n                const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) {\n                    { \"default\", FF_SPACE_BEFORE_UNIT_DEFAULT },\n                    { \"always\", FF_SPACE_BEFORE_UNIT_ALWAYS },\n                    { \"never\", FF_SPACE_BEFORE_UNIT_NEVER },\n                    {},\n                });\n                if (error) return error;\n                options->sizeSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;\n            }\n        }\n        else if (unsafe_yyjson_equals_str(key, \"temp\"))\n        {\n            if (!yyjson_is_obj(val))\n                return \"display.temp must be an object\";\n\n            yyjson_val* unit = yyjson_obj_get(val, \"unit\");\n            if (unit)\n            {\n                int value;\n                const char* error = ffJsonConfigParseEnum(unit, &value, (FFKeyValuePair[]) {\n                    { \"DEFAULT\", FF_TEMPERATURE_UNIT_DEFAULT },\n                    { \"D\", FF_TEMPERATURE_UNIT_DEFAULT },\n                    { \"CELSIUS\", FF_TEMPERATURE_UNIT_CELSIUS },\n                    { \"C\", FF_TEMPERATURE_UNIT_CELSIUS },\n                    { \"FAHRENHEIT\", FF_TEMPERATURE_UNIT_FAHRENHEIT },\n                    { \"F\", FF_TEMPERATURE_UNIT_FAHRENHEIT },\n                    { \"KELVIN\", FF_TEMPERATURE_UNIT_KELVIN },\n                    { \"K\", FF_TEMPERATURE_UNIT_KELVIN },\n                    {},\n                });\n                if (error) return error;\n                options->tempUnit = (FFTemperatureUnit) value;\n            }\n\n            yyjson_val* ndigits = yyjson_obj_get(val, \"ndigits\");\n            if (ndigits)\n            {\n                if (!yyjson_is_uint(ndigits))\n                    return \"display.temperature.ndigits must be an unsigned integer\";\n                uint64_t val = yyjson_get_uint(ndigits);\n                if (val > 9)\n                    return \"display.temperature.ndigits must be between 0 and 9\";\n                options->tempNdigits = (uint8_t) val;\n            }\n\n            yyjson_val* color = yyjson_obj_get(val, \"color\");\n            if (color)\n            {\n                if (!yyjson_is_obj(color))\n                    return \"display.temperature.color must be an object\";\n\n                yyjson_val* green = yyjson_obj_get(color, \"green\");\n                if (green) ffOptionParseColor(yyjson_get_str(green), &options->tempColorGreen);\n\n                yyjson_val* yellow = yyjson_obj_get(color, \"yellow\");\n                if (yellow) ffOptionParseColor(yyjson_get_str(yellow), &options->tempColorYellow);\n\n                yyjson_val* red = yyjson_obj_get(color, \"red\");\n                if (red) ffOptionParseColor(yyjson_get_str(red), &options->tempColorRed);\n            }\n\n            yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, \"spaceBeforeUnit\");\n            if (spaceBeforeUnit)\n            {\n                int value;\n                const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) {\n                    { \"default\", FF_SPACE_BEFORE_UNIT_DEFAULT },\n                    { \"always\", FF_SPACE_BEFORE_UNIT_ALWAYS },\n                    { \"never\", FF_SPACE_BEFORE_UNIT_NEVER },\n                    {},\n                });\n                if (error) return error;\n                options->tempSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;\n            }\n        }\n        else if (unsafe_yyjson_equals_str(key, \"percent\"))\n        {\n            if (!yyjson_is_obj(val))\n                return \"display.percent must be an object\";\n\n            yyjson_val* type = yyjson_obj_get(val, \"type\");\n            if (type)\n            {\n                const char* error = ffPercentParseTypeJsonConfig(type, &options->percentType);\n                if (error) return error;\n            }\n\n            yyjson_val* ndigits = yyjson_obj_get(val, \"ndigits\");\n            if (ndigits)\n            {\n                if (!yyjson_is_uint(ndigits))\n                    return \"display.percent.ndigits must be an unsigned integer\";\n                uint64_t val = yyjson_get_uint(ndigits);\n                if (val > 9)\n                    return \"display.percent.ndigits must be between 0 and 9\";\n                options->percentNdigits = (uint8_t) val;\n            }\n\n            yyjson_val* color = yyjson_obj_get(val, \"color\");\n            if (color)\n            {\n                if (!yyjson_is_obj(color))\n                    return \"display.percent.color must be an object\";\n\n                yyjson_val* green = yyjson_obj_get(color, \"green\");\n                if (green) ffOptionParseColor(yyjson_get_str(green), &options->percentColorGreen);\n\n                yyjson_val* yellow = yyjson_obj_get(color, \"yellow\");\n                if (yellow) ffOptionParseColor(yyjson_get_str(yellow), &options->percentColorYellow);\n\n                yyjson_val* red = yyjson_obj_get(color, \"red\");\n                if (red) ffOptionParseColor(yyjson_get_str(red), &options->percentColorRed);\n            }\n\n            yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, \"spaceBeforeUnit\");\n            if (spaceBeforeUnit)\n            {\n                int value;\n                const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) {\n                    { \"default\", FF_SPACE_BEFORE_UNIT_DEFAULT },\n                    { \"always\", FF_SPACE_BEFORE_UNIT_ALWAYS },\n                    { \"never\", FF_SPACE_BEFORE_UNIT_NEVER },\n                    {},\n                });\n                if (error) return error;\n                options->percentSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;\n            }\n\n            yyjson_val* width = yyjson_obj_get(val, \"width\");\n            if (width) options->percentWidth = (uint8_t) yyjson_get_uint(width);\n        }\n        else if (unsafe_yyjson_equals_str(key, \"bar\"))\n        {\n            if (yyjson_is_obj(val))\n            {\n                yyjson_val* char_ = yyjson_obj_get(val, \"char\");\n                if (char_)\n                {\n                    if (!yyjson_is_obj(char_)) return \"display.bar.char must be an object\";\n\n                    yyjson_val* charElapsed = yyjson_obj_get(char_, \"elapsed\");\n                    if (charElapsed)\n                        ffStrbufSetJsonVal(&options->barCharElapsed, charElapsed);\n\n                    yyjson_val* charTotal = yyjson_obj_get(char_, \"total\");\n                    if (charTotal)\n                        ffStrbufSetJsonVal(&options->barCharTotal, charTotal);\n                }\n                else\n                {\n                    yyjson_val* charElapsed = yyjson_obj_get(val, \"charElapsed\");\n                    if (charElapsed)\n                        return \"display.bar.charElapsed has been renamed to display.bar.char.elapsed.\";\n\n                    yyjson_val* charTotal = yyjson_obj_get(val, \"charTotal\");\n                    if (charTotal)\n                        return \"display.bar.charTotal has been renamed to display.bar.char.total.\";\n                }\n\n                yyjson_val* border = yyjson_obj_get(val, \"border\");\n                if (border)\n                {\n                    if (yyjson_is_null(border))\n                    {\n                        ffStrbufClear(&options->barBorderLeft);\n                        ffStrbufClear(&options->barBorderRight);\n                        ffStrbufClear(&options->barBorderLeftElapsed);\n                        ffStrbufClear(&options->barBorderRightElapsed);\n                    }\n                    else\n                    {\n                        if (!yyjson_is_obj(border)) return \"display.bar.border must be an object\";\n\n                        yyjson_val* borderLeft = yyjson_obj_get(border, \"left\");\n                        if (borderLeft)\n                            ffStrbufSetJsonVal(&options->barBorderLeft, borderLeft);\n\n                        yyjson_val* borderRight = yyjson_obj_get(border, \"right\");\n                        if (borderRight)\n                            ffStrbufSetJsonVal(&options->barBorderRight, borderRight);\n\n                        yyjson_val* borderLeftElapsed = yyjson_obj_get(border, \"leftElapsed\");\n                        if (borderLeftElapsed)\n                            ffStrbufSetJsonVal(&options->barBorderLeftElapsed, borderLeftElapsed);\n\n                        yyjson_val* borderRightElapsed = yyjson_obj_get(border, \"rightElapsed\");\n                        if (borderRightElapsed)\n                            ffStrbufSetJsonVal(&options->barBorderRightElapsed, borderRightElapsed);\n                    }\n                }\n                else\n                {\n                    yyjson_val* borderLeft = yyjson_obj_get(val, \"borderLeft\");\n                    if (borderLeft)\n                        return \"display.bar.borderLeft has been renamed to display.bar.border.left.\";\n\n                    yyjson_val* borderRight = yyjson_obj_get(val, \"borderRight\");\n                    if (borderRight)\n                        return \"display.bar.borderRight has been renamed to display.bar.border.right.\";\n                }\n\n                yyjson_val* color = yyjson_obj_get(val, \"color\");\n                if (color)\n                {\n                    if (yyjson_is_null(color))\n                    {\n                        ffStrbufClear(&options->barColorElapsed);\n                        ffStrbufClear(&options->barColorTotal);\n                        ffStrbufClear(&options->barColorBorder);\n                    }\n                    else\n                    {\n                        if (!yyjson_is_obj(color)) return \"display.bar.color must be an object\";\n\n                        yyjson_val* colorElapsed = yyjson_obj_get(color, \"elapsed\");\n                        if (colorElapsed)\n                        {\n                            const char* value = yyjson_get_str(colorElapsed);\n                            if (!value)\n                                ffStrbufClear(&options->barColorElapsed);\n                            else if (ffStrEqualsIgnCase(value, \"auto\"))\n                                ffStrbufSetStatic(&options->barColorElapsed, \"auto\");\n                            else\n                                ffOptionParseColor(value, &options->barColorElapsed);\n                        }\n\n                        yyjson_val* colorTotal = yyjson_obj_get(color, \"total\");\n                        if (colorTotal) ffOptionParseColor(yyjson_get_str(colorTotal), &options->barColorTotal);\n\n                        yyjson_val* colorBorder = yyjson_obj_get(color, \"border\");\n                        if (colorBorder) ffOptionParseColor(yyjson_get_str(colorBorder), &options->barColorBorder);\n                    }\n                }\n\n                yyjson_val* width = yyjson_obj_get(val, \"width\");\n                if (width)\n                    options->barWidth = (uint8_t) yyjson_get_uint(width);\n            }\n            else\n                return \"display.bar must be an object\";\n        }\n        else if (unsafe_yyjson_equals_str(key, \"fraction\"))\n        {\n            if (yyjson_is_obj(val))\n            {\n                yyjson_val* ndigits = yyjson_obj_get(val, \"ndigits\");\n                if (ndigits)\n                {\n                    if (yyjson_is_null(ndigits))\n                        options->fractionNdigits = -1;\n                    else\n                    {\n                        if (!yyjson_is_int(ndigits))\n                            return \"display.fraction.ndigits must be an integer\";\n                        int64_t val = yyjson_get_int(ndigits);\n                        if (val < -1 || val > 9)\n                            return \"display.fraction.ndigits must be between -1 and 9\";\n                        options->fractionNdigits = (int8_t) val;\n                    }\n                }\n                yyjson_val* trailingZeros = yyjson_obj_get(val, \"trailingZeros\");\n                if (trailingZeros)\n                {\n                    if (yyjson_is_null(trailingZeros))\n                        options->fractionTrailingZeros = FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT;\n                    else\n                    {\n                        int value;\n                        const char* error = ffJsonConfigParseEnum(trailingZeros, &value, (FFKeyValuePair[]) {\n                            { \"default\", FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT },\n                            { \"always\", FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS },\n                            { \"never\", FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER },\n                            {},\n                        });\n                        if (error) return error;\n                        options->fractionTrailingZeros = (FFFractionTrailingZerosType) value;\n                    }\n                }\n            }\n            else\n                return \"display.fraction must be an object\";\n        }\n        else if (unsafe_yyjson_equals_str(key, \"noBuffer\"))\n            options->noBuffer = yyjson_get_bool(val);\n        else if (unsafe_yyjson_equals_str(key, \"key\"))\n        {\n            if (yyjson_is_obj(val))\n            {\n                yyjson_val* width = yyjson_obj_get(val, \"width\");\n                if (width)\n                    options->keyWidth = (uint16_t) yyjson_get_uint(width);\n\n                yyjson_val* type = yyjson_obj_get(val, \"type\");\n                if (type)\n                {\n                    int value;\n                    const char* error = ffJsonConfigParseEnum(type, &value, (FFKeyValuePair[]) {\n                        { \"none\", FF_MODULE_KEY_TYPE_NONE },\n                        { \"string\", FF_MODULE_KEY_TYPE_STRING },\n                        { \"icon\", FF_MODULE_KEY_TYPE_ICON },\n                        { \"both\", FF_MODULE_KEY_TYPE_BOTH },\n                        { \"both-0\", FF_MODULE_KEY_TYPE_BOTH_0 },\n                        { \"both-1\", FF_MODULE_KEY_TYPE_BOTH_1 },\n                        { \"both-2\", FF_MODULE_KEY_TYPE_BOTH_2 },\n                        { \"both-3\", FF_MODULE_KEY_TYPE_BOTH_3 },\n                        { \"both-4\", FF_MODULE_KEY_TYPE_BOTH_4 },\n                        {}\n                    });\n                    if (error) return error;\n                    options->keyType = (uint8_t) value;\n                }\n\n                yyjson_val* paddingLeft = yyjson_obj_get(val, \"paddingLeft\");\n                if (paddingLeft)\n                    options->keyPaddingLeft = (uint16_t) yyjson_get_uint(paddingLeft);\n            }\n            else\n                return \"display.key must be an object\";\n        }\n        else if (unsafe_yyjson_equals_str(key, \"constants\"))\n        {\n            if (!yyjson_is_arr(val))\n                return \"display.constants must be an array\";\n            yyjson_val* item;\n            size_t idx, max;\n            yyjson_arr_foreach(val, idx, max, item)\n                ffStrbufInitJsonVal(ffListAdd(&options->constants), item);\n        }\n        else if (unsafe_yyjson_equals_str(key, \"freq\"))\n        {\n            if (!yyjson_is_obj(val))\n                return \"display.freq must be an object\";\n\n            yyjson_val* ndigits = yyjson_obj_get(val, \"ndigits\");\n            if (ndigits)\n            {\n                if (yyjson_is_null(ndigits))\n                    options->freqNdigits = -1;\n                else\n                {\n                    if (!yyjson_is_int(ndigits))\n                        return \"display.freq.ndigits must be an integer\";\n                    int64_t val = yyjson_get_int(ndigits);\n                    if (val < -1 || val > 9)\n                        return \"display.freq.ndigits must be between -1 and 9\";\n                    options->freqNdigits = (int8_t) val;\n                }\n            }\n\n            yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, \"spaceBeforeUnit\");\n            if (spaceBeforeUnit)\n            {\n                int value;\n                const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) {\n                    { \"default\", FF_SPACE_BEFORE_UNIT_DEFAULT },\n                    { \"always\", FF_SPACE_BEFORE_UNIT_ALWAYS },\n                    { \"never\", FF_SPACE_BEFORE_UNIT_NEVER },\n                    {},\n                });\n                if (error) return error;\n                options->freqSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;\n            }\n        }\n        else\n            return \"Unknown display property\";\n    }\n\n    return NULL;\n}\n\nstatic inline void optionCheckString(const char* key, const char* value, FFstrbuf* buffer)\n{\n    if(value == NULL)\n    {\n        fprintf(stderr, \"Error: usage: %s <str>\\n\", key);\n        exit(477);\n    }\n    ffStrbufEnsureFree(buffer, 63); //This is not needed, as ffStrbufSetS will resize capacity if needed, but giving a higher start should improve performance\n}\n\nbool ffOptionsParseDisplayCommandLine(FFOptionsDisplay* options, const char* key, const char* value)\n{\n    if(ffStrEqualsIgnCase(key, \"--stat\"))\n    {\n        if(ffOptionParseBoolean(value))\n        {\n            options->stat = 0;\n            options->showErrors = true;\n        }\n        else if (value)\n        {\n            char* end;\n            uint32_t num = (uint32_t) strtoul(value, &end, 10);\n            if (*end == '\\0')\n            {\n                options->stat = (int32_t) num;\n                options->showErrors = true;\n            }\n            else\n                options->stat = -1;\n        }\n        else\n            options->stat = -1;\n    }\n    else if(ffStrEqualsIgnCase(key, \"--pipe\"))\n        options->pipe = ffOptionParseBoolean(value);\n    else if(ffStrEqualsIgnCase(key, \"--show-errors\"))\n        options->showErrors = ffOptionParseBoolean(value);\n    else if(ffStrEqualsIgnCase(key, \"--debug\"))\n    #ifndef NDEBUG\n        options->debugMode = ffOptionParseBoolean(value);\n    #else\n    {\n        fprintf(stderr, \"--debug is only available in debug builds\\n\");\n        exit(477);\n    }\n    #endif\n    else if(ffStrEqualsIgnCase(key, \"--disable-linewrap\"))\n        options->disableLinewrap = ffOptionParseBoolean(value);\n    else if(ffStrEqualsIgnCase(key, \"--hide-cursor\"))\n        options->hideCursor = ffOptionParseBoolean(value);\n    else if(ffStrEqualsIgnCase(key, \"--separator\"))\n        ffOptionParseString(key, value, &options->keyValueSeparator);\n    else if(ffStrEqualsIgnCase(key, \"--color\"))\n    {\n        optionCheckString(key, value, &options->colorKeys);\n        ffOptionParseColor(value, &options->colorKeys);\n        ffStrbufSet(&options->colorTitle, &options->colorKeys);\n    }\n    else if(ffStrStartsWithIgnCase(key, \"--color-\"))\n    {\n        const char* subkey = key + strlen(\"--color-\");\n        if(ffStrEqualsIgnCase(subkey, \"keys\"))\n        {\n            optionCheckString(key, value, &options->colorKeys);\n            ffOptionParseColor(value, &options->colorKeys);\n        }\n        else if(ffStrEqualsIgnCase(subkey, \"title\"))\n        {\n            optionCheckString(key, value, &options->colorTitle);\n            ffOptionParseColor(value, &options->colorTitle);\n        }\n        else if(ffStrEqualsIgnCase(subkey, \"output\"))\n        {\n            optionCheckString(key, value, &options->colorOutput);\n            ffOptionParseColor(value, &options->colorOutput);\n        }\n        else if(ffStrEqualsIgnCase(subkey, \"separator\"))\n        {\n            optionCheckString(key, value, &options->colorSeparator);\n            ffOptionParseColor(value, &options->colorSeparator);\n        }\n        else\n            return false;\n    }\n    else if(ffStrStartsWithIgnCase(key, \"--key-\"))\n    {\n        const char* subkey = key + strlen(\"--key-\");\n        if(ffStrEqualsIgnCase(subkey, \"width\"))\n            options->keyWidth = (uint16_t) ffOptionParseUInt32(key, value);\n        else if(ffStrEqualsIgnCase(subkey, \"type\"))\n        {\n            options->keyType = (FFModuleKeyType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {\n                { \"none\", FF_MODULE_KEY_TYPE_NONE },\n                { \"string\", FF_MODULE_KEY_TYPE_STRING },\n                { \"icon\", FF_MODULE_KEY_TYPE_ICON },\n                { \"both\", FF_MODULE_KEY_TYPE_BOTH },\n                { \"both-0\", FF_MODULE_KEY_TYPE_BOTH_0 },\n                { \"both-1\", FF_MODULE_KEY_TYPE_BOTH_1 },\n                { \"both-2\", FF_MODULE_KEY_TYPE_BOTH_2 },\n                { \"both-3\", FF_MODULE_KEY_TYPE_BOTH_3 },\n                { \"both-4\", FF_MODULE_KEY_TYPE_BOTH_4 },\n                {}\n            });\n        }\n        else if(ffStrEqualsIgnCase(subkey, \"padding-left\"))\n            options->keyPaddingLeft = (uint16_t) ffOptionParseUInt32(key, value);\n        else\n            return false;\n    }\n    else if(ffStrEqualsIgnCase(key, \"--bright-color\"))\n        options->brightColor = ffOptionParseBoolean(value);\n    else if(ffStrEqualsIgnCase(key, \"--binary-prefix\"))\n    {\n        fprintf(stderr, \"--binary-prefix has been renamed to --size-binary-prefix\\n\");\n        exit(477);\n    }\n    else if(ffStrStartsWithIgnCase(key, \"--duration-\"))\n    {\n        const char* subkey = key + strlen(\"--duration-\");\n        if(ffStrEqualsIgnCase(subkey, \"abbreviation\"))\n            options->durationAbbreviation = ffOptionParseBoolean(value);\n        else if(ffStrEqualsIgnCase(subkey, \"space-before-unit\"))\n        {\n            options->durationSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {\n                { \"default\", FF_SPACE_BEFORE_UNIT_DEFAULT },\n                { \"always\", FF_SPACE_BEFORE_UNIT_ALWAYS },\n                { \"never\", FF_SPACE_BEFORE_UNIT_NEVER },\n                {},\n            });\n        }\n        else\n            return false;\n    }\n    else if(ffStrStartsWithIgnCase(key, \"--size-\"))\n    {\n        const char* subkey = key + strlen(\"--size-\");\n        if (ffStrEqualsIgnCase(subkey, \"binary-prefix\"))\n        {\n            options->sizeBinaryPrefix = (FFSizeBinaryPrefixType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {\n                { \"iec\", FF_SIZE_BINARY_PREFIX_TYPE_IEC },\n                { \"si\", FF_SIZE_BINARY_PREFIX_TYPE_SI },\n                { \"jedec\", FF_SIZE_BINARY_PREFIX_TYPE_JEDEC },\n                {}\n            });\n        }\n        else if (ffStrEqualsIgnCase(subkey, \"ndigits\"))\n            options->sizeNdigits = (uint8_t) ffOptionParseUInt32(key, value);\n        else if (ffStrEqualsIgnCase(subkey, \"max-prefix\"))\n        {\n            options->sizeMaxPrefix = (uint8_t) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {\n                { \"B\", 0 },\n                { \"kB\", 1 },\n                { \"MB\", 2 },\n                { \"GB\", 3 },\n                { \"TB\", 4 },\n                { \"PB\", 5 },\n                { \"EB\", 6 },\n                { \"ZB\", 7 },\n                { \"YB\", 8 },\n                {}\n            });\n        }\n        else if(ffStrEqualsIgnCase(subkey, \"space-before-unit\"))\n        {\n            options->sizeSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {\n                { \"default\", FF_SPACE_BEFORE_UNIT_DEFAULT },\n                { \"always\", FF_SPACE_BEFORE_UNIT_ALWAYS },\n                { \"never\", FF_SPACE_BEFORE_UNIT_NEVER },\n                {},\n            });\n        }\n        else\n            return false;\n    }\n    else if(ffStrStartsWithIgnCase(key, \"--temp-\"))\n    {\n        const char* subkey = key + strlen(\"--temp-\");\n        if(ffStrEqualsIgnCase(subkey, \"unit\"))\n        {\n            options->tempUnit = (FFTemperatureUnit) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {\n                { \"DEFAULT\", FF_TEMPERATURE_UNIT_DEFAULT },\n                { \"D\", FF_TEMPERATURE_UNIT_DEFAULT },\n                { \"CELSIUS\", FF_TEMPERATURE_UNIT_CELSIUS },\n                { \"C\", FF_TEMPERATURE_UNIT_CELSIUS },\n                { \"FAHRENHEIT\", FF_TEMPERATURE_UNIT_FAHRENHEIT },\n                { \"F\", FF_TEMPERATURE_UNIT_FAHRENHEIT },\n                { \"KELVIN\", FF_TEMPERATURE_UNIT_KELVIN },\n                { \"K\", FF_TEMPERATURE_UNIT_KELVIN },\n                {},\n            });\n        }\n        else if (ffStrEqualsIgnCase(subkey, \"ndigits\"))\n            options->tempNdigits = (uint8_t) ffOptionParseUInt32(key, value);\n        else if(ffStrEqualsIgnCase(subkey, \"color-green\"))\n            ffOptionParseColor(value, &options->tempColorGreen);\n        else if(ffStrEqualsIgnCase(subkey, \"color-yellow\"))\n            ffOptionParseColor(value, &options->tempColorYellow);\n        else if(ffStrEqualsIgnCase(subkey, \"color-red\"))\n            ffOptionParseColor(value, &options->tempColorRed);\n        else if(ffStrEqualsIgnCase(subkey, \"space-before-unit\"))\n        {\n            options->tempSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {\n                { \"default\", FF_SPACE_BEFORE_UNIT_DEFAULT },\n                { \"always\", FF_SPACE_BEFORE_UNIT_ALWAYS },\n                { \"never\", FF_SPACE_BEFORE_UNIT_NEVER },\n                {},\n            });\n        }\n        else\n            return false;\n    }\n    else if(ffStrStartsWithIgnCase(key, \"--percent-\"))\n    {\n        const char* subkey = key + strlen(\"--percent-\");\n        if(ffStrEqualsIgnCase(subkey, \"type\"))\n            options->percentType = (uint8_t) ffOptionParseUInt32(key, value);\n        else if(ffStrEqualsIgnCase(subkey, \"ndigits\"))\n            options->percentNdigits = (uint8_t) ffOptionParseUInt32(key, value);\n        else if(ffStrEqualsIgnCase(subkey, \"color-green\"))\n            ffOptionParseColor(value, &options->percentColorGreen);\n        else if(ffStrEqualsIgnCase(subkey, \"color-yellow\"))\n            ffOptionParseColor(value, &options->percentColorYellow);\n        else if(ffStrEqualsIgnCase(subkey, \"color-red\"))\n            ffOptionParseColor(value, &options->percentColorRed);\n        else if(ffStrEqualsIgnCase(subkey, \"space-before-unit\"))\n        {\n            options->percentSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {\n                { \"default\", FF_SPACE_BEFORE_UNIT_DEFAULT },\n                { \"always\", FF_SPACE_BEFORE_UNIT_ALWAYS },\n                { \"never\", FF_SPACE_BEFORE_UNIT_NEVER },\n                {},\n            });\n        }\n        else if(ffStrEqualsIgnCase(subkey, \"width\"))\n            options->percentWidth = (uint8_t) ffOptionParseUInt32(key, value);\n        else\n            return false;\n    }\n    else if(ffStrEqualsIgnCase(key, \"--fraction-ndigits\"))\n        options->fractionNdigits = (int8_t) ffOptionParseInt32(key, value);\n    else if(ffStrEqualsIgnCase(key, \"--fraction-trailing-zeros\"))\n    {\n        options->fractionTrailingZeros = (FFFractionTrailingZerosType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {\n            { \"default\", FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT },\n            { \"always\", FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS },\n            { \"never\", FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER },\n            {},\n        });\n    }\n    else if(ffStrEqualsIgnCase(key, \"--no-buffer\"))\n        options->noBuffer = ffOptionParseBoolean(value);\n    else if(ffStrStartsWithIgnCase(key, \"--bar-\"))\n    {\n        const char* subkey = key + strlen(\"--bar-\");\n        if(ffStrEqualsIgnCase(subkey, \"char-elapsed\"))\n            ffOptionParseString(key, value, &options->barCharElapsed);\n        else if(ffStrEqualsIgnCase(subkey, \"char-total\"))\n            ffOptionParseString(key, value, &options->barCharTotal);\n        else if(ffStrEqualsIgnCase(subkey, \"width\"))\n            options->barWidth = (uint8_t) ffOptionParseUInt32(key, value);\n        else if(ffStrEqualsIgnCase(subkey, \"border-left\"))\n            ffOptionParseString(key, value, &options->barBorderLeft);\n        else if(ffStrEqualsIgnCase(subkey, \"border-right\"))\n            ffOptionParseString(key, value, &options->barBorderRight);\n        else if(ffStrEqualsIgnCase(subkey, \"border-left-elapsed\"))\n            ffOptionParseString(key, value, &options->barBorderLeftElapsed);\n        else if(ffStrEqualsIgnCase(subkey, \"border-right-elapsed\"))\n            ffOptionParseString(key, value, &options->barBorderRightElapsed);\n        else if(ffStrEqualsIgnCase(subkey, \"color-elapsed\"))\n        {\n            if (!value)\n                ffStrbufClear(&options->barColorElapsed);\n            else if (ffStrEqualsIgnCase(value, \"auto\"))\n                ffStrbufSetStatic(&options->barColorElapsed, \"auto\");\n            else\n                ffOptionParseColor(value, &options->barColorElapsed);\n        }\n        else if(ffStrEqualsIgnCase(subkey, \"color-total\"))\n            ffOptionParseColor(value, &options->barColorTotal);\n        else if(ffStrEqualsIgnCase(subkey, \"color-border\"))\n            ffOptionParseColor(value, &options->barColorBorder);\n        else\n            return false;\n    }\n    else if(ffStrStartsWithIgnCase(key, \"--freq-\"))\n    {\n        const char* subkey = key + strlen(\"--freq-\");\n        if(ffStrEqualsIgnCase(subkey, \"ndigits\"))\n            options->freqNdigits = (int8_t) ffOptionParseInt32(key, value);\n        else if(ffStrEqualsIgnCase(subkey, \"space-before-unit\"))\n        {\n            options->freqSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {\n                { \"default\", FF_SPACE_BEFORE_UNIT_DEFAULT },\n                { \"always\", FF_SPACE_BEFORE_UNIT_ALWAYS },\n                { \"never\", FF_SPACE_BEFORE_UNIT_NEVER },\n                {},\n            });\n        }\n        else\n            return false;\n    }\n    else\n        return false;\n    return true;\n}\n\nvoid ffOptionsInitDisplay(FFOptionsDisplay* options)\n{\n    ffStrbufInit(&options->colorKeys);\n    ffStrbufInit(&options->colorTitle);\n    ffStrbufInit(&options->colorOutput);\n    ffStrbufInit(&options->colorSeparator);\n    options->brightColor = !instance.state.terminalLightTheme;\n    ffStrbufInitStatic(&options->keyValueSeparator, \": \");\n\n    options->showErrors = false;\n    options->pipe = !isatty(STDOUT_FILENO) || !!getenv(\"NO_COLOR\");\n\n    #ifdef NDEBUG\n    options->disableLinewrap = !options->pipe;\n    #else\n    options->disableLinewrap = false;\n    options->debugMode = !!getenv(\"FF_DEBUG\");\n    #endif\n\n    options->durationSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;\n    options->hideCursor = false;\n    options->sizeBinaryPrefix = FF_SIZE_BINARY_PREFIX_TYPE_IEC;\n    options->sizeNdigits = 2;\n    options->sizeMaxPrefix = 8; // YB\n    options->sizeSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;\n\n    options->stat = -1;\n    options->noBuffer = false;\n    options->keyWidth = 0;\n    options->keyPaddingLeft = 0;\n    options->keyType = FF_MODULE_KEY_TYPE_STRING;\n\n    options->tempUnit = FF_TEMPERATURE_UNIT_DEFAULT;\n    options->tempNdigits = 1;\n    ffStrbufInitStatic(&options->tempColorGreen, FF_COLOR_FG_GREEN);\n    ffStrbufInitStatic(&options->tempColorYellow, instance.state.terminalLightTheme ? FF_COLOR_FG_YELLOW : FF_COLOR_FG_LIGHT_YELLOW);\n    ffStrbufInitStatic(&options->tempColorRed, instance.state.terminalLightTheme ? FF_COLOR_FG_RED : FF_COLOR_FG_LIGHT_RED);\n    options->tempSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;\n\n    ffStrbufInitStatic(&options->barCharElapsed, \"■\");\n    ffStrbufInitStatic(&options->barCharTotal, \"-\");\n    ffStrbufInitStatic(&options->barBorderLeft, \"[ \");\n    ffStrbufInitStatic(&options->barBorderRight, \" ]\");\n    ffStrbufInit(&options->barBorderLeftElapsed);\n    ffStrbufInit(&options->barBorderRightElapsed);\n    ffStrbufInitStatic(&options->barColorElapsed, \"auto\");\n    ffStrbufInitStatic(&options->barColorTotal, instance.state.terminalLightTheme ? FF_COLOR_FG_WHITE : FF_COLOR_FG_LIGHT_WHITE);\n    ffStrbufInitStatic(&options->barColorBorder, instance.state.terminalLightTheme ? FF_COLOR_FG_WHITE : FF_COLOR_FG_LIGHT_WHITE);\n    options->barWidth = 10;\n\n    options->durationAbbreviation = false;\n    options->durationSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;\n    options->percentType = 9;\n    options->percentNdigits = 0;\n    ffStrbufInitStatic(&options->percentColorGreen, FF_COLOR_FG_GREEN);\n    ffStrbufInitStatic(&options->percentColorYellow, instance.state.terminalLightTheme ? FF_COLOR_FG_YELLOW : FF_COLOR_FG_LIGHT_YELLOW);\n    ffStrbufInitStatic(&options->percentColorRed, instance.state.terminalLightTheme ? FF_COLOR_FG_RED : FF_COLOR_FG_LIGHT_RED);\n    options->percentSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;\n    options->percentWidth = 0;\n\n    options->freqNdigits = 2;\n    options->freqSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;\n    options->fractionNdigits = 2;\n    options->fractionTrailingZeros = FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT;\n\n    ffListInit(&options->constants, sizeof(FFstrbuf));\n}\n\nvoid ffOptionsDestroyDisplay(FFOptionsDisplay* options)\n{\n    ffStrbufDestroy(&options->colorKeys);\n    ffStrbufDestroy(&options->colorTitle);\n    ffStrbufDestroy(&options->colorOutput);\n    ffStrbufDestroy(&options->colorSeparator);\n    ffStrbufDestroy(&options->keyValueSeparator);\n    ffStrbufDestroy(&options->barCharElapsed);\n    ffStrbufDestroy(&options->barCharTotal);\n    FF_LIST_FOR_EACH(FFstrbuf, item, options->constants)\n        ffStrbufDestroy(item);\n    ffListDestroy(&options->constants);\n}\n\nvoid ffOptionsGenerateDisplayJsonConfig(FFdata* data, FFOptionsDisplay* options)\n{\n    yyjson_mut_doc* doc = data->resultDoc;\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, doc->root, \"display\");\n\n    if (options->stat <= 0)\n        yyjson_mut_obj_add_bool(doc, obj, \"stat\", options->stat == 0);\n    else\n        yyjson_mut_obj_add_int(doc, obj, \"stat\", options->stat);\n\n    yyjson_mut_obj_add_bool(doc, obj, \"pipe\", options->pipe);\n\n    yyjson_mut_obj_add_bool(doc, obj, \"showErrors\", options->showErrors);\n\n    yyjson_mut_obj_add_bool(doc, obj, \"disableLinewrap\", options->disableLinewrap);\n\n    yyjson_mut_obj_add_bool(doc, obj, \"hideCursor\", options->hideCursor);\n\n    yyjson_mut_obj_add_strbuf(doc, obj, \"separator\", &options->keyValueSeparator);\n\n    {\n        yyjson_mut_val* color = yyjson_mut_obj_add_obj(doc, obj, \"color\");\n        yyjson_mut_obj_add_strbuf(doc, color, \"keys\", &options->colorKeys);\n        yyjson_mut_obj_add_strbuf(doc, color, \"title\", &options->colorTitle);\n        yyjson_mut_obj_add_strbuf(doc, color, \"output\", &options->colorOutput);\n        yyjson_mut_obj_add_strbuf(doc, color, \"separator\", &options->colorSeparator);\n    }\n\n    yyjson_mut_obj_add_bool(doc, obj, \"brightColor\", options->brightColor);\n\n    {\n        yyjson_mut_val* duration = yyjson_mut_obj_add_obj(doc, obj, \"duration\");\n        yyjson_mut_obj_add_bool(doc, duration, \"abbreviation\", options->durationAbbreviation);\n        switch (options->durationSpaceBeforeUnit)\n        {\n            case FF_SPACE_BEFORE_UNIT_DEFAULT:\n                yyjson_mut_obj_add_str(doc, duration, \"spaceBeforeUnit\", \"default\");\n                break;\n            case FF_SPACE_BEFORE_UNIT_ALWAYS:\n                yyjson_mut_obj_add_str(doc, duration, \"spaceBeforeUnit\", \"always\");\n                break;\n            case FF_SPACE_BEFORE_UNIT_NEVER:\n                yyjson_mut_obj_add_str(doc, duration, \"spaceBeforeUnit\", \"never\");\n                break;\n        }\n    }\n\n    {\n        yyjson_mut_val* size = yyjson_mut_obj_add_obj(doc, obj, \"size\");\n        yyjson_mut_obj_add_str(doc, size, \"maxPrefix\", ((const char* []) {\n            \"B\",\n            \"kB\",\n            \"MB\",\n            \"GB\",\n            \"TB\",\n            \"PB\",\n            \"EB\",\n            \"ZB\",\n            \"YB\",\n        })[options->sizeMaxPrefix]);\n        switch (options->sizeBinaryPrefix)\n        {\n            case FF_SIZE_BINARY_PREFIX_TYPE_IEC:\n                yyjson_mut_obj_add_str(doc, size, \"binaryPrefix\", \"iec\");\n                break;\n            case FF_SIZE_BINARY_PREFIX_TYPE_SI:\n                yyjson_mut_obj_add_str(doc, size, \"binaryPrefix\", \"si\");\n                break;\n            case FF_SIZE_BINARY_PREFIX_TYPE_JEDEC:\n                yyjson_mut_obj_add_str(doc, size, \"binaryPrefix\", \"jedec\");\n                break;\n        }\n        yyjson_mut_obj_add_uint(doc, size, \"ndigits\", options->sizeNdigits);\n        switch (options->sizeSpaceBeforeUnit)\n        {\n            case FF_SPACE_BEFORE_UNIT_DEFAULT:\n                yyjson_mut_obj_add_str(doc, size, \"spaceBeforeUnit\", \"default\");\n                break;\n            case FF_SPACE_BEFORE_UNIT_ALWAYS:\n                yyjson_mut_obj_add_str(doc, size, \"spaceBeforeUnit\", \"always\");\n                break;\n            case FF_SPACE_BEFORE_UNIT_NEVER:\n                yyjson_mut_obj_add_str(doc, size, \"spaceBeforeUnit\", \"never\");\n                break;\n        }\n    }\n\n    {\n        yyjson_mut_val* temperature = yyjson_mut_obj_add_obj(doc, obj, \"temp\");\n        switch (options->tempUnit)\n        {\n            case FF_TEMPERATURE_UNIT_DEFAULT:\n                yyjson_mut_obj_add_str(doc, temperature, \"unit\", \"D\");\n                break;\n            case FF_TEMPERATURE_UNIT_CELSIUS:\n                yyjson_mut_obj_add_str(doc, obj, \"unit\", \"C\");\n                break;\n            case FF_TEMPERATURE_UNIT_FAHRENHEIT:\n                yyjson_mut_obj_add_str(doc, obj, \"unit\", \"F\");\n                break;\n            case FF_TEMPERATURE_UNIT_KELVIN:\n                yyjson_mut_obj_add_str(doc, obj, \"unit\", \"K\");\n                break;\n        }\n        yyjson_mut_obj_add_uint(doc, temperature, \"ndigits\", options->tempNdigits);\n        {\n            yyjson_mut_val* color = yyjson_mut_obj_add_obj(doc, temperature, \"color\");\n            yyjson_mut_obj_add_strbuf(doc, color, \"green\", &options->tempColorGreen);\n            yyjson_mut_obj_add_strbuf(doc, color, \"yellow\", &options->tempColorYellow);\n            yyjson_mut_obj_add_strbuf(doc, color, \"red\", &options->tempColorRed);\n        }\n        switch (options->tempSpaceBeforeUnit)\n        {\n            case FF_SPACE_BEFORE_UNIT_DEFAULT:\n                yyjson_mut_obj_add_str(doc, temperature, \"spaceBeforeUnit\", \"default\");\n                break;\n            case FF_SPACE_BEFORE_UNIT_ALWAYS:\n                yyjson_mut_obj_add_str(doc, temperature, \"spaceBeforeUnit\", \"always\");\n                break;\n            case FF_SPACE_BEFORE_UNIT_NEVER:\n                yyjson_mut_obj_add_str(doc, temperature, \"spaceBeforeUnit\", \"never\");\n                break;\n        }\n    }\n\n    {\n        yyjson_mut_val* percent = yyjson_mut_obj_add_obj(doc, obj, \"percent\");\n        {\n            yyjson_mut_val* type = yyjson_mut_obj_add_arr(doc, percent, \"type\");\n            if (options->percentType & FF_PERCENTAGE_TYPE_NUM_BIT)\n                yyjson_mut_arr_add_str(doc, type, \"num\");\n            if (options->percentType & FF_PERCENTAGE_TYPE_BAR_BIT)\n                yyjson_mut_arr_add_str(doc, type, \"var\");\n            if (options->percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)\n                yyjson_mut_arr_add_str(doc, type, \"hide-others\");\n            if (options->percentType & FF_PERCENTAGE_TYPE_NUM_COLOR_BIT)\n                yyjson_mut_arr_add_str(doc, type, \"num-color\");\n            if (options->percentType & FF_PERCENTAGE_TYPE_BAR_MONOCHROME_BIT)\n                yyjson_mut_arr_add_str(doc, type, \"bar-monochrome\");\n        }\n        yyjson_mut_obj_add_uint(doc, percent, \"ndigits\", options->percentNdigits);\n        {\n            yyjson_mut_val* color = yyjson_mut_obj_add_obj(doc, percent, \"color\");\n            yyjson_mut_obj_add_strbuf(doc, color, \"green\", &options->percentColorGreen);\n            yyjson_mut_obj_add_strbuf(doc, color, \"yellow\", &options->percentColorYellow);\n            yyjson_mut_obj_add_strbuf(doc, color, \"red\", &options->percentColorRed);\n        }\n        switch (options->percentSpaceBeforeUnit)\n        {\n            case FF_SPACE_BEFORE_UNIT_DEFAULT:\n                yyjson_mut_obj_add_str(doc, percent, \"spaceBeforeUnit\", \"default\");\n                break;\n            case FF_SPACE_BEFORE_UNIT_ALWAYS:\n                yyjson_mut_obj_add_str(doc, percent, \"spaceBeforeUnit\", \"always\");\n                break;\n            case FF_SPACE_BEFORE_UNIT_NEVER:\n                yyjson_mut_obj_add_str(doc, percent, \"spaceBeforeUnit\", \"never\");\n                break;\n        }\n        yyjson_mut_obj_add_uint(doc, percent, \"width\", options->percentWidth);\n    }\n\n    {\n        yyjson_mut_val* bar = yyjson_mut_obj_add_obj(doc, obj, \"bar\");\n\n        yyjson_mut_val* char_ = yyjson_mut_obj_add_obj(doc, bar, \"char\");\n        yyjson_mut_obj_add_strbuf(doc, char_, \"elapsed\", &options->barCharElapsed);\n        yyjson_mut_obj_add_strbuf(doc, char_, \"total\", &options->barCharTotal);\n\n        yyjson_mut_val* border = yyjson_mut_obj_add_obj(doc, bar, \"border\");\n        yyjson_mut_obj_add_strbuf(doc, border, \"left\", &options->barBorderLeft);\n        yyjson_mut_obj_add_strbuf(doc, border, \"right\", &options->barBorderRight);\n        yyjson_mut_obj_add_strbuf(doc, border, \"leftElapsed\", &options->barBorderLeftElapsed);\n        yyjson_mut_obj_add_strbuf(doc, border, \"rightElapsed\", &options->barBorderRightElapsed);\n\n        yyjson_mut_val* color = yyjson_mut_obj_add_obj(doc, bar, \"color\");\n        yyjson_mut_obj_add_strbuf(doc, color, \"elapsed\", &options->barColorElapsed);\n        yyjson_mut_obj_add_strbuf(doc, color, \"total\", &options->barColorTotal);\n        yyjson_mut_obj_add_strbuf(doc, color, \"border\", &options->barColorBorder);\n\n        yyjson_mut_obj_add_uint(doc, bar, \"width\", options->barWidth);\n    }\n\n    {\n        yyjson_mut_val* fraction = yyjson_mut_obj_add_obj(doc, obj, \"fraction\");\n\n        if (options->fractionNdigits < 0)\n            yyjson_mut_obj_add_null(doc, fraction, \"ndigits\");\n        else\n            yyjson_mut_obj_add_uint(doc, fraction, \"ndigits\", (uint8_t) options->fractionNdigits);\n    }\n\n    yyjson_mut_obj_add_bool(doc, obj, \"noBuffer\", options->noBuffer);\n\n    {\n        yyjson_mut_val* key = yyjson_mut_obj_add_obj(doc, obj, \"key\");\n        yyjson_mut_obj_add_uint(doc, key, \"width\", options->keyWidth);\n        switch ((uint8_t) options->keyType)\n        {\n            case FF_MODULE_KEY_TYPE_NONE:\n                yyjson_mut_obj_add_str(doc, key, \"type\", \"none\");\n                break;\n            case FF_MODULE_KEY_TYPE_STRING:\n                yyjson_mut_obj_add_str(doc, key, \"type\", \"string\");\n                break;\n            case FF_MODULE_KEY_TYPE_ICON:\n                yyjson_mut_obj_add_str(doc, key, \"type\", \"icon\");\n                break;\n            case FF_MODULE_KEY_TYPE_BOTH:\n                yyjson_mut_obj_add_str(doc, key, \"type\", \"both\");\n                break;\n        }\n\n        yyjson_mut_obj_add_uint(doc, key, \"paddingLeft\", options->keyPaddingLeft);\n    }\n\n    {\n        yyjson_mut_val* freq = yyjson_mut_obj_add_obj(doc, obj, \"freq\");\n        yyjson_mut_obj_add_int(doc, freq, \"ndigits\", options->freqNdigits);\n        switch (options->percentSpaceBeforeUnit)\n        {\n            case FF_SPACE_BEFORE_UNIT_DEFAULT:\n                yyjson_mut_obj_add_str(doc, freq, \"spaceBeforeUnit\", \"default\");\n                break;\n            case FF_SPACE_BEFORE_UNIT_ALWAYS:\n                yyjson_mut_obj_add_str(doc, freq, \"spaceBeforeUnit\", \"always\");\n                break;\n            case FF_SPACE_BEFORE_UNIT_NEVER:\n                yyjson_mut_obj_add_str(doc, freq, \"spaceBeforeUnit\", \"never\");\n                break;\n        }\n    }\n\n    {\n        yyjson_mut_val* constants = yyjson_mut_obj_add_arr(doc, obj, \"constants\");\n        FF_LIST_FOR_EACH(FFstrbuf, item, options->constants)\n            yyjson_mut_arr_add_strbuf(doc, constants, item);\n    }\n}\n"
  },
  {
    "path": "src/options/display.h",
    "content": "#pragma once\n\n#include \"common/ffdata.h\"\n#include \"common/percent.h\"\n#include \"common/FFstrbuf.h\"\n#include \"common/FFlist.h\"\n\ntypedef enum __attribute__((__packed__)) FFSizeBinaryPrefixType\n{\n    FF_SIZE_BINARY_PREFIX_TYPE_IEC,   // 1024 Bytes = 1 KiB, 1024 KiB = 1 MiB, ... (standard)\n    FF_SIZE_BINARY_PREFIX_TYPE_SI,    // 1000 Bytes = 1 kB, 1000 kB = 1 MB, ...\n    FF_SIZE_BINARY_PREFIX_TYPE_JEDEC, // 1024 Bytes = 1 KB, 1024 KB = 1 MB, ...\n} FFSizeBinaryPrefixType;\n\ntypedef enum __attribute__((__packed__)) FFTemperatureUnit\n{\n    FF_TEMPERATURE_UNIT_DEFAULT,\n    FF_TEMPERATURE_UNIT_CELSIUS,\n    FF_TEMPERATURE_UNIT_FAHRENHEIT,\n    FF_TEMPERATURE_UNIT_KELVIN,\n} FFTemperatureUnit;\n\ntypedef enum __attribute__((__packed__)) FFSpaceBeforeUnitType\n{\n    FF_SPACE_BEFORE_UNIT_DEFAULT,\n    FF_SPACE_BEFORE_UNIT_ALWAYS,\n    FF_SPACE_BEFORE_UNIT_NEVER,\n} FFSpaceBeforeUnitType;\n\ntypedef enum __attribute__((__packed__)) FFFractionTrailingZerosType\n{\n    FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT,\n    FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS,\n    FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER,\n} FFFractionTrailingZerosType;\n\ntypedef struct FFOptionsDisplay\n{\n    //If one of those is empty, ffLogoPrint will set them\n    FFstrbuf colorKeys;\n    FFstrbuf colorTitle;\n    FFstrbuf colorOutput;\n    FFstrbuf colorSeparator;\n\n    bool brightColor;\n\n    FFstrbuf keyValueSeparator;\n\n    int32_t stat; // <0: disable stat; 0: no threshold; >0: threshold in ms\n    bool pipe; //disables all escape sequences\n    bool showErrors;\n    #ifndef NDEBUG\n    bool debugMode;\n    #endif\n    bool disableLinewrap;\n    bool durationAbbreviation;\n    FFSpaceBeforeUnitType durationSpaceBeforeUnit;\n    bool hideCursor;\n    FFSizeBinaryPrefixType sizeBinaryPrefix;\n    uint8_t sizeNdigits;\n    uint8_t sizeMaxPrefix;\n    FFSpaceBeforeUnitType sizeSpaceBeforeUnit;\n    FFTemperatureUnit tempUnit;\n    uint8_t tempNdigits;\n    FFstrbuf tempColorGreen;\n    FFstrbuf tempColorYellow;\n    FFstrbuf tempColorRed;\n    FFSpaceBeforeUnitType tempSpaceBeforeUnit;\n    FFstrbuf barCharElapsed;\n    FFstrbuf barCharTotal;\n    FFstrbuf barBorderLeft;\n    FFstrbuf barBorderRight;\n    FFstrbuf barBorderLeftElapsed;\n    FFstrbuf barBorderRightElapsed;\n    FFstrbuf barColorElapsed; // \"auto\" for auto selection from percent config; empty for no custom color (inherits)\n    FFstrbuf barColorTotal; // empty for no custom color (inherits)\n    FFstrbuf barColorBorder; // empty for no custom color (inherits)\n    uint8_t barWidth;\n    FFPercentageTypeFlags percentType;\n    uint8_t percentNdigits;\n    FFstrbuf percentColorGreen;\n    FFstrbuf percentColorYellow;\n    FFstrbuf percentColorRed;\n    FFSpaceBeforeUnitType percentSpaceBeforeUnit;\n    uint8_t percentWidth;\n    bool noBuffer;\n    FFModuleKeyType keyType;\n    uint16_t keyWidth;\n    uint16_t keyPaddingLeft;\n    int8_t freqNdigits;\n    FFSpaceBeforeUnitType freqSpaceBeforeUnit;\n    int8_t fractionNdigits;\n    FFFractionTrailingZerosType fractionTrailingZeros;\n\n    FFlist constants; // list of FFstrbuf\n} FFOptionsDisplay;\n\nconst char* ffOptionsParseDisplayJsonConfig(FFOptionsDisplay* options, yyjson_val* root);\nbool ffOptionsParseDisplayCommandLine(FFOptionsDisplay* options, const char* key, const char* value);\nvoid ffOptionsInitDisplay(FFOptionsDisplay* options);\nvoid ffOptionsDestroyDisplay(FFOptionsDisplay* options);\nvoid ffOptionsGenerateDisplayJsonConfig(FFdata* data, FFOptionsDisplay* options);\n"
  },
  {
    "path": "src/options/general.c",
    "content": "#include \"fastfetch.h\"\n#include \"common/jsonconfig.h\"\n#include \"common/processing.h\"\n#include \"common/stringUtils.h\"\n#include \"options/general.h\"\n\n#include <unistd.h>\n\nconst char* ffOptionsParseGeneralJsonConfig(FFOptionsGeneral* options, yyjson_val* root)\n{\n    yyjson_val* object = yyjson_obj_get(root, \"general\");\n    if (!object) return NULL;\n    if (!yyjson_is_obj(object)) return \"Property 'general' must be an object\";\n\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(object, idx, max, key, val)\n    {\n        if (unsafe_yyjson_equals_str(key, \"thread\"))\n            options->multithreading = yyjson_get_bool(val);\n        else if (unsafe_yyjson_equals_str(key, \"processingTimeout\"))\n            options->processingTimeout = (int32_t) yyjson_get_int(val);\n        else if (unsafe_yyjson_equals_str(key, \"preRun\"))\n        {\n            if (!yyjson_is_str(val))\n                return \"general.preRun must be a string\";\n            if (system(unsafe_yyjson_get_str(val)) < 0)\n                return \"Failed to execute preRun command\";\n        }\n        else if (unsafe_yyjson_equals_str(key, \"detectVersion\"))\n            options->detectVersion = yyjson_get_bool(val);\n\n        #if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__)\n        else if (unsafe_yyjson_equals_str(key, \"playerName\"))\n            ffStrbufSetJsonVal(&options->playerName, val);\n        else if (unsafe_yyjson_equals_str(key, \"dsForceDrm\"))\n        {\n            if (yyjson_is_str(val))\n            {\n                int value;\n                const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {\n                    { \"sysfs-only\", FF_DS_FORCE_DRM_TYPE_SYSFS_ONLY },\n                    { \"false\", FF_DS_FORCE_DRM_TYPE_FALSE },\n                    { \"true\", FF_DS_FORCE_DRM_TYPE_TRUE },\n                    {},\n                });\n                if (error)\n                    return \"Invalid enum value of `dsForceDrm`\";\n                else\n                    options->dsForceDrm = (FFDsForceDrmType) value;\n            }\n            else\n                options->dsForceDrm = yyjson_get_bool(val) ? FF_DS_FORCE_DRM_TYPE_TRUE : FF_DS_FORCE_DRM_TYPE_FALSE;\n        }\n        #elif defined(_WIN32)\n        else if (unsafe_yyjson_equals_str(key, \"wmiTimeout\"))\n            options->wmiTimeout = (int32_t) yyjson_get_int(val);\n        #endif\n\n        else\n            return \"Unknown general property\";\n    }\n\n    return NULL;\n}\n\nbool ffOptionsParseGeneralCommandLine(FFOptionsGeneral* options, const char* key, const char* value)\n{\n    if(ffStrEqualsIgnCase(key, \"--thread\") || ffStrEqualsIgnCase(key, \"--multithreading\"))\n        options->multithreading = ffOptionParseBoolean(value);\n    else if(ffStrEqualsIgnCase(key, \"--processing-timeout\"))\n        options->processingTimeout = ffOptionParseInt32(key, value);\n    else if(ffStrEqualsIgnCase(key, \"--detect-version\"))\n        options->detectVersion = ffOptionParseBoolean(value);\n\n    #if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__)\n    else if(ffStrEqualsIgnCase(key, \"--player-name\"))\n        ffOptionParseString(key, value, &options->playerName);\n    else if(ffStrEqualsIgnCase(key, \"--ds-force-drm\"))\n    {\n        if (ffOptionParseBoolean(value))\n            options->dsForceDrm = FF_DS_FORCE_DRM_TYPE_TRUE;\n        else if (ffStrEqualsIgnCase(value, \"sysfs-only\"))\n            options->dsForceDrm = FF_DS_FORCE_DRM_TYPE_SYSFS_ONLY;\n        else\n            options->dsForceDrm = FF_DS_FORCE_DRM_TYPE_FALSE;\n    }\n    #elif defined(_WIN32)\n    else if (ffStrEqualsIgnCase(key, \"--wmi-timeout\"))\n        options->wmiTimeout = ffOptionParseInt32(key, value);\n    #endif\n\n    else\n        return false;\n\n    return true;\n}\n\nvoid ffOptionsInitGeneral(FFOptionsGeneral* options)\n{\n    options->processingTimeout = 5000;\n    options->multithreading = true;\n    options->detectVersion = true;\n\n    #if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__)\n    ffStrbufInit(&options->playerName);\n    options->dsForceDrm = FF_DS_FORCE_DRM_TYPE_FALSE;\n    #elif defined(_WIN32)\n    options->wmiTimeout = 5000;\n    #endif\n}\n\nvoid ffOptionsDestroyGeneral(FF_MAYBE_UNUSED FFOptionsGeneral* options)\n{\n    #if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__)\n    ffStrbufDestroy(&options->playerName);\n    #endif\n}\n\nvoid ffOptionsGenerateGeneralJsonConfig(FFdata* data, FFOptionsGeneral* options)\n{\n    yyjson_mut_doc* doc = data->resultDoc;\n    yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, doc->root, \"general\");\n\n    yyjson_mut_obj_add_bool(doc, obj, \"thread\", options->multithreading);\n\n    yyjson_mut_obj_add_int(doc, obj, \"processingTimeout\", options->processingTimeout);\n\n    yyjson_mut_obj_add_bool(doc, obj, \"detectVersion\", options->detectVersion);\n\n    #if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__)\n\n    yyjson_mut_obj_add_strbuf(doc, obj, \"playerName\", &options->playerName);\n\n    switch (options->dsForceDrm)\n    {\n        case FF_DS_FORCE_DRM_TYPE_FALSE:\n            yyjson_mut_obj_add_bool(doc, obj, \"dsForceDrm\", false);\n            break;\n        case FF_DS_FORCE_DRM_TYPE_SYSFS_ONLY:\n            yyjson_mut_obj_add_str(doc, obj, \"dsForceDrm\", \"sysfs-only\");\n            break;\n        case FF_DS_FORCE_DRM_TYPE_TRUE:\n            yyjson_mut_obj_add_bool(doc, obj, \"dsForceDrm\", true);\n            break;\n    }\n\n    #elif defined(_WIN32)\n\n    yyjson_mut_obj_add_int(doc, obj, \"wmiTimeout\", options->wmiTimeout);\n\n    #endif\n}\n"
  },
  {
    "path": "src/options/general.h",
    "content": "#pragma once\n\n#include \"common/ffdata.h\"\n\ntypedef enum __attribute__((__packed__)) FFDsForceDrmType\n{\n    FF_DS_FORCE_DRM_TYPE_FALSE = 0,   // Disable\n    FF_DS_FORCE_DRM_TYPE_TRUE = 1,    // Try `libdrm`, then `sysfs` if libdrm failed\n    FF_DS_FORCE_DRM_TYPE_SYSFS_ONLY, // Use `/sys/class/drm` only\n} FFDsForceDrmType;\n\ntypedef struct FFOptionsGeneral\n{\n    bool multithreading;\n    int32_t processingTimeout;\n    bool detectVersion;\n\n    // Module options that cannot be put in module option structure\n    #if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__)\n    FFstrbuf playerName;\n    FFDsForceDrmType dsForceDrm;\n    #elif defined(_WIN32)\n    int32_t wmiTimeout;\n    #endif\n} FFOptionsGeneral;\n\nconst char* ffOptionsParseGeneralJsonConfig(FFOptionsGeneral* options, yyjson_val* root);\nbool ffOptionsParseGeneralCommandLine(FFOptionsGeneral* options, const char* key, const char* value);\nvoid ffOptionsInitGeneral(FFOptionsGeneral* options);\nvoid ffOptionsDestroyGeneral(FFOptionsGeneral* options);\nvoid ffOptionsGenerateGeneralJsonConfig(FFdata* data, FFOptionsGeneral* options);\n"
  },
  {
    "path": "src/options/logo.c",
    "content": "#include \"logo/logo.h\"\n\n#include \"common/jsonconfig.h\"\n#include \"common/stringUtils.h\"\n\nvoid ffOptionsInitLogo(FFOptionsLogo* options)\n{\n    ffStrbufInit(&options->source);\n    options->type = FF_LOGO_TYPE_AUTO;\n    for(uint8_t i = 0; i < (uint8_t) FASTFETCH_LOGO_MAX_COLORS; ++i)\n        ffStrbufInit(&options->colors[i]);\n    options->width = 0;\n    options->height = 0; //preserve aspect ratio\n    options->paddingTop = 0;\n    options->paddingLeft = 0;\n    options->paddingRight = 4;\n    options->printRemaining = true;\n    options->preserveAspectRatio = false;\n    options->recache = false;\n    options->position = FF_LOGO_POSITION_LEFT;\n\n    options->chafaFgOnly = false;\n    ffStrbufInitStatic(&options->chafaSymbols, \"block+border+space-wide-inverted\"); // Chafa default\n    options->chafaCanvasMode = UINT32_MAX;\n    options->chafaColorSpace = UINT32_MAX;\n    options->chafaDitherMode = UINT32_MAX;\n}\n\nbool ffOptionsParseLogoCommandLine(FFOptionsLogo* options, const char* key, const char* value)\n{\n    if (ffStrEqualsIgnCase(key, \"-l\"))\n        goto logoType;\n\n    const char* subKey = ffOptionTestPrefix(key, \"logo\");\n    if(subKey)\n    {\n        if (subKey[0] == '\\0')\n        {\nlogoType:\n            if(value == NULL)\n            {\n                fprintf(stderr, \"Error: usage: %s <none|small|logo-source>\\n\", key);\n                exit(477);\n            }\n            //this is usually wanted when disabling logo\n            if(ffStrEqualsIgnCase(value, \"none\"))\n                options->type = FF_LOGO_TYPE_NONE;\n            else if(ffStrEqualsIgnCase(value, \"small\"))\n                options->type = FF_LOGO_TYPE_SMALL;\n            else\n                ffOptionParseString(key, value, &options->source);\n        }\n        else if(ffStrEqualsIgnCase(subKey, \"type\"))\n        {\n            options->type = (FFLogoType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {\n                { \"auto\", FF_LOGO_TYPE_AUTO },\n                { \"builtin\", FF_LOGO_TYPE_BUILTIN },\n                { \"small\", FF_LOGO_TYPE_SMALL },\n                { \"file\", FF_LOGO_TYPE_FILE },\n                { \"file-raw\", FF_LOGO_TYPE_FILE_RAW },\n                { \"data\", FF_LOGO_TYPE_DATA },\n                { \"data-raw\", FF_LOGO_TYPE_DATA_RAW },\n                { \"command-raw\", FF_LOGO_TYPE_COMMAND_RAW },\n                { \"sixel\", FF_LOGO_TYPE_IMAGE_SIXEL },\n                { \"kitty\", FF_LOGO_TYPE_IMAGE_KITTY },\n                { \"kitty-direct\", FF_LOGO_TYPE_IMAGE_KITTY_DIRECT },\n                { \"kitty-icat\", FF_LOGO_TYPE_IMAGE_KITTY_ICAT },\n                { \"iterm\", FF_LOGO_TYPE_IMAGE_ITERM },\n                { \"chafa\", FF_LOGO_TYPE_IMAGE_CHAFA },\n                { \"raw\", FF_LOGO_TYPE_IMAGE_RAW },\n                { \"none\", FF_LOGO_TYPE_NONE },\n                {},\n            });\n        }\n        else if(ffStrStartsWithIgnCase(subKey, \"color-\") && subKey[6] != '\\0' && subKey[7] == '\\0') // matches \"--logo-color-*\"\n        {\n            //Map the number to an array index, so that '1' -> 0, '2' -> 1, etc.\n            int index = (int)subKey[6] - '0' - 1;\n\n            //Match only --logo-color-[1-9]\n            if(index < 0 || index >= FASTFETCH_LOGO_MAX_COLORS)\n            {\n                fprintf(stderr, \"Error: invalid --color-[1-9] index: %c\\n\", key[13]);\n                exit(472);\n            }\n            if(value == NULL)\n            {\n                fprintf(stderr, \"Error: usage: %s <str>\\n\", key);\n                exit(477);\n            }\n            ffOptionParseColor(value, &options->colors[index]);\n        }\n        else if(ffStrEqualsIgnCase(subKey, \"width\"))\n            options->width = ffOptionParseUInt32(key, value);\n        else if(ffStrEqualsIgnCase(subKey, \"height\"))\n            options->height = ffOptionParseUInt32(key, value);\n        else if(ffStrEqualsIgnCase(subKey, \"padding\"))\n        {\n            uint32_t padding = ffOptionParseUInt32(key, value);\n            options->paddingLeft = padding;\n            options->paddingRight = padding;\n        }\n        else if(ffStrEqualsIgnCase(subKey, \"padding-top\"))\n            options->paddingTop = ffOptionParseUInt32(key, value);\n        else if(ffStrEqualsIgnCase(subKey, \"padding-left\"))\n            options->paddingLeft = ffOptionParseUInt32(key, value);\n        else if(ffStrEqualsIgnCase(subKey, \"padding-right\"))\n            options->paddingRight = ffOptionParseUInt32(key, value);\n        else if(ffStrEqualsIgnCase(subKey, \"print-remaining\"))\n            options->printRemaining = ffOptionParseBoolean(value);\n        else if(ffStrEqualsIgnCase(subKey, \"preserve-aspect-ratio\"))\n            options->preserveAspectRatio = ffOptionParseBoolean(value);\n        else if(ffStrEqualsIgnCase(subKey, \"recache\"))\n            options->recache = ffOptionParseBoolean(value);\n        else if(ffStrEqualsIgnCase(subKey, \"separate\"))\n        {\n            fputs(\"--logo-separate has been renamed to --logo-position\\n\", stderr);\n            exit(477);\n        }\n        else if(ffStrEqualsIgnCase(subKey, \"position\"))\n        {\n            options->position = (FFLogoPosition) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {\n                { \"left\", FF_LOGO_POSITION_LEFT },\n                { \"right\", FF_LOGO_POSITION_RIGHT },\n                { \"top\", FF_LOGO_POSITION_TOP },\n                {},\n            });\n        }\n        else\n            return false;\n    }\n    else if((subKey = ffOptionTestPrefix(key, \"file\")))\n    {\n        if(subKey[0] == '\\0')\n        {\n            ffOptionParseString(key, value, &options->source);\n            options->type = FF_LOGO_TYPE_FILE;\n        }\n        else if(ffStrEqualsIgnCase(subKey, \"raw\"))\n        {\n            ffOptionParseString(key, value, &options->source);\n            options->type = FF_LOGO_TYPE_FILE_RAW;\n        }\n        else\n            return false;\n    }\n    else if((subKey = ffOptionTestPrefix(key, \"data\")))\n    {\n        if(subKey[0] == '\\0')\n        {\n            ffOptionParseString(key, value, &options->source);\n            options->type = FF_LOGO_TYPE_DATA;\n        }\n        else if(ffStrEqualsIgnCase(subKey, \"raw\"))\n        {\n            ffOptionParseString(key, value, &options->source);\n            options->type = FF_LOGO_TYPE_DATA_RAW;\n        }\n        else\n            return false;\n    }\n    else if(ffStrEqualsIgnCase(key, \"--sixel\"))\n    {\n        ffOptionParseString(key, value, &options->source);\n        options->type = FF_LOGO_TYPE_IMAGE_SIXEL;\n    }\n    else if(ffStrEqualsIgnCase(key, \"--kitty\"))\n    {\n        ffOptionParseString(key, value, &options->source);\n        options->type = FF_LOGO_TYPE_IMAGE_KITTY;\n    }\n    else if(ffStrEqualsIgnCase(key, \"--kitty-direct\"))\n    {\n        ffOptionParseString(key, value, &options->source);\n        options->type = FF_LOGO_TYPE_IMAGE_KITTY_DIRECT;\n    }\n    else if(ffStrEqualsIgnCase(key, \"--kitty-icat\"))\n    {\n        ffOptionParseString(key, value, &options->source);\n        options->type = FF_LOGO_TYPE_IMAGE_KITTY_ICAT;\n    }\n    else if(ffStrEqualsIgnCase(key, \"--iterm\"))\n    {\n        ffOptionParseString(key, value, &options->source);\n        options->type = FF_LOGO_TYPE_IMAGE_ITERM;\n    }\n    else if(ffStrEqualsIgnCase(key, \"--raw\"))\n    {\n        ffOptionParseString(key, value, &options->source);\n        options->type = FF_LOGO_TYPE_IMAGE_RAW;\n    }\n    else if((subKey = ffOptionTestPrefix(key, \"chafa\")))\n    {\n        if(subKey[0] == '\\0')\n        {\n            ffOptionParseString(key, value, &options->source);\n            options->type = FF_LOGO_TYPE_IMAGE_CHAFA;\n        }\n        else if(ffStrEqualsIgnCase(subKey, \"fg-only\"))\n            options->chafaFgOnly = ffOptionParseBoolean(value);\n        else if(ffStrEqualsIgnCase(subKey, \"symbols\"))\n            ffOptionParseString(key, value, &options->chafaSymbols);\n        else if(ffStrEqualsIgnCase(subKey, \"canvas-mode\"))\n        {\n            options->chafaCanvasMode = (uint32_t) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {\n                { \"TRUECOLOR\", 0 },\n                { \"INDEXED_256\", 1 },\n                { \"INDEXED_240\", 2 },\n                { \"INDEXED_16\", 3 },\n                { \"FGBG_BGFG\", 4 },\n                { \"FGBG\", 5 },\n                { \"INDEXED_8\", 6 },\n                { \"INDEXED_16_8\", 7 },\n                {},\n            });\n        }\n        else if(ffStrEqualsIgnCase(subKey, \"color-space\"))\n        {\n            options->chafaColorSpace = (uint32_t) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {\n                { \"RGB\", 0 },\n                { \"DIN99D\", 1 },\n                {},\n            });\n        }\n        else if(ffStrEqualsIgnCase(subKey, \"dither-mode\"))\n        {\n            options->chafaDitherMode = (uint32_t) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {\n                { \"NONE\", 0 },\n                { \"ORDERED\", 1 },\n                { \"DIFFUSION\", 2 },\n                {},\n            });\n        }\n        else\n            return false;\n    }\n    else\n        return false;\n\n    return true;\n}\n\nvoid ffOptionsDestroyLogo(FFOptionsLogo* options)\n{\n    ffStrbufDestroy(&options->source);\n    ffStrbufDestroy(&options->chafaSymbols);\n    for(uint8_t i = 0; i < (uint8_t) FASTFETCH_LOGO_MAX_COLORS; ++i)\n        ffStrbufDestroy(&options->colors[i]);\n}\n\nconst char* ffOptionsParseLogoJsonConfig(FFOptionsLogo* options, yyjson_val* root)\n{\n    yyjson_val* object = yyjson_obj_get(root, \"logo\");\n    if (!object) return NULL;\n    if (yyjson_is_null(object))\n    {\n        options->type = FF_LOGO_TYPE_NONE;\n        options->paddingTop = 0;\n        options->paddingRight = 0;\n        options->paddingLeft = 0;\n        return NULL;\n    }\n\n    if (yyjson_is_str(object))\n    {\n        ffStrbufSetJsonVal(&options->source, object);\n        return NULL;\n    }\n\n    if (!yyjson_is_obj(object)) return \"Property 'logo' must be an object\";\n\n    yyjson_val *key, *val;\n    size_t idx, max;\n    yyjson_obj_foreach(object, idx, max, key, val)\n    {\n        if (unsafe_yyjson_equals_str(key, \"type\"))\n        {\n            int value;\n            const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {\n                { \"auto\", FF_LOGO_TYPE_AUTO },\n                { \"builtin\", FF_LOGO_TYPE_BUILTIN },\n                { \"small\", FF_LOGO_TYPE_SMALL },\n                { \"file\", FF_LOGO_TYPE_FILE },\n                { \"file-raw\", FF_LOGO_TYPE_FILE_RAW },\n                { \"data\", FF_LOGO_TYPE_DATA },\n                { \"data-raw\", FF_LOGO_TYPE_DATA_RAW },\n                { \"command-raw\", FF_LOGO_TYPE_COMMAND_RAW },\n                { \"sixel\", FF_LOGO_TYPE_IMAGE_SIXEL },\n                { \"kitty\", FF_LOGO_TYPE_IMAGE_KITTY },\n                { \"kitty-direct\", FF_LOGO_TYPE_IMAGE_KITTY_DIRECT },\n                { \"kitty-icat\", FF_LOGO_TYPE_IMAGE_KITTY_ICAT },\n                { \"iterm\", FF_LOGO_TYPE_IMAGE_ITERM },\n                { \"chafa\", FF_LOGO_TYPE_IMAGE_CHAFA },\n                { \"raw\", FF_LOGO_TYPE_IMAGE_RAW },\n                { \"none\", FF_LOGO_TYPE_NONE },\n                {},\n            });\n\n            if (error) return error;\n            options->type = (FFLogoType) value;\n            continue;\n        }\n        else if (unsafe_yyjson_equals_str(key, \"source\"))\n        {\n            ffStrbufSetJsonVal(&options->source, val);\n            continue;\n        }\n        else if (unsafe_yyjson_equals_str(key, \"color\"))\n        {\n            if (!yyjson_is_obj(val))\n                return \"Property 'color' must be an object\";\n\n            yyjson_val *keyc, *valc;\n            size_t idxc, maxc;\n            yyjson_obj_foreach(val, idxc, maxc, keyc, valc)\n            {\n                uint32_t index = (uint32_t) strtoul(unsafe_yyjson_get_str(keyc), NULL, 10);\n                if (index < 1 || index > FASTFETCH_LOGO_MAX_COLORS)\n                    return \"Keys of property 'color' must be a number between 1 to 9\";\n\n                ffOptionParseColor(yyjson_get_str(valc), &options->colors[index - 1]);\n            }\n            continue;\n        }\n        else if (unsafe_yyjson_equals_str(key, \"width\"))\n        {\n            if (yyjson_is_null(val))\n                options->width = 0;\n            else\n            {\n                uint32_t value = (uint32_t) yyjson_get_uint(val);\n                if (value == 0)\n                    return \"Logo width must be a positive integer\";\n                options->width = value;\n            }\n            continue;\n        }\n        else if (unsafe_yyjson_equals_str(key, \"height\"))\n        {\n            if (yyjson_is_null(val))\n                options->height = 0;\n            else\n            {\n                uint32_t value = (uint32_t) yyjson_get_uint(val);\n                if (value == 0)\n                    return \"Logo height must be a positive integer\";\n                options->height = value;\n            }\n            continue;\n        }\n        else if (unsafe_yyjson_equals_str(key, \"padding\"))\n        {\n            if (!yyjson_is_obj(val))\n                return \"Logo padding must be an object\";\n\n            #define FF_PARSE_PADDING_POSITON(pos, paddingPos) \\\n                yyjson_val* pos = yyjson_obj_get(val, #pos); \\\n                if (pos) \\\n                { \\\n                    if (!yyjson_is_uint(pos)) \\\n                        return \"Logo padding values must be positive integers\"; \\\n                    options->paddingPos = (uint32_t) yyjson_get_uint(pos); \\\n                }\n            FF_PARSE_PADDING_POSITON(left, paddingLeft);\n            FF_PARSE_PADDING_POSITON(top, paddingTop);\n            FF_PARSE_PADDING_POSITON(right, paddingRight);\n            #undef FF_PARSE_PADDING_POSITON\n            continue;\n        }\n        else if (unsafe_yyjson_equals_str(key, \"printRemaining\"))\n        {\n            options->printRemaining = yyjson_get_bool(val);\n            continue;\n        }\n        else if (unsafe_yyjson_equals_str(key, \"preserveAspectRatio\"))\n        {\n            options->preserveAspectRatio = yyjson_get_bool(val);\n            continue;\n        }\n        else if (unsafe_yyjson_equals_str(key, \"recache\"))\n        {\n            options->recache = yyjson_get_bool(val);\n            continue;\n        }\n        else if (unsafe_yyjson_equals_str(key, \"position\"))\n        {\n            int value;\n            const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {\n                { \"left\", FF_LOGO_POSITION_LEFT },\n                { \"top\", FF_LOGO_POSITION_TOP },\n                { \"right\", FF_LOGO_POSITION_RIGHT },\n                {},\n            });\n\n            if (error) return error;\n            options->position = (FFLogoPosition) value;\n            continue;\n        }\n        else if (unsafe_yyjson_equals_str(key, \"chafa\"))\n        {\n            if (!yyjson_is_obj(val))\n                return \"Chafa config must be an object\";\n\n            yyjson_val* fgOnly = yyjson_obj_get(val, \"fgOnly\");\n            if (fgOnly)\n                options->chafaFgOnly = yyjson_get_bool(fgOnly);\n\n            yyjson_val* symbols = yyjson_obj_get(val, \"symbols\");\n            if (symbols)\n                ffStrbufSetJsonVal(&options->chafaSymbols, symbols);\n\n            yyjson_val* canvasMode = yyjson_obj_get(val, \"canvasMode\");\n            if (canvasMode)\n            {\n                int value;\n                const char* error = ffJsonConfigParseEnum(canvasMode, &value, (FFKeyValuePair[]) {\n                    { \"TRUECOLOR\", 0 },\n                    { \"INDEXED_256\", 1 },\n                    { \"INDEXED_240\", 2 },\n                    { \"INDEXED_16\", 3 },\n                    { \"FGBG_BGFG\", 4 },\n                    { \"FGBG\", 5 },\n                    { \"INDEXED_8\", 6 },\n                    { \"INDEXED_16_8\", 7 },\n                    {},\n                });\n\n                if (error) return error;\n                options->chafaCanvasMode = (uint32_t) value;\n            }\n\n            yyjson_val* colorSpace = yyjson_obj_get(val, \"colorSpace\");\n            if (colorSpace)\n            {\n                int value;\n                const char* error = ffJsonConfigParseEnum(colorSpace, &value, (FFKeyValuePair[]) {\n                    { \"RGB\", 0 },\n                    { \"DIN99D\", 1 },\n                    {},\n                });\n\n                if (error) return error;\n                options->chafaColorSpace = (uint32_t) value;\n            }\n\n            yyjson_val* ditherMode = yyjson_obj_get(val, \"ditherMode\");\n            if (ditherMode)\n            {\n                int value;\n                const char* error = ffJsonConfigParseEnum(ditherMode, &value, (FFKeyValuePair[]) {\n                    { \"NONE\", 0 },\n                    { \"ORDERED\", 1 },\n                    { \"DIFFUSION\", 2 },\n                    {},\n                });\n\n                if (error) return error;\n                options->chafaDitherMode = (uint32_t) value;\n            }\n            continue;\n        }\n        else\n            return \"Unknown logo key\";\n    }\n\n    return NULL;\n}\n\nvoid ffOptionsGenerateLogoJsonConfig(FFdata* data, FFOptionsLogo* options)\n{\n    yyjson_mut_doc* doc = data->resultDoc;\n    yyjson_mut_val* obj = yyjson_mut_obj(doc);\n\n    switch (options->type)\n    {\n        case FF_LOGO_TYPE_NONE:\n            yyjson_mut_obj_add_null(doc, doc->root, \"logo\");\n            return;\n        case FF_LOGO_TYPE_BUILTIN:\n            yyjson_mut_obj_add_str(doc, obj, \"type\", \"builtin\");\n            break;\n        case FF_LOGO_TYPE_SMALL:\n            yyjson_mut_obj_add_str(doc, obj, \"type\", \"small\");\n            break;\n        case FF_LOGO_TYPE_FILE:\n            yyjson_mut_obj_add_str(doc, obj, \"type\", \"file\");\n            break;\n        case FF_LOGO_TYPE_FILE_RAW:\n            yyjson_mut_obj_add_str(doc, obj, \"type\", \"file-raw\");\n            break;\n        case FF_LOGO_TYPE_DATA:\n            yyjson_mut_obj_add_str(doc, obj, \"type\", \"data\");\n            break;\n        case FF_LOGO_TYPE_DATA_RAW:\n            yyjson_mut_obj_add_str(doc, obj, \"type\", \"data-raw\");\n            break;\n        case FF_LOGO_TYPE_COMMAND_RAW:\n            yyjson_mut_obj_add_str(doc, obj, \"type\", \"command-raw\");\n            break;\n        case FF_LOGO_TYPE_IMAGE_SIXEL:\n            yyjson_mut_obj_add_str(doc, obj, \"type\", \"sixel\");\n            break;\n        case FF_LOGO_TYPE_IMAGE_KITTY:\n            yyjson_mut_obj_add_str(doc, obj, \"type\", \"kitty\");\n            break;\n        case FF_LOGO_TYPE_IMAGE_KITTY_DIRECT:\n            yyjson_mut_obj_add_str(doc, obj, \"type\", \"kitty-direct\");\n            break;\n        case FF_LOGO_TYPE_IMAGE_KITTY_ICAT:\n            yyjson_mut_obj_add_str(doc, obj, \"type\", \"kitty-icat\");\n            break;\n        case FF_LOGO_TYPE_IMAGE_ITERM:\n            yyjson_mut_obj_add_str(doc, obj, \"type\", \"iterm\");\n            break;\n        case FF_LOGO_TYPE_IMAGE_CHAFA:\n            yyjson_mut_obj_add_str(doc, obj, \"type\", \"chafa\");\n            break;\n        case FF_LOGO_TYPE_IMAGE_RAW:\n            yyjson_mut_obj_add_str(doc, obj, \"type\", \"raw\");\n            break;\n        default:\n            yyjson_mut_obj_add_str(doc, obj, \"type\", \"auto\");\n            break;\n    }\n\n    yyjson_mut_obj_add_str(doc, obj, \"source\", options->source.chars);\n\n    {\n        yyjson_mut_val* color = yyjson_mut_obj(doc);\n        for (int i = 0; i < FASTFETCH_LOGO_MAX_COLORS; i++)\n        {\n            char c = (char)('1' + i);\n            yyjson_mut_obj_add(color, yyjson_mut_strncpy(doc, &c, 1), yyjson_mut_strbuf(doc, &options->colors[i]));\n        }\n        yyjson_mut_obj_add_val(doc, obj, \"color\", color);\n    }\n\n    if (options->width == 0)\n        yyjson_mut_obj_add_null(doc, obj, \"width\");\n    else\n        yyjson_mut_obj_add_uint(doc, obj, \"width\", options->width);\n\n    if (options->height == 0)\n        yyjson_mut_obj_add_null(doc, obj, \"height\");\n    else\n        yyjson_mut_obj_add_uint(doc, obj, \"height\", options->height);\n\n    {\n        yyjson_mut_val* padding = yyjson_mut_obj_add_obj(doc, obj, \"padding\");\n        yyjson_mut_obj_add_uint(doc, padding, \"top\", options->paddingTop);\n        yyjson_mut_obj_add_uint(doc, padding, \"left\", options->paddingLeft);\n        yyjson_mut_obj_add_uint(doc, padding, \"right\", options->paddingRight);\n    }\n\n    yyjson_mut_obj_add_bool(doc, obj, \"printRemaining\", options->printRemaining);\n\n    yyjson_mut_obj_add_bool(doc, obj, \"preserveAspectRatio\", options->preserveAspectRatio);\n\n    yyjson_mut_obj_add_bool(doc, obj, \"recache\", options->recache);\n\n    yyjson_mut_obj_add_str(doc, obj, \"position\", ((const char* []) {\n        \"left\",\n        \"top\",\n        \"right\",\n    })[options->position]);\n\n    {\n        yyjson_mut_val* chafa = yyjson_mut_obj(doc);\n        yyjson_mut_obj_add_bool(doc, chafa, \"fgOnly\", options->chafaFgOnly);\n        yyjson_mut_obj_add_strbuf(doc, chafa, \"symbols\", &options->chafaSymbols);\n        if (options->chafaCanvasMode <= 7)\n        {\n            yyjson_mut_obj_add_str(doc, chafa, \"canvasMode\", ((const char* []) {\n                \"TRUECOLOR\",\n                \"INDEXED_256\",\n                \"INDEXED_240\",\n                \"INDEXED_16\",\n                \"FGBG_BGFG\",\n                \"FGBG\",\n                \"INDEXED_8\",\n                \"INDEXED_16_8\",\n            })[options->chafaCanvasMode]);\n        }\n        if (options->chafaColorSpace <= 1)\n        {\n            yyjson_mut_obj_add_str(doc, chafa, \"colorSpace\", ((const char* []) {\n                \"RGB\",\n                \"DIN99D\",\n            })[options->chafaColorSpace]);\n        }\n        if (options->chafaDitherMode <= 2)\n        {\n            yyjson_mut_obj_add_str(doc, chafa, \"ditherMode\", ((const char* []) {\n                \"NONE\",\n                \"ORDERED\",\n                \"DIFFUSION\",\n            })[options->chafaDitherMode]);\n        }\n\n        yyjson_mut_obj_add_val(doc, obj, \"chafa\", chafa);\n    }\n\n    yyjson_mut_obj_add_val(doc, doc->root, \"logo\", obj);\n}\n"
  },
  {
    "path": "src/options/logo.h",
    "content": "#pragma once\n\n#include \"common/ffdata.h\"\n\n#define FASTFETCH_LOGO_MAX_NAMES 9\n#define FASTFETCH_LOGO_MAX_COLORS 9 // two digits would make parsing much more complicated (index 1 - 9)\n\ntypedef enum __attribute__((__packed__)) FFLogoType\n{\n    FF_LOGO_TYPE_AUTO,               // if something is given, first try builtin, then file. Otherwise detect logo\n    FF_LOGO_TYPE_BUILTIN,            // builtin ascii art\n    FF_LOGO_TYPE_SMALL,              // builtin ascii art, small version\n    FF_LOGO_TYPE_FILE,               // text file, printed with color code replacement\n    FF_LOGO_TYPE_FILE_RAW,           // text file, printed as is\n    FF_LOGO_TYPE_DATA,               // text data, printed with color code replacement\n    FF_LOGO_TYPE_DATA_RAW,           // text data, printed as is\n    FF_LOGO_TYPE_COMMAND_RAW,        // command to generate text data, printed as is\n    FF_LOGO_TYPE_IMAGE_SIXEL,        // image file, printed as sixel codes\n    FF_LOGO_TYPE_IMAGE_KITTY,        // image file, printed as kitty graphics protocol\n    FF_LOGO_TYPE_IMAGE_KITTY_DIRECT, // image file, tell the terminal emulator to read image data from the specified file (Supported by kitty and wezterm)\n    FF_LOGO_TYPE_IMAGE_KITTY_ICAT,   // image file, use `kitten icat` to display the image. Requires binary `kitten` to be installed\"\n    FF_LOGO_TYPE_IMAGE_ITERM,        // image file, printed as iterm graphics protocol\n    FF_LOGO_TYPE_IMAGE_CHAFA,        // image file, printed as ascii art using libchafa\n    FF_LOGO_TYPE_IMAGE_RAW,          // image file, printed as raw binary string\n    FF_LOGO_TYPE_NONE,               // `--logo none`, but still applies colors to the system information output (unless `--pipe` is set)\n} FFLogoType;\n\ntypedef enum __attribute__((__packed__)) FFLogoPosition\n{\n    FF_LOGO_POSITION_LEFT,\n    FF_LOGO_POSITION_TOP,\n    FF_LOGO_POSITION_RIGHT,\n} FFLogoPosition;\n\ntypedef struct FFOptionsLogo\n{\n    FFstrbuf source;\n    FFLogoType type;\n    FFLogoPosition position;\n    FFstrbuf colors[FASTFETCH_LOGO_MAX_COLORS];\n    uint32_t width;\n    uint32_t height;\n    uint32_t paddingTop;\n    uint32_t paddingLeft;\n    uint32_t paddingRight;\n    bool printRemaining;\n    bool preserveAspectRatio;\n    bool recache;\n\n    bool chafaFgOnly;\n    FFstrbuf chafaSymbols;\n    uint32_t chafaCanvasMode;\n    uint32_t chafaColorSpace;\n    uint32_t chafaDitherMode;\n} FFOptionsLogo;\n\nvoid ffOptionsInitLogo(FFOptionsLogo* options);\nbool ffOptionsParseLogoCommandLine(FFOptionsLogo* options, const char* key, const char* value);\nvoid ffOptionsDestroyLogo(FFOptionsLogo* options);\nconst char* ffOptionsParseLogoJsonConfig(FFOptionsLogo* options, yyjson_val* root);\nvoid ffOptionsGenerateLogoJsonConfig(FFdata* data, FFOptionsLogo* options);\n"
  },
  {
    "path": "tests/color.c",
    "content": "#include \"common/format.h\"\n#include \"common/textModifier.h\"\n#include \"fastfetch.h\"\n\n#include <stdlib.h>\n\nstatic void verify(const char* color, const char* expected, int lineNo)\n{\n    FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();\n    ffOptionParseColorNoClear(color, &result);\n    if (!ffStrbufEqualS(&result, expected))\n    {\n        fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR \"[%d] %s: expected \\\"%s\\\", got \\\"%s\\\"\\n\" FASTFETCH_TEXT_MODIFIER_RESET, lineNo, color, expected, result.chars);\n        exit(1);\n    }\n}\n\n#define VERIFY(color, expected) verify((color), (expected), __LINE__)\n\nint main(void)\n{\n    instance.config.display.pipe = true;\n    // Initialize dummy config colors for property tests\n    ffStrbufInitS(&instance.config.display.colorKeys, \"94\"); // light_blue\n    ffStrbufInitS(&instance.config.display.colorTitle, \"95\"); // light_magenta\n\n    {\n        VERIFY(\"\", \"\");\n        VERIFY(\"1\", \"1\");\n\n        VERIFY(\"red\", \"31\");\n        VERIFY(\"light_green\", \"92\");\n        VERIFY(\"default\", \"39\");\n        VERIFY(\"blue\", \"34\");\n        VERIFY(\"light_cyan\", \"96\");\n\n        VERIFY(\"bold_red\", \"1;31\");\n        VERIFY(\"dim_light_yellow\", \"2;93\");\n        VERIFY(\"italic_underline_green\", \"3;4;32\");\n        VERIFY(\"reset_blue\", \"0;34\"); // Reset followed by color\n\n        VERIFY(\"#ff0000\", \"38;2;255;0;0\");  // RRGGBB\n        VERIFY(\"#0f0\", \"38;2;0;255;0\");     // RGB\n        VERIFY(\"#123456\", \"38;2;18;52;86\");\n        VERIFY(\"#abc\", \"38;2;170;187;204\");\n\n        // xterm 256 color codes\n        VERIFY(\"@0\", \"38;5;0\");\n        VERIFY(\"@1\", \"38;5;1\");\n        VERIFY(\"@255\", \"38;5;255\");\n\n        VERIFY(\"bold_#ff00ff\", \"1;38;2;255;0;255\");\n        VERIFY(\"underline_#123\", \"4;38;2;17;34;51\");\n\n        VERIFY(\"\\e[32m\", \"32\"); // Direct ANSI code\n        VERIFY(\"\\e[1;94m\", \"1;94\"); // Direct ANSI code with mode\n\n        // Property colors (ensure dummy config colors are set)\n        VERIFY(\"keys\", \"94\");\n        VERIFY(\"title\", \"95\");\n        VERIFY(\"bold_keys\", \"1;94\");\n    }\n\n    // Clean up dummy config colors\n    ffStrbufDestroy(&instance.config.display.colorKeys);\n    ffStrbufDestroy(&instance.config.display.colorTitle);\n\n    //Success\n    puts(\"\\033[32mAll tests passed!\" FASTFETCH_TEXT_MODIFIER_RESET);\n}\n"
  },
  {
    "path": "tests/duration.c",
    "content": "#include \"common/duration.h\"\n#include \"common/textModifier.h\"\n#include \"fastfetch.h\"\n\n#include <stdlib.h>\n\nstatic void verify(uint64_t totalSeconds, const char* expected, int lineNo)\n{\n    FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();\n    ffDurationAppendNum(totalSeconds, &result);\n    if (!ffStrbufEqualS(&result, expected))\n    {\n        fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR \"[%d] %llu: expected \\\"%s\\\", got \\\"%s\\\"\\n\" FASTFETCH_TEXT_MODIFIER_RESET, lineNo, (unsigned long long) totalSeconds, expected, result.chars);\n        exit(1);\n    }\n}\n\n#define VERIFY(color, expected) verify((color), (expected), __LINE__)\n\nint main(void)\n{\n    // Test seconds less than 60\n    VERIFY(0, \"0 seconds\");\n    VERIFY(1, \"1 second\");\n    VERIFY(2, \"2 seconds\");\n    VERIFY(59, \"59 seconds\");\n\n    // Test minute rounding (when seconds >= 30)\n    VERIFY(60, \"1 min\");\n    VERIFY(60 + 29, \"1 min\");\n    VERIFY(60 + 30, \"2 mins\");\n\n    // Test only minutes\n    VERIFY(60 * 2 - 1, \"2 mins\");\n    VERIFY(60 * 2, \"2 mins\");\n    VERIFY(60 * 59 + 29, \"59 mins\");\n\n    // Test only hours (no minutes)\n    VERIFY(60 * 59 + 30, \"1 hour\");\n    VERIFY(60 * 60, \"1 hour\");\n    VERIFY(2 * 60 * 60, \"2 hours\");\n    VERIFY(23 * 60 * 60, \"23 hours\");\n\n    // Test combination of hours and minutes\n    VERIFY(60 * 60 + 60, \"1 hour, 1 min\");\n    VERIFY(60 * 60 + 60 * 2, \"1 hour, 2 mins\");\n    VERIFY(60 * 60 * 2 + 60 + 29, \"2 hours, 1 min\");\n    VERIFY(60 * 60 * 2 + 60 + 30, \"2 hours, 2 mins\");\n\n    // Test days\n    VERIFY(60 * 60 * 24, \"1 day\");\n    VERIFY(60 * 60 * 24 - 1, \"1 day\");\n    VERIFY(60 * 60 * 24 * 2, \"2 days\");\n\n    // Test combination of days and hours\n    VERIFY(60 * 60 * 24 + 60 * 60, \"1 day, 1 hour\");\n    VERIFY(60 * 60 * 24 * 2 + 60 * 60, \"2 days, 1 hour\");\n    VERIFY(60 * 60 * 24 * 2 + 60 * 60 * 2, \"2 days, 2 hours\");\n\n    // Test combination of days, hours, and minutes\n    VERIFY(60 * 60 * 24 + 60 * 60 + 60, \"1 day, 1 hour, 1 min\");\n    VERIFY(60 * 60 * 24 * 2 + 60 * 60 + 60 * 2, \"2 days, 1 hour, 2 mins\");\n    VERIFY(60 * 60 * 24 * 2 + 60 * 2, \"2 days, 2 mins\");\n\n    // Test very large number of days\n    VERIFY(60 * 60 * 24 * 100, \"100 days(!)\");\n    VERIFY(60 * 60 * 24 * 200, \"200 days(!)\");\n\n    instance.config.display.durationAbbreviation = true;\n    instance.config.display.durationSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_NEVER;\n    // Test seconds less than 60\n    VERIFY(0, \"0secs\");\n    VERIFY(1, \"1sec\");\n    VERIFY(2, \"2secs\");\n    VERIFY(59, \"59secs\");\n\n    // Test minute rounding (when seconds >= 30)\n    VERIFY(60, \"1m\");\n    VERIFY(60 + 29, \"1m\");\n    VERIFY(60 + 30, \"2m\");\n\n    // Test only minutes\n    VERIFY(60 * 2 - 1, \"2m\");\n    VERIFY(60 * 2, \"2m\");\n    VERIFY(60 * 59 + 29, \"59m\");\n\n    // Test only hours (no minutes)\n    VERIFY(60 * 59 + 30, \"1h\");\n    VERIFY(60 * 60, \"1h\");\n    VERIFY(2 * 60 * 60, \"2h\");\n    VERIFY(23 * 60 * 60, \"23h\");\n\n    // Test combination of hours and minutes\n    VERIFY(60 * 60 + 60, \"1h 1m\");\n    VERIFY(60 * 60 + 60 * 2, \"1h 2m\");\n    VERIFY(60 * 60 * 2 + 60 + 29, \"2h 1m\");\n    VERIFY(60 * 60 * 2 + 60 + 30, \"2h 2m\");\n\n    // Test days\n    VERIFY(60 * 60 * 24, \"1d\");\n    VERIFY(60 * 60 * 24 - 1, \"1d\");\n    VERIFY(60 * 60 * 24 * 2, \"2d\");\n\n    // Test combination of days and hours\n    VERIFY(60 * 60 * 24 + 60 * 60, \"1d 1h\");\n    VERIFY(60 * 60 * 24 * 2 + 60 * 60, \"2d 1h\");\n    VERIFY(60 * 60 * 24 * 2 + 60 * 60 * 2, \"2d 2h\");\n\n    // Test combination of days, hours, and minutes\n    VERIFY(60 * 60 * 24 + 60 * 60 + 60, \"1d 1h 1m\");\n    VERIFY(60 * 60 * 24 * 2 + 60 * 60 + 60 * 2, \"2d 1h 2m\");\n    VERIFY(60 * 60 * 24 * 2 + 60 * 2, \"2d 2m\");\n\n    // Test very large number of days\n    VERIFY(60 * 60 * 24 * 100, \"100d\");\n    VERIFY(60 * 60 * 24 * 200, \"200d\");\n\n    //Success\n    puts(\"\\033[32mAll tests passed!\" FASTFETCH_TEXT_MODIFIER_RESET);\n}\n"
  },
  {
    "path": "tests/format.c",
    "content": "#include \"common/format.h\"\n#include \"common/textModifier.h\"\n#include \"fastfetch.h\"\n\n#include <stdlib.h>\n\nstatic void verify(const char* format, const char* arg, const char* expected, int lineNo)\n{\n    FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();\n    FF_STRBUF_AUTO_DESTROY formatter = ffStrbufCreateStatic(format);\n    const FFformatarg arguments[] = {\n        { .type = FF_ARG_TYPE_STRING, arg }\n    };\n    ffParseFormatString(&result, &formatter, 1, arguments);\n    if (!ffStrbufEqualS(&result, expected))\n    {\n        fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR \"[%d] %s: expected \\\"%s\\\", got \\\"%s\\\"\\n\" FASTFETCH_TEXT_MODIFIER_RESET, lineNo, format, expected, result.chars);\n        exit(1);\n    }\n}\n\n#define VERIFY(format, argument, expected) verify((format), (argument), (expected), __LINE__)\n\nint main(void)\n{\n    instance.config.display.pipe = true;\n\n    {\n    VERIFY(\"output({})\", \"12345 67890\", \"output(12345 67890)\");\n    VERIFY(\"output({1})\", \"12345 67890\", \"output(12345 67890)\");\n    VERIFY(\"output({})\", \"\", \"output()\");\n    VERIFY(\"output({1})\", \"\", \"output()\");\n    }\n\n    {\n    VERIFY(\"output({1:20})\", \"12345 67890\", \"output(12345 67890)\");\n    VERIFY(\"output({1:11})\", \"12345 67890\", \"output(12345 67890)\");\n    VERIFY(\"output({1:-11})\", \"12345 67890\", \"output(12345 67890)\");\n    VERIFY(\"output({1:6})\", \"12345 67890\", \"output(12345)\");\n    VERIFY(\"output({:6})\", \"12345 67890\", \"output(12345)\");\n    VERIFY(\"output({:-6})\", \"12345 67890\", \"output(12345…)\");\n    VERIFY(\"output({:0})\", \"12345 67890\", \"output()\");\n    VERIFY(\"output({:})\", \"12345 67890\", \"output()\");\n    }\n\n    {\n    VERIFY(\"output({1<20})\", \"12345 67890\", \"output(12345 67890         )\");\n    VERIFY(\"output({1<-20})\", \"12345 67890\", \"output(12345 67890         )\");\n    VERIFY(\"output({1<11})\", \"12345 67890\", \"output(12345 67890)\");\n    VERIFY(\"output({1<-11})\", \"12345 67890\", \"output(12345 67890)\");\n    VERIFY(\"output({1<6})\", \"12345 67890\", \"output(12345 )\");\n    VERIFY(\"output({<6})\", \"12345 67890\", \"output(12345 )\");\n    VERIFY(\"output({<-6})\", \"12345 67890\", \"output(12345…)\");\n    VERIFY(\"output({<0})\", \"12345 67890\", \"output()\");\n    VERIFY(\"output({<})\", \"12345 67890\", \"output()\");\n    }\n\n    {\n    VERIFY(\"output({1>20})\", \"12345 67890\", \"output(         12345 67890)\");\n    VERIFY(\"output({1>-20})\", \"12345 67890\", \"output(         12345 67890)\");\n    VERIFY(\"output({1>11})\", \"12345 67890\", \"output(12345 67890)\");\n    VERIFY(\"output({1>-11})\", \"12345 67890\", \"output(12345 67890)\");\n    VERIFY(\"output({1>6})\", \"12345 67890\", \"output(12345 )\");\n    VERIFY(\"output({>6})\", \"12345 67890\", \"output(12345 )\");\n    VERIFY(\"output({>-6})\", \"12345 67890\", \"output(12345…)\");\n    VERIFY(\"output({>0})\", \"12345 67890\", \"output()\");\n    VERIFY(\"output({>})\", \"12345 67890\", \"output()\");\n    }\n\n    {\n    VERIFY(\"output({1n>20})\", \"12345 67890\", \"output({1n>20})\");\n    VERIFY(\"output({120})\", \"12345 67890\", \"output({120})\");\n    VERIFY(\"output({1:11})\", \"\", \"output()\");\n    VERIFY(\"output({2:2})\", \"\", \"output({2:2})\");\n    }\n\n    {\n    VERIFY(\"output({1~0})\", \"12345 67890\", \"output(12345 67890)\");\n    VERIFY(\"output({1~1})\", \"12345 67890\", \"output(2345 67890)\");\n    VERIFY(\"output({1~10})\", \"12345 67890\", \"output(0)\");\n    VERIFY(\"output({1~20})\", \"12345 67890\", \"output()\");\n    VERIFY(\"output({1~-5})\", \"12345 67890\", \"output(67890)\");\n    VERIFY(\"output({1~-50})\", \"12345 67890\", \"output()\");\n    VERIFY(\"output({1~0,1})\", \"12345 67890\", \"output(1)\");\n    VERIFY(\"output({1~0,6})\", \"12345 67890\", \"output(12345 )\");\n    VERIFY(\"output({1~0,10})\", \"12345 67890\", \"output(12345 6789)\");\n    VERIFY(\"output({1~5,10})\", \"12345 67890\", \"output( 6789)\");\n    VERIFY(\"output({1~5,100})\", \"12345 67890\", \"output( 67890)\");\n    VERIFY(\"output({1~10,10})\", \"12345 67890\", \"output()\");\n    VERIFY(\"output({1~10,5})\", \"12345 67890\", \"output()\");\n    VERIFY(\"output({1~3,-3})\", \"12345 67890\", \"output(45 67)\");\n    VERIFY(\"output({1~-5,-3})\", \"12345 67890\", \"output(67)\");\n    VERIFY(\"output({1~-0,10})\", \"12345 67890\", \"output(12345 6789)\");\n    VERIFY(\"output({1~-3,-5})\", \"12345 67890\", \"output()\");\n    VERIFY(\"output({1~})\", \"12345 67890\", \"output(12345 67890)\"); // Same as {1~0}\n    VERIFY(\"output({1~-0})\", \"12345 67890\", \"output(12345 67890)\"); // Same as {1~0}\n    VERIFY(\"output({1~,-1})\", \"12345 67890\", \"output(12345 6789)\"); // Same as {1~0,-1}\n    VERIFY(\"output({1~,})\", \"12345 67890\", \"output()\"); // Same as {1~0,0}\n    }\n\n    {\n    VERIFY(\"output({1n~0})\", \"12345 67890\", \"output({1n~0})\");\n    VERIFY(\"output({1~<0})\", \"12345 67890\", \"output({1~<0})\");\n    VERIFY(\"output({1~-})\", \"12345 67890\", \"output({1~-})\");\n    VERIFY(\"output({1~-,1})\", \"12345 67890\", \"output({1~-,1})\");\n    VERIFY(\"output({1~0,,1})\", \"12345 67890\", \"output({1~0,,1})\");\n    VERIFY(\"output({1~0,1,})\", \"12345 67890\", \"output({1~0,1,})\");\n    VERIFY(\"output({1~,0,1})\", \"12345 67890\", \"output({1~,0,1})\");\n    VERIFY(\"output({2,0})\", \"12345 67890\", \"output({2,0})\");\n    }\n\n    {\n    VERIFY(\"output({1:20}{1<20}{1>20})\", \"12345 67890\", \"output(12345 6789012345 67890                  12345 67890)\");\n    VERIFY(\"output({?1}OK{?}{/1}NOT OK{/})\", \"12345 67890\", \"output(OK)\");\n    VERIFY(\"output({?1}OK{?}{/1}NOT OK{/})\", \"\", \"output(NOT OK)\");\n    }\n\n    #ifndef _WIN32 // Windows doesn't have setenv\n    {\n        ffListInit(&instance.config.display.constants, sizeof(FFstrbuf));\n        ffStrbufInitStatic(ffListAdd(&instance.config.display.constants), \"CONST1\");\n        ffStrbufInitStatic(ffListAdd(&instance.config.display.constants), \"CONST2\");\n        setenv(\"FF_TEST\", \"ENVVAR\", 1);\n        VERIFY(\"output({$FF_TEST})\", \"\", \"output(ENVVAR)\");\n        VERIFY(\"output({$1})\", \"\", \"output(CONST1)\");\n        VERIFY(\"output({$FF_TEST}{$1})\", \"\", \"output(ENVVARCONST1)\");\n        VERIFY(\"output({$1}{$FF_TEST})\", \"\", \"output(CONST1ENVVAR)\");\n        VERIFY(\"output({$FF_TEST}{$FF_TEST})\", \"\", \"output(ENVVARENVVAR)\");\n        VERIFY(\"output({$1}{$-1})\", \"\", \"output(CONST1CONST2)\");\n\n        VERIFY(\"output({$FF_INVAL})\", \"\", \"output({$FF_INVAL})\");\n        VERIFY(\"output({$9}{$0}${-9})\", \"\", \"output({$9}{$0}${-9})\");\n        VERIFY(\"output({$1NO})\", \"\", \"output({$1NO})\");\n        ffListDestroy(&instance.config.display.constants);\n    }\n    #endif\n\n    //Success\n    puts(\"\\033[32mAll tests passed!\" FASTFETCH_TEXT_MODIFIER_RESET);\n}\n"
  },
  {
    "path": "tests/list.c",
    "content": "#include \"common/FFlist.h\"\n#include \"common/textModifier.h\"\n\n#include <string.h>\n#include <stdarg.h>\n#include <stdlib.h>\n#include <stdio.h>\n\n__attribute__((__noreturn__))\nstatic void testFailed(const FFlist* list, const char* expression, int lineNo)\n{\n    fputs(FASTFETCH_TEXT_MODIFIER_ERROR, stderr);\n    fprintf(stderr, \"[%d] %s, list:\", lineNo, expression);\n    for (uint32_t i = 0; i < list->length; ++i)\n    {\n        fprintf(stderr, \"%u \", *(uint32_t*)ffListGet(list, i));\n    }\n    fputc('\\n', stderr);\n    fputs(FASTFETCH_TEXT_MODIFIER_RESET, stderr);\n    fputc('\\n', stderr);\n    exit(1);\n}\n\nstatic bool numEqualsAdapter(const void* first, const void* second)\n{\n    return *(uint32_t*)first == *(uint32_t*)second;\n}\n\n#define VERIFY(expression) if(!(expression)) testFailed(&list, #expression, __LINE__)\n\nint main(void)\n{\n    FFlist list;\n    uint32_t n = 0;\n\n    //initA\n\n    ffListInit(&list, sizeof(uint32_t));\n\n    VERIFY(list.elementSize == sizeof(uint32_t));\n    VERIFY(list.capacity == 0);\n    VERIFY(list.length == 0);\n\n    //forEach\n    FF_LIST_FOR_EACH(uint32_t, item, list)\n        VERIFY(false);\n\n    //shift\n    VERIFY(!ffListShift(&list, &n));\n    VERIFY(list.length == 0);\n\n    //pop\n    VERIFY(!ffListPop(&list, &n));\n    VERIFY(list.length == 0);\n\n    //add\n    for (uint32_t i = 1; i <= FF_LIST_DEFAULT_ALLOC + 1; ++i)\n    {\n        *(uint32_t*)ffListAdd(&list) = i;\n\n        VERIFY(list.elementSize == sizeof(uint32_t));\n        VERIFY(list.length == i);\n\n        if(i <= FF_LIST_DEFAULT_ALLOC)\n        {\n            VERIFY(list.capacity == FF_LIST_DEFAULT_ALLOC);\n        }\n        else\n        {\n            VERIFY(list.capacity == FF_LIST_DEFAULT_ALLOC * 2);\n        }\n\n        VERIFY(*(uint32_t*)ffListGet(&list, 0) == 1);\n        VERIFY(*(uint32_t*)ffListGet(&list, i - 1) == i);\n    }\n    VERIFY(list.length == FF_LIST_DEFAULT_ALLOC + 1);\n\n    uint32_t sum = 0;\n    //forEach\n    FF_LIST_FOR_EACH(uint32_t, item, list)\n        sum += *item;\n    VERIFY(sum == (1 + FF_LIST_DEFAULT_ALLOC + 1) * (FF_LIST_DEFAULT_ALLOC + 1) / 2);\n\n    // ffListFirstIndexComp\n    n = 10;\n    VERIFY(ffListFirstIndexComp(&list, &n, numEqualsAdapter) == 9);\n    n = 999;\n    VERIFY(ffListFirstIndexComp(&list, &n, numEqualsAdapter) == list.length);\n\n    // ffListContains\n    n = 10;\n    VERIFY(ffListContains(&list, &n, numEqualsAdapter));\n    n = 999;\n    VERIFY(!ffListContains(&list, &n, numEqualsAdapter));\n\n    //shift\n    VERIFY(ffListShift(&list, &n));\n    VERIFY(n == 1);\n    VERIFY(list.length == FF_LIST_DEFAULT_ALLOC);\n    VERIFY(*(uint32_t*) ffListGet(&list, 0) == 2);\n    VERIFY(*(uint32_t*) ffListGet(&list, list.length - 1) == FF_LIST_DEFAULT_ALLOC + 1);\n\n    //pop\n    VERIFY(ffListPop(&list, &n));\n    VERIFY(n == FF_LIST_DEFAULT_ALLOC + 1);\n    VERIFY(list.length == FF_LIST_DEFAULT_ALLOC - 1);\n    VERIFY(*(uint32_t*) ffListGet(&list, 0) == 2);\n    VERIFY(*(uint32_t*) ffListGet(&list, list.length - 1) == FF_LIST_DEFAULT_ALLOC);\n\n    //Destroy\n    ffListDestroy(&list);\n\n    VERIFY(list.elementSize == sizeof(uint32_t));\n    VERIFY(list.capacity == 0);\n    VERIFY(list.length == 0);\n\n    {\n        FF_LIST_AUTO_DESTROY test = ffListCreate(1);\n        VERIFY(test.elementSize == 1);\n        VERIFY(test.capacity == 0);\n        VERIFY(test.length == 0);\n    }\n\n    //Success\n    puts(\"\\033[32mAll tests passed!\"FASTFETCH_TEXT_MODIFIER_RESET);\n}\n"
  },
  {
    "path": "tests/strbuf.c",
    "content": "#include \"common/FFstrbuf.h\"\n#include \"common/textModifier.h\"\n\n#include <string.h>\n#include <stdlib.h>\n#include <stdio.h>\n\n__attribute__((__noreturn__))\nstatic void testFailed(const FFstrbuf* strbuf, const char* expression, int lineNo)\n{\n    fputs(FASTFETCH_TEXT_MODIFIER_ERROR, stderr);\n    fprintf(stderr, \"[%d] %s, strbuf:\", lineNo, expression);\n    ffStrbufWriteTo(strbuf, stderr);\n    fputs(FASTFETCH_TEXT_MODIFIER_RESET, stderr);\n    fputc('\\n', stderr);\n    exit(1);\n}\n\n#define VERIFY(expression) if(!(expression)) testFailed(&strbuf, #expression, __LINE__)\n\nint shouldNotBeCalled(int c) {\n    (void)c;\n    exit(1);\n}\n\nint main(void)\n{\n    FFstrbuf strbuf;\n\n    //destroy 0\n    ffStrbufInit(&strbuf);\n    ffStrbufDestroy(&strbuf);\n\n    //initA\n\n    ffStrbufInit(&strbuf);\n\n    VERIFY(strbuf.chars[0] == 0);\n    VERIFY(strbuf.allocated == 0);\n    VERIFY(strbuf.length == 0);\n\n    VERIFY(!ffStrbufStartsWithC(&strbuf, '0'));\n    VERIFY(!ffStrbufEndsWithC(&strbuf, '0'));\n    VERIFY(!ffStrbufStartsWithS(&strbuf, \"0\"));\n    VERIFY(!ffStrbufEndsWithS(&strbuf, \"0\"));\n    VERIFY(ffStrbufCountC(&strbuf, '0') == 0);\n\n    //Ensure following functions work with non-allocated string\n    ffStrbufAppendS(&strbuf, \"\");\n    ffStrbufAppendF(&strbuf, \"%s\", \"\");\n    ffStrbufAppendTransformS(&strbuf, \"\", shouldNotBeCalled);\n    ffStrbufPrependS(&strbuf, \"\");\n    ffStrbufTrim(&strbuf, ' ');\n\n    VERIFY(strbuf.chars[0] == 0);\n    VERIFY(strbuf.allocated == 0);\n    VERIFY(strbuf.length == 0);\n\n    //append(N)C\n\n    ffStrbufAppendC(&strbuf, '1');\n    VERIFY(ffStrbufEqualS(&strbuf, \"1\"));\n    VERIFY(strbuf.allocated >= 1);\n    ffStrbufAppendNC(&strbuf, 5, '2');\n    VERIFY(ffStrbufEqualS(&strbuf, \"122222\"));\n    VERIFY(strbuf.allocated >= 6);\n    ffStrbufClear(&strbuf);\n\n    //appendS\n\n    ffStrbufAppendS(&strbuf, \"12345\");\n    ffStrbufAppendS(&strbuf, NULL);\n\n    VERIFY(strbuf.length == 5);\n    VERIFY(strbuf.allocated >= 6);\n    VERIFY(ffStrbufEqualS(&strbuf, \"12345\"));\n\n    //appendNS\n\n    ffStrbufAppendNS(&strbuf, 4, \"67890\");\n    ffStrbufAppendNS(&strbuf, 0, NULL);\n\n    VERIFY(strbuf.length == 9);\n    VERIFY(strbuf.allocated >= 10);\n    VERIFY(ffStrbufEqualS(&strbuf, \"123456789\"));\n\n    //appendS long\n\n    ffStrbufAppendS(&strbuf, \"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\");\n    VERIFY(strbuf.length == 109);\n    VERIFY(strbuf.allocated >= 110);\n    VERIFY(strbuf.chars[strbuf.length] == 0);\n\n    //substr\n\n    VERIFY(ffStrbufSubstrBefore(&strbuf, 9));\n    VERIFY(strbuf.length == 9);\n    VERIFY(strbuf.allocated >= 110);\n    VERIFY(strbuf.chars[strbuf.length] == 0);\n    VERIFY(ffStrbufEqualS(&strbuf, \"123456789\"));\n\n    VERIFY(!ffStrbufSubstrBeforeFirstC(&strbuf, '0'));\n    VERIFY(!ffStrbufSubstrBeforeLastC(&strbuf, '0'));\n    VERIFY(!ffStrbufSubstrAfterFirstC(&strbuf, '0'));\n    VERIFY(!ffStrbufSubstrAfterLastC(&strbuf, '0'));\n    VERIFY(ffStrbufEqualS(&strbuf, \"123456789\"));\n\n    VERIFY(ffStrbufSubstrBeforeFirstC(&strbuf, '9'));\n    VERIFY(ffStrbufSubstrBeforeLastC(&strbuf, '8'));\n    VERIFY(ffStrbufSubstrAfterFirstC(&strbuf, '1'));\n    VERIFY(ffStrbufSubstrAfterLastC(&strbuf, '2'));\n    VERIFY(ffStrbufEqualS(&strbuf, \"34567\"));\n\n    ffStrbufSetS(&strbuf, \"123456789\");\n\n    //startsWithC\n\n    VERIFY(ffStrbufStartsWithC(&strbuf, '1'));\n    VERIFY(!ffStrbufStartsWithC(&strbuf, '2'));\n\n    //startsWithS\n\n    VERIFY(ffStrbufStartsWithS(&strbuf, \"123\"));\n    VERIFY(ffStrbufStartsWithS(&strbuf, \"123456789\"));\n    VERIFY(!ffStrbufStartsWithS(&strbuf, \"1234567890123\"));\n\n    //endsWithC\n    VERIFY(ffStrbufEndsWithC(&strbuf, '9'));\n    VERIFY(!ffStrbufEndsWithC(&strbuf, '1'));\n\n    //endsWithS\n\n    VERIFY(ffStrbufEndsWithS(&strbuf, \"789\"));\n    VERIFY(ffStrbufEndsWithS(&strbuf, \"123456789\"));\n    VERIFY(!ffStrbufEndsWithS(&strbuf, \"1234567890123\"));\n\n    //toNumber\n\n    VERIFY(ffStrbufToDouble(&strbuf, -DBL_MAX) == 123456789.0);\n    VERIFY(ffStrbufToUInt(&strbuf, 999) == 123456789);\n\n    //countC\n\n    VERIFY(ffStrbufCountC(&strbuf, '1') == 1);\n    VERIFY(ffStrbufCountC(&strbuf, '0') == 0);\n\n    //removeS\n\n    ffStrbufRemoveS(&strbuf, \"78\");\n\n    VERIFY(strbuf.length == 7);\n    VERIFY(strcmp(strbuf.chars, \"1234569\") == 0);\n\n    //removeStrings\n\n    ffStrbufRemoveStrings(&strbuf, 3, (const char*[]) { \"23\", \"45\", \"9\" });\n\n    VERIFY(strbuf.length == 2);\n    VERIFY(strcmp(strbuf.chars, \"16\") == 0);\n\n    //PrependS\n\n    ffStrbufPrependS(&strbuf, \"123\");\n    ffStrbufPrependS(&strbuf, NULL);\n\n    VERIFY(strbuf.length == 5);\n    VERIFY(strcmp(strbuf.chars, \"12316\") == 0);\n\n    //indexC\n    VERIFY(ffStrbufFirstIndexC(&strbuf, '1') == 0);\n    VERIFY(ffStrbufNextIndexC(&strbuf, 1, '1') == 3);\n    VERIFY(ffStrbufNextIndexC(&strbuf, 4, '1') == 5);\n    VERIFY(ffStrbufLastIndexC(&strbuf, '1') == 3);\n    VERIFY(ffStrbufPreviousIndexC(&strbuf, 2, '1') == 0);\n    VERIFY(ffStrbufPreviousIndexC(&strbuf, 0, '1') == 0);\n    VERIFY(ffStrbufPreviousIndexC(&strbuf, 0, '2') == 5);\n\n    //indexS\n    VERIFY(ffStrbufFirstIndexS(&strbuf, \"12316\") == 0);\n    VERIFY(ffStrbufNextIndexS(&strbuf, 1, \"1\") == 3);\n    VERIFY(ffStrbufNextIndexS(&strbuf, 4, \"1\") == 5);\n\n    //ignCase\n    ffStrbufSetS(&strbuf, \"AbCdEfG\");\n\n    VERIFY(ffStrbufIgnCaseCompS(&strbuf, \"aBcDeFg\") == 0);\n    VERIFY(ffStrbufStartsWithIgnCaseS(&strbuf, \"ABCD\"));\n    VERIFY(ffStrbufEndsWithIgnCaseS(&strbuf, \"defg\"));\n    VERIFY(!ffStrbufStartsWithIgnCaseS(&strbuf, \"aBcDeFgH\"));\n    VERIFY(!ffStrbufEndsWithIgnCaseS(&strbuf, \"0aBcDeFg\"));\n\n    //ensure\n    ffStrbufEnsureEndsWithC(&strbuf, '$');\n    VERIFY(ffStrbufEqualS(&strbuf, \"AbCdEfG$\"));\n    ffStrbufEnsureEndsWithC(&strbuf, '$');\n    VERIFY(ffStrbufEqualS(&strbuf, \"AbCdEfG$\"));\n\n    //trimRight\n    ffStrbufTrimRight(&strbuf, '$');\n    VERIFY(ffStrbufEqualS(&strbuf, \"AbCdEfG\"));\n    ffStrbufTrimRight(&strbuf, '$');\n    VERIFY(ffStrbufEqualS(&strbuf, \"AbCdEfG\"));\n\n    //clear\n    ffStrbufClear(&strbuf);\n    VERIFY(strbuf.allocated > 0);\n    VERIFY(strbuf.length == 0);\n    VERIFY(strbuf.chars && strbuf.chars[0] == 0);\n\n    //Destroy\n\n    ffStrbufDestroy(&strbuf);\n\n    VERIFY(strbuf.allocated == 0);\n    VERIFY(strbuf.length == 0);\n    VERIFY(strbuf.chars && strbuf.chars[0] == 0);\n\n    //initA\n    ffStrbufInitA(&strbuf, 32);\n\n    VERIFY(strbuf.allocated == 32);\n    VERIFY(strbuf.length == 0);\n    VERIFY(strbuf.chars && strbuf.chars[0] == 0);\n\n    //appendF\n    ffStrbufAppendF(&strbuf, \"%s\", \"1234567890123456789012345678901\");\n    VERIFY(strbuf.allocated == 32);\n    VERIFY(ffStrbufEqualS(&strbuf, \"1234567890123456789012345678901\"));\n\n    ffStrbufDestroy(&strbuf);\n\n    //initMoveS\n    {\n        char* heapStr = strdup(\"1234567890\");\n        ffStrbufInitMoveS(&strbuf, heapStr);\n        VERIFY(ffStrbufEqualS(&strbuf, \"1234567890\"));\n        VERIFY(strbuf.allocated >= 11);\n        ffStrbufDestroy(&strbuf);\n    }\n\n    //initF\n    ffStrbufInitF(&strbuf, \"%s\", \"1234567890123456789012345678901\");\n    VERIFY(strbuf.allocated >= 32);\n    VERIFY(ffStrbufEqualS(&strbuf, \"1234567890123456789012345678901\"));\n\n    //containC\n    VERIFY(ffStrbufContainC(&strbuf, '1'));\n    VERIFY(!ffStrbufContainC(&strbuf, '-'));\n\n    //replaceAllC\n    ffStrbufReplaceAllC(&strbuf, '1', '-');\n    VERIFY(ffStrbufEqualS(&strbuf, \"-234567890-234567890-234567890-\"));\n    ffStrbufReplaceAllC(&strbuf, '1', '-');\n    VERIFY(ffStrbufEqualS(&strbuf, \"-234567890-234567890-234567890-\"));\n\n    //trim\n    ffStrbufTrim(&strbuf, '-');\n    VERIFY(ffStrbufEqualS(&strbuf, \"234567890-234567890-234567890\"));\n\n    ffStrbufDestroy(&strbuf);\n\n    //ffStrbufCreateS\n    {\n        FF_STRBUF_AUTO_DESTROY testCreate = ffStrbufCreateS(\"TEST\");\n        VERIFY(ffStrbufEqualS(&testCreate, \"TEST\"));\n    }\n\n    //ffStrbufCreateStatic\n    ffStrbufInitStatic(&strbuf, \"TEST\");\n    VERIFY(ffStrbufEqualS(&strbuf, \"TEST\"));\n    VERIFY(strbuf.length == 4);\n    VERIFY(strbuf.allocated == 0);\n\n    ffStrbufDestroy(&strbuf);\n    VERIFY(strbuf.length == 0);\n    VERIFY(strbuf.allocated == 0);\n\n    //ffStrbufCreateStatic / Allocate\n    ffStrbufInitStatic(&strbuf, \"TEST\");\n    ffStrbufEnsureFree(&strbuf, 0);\n    VERIFY(ffStrbufEqualS(&strbuf, \"TEST\"));\n    VERIFY(strbuf.length == 4);\n    VERIFY(strbuf.allocated > 0);\n\n    //ffStrbufCreateStatic / Append\n    ffStrbufInitStatic(&strbuf, \"TEST\");\n    ffStrbufAppendS(&strbuf, \"_TEST\");\n    VERIFY(ffStrbufEqualS(&strbuf, \"TEST_TEST\"));\n    VERIFY(strbuf.length == 9);\n    VERIFY(strbuf.allocated >= 10);\n    ffStrbufAppendC(&strbuf, '_');\n    VERIFY(ffStrbufEqualS(&strbuf, \"TEST_TEST_\"));\n    ffStrbufDestroy(&strbuf);\n    VERIFY(strbuf.length == 0);\n    VERIFY(strbuf.allocated == 0);\n\n    //ffStrbufCreateStatic / Prepend\n    ffStrbufInitStatic(&strbuf, \"TEST\");\n    ffStrbufPrependS(&strbuf, \"TEST_\");\n    VERIFY(ffStrbufEqualS(&strbuf, \"TEST_TEST\"));\n    VERIFY(strbuf.length == 9);\n    VERIFY(strbuf.allocated >= 10);\n    ffStrbufPrependC(&strbuf, '_');\n    VERIFY(ffStrbufEqualS(&strbuf, \"_TEST_TEST\"));\n    ffStrbufDestroy(&strbuf);\n    VERIFY(strbuf.length == 0);\n    VERIFY(strbuf.allocated == 0);\n\n    //ffStrbufCreateStatic / Clear\n    ffStrbufInitStatic(&strbuf, \"TEST\");\n    ffStrbufClear(&strbuf);\n    VERIFY(strbuf.length == 0);\n    VERIFY(strbuf.allocated == 0);\n    ffStrbufDestroy(&strbuf);\n\n    //ffStrbufCreateStatic / Set\n    ffStrbufInitStatic(&strbuf, \"TEST\"); // static\n    ffStrbufSetStatic(&strbuf, \"test\");\n    VERIFY(ffStrbufEqualS(&strbuf, \"test\"));\n    VERIFY(strbuf.length == 4);\n    VERIFY(strbuf.allocated == 0);\n    ffStrbufDestroy(&strbuf);\n\n    //ffStrbufCreateStatic / Set\n    ffStrbufInitS(&strbuf, \"TEST\"); // allocated\n    ffStrbufSetStatic(&strbuf, \"test\");\n    VERIFY(ffStrbufEqualS(&strbuf, \"test\"));\n    VERIFY(strbuf.length == 4);\n    VERIFY(strbuf.allocated == 0);\n    ffStrbufDestroy(&strbuf);\n\n    //ffStrbufCreateStatic / TrimL\n    ffStrbufInitStatic(&strbuf, \"_TEST_\");\n    ffStrbufTrimLeft(&strbuf, '_');\n    VERIFY(ffStrbufEqualS(&strbuf, \"TEST_\"));\n    VERIFY(strbuf.length == 5);\n    VERIFY(strbuf.allocated == 0);\n    ffStrbufDestroy(&strbuf);\n\n    //ffStrbufCreateStatic / TrimR\n    ffStrbufInitStatic(&strbuf, \"_TEST_\");\n    ffStrbufTrimRight(&strbuf, ' ');\n    VERIFY(strbuf.allocated == 0);\n    VERIFY(ffStrbufEqualS(&strbuf, \"_TEST_\"));\n    ffStrbufTrimRight(&strbuf, '_');\n    VERIFY(ffStrbufEqualS(&strbuf, \"_TEST\"));\n    VERIFY(strbuf.length == 5);\n    VERIFY(strbuf.allocated > 0);\n    ffStrbufDestroy(&strbuf);\n\n    //ffStrbufCreateStatic / Substr\n    ffStrbufInitStatic(&strbuf, \"__TEST__\");\n    VERIFY(ffStrbufRemoveSubstr(&strbuf, 0, 6));\n    VERIFY(ffStrbufEqualS(&strbuf, \"__\"));\n    VERIFY(strbuf.length == 2);\n    VERIFY(strbuf.allocated > 0);\n    ffStrbufDestroy(&strbuf);\n\n    //ffStrbufCreateStatic / Substr\n    ffStrbufInitStatic(&strbuf, \"__TEST__\");\n    VERIFY(ffStrbufRemoveSubstr(&strbuf, 2, 8));\n    VERIFY(ffStrbufEqualS(&strbuf, \"__\"));\n    VERIFY(strbuf.length == 2);\n    VERIFY(strbuf.allocated > 0);\n    ffStrbufDestroy(&strbuf);\n\n    //ffStrbufCreateStatic / Substr\n    ffStrbufInitStatic(&strbuf, \"__TEST__\");\n    VERIFY(ffStrbufRemoveSubstr(&strbuf, 2, 6));\n    VERIFY(ffStrbufEqualS(&strbuf, \"____\"));\n    VERIFY(strbuf.length == 4);\n    VERIFY(strbuf.allocated > 0);\n    ffStrbufDestroy(&strbuf);\n\n    //ffStrbufCreateStatic / ReplaceAllC\n    ffStrbufInitStatic(&strbuf, \"__TEST__\");\n    ffStrbufReplaceAllC(&strbuf, '_', '-');\n    VERIFY(ffStrbufEqualS(&strbuf, \"--TEST--\"));\n    VERIFY(strbuf.length == 8);\n    VERIFY(strbuf.allocated > 0);\n    ffStrbufDestroy(&strbuf);\n\n    //ffStrbufCreateStatic / TrimSpace\n    ffStrbufInitStatic(&strbuf, \"\\n TEST\\n \");\n    ffStrbufTrimSpace(&strbuf);\n    VERIFY(strbuf.length == 4);\n    VERIFY(strbuf.allocated > 0);\n    VERIFY(ffStrbufEqualS(&strbuf, \"TEST\"));\n    ffStrbufDestroy(&strbuf);\n\n    //ffStrbufCreate / TrimSpace\n    ffStrbufInitS(&strbuf, \"\\n TEST\\n \");\n    ffStrbufTrimSpace(&strbuf);\n    VERIFY(strbuf.length == 4);\n    VERIFY(strbuf.allocated > 0);\n    VERIFY(ffStrbufEqualS(&strbuf, \"TEST\"));\n    ffStrbufDestroy(&strbuf);\n\n    //ffStrbufEnsureFixedLengthFree / empty buffer\n    ffStrbufInit(&strbuf);\n    ffStrbufEnsureFixedLengthFree(&strbuf, 10);\n    VERIFY(strbuf.length == 0);\n    VERIFY(strbuf.allocated == 11);\n    ffStrbufDestroy(&strbuf);\n    ffStrbufInitA(&strbuf, 10);\n    ffStrbufEnsureFixedLengthFree(&strbuf, 10);\n    VERIFY(strbuf.length == 0);\n    VERIFY(strbuf.allocated == 11);\n    ffStrbufEnsureFixedLengthFree(&strbuf, 12);\n    VERIFY(strbuf.length == 0);\n    VERIFY(strbuf.allocated == 13);\n    ffStrbufDestroy(&strbuf);\n\n    //ffStrbufEnsureFixedLengthFree / empty buffer with zero free length\n    ffStrbufInit(&strbuf);\n    ffStrbufEnsureFixedLengthFree(&strbuf, 0);\n    VERIFY(strbuf.length == 0);\n    VERIFY(strbuf.allocated == 0);\n    ffStrbufDestroy(&strbuf);\n\n    //ffStrbufEnsureFixedLengthFree / empty buffer but oldFree >= newFree\n    ffStrbufInitA(&strbuf, 11);\n    ffStrbufEnsureFixedLengthFree(&strbuf, 10);\n    VERIFY(strbuf.length == 0);\n    VERIFY(strbuf.allocated == 11);\n    ffStrbufDestroy(&strbuf);\n    ffStrbufInitA(&strbuf, 12);\n    ffStrbufEnsureFixedLengthFree(&strbuf, 10);\n    VERIFY(strbuf.length == 0);\n    VERIFY(strbuf.allocated == 12);\n    ffStrbufDestroy(&strbuf);\n\n    //ffStrbufEnsureFixedLengthFree / non empty buffer\n    ffStrbufAppendF(&strbuf, \"%s\", \"1234567890\");\n    VERIFY(strbuf.length == 10);\n    VERIFY(strbuf.allocated == 32);\n    ffStrbufEnsureFixedLengthFree(&strbuf, 0);\n    VERIFY(strbuf.length == 10);\n    VERIFY(strbuf.allocated == 32);\n    ffStrbufEnsureFixedLengthFree(&strbuf, 20); // less than oldFree (=21)\n    VERIFY(strbuf.length == 10);\n    VERIFY(strbuf.allocated == 32);\n    ffStrbufEnsureFixedLengthFree(&strbuf, 21); // equal to oldFree (=21)\n    VERIFY(strbuf.length == 10);\n    VERIFY(strbuf.allocated == 32);\n    ffStrbufEnsureFixedLengthFree(&strbuf, 22); // greater than oldFree (=21)\n    VERIFY(strbuf.length == 10);\n    VERIFY(strbuf.allocated == 33);\n    ffStrbufDestroy(&strbuf);\n\n    //ffStrbufEnsureFixedLengthFree / static buffer\n    ffStrbufInitStatic(&strbuf, \"__TEST__\");\n    VERIFY(strbuf.length > 0);\n    VERIFY(strbuf.allocated == 0);\n    ffStrbufEnsureFixedLengthFree(&strbuf, 10);\n    VERIFY(strbuf.length == strlen(\"__TEST__\"));\n    VERIFY(strbuf.allocated == strlen(\"__TEST__\") + 1 + 10);\n    VERIFY(ffStrbufEqualS(&strbuf, \"__TEST__\"));\n    ffStrbufDestroy(&strbuf);\n\n    //ffStrbufInsertNC\n    ffStrbufInitStatic(&strbuf, \"123456\");\n    ffStrbufInsertNC(&strbuf, 0, 2, 'A');\n    VERIFY(ffStrbufEqualS(&strbuf, \"AA123456\"));\n    ffStrbufInsertNC(&strbuf, 4, 2, 'B');\n    VERIFY(ffStrbufEqualS(&strbuf, \"AA12BB3456\"));\n    ffStrbufInsertNC(&strbuf, strbuf.length, 2, 'C');\n    VERIFY(ffStrbufEqualS(&strbuf, \"AA12BB3456CC\"));\n    ffStrbufInsertNC(&strbuf, 999, 2, 'D');\n    VERIFY(ffStrbufEqualS(&strbuf, \"AA12BB3456CCDD\"));\n    ffStrbufDestroy(&strbuf);\n\n    // smallest allocation test\n    {\n        FF_STRBUF_AUTO_DESTROY strbuf1 = ffStrbufCreateA(10);\n        VERIFY(strbuf1.allocated == 10);\n        ffStrbufEnsureFree(&strbuf1, 16);\n        VERIFY(strbuf1.allocated == 32);\n\n        FF_STRBUF_AUTO_DESTROY strbuf2 = ffStrbufCreate();\n        VERIFY(strbuf2.allocated == 0);\n        ffStrbufEnsureFree(&strbuf2, 16);\n        VERIFY(strbuf2.allocated == 32);\n    }\n\n    {\n        int i = 0;\n        char* lineptr = NULL;\n        size_t n = 0;\n        const char* text = \"Processor\\t: ARMv7\\nprocessor\\t: 0\\nBogoMIPS\\t: 38.00\\n\\nprocessor\\t: 1\\nBogoMIPS\\t: 38.00\";\n        ffStrbufSetS(&strbuf, text);\n\n        while (ffStrbufGetline(&lineptr, &n, &strbuf))\n        {\n            ++i;\n            switch (i)\n            {\n                case 1:\n                    VERIFY(strcmp(lineptr, \"Processor\\t: ARMv7\") == 0);\n                    VERIFY(n == strlen(\"Processor\\t: ARMv7\"));\n                    break;\n                case 2:\n                    VERIFY(strcmp(lineptr, \"processor\\t: 0\") == 0);\n                    VERIFY(n == strlen(\"processor\\t: 0\"));\n                    break;\n                case 3:\n                    VERIFY(strcmp(lineptr, \"BogoMIPS\\t: 38.00\") == 0);\n                    VERIFY(n == strlen(\"BogoMIPS\\t: 38.00\"));\n                    break;\n                case 4:\n                    VERIFY(strcmp(lineptr, \"\") == 0);\n                    VERIFY(n == 0);\n                    break;\n                case 5:\n                    VERIFY(strcmp(lineptr, \"processor\\t: 1\") == 0);\n                    VERIFY(n == strlen(\"processor\\t: 1\"));\n                    break;\n                case 6:\n                    VERIFY(strcmp(lineptr, \"BogoMIPS\\t: 38.00\") == 0);\n                    VERIFY(n == strlen(\"BogoMIPS\\t: 38.00\"));\n                    break;\n                default:\n                    VERIFY(false);\n                    break;\n            }\n        }\n        VERIFY(ffStrbufEqualS(&strbuf, text));\n        VERIFY(*lineptr == '\\0');\n        VERIFY(i == 6);\n\n        lineptr = NULL;\n        n = 0;\n        i = 0;\n        text = \"\\n\";\n        ffStrbufSetS(&strbuf, text);\n        while (ffStrbufGetline(&lineptr, &n, &strbuf))\n        {\n            ++i;\n            switch (i)\n            {\n                case 1:\n                    VERIFY(strcmp(lineptr, \"\") == 0);\n                    VERIFY(n == 0);\n                    break;\n                default:\n                    VERIFY(false);\n                    break;\n            }\n        }\n        VERIFY(ffStrbufEqualS(&strbuf, text));\n        VERIFY(*lineptr == '\\0');\n        VERIFY(i == 1);\n\n        lineptr = NULL;\n        n = 0;\n        i = 0;\n        text = \"abcd\";\n        ffStrbufSetS(&strbuf, text);\n        while (ffStrbufGetline(&lineptr, &n, &strbuf))\n        {\n            ++i;\n            switch (i)\n            {\n                case 1:\n                    VERIFY(strcmp(lineptr, \"abcd\") == 0);\n                    VERIFY(n == strlen(\"abcd\"));\n                    break;\n                default:\n                    VERIFY(false);\n                    break;\n            }\n        }\n        VERIFY(ffStrbufEqualS(&strbuf, text));\n        VERIFY(*lineptr == '\\0');\n        VERIFY(i == 1);\n\n        lineptr = NULL;\n        n = 0;\n        i = 0;\n        text = \"\";\n        ffStrbufSetS(&strbuf, text);\n        while (ffStrbufGetline(&lineptr, &n, &strbuf))\n        {\n            ++i;\n            VERIFY(false);\n        }\n\n        VERIFY(ffStrbufEqualS(&strbuf, text));\n        VERIFY(*lineptr == '\\0');\n        VERIFY(i == 0);\n    }\n\n    ffStrbufSetS(&strbuf, \"Hello World\");\n    VERIFY(ffStrbufRemoveDupWhitespaces(&strbuf) == false);\n    VERIFY(strcmp(strbuf.chars, \"Hello World\") == 0);\n\n    ffStrbufSetS(&strbuf, \"Hello   World\");\n    VERIFY(ffStrbufRemoveDupWhitespaces(&strbuf) == true);\n    VERIFY(strcmp(strbuf.chars, \"Hello World\") == 0);\n\n    ffStrbufSetS(&strbuf, \"   Hello World   \");\n    VERIFY(ffStrbufRemoveDupWhitespaces(&strbuf) == true);\n    VERIFY(strcmp(strbuf.chars, \" Hello World \") == 0);\n\n    ffStrbufSetS(&strbuf, \"   Hello   World   \");\n    VERIFY(ffStrbufRemoveDupWhitespaces(&strbuf) == true);\n    VERIFY(strcmp(strbuf.chars, \" Hello World \") == 0);\n\n    ffStrbufSetS(&strbuf, \"   \");\n    VERIFY(ffStrbufRemoveDupWhitespaces(&strbuf) == true);\n    VERIFY(strcmp(strbuf.chars, \" \") == 0);\n\n    ffStrbufClear(&strbuf);\n    VERIFY(ffStrbufRemoveDupWhitespaces(&strbuf) == false);\n    VERIFY(strcmp(strbuf.chars, \"\") == 0);\n\n    ffStrbufSetStatic(&strbuf, \"   \");\n    VERIFY(ffStrbufRemoveDupWhitespaces(&strbuf) == false);\n    VERIFY(strcmp(strbuf.chars, \"   \") == 0);\n\n    {\n        ffStrbufSetStatic(&strbuf, \"abcdef\");\n        FF_STRBUF_AUTO_DESTROY newStr = ffStrbufCreateCopy(&strbuf);\n        VERIFY(newStr.allocated == 0);\n        VERIFY(newStr.chars == strbuf.chars);\n    }\n\n    {\n        ffStrbufSetStatic(&strbuf, \"abcdef\");\n        FF_STRBUF_AUTO_DESTROY newStr = ffStrbufCreateS(\"123456\");\n        ffStrbufSet(&newStr, &strbuf);\n        VERIFY(newStr.allocated == 0);\n        VERIFY(newStr.chars == strbuf.chars);\n        VERIFY(ffStrbufEqualS(&newStr, \"abcdef\"));\n    }\n\n    {\n        ffStrbufClear(&strbuf);\n        FF_STRBUF_AUTO_DESTROY newStr = ffStrbufCreateS(\"123456\");\n        uint32_t oldAlloc = newStr.allocated;\n        VERIFY(oldAlloc > newStr.length);\n        ffStrbufSet(&newStr, &strbuf);\n        VERIFY(newStr.allocated == oldAlloc);\n        VERIFY(newStr.chars != strbuf.chars);\n        VERIFY(ffStrbufEqualS(&newStr, \"\"));\n    }\n\n    {\n        ffStrbufSetStatic(&strbuf, \"abcdefghijkl\");\n        FF_STRBUF_AUTO_DESTROY newStr = ffStrbufCreateS(\"123456\");\n        ffStrbufSet(&newStr, &strbuf);\n        VERIFY(newStr.allocated == 0);\n        VERIFY(newStr.chars == strbuf.chars);\n        VERIFY(ffStrbufEqualS(&newStr, \"abcdefghijkl\"));\n    }\n\n    {\n        ffStrbufClear(&strbuf);\n        FF_STRBUF_AUTO_DESTROY newStr = ffStrbufCreateCopy(&strbuf);\n        VERIFY(newStr.allocated == 0);\n        VERIFY(newStr.chars == strbuf.chars);\n        VERIFY(newStr.chars[0] == '\\0');\n    }\n\n    {\n        ffStrbufClear(&strbuf);\n        FF_STRBUF_AUTO_DESTROY newStr = ffStrbufCreateS(\"123456\");\n        ffStrbufSet(&newStr, &strbuf);\n        VERIFY(newStr.allocated > 0);\n        VERIFY(newStr.chars != strbuf.chars);\n        VERIFY(ffStrbufEqualS(&newStr, \"\"));\n    }\n\n    {\n        ffStrbufSetStatic(&strbuf, \"abc\");\n        VERIFY(ffStrbufMatchSeparatedS(&strbuf, \"abc:def:ghi\", ' ') == false);\n        VERIFY(ffStrbufMatchSeparatedS(&strbuf, \"abc:def:ghi\", ':') == true);\n        VERIFY(ffStrbufMatchSeparatedS(&strbuf, \"def:ghi\", ' ') == false);\n        VERIFY(ffStrbufMatchSeparatedS(&strbuf, \"def:ghi\", ':') == false);\n        VERIFY(ffStrbufMatchSeparatedS(&strbuf, \"def\", ':') == false);\n        VERIFY(ffStrbufMatchSeparatedS(&strbuf, \"abc\", ':') == true);\n        VERIFY(ffStrbufMatchSeparatedS(&strbuf, \"\", ' ') == false);\n        VERIFY(ffStrbufMatchSeparatedS(&strbuf, \":abc:\", ':') == true);\n        VERIFY(ffStrbufMatchSeparatedS(&strbuf, \"abc:\", ':') == true);\n        VERIFY(ffStrbufMatchSeparatedS(&strbuf, \":abc\", ':') == true);\n        VERIFY(ffStrbufMatchSeparatedS(&strbuf, \":ABC\", ':') == false);\n        VERIFY(ffStrbufMatchSeparatedS(&strbuf, \":abcdef\", ':') == false);\n    }\n\n    {\n        ffStrbufSetStatic(&strbuf, \"ABC\");\n        VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, \"abc:def:ghi\", ' ') == false);\n        VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, \"abc:def:ghi\", ':') == true);\n        VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, \"def:ghi\", ' ') == false);\n        VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, \"def:ghi\", ':') == false);\n        VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, \"def\", ':') == false);\n        VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, \"abc\", ':') == true);\n        VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, \"\", ' ') == false);\n        VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, \":abc:\", ':') == true);\n        VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, \"abc:\", ':') == true);\n        VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, \":abc\", ':') == true);\n        VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, \":ABC\", ':') == true);\n        VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, \":abcdef\", ':') == false);\n    }\n\n    {\n        ffStrbufSetStatic(&strbuf, \"abc:def:ghi\");\n        VERIFY(ffStrbufSeparatedContainS(&strbuf, \"abc\", ' ') == false);\n        VERIFY(ffStrbufSeparatedContainS(&strbuf, \"abc\", ':') == true);\n        VERIFY(ffStrbufSeparatedContainS(&strbuf, \"def\", ' ') == false);\n        VERIFY(ffStrbufSeparatedContainS(&strbuf, \"def\", ':') == true);\n        VERIFY(ffStrbufSeparatedContainS(&strbuf, \"DEF\", ':') == false);\n        VERIFY(ffStrbufSeparatedContainS(&strbuf, \"a\", ':') == false);\n        VERIFY(ffStrbufSeparatedContainS(&strbuf, \"e\", ':') == false);\n        VERIFY(ffStrbufSeparatedContainS(&strbuf, \"i\", ':') == false);\n    }\n\n    {\n        ffStrbufSetStatic(&strbuf, \"ABC:DEF:GHI\");\n        VERIFY(ffStrbufSeparatedContainIgnCaseS(&strbuf, \"abc\", ' ') == false);\n        VERIFY(ffStrbufSeparatedContainIgnCaseS(&strbuf, \"abc\", ':') == true);\n        VERIFY(ffStrbufSeparatedContainIgnCaseS(&strbuf, \"def\", ' ') == false);\n        VERIFY(ffStrbufSeparatedContainIgnCaseS(&strbuf, \"def\", ':') == true);\n        VERIFY(ffStrbufSeparatedContainIgnCaseS(&strbuf, \"DEF\", ':') == true);\n        VERIFY(ffStrbufSeparatedContainIgnCaseS(&strbuf, \"a\", ':') == false);\n        VERIFY(ffStrbufSeparatedContainIgnCaseS(&strbuf, \"e\", ':') == false);\n        VERIFY(ffStrbufSeparatedContainIgnCaseS(&strbuf, \"i\", ':') == false);\n    }\n\n    {\n        ffStrbufSetStatic(&strbuf, \"abc\");\n        ffStrbufSubstr(&strbuf, 0, 1); // start, end\n        VERIFY(ffStrbufEqualS(&strbuf, \"a\"));\n\n        ffStrbufSetStatic(&strbuf, \"abc\");\n        ffStrbufSubstr(&strbuf, 1, 1);\n        VERIFY(ffStrbufEqualS(&strbuf, \"\"));\n\n        ffStrbufSetStatic(&strbuf, \"abc\");\n        ffStrbufSubstr(&strbuf, 2, 1);\n        VERIFY(ffStrbufEqualS(&strbuf, \"\"));\n\n        ffStrbufSetStatic(&strbuf, \"abc\");\n        ffStrbufSubstr(&strbuf, 2, 3);\n        VERIFY(ffStrbufEqualS(&strbuf, \"c\"));\n\n        ffStrbufSetStatic(&strbuf, \"abc\");\n        ffStrbufSubstr(&strbuf, 0, 3);\n        VERIFY(ffStrbufEqualS(&strbuf, \"abc\"));\n    }\n\n    {\n        ffStrbufSetS(&strbuf, \"abc\");\n        ffStrbufSubstr(&strbuf, 0, 1); // start, end\n        VERIFY(ffStrbufEqualS(&strbuf, \"a\"));\n\n        ffStrbufSetS(&strbuf, \"abc\");\n        ffStrbufSubstr(&strbuf, 1, 1);\n        VERIFY(ffStrbufEqualS(&strbuf, \"\"));\n\n        ffStrbufSetS(&strbuf, \"abc\");\n        ffStrbufSubstr(&strbuf, 2, 1);\n        VERIFY(ffStrbufEqualS(&strbuf, \"\"));\n\n        ffStrbufSetS(&strbuf, \"abc\");\n        ffStrbufSubstr(&strbuf, 2, 3);\n        VERIFY(ffStrbufEqualS(&strbuf, \"c\"));\n\n        ffStrbufSetS(&strbuf, \"abc\");\n        ffStrbufSubstr(&strbuf, 0, 3);\n        VERIFY(ffStrbufEqualS(&strbuf, \"abc\"));\n\n        ffStrbufDestroy(&strbuf);\n    }\n\n    {\n        ffStrbufAppendUtf32CodePoint(&strbuf, 0x6587);\n        ffStrbufAppendUtf32CodePoint(&strbuf, 0x6cc9);\n        ffStrbufAppendUtf32CodePoint(&strbuf, 0x9a7f);\n        VERIFY(ffStrbufEqualS(&strbuf, u8\"文泉驿\"));\n\n        ffStrbufDestroy(&strbuf);\n    }\n\n    {\n        ffStrbufAppendSInt(&strbuf, 1234567890);\n        VERIFY(ffStrbufEqualS(&strbuf, \"1234567890\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendSInt(&strbuf, -1234567890);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-1234567890\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendSInt(&strbuf, 0);\n        VERIFY(ffStrbufEqualS(&strbuf, \"0\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendUInt(&strbuf, 1234567890);\n        VERIFY(ffStrbufEqualS(&strbuf, \"1234567890\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendUInt(&strbuf, 0);\n        VERIFY(ffStrbufEqualS(&strbuf, \"0\"));\n\n        ffStrbufDestroy(&strbuf);\n    }\n\n    {\n        ffStrbufAppendDouble(&strbuf, 120.0, 0, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"120\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 120.0, 1, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"120.0\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 120.0, 5, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"120.00000\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 120.123456789, 5, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"120.12346\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 120.888888, 0, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"121\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 120.999999, 2, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"121.00\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 120.123456789, 0, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"120\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 120.123456789, -1, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"120.123456789\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 120.123, 5, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"120.12300\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -120.0, 0, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-120\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -120.0, 1, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-120.0\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -120.0, 5, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-120.00000\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -120.123456789, 5, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-120.12346\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -120.123456789, 0, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-120\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -120.123456789, -1, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-120.123456789\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -120.123, 5, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-120.12300\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -120.888888, 0, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-121\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -120.999999, 2, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-121.00\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 1.2345e50, 1, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"1.2345e50\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -1.2345e50, 1, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-1.2345e50\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 1.2345e50, 0, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"1.2345e50\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -1.2345e50, 0, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-1.2345e50\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 1.2345e50, -1, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"1.2345e50\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -1.2345e50, -1, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-1.2345e50\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 1.2345e20, 1, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"123450000000000000000.0\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -1.2345e20, 1, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-123450000000000000000.0\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, +0.0, 0, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"0\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -0.0, 0, true);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-0\"));\n\n        ffStrbufDestroy(&strbuf);\n    }\n\n    {\n        ffStrbufAppendDouble(&strbuf, 120.0, 0, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"120\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 120.0, 1, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"120\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 120.0, 5, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"120\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 120.123456789, 5, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"120.12346\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 120.888888, 0, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"121\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 120.999999, 2, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"121\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 120.123456789, 0, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"120\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 120.123456789, -1, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"120.123456789\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 120.123, 5, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"120.123\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -120.0, 0, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-120\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -120.0, 1, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-120\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -120.0, 5, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-120\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -120.123456789, 5, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-120.12346\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -120.123456789, 0, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-120\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -120.123456789, -1, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-120.123456789\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -120.123, 5, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-120.123\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -120.888888, 0, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-121\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -120.999999, 2, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-121\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 1.2345e50, 1, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"1.2345e50\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -1.2345e50, 1, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-1.2345e50\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 1.2345e50, 0, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"1.2345e50\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -1.2345e50, 0, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-1.2345e50\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 1.2345e50, -1, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"1.2345e50\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -1.2345e50, -1, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-1.2345e50\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, 1.2345e20, 1, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"123450000000000000000\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -1.2345e20, 1, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-123450000000000000000\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, +0.0, 0, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"0\"));\n\n        ffStrbufClear(&strbuf);\n        ffStrbufAppendDouble(&strbuf, -0.0, 0, false);\n        VERIFY(ffStrbufEqualS(&strbuf, \"-0\"));\n\n        ffStrbufDestroy(&strbuf);\n    }\n\n    //setS\n    ffStrbufInitStatic(&strbuf, \"STATIC\");\n    ffStrbufSetS(&strbuf, \"DYNAMIC\");\n    VERIFY(ffStrbufEqualS(&strbuf, \"DYNAMIC\"));\n    VERIFY(strbuf.allocated > 0);\n    ffStrbufDestroy(&strbuf);\n\n    ffStrbufInit(&strbuf);\n    ffStrbufSetS(&strbuf, \"\");\n    VERIFY(ffStrbufEqualS(&strbuf, \"\"));\n    VERIFY(strbuf.allocated == 0);\n    ffStrbufDestroy(&strbuf);\n\n    ffStrbufInitStatic(&strbuf, \"STATIC\");\n    ffStrbufSetS(&strbuf, \"\");\n    VERIFY(ffStrbufEqualS(&strbuf, \"\"));\n    VERIFY(strbuf.allocated == 0);\n    ffStrbufDestroy(&strbuf);\n\n    ffStrbufInitStatic(&strbuf, \"STATIC\");\n    ffStrbufSetStatic(&strbuf, \"\");\n    VERIFY(ffStrbufEqualS(&strbuf, \"\"));\n    VERIFY(strbuf.allocated == 0);\n    ffStrbufDestroy(&strbuf);\n\n    ffStrbufInitS(&strbuf, \"DYNAMIC\");\n    ffStrbufSetStatic(&strbuf, \"\");\n    VERIFY(ffStrbufEqualS(&strbuf, \"\"));\n    VERIFY(strbuf.allocated == 0);\n    ffStrbufDestroy(&strbuf);\n\n    ffStrbufInitS(&strbuf, \"DYNAMIC\");\n    ffStrbufSetS(&strbuf, \"ANOTHER DYNAMIC\");\n    VERIFY(ffStrbufEqualS(&strbuf, \"ANOTHER DYNAMIC\"));\n    VERIFY(strbuf.allocated > 0);\n    ffStrbufDestroy(&strbuf);\n\n    ffStrbufInit(&strbuf);\n    ffStrbufSetStatic(&strbuf, \"\");\n    VERIFY(ffStrbufEqualS(&strbuf, \"\"));\n    VERIFY(strbuf.allocated == 0);\n    ffStrbufDestroy(&strbuf);\n\n    //set\n    ffStrbufInitStatic(&strbuf, \"STATIC\");\n    {\n        FF_STRBUF_AUTO_DESTROY other = ffStrbufCreateS(\"DYNAMIC\");\n        ffStrbufSet(&strbuf, &other);\n    }\n    VERIFY(ffStrbufEqualS(&strbuf, \"DYNAMIC\"));\n    VERIFY(strbuf.allocated > 0);\n    ffStrbufDestroy(&strbuf);\n\n    ffStrbufInit(&strbuf);\n    {\n        FF_STRBUF_AUTO_DESTROY other = ffStrbufCreateS(\"\");\n        ffStrbufSet(&strbuf, &other);\n    }\n    VERIFY(ffStrbufEqualS(&strbuf, \"\"));\n    VERIFY(strbuf.allocated == 0);\n    ffStrbufDestroy(&strbuf);\n\n    ffStrbufInitStatic(&strbuf, \"STATIC\");\n    {\n        FF_STRBUF_AUTO_DESTROY other = ffStrbufCreateS(\"\");\n        ffStrbufSet(&strbuf, &other);\n    }\n    VERIFY(ffStrbufEqualS(&strbuf, \"\"));\n    VERIFY(strbuf.allocated == 0);\n    ffStrbufDestroy(&strbuf);\n\n    ffStrbufInitS(&strbuf, \"DYNAMIC\");\n    {\n        FF_STRBUF_AUTO_DESTROY other = ffStrbufCreateS(\"\");\n        ffStrbufSet(&strbuf, &other);\n    }\n    VERIFY(ffStrbufEqualS(&strbuf, \"\"));\n    VERIFY(strbuf.allocated > 0);\n    ffStrbufDestroy(&strbuf);\n\n    ffStrbufInitS(&strbuf, \"DYNAMIC\");\n    {\n        FF_STRBUF_AUTO_DESTROY other = ffStrbufCreateStatic(\"STATIC\");\n        ffStrbufSet(&strbuf, &other);\n    }\n    VERIFY(ffStrbufEqualS(&strbuf, \"STATIC\"));\n    VERIFY(strbuf.allocated == 0);\n    ffStrbufDestroy(&strbuf);\n\n    //Success\n    puts(\"\\e[32mAll tests passed!\" FASTFETCH_TEXT_MODIFIER_RESET);\n}\n"
  },
  {
    "path": "tests/testlogo-hardcolors.fflogo",
    "content": "\u001b[36;1m   .   \n\u001b[36;1m  ...  \n\u001b[36;1m .-|-. \n\u001b[36;1m.-| |-."
  },
  {
    "path": "tests/testlogo-softcolors.fflogo",
    "content": "$1   .   \n$1  ...  \n$1 .-|-. \n$1.-| |-."
  }
]