diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8a47a75a2..d2c18f7903 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: run: uname -a - name: configure project - run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr . -DENABLE_VULKAN=OFF -DENABLE_WAYLAND=OFF -DENABLE_XCB_RANDR=OFF -DENABLE_XCB=OFF -DENABLE_XRANDR=OFF -DENABLE_X11=OFF -DENABLE_DRM=OFF -DENABLE_DRM_AMDGPU=OFF -DENABLE_GIO=OFF -DENABLE_DCONF=OFF -DENABLE_DBUS=OFF -DENABLE_XFCONF=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_OSMESA=OFF -DENABLE_OPENCL=OFF -DENABLE_FREETYPE=OFF -DENABLE_PULSE=OFF -DENABLE_DDCUTIL=OFF -DENABLE_ELF=OFF -DENABLE_DIRECTX_HEADERS=OFF -DENABLE_THREADS=OFF + 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_XFCONF=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 - name: build project run: cmake --build . --target package --verbose -j4 @@ -80,7 +80,7 @@ jobs: run: cat /proc/cpuinfo - name: install required packages - run: sudo apt-get update && sudo apt-get install -y libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libxfconf-0-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev libosmesa6-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev directx-headers-dev + run: sudo apt-get update && sudo apt-get install -y libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libxfconf-0-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev directx-headers-dev - name: install linuxbrew packages run: | @@ -149,7 +149,7 @@ jobs: run: cat /proc/cpuinfo - name: install required packages - run: sudo apt-get update && sudo apt-get install -y libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libxfconf-0-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev libosmesa6-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev directx-headers-dev libchafa-dev libddcutil-dev rpm + run: sudo apt-get update && sudo apt-get install -y libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libxfconf-0-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev directx-headers-dev libchafa-dev libddcutil-dev rpm - name: configure project run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_INSTALL_PREFIX=/usr . @@ -208,7 +208,7 @@ jobs: # CMake installed by apt has bug `list sub-command REMOVE_ITEM requires two or more arguments` wget --no-check-certificate https://apt.kitware.com/ubuntu/pool/main/c/cmake/{cmake_3.29.2-0kitware1ubuntu20.04.1_armhf.deb,cmake-data_3.29.2-0kitware1ubuntu20.04.1_all.deb} dpkg -i *.deb - apt-get install -y make g++ libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libxfconf-0-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev libosmesa6-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev directx-headers-dev rpm + apt-get install -y make g++ libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libxfconf-0-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev directx-headers-dev rpm cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr . cmake --build . --target package --verbose -j4 ./fastfetch --list-features @@ -244,7 +244,7 @@ jobs: run: | uname -a apt-get update && apt-get install -y wget - apt-get install -y cmake make g++ libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libxfconf-0-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev libosmesa6-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev directx-headers-dev rpm + apt-get install -y cmake make g++ libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libxfconf-0-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev directx-headers-dev rpm cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr . cmake --build . --target package --verbose -j4 ./fastfetch --list-features @@ -279,7 +279,7 @@ jobs: githubToken: ${{ github.token }} run: | uname -a - apt-get update && apt-get install -y cmake make g++ libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libxfconf-0-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev libosmesa6-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libddcutil-dev libchafa-dev libelf-dev directx-headers-dev rpm + apt-get update && apt-get install -y cmake make g++ libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libxfconf-0-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libddcutil-dev libchafa-dev libelf-dev directx-headers-dev rpm cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr . cmake --build . --target package --verbose -j4 ./fastfetch --list-features @@ -314,7 +314,7 @@ jobs: githubToken: ${{ github.token }} run: | uname -a - apt-get update && apt-get install -y cmake make g++ libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libxfconf-0-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev libosmesa6-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libchafa-dev libelf-dev directx-headers-dev rpm + apt-get update && apt-get install -y cmake make g++ libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libxfconf-0-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libchafa-dev libelf-dev directx-headers-dev rpm cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr . cmake --build . --target package --verbose -j4 ./fastfetch --list-features @@ -349,7 +349,7 @@ jobs: githubToken: ${{ github.token }} run: | uname -a - apt-get update && apt-get install -y cmake make g++ libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libxfconf-0-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev libosmesa6-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libchafa-dev libelf-dev directx-headers-dev rpm + apt-get update && apt-get install -y cmake make g++ libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libxfconf-0-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libchafa-dev libelf-dev directx-headers-dev rpm cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr . cmake --build . --target package --verbose -j4 ./fastfetch --list-features @@ -504,7 +504,7 @@ jobs: run: | uname -a sudo pkg update - sudo pkg install -y cmake git pkgconf binutils wayland vulkan-headers vulkan-loader libxcb libXrandr libX11 libdrm glib dconf dbus sqlite3-tcl xfce4-conf egl libosmesa opencl ocl-icd v4l_compat chafa + sudo pkg install -y cmake git pkgconf binutils wayland vulkan-headers vulkan-loader libxcb libXrandr libX11 libdrm glib dconf dbus sqlite3-tcl xfce4-conf egl opencl ocl-icd v4l_compat chafa cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On . cmake --build . --target package --verbose -j4 ./fastfetch --list-features @@ -537,7 +537,7 @@ jobs: prepare: | uname -a pkg update - pkg install -y cmake git pkgconf binutils wayland vulkan-headers vulkan-loader libxcb libXrandr libX11 libdrm glib dconf dbus sqlite3-tcl xfce4-conf egl libosmesa opencl ocl-icd v4l_compat chafa libelf + pkg install -y cmake git pkgconf binutils wayland vulkan-headers vulkan-loader libxcb libXrandr libX11 libdrm glib dconf dbus sqlite3-tcl xfce4-conf egl opencl ocl-icd v4l_compat chafa libelf run: | cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On . @@ -575,7 +575,7 @@ jobs: version: '7.6' run: | uname -a - sudo pkg_add -r cmake git pkgconf wayland vulkan-headers vulkan-loader glib2 dconf dbus sqlite3 xfconf imagemagick chafa pulseaudio hwdata + sudo pkg_add -r cmake git pkgconf wayland vulkan-headers vulkan-loader glib2 dconf dbus sqlite3 xfconf imagemagick chafa cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On . cmake --build . --target package --verbose -j4 ./fastfetch --list-features @@ -611,7 +611,7 @@ jobs: version: '10.1' run: | uname -a - sudo pkgin -y install cmake git pkgconf wayland vulkan-headers dconf dbus sqlite3 ImageMagick pulseaudio opencl-headers ocl-icd + sudo pkgin -y install cmake git pkgconf wayland vulkan-headers dconf dbus sqlite3 ImageMagick opencl-headers ocl-icd cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On . cmake --build . --target package --verbose -j4 ./fastfetch --list-features diff --git a/CHANGELOG.md b/CHANGELOG.md index f80f37bb8e..b412be171b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +# 2.39.0 + +Changes: +* OSMesa backend for OpenGL detection is removed (#1618) +* 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) + +Features: +* Improve accuracy of HDR support on Windows 11 24H2 (Display, Windows) +* Improve performance of SSID detection on macOS Sequoia (Wifi, macOS, #1597) +* Support warp terminal version detection on Windows (Terminal, Windows) +* Support default route detection on OpenBSD & DragonFly BSD (LocalIP, OpenBSD / DragonFly) +* Improve bash completion script +* Improve performance of networking (PublicIP / Weather) +* Support pkgsrc package manager detection on Linux (Packages, Linux) + +Logo: +* Add Common Torizon OS +* Change FoxOS to WolfOS +* Add Bredos +* Add NetBSD2 + # 2.38.0 Bugfixes: diff --git a/CMakeLists.txt b/CMakeLists.txt index 9284b746a5..6ab1d4ae45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.12.0) # target_link_libraries with OBJECT libs & project homepage url project(fastfetch - VERSION 2.38.0 + VERSION 2.39.0 LANGUAGES C DESCRIPTION "Fast neofetch-like system information tool" HOMEPAGE_URL "https://github.com/fastfetch-cli/fastfetch" @@ -74,7 +74,6 @@ cmake_dependent_option(ENABLE_CHAFA "Enable chafa" ON "ENABLE_IMAGEMAGICK6 OR EN cmake_dependent_option(ENABLE_ZLIB "Enable zlib" ON "ENABLE_IMAGEMAGICK6 OR ENABLE_IMAGEMAGICK7" OFF) cmake_dependent_option(ENABLE_EGL "Enable egl" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR WIN32 OR SunOS OR Haiku" OFF) cmake_dependent_option(ENABLE_GLX "Enable glx" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS" OFF) -cmake_dependent_option(ENABLE_OSMESA "Enable osmesa" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR SunOS OR Haiku" OFF) cmake_dependent_option(ENABLE_OPENCL "Enable opencl" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR WIN32 OR ANDROID OR SunOS OR Haiku" OFF) cmake_dependent_option(ENABLE_FREETYPE "Enable freetype" ON "ANDROID" OFF) cmake_dependent_option(ENABLE_PULSE "Enable pulse" ON "LINUX OR SunOS" OFF) @@ -365,6 +364,7 @@ set(LIBFASTFETCH_SRC src/common/library.c src/common/modules.c src/common/netif/netif.c + src/common/networking/networking_common.c src/common/option.c src/common/parsing.c src/common/printing.c @@ -493,7 +493,7 @@ if(LINUX) src/common/dbus.c src/common/io/io_unix.c src/common/netif/netif_linux.c - src/common/networking_linux.c + src/common/networking/networking_linux.c src/common/processing_linux.c src/detection/battery/battery_linux.c src/detection/bios/bios_linux.c @@ -574,7 +574,7 @@ elseif(ANDROID) list(APPEND LIBFASTFETCH_SRC src/common/io/io_unix.c src/common/netif/netif_linux.c - src/common/networking_linux.c + src/common/networking/networking_linux.c src/common/processing_linux.c src/detection/battery/battery_android.c src/detection/bios/bios_android.c @@ -640,7 +640,7 @@ elseif(FreeBSD) src/common/dbus.c src/common/io/io_unix.c src/common/netif/netif_bsd.c - src/common/networking_linux.c + src/common/networking/networking_linux.c src/common/processing_linux.c src/common/sysctl.c src/detection/battery/battery_bsd.c @@ -731,7 +731,7 @@ elseif(NetBSD) src/common/dbus.c src/common/io/io_unix.c src/common/netif/netif_bsd.c - src/common/networking_linux.c + src/common/networking/networking_linux.c src/common/processing_linux.c src/common/sysctl.c src/detection/battery/battery_nbsd.c @@ -813,7 +813,7 @@ elseif(OpenBSD) src/common/dbus.c src/common/io/io_unix.c src/common/netif/netif_bsd.c - src/common/networking_linux.c + src/common/networking/networking_linux.c src/common/processing_linux.c src/common/sysctl.c src/detection/battery/battery_obsd.c @@ -894,7 +894,7 @@ elseif(APPLE) list(APPEND LIBFASTFETCH_SRC src/common/io/io_unix.c src/common/netif/netif_bsd.c - src/common/networking_linux.c + src/common/networking/networking_linux.c src/common/processing_linux.c src/common/sysctl.c src/detection/battery/battery_apple.c @@ -965,7 +965,7 @@ elseif(WIN32) list(APPEND LIBFASTFETCH_SRC src/common/io/io_windows.c src/common/netif/netif_windows.c - src/common/networking_windows.c + src/common/networking/networking_windows.c src/common/processing_windows.c src/detection/battery/battery_windows.c src/detection/bios/bios_windows.c @@ -1034,13 +1034,14 @@ elseif(WIN32) src/util/windows/version.c src/util/platform/FFPlatform_windows.c src/util/binary_windows.c + src/util/debug_windows.c ) elseif(SunOS) list(APPEND LIBFASTFETCH_SRC src/common/dbus.c src/common/io/io_unix.c src/common/netif/netif_bsd.c - src/common/networking_linux.c + src/common/networking/networking_linux.c src/common/processing_linux.c src/detection/battery/battery_nosupport.c src/detection/bios/bios_windows.c @@ -1121,7 +1122,7 @@ elseif(Haiku) src/common/dbus.c src/common/io/io_unix.c src/common/netif/netif_haiku.c - src/common/networking_linux.c + src/common/networking/networking_linux.c src/common/processing_linux.c src/detection/battery/battery_haiku.c src/detection/bios/bios_windows.c @@ -1478,10 +1479,6 @@ else() "GL" ) endif() -ff_lib_enable(OSMESA - "osmesa" - "OSMesa" -) ff_lib_enable(OPENCL "OpenCL" "OpenCL" @@ -1575,7 +1572,6 @@ elseif(APPLE) PRIVATE "-F /System/Library/PrivateFrameworks" PRIVATE "-weak_framework DisplayServices" PRIVATE "-weak_framework MediaRemote" - PRIVATE "-weak_framework Apple80211" ) elseif(WIN32) target_link_libraries(libfastfetch diff --git a/completions/fastfetch.bash b/completions/fastfetch.bash index 0e7464d6b7..681046ddf6 100644 --- a/completions/fastfetch.bash +++ b/completions/fastfetch.bash @@ -1,430 +1,121 @@ -__fastfetch_complete_help() -{ - local __ff_helps=( - "color" - "format" - "config" - "os-format" - "host-format" - "kernel-format" - "uptime-format" - "processes-format" - "packages-format" - "shell-format" - "display-format" - "de-format" - "wm-format" - "wmtheme-format" - "theme-format" - "icons-format" - "font-format" - "cursor-format" - "terminal-format" - "terminalfont-format" - "cpu-format" - "cpuusage-format" - "gpu-format" - "memory-format" - "swap-format" - "disk-format" - "battery-format" - "poweradapter-format" - "locale-format" - "localip-format" - "publicip-format" - "player-format" - "media-format" - "datetime-format" - "date-format" - "time-format" - "vulkan-format" - "opengl-format" - "opencl-format" - "bluetooth-format" - ) - COMPREPLY=($(compgen -W "${__ff_helps[*]}" -- "$CURRENT_WORD")) +#!/usr/bin/env bash + +_fastfetch() { + # Use Bash built-in variables directly + local cur="${COMP_WORDS[COMP_CWORD]}" + local prev="${COMP_WORDS[COMP_CWORD-1]}" + + # Check if Python is available + if ! command -v python3 &>/dev/null; then + return + fi + + # Handle standard completion cases + case "$prev" in + --color|--color-keys|--color-title|--color-output|--color-separator|--*-color|--*-key-color|--*-output-color|--logo-color-[1-9]|--percent-color-*|--temp-color-*) + local -a colors=("black" "red" "green" "yellow" "blue" "magenta" "cyan" "white" "default") + COMPREPLY=($(compgen -W "${colors[*]}" -- "$cur")) + return + ;; + --logo|-l) + local -a logos + readarray -t logos < <(fastfetch --list-logos autocompletion 2>/dev/null) + logos+=("none" "small") + COMPREPLY=($(compgen -W "${logos[*]}" -- "$cur")) + return + ;; + --config|-c) + local -a presets + readarray -t presets < <(fastfetch --list-presets autocompletion 2>/dev/null) + presets+=("none") + COMPREPLY=($(compgen -W "${presets[*]}" -- "$cur")) + # Also allow file path completion + if type _filedir &>/dev/null; then + _filedir + elif type compgen &>/dev/null; then + COMPREPLY+=($(compgen -f -- "$cur")) + fi + return + ;; + --structure|-s) + # Get all module names in lowercase only + local -a structures + readarray -t structures < <(fastfetch --list-modules autocompletion 2>/dev/null | cut -d':' -f1 | tr '[:upper:]' '[:lower:]') + COMPREPLY=($(compgen -W "${structures[*]}" -- "$cur")) + return + ;; + --help|-h) + local -a modules + readarray -t modules < <(fastfetch --list-modules autocompletion 2>/dev/null) + # Convert to lowercase and keep only module names + local -a module_names=() + for module in "${modules[@]}"; do + module_names+=($(echo "$module" | cut -d':' -f1 | tr '[:upper:]' '[:lower:]')-format) + done + module_names+=("format" "color") + COMPREPLY=($(compgen -W "${module_names[*]}" -- "$cur")) + return + ;; + --format) + COMPREPLY=($(compgen -W "json default" -- "$cur")) + return + ;; + --*-format) + # Format string completion, handle spaces + return + ;; + --*path*|--*file*|--gen-config*|--*data*) + # File path completion + if type _filedir &>/dev/null; then + _filedir + elif type compgen &>/dev/null; then + COMPREPLY=($(compgen -f -- "$cur")) + fi + return + ;; + esac + + # If not a special option, generate all possible options + if [[ "$cur" == -* ]]; then + local -a opts + readarray -t opts < <(python3 - "$cur" <<'EOF' +import json +import sys +import subprocess + +def main(current): + try: + # Use fastfetch --help-raw to get option data + output = subprocess.check_output(['fastfetch', '--help-raw'], stderr=subprocess.DEVNULL) + data = json.loads(output) + + for category in data.values(): + for flag in category: + if flag.get("pseudo", False): + continue + + if "short" in flag: + print(f"-{flag['short']}") + + if "long" in flag: + if flag["long"] == "logo-color-[1-9]": + for i in range(1, 10): + print(f"--logo-color-{i}") + else: + print(f"--{flag['long']}") + except Exception: + # If error occurs, return no options + pass + +if __name__ == "__main__": + main(sys.argv[1]) +EOF +) + COMPREPLY=($(compgen -W "${opts[*]}" -- "$cur")) + fi + + return 0 } -__fastfetch_complete_bool() -{ - COMPREPLY=($(compgen -W "true false" -- "$CURRENT_WORD")) -} - -__fastfetch_complete_string() -{ - if [[ $CURRENT_WORD != "" ]]; then - COMPREPLY=("$CURRENT_WORD") - fi -} - -__fastfetch_complete_path() -{ - COMPREPLY=($(compgen -A file -- "$CURRENT_WORD")) -} - -__fastfetch_complete_logo() -{ - COMPREPLY=($(compgen -W "$(fastfetch --list-logos autocompletion)" -- "$CURRENT_WORD")) -} - -__fastfetch_complete_logo_type() -{ - local __ff_logo_types=( - "auto" - "builtin" - "file" - "raw" - "sixel" - "kitty" - "chafa" - ) - COMPREPLY=($(compgen -W "${__ff_logo_types[*]}" -- "$CURRENT_WORD")) -} - -__fastfetch_complete_binary_prefix() -{ - local __ff_size_binary_prefixes=( - "iec" - "si" - "jedec" - ) - COMPREPLY=($(compgen -W "${__ff_size_binary_prefixes[*]}" -- "$CURRENT_WORD")) -} - -__fastfetch_complete_gl() -{ - local __ff_gl_types=( - "auto" - "egl" - "glx" - "osmesa" - ) - COMPREPLY=($(compgen -W "${__ff_gl_types[*]}" -- "$CURRENT_WORD")) -} - -__fastfetch_complete_option() -{ - local FF_OPTIONS_ALL=( - "${FF_OPTIONS_BOOL[@]}" - "${FF_OPTIONS_STRING[@]}" - "${FF_OPTIONS_PATH[@]}" - "${FF_OPTIONS_LOGO[@]}" - "${FF_OPTIONS_LOGO_TYPE[@]}" - "${FF_OPTIONS_BINARY_PREFIX[@]}" - "${FF_OPTIONS_OPENGL[@]}" - ) - - if [[ $WORD_COUND -lt 3 ]]; then - FF_OPTIONS_ALL+=( - "${FF_OPTIONS_SINGLE[@]}" - "${FF_OPTIONS_HELP[@]}" - ) - fi - - for ff_word in ${COMP_WORDS[@]}; do - if [[ $ff_word == $CURRENT_WORD ]]; then - break - fi - - FF_OPTIONS_ALL=("${FF_OPTIONS_ALL[@]/$ff_word}") - done - - COMPREPLY=($(compgen -W "${FF_OPTIONS_ALL[*]}" -- "$CURRENT_WORD")) -} - -__fastfetch_previous_matches() -{ - for ff_option in "$@"; do - if [[ $ff_option == "$PREVIOUS_WORD" ]]; then - return 0 - fi - done - return 1 -} - -__fastfetch_completion() -{ - local CURRENT_WORD="${COMP_WORDS[$COMP_CWORD]}" - local PREVIOUS_WORD="${COMP_WORDS[$COMP_CWORD - 1]}" - local WORD_COUND="${#COMP_WORDS[@]}" - - local FF_OPTIONS_SINGLE=( - "-v" - "--version" - "--list-logos" - "--list-modules" - "--list-presets" - "--list-features" - "--print-logos" - "--print-config-system" - "--print-config-user" - "--print-structure" - "--gen-config" - "--gen-config-force" - ) - - local FF_OPTIONS_HELP=( - "-h" - "--help" - ) - - local FF_OPTIONS_BOOL=( - "-r" - "--show-errors" - "--logo-print-remaining" - "--multithreading" - "--stat" - "--allow-slow-operations" - "--disable-linewrap" - "--hide-cursor" - "--cpu-temp" - "--gpu-temp" - "--battery-temp" - "--display-precise-refresh-rate" - "--localip-show-ipv4" - "--localip-show-ipv6" - "--localip-show-flags" - "--localip-show-loop" - "--localip-show-mtu" - "--localip-show-speed" - "--localip-name-prefix" - "--localip-compact-type" - "--escape-bedrock" - "--pipe" - "--title-fqdn" - "--disk-folders" - "--disk-show-external" - "--disk-show-hidden" - "--disk-show-subvolumes" - "--gpu-hide-integrated" - "--gpu-hide-discrete" - "--gpu-force-method" - "--disk-show-unknown" - "--bluetooth-show-disconnected" - ) - - local FF_OPTIONS_STRING=( - "--logo-type" - "--logo-padding" - "--logo-padding-left" - "--logo-padding-right" - "--logo-padding-top" - "--logo-color-1" - "--logo-color-2" - "--logo-color-3" - "--logo-color-4" - "--logo-color-5" - "--logo-color-6" - "--logo-color-7" - "--logo-color-8" - "--logo-color-9" - "--logo-width" - "--logo-height" - "--color" - "--color-keys" - "--color-title" - "--display-compact-type" - "--separator" - "-s" - "--structure" - "--player-name" - "--percent-type" - "--publicip-url" - "--publicip-timeout" - "--weather-output-format" - "--weather-timeout" - "--os-key" - "--os-format" - "--os-key-color" - "--host-key" - "--host-format" - "--host-key-color" - "--kernel-key" - "--kernel-format" - "--kernel-key-color" - "--uptime-key" - "--uptime-format" - "--uptime-key-color" - "--processes-key" - "--processes-format" - "--processes-key-color" - "--packages-key" - "--packages-format" - "--packages-key-color" - "--shell-key" - "--shell-format" - "--shell-key-color" - "--display-key" - "--display-format" - "--display-key-color" - "--de-key" - "--de-format" - "--de-key-color" - "--wm-key" - "--wm-format" - "--wm-key-color" - "--wmtheme-key" - "--wmtheme-format" - "--wmtheme-key-color" - "--theme-key" - "--theme-format" - "--theme-key-color" - "--icons-key" - "--icons-format" - "--icons-key-color" - "--font-key" - "--font-format" - "--font-key-color" - "--cursor-key" - "--cursor-format" - "--cursor-key-color" - "--terminal-key" - "--terminal-format" - "--terminal-key-color" - "--terminalfont-key" - "--terminalfont-format" - "--terminalfont-key-color" - "--cpu-key" - "--cpu-format" - "--cpu-key-color" - "--cpu-useage-key" - "--cpu-useage-format" - "--cpu-useage-key-color" - "--gpu-key" - "--gpu-format" - "--gpu-key-color" - "--memory-key" - "--memory-format" - "--memory-key-color" - "--swap-key" - "--swap-format" - "--swap-key-color" - "--disk-key" - "--disk-format" - "--disk-key-color" - "--battery-key" - "--battery-format" - "--battery-key-color" - "--poweradapter-key" - "--poweradapter-format" - "--poweradapter-key-color" - "--locale-key" - "--locale-format" - "--locale-key-color" - "--localip-key" - "--localip-format" - "--localip-key-color" - "--publicip-key" - "--publicip-format" - "--publicip-key-color" - "--wifi-key" - "--wifi-format" - "--wifi-key-color" - "--weather-key" - "--weather-format" - "--weather-key-color" - "--player-key" - "--player-format" - "--player-key-color" - "--media-key" - "--media-format" - "--media-key-color" - "--datetime-key" - "--datetime-format" - "--datetime-key-color" - "--date-key" - "--date-format" - "--date-key-color" - "--time-key" - "--time-format" - "--time-key-color" - "--vulkan-key" - "--vulkan-format" - "--vulkan-key-color" - "--opengl-key" - "--opengl-format" - "--opengl-key-color" - "--opencl-key" - "--opencl-format" - "--opencl-key-color" - "--users-key" - "--users-format" - "--users-key-color" - "--users-myself-only" - "--bluetooth-key" - "--bluetooth-format" - "--bluetooth-key-color" - ) - - local FF_OPTIONS_PATH=( - "-c" - "--config" - "--lib-vulkan" - "--lib-wayland" - "--lib-xcb-randr" - "--lib-xcb" - "--lib-xrandr" - "--lib-X11" - "--lib-gio" - "--lib-dconf" - "--lib-dbus" - "--lib-xfconf" - "--lib-sqlite3" - "--lib-rpm" - "--lib-imagemagick" - "--lib-z" - "--lib-chafa" - "--lib-egl" - "--lib-glx" - "--lib-osmesa" - "--lib-opencl" - "--lib-pulse" - "--lib-ddcutil" - "--lib-nm" - ) - - local FF_OPTIONS_LOGO=( - "-l" - "--logo" - ) - - local FF_OPTIONS_LOGO_TYPE=( - "--logo-type" - ) - - local FF_OPTIONS_BINARY_PREFIX=( - "--binary-prefix" - ) - - local FF_OPTIONS_OPENGL=( - "--opengl-type" - ) - - if __fastfetch_previous_matches "${FF_OPTIONS_SINGLE[@]}"; then - return - elif [[ $WORD_COUND -gt 3 && ( ${COMP_WORDS[$COMP_CWORD - 2]} == "--help" || ${COMP_WORDS[$COMP_CWORD - 2]} == "-h" ) ]]; then - return - elif [[ $CURRENT_WORD == "-"* ]]; then - __fastfetch_complete_option - elif __fastfetch_previous_matches "${FF_OPTIONS_HELP[@]}"; then - __fastfetch_complete_help - elif __fastfetch_previous_matches "${FF_OPTIONS_BOOL[@]}"; then - __fastfetch_complete_bool - elif __fastfetch_previous_matches "${FF_OPTIONS_STRING[@]}"; then - __fastfetch_complete_string - elif __fastfetch_previous_matches "${FF_OPTIONS_PATH[@]}"; then - __fastfetch_complete_path - elif __fastfetch_previous_matches "${FF_OPTIONS_LOGO[@]}"; then - __fastfetch_complete_logo - elif __fastfetch_previous_matches "${FF_OPTIONS_LOGO_TYPE[@]}"; then - __fastfetch_complete_logo_type - elif __fastfetch_previous_matches "${FF_OPTIONS_BINARY_PREFIX[@]}"; then - __fastfetch_complete_binary_prefix - elif __fastfetch_previous_matches "${FF_OPTIONS_OPENGL[@]}"; then - __fastfetch_complete_gl - else - __fastfetch_complete_option - fi -} - -complete -F __fastfetch_completion fastfetch +# Register completion +complete -F _fastfetch fastfetch diff --git a/debian/changelog b/debian/changelog index 30b84d86ca..18eaa925ed 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +fastfetch (2.38.0) jammy; urgency=medium + + * Update to 2.38.0 + + -- Carter Li Wed, 05 Mar 2025 14:56:24 +0800 + fastfetch (2.37.0) jammy; urgency=medium * Update to 2.37.0 diff --git a/debian/control b/debian/control index 0f35707a52..e161d94dc1 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: fastfetch Section: universe/utils Priority: optional Maintainer: Carter Li -Build-Depends: libvulkan-dev, libwayland-dev, libxrandr-dev, libxcb-randr0-dev, libdconf-dev, libdbus-1-dev, libmagickcore-dev, libxfconf-0-dev, libsqlite3-dev, librpm-dev, libegl-dev, libglx-dev, libosmesa6-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 +Build-Depends: libvulkan-dev, libwayland-dev, libxrandr-dev, libxcb-randr0-dev, libdconf-dev, libdbus-1-dev, libmagickcore-dev, libxfconf-0-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 Standards-Version: 4.0.0 Homepage: https://github.com/fastfetch-cli/fastfetch diff --git a/debian/files b/debian/files index f056cc7e5b..475dca9323 100644 --- a/debian/files +++ b/debian/files @@ -1 +1 @@ -fastfetch_2.37.0_source.buildinfo universe/utils optional +fastfetch_2.38.0_source.buildinfo universe/utils optional diff --git a/doc/json_schema.json b/doc/json_schema.json index ae1f3f9d87..eaf5f4c064 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -2563,8 +2563,7 @@ "enum": [ "auto", "egl", - "glx", - "osmesa" + "glx" ], "default": "auto" }, diff --git a/src/common/init.c b/src/common/init.c index 91d30d64cb..1db2aa01d7 100644 --- a/src/common/init.c +++ b/src/common/init.c @@ -227,9 +227,6 @@ void ffListFeatures(void) #if FF_HAVE_GLX "glx\n" #endif - #if FF_HAVE_OSMESA - "osmesa\n" - #endif #if FF_HAVE_OPENCL "opencl\n" #endif diff --git a/src/common/netif/netif.c b/src/common/netif/netif.c index ec317454b8..da5a838594 100644 --- a/src/common/netif/netif.c +++ b/src/common/netif/netif.c @@ -3,7 +3,7 @@ #ifndef _WIN32 #include #else - #define IF_NAMESIZE 16 + #define IF_NAMESIZE 0 #endif bool ffNetifGetDefaultRouteImpl(char iface[IF_NAMESIZE + 1], uint32_t* ifIndex); diff --git a/src/common/netif/netif_bsd.c b/src/common/netif/netif_bsd.c index 91a270905b..f85c39a629 100644 --- a/src/common/netif/netif_bsd.c +++ b/src/common/netif/netif_bsd.c @@ -1,6 +1,7 @@ #include "netif.h" #include "common/io/io.h" +#include "util/mallocHelper.h" #include #include @@ -8,13 +9,17 @@ #include #include +#if defined(__OpenBSD__) || defined(__DragonFly__) + #include +#endif + #define ROUNDUP2(a, n) ((a) > 0 ? (1 + (((a) - 1U) | ((n) - 1))) : (n)) #if defined(__APPLE__) # define ROUNDUP(a) ROUNDUP2((a), sizeof(int)) #elif defined(__NetBSD__) # define ROUNDUP(a) ROUNDUP2((a), sizeof(uint64_t)) -#elif defined(__FreeBSD__) +#elif defined(__FreeBSD__) || defined(__DragonFly__) # define ROUNDUP(a) ROUNDUP2((a), sizeof(int)) #elif defined(__OpenBSD__) # define ROUNDUP(a) ROUNDUP2((a), sizeof(int)) @@ -47,6 +52,39 @@ get_rt_address(struct rt_msghdr *rtm, int desired) bool ffNetifGetDefaultRouteImpl(char iface[IF_NAMESIZE + 1], uint32_t* ifIndex) { + #if defined(__OpenBSD__) || defined(__DragonFly__) + int mib[6] = {CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_FLAGS, RTF_GATEWAY}; + size_t needed; + + if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) + return false; + + FF_AUTO_FREE char* buf = malloc(needed); + + if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) + return false; + + char* lim = buf + needed; + struct rt_msghdr* rtm; + for (char* next = buf; next < lim; next += rtm->rtm_msglen) + { + rtm = (struct rt_msghdr *)next; + struct sockaddr* sa = (struct sockaddr *)(rtm + 1); + + if ((rtm->rtm_flags & RTF_GATEWAY) && (sa->sa_family == AF_INET)) + { + struct sockaddr_dl* sdl = (struct sockaddr_dl *)get_rt_address(rtm, RTA_IFP); + if (sdl->sdl_family == AF_LINK) + { + assert(sdl->sdl_nlen <= IF_NAMESIZE); + memcpy(iface, sdl->sdl_data, sdl->sdl_nlen); + iface[sdl->sdl_nlen] = '\0'; + *ifIndex = sdl->sdl_index; + return true; + } + } + } + #else //https://github.com/hashPirate/copenheimer-masscan-fork/blob/36f1ed9f7b751a7dccd5ed27874e2e703db7d481/src/rawsock-getif.c#L104 FF_AUTO_CLOSE_FD int pfRoute = socket(PF_ROUTE, SOCK_RAW, AF_INET); @@ -106,5 +144,7 @@ bool ffNetifGetDefaultRouteImpl(char iface[IF_NAMESIZE + 1], uint32_t* ifIndex) return false; } } + #endif + return false; } diff --git a/src/common/netif/netif_windows.c b/src/common/netif/netif_windows.c index 8607ed4866..6042dd8d6e 100644 --- a/src/common/netif/netif_windows.c +++ b/src/common/netif/netif_windows.c @@ -1,27 +1,36 @@ #include "netif.h" #include "util/mallocHelper.h" +#include // AF_INET6, IN6_IS_ADDR_UNSPECIFIED #include -bool ffNetifGetDefaultRouteImpl(FF_MAYBE_UNUSED char* iface/* unsupported */, uint32_t* ifIndex) +bool ffNetifGetDefaultRouteImpl(FF_MAYBE_UNUSED char iface[IF_NAMESIZE + 1], uint32_t* ifIndex) { - ULONG size = 0; - if (GetIpForwardTable(NULL, &size, TRUE) != ERROR_INSUFFICIENT_BUFFER) - return false; + PMIB_IPFORWARD_TABLE2 pIpForwardTable = NULL; + DWORD result = GetIpForwardTable2(AF_UNSPEC, &pIpForwardTable); - FF_AUTO_FREE MIB_IPFORWARDTABLE* pIpForwardTable = (MIB_IPFORWARDTABLE*) malloc(size); - if (GetIpForwardTable(pIpForwardTable, &size, TRUE) != ERROR_SUCCESS) + if (result != NO_ERROR) return false; - for (uint32_t i = 0; i < pIpForwardTable->dwNumEntries; ++i) + bool foundDefault = false; + + for (ULONG i = 0; i < pIpForwardTable->NumEntries; ++i) { - MIB_IPFORWARDROW* ipForwardRow = &pIpForwardTable->table[i]; - if (ipForwardRow->dwForwardDest == 0 && ipForwardRow->dwForwardMask == 0) + MIB_IPFORWARD_ROW2* row = &pIpForwardTable->Table[i]; + + if ((row->DestinationPrefix.PrefixLength == 0) && + ((row->DestinationPrefix.Prefix.Ipv4.sin_family == AF_INET && + row->DestinationPrefix.Prefix.Ipv4.sin_addr.S_un.S_addr == 0) || + (row->DestinationPrefix.Prefix.Ipv6.sin6_family == AF_INET6 && + IN6_IS_ADDR_UNSPECIFIED(&row->DestinationPrefix.Prefix.Ipv6.sin6_addr)))) { - *ifIndex = ipForwardRow->dwForwardIfIndex; + *ifIndex = row->InterfaceIndex; + foundDefault = true; break; } } - return true; + FreeMibTable(pIpForwardTable); + + return foundDefault; } diff --git a/src/common/networking.h b/src/common/networking/networking.h similarity index 61% rename from src/common/networking.h rename to src/common/networking/networking.h index 8122e4eb00..6662f3117d 100644 --- a/src/common/networking.h +++ b/src/common/networking/networking.h @@ -7,14 +7,16 @@ #include #endif +struct addrinfo; + typedef struct FFNetworkingState { #ifdef _WIN32 uintptr_t sockfd; OVERLAPPED overlapped; #else int sockfd; - FFstrbuf host; FFstrbuf command; + struct addrinfo* addr; #ifdef FF_HAVE_THREADS FFThreadType thread; @@ -23,7 +25,14 @@ typedef struct FFNetworkingState { uint32_t timeout; bool ipv6; + bool compression; // if true, HTTP content compression will be enabled if supported + bool tfo; // if true, TCP Fast Open will be attempted first, and fallback to traditional connection if it fails } FFNetworkingState; const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, const char* path, const char* headers); const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer); + +#ifdef FF_HAVE_ZLIB +const char* ffNetworkingLoadZlibLibrary(void); +bool ffNetworkingDecompressGzip(FFstrbuf* buffer, char* headerEnd); +#endif diff --git a/src/common/networking/networking_common.c b/src/common/networking/networking_common.c new file mode 100644 index 0000000000..df04eb3f0d --- /dev/null +++ b/src/common/networking/networking_common.c @@ -0,0 +1,194 @@ +#include "fastfetch.h" +#include "common/library.h" +#include "common/networking/networking.h" +#include "util/stringUtils.h" +#include "util/debug.h" + +#ifdef FF_HAVE_ZLIB +#include + +struct FFZlibLibrary +{ + FF_LIBRARY_SYMBOL(inflateInit2_) + FF_LIBRARY_SYMBOL(inflate) + FF_LIBRARY_SYMBOL(inflateEnd) + FF_LIBRARY_SYMBOL(inflateGetHeader) + + bool inited; +} zlibData; + +const char* ffNetworkingLoadZlibLibrary(void) +{ + if (!zlibData.inited) + { + zlibData.inited = true; + FF_LIBRARY_LOAD(zlib, "dlopen libz failed", + #ifdef _WIN32 + "zlib1" + #else + "libz" + #endif + FF_LIBRARY_EXTENSION, 2) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflateInit2_) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflate) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflateEnd) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflateGetHeader) + zlib = NULL; // don't auto dlclose + } + return zlibData.ffinflateEnd == NULL ? "Failed to load libz" : NULL; +} + +// Try to pre-read gzip header to determine uncompressed size +static uint32_t guessGzipOutputSize(const void* data, uint32_t dataSize) +{ + // gzip file format: http://www.zlib.org/rfc-gzip.html + if (dataSize < 10 || ((const uint8_t*)data)[0] != 0x1f || ((const uint8_t*)data)[1] != 0x8b) + return 0; + + // Uncompressed size in gzip format is stored in the last 4 bytes, but only valid if data is less than 4GB + if (dataSize > 18) { + // Get ISIZE value from the end of file (little endian) + const uint8_t* tail = (const uint8_t*)data + dataSize - 4; + uint32_t uncompressedSize = (uint32_t) tail[0] | ((uint32_t) tail[1] << 8u) | ((uint32_t) tail[2] << 16u) | ((uint32_t) tail[3] << 24u); + + // For valid gzip files, this value is the length of the uncompressed data modulo 2^32 + if (uncompressedSize > 0) { + FF_DEBUG("Read uncompressed size from GZIP trailer: %u bytes", uncompressedSize); + // Add some margin to the estimated size for safety + return uncompressedSize + 64; + } + } + + // If unable to get size from trailer or size is 0, use estimated value + // Typically, text data compression ratio is between 3-5x, we use the larger value + uint32_t estimatedSize = dataSize * 5; + FF_DEBUG("Unable to read exact uncompressed size, estimated as 5x of compressed data: %u bytes", estimatedSize); + return estimatedSize; +} + +// Decompress gzip content +bool ffNetworkingDecompressGzip(FFstrbuf* buffer, char* headerEnd) +{ + // Calculate header size + uint32_t headerSize = (uint32_t) (headerEnd - buffer->chars); + + *headerEnd = '\0'; // Replace delimiter with null character for easier processing + // Ensure Content-Encoding is in response headers, not in response body + bool hasGzip = strcasestr(buffer->chars, "\nContent-Encoding: gzip") != NULL; + *headerEnd = '\r'; // Restore delimiter + + if (!hasGzip) { + FF_DEBUG("No gzip compressed content detected, skipping decompression"); + return true; + } + + FF_DEBUG("Gzip compressed content detected, preparing for decompression"); + + const char* bodyStart = headerEnd + 4; // Skip delimiter + + // Calculate compressed content size + uint32_t compressedSize = buffer->length - headerSize - 4; + + if (compressedSize <= 0) { + // No content to decompress + FF_DEBUG("Compressed content size is 0, skipping decompression"); + return true; + } + + // Check if content is actually in gzip format (gzip header magic is 0x1f 0x8b) + if (compressedSize < 2 || (uint8_t)bodyStart[0] != 0x1f || (uint8_t)bodyStart[1] != 0x8b) { + FF_DEBUG("Content is not valid gzip format, skipping decompression"); + return false; + } + + // Predict uncompressed size + uint32_t estimatedSize = guessGzipOutputSize(bodyStart, compressedSize); + + // Create decompression buffer with estimated size + FF_STRBUF_AUTO_DESTROY decompressedBuffer = ffStrbufCreateA(estimatedSize > 0 ? estimatedSize : compressedSize * 5); + FF_DEBUG("Created decompression buffer: %u bytes", decompressedBuffer.allocated); + + // Initialize decompression + z_stream zs = { + .zalloc = Z_NULL, + .zfree = Z_NULL, + .opaque = Z_NULL, + .avail_in = (uInt)compressedSize, + .next_in = (Bytef*)bodyStart, + .avail_out = (uInt)ffStrbufGetFree(&decompressedBuffer), + .next_out = (Bytef*)decompressedBuffer.chars, + }; + + // Initialize decompression engine + if (zlibData.ffinflateInit2_(&zs, 16 + MAX_WBITS, ZLIB_VERSION, (int)sizeof(z_stream)) != Z_OK) { + FF_DEBUG("Failed to initialize decompression engine"); + return false; + } + uInt availableOut = zs.avail_out; + + // Perform decompression + int result = zlibData.ffinflate(&zs, Z_FINISH); + + // If output buffer is insufficient, try to extend buffer + while (result == Z_BUF_ERROR || (result != Z_STREAM_END && zs.avail_out == 0)) + { + FF_DEBUG("Output buffer insufficient, trying to extend"); + + // Save already decompressed data amount + uint32_t alreadyDecompressed = (uint32_t)(availableOut - zs.avail_out); + decompressedBuffer.length += alreadyDecompressed; + decompressedBuffer.chars[decompressedBuffer.length] = '\0'; // Ensure null-terminated string + + ffStrbufEnsureFree(&decompressedBuffer, decompressedBuffer.length / 2); + + // Set output parameters to point to new buffer + zs.avail_out = (uInt)ffStrbufGetFree(&decompressedBuffer); + zs.next_out = (Bytef*)(decompressedBuffer.chars + decompressedBuffer.length); + availableOut = zs.avail_out; + + // Decompress again + result = zlibData.ffinflate(&zs, Z_FINISH); + } + + zlibData.ffinflateEnd(&zs); + + // Calculate decompressed size + uint32_t decompressedSize = (uint32_t)(availableOut - zs.avail_out); + decompressedBuffer.length += decompressedSize; + decompressedBuffer.chars[decompressedSize] = '\0'; // Ensure null-terminated string + FF_DEBUG("Successfully decompressed %u bytes compressed data to %u bytes", compressedSize, decompressedBuffer.length); + + // Modify Content-Length header and remove Content-Encoding header + FF_STRBUF_AUTO_DESTROY newBuffer = ffStrbufCreateA(headerSize + decompressedSize + 64); + + char* line = NULL; + size_t len = 0; + while (ffStrbufGetline(&line, &len, buffer)) + { + if (ffStrStartsWithIgnCase(line, "Content-Encoding:")) + { + continue; + } + else if (ffStrStartsWithIgnCase(line, "Content-Length:")) + { + ffStrbufAppendF(&newBuffer, "Content-Length: %u\r\n", decompressedSize); + continue; + } + else if (line[0] == '\r') + { + ffStrbufAppendS(&newBuffer, "\r\n"); + ffStrbufGetlineRestore(&line, &len, buffer); + break; + } + + ffStrbufAppendS(&newBuffer, line); + ffStrbufAppendC(&newBuffer, '\n'); + } + + ffStrbufAppend(&newBuffer, &decompressedBuffer); + ffStrbufDestroy(buffer); + ffStrbufInitMove(buffer, &newBuffer); + + return true; +} +#endif // FF_HAVE_ZLIB diff --git a/src/common/networking/networking_linux.c b/src/common/networking/networking_linux.c new file mode 100644 index 0000000000..731ca2d1a2 --- /dev/null +++ b/src/common/networking/networking_linux.c @@ -0,0 +1,500 @@ +#include "fastfetch.h" +#include "common/networking/networking.h" +#include "common/time.h" +#include "common/library.h" +#include "util/stringUtils.h" +#include "util/mallocHelper.h" +#include "util/debug.h" + +#include +#include +#include +#include +#include +#include // For FreeBSD +#include +#include +#include + +static const char* tryNonThreadingFastPath(FFNetworkingState* state) +{ + #if defined(TCP_FASTOPEN) || __APPLE__ + + if (!state->tfo) + { + #ifdef __linux__ + // Linux doesn't support sendto() on unconnected sockets + FF_DEBUG("TCP Fast Open disabled, skipping"); + return "TCP Fast Open disabled"; + #endif + } + else + { + FF_DEBUG("Attempting to use TCP Fast Open to connect"); + + #ifndef __APPLE__ // On macOS, TCP_FASTOPEN doesn't seem to be needed + // Set TCP Fast Open + #ifdef __linux__ + int flag = 5; // the queue length of pending packets + #else + int flag = 1; // enable TCP Fast Open + #endif + if (setsockopt(state->sockfd, IPPROTO_TCP, + #ifdef __APPLE__ + // https://github.com/rust-lang/libc/pull/3135 + 0x218 // TCP_FASTOPEN_FORCE_ENABLE + #else + TCP_FASTOPEN + #endif + , &flag, sizeof(flag)) != 0) { + FF_DEBUG("Failed to set TCP_FASTOPEN option: %s", strerror(errno)); + return "setsockopt(TCP_FASTOPEN) failed"; + } else { + #ifdef __linux__ + FF_DEBUG("Successfully set TCP_FASTOPEN option, queue length: %d", flag); + #elif defined(__APPLE__) + FF_DEBUG("Successfully set TCP_FASTOPEN_FORCE_ENABLE option"); + #else + FF_DEBUG("Successfully set TCP_FASTOPEN option"); + #endif + } + #endif + } + + #ifndef __APPLE__ + FF_DEBUG("Using sendto() + MSG_DONTWAIT to send %u bytes of data", state->command.length); + ssize_t sent = sendto(state->sockfd, + state->command.chars, + state->command.length, + #ifdef MSG_FASTOPEN + MSG_FASTOPEN | + #endif + #ifdef MSG_NOSIGNAL + MSG_NOSIGNAL | + #endif + MSG_DONTWAIT, + state->addr->ai_addr, + state->addr->ai_addrlen); + #else + if (fcntl(state->sockfd, F_SETFL, O_NONBLOCK) == -1) { + FF_DEBUG("fcntl(F_SETFL) failed: %s", strerror(errno)); + return "fcntl(F_SETFL) failed"; + } + FF_DEBUG("Using connectx() to send %u bytes of data", state->command.length); + // Use connectx to establish connection and send data in one call + size_t sent; + if (connectx(state->sockfd, + &(sa_endpoints_t) { + .sae_dstaddr = state->addr->ai_addr, + .sae_dstaddrlen = state->addr->ai_addrlen, + }, + SAE_ASSOCID_ANY, state->tfo ? CONNECT_DATA_IDEMPOTENT : 0, + &(struct iovec) { + .iov_base = state->command.chars, + .iov_len = state->command.length, + }, 1, &sent, NULL) != 0) sent = 0; + if (fcntl(state->sockfd, F_SETFL, 0) == -1) { + FF_DEBUG("fcntl(F_SETFL) failed: %s", strerror(errno)); + return "fcntl(F_SETFL) failed"; + } + #endif + if (sent > 0 || (errno == EAGAIN || errno == EWOULDBLOCK + #ifdef __APPLE__ + // On macOS EINPROGRESS means the connection cannot be completed immediately + // On Linux, it means the TFO cookie is not available locally + || errno == EINPROGRESS + #endif + )) + { + FF_DEBUG( + #ifdef __APPLE__ + "connectx()" + #else + "sendto()" + #endif + " %s (sent=%zd, errno=%d: %s)", errno == 0 ? "succeeded" : "was in progress", + sent, errno, strerror(errno)); + freeaddrinfo(state->addr); + state->addr = NULL; + ffStrbufDestroy(&state->command); + return NULL; + } + + FF_DEBUG( + #ifdef __APPLE__ + "connectx()" + #else + "sendto()" + #endif + " failed: %s (errno=%d)", strerror(errno), errno); + #ifdef __APPLE__ + return "connectx() failed"; + #else + return "sendto() failed"; + #endif + #else + FF_UNUSED(state); + return "TFO support is not available"; + #endif +} + +// Traditional connect and send function +static const char* connectAndSend(FFNetworkingState* state) +{ + const char* ret = NULL; + FF_DEBUG("Using traditional connection method to connect"); + + FF_DEBUG("Attempting connect() to server..."); + if(connect(state->sockfd, state->addr->ai_addr, state->addr->ai_addrlen) == -1) + { + FF_DEBUG("connect() failed: %s (errno=%d)", strerror(errno), errno); + ret = "connect() failed"; + goto error; + } + FF_DEBUG("connect() succeeded"); + + FF_DEBUG("Attempting to send %u bytes of data...", state->command.length); + if(send(state->sockfd, state->command.chars, state->command.length, 0) < 0) + { + FF_DEBUG("send() failed: %s (errno=%d)", strerror(errno), errno); + ret = "send() failed"; + goto error; + } + FF_DEBUG("Data sent successfully"); + + goto exit; + +error: + FF_DEBUG("Error occurred, closing socket"); + close(state->sockfd); + state->sockfd = -1; + +exit: + FF_DEBUG("Releasing address info and other resources"); + freeaddrinfo(state->addr); + state->addr = NULL; + ffStrbufDestroy(&state->command); + + return ret; +} + +FF_THREAD_ENTRY_DECL_WRAPPER(connectAndSend, FFNetworkingState*); + +// Parallel DNS resolution and socket creation +static const char* initNetworkingState(FFNetworkingState* state, const char* host, const char* path, const char* headers) +{ + FF_DEBUG("Initializing network connection state: host=%s, path=%s", host, path); + + // Initialize command and host information + ffStrbufInitA(&state->command, 64); + ffStrbufAppendS(&state->command, "GET "); + ffStrbufAppendS(&state->command, path); + ffStrbufAppendS(&state->command, " HTTP/1.1\nHost: "); + ffStrbufAppendS(&state->command, host); + ffStrbufAppendS(&state->command, "\r\n"); + + // Add extra optimized HTTP headers + ffStrbufAppendS(&state->command, "Connection: close\r\n"); // Explicitly tell the server we don't need to keep the connection + + // If compression needs to be enabled + if (state->compression) { + FF_DEBUG("Enabling HTTP content compression"); + ffStrbufAppendS(&state->command, "Accept-Encoding: gzip\r\n"); + } + + ffStrbufAppendS(&state->command, headers); + ffStrbufAppendS(&state->command, "\r\n"); + + #ifdef FF_HAVE_THREADS + state->thread = 0; + FF_DEBUG("Thread ID initialized to 0"); + #endif + + const char* ret = NULL; + + struct addrinfo hints = { + .ai_family = state->ipv6 ? AF_INET6 : AF_INET, + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_NUMERICSERV + }; + + FF_DEBUG("Resolving address: %s (%s)", host, state->ipv6 ? "IPv6" : "IPv4"); + // Use AI_NUMERICSERV flag to indicate the service is a numeric port, reducing parsing time + + if(getaddrinfo(host, "80", &hints, &state->addr) != 0) + { + FF_DEBUG("getaddrinfo() failed: %s", gai_strerror(errno)); + ret = "getaddrinfo() failed"; + goto error; + } + FF_DEBUG("Address resolution successful"); + + FF_DEBUG("Creating socket"); + state->sockfd = socket(state->addr->ai_family, state->addr->ai_socktype, state->addr->ai_protocol); + if(state->sockfd == -1) + { + FF_DEBUG("socket() failed: %s (errno=%d)", strerror(errno), errno); + ret = "socket() failed"; + goto error; + } + FF_DEBUG("Socket creation successful: fd=%d", state->sockfd); + + int flag = 1; + #ifdef TCP_NODELAY + // Disable Nagle's algorithm to reduce small packet transmission delay + if (setsockopt(state->sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag)) != 0) { + FF_DEBUG("Failed to set TCP_NODELAY: %s", strerror(errno)); + } else { + FF_DEBUG("Successfully disabled Nagle's algorithm"); + } + #endif + + #ifdef TCP_QUICKACK + // Set TCP_QUICKACK option to avoid delayed acknowledgments + if (setsockopt(state->sockfd, IPPROTO_TCP, TCP_QUICKACK, &flag, sizeof(flag)) != 0) { + FF_DEBUG("Failed to set TCP_QUICKACK: %s", strerror(errno)); + } else { + FF_DEBUG("Successfully enabled TCP quick acknowledgment"); + } + #endif + + if (state->timeout > 0) + { + FF_DEBUG("Setting connection timeout: %u ms", state->timeout); + FF_MAYBE_UNUSED uint32_t sec = state->timeout / 1000; + if (sec == 0) sec = 1; + + #ifdef TCP_CONNECTIONTIMEOUT + FF_DEBUG("Using TCP_CONNECTIONTIMEOUT: %u seconds", sec); + setsockopt(state->sockfd, IPPROTO_TCP, TCP_CONNECTIONTIMEOUT, &sec, sizeof(sec)); + #elif defined(TCP_KEEPINIT) + FF_DEBUG("Using TCP_KEEPINIT: %u seconds", sec); + setsockopt(state->sockfd, IPPROTO_TCP, TCP_KEEPINIT, &sec, sizeof(sec)); + #elif defined(TCP_USER_TIMEOUT) + FF_DEBUG("Using TCP_USER_TIMEOUT: %u milliseconds", state->timeout); + setsockopt(state->sockfd, IPPROTO_TCP, TCP_USER_TIMEOUT, &state->timeout, sizeof(state->timeout)); + #else + FF_DEBUG("Current platform does not support TCP connection timeout"); + #endif + } + + return NULL; + +error: + FF_DEBUG("Error occurred during initialization"); + if (state->addr != NULL) + { + FF_DEBUG("Releasing address information"); + freeaddrinfo(state->addr); + state->addr = NULL; + } + + if (state->sockfd > 0) + { + FF_DEBUG("Closing socket: fd=%d", state->sockfd); + close(state->sockfd); + state->sockfd = -1; + } + return ret; +} + +const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, const char* path, const char* headers) +{ + FF_DEBUG("Preparing to send HTTP request: host=%s, path=%s", host, path); + + if (state->compression) + { + FF_DEBUG("Compression enabled, checking if zlib is available"); + + #ifdef FF_HAVE_ZLIB + const char* zlibError = ffNetworkingLoadZlibLibrary(); + // Only enable compression if zlib library is successfully loaded + if (zlibError == NULL) + { + FF_DEBUG("Successfully loaded zlib library, compression enabled"); + } else { + FF_DEBUG("Failed to load zlib library, compression disabled: %s", zlibError); + state->compression = false; + } + #else + FF_DEBUG("zlib not supported at build time, compression disabled"); + state->compression = false; + #endif + } + else + { + FF_DEBUG("Compression disabled"); + } + + const char* initResult = initNetworkingState(state, host, path, headers); + if (initResult != NULL) { + FF_DEBUG("Initialization failed: %s", initResult); + return initResult; + } + FF_DEBUG("Network state initialization successful"); + + const char* tfoResult = tryNonThreadingFastPath(state); + if (tfoResult == NULL) { + FF_DEBUG("TryNonThreadingFastPath() succeeded or in progress"); + return NULL; + } + FF_DEBUG("TryNonThreadingFastPath() failed: %s, trying traditional connection", tfoResult); + + #ifdef FF_HAVE_THREADS + if (instance.config.general.multithreading) + { + FF_DEBUG("Multithreading mode enabled, creating connection thread"); + state->thread = ffThreadCreate(connectAndSendThreadMain, state); + if (state->thread) { + FF_DEBUG("Thread creation successful: thread=%p", (void*)state->thread); + return NULL; + } + FF_DEBUG("Thread creation failed"); + } else { + FF_DEBUG("Multithreading mode disabled, connecting in main thread"); + } + #endif + + return connectAndSend(state); +} + +const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer) +{ + FF_DEBUG("Preparing to receive HTTP response"); + uint32_t timeout = state->timeout; + + #ifdef FF_HAVE_THREADS + if (state->thread) + { + FF_DEBUG("Connection thread is running, waiting for it to complete (timeout=%u ms)", timeout); + if (!ffThreadJoin(state->thread, timeout)) { + FF_DEBUG("Thread join failed or timed out"); + return "ffThreadJoin() failed or timeout"; + } + FF_DEBUG("Thread completed successfully"); + state->thread = 0; + } + #endif + + if(state->sockfd == -1) + { + FF_DEBUG("Invalid socket, HTTP request might have failed"); + return "ffNetworkingSendHttpRequest() failed"; + } + + // Set larger initial receive buffer instead of small repeated receives + int rcvbuf = 65536; // 64KB + setsockopt(state->sockfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)); + + #ifdef __APPLE__ + // poll for the socket to be readable. + // Because of the non-blocking connectx() call, the connection might not be established yet + FF_DEBUG("Using poll() to check if socket is readable"); + if (poll(&(struct pollfd) { + .fd = state->sockfd, + .events = POLLIN + }, 1, timeout > 0 ? (int) timeout : -1) == -1) + { + FF_DEBUG("poll() failed: %s (errno=%d)", strerror(errno), errno); + close(state->sockfd); + state->sockfd = -1; + return "poll() failed"; + } + FF_DEBUG("Socket is readable, proceeding to receive data"); + #else + if(timeout > 0) + { + FF_DEBUG("Setting receive timeout: %u ms", timeout); + struct timeval timev; + timev.tv_sec = timeout / 1000; + timev.tv_usec = (__typeof__(timev.tv_usec)) ((timeout % 1000) * 1000); //milliseconds to microseconds + setsockopt(state->sockfd, SOL_SOCKET, SO_RCVTIMEO, &timev, sizeof(timev)); + } + #endif + + FF_DEBUG("Starting data reception"); + FF_MAYBE_UNUSED int recvCount = 0; + uint32_t contentLength = 0; + char* headerEnd = NULL; + + do { + FF_DEBUG("Data reception loop #%d, current buffer size: %u, available space: %u", + ++recvCount, buffer->length, ffStrbufGetFree(buffer)); + + ssize_t received = recv(state->sockfd, buffer->chars + buffer->length, ffStrbufGetFree(buffer), MSG_WAITALL); + + if (received <= 0) { + if (received == 0) { + FF_DEBUG("Connection closed (received=0)"); + } else { + FF_DEBUG("Reception failed: %s (errno=%d)", strerror(errno), errno); + } + break; + } + + buffer->length += (uint32_t) received; + buffer->chars[buffer->length] = '\0'; + + FF_DEBUG("Successfully received %zd bytes of data, total: %u bytes", received, buffer->length); + + // Check if HTTP header end marker is found + if (headerEnd == NULL) { + headerEnd = memmem(buffer->chars, buffer->length, "\r\n\r\n", 4); + if (headerEnd != NULL) { + FF_DEBUG("Found HTTP header end marker, position: %ld", (long)(headerEnd - buffer->chars)); + + // Check for Content-Length header to pre-allocate enough memory + const char* clHeader = strcasestr(buffer->chars, "Content-Length:"); + if (clHeader) { + contentLength = (uint32_t) strtoul(clHeader + 16, NULL, 10); + if (contentLength > 0) { + FF_DEBUG("Detected Content-Length: %u, pre-allocating buffer", contentLength); + // Ensure buffer is large enough, adding header size and some margin + ffStrbufEnsureFree(buffer, contentLength + 16); + FF_DEBUG("Extended receive buffer to %u bytes", buffer->length); + } + } + } + } + } while (ffStrbufGetFree(buffer) > 0); + + FF_DEBUG("Closing socket: fd=%d", state->sockfd); + close(state->sockfd); + state->sockfd = -1; + + if (buffer->length == 0) { + FF_DEBUG("Server response is empty"); + return "Empty server response received"; + } + + if (headerEnd == NULL) { + FF_DEBUG("No HTTP header end marker found"); + return "No HTTP header end found"; + } + if (contentLength > 0 && buffer->length != contentLength + (uint32_t)(headerEnd - buffer->chars) + 4) { + FF_DEBUG("Received content length mismatches: %u != %u", buffer->length, contentLength + (uint32_t)(headerEnd - buffer->chars) + 4); + return "Content length mismatch"; + } + + if (ffStrbufStartsWithS(buffer, "HTTP/1.1 200 OK\r\n")) { + FF_DEBUG("Received valid HTTP 200 response, content %u bytes, total %u bytes", contentLength, buffer->length); + } else { + FF_DEBUG("Invalid response: %.40s...", buffer->chars); + return "Invalid response"; + } + + // If compression was used, try to decompress + #ifdef FF_HAVE_ZLIB + if (state->compression) { + FF_DEBUG("Content received, checking if compressed"); + if (!ffNetworkingDecompressGzip(buffer, headerEnd)) { + FF_DEBUG("Decompression failed or invalid compression format"); + return "Failed to decompress or invalid format"; + } else { + FF_DEBUG("Decompression successful or no decompression needed, total length after decompression: %u bytes", buffer->length); + } + } + #endif + + return NULL; +} diff --git a/src/common/networking/networking_windows.c b/src/common/networking/networking_windows.c new file mode 100644 index 0000000000..65a3b0d66d --- /dev/null +++ b/src/common/networking/networking_windows.c @@ -0,0 +1,339 @@ +#include +#include + +//Must be included after +#include "fastfetch.h" +#include "common/networking/networking.h" +#include "util/stringUtils.h" +#include "util/debug.h" + +static LPFN_CONNECTEX ConnectEx; + +static const char* initWsaData(WSADATA* wsaData) +{ + FF_DEBUG("Initializing WinSock"); + if(WSAStartup(MAKEWORD(2, 2), wsaData) != 0) { + FF_DEBUG("WSAStartup() failed"); + return "WSAStartup() failed"; + } + + if(LOBYTE(wsaData->wVersion) != 2 || HIBYTE(wsaData->wVersion) != 2) { + FF_DEBUG("Invalid wsaData version found: %d.%d", LOBYTE(wsaData->wVersion), HIBYTE(wsaData->wVersion)); + return "Invalid wsaData version found"; + } + + //Dummy socket needed for WSAIoctl + SOCKET sockfd = socket(AF_INET, SOCK_STREAM, 0); + if(sockfd == INVALID_SOCKET) { + FF_DEBUG("socket(AF_INET, SOCK_STREAM) failed"); + return "socket(AF_INET, SOCK_STREAM) failed"; + } + + DWORD dwBytes; + GUID guid = WSAID_CONNECTEX; + if(WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER, + &guid, sizeof(guid), + &ConnectEx, sizeof(ConnectEx), + &dwBytes, NULL, NULL) != 0) { + FF_DEBUG("WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER) failed"); + return "WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER) failed"; + } + + closesocket(sockfd); + FF_DEBUG("WinSock initialized successfully"); + + return NULL; +} + +const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, const char* path, const char* headers) +{ + FF_DEBUG("Preparing to send HTTP request: host=%s, path=%s", host, path); + + if (state->compression) + { + #ifdef FF_HAVE_ZLIB + const char* zlibError = ffNetworkingLoadZlibLibrary(); + // Only enable compression if zlib library is successfully loaded + if (zlibError == NULL) + { + FF_DEBUG("Successfully loaded zlib library, compression enabled"); + } else { + FF_DEBUG("Failed to load zlib library, compression disabled: %s", zlibError); + state->compression = false; + } + #else + FF_DEBUG("zlib not supported at build time, compression disabled"); + state->compression = false; + #endif + } + else + { + FF_DEBUG("Compression disabled"); + } + + static WSADATA wsaData; + if (wsaData.wVersion == 0) + { + const char* error = initWsaData(&wsaData); + if (error != NULL) + { + wsaData.wVersion = (WORD) -1; + FF_DEBUG("WinSock initialization failed: %s", error); + return error; + } + } + else if (wsaData.wVersion == (WORD) -1) + { + FF_DEBUG("WinSock initialization previously failed"); + return "initWsaData() failed before"; + } + + struct addrinfo* addr; + struct addrinfo hints = { + .ai_family = state->ipv6 ? AF_INET6 : AF_INET, + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_NUMERICSERV, + }; + + FF_DEBUG("Resolving address: %s (%s)", host, state->ipv6 ? "IPv6" : "IPv4"); + if(getaddrinfo(host, "80", &hints, &addr) != 0) + { + FF_DEBUG("getaddrinfo() failed"); + return "getaddrinfo() failed"; + } + + state->sockfd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + if(state->sockfd == INVALID_SOCKET) + { + FF_DEBUG("socket() failed"); + freeaddrinfo(addr); + return "socket() failed"; + } + + DWORD flag = 1; + #ifdef TCP_NODELAY + // Enable TCP_NODELAY to disable Nagle's algorithm + if (setsockopt(state->sockfd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(flag)) != 0) { + FF_DEBUG("Failed to set TCP_NODELAY: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); + } else { + FF_DEBUG("Successfully disabled Nagle's algorithm"); + } + #endif + + // Set timeout if needed + if (state->timeout > 0) { + FF_DEBUG("Setting connection timeout: %u ms", state->timeout); + setsockopt(state->sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&state->timeout, sizeof(state->timeout)); + } + + //ConnectEx requires the socket to be initially bound + if((state->ipv6 + ? bind(state->sockfd, (SOCKADDR *) &(struct sockaddr_in6) { + .sin6_family = AF_INET6, + .sin6_addr = in6addr_any, + }, sizeof(struct sockaddr_in6)) + : bind(state->sockfd, (SOCKADDR *) &(struct sockaddr_in) { + .sin_family = AF_INET, + .sin_addr.s_addr = INADDR_ANY, + }, sizeof(struct sockaddr_in))) != 0) + { + FF_DEBUG("bind() failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); + closesocket(state->sockfd); + freeaddrinfo(addr); + state->sockfd = INVALID_SOCKET; + return "bind() failed"; + } + + // Initialize overlapped structure for asynchronous I/O + memset(&state->overlapped, 0, sizeof(OVERLAPPED)); + + // Build HTTP command + FF_STRBUF_AUTO_DESTROY command = ffStrbufCreateA(64); + ffStrbufAppendS(&command, "GET "); + ffStrbufAppendS(&command, path); + ffStrbufAppendS(&command, " HTTP/1.1\nHost: "); + ffStrbufAppendS(&command, host); + ffStrbufAppendS(&command, "\r\n"); + ffStrbufAppendS(&command, "Connection: close\r\n"); // Explicitly request connection closure + + // Add compression support if enabled + if (state->compression) { + FF_DEBUG("Enabling HTTP content compression"); + ffStrbufAppendS(&command, "Accept-Encoding: gzip\r\n"); + } + + ffStrbufAppendS(&command, headers); + ffStrbufAppendS(&command, "\r\n"); + + #ifdef TCP_FASTOPEN + if (state->tfo) + { + // Set TCP Fast Open + flag = 1; + if (setsockopt(state->sockfd, IPPROTO_TCP, TCP_FASTOPEN, (char*)&flag, sizeof(flag)) != 0) { + FF_DEBUG("Failed to set TCP_FASTOPEN option: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); + } else { + FF_DEBUG("Successfully set TCP_FASTOPEN option"); + } + } + else + { + FF_DEBUG("TCP Fast Open disabled"); + } + #endif + + FF_DEBUG("Using ConnectEx to send %u bytes of data", command.length); + DWORD sent = 0; + BOOL result = ConnectEx(state->sockfd, addr->ai_addr, (int)addr->ai_addrlen, + command.chars, command.length, &sent, &state->overlapped); + + freeaddrinfo(addr); + addr = NULL; + + if(!result) + { + if (WSAGetLastError() != WSA_IO_PENDING) + { + FF_DEBUG("ConnectEx() failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); + closesocket(state->sockfd); + state->sockfd = INVALID_SOCKET; + return "ConnectEx() failed"; + } + else + { + FF_DEBUG("ConnectEx() pending"); + } + } + else + { + FF_DEBUG("ConnectEx() succeeded, sent %u bytes of data", (unsigned) sent); + } + + // No need to cleanup state fields here since we need them in the receive function + return NULL; +} + +const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer) +{ + FF_DEBUG("Preparing to receive HTTP response"); + + if (state->sockfd == INVALID_SOCKET) + { + FF_DEBUG("Invalid socket, HTTP request might have failed"); + return "ffNetworkingSendHttpRequest() failed"; + } + + uint32_t timeout = state->timeout; + if (timeout > 0) + { + FF_DEBUG("WaitForSingleObject with timeout: %u ms", timeout); + if (WaitForSingleObject((HANDLE)state->sockfd, timeout) != WAIT_OBJECT_0) + { + FF_DEBUG("WaitForSingleObject failed or timed out"); + CancelIo((HANDLE) state->sockfd); + closesocket(state->sockfd); + return "WaitForSingleObject(state->sockfd) failed or timeout"; + } + } + + DWORD transfer, flags; + if (!WSAGetOverlappedResult(state->sockfd, &state->overlapped, &transfer, TRUE, &flags)) + { + FF_DEBUG("WSAGetOverlappedResult failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); + closesocket(state->sockfd); + return "WSAGetOverlappedResult() failed"; + } + + if(timeout > 0) + { + FF_DEBUG("Setting receive timeout: %u ms", timeout); + setsockopt(state->sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)); + } + + // Set larger receive buffer for better performance + int rcvbuf = 65536; // 64KB + setsockopt(state->sockfd, SOL_SOCKET, SO_RCVBUF, (const char*)&rcvbuf, sizeof(rcvbuf)); + + FF_DEBUG("Starting data reception"); + FF_MAYBE_UNUSED int recvCount = 0; + uint32_t contentLength = 0; + char* headerEnd = NULL; + + do { + FF_DEBUG("Data reception loop #%d, current buffer size: %u, available space: %u", + ++recvCount, buffer->length, ffStrbufGetFree(buffer)); + + ssize_t received = recv(state->sockfd, buffer->chars + buffer->length, (int)ffStrbufGetFree(buffer), 0); + + if (received <= 0) { + if (received == 0) { + FF_DEBUG("Connection closed (received=0)"); + } else { + FF_DEBUG("Reception failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); + } + break; + } + + buffer->length += (uint32_t) received; + buffer->chars[buffer->length] = '\0'; + + FF_DEBUG("Successfully received %zd bytes of data, total: %u bytes", received, buffer->length); + + // Check if HTTP header end marker is found + if (headerEnd == NULL) { + headerEnd = strstr(buffer->chars, "\r\n\r\n"); + if (headerEnd != NULL) { + FF_DEBUG("Found HTTP header end marker, position: %ld", (long)(headerEnd - buffer->chars)); + + // Check for Content-Length header to pre-allocate enough memory + const char* clHeader = strcasestr(buffer->chars, "Content-Length:"); + if (clHeader) { + contentLength = (uint32_t) strtoul(clHeader + 16, NULL, 10); + if (contentLength > 0) { + FF_DEBUG("Detected Content-Length: %u, pre-allocating buffer", contentLength); + // Ensure buffer is large enough, adding header size and some margin + ffStrbufEnsureFree(buffer, contentLength + 16); + FF_DEBUG("Extended receive buffer to %u bytes", buffer->allocated); + } + } + } + } + } while (ffStrbufGetFree(buffer) > 0); + + FF_DEBUG("Closing socket: fd=%u", (unsigned)state->sockfd); + closesocket(state->sockfd); + state->sockfd = INVALID_SOCKET; + + if (buffer->length == 0) { + FF_DEBUG("Server response is empty"); + return "Empty server response received"; + } + + if (headerEnd == NULL) { + FF_DEBUG("No HTTP header end marker found"); + return "No HTTP header end found"; + } + + if (ffStrbufStartsWithS(buffer, "HTTP/1.1 200 OK\r\n")) { + FF_DEBUG("Received valid HTTP 200 response, content length: %u bytes, total length: %u bytes", + contentLength, buffer->length); + } else { + FF_DEBUG("Invalid response: %.40s...", buffer->chars); + return "Invalid response"; + } + + // If compression was used, try to decompress + #ifdef FF_HAVE_ZLIB + if (state->compression) { + FF_DEBUG("Content received, checking if compressed"); + if (!ffNetworkingDecompressGzip(buffer, headerEnd)) { + FF_DEBUG("Decompression failed or invalid compression format"); + return "Failed to decompress or invalid format"; + } else { + FF_DEBUG("Decompression successful or no decompression needed, total length after decompression: %u bytes", buffer->length); + } + } + #endif + + return NULL; +} diff --git a/src/common/networking_linux.c b/src/common/networking_linux.c deleted file mode 100644 index 0bc39d6126..0000000000 --- a/src/common/networking_linux.c +++ /dev/null @@ -1,137 +0,0 @@ -#include "fastfetch.h" -#include "common/networking.h" - -#include -#include -#include -#include -#include // For FreeBSD -#include - -static const char* connectAndSend(FFNetworkingState* state) -{ - const char* ret = NULL; - struct addrinfo* addr; - - if(getaddrinfo(state->host.chars, "80", &(struct addrinfo) { - .ai_family = state->ipv6 ? AF_INET6 : AF_INET, - .ai_socktype = SOCK_STREAM, - }, &addr) != 0) - { - ret = "getaddrinfo() failed"; - goto error; - } - - state->sockfd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); - if(state->sockfd == -1) - { - freeaddrinfo(addr); - ret = "socket() failed"; - goto error; - } - - if (state->timeout > 0) - { - FF_MAYBE_UNUSED uint32_t sec = state->timeout / 1000; - if (sec == 0) sec = 1; - - #ifdef TCP_CONNECTIONTIMEOUT - setsockopt(state->sockfd, IPPROTO_TCP, TCP_CONNECTIONTIMEOUT, &sec, sizeof(sec)); - #elif defined(TCP_KEEPINIT) - setsockopt(state->sockfd, IPPROTO_TCP, TCP_KEEPINIT, &sec, sizeof(sec)); - #elif defined(TCP_USER_TIMEOUT) - setsockopt(state->sockfd, IPPROTO_TCP, TCP_USER_TIMEOUT, &state->timeout, sizeof(state->timeout)); - #endif - } - - if(connect(state->sockfd, addr->ai_addr, addr->ai_addrlen) == -1) - { - close(state->sockfd); - freeaddrinfo(addr); - ret = "connect() failed"; - goto error; - } - - freeaddrinfo(addr); - - if(send(state->sockfd, state->command.chars, state->command.length, 0) < 0) - { - close(state->sockfd); - ret = "send() failed"; - goto error; - } - - goto exit; - -error: - state->sockfd = -1; - -exit: - ffStrbufDestroy(&state->host); - ffStrbufDestroy(&state->command); - - return ret; -} - -FF_THREAD_ENTRY_DECL_WRAPPER(connectAndSend, FFNetworkingState*); - -const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, const char* path, const char* headers) -{ - ffStrbufInitS(&state->host, host); - - ffStrbufInitA(&state->command, 64); - ffStrbufAppendS(&state->command, "GET "); - ffStrbufAppendS(&state->command, path); - ffStrbufAppendS(&state->command, " HTTP/1.1\nHost: "); - ffStrbufAppendS(&state->command, host); - ffStrbufAppendS(&state->command, "\r\n"); - ffStrbufAppendS(&state->command, headers); - ffStrbufAppendS(&state->command, "\r\n"); - - #ifdef FF_HAVE_THREADS - if (instance.config.general.multithreading) - { - state->thread = ffThreadCreate(connectAndSendThreadMain, state); - return state->thread ? NULL : "ffThreadCreate(connectAndSend) failed"; - } - #endif - - return connectAndSend(state); -} - -const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer) -{ - uint32_t timeout = state->timeout; - - #ifdef FF_HAVE_THREADS - if (instance.config.general.multithreading) - { - if (!ffThreadJoin(state->thread, timeout)) - return "ffThreadJoin() failed or timeout"; - } - #endif - - if(state->sockfd == -1) - return "ffNetworkingSendHttpRequest() failed"; - - if(timeout > 0) - { - struct timeval timev; - timev.tv_sec = timeout / 1000; - timev.tv_usec = (__typeof__(timev.tv_usec)) ((timeout % 1000) * 1000); //milliseconds to microseconds - setsockopt(state->sockfd, SOL_SOCKET, SO_RCVTIMEO, &timev, sizeof(timev)); - } - - uint32_t recvStart; - do { - recvStart = buffer->length; - ssize_t received = recv(state->sockfd, buffer->chars + buffer->length, ffStrbufGetFree(buffer), 0); - if (received <= 0) break; - buffer->length += (uint32_t) received; - buffer->chars[buffer->length] = '\0'; - } while (ffStrbufGetFree(buffer) > 0 && strstr(buffer->chars + recvStart, "\r\n\r\n") == NULL); - - close(state->sockfd); - if (buffer->length == 0) return "Empty server response received"; - return ffStrbufStartsWithS(buffer, "HTTP/1.1 200 OK\r\n") ? NULL : "Invalid response"; -} diff --git a/src/common/networking_windows.c b/src/common/networking_windows.c deleted file mode 100644 index 305f558642..0000000000 --- a/src/common/networking_windows.c +++ /dev/null @@ -1,146 +0,0 @@ -#include -#include - -//Must be included after -#include "fastfetch.h" -#include "common/networking.h" - -static LPFN_CONNECTEX ConnectEx; - -static const char* initWsaData(WSADATA* wsaData) -{ - if(WSAStartup(MAKEWORD(2, 2), wsaData) != 0) - return "WSAStartup() failed"; - - if(LOBYTE(wsaData->wVersion) != 2 || HIBYTE(wsaData->wVersion) != 2) - return "Invalid wsaData version found"; - - //Dummy socket needed for WSAIoctl - SOCKET sockfd = socket(AF_INET, SOCK_STREAM, 0); - if(sockfd == INVALID_SOCKET) - return "socket(AF_INET, SOCK_STREAM) failed"; - - DWORD dwBytes; - GUID guid = WSAID_CONNECTEX; - if(WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER, - &guid, sizeof(guid), - &ConnectEx, sizeof(ConnectEx), - &dwBytes, NULL, NULL) != 0) - return "WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER) failed"; - - closesocket(sockfd); - - return NULL; -} - -const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, const char* path, const char* headers) -{ - static WSADATA wsaData; - if (wsaData.wVersion == 0) - { - const char* error = initWsaData(&wsaData); - if (error != NULL) - { - wsaData.wVersion = (WORD) -1; - return error; - } - } - else if (wsaData.wVersion == (WORD) -1) - return "initWsaData() failed before"; - - struct addrinfo* addr; - - if(getaddrinfo(host, "80", &(struct addrinfo) { - .ai_family = state->ipv6 ? AF_INET6 : AF_INET, - .ai_socktype = SOCK_STREAM, - }, &addr) != 0) - return "getaddrinfo() failed"; - - state->sockfd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); - if(state->sockfd == INVALID_SOCKET) - { - freeaddrinfo(addr); - return "socket() failed"; - } - - //ConnectEx requires the socket to be initially bound - if((state->ipv6 - ? bind(state->sockfd, (SOCKADDR *) &(struct sockaddr_in6) { - .sin6_family = AF_INET6, - .sin6_addr = in6addr_any, - }, sizeof(struct sockaddr_in6)) - : bind(state->sockfd, (SOCKADDR *) &(struct sockaddr_in) { - .sin_family = AF_INET, - .sin_addr.s_addr = INADDR_ANY, - }, sizeof(struct sockaddr_in))) != 0) - { - closesocket(state->sockfd); - freeaddrinfo(addr); - state->sockfd = INVALID_SOCKET; - return "bind() failed"; - } - - FF_STRBUF_AUTO_DESTROY command = ffStrbufCreateA(64); - ffStrbufAppendS(&command, "GET "); - ffStrbufAppendS(&command, path); - ffStrbufAppendS(&command, " HTTP/1.1\nHost: "); - ffStrbufAppendS(&command, host); - ffStrbufAppendS(&command, "\r\n"); - ffStrbufAppendS(&command, headers); - ffStrbufAppendS(&command, "\r\n"); - - BOOL result = ConnectEx(state->sockfd, addr->ai_addr, (int)addr->ai_addrlen, command.chars, command.length, NULL, &state->overlapped); - freeaddrinfo(addr); - - if(!result && WSAGetLastError() != WSA_IO_PENDING) - { - closesocket(state->sockfd); - state->sockfd = INVALID_SOCKET; - return "ConnectEx() failed"; - } - - return NULL; -} - -const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer) -{ - if (state->sockfd == INVALID_SOCKET) - return "ffNetworkingSendHttpRequest() failed"; - - uint32_t timeout = state->timeout; - if (timeout > 0) - { - if (WaitForSingleObject((HANDLE) state->sockfd, timeout) != WAIT_OBJECT_0) - { - CancelIo((HANDLE) state->sockfd); - closesocket(state->sockfd); - return "WaitForSingleObject(state->sockfd) failed or timeout"; - } - } - - DWORD transfer, flags; - if (!WSAGetOverlappedResult(state->sockfd, &state->overlapped, &transfer, TRUE, &flags)) - { - closesocket(state->sockfd); - return "WSAGetOverlappedResult() failed"; - } - - if(timeout > 0) - { - //https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-setsockopt - setsockopt(state->sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*) &timeout, sizeof(timeout)); - } - - uint32_t recvStart; - do { - recvStart = buffer->length; - ssize_t received = recv(state->sockfd, buffer->chars + buffer->length, (int) ffStrbufGetFree(buffer), 0); - if (received <= 0) break; - buffer->length = recvStart + (uint32_t) received; - buffer->chars[buffer->length] = '\0'; - } while (ffStrbufGetFree(buffer) > 0 && strstr(buffer->chars + recvStart, "\r\n\r\n") == NULL); - - closesocket(state->sockfd); - if (buffer->length == 0) return "Empty server response received"; - return ffStrbufStartsWithS(buffer, "HTTP/1.1 200 OK\r\n") ? NULL : "Invalid response"; -} diff --git a/src/common/time.h b/src/common/time.h index a041209059..0745574bbb 100644 --- a/src/common/time.h +++ b/src/common/time.h @@ -82,6 +82,18 @@ static inline const char* ffTimeToShortStr(uint64_t msec) return buf; } +// Not thread-safe +static inline const char* ffTimeToTimeStr(uint64_t msec) +{ + if (msec == 0) return ""; + time_t tsec = (time_t) (msec / 1000); + + static char buf[32]; + strftime(buf, sizeof(buf), "%T", localtime(&tsec)); + sprintf(buf + __builtin_strlen("00:00:00"), ".%03u", (unsigned) (msec % 1000)); + return buf; +} + #ifdef _WIN32 #pragma GCC diagnostic pop #endif diff --git a/src/data/help.json b/src/data/help.json index a4a869f5a3..ed422f656c 100644 --- a/src/data/help.json +++ b/src/data/help.json @@ -1419,10 +1419,9 @@ "arg": { "type": "enum", "enum": { - "auto": "Auto detection", + "auto": "Prefer EGL on *nix; prefer platform specific implementation on others", "egl": "EGL", - "glx": "GLX", - "osmesa": "OSMesa" + "glx": "GLX (*nix only)" }, "default": "auto" } diff --git a/src/detection/displayserver/displayserver_windows.c b/src/detection/displayserver/displayserver_windows.c index 95f435cc94..0773909602 100644 --- a/src/detection/displayserver/displayserver_windows.c +++ b/src/detection/displayserver/displayserver_windows.c @@ -189,26 +189,47 @@ static void detectDisplays(FFDisplayServerResult* ds) if (display) { - DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO advColorInfo = { + DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO_2 advColorInfo2 = { .header = { - .type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO, - .size = sizeof(advColorInfo), + .type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO_2, + .size = sizeof(advColorInfo2), .adapterId = path->targetInfo.adapterId, .id = path->targetInfo.id, } }; - if (DisplayConfigGetDeviceInfo(&advColorInfo.header) == ERROR_SUCCESS) + if (DisplayConfigGetDeviceInfo(&advColorInfo2.header) == ERROR_SUCCESS) { - if (advColorInfo.advancedColorEnabled) + if (advColorInfo2.highDynamicRangeUserEnabled) display->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED; - else if (advColorInfo.advancedColorSupported) + else if (advColorInfo2.highDynamicRangeSupported) display->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED; else display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED; - display->bitDepth = (uint8_t) advColorInfo.bitsPerColorChannel; + display->bitDepth = (uint8_t) advColorInfo2.bitsPerColorChannel; } else - display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN; + { + DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO advColorInfo = { + .header = { + .type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO, + .size = sizeof(advColorInfo), + .adapterId = path->targetInfo.adapterId, + .id = path->targetInfo.id, + } + }; + if (DisplayConfigGetDeviceInfo(&advColorInfo.header) == ERROR_SUCCESS) + { + if (advColorInfo.advancedColorEnabled) + display->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED; + else if (advColorInfo.advancedColorSupported) + display->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED; + else + display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED; + display->bitDepth = (uint8_t) advColorInfo.bitsPerColorChannel; + } + else + display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN; + } if (edidLength > 0) ffEdidGetSerialAndManufactureDate(edidData, &display->serial, &display->manufactureYear, &display->manufactureWeek); } diff --git a/src/detection/displayserver/linux/drm.c b/src/detection/displayserver/linux/drm.c index 1fe274b479..c62c544e55 100644 --- a/src/detection/displayserver/linux/drm.c +++ b/src/detection/displayserver/linux/drm.c @@ -30,7 +30,7 @@ static const char* drmParseSysfs(FFDisplayServerResult* result) char buf; ffStrbufAppendS(&drmDir, "/enabled"); - if (!ffReadFileData(drmDir.chars, sizeof(buf), &buf) || buf != 'e') { + if (ffReadFileData(drmDir.chars, sizeof(buf), &buf) <= 0 || buf != 'e') { /* read failed or enabled != "enabled" */ ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength); ffStrbufAppendS(&drmDir, "/status"); diff --git a/src/detection/opengl/opengl_linux.c b/src/detection/opengl/opengl_linux.c index 01491ef666..424cbfe258 100644 --- a/src/detection/opengl/opengl_linux.c +++ b/src/detection/opengl/opengl_linux.c @@ -11,7 +11,7 @@ #endif #endif -#if defined(FF_HAVE_EGL) || defined(FF_HAVE_GLX) || defined(FF_HAVE_OSMESA) +#if defined(FF_HAVE_EGL) || defined(FF_HAVE_GLX) #define FF_HAVE_GL 1 #include "common/library.h" @@ -155,66 +155,6 @@ static const char* detectByGlx(FFOpenGLResult* result) #endif //FF_HAVE_GLX -#ifdef FF_HAVE_OSMESA -#if __has_include() - #include -#else - #include // for sunos -#endif - -typedef struct OSMesaData -{ - FF_LIBRARY_SYMBOL(glGetString) - FF_LIBRARY_SYMBOL(OSMesaGetProcAddress) - FF_LIBRARY_SYMBOL(OSMesaCreateContext) - FF_LIBRARY_SYMBOL(OSMesaMakeCurrent) - FF_LIBRARY_SYMBOL(OSMesaDestroyContext) - - OSMesaContext context; -} OSMesaData; - -static const char* osMesaHandleContext(FFOpenGLResult* result, OSMesaData* data) -{ - unsigned char buffer[FF_OPENGL_BUFFER_WIDTH * FF_OPENGL_BUFFER_HEIGHT * sizeof(uint32_t)]; // 4 bytes per pixel (RGBA) - - if(data->ffOSMesaMakeCurrent(data->context, buffer, GL_UNSIGNED_BYTE, FF_OPENGL_BUFFER_WIDTH, FF_OPENGL_BUFFER_HEIGHT) != GL_TRUE) - return "OSMesaMakeCurrent returned GL_FALSE"; - - ffOpenGLHandleResult(result, data->ffglGetString); - ffStrbufSetF(&result->library, "OSMesa %d.%d.%d", OSMESA_MAJOR_VERSION, OSMESA_MINOR_VERSION, OSMESA_PATCH_VERSION); - return NULL; -} - -static const char* osMesaHandleData(FFOpenGLResult* result, OSMesaData* data) -{ - data->ffglGetString = (void*) data->ffOSMesaGetProcAddress("glGetString"); - if(data->ffglGetString == NULL) - return "OSMesaGetProcAddress(glGetString) returned NULL"; - - data->context = data->ffOSMesaCreateContext(OSMESA_RGBA, NULL); - if(data->context == NULL) - return "OSMesaCreateContext returned NULL"; - - const char* error = osMesaHandleContext(result, data); - data->ffOSMesaDestroyContext(data->context); - return error; -} - -static const char* detectByOsMesa(FFOpenGLResult* result) -{ - OSMesaData data; - - FF_LIBRARY_LOAD(osmesa, "dlopen osmesa failed", "libOSMesa" FF_LIBRARY_EXTENSION, 8); - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(osmesa, data, OSMesaGetProcAddress); - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(osmesa, data, OSMesaCreateContext); - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(osmesa, data, OSMesaMakeCurrent); - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(osmesa, data, OSMesaDestroyContext); - - return osMesaHandleData(result, &data); -} - -#endif //FF_HAVE_OSMESA - const char* ffDetectOpenGL(FFOpenGLOptions* options, FFOpenGLResult* result) { #if FF_HAVE_GL @@ -238,15 +178,6 @@ const char* ffDetectOpenGL(FFOpenGLOptions* options, FFOpenGLResult* result) #endif } - if(options->library == FF_OPENGL_LIBRARY_OSMESA) - { - #ifdef FF_HAVE_OSMESA - return detectByOsMesa(result); - #else - return "fastfetch was compiled without osmesa support"; - #endif - } - const char* error = ""; // not NULL dummy value #ifdef FF_HAVE_EGL @@ -259,9 +190,6 @@ const char* ffDetectOpenGL(FFOpenGLOptions* options, FFOpenGLResult* result) error = detectByGlx(result); #endif - //We don't use osmesa in auto mode here, because it is a software implementation, - //that doesn't reflect the opengl supported by the hardware - return error; #else diff --git a/src/detection/packages/packages_linux.c b/src/detection/packages/packages_linux.c index ab677ccfce..43ed96e6c7 100644 --- a/src/detection/packages/packages_linux.c +++ b/src/detection/packages/packages_linux.c @@ -574,6 +574,7 @@ static void getPackageCounts(FFstrbuf* baseDir, FFPackagesResult* packageCounts, if (!(options->disabled & FF_PACKAGES_FLAG_PACSTALL_BIT)) packageCounts->pacstall += getNumElements(baseDir, "/var/lib/pacstall/metadata", false); if (!(options->disabled & FF_PACKAGES_FLAG_QI_BIT)) packageCounts->qi += getNumStrings(baseDir, "/var/qi/installed_packages.list", "\n", "qi"); if (!(options->disabled & FF_PACKAGES_FLAG_PISI_BIT)) packageCounts->pisi += getNumElements(baseDir, "/var/lib/pisi/package", true); + if (!(options->disabled & FF_PACKAGES_FLAG_PKGSRC_BIT)) packageCounts->pkgsrc += getNumElements(baseDir, "/usr/pkg/pkgdb", DT_DIR); } static void getPackageCountsRegular(FFstrbuf* baseDir, FFPackagesResult* packageCounts, FFPackagesOptions* options) diff --git a/src/detection/publicip/publicip.c b/src/detection/publicip/publicip.c index aa5f224193..a568f5d707 100644 --- a/src/detection/publicip/publicip.c +++ b/src/detection/publicip/publicip.c @@ -1,5 +1,5 @@ #include "publicip.h" -#include "common/networking.h" +#include "common/networking/networking.h" #define FF_UNITIALIZED ((const char*)(uintptr_t) -1) static FFNetworkingState states[2]; @@ -19,7 +19,11 @@ void ffPreparePublicIp(FFPublicIpOptions* options) state->ipv6 = options->ipv6; if (options->url.length == 0) + { + state->compression = true; + state->tfo = true; *status = ffNetworkingSendHttpRequest(state, options->ipv6 ? "v6.ipinfo.io" : "ipinfo.io", "/json", NULL); + } else { FF_STRBUF_AUTO_DESTROY host = ffStrbufCreateCopy(&options->url); diff --git a/src/detection/terminalshell/terminalshell.c b/src/detection/terminalshell/terminalshell.c index d3c69d63eb..92bd7294ec 100644 --- a/src/detection/terminalshell/terminalshell.c +++ b/src/detection/terminalshell/terminalshell.c @@ -20,13 +20,14 @@ #include "util/windows/version.h" #include -static bool getFileVersion(const FFstrbuf* exePath, FFstrbuf* version) +static bool getFileVersion(const FFstrbuf* exePath, const wchar_t* stringName, FFstrbuf* version) { wchar_t exePathW[PATH_MAX]; int len = MultiByteToWideChar(CP_UTF8, 0, exePath->chars, (int)exePath->length, exePathW, ARRAY_SIZE(exePathW)); if (len <= 0) return false; - return ffGetFileVersion(exePathW, version); + return ffGetFileVersion(exePathW, stringName, version); } + #elif __HAIKU__ #include "util/haiku/version.h" #endif @@ -98,7 +99,7 @@ static bool getShellVersionPwsh(FFstrbuf* exe, FFstrbuf* version) } #ifdef _WIN32 - if(getFileVersion(exe, version)) + if(getFileVersion(exe, NULL, version)) { ffStrbufSubstrBeforeLastC(version, '.'); return true; @@ -291,7 +292,7 @@ bool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, FFstrbuf* exePath, if(ffStrEqualsIgnCase(exeName, "powershell") || ffStrEqualsIgnCase(exeName, "powershell_ise")) return getShellVersionWinPowerShell(exe, version); - return getFileVersion(exe, version); + return getFileVersion(exe, NULL, version); #endif return false; @@ -737,7 +738,7 @@ static bool getTerminalVersionWindowsTerminal(FFstrbuf* exe, FFstrbuf* version) return true; } - return getFileVersion(exe, version); + return getFileVersion(exe, NULL, version); } static bool getTerminalVersionConEmu(FFstrbuf* exe, FFstrbuf* version) @@ -747,7 +748,7 @@ static bool getTerminalVersionConEmu(FFstrbuf* exe, FFstrbuf* version) if(version->length) return true; - return getFileVersion(exe, version); + return getFileVersion(exe, NULL, version); } #endif @@ -839,6 +840,9 @@ bool fftsGetTerminalVersion(FFstrbuf* processName, FF_MAYBE_UNUSED FFstrbuf* exe if(ffStrbufStartsWithIgnCaseS(processName, "ConEmu")) return getTerminalVersionConEmu(exe, version); + if(ffStrbufIgnCaseEqualS(processName, "warp.exe")) + return getFileVersion(exe, L"ProductVersion", version); + #endif #ifndef _WIN32 @@ -914,7 +918,7 @@ bool fftsGetTerminalVersion(FFstrbuf* processName, FF_MAYBE_UNUSED FFstrbuf* exe #ifdef _WIN32 - return getFileVersion(exe, version); + return getFileVersion(exe, NULL, version); #else diff --git a/src/detection/terminalshell/terminalshell_windows.c b/src/detection/terminalshell/terminalshell_windows.c index 97bfe1820b..68043ab28f 100644 --- a/src/detection/terminalshell/terminalshell_windows.c +++ b/src/detection/terminalshell/terminalshell_windows.c @@ -87,7 +87,7 @@ static void setShellInfoDetails(FFShellResult* result) if(wcsncmp(module.szModule, L"clink_dll_", strlen("clink_dll_")) == 0) { ffStrbufAppendS(&result->prettyName, " (with Clink "); - ffGetFileVersion(module.szExePath, &result->prettyName); + ffGetFileVersion(module.szExePath, NULL, &result->prettyName); ffStrbufAppendC(&result->prettyName, ')'); break; } diff --git a/src/detection/weather/weather.c b/src/detection/weather/weather.c index c340db6369..8f1a2accba 100644 --- a/src/detection/weather/weather.c +++ b/src/detection/weather/weather.c @@ -1,4 +1,5 @@ #include "weather.h" +#include "common/networking/networking.h" #define FF_UNITIALIZED ((const char*)(uintptr_t) -1) static FFNetworkingState state; diff --git a/src/detection/weather/weather.h b/src/detection/weather/weather.h index 55a5786281..24dde33bba 100644 --- a/src/detection/weather/weather.h +++ b/src/detection/weather/weather.h @@ -1,6 +1,6 @@ #pragma once -#include "common/networking.h" +#include "fastfetch.h" void ffPrepareWeather(FFWeatherOptions* options); const char* ffDetectWeather(FFWeatherOptions* options, FFstrbuf* result); diff --git a/src/detection/wifi/wifi_apple.m b/src/detection/wifi/wifi_apple.m index aefe49c881..082b9966e3 100644 --- a/src/detection/wifi/wifi_apple.m +++ b/src/detection/wifi/wifi_apple.m @@ -4,61 +4,41 @@ #import -struct Apple80211; // https://code.google.com/archive/p/iphone-wireless/wikis/Apple80211.wiki - -// 0 is successful; < 0 is failure -int Apple80211GetInfoCopy(struct Apple80211 *handle, CFDictionaryRef *info) __attribute__((weak_import)); - -@interface CWInterface() -@property (readonly) struct Apple80211* device; -@end - -inline static NSDictionary* getWifiInfoByApple80211(CWInterface* inf) +static bool queryIpconfig(const char* ifName, FFstrbuf* result) { - if (!inf.device || !Apple80211GetInfoCopy) return NULL; - CFDictionaryRef result = NULL; - if (Apple80211GetInfoCopy(inf.device, &result) < 0) return NULL; - return CFBridgingRelease(result); + return ffProcessAppendStdOut(result, (char* const[]) { + "/usr/sbin/ipconfig", + "getsummary", + (char* const) ifName, + NULL + }) == NULL; } -static NSDictionary* getWifiInfoBySystemProfiler(NSString* ifName) +static bool getWifiInfoByIpconfig(FFstrbuf* ipconfig, const char* prefix, FFstrbuf* result) { - // Warning: costs about 2s on my machine - static NSArray* spData; - static bool inited; - if (!inited) - { - inited = true; - FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); - if (ffProcessAppendStdOut(&buffer, (char* const[]) { - "system_profiler", - "SPAirPortDataType", - "-xml", - "-detailLevel", - "basic", - NULL - }) != NULL) - return nil; - - spData = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytes:buffer.chars length:buffer.length] - options:NSPropertyListImmutable - format:nil - error:nil]; - } - - if (spData) - { - for (NSDictionary* data in spData[0][@"_items"]) - { - for (NSDictionary* inf in data[@"spairport_airport_interfaces"]) - { - if ([ifName isEqualToString:inf[@"_name"]]) - return inf[@"spairport_current_network_information"]; - } - } - } - - return NULL; + // `ipconfig getsummary ` returns a string like this: + // { + // BSSID : + // IPv4 : { + // ... + // } + // IPv6 : { + // ... + // } + // InterfaceType : WiFi + // LinkStatusActive : TRUE + // NetworkID : XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + // SSID : XXXXXX + // Security : WPA2_PSK + // } + + const char* start = memmem(ipconfig->chars, ipconfig->length, prefix, strlen(prefix)); + if (!start) return false; + start += strlen(prefix); + const char* end = strchr(start, '\n'); + if (!end) return false; + ffStrbufSetNS(result, (uint32_t) (end - start), start); + return false; } const char* ffDetectWifi(FFlist* result) @@ -92,22 +72,19 @@ @interface CWInterface() if(inf.interfaceMode == kCWInterfaceModeNone) continue; - NSDictionary* apple = nil; // For getWifiInfoByApple80211 - NSDictionary* sp = nil; // For getWifiInfoBySystemProfiler + FF_STRBUF_AUTO_DESTROY ipconfig = ffStrbufCreate(); if (inf.ssid) // https://developer.apple.com/forums/thread/732431 ffStrbufAppendS(&item->conn.ssid, inf.ssid.UTF8String); - else if (apple || (apple = getWifiInfoByApple80211(inf))) - ffStrbufAppendS(&item->conn.ssid, [apple[@"SSID_STR"] UTF8String]); - else if (sp || (sp = getWifiInfoBySystemProfiler(inf.interfaceName))) - ffStrbufAppendS(&item->conn.ssid, [sp[@"_name"] UTF8String]); + else if (ipconfig.length || (queryIpconfig(item->inf.description.chars, &ipconfig))) + getWifiInfoByIpconfig(&ipconfig, "\n SSID : ", &item->conn.ssid); else ffStrbufSetStatic(&item->conn.ssid, ""); // https://developer.apple.com/forums/thread/732431 if (inf.bssid) ffStrbufAppendS(&item->conn.bssid, inf.bssid.UTF8String); - else if (apple || (apple = getWifiInfoByApple80211(inf))) - ffStrbufAppendS(&item->conn.bssid, [apple[@"BSSID"] UTF8String]); + else if (ipconfig.length || (queryIpconfig(item->inf.description.chars, &ipconfig))) + getWifiInfoByIpconfig(&ipconfig, "\n BSSID : ", &item->conn.bssid); switch(inf.activePHYMode) { @@ -138,22 +115,6 @@ @interface CWInterface() default: if (inf.activePHYMode < 8) ffStrbufAppendF(&item->conn.protocol, "Unknown (%ld)", inf.activePHYMode); - else if (sp || (sp = getWifiInfoBySystemProfiler(inf.interfaceName))) - { - ffStrbufSetS(&item->conn.protocol, [sp[@"spairport_network_phymode"] UTF8String]); - if (ffStrbufStartsWithS(&item->conn.protocol, "802.11")) - { - const char* subProtocol = item->conn.protocol.chars + strlen("802.11"); - if (ffStrEquals(subProtocol, "be")) - ffStrbufSetStatic(&item->conn.protocol, "802.11be (Wi-Fi 7)"); - else if (ffStrEquals(subProtocol, "ax")) - ffStrbufSetStatic(&item->conn.protocol, "802.11ax (Wi-Fi 6)"); - else if (ffStrEquals(subProtocol, "ac")) - ffStrbufSetStatic(&item->conn.protocol, "802.11ac (Wi-Fi 5)"); - else if (ffStrEquals(subProtocol, "n")) - ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)"); - } - } break; } item->conn.signalQuality = (double) (inf.rssiValue >= -50 ? 100 : inf.rssiValue <= -100 ? 0 : (inf.rssiValue + 100) * 2); @@ -210,82 +171,9 @@ @interface CWInterface() ffStrbufSetStatic(&item->conn.security, "OWE Transition"); break; case kCWSecurityUnknown: - // Sonoma... - if (apple || (apple = getWifiInfoByApple80211(inf))) - { - NSDictionary* authType = apple[@"AUTH_TYPE"]; - if (authType) - { - // AUTH_LOWER seems useless. `airport` verifies if its value is between 1 and 3, and prints `unknown` if not - - NSNumber* authUpper = authType[@"AUTH_UPPER"]; - if (!authUpper) - ffStrbufSetStatic(&item->conn.security, "Insecure"); - else - { - int authUpperValue = authUpper.intValue; - switch (authUpperValue) - { - case 1: - ffStrbufSetStatic(&item->conn.security, "WPA"); - break; - case 2: - ffStrbufSetStatic(&item->conn.security, "WPA-PSK"); - break; - case 4: - ffStrbufSetStatic(&item->conn.security, "WPA2"); - break; - case 8: - ffStrbufSetStatic(&item->conn.security, "WPA2-PSK"); - break; - case 16: - ffStrbufSetStatic(&item->conn.security, "FT-WPA2-PSK"); - break; - case 32: - ffStrbufSetStatic(&item->conn.security, "LEAP"); - break; - case 64: - ffStrbufSetStatic(&item->conn.security, "802.1X"); - break; - case 128: - ffStrbufSetStatic(&item->conn.security, "FT-WPA2"); - break; - case 256: - ffStrbufSetStatic(&item->conn.security, "WPS"); - break; - case 4096: - ffStrbufSetStatic(&item->conn.security, "WPA3-SAE"); - break; - case 8192: - ffStrbufSetStatic(&item->conn.security, "WPA3-FT-SAE"); - break; - default: // TODO: support more auth types - ffStrbufAppendF(&item->conn.security, "To be supported (%d)", authUpperValue); - break; - } - } - } - } - else if (sp || (sp = getWifiInfoBySystemProfiler(inf.interfaceName))) - { - ffStrbufSetS(&item->conn.security, [sp[@"spairport_security_mode"] UTF8String]); - ffStrbufSubstrAfterFirstS(&item->conn.security, "_mode_"); - if (ffStrbufEqualS(&item->conn.security, "none")) - ffStrbufSetStatic(&item->conn.security, "Insecure"); - else - { - ffStrbufReplaceAllC(&item->conn.security, '_', ' '); - if (ffStrbufStartsWithS(&item->conn.security, "wpa")) - { - item->conn.security.chars[0] = 'W'; - item->conn.security.chars[1] = 'P'; - item->conn.security.chars[2] = 'A'; - char* sub = strchr(item->conn.security.chars, ' '); - if (sub && sub[1]) - sub[1] = (char) toupper(sub[1]); - } - } - } + // Sonoma? + if (ipconfig.length || (queryIpconfig(item->inf.description.chars, &ipconfig))) + getWifiInfoByIpconfig(&ipconfig, "\n Security : ", &item->conn.security); break; default: ffStrbufAppendF(&item->conn.security, "Unknown (%ld)", inf.security); diff --git a/src/detection/wm/wm_windows.cpp b/src/detection/wm/wm_windows.cpp index e111968050..62e20a9268 100644 --- a/src/detection/wm/wm_windows.cpp +++ b/src/detection/wm/wm_windows.cpp @@ -79,7 +79,7 @@ const char* ffDetectWMVersion(const FFstrbuf* wmName, FFstrbuf* result, FF_MAYBE wchar_t fullPath[MAX_PATH]; wcscpy(fullPath, pPath); wcscat(fullPath, L"\\dwm.exe"); - ffGetFileVersion(fullPath, result); + ffGetFileVersion(fullPath, NULL, result); } CoTaskMemFree(pPath); return NULL; diff --git a/src/logo/ascii/bredos.txt b/src/logo/ascii/bredos.txt new file mode 100644 index 0000000000..d8542fce08 --- /dev/null +++ b/src/logo/ascii/bredos.txt @@ -0,0 +1,9 @@ + ████████████████ + ██████░░░░░░████░░░░██ ████ + ████░░░░████░░░░░░██░░░░░░██░░░░██ + ██░░░░░░░░░░██░░░░░░██░░░░░░░░██░░░░██ + ██░░░░░░░░░░██░░░░░░░░░░░░░░░░██░░░░░░██ +██▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓██ +██▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓▓▓██ +██▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓██ + ██████████████████████████ \ No newline at end of file diff --git a/src/logo/ascii/foxos.txt b/src/logo/ascii/foxos.txt deleted file mode 100644 index 91f114bb0b..0000000000 --- a/src/logo/ascii/foxos.txt +++ /dev/null @@ -1,8 +0,0 @@ - ________ ________ ___ ___ ________ ________ -|\ _____\\ __ \ |\ \ / /|\ __ \|\ ____\ -\ \ \__/\ \ \|\ \ \ \ \/ / | \ \|\ \ \ \___|_ - \ \ __\\ \ \\\ \ \ \ / / \ \ \\\ \ \_____ \ - \ \ \_| \ \ \\\ \ / \/ \ \ \\\ \|____|\ \ - \ \__\ \ \_______\/ /\ \ \ \_______\____\_\ \ - \|__| \|_______/__/ /\ __\ \|_______|\_________\ - |__|/ \|__| \|_________| \ No newline at end of file diff --git a/src/logo/ascii/netbsd2.txt b/src/logo/ascii/netbsd2.txt new file mode 100644 index 0000000000..81777a466d --- /dev/null +++ b/src/logo/ascii/netbsd2.txt @@ -0,0 +1,20 @@ + __,gnnnOCCCCCOObaau,_ +$2 _._ $1__,gnnCCCCCCCCOPF"'' +$2 (N\\$1XCbngg,._____.,gnnndCCCCCCCCCCCCF"___,,,,___ +$2 \N\\$1XCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCOOOOPYvv. +$2 \N\\$1XCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCPF"'' +$2 \N\\$1XCCCCCCCCCCCCCCCCCCCCCCCCCOF"' +$2 \N\\$1XCCCCCCCCCCCCCCCCCCCCOF"' +$2 \N\\$1XCCCCCCCCCCCCCCCPF"' +$2 \N\\$1\"PCOCCCOCCFP"" +$2 \N\\ + \N\\ + \N\\ + \NN\ + \NN\ + \NNA. + \NNA, + \NNN, + \NNN\ + \NNN\ + \NNNA \ No newline at end of file diff --git a/src/logo/ascii/wolfos.txt b/src/logo/ascii/wolfos.txt new file mode 100644 index 0000000000..f0a7fed32d --- /dev/null +++ b/src/logo/ascii/wolfos.txt @@ -0,0 +1,5 @@ +__ __ _ __ ___ ____ +\ \ / /__ | |/ _|/ _ \/ ___| + \ \ /\ / / _ \| | |_| | | \___ \ + \ V V / (_) | | _| |_| |___) | + \_/\_/ \___/|_|_| \___/|____/ diff --git a/src/logo/builtin.c b/src/logo/builtin.c index 3e7f81d5d2..19dffec73e 100644 --- a/src/logo/builtin.c +++ b/src/logo/builtin.c @@ -766,6 +766,16 @@ static const FFlogo B[] = { .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_GREEN, }, + // BredOS + { + .names = {"Bredos"}, + .lines = FASTFETCH_DATATEXT_LOGO_BREDOS, + .colors = { + FF_COLOR_FG_RGB "198;151;66", //grey + }, + .colorKeys = FF_COLOR_FG_RGB "198;151;66", + .colorTitle = FF_COLOR_FG_RGB "198;151;66", + }, // BSD { .names = {"BSD"}, @@ -1093,6 +1103,16 @@ static const FFlogo C[] = { .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_WHITE, }, + // Common Torizon + { + .names = {"common-torizon"}, + .lines = FASTFETCH_DATATEXT_LOGO_TORIZONCORE, + .colors = { + FF_COLOR_FG_LIGHT_WHITE, + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_BLUE + }, + }, // Cosmic DE { .names = {"Cosmic"}, @@ -1748,14 +1768,6 @@ static const FFlogo F[] = { .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_WHITE, }, - // FoxOS - { - .names = {"FoxOS"}, - .lines = FASTFETCH_DATATEXT_LOGO_FOXOS, - .colors = { - FF_COLOR_FG_YELLOW, - }, - }, // FreeBSD { .names = {"Freebsd"}, @@ -3136,6 +3148,18 @@ static const FFlogo N[] = { .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_WHITE, }, + // NetBSD2 + { + .names = {"NetBSD2"}, + .lines = FASTFETCH_DATATEXT_LOGO_NETBSD2, + .type = FF_LOGO_LINE_TYPE_ALTER_BIT, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_WHITE, + }, + .colorKeys = FF_COLOR_FG_RED, + .colorTitle = FF_COLOR_FG_WHITE, + }, // NetBSD Small { .names = {"NetBSD_small"}, @@ -5061,6 +5085,15 @@ static const FFlogo W[] = { .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_BLUE, }, + // WolfOS + { + .names = {"WolfOS"}, + .lines = FASTFETCH_DATATEXT_LOGO_WOLFOS, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_GREEN, + }, + }, // LAST {}, }; diff --git a/src/modules/localip/localip.c b/src/modules/localip/localip.c index 8553809297..2500ab32d0 100644 --- a/src/modules/localip/localip.c +++ b/src/modules/localip/localip.c @@ -525,7 +525,7 @@ void ffInitLocalIpOptions(FFLocalIpOptions* options) ffOptionInitModuleArg(&options->moduleArgs, "󰩟"); options->showType = FF_LOCALIP_TYPE_IPV4_BIT | FF_LOCALIP_TYPE_PREFIX_LEN_BIT - #if !__ANDROID__ /*Permission denied*/ && !__OpenBSD__ /*Report invalid argument for some reason*/ && !__DragonFly__ /*Doesn't work*/ + #if !__ANDROID__ /*Permission denied*/ | FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT #endif ; diff --git a/src/modules/opengl/opengl.c b/src/modules/opengl/opengl.c index 8aeeb9c5d2..f6d5398953 100644 --- a/src/modules/opengl/opengl.c +++ b/src/modules/opengl/opengl.c @@ -56,7 +56,6 @@ bool ffParseOpenGLCommandOptions(FFOpenGLOptions* options, const char* key, cons { "auto", FF_OPENGL_LIBRARY_AUTO }, { "egl", FF_OPENGL_LIBRARY_EGL }, { "glx", FF_OPENGL_LIBRARY_GLX }, - { "osmesa", FF_OPENGL_LIBRARY_OSMESA }, {} }); return true; @@ -85,7 +84,6 @@ void ffParseOpenGLJsonObject(FFOpenGLOptions* options, yyjson_val* module) { "auto", FF_OPENGL_LIBRARY_AUTO }, { "egl", FF_OPENGL_LIBRARY_EGL }, { "glx", FF_OPENGL_LIBRARY_GLX }, - { "osmesa", FF_OPENGL_LIBRARY_OSMESA }, {}, }); if (error) @@ -119,9 +117,6 @@ void ffGenerateOpenGLJsonConfig(FFOpenGLOptions* options, yyjson_mut_doc* doc, y case FF_OPENGL_LIBRARY_GLX: yyjson_mut_obj_add_str(doc, module, "library", "glx"); break; - case FF_OPENGL_LIBRARY_OSMESA: - yyjson_mut_obj_add_str(doc, module, "library", "osmesa"); - break; } } } diff --git a/src/modules/opengl/option.h b/src/modules/opengl/option.h index 54fd466c98..bdd4e446a8 100644 --- a/src/modules/opengl/option.h +++ b/src/modules/opengl/option.h @@ -9,7 +9,6 @@ typedef enum __attribute__((__packed__)) FFOpenGLLibrary FF_OPENGL_LIBRARY_AUTO, FF_OPENGL_LIBRARY_EGL, FF_OPENGL_LIBRARY_GLX, - FF_OPENGL_LIBRARY_OSMESA } FFOpenGLLibrary; typedef struct FFOpenGLOptions diff --git a/src/options/display.c b/src/options/display.c index 247abfc6fc..61393a91ba 100644 --- a/src/options/display.c +++ b/src/options/display.c @@ -317,6 +317,10 @@ bool ffOptionsParseDisplayCommandLine(FFOptionsDisplay* options, const char* key options->pipe = ffOptionParseBoolean(value); else if(ffStrEqualsIgnCase(key, "--show-errors")) options->showErrors = ffOptionParseBoolean(value); + #ifndef NDEBUG + else if(ffStrEqualsIgnCase(key, "--debug")) + options->debugMode = ffOptionParseBoolean(value); + #endif else if(ffStrEqualsIgnCase(key, "--disable-linewrap")) options->disableLinewrap = ffOptionParseBoolean(value); else if(ffStrEqualsIgnCase(key, "--hide-cursor")) @@ -505,6 +509,7 @@ void ffOptionsInitDisplay(FFOptionsDisplay* options) options->disableLinewrap = !options->pipe; #else options->disableLinewrap = false; + options->debugMode = false; #endif options->hideCursor = false; diff --git a/src/options/display.h b/src/options/display.h index f96f304bec..74c4ecb3bb 100644 --- a/src/options/display.h +++ b/src/options/display.h @@ -32,6 +32,9 @@ typedef struct FFOptionsDisplay int32_t stat; // <0: disable stat; 0: no threshold; >0: threshold in ms bool pipe; //disables all escape sequences bool showErrors; + #ifndef NDEBUG + bool debugMode; + #endif bool disableLinewrap; bool hideCursor; FFSizeBinaryPrefixType sizeBinaryPrefix; diff --git a/src/util/debug.h b/src/util/debug.h new file mode 100644 index 0000000000..9e6bf7228c --- /dev/null +++ b/src/util/debug.h @@ -0,0 +1,28 @@ +#include "common/time.h" + +static inline const char* ffFindFileName(const char* file) +{ + const char* lastSlash = __builtin_strrchr(file, '/'); + #ifdef _WIN32 + if (lastSlash == NULL) + lastSlash = __builtin_strrchr(file, '\\'); + #endif + if (lastSlash != NULL) + return lastSlash + 1; + return file; +} + +#ifndef NDEBUG + #define FF_DEBUG_PRINT(file_, line_, format_, ...) \ + do { \ + if (instance.config.display.debugMode) \ + fprintf(stderr, "[%s%4d, %s] " format_ "\n", ffFindFileName(file_), line_, ffTimeToTimeStr(ffTimeGetNow()), ##__VA_ARGS__); \ + } while (0) +#else + #define FF_DEBUG_PRINT(file_, line_, format_, ...) \ + do { } while (0) +#endif + +#define FF_DEBUG(format, ...) FF_DEBUG_PRINT(__FILE__, __LINE__, format, ##__VA_ARGS__) + +const char* ffDebugWin32Error(unsigned long errorCode); diff --git a/src/util/debug_windows.c b/src/util/debug_windows.c new file mode 100644 index 0000000000..e6bf3f7865 --- /dev/null +++ b/src/util/debug_windows.c @@ -0,0 +1,28 @@ +#include "debug.h" + +#include + +const char* ffDebugWin32Error(DWORD errorCode) +{ + static char buffer[256]; + + DWORD len = FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + (DWORD) errorCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + buffer, + sizeof(buffer), + NULL); + + if (len == 0) { + snprintf(buffer, sizeof(buffer), "Unknown error code (%lu)", errorCode); + } else { + // Remove trailing newline + if (buffer[len - 1] == '\n') buffer[len - 1] = '\0'; + if (buffer[len - 2] == '\r') buffer[len - 2] = '\0'; + snprintf(buffer + len - 2, sizeof(buffer) - len + 2, " (%lu)", errorCode); + } + + return buffer; +} diff --git a/src/util/kmod.c b/src/util/kmod.c index 60d1889bf1..c558481afc 100644 --- a/src/util/kmod.c +++ b/src/util/kmod.c @@ -31,6 +31,50 @@ bool ffKmodLoaded(const char* modName) { return modfind(modName) >= 0; } +#elif __NetBSD__ +#include "util/stringUtils.h" + +#include +#include + +typedef struct __attribute__((__packed__)) FFNbsdModList +{ + int len; + modstat_t mods[]; +} FFNbsdModList; + +bool ffKmodLoaded(const char* modName) +{ + static FFNbsdModList* list = NULL; + + if (list == NULL) + { + struct iovec iov = {}; + + // 初始尝试分配的内存大小 + for (size_t len = 8192;; len = iov.iov_len) + { + iov.iov_len = len; + iov.iov_base = realloc(iov.iov_base, len); + if (modctl(MODCTL_STAT, &iov) < 0) + { + free(iov.iov_base); + return true; // ignore errors + } + + if (len >= iov.iov_len) break; + } + list = (FFNbsdModList*) iov.iov_base; + } + + for (int i = 0; i < list->len; i++) + { + if (ffStrEquals(list->mods[i].ms_name, modName)) + return true; + } + + return false; +} #elif __APPLE__ #include "util/apple/cf_helpers.h" #include diff --git a/src/util/windows/version.c b/src/util/windows/version.c index e01fe27a14..684a4fcce5 100644 --- a/src/util/windows/version.c +++ b/src/util/windows/version.c @@ -1,28 +1,59 @@ #include "util/windows/version.h" #include "util/mallocHelper.h" +#include "util/windows/unicode.h" #include -bool ffGetFileVersion(const wchar_t* filePath, FFstrbuf* version) +bool ffGetFileVersion(const wchar_t* filePath, const wchar_t* stringName, FFstrbuf* version) { DWORD handle; DWORD size = GetFileVersionInfoSizeW(filePath, &handle); - if(size > 0) + if (size > 0) { - FF_AUTO_FREE void* versionData = malloc(size); - if(GetFileVersionInfoW(filePath, handle, size, versionData)) + FF_AUTO_FREE void *versionData = malloc(size); + if (GetFileVersionInfoW(filePath, handle, size, versionData)) { - VS_FIXEDFILEINFO* verInfo; - UINT len; - if(VerQueryValueW(versionData, L"\\", (void**)&verInfo, &len) && len && verInfo->dwSignature == 0xFEEF04BD) + if (!stringName) { - ffStrbufAppendF(version, "%u.%u.%u.%u", - (unsigned)(( verInfo->dwProductVersionMS >> 16 ) & 0xffff), - (unsigned)(( verInfo->dwProductVersionMS >> 0 ) & 0xffff), - (unsigned)(( verInfo->dwProductVersionLS >> 16 ) & 0xffff), - (unsigned)(( verInfo->dwProductVersionLS >> 0 ) & 0xffff) - ); - return true; + VS_FIXEDFILEINFO* verInfo; + UINT len; + if (VerQueryValueW(versionData, L"\\", (void **)&verInfo, &len) && len && verInfo->dwSignature == 0xFEEF04BD) + { + ffStrbufAppendF(version, "%u.%u.%u.%u", + (unsigned)((verInfo->dwProductVersionMS >> 16) & 0xffff), + (unsigned)((verInfo->dwProductVersionMS >> 0) & 0xffff), + (unsigned)((verInfo->dwProductVersionLS >> 16) & 0xffff), + (unsigned)((verInfo->dwProductVersionLS >> 0) & 0xffff)); + return true; + } + } + else + { + struct + { + WORD language; + WORD codePage; + }* translations; + + UINT translationsLen; + + if (VerQueryValueW(versionData, L"\\VarFileInfo\\Translation", + (void **) &translations, &translationsLen) && + translationsLen >= sizeof(*translations)) + { + wchar_t subBlock[128]; + snwprintf(subBlock, ARRAY_SIZE(subBlock), L"\\StringFileInfo\\%04x%04x\\%ls", + translations[0].language, translations[0].codePage, stringName); + + wchar_t* value; + UINT valueLen; + + if (VerQueryValueW(versionData, subBlock, (void **)&value, &valueLen) && valueLen > 0) + { + ffStrbufSetWS(version, value); + return true; + } + } } } } diff --git a/src/util/windows/version.h b/src/util/windows/version.h index c6206fa5b4..73e7cc1e50 100644 --- a/src/util/windows/version.h +++ b/src/util/windows/version.h @@ -1,3 +1,15 @@ #include "fastfetch.h" -bool ffGetFileVersion(const wchar_t* filePath, FFstrbuf* version); + +/** + * @brief Retrieves a specific version string for a Windows file. + * + * This function gets a version string from a Windows file's version information. + * + * @param filePath The path to the file for which version information is requested. + * @param stringName The name of the specific version string to retrieve (e.g., "FileVersion", "ProductVersion"). + * @param version Pointer to an FFstrbuf where the version string will be stored. + * + * @return true if the version string was successfully retrieved, false otherwise. + */ +bool ffGetFileVersion(const wchar_t* filePath, const wchar_t* stringName, FFstrbuf* version);