diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 90cf569b75..e24560b4ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -190,11 +190,14 @@ jobs: run: cmake --version - name: configure project - run: CC=gcc-13 PKG_CONFIG_PATH=/home/linuxbrew/.linuxbrew/lib/pkgconfig:$PKG_CONFIG_PATH cmake -DCMAKE_C_FLAGS="-m32 -march=i686 -mtune=i686" -DCMAKE_SYSTEM_PROCESSOR_OVERRIDE=i686 -GNinja -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_INSTALL_PREFIX=/usr -DENABLE_DIRECTX_HEADERS=Off . + run: CC=gcc-13 PKG_CONFIG_PATH=/home/linuxbrew/.linuxbrew/lib/pkgconfig:$PKG_CONFIG_PATH cmake -DCMAKE_C_FLAGS="-m32 -march=i686 -mtune=i686" -DCMAKE_SYSTEM_PROCESSOR_OVERRIDE=i686 -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=i386 -GNinja -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_INSTALL_PREFIX=/usr -DENABLE_DIRECTX_HEADERS=Off . - name: build project run: cmake --build . --target package --verbose -j4 + - name: check deb package + run: dpkg -I fastfetch-*.deb + - name: list features run: ./fastfetch --list-features diff --git a/CHANGELOG.md b/CHANGELOG.md index ed64b20c3d..8ea1d55f84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,41 @@ +# 2.55.0 + +Changes: +* Commands are now executed in parallel by default to improve performance (#2045, Command) + * This behavior can be disabled in the config file with `"parallel": false` if it causes problems with certain scripts +* Folder/filesystem hiding is moved to the detection stage; hidden entries are no longer probed, improving performance (#2043, Disk) + +Features: +* Adds `command.parallel` and `command.useStdErr` config options (Command) + * `parallel`: set to `false` to disable parallel execution (see Changes above) + * `useStdErr`: set to `true` to use stderr output instead of stdout +* Adds the command-line flag `--dynamic-interval ` to enable dynamic output auto-refresh (#2041) + * Due to internal limitations, some modules do not support dynamic updates (notably Display and Media) +* Adds support for using the current playing media's cover art as a logo source (Media / Logo) + * Usage: `"logo": { "type": "", "source": "media-cover" }` in JSON config; or `-- media-cover` in command line + * Supports local sources only +* Adds native GPU detection support on OpenBSD and NetBSD (instead of depending on `libpciaccess`) (GPU) + * No functional changes + * Root privileges are required to access PCI config space on OpenBSD (as always) +* Adds GPU detection support on GNU/Hurd (GPU) + * Requires building with `libpciaccess` +* Shows Debian point release on Raspberry Pi OS (#2032, OS, Linux) +* Adds `Brush` shell version detection (Shell) +* Improves Mac family detection via prefix matching (Host) + +Bugfixes: +* Ignores `run-parts` during terminal/shell detection (#2048, Terminal / Shell, Linux) +* Fixes fish version detection when `LC_ALL` is set (#2014, Shell, Linux) +* Hides the module when no desktop icons are found (#2023, Icons, Windows) +* Skips auxiliary display controllers to prevent the module from reporting duplicate entries (#2034, GPU, Linux) +* Refines Apple rpath handling; fixes building for the Homebrew version on macOS (#1998, CMake) + +Logos: +* Adds Vincent OS and MacaroniOS + # 2.54.0 -Windows binaries in Release page are now signed using SignPath. +Windows binaries in Release page are now signed by SignPath. Changes: * Moves macOS and Windows design language detection from the DE module to the Theme module diff --git a/CMakeLists.txt b/CMakeLists.txt index 930dd540d0..77647ccc18 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.54.0 + VERSION 2.55.0 LANGUAGES C DESCRIPTION "Fast neofetch-like system information tool" HOMEPAGE_URL "https://github.com/fastfetch-cli/fastfetch" @@ -93,7 +93,7 @@ cmake_dependent_option(ENABLE_DIRECTX_HEADERS "Enable DirectX headers for WSL" O cmake_dependent_option(ENABLE_ELF "Enable libelf" ON "LINUX OR ANDROID OR DragonFly OR Haiku OR GNU" OFF) cmake_dependent_option(ENABLE_THREADS "Enable multithreading" ON "Threads_FOUND" OFF) cmake_dependent_option(ENABLE_LIBZFS "Enable libzfs" ON "LINUX OR FreeBSD OR SunOS" OFF) -cmake_dependent_option(ENABLE_PCIACCESS "Enable libpciaccess" ON "NetBSD OR OpenBSD" OFF) +cmake_dependent_option(ENABLE_PCIACCESS "Enable libpciaccess" ON "GNU" OFF) option(ENABLE_ZLIB "Enable zlib" ON) option(ENABLE_SYSTEM_YYJSON "Use system provided (instead of fastfetch embedded) yyjson library" OFF) @@ -389,6 +389,7 @@ set(LIBFASTFETCH_SRC src/detection/chassis/chassis.c src/detection/cpu/cpu.c src/detection/cpuusage/cpuusage.c + src/detection/command/command.c src/detection/disk/disk.c src/detection/diskio/diskio.c src/detection/displayserver/displayserver.c @@ -794,7 +795,7 @@ elseif(NetBSD) src/detection/displayserver/linux/xcb.c src/detection/displayserver/linux/xlib.c src/detection/font/font_linux.c - src/detection/gpu/gpu_general.c + src/detection/gpu/gpu_nbsd.c src/detection/gpu/gpu_pci.c src/detection/gtk_qt/gtk.c src/detection/host/host_nbsd.c @@ -878,7 +879,7 @@ elseif(OpenBSD) src/detection/displayserver/linux/xlib.c src/detection/font/font_linux.c src/detection/gpu/gpu_pci.c - src/detection/gpu/gpu_general.c + src/detection/gpu/gpu_obsd.c src/detection/gtk_qt/gtk.c src/detection/host/host_obsd.c src/detection/lm/lm_nosupport.c @@ -1254,7 +1255,7 @@ elseif(GNU) src/detection/displayserver/linux/xcb.c src/detection/displayserver/linux/xlib.c src/detection/font/font_linux.c - src/detection/gpu/gpu_nosupport.c + src/detection/gpu/gpu_general.c src/detection/gpu/gpu_pci.c src/detection/gtk_qt/gtk.c src/detection/host/host_nosupport.c @@ -1340,15 +1341,15 @@ add_library(libfastfetch OBJECT ) if(yyjson_FOUND) - target_compile_definitions(libfastfetch PUBLIC FF_USE_SYSTEM_YYJSON) + target_compile_definitions(libfastfetch PUBLIC FF_USE_SYSTEM_YYJSON=1) target_link_libraries(libfastfetch PUBLIC yyjson::yyjson) # `target_link_libraries(yyjson::yyjson)` sets rpath implicitly -else() - # Used for dlopen finding dylibs installed by homebrew - # `/opt/homebrew/lib` is not on in dlopen search path by default - if(APPLE) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,/opt/homebrew/lib -Wl,-rpath,/usr/local/lib") - endif() +endif() + +# Used for dlopen finding dylibs installed by homebrew +# `/opt/homebrew/lib` is not on in dlopen search path by default +if(APPLE AND NOT BINARY_LINK_TYPE STREQUAL "dlopen") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,/opt/homebrew/lib -Wl,-rpath,/usr/local/lib") endif() if(ANDROID) @@ -1823,8 +1824,14 @@ if(NOT WIN32) CHECK_INCLUDE_FILE("wordexp.h" HAVE_WORDEXP) if(HAVE_WORDEXP) target_compile_definitions(libfastfetch PRIVATE FF_HAVE_WORDEXP=1) + message(STATUS "wordexp.h found, wordexp support enabled") + else() + set(ENABLE_WORDEXP OFF) endif() endif() + if(NOT ENABLE_WORDEXP) + message(STATUS "wordexp.h not found or disabled, glob used instead") + endif() if(ENABLE_THREADS AND CMAKE_USE_PTHREADS_INIT) CHECK_INCLUDE_FILE("pthread_np.h" HAVE_PTHREAD_NP) if(HAVE_PTHREAD_NP) diff --git a/README.md b/README.md index 8f9ab82c5d..183bb1792e 100644 --- a/README.md +++ b/README.md @@ -322,6 +322,11 @@ If you find Fastfetch useful, please consider donating. * Current maintainer: [@CarterLi](https://paypal.me/zhangsongcui) * Original author: [@LinusDierheimer](https://github.com/sponsors/LinusDierheimer) +## Code signing + +* Free code signing provided by [SignPath.io](https://about.signpath.io/), certificate by [SignPath Foundation](https://signpath.org/) +* This program will not transfer any information to other networked systems unless specifically requested by the user or the person installing or operating it + ## Star History Give us a star to show your support! diff --git a/debian/changelog b/debian/changelog.tpl similarity index 96% rename from debian/changelog rename to debian/changelog.tpl index d44caadef4..6a1b897380 100644 --- a/debian/changelog +++ b/debian/changelog.tpl @@ -1,3 +1,16 @@ +fastfetch (2.54.1~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium + + * Fix building on plucky + + -- Carter Li Mon, 20 Oct 2025 14:27:02 +0800 + +fastfetch (2.54.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium + + * Update to 2.54.0 + * Test independent changelog entries for different Ubuntu releases + + -- Carter Li Mon, 20 Oct 2025 10:47:02 +0800 + fastfetch (2.53.0) jammy; urgency=medium * Update to 2.53.0 @@ -435,4 +448,3 @@ fastfetch (2.7.1) jammy; urgency=medium * Initial release. -- Carter Li Tue, 06 Feb 2024 15:01:11 +0800 - diff --git a/debian/control b/debian/control index 652c2336d8..ed371f48a7 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: fastfetch Section: universe/utils Priority: optional Maintainer: Carter Li -Build-Depends: libelf-dev, libvulkan-dev, libwayland-dev, libxrandr-dev, libxcb-randr0-dev, libdconf-dev, libdbus-1-dev, libmagickcore-dev, libsqlite3-dev, librpm-dev, libegl-dev, libglx-dev, ocl-icd-opencl-dev, libpulse-dev, libdrm-dev, libddcutil-dev, libchafa-dev, 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: libelf-dev, libvulkan-dev, libwayland-dev, libxrandr-dev, libxcb-randr0-dev, libdconf-dev, libdbus-1-dev, libmagickcore-dev, libsqlite3-dev, librpm-dev, libegl-dev, libglx-dev, ocl-icd-opencl-dev, libpulse-dev, libdrm-dev, libddcutil-dev, libchafa-dev, directx-headers-dev, pkgconf, cmake (>= 3.12), debhelper (>= 11.2), dh-cmake, dh-cmake-compat (= 1), dh-sequence-cmake, dh-sequence-ctest, ninja-build, python3-setuptools Standards-Version: 4.0.0 Homepage: https://github.com/fastfetch-cli/fastfetch diff --git a/debian/files b/debian/files deleted file mode 100644 index ee72e43dda..0000000000 --- a/debian/files +++ /dev/null @@ -1 +0,0 @@ -fastfetch_2.53.0_source.buildinfo universe/utils optional diff --git a/debian/publish.sh b/debian/publish.sh index a675a46ce3..af3a1fc0d7 100755 --- a/debian/publish.sh +++ b/debian/publish.sh @@ -1,4 +1,76 @@ #!/usr/bin/env bash +# WARNING: DO NOT RUN THIS SCRIPT UNLESS YOU KNOW WHAT YOU ARE DOING. -debuild -S -i -I -dput ppa:zhangsongcui3371/fastfetch ~/fastfetch_*.changes +set -euo pipefail + +command -v debuild >/dev/null 2>&1 || { echo "debuild not found. Install devscripts." >&2; exit 1; } +command -v dput >/dev/null 2>&1 || { echo "dput not found." >&2; exit 1; } + +SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" +ROOT_DIR="$(cd -- "$SCRIPT_DIR/.." >/dev/null 2>&1 && pwd)" +OUT_DIR="$(dirname "$ROOT_DIR")" +PPA="ppa:zhangsongcui3371/fastfetch" +CODENAMES=( jammy noble plucky questing resolute ) +DRY_RUN=0 + +if [[ -d "$OUT_DIR/build" ]]; then + echo "Output directory exists: $OUT_DIR/build" >&2 + echo "Please move or delete it before proceeding." >&2 + exit 1 +fi + +TEMPLATE="$ROOT_DIR/debian/changelog.tpl" +CHANGELOG_DEBIAN="$ROOT_DIR/debian/changelog" + +if [[ ! -f "$TEMPLATE" ]]; then + echo "Template not found: $TEMPLATE" >&2 + exit 1 +fi + +if ! grep -q '#UBUNTU_CODENAME#' "$TEMPLATE"; then + echo "Template missing placeholder: #UBUNTU_CODENAME#" >&2 + exit 1 +fi + +echo "IMPORTANT: Before proceeding, please ensure that 'debian/changelog.tpl' is up-to-date" +echo " with the correct version number, release notes, and maintainer information." +echo +echo " The template must contain a placeholder like '#UBUNTU_CODENAME#' for the codename." +echo +read -p "Have you reviewed and updated 'debian/changelog.tpl'? (y/N): " -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 +fi + +cleanup() { + rm -f "$CHANGELOG_DEBIAN" + rm -f "$ROOT_DIR/debian/files" +} +trap cleanup EXIT + +shopt -s nullglob + +for codename in "${CODENAMES[@]}"; do + echo "==> Publishing distro: $codename" + sed "s/#UBUNTU_CODENAME#/${codename}/g" "$TEMPLATE" > "$CHANGELOG_DEBIAN" + + ( cd "$ROOT_DIR" && debuild -S -i -I ) + + changes=( "$OUT_DIR"/fastfetch_*~${codename}_source.changes ) + if [[ ${#changes[@]} -ne 1 ]]; then + echo "Unable to uniquely identify .changes for '$codename' in: $OUT_DIR" >&2 + printf 'Found:\n'; printf ' %s\n' "${changes[@]}" >&2 || true + exit 1 + fi + + if [[ $DRY_RUN -eq 1 ]]; then + echo "[DRY-RUN] dput \"$PPA\" \"${changes[0]}\"" + else + dput "$PPA" "${changes[0]}" + fi + + rm -f "$OUT_DIR"/fastfetch_*~${codename}_source.{changes,dsc,tar.*} + + echo "<== Done: $codename" +done diff --git a/doc/json_schema.json b/doc/json_schema.json index f8f34d607e..7e64a7cf53 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -1008,7 +1008,7 @@ }, "type": { "type": "string", - "description": "Set whether to show icon before string keys", + "description": "Set whether to show builtin icon before string keys", "oneOf": [ { "const": "none", @@ -1020,7 +1020,7 @@ }, { "const": "icon", - "description": "Show icon keys (requires newest nerd font)" + "description": "Show builtin icon (requires newest nerd font)" }, { "const": "both", @@ -1047,7 +1047,6 @@ "description": "Show both icon and string with 4 spaces between them" } ], - ], "default": "string" }, "paddingLeft": { @@ -2102,6 +2101,16 @@ "description": "Set the command text to be executed", "type": "string" }, + "useStdErr": { + "description": "Set if stderr should be used instead of stdout for command output", + "type": "boolean", + "default": false + }, + "parallel": { + "description": "Set if the command should be executed in parallel with other commands\nImprove performance when using multiple commands, but may cause issues with some commands", + "type": "boolean", + "default": true + }, "key": { "$ref": "#/$defs/key" }, diff --git a/presets/examples/25.jsonc b/presets/examples/25.jsonc index 5d0ce75bb9..c00265716a 100644 --- a/presets/examples/25.jsonc +++ b/presets/examples/25.jsonc @@ -183,7 +183,7 @@ }, "keyIcon": "", "key": "│{#red}│ {icon} Clang │{$4}│{#keys}│{$2}", - "text": "clang --version | head -1 | awk '{print $NF}'", + "text": "clang --version | sed -n 's/.*version \\([0-9][0-9.]*\\).*/\\1/p'", "format": "clang {}" }, { diff --git a/src/common/init.c b/src/common/init.c index 9735c96ed2..8e27ebaf1c 100644 --- a/src/common/init.c +++ b/src/common/init.c @@ -30,6 +30,7 @@ static void initState(FFstate* state) ffPlatformInit(&state->platform); state->configDoc = NULL; state->resultDoc = NULL; + state->dynamicInterval = 0; { // don't enable bright color if the terminal is in light mode @@ -74,6 +75,9 @@ static void resetConsole(void) #if defined(_WIN32) fflush(stdout); #endif + + if(instance.state.dynamicInterval > 0) + fputs("\033[?1049l", stdout); // Disable alternate buffer } #ifdef _WIN32 @@ -130,14 +134,17 @@ void ffStart(void) if(ffDisableLinewrap) fputs("\033[?7l", stdout); + if(instance.state.dynamicInterval > 0) + { + fputs("\033[?1049h\033[H", stdout); // Enable alternate buffer + fflush(stdout); + } + ffLogoPrint(); } void ffFinish(void) { - if(instance.config.logo.printRemaining) - ffLogoPrintRemaining(); - resetConsole(); } diff --git a/src/common/io/io.h b/src/common/io/io.h index c783e5323c..fc4f4b48b8 100644 --- a/src/common/io/io.h +++ b/src/common/io/io.h @@ -241,6 +241,7 @@ static inline bool ffSearchUserConfigFile(const FFlist* configDirs, const char* } FFNativeFD ffGetNullFD(void); +bool ffRemoveFile(const char* fileName); #ifdef _WIN32 // Only O_RDONLY is supported diff --git a/src/common/io/io_unix.c b/src/common/io/io_unix.c index cd4f3fae52..a24c1aa654 100644 --- a/src/common/io/io_unix.c +++ b/src/common/io/io_unix.c @@ -16,7 +16,6 @@ #if FF_HAVE_WORDEXP #include #else - #warning " is not available, use glob(3) instead" #include #endif @@ -372,3 +371,8 @@ FFNativeFD ffGetNullFD(void) hNullFile = open("/dev/null", O_WRONLY | O_CLOEXEC); return hNullFile; } + +bool ffRemoveFile(const char* fileName) +{ + return unlink(fileName) == 0; +} diff --git a/src/common/io/io_windows.c b/src/common/io/io_windows.c index 419a299299..ae1d584e9a 100644 --- a/src/common/io/io_windows.c +++ b/src/common/io/io_windows.c @@ -371,3 +371,8 @@ FFNativeFD ffGetNullFD(void) }); return hNullFile; } + +bool ffRemoveFile(const char* fileName) +{ + return DeleteFileA(fileName) != FALSE; +} diff --git a/src/common/jsonconfig.c b/src/common/jsonconfig.c index 7cd73d9a4a..208710628f 100644 --- a/src/common/jsonconfig.c +++ b/src/common/jsonconfig.c @@ -158,6 +158,16 @@ static void prepareModuleJsonObject(const char* type, yyjson_val* module) ffPrepareCPUUsage(); break; } + case 'c': case 'C': { + if (ffStrEqualsIgnCase(type, FF_COMMAND_MODULE_NAME)) + { + __attribute__((__cleanup__(ffDestroyCommandOptions))) FFCommandOptions options; + ffInitCommandOptions(&options); + if (module) ffCommandModuleInfo.parseJsonObject(&options, module); + ffPrepareCommand(&options); + } + break; + } case 'd': case 'D': { if (ffStrEqualsIgnCase(type, FF_DISKIO_MODULE_NAME)) { diff --git a/src/common/netif/netif_linux.c b/src/common/netif/netif_linux.c index c54cc32420..49976b6957 100644 --- a/src/common/netif/netif_linux.c +++ b/src/common/netif/netif_linux.c @@ -89,7 +89,7 @@ bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) uint8_t buffer[1024 * 16]; // 16 KB buffer should be sufficient uint32_t minMetric = UINT32_MAX; - int routeCount = 0; + FF_MAYBE_UNUSED int routeCount = 0; while (true) { @@ -299,7 +299,7 @@ bool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result) uint8_t buffer[1024 * 16]; // 16 KB buffer should be sufficient uint32_t minMetric = UINT32_MAX; - int routeCount = 0; + FF_MAYBE_UNUSED int routeCount = 0; while (true) { diff --git a/src/common/processing.h b/src/common/processing.h index 7208214dbb..7b9f373fdd 100644 --- a/src/common/processing.h +++ b/src/common/processing.h @@ -2,13 +2,30 @@ #include "util/FFstrbuf.h" -#include +#ifndef _WIN32 +#include // pid_t +#endif + +typedef struct FFProcessHandle { + #if _WIN32 + void* pid; // HANDLE + void* pipeRead; // HANDLE + #else + pid_t pid; + int pipeRead; + #endif +} FFProcessHandle; -const char* ffProcessAppendOutput(FFstrbuf* buffer, char* const argv[], bool useStdErr); +const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFProcessHandle* outHandle); +const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer); // Destroys handle internally static inline const char* ffProcessAppendStdOut(FFstrbuf* buffer, char* const argv[]) { - const char* error = ffProcessAppendOutput(buffer, argv, false); + FFProcessHandle handle; + const char* error = ffProcessSpawn(argv, false, &handle); + if (error) return error; + + error = ffProcessReadOutput(&handle, buffer); if (!error) ffStrbufTrimRightSpace(buffer); return error; @@ -16,7 +33,11 @@ static inline const char* ffProcessAppendStdOut(FFstrbuf* buffer, char* const ar static inline const char* ffProcessAppendStdErr(FFstrbuf* buffer, char* const argv[]) { - const char* error = ffProcessAppendOutput(buffer, argv, true); + FFProcessHandle handle; + const char* error = ffProcessSpawn(argv, true, &handle); + if (error) return error; + + error = ffProcessReadOutput(&handle, buffer); if (!error) ffStrbufTrimRightSpace(buffer); return error; diff --git a/src/common/processing_linux.c b/src/common/processing_linux.c index fc9def0dd0..c4139ff4b9 100644 --- a/src/common/processing_linux.c +++ b/src/common/processing_linux.c @@ -58,11 +58,9 @@ static inline int ffPipe2(int* fds, int flags) // Not thread-safe -const char* ffProcessAppendOutput(FFstrbuf* buffer, char* const argv[], bool useStdErr) +const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFProcessHandle* outHandle) { int pipes[2]; - const int timeout = instance.config.general.processingTimeout; - if(ffPipe2(pipes, O_CLOEXEC) == -1) return "pipe() failed"; @@ -146,7 +144,7 @@ const char* ffProcessAppendOutput(FFstrbuf* buffer, char* const argv[], bool use if(childPid == 0) { - //Child + // Child process dup2(pipes[1], useStdErr ? STDERR_FILENO : STDOUT_FILENO); dup2(nullFile, useStdErr ? STDOUT_FILENO : STDERR_FILENO); putenv("LANG=C.UTF-8"); @@ -157,11 +155,24 @@ const char* ffProcessAppendOutput(FFstrbuf* buffer, char* const argv[], bool use #endif close(pipes[1]); + outHandle->pid = childPid; + outHandle->pipeRead = pipes[0]; + return NULL; +} - int FF_AUTO_CLOSE_FD childPipeFd = pipes[0]; +const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) +{ + assert(handle->pipeRead != -1); + assert(handle->pid != -1); + + const int32_t timeout = instance.config.general.processingTimeout; + FF_AUTO_CLOSE_FD int childPipeFd = handle->pipeRead; + pid_t childPid = handle->pid; + handle->pipeRead = -1; + handle->pid = -1; char str[FF_PIPE_BUFSIZ]; - while(1) + for (;;) { if (timeout >= 0) { @@ -189,14 +200,14 @@ const char* ffProcessAppendOutput(FFstrbuf* buffer, char* const argv[], bool use { kill(childPid, SIGTERM); waitpid(childPid, NULL, 0); - return "poll(&pollfd, 1, timeout) error"; + return "poll(&pollfd, 1, timeout) error: not EINTR"; } } else if (pollfd.revents & POLLERR) { kill(childPid, SIGTERM); waitpid(childPid, NULL, 0); - return "poll(&pollfd, 1, timeout) error"; + return "poll(&pollfd, 1, timeout) error: POLLERR"; } } @@ -211,7 +222,7 @@ const char* ffProcessAppendOutput(FFstrbuf* buffer, char* const argv[], bool use if (!WIFEXITED(stat_loc)) return "child process exited abnormally"; if (WEXITSTATUS(stat_loc) == 127) - return "command was not found"; + return "command not found"; // We only handle 127 as an error. See `getTerminalVersionUrxvt` in `terminalshell.c` return NULL; } @@ -224,7 +235,7 @@ const char* ffProcessAppendOutput(FFstrbuf* buffer, char* const argv[], bool use else break; } - }; + } return "read(childPipeFd, str, FF_PIPE_BUFSIZ) failed"; } diff --git a/src/common/processing_windows.c b/src/common/processing_windows.c index f414ac7a19..51042a625a 100644 --- a/src/common/processing_windows.c +++ b/src/common/processing_windows.c @@ -45,12 +45,13 @@ static void argvToCmdline(char* const argv[], FFstrbuf* result) } } -const char* ffProcessAppendOutput(FFstrbuf* buffer, char* const argv[], bool useStdErr) +const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFProcessHandle* outHandle) { - int timeout = instance.config.general.processingTimeout; + const int32_t timeout = instance.config.general.processingTimeout; wchar_t pipeName[32]; - swprintf(pipeName, ARRAY_SIZE(pipeName), L"\\\\.\\pipe\\FASTFETCH-%u", GetCurrentProcessId()); + static unsigned pidCounter = 0; + swprintf(pipeName, ARRAY_SIZE(pipeName), L"\\\\.\\pipe\\FASTFETCH-%u-%u", GetCurrentProcessId(), ++pidCounter); FF_AUTO_CLOSE_FD HANDLE hChildPipeRead = CreateNamedPipeW( pipeName, @@ -82,52 +83,67 @@ const char* ffProcessAppendOutput(FFstrbuf* buffer, char* const argv[], bool use return "CreateFileW(L\"\\\\.\\pipe\\FASTFETCH-$(PID)\") failed"; PROCESS_INFORMATION piProcInfo = {}; - - BOOL success; - + STARTUPINFOA siStartInfo = { + .cb = sizeof(siStartInfo), + .dwFlags = STARTF_USESTDHANDLES, + }; + if (useStdErr) { - STARTUPINFOA siStartInfo = { - .cb = sizeof(siStartInfo), - .dwFlags = STARTF_USESTDHANDLES, - }; - if (useStdErr) - { - siStartInfo.hStdOutput = ffGetNullFD(); - siStartInfo.hStdError = hChildPipeWrite; - } - else - { - siStartInfo.hStdOutput = hChildPipeWrite; - siStartInfo.hStdError = ffGetNullFD(); - } - - FF_STRBUF_AUTO_DESTROY cmdline = ffStrbufCreate(); - argvToCmdline(argv, &cmdline); - - success = CreateProcessA( - NULL, // application name - cmdline.chars, // command line - NULL, // process security attributes - NULL, // primary thread security attributes - TRUE, // handles are inherited - 0, // creation flags - NULL, // use parent's environment - NULL, // use parent's current directory - &siStartInfo, // STARTUPINFO pointer - &piProcInfo // receives PROCESS_INFORMATION - ); + siStartInfo.hStdOutput = ffGetNullFD(); + siStartInfo.hStdError = hChildPipeWrite; + } + else + { + siStartInfo.hStdOutput = hChildPipeWrite; + siStartInfo.hStdError = ffGetNullFD(); } + FF_STRBUF_AUTO_DESTROY cmdline = ffStrbufCreate(); + argvToCmdline(argv, &cmdline); + + BOOL success = CreateProcessA( + NULL, // application name + cmdline.chars, // command line + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // handles are inherited + 0, // creation flags + NULL, // use parent's environment + NULL, // use parent's current directory + &siStartInfo, // STARTUPINFO pointer + &piProcInfo // receives PROCESS_INFORMATION + ); + CloseHandle(hChildPipeWrite); if(!success) + { + if (GetLastError() == ERROR_FILE_NOT_FOUND) + return "command not found"; return "CreateProcessA() failed"; + } + + CloseHandle(piProcInfo.hThread); // we don't need the thread handle + outHandle->pid = piProcInfo.hProcess; + outHandle->pipeRead = hChildPipeRead; + hChildPipeRead = INVALID_HANDLE_VALUE; // ownership transferred, don't close it - FF_AUTO_CLOSE_FD HANDLE hProcess = piProcInfo.hProcess; - FF_MAYBE_UNUSED FF_AUTO_CLOSE_FD HANDLE hThread = piProcInfo.hThread; + return NULL; +} + +const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) +{ + assert(handle->pipeRead != INVALID_HANDLE_VALUE); + assert(handle->pid != INVALID_HANDLE_VALUE); + + int32_t timeout = instance.config.general.processingTimeout; + FF_AUTO_CLOSE_FD HANDLE hProcess = handle->pid; + FF_AUTO_CLOSE_FD HANDLE hChildPipeRead = handle->pipeRead; + handle->pid = INVALID_HANDLE_VALUE; + handle->pipeRead = INVALID_HANDLE_VALUE; char str[FF_PIPE_BUFSIZ]; DWORD nRead = 0; - OVERLAPPED overlapped = { }; + OVERLAPPED overlapped = {}; // ReadFile always completes synchronously if the pipe is not created with FILE_FLAG_OVERLAPPED do { @@ -164,7 +180,7 @@ const char* ffProcessAppendOutput(FFstrbuf* buffer, char* const argv[], bool use break; case ERROR_BROKEN_PIPE: - return NULL; + goto exit; default: CancelIo(hChildPipeRead); @@ -175,6 +191,13 @@ const char* ffProcessAppendOutput(FFstrbuf* buffer, char* const argv[], bool use ffStrbufAppendNS(buffer, nRead, str); } while (nRead > 0); +exit: + { + DWORD exitCode = 0; + if (GetExitCodeProcess(hProcess, &exitCode) && exitCode != STILL_ACTIVE && exitCode != 0) + return "Child process exited with an error"; + } + return NULL; } diff --git a/src/data/help.json b/src/data/help.json index 745570ff20..df10972ccf 100644 --- a/src/data/help.json +++ b/src/data/help.json @@ -74,6 +74,15 @@ }, "desc": "Enable or disable JSON output", "remark": "Shortcut for `--format json`" + }, + { + "long": "dynamic-interval", + "desc": "Keep fastfetch open and update the output every milliseconds", + "remark": "0 (default) to disable the behavior; don't work with --json", + "arg": { + "type": "num", + "default": 0 + } } ], "Config": [ diff --git a/src/detection/command/command.c b/src/detection/command/command.c new file mode 100644 index 0000000000..f99c4fda5f --- /dev/null +++ b/src/detection/command/command.c @@ -0,0 +1,60 @@ +#include "common/processing.h" +#include "util/FFstrbuf.h" +#include "detection/command/command.h" + +typedef struct FFCommandResultBundle +{ + FFProcessHandle handle; + const char* error; +} FFCommandResultBundle; + +// FIFO, non-thread-safe list of running commands +static FFlist commandQueue = { + .elementSize = sizeof(FFCommandResultBundle), +}; + +static const char* spawnProcess(FFCommandOptions* options, FFProcessHandle* handle) +{ + if (options->text.length == 0) + return "No command text specified"; + + return ffProcessSpawn(options->param.length ? (char* const[]){ + options->shell.chars, + options->param.chars, + options->text.chars, + NULL + } : (char* const[]){ + options->shell.chars, + options->text.chars, + NULL + }, options->useStdErr, handle); +} + +bool ffPrepareCommand(FFCommandOptions* options) +{ + if (!options->parallel) return false; + + FFCommandResultBundle* bundle = ffListAdd(&commandQueue); + bundle->error = spawnProcess(options, &bundle->handle); + + return true; +} + +const char* ffDetectCommand(FFCommandOptions* options, FFstrbuf* result) +{ + FFCommandResultBundle bundle = {}; + if (!options->parallel) + bundle.error = spawnProcess(options, &bundle.handle); + else if (!ffListShift(&commandQueue, &bundle)) + return "[BUG] command queue is empty"; + + if (bundle.error) + return bundle.error; + + bundle.error = ffProcessReadOutput(&bundle.handle, result); + if (bundle.error) + return bundle.error; + + ffStrbufTrimRightSpace(result); + return NULL; +} diff --git a/src/detection/command/command.h b/src/detection/command/command.h new file mode 100644 index 0000000000..73acc0abcb --- /dev/null +++ b/src/detection/command/command.h @@ -0,0 +1,6 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/command/option.h" + +const char* ffDetectCommand(FFCommandOptions* options, FFstrbuf* result); diff --git a/src/detection/disk/disk.c b/src/detection/disk/disk.c index 5d52b86e7b..d63a105fec 100644 --- a/src/detection/disk/disk.c +++ b/src/detection/disk/disk.c @@ -10,7 +10,7 @@ const char* ffDetectDisks(FFDiskOptions* options, FFlist* disks) const char* error = ffDetectDisksImpl(options, disks); if (error) return error; - if (disks->length == 0) return "No disks found"; + if (disks->length == 0) return NULL; //We need to sort the disks, so that we can detect, which disk a path resides on // For example for /boot/efi/bootmgr we need to check /boot/efi before /boot @@ -31,3 +31,27 @@ const char* ffDetectDisks(FFDiskOptions* options, FFlist* disks) return NULL; } + +#ifndef _WIN32 +#include + +bool ffDiskMatchesFolderPatterns(FFstrbuf* folders, const char* path, char separator) +{ + uint32_t startIndex = 0; + while(startIndex < folders->length) + { + uint32_t sepIndex = ffStrbufNextIndexC(folders, startIndex, separator); + + char savedSep = folders->chars[sepIndex]; // Can be '\0' if at end + folders->chars[sepIndex] = '\0'; + + bool matched = fnmatch(&folders->chars[startIndex], path, 0) == 0; + folders->chars[sepIndex] = savedSep; + + if (matched) return true; + + startIndex = sepIndex + 1; + } + return false; +} +#endif diff --git a/src/detection/disk/disk.h b/src/detection/disk/disk.h index f2fab87826..abf6884483 100644 --- a/src/detection/disk/disk.h +++ b/src/detection/disk/disk.h @@ -35,3 +35,7 @@ typedef struct FFDisk const char* ffDetectDisks(FFDiskOptions* options, FFlist* disks /* list of FFDisk */); const char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks); + +#ifndef _WIN32 +bool ffDiskMatchesFolderPatterns(FFstrbuf* folders, const char* path, char separator); +#endif diff --git a/src/detection/disk/disk_bsd.c b/src/detection/disk/disk_bsd.c index 13597fb967..35386b8bbe 100644 --- a/src/detection/disk/disk_bsd.c +++ b/src/detection/disk/disk_bsd.c @@ -151,6 +151,12 @@ const char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks) else if(!ffStrEquals(fs->f_mntonname, "/") && !ffStrStartsWith(fs->f_mntfromname, "/dev/") && !ffStrEquals(fs->f_fstypename, "zfs") && !ffStrEquals(fs->f_fstypename, "fusefs.sshfs")) continue; + if (options->hideFolders.length && ffDiskMatchesFolderPatterns(&options->hideFolders, fs->f_mntonname, FF_DISK_FOLDER_SEPARATOR)) + continue; + + if (options->hideFS.length && ffStrbufSeparatedContainS(&options->hideFS, fs->f_fstypename, ':')) + continue; + #ifdef __FreeBSD__ // f_bavail and f_ffree are signed on FreeBSD... if(fs->f_bavail < 0) fs->f_bavail = 0; diff --git a/src/detection/disk/disk_haiku.cpp b/src/detection/disk/disk_haiku.cpp index 25fe018b96..70479667ea 100644 --- a/src/detection/disk/disk_haiku.cpp +++ b/src/detection/disk/disk_haiku.cpp @@ -27,6 +27,12 @@ const char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks) continue; } + if (options->hideFolders.length && ffDiskMatchesFolderPatterns(&options->hideFolders, path.Path(), FF_DISK_FOLDER_SEPARATOR)) + continue; + + if (options->hideFS.length && ffStrbufSeparatedContainS(&options->hideFS, fs.fsh_name, ':')) + continue; + FFDisk* disk = (FFDisk*) ffListAdd(disks); disk->bytesTotal = (uint64_t)fs.total_blocks * (uint64_t) fs.block_size; diff --git a/src/detection/disk/disk_linux.c b/src/detection/disk/disk_linux.c index 68f783e4cd..518d356ac8 100644 --- a/src/detection/disk/disk_linux.c +++ b/src/detection/disk/disk_linux.c @@ -297,6 +297,12 @@ const char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks) else if(!isPhysicalDevice(device)) continue; + if (options->hideFolders.length && ffDiskMatchesFolderPatterns(&options->hideFolders, device->mnt_dir, FF_DISK_FOLDER_SEPARATOR)) + continue; + + if (options->hideFS.length && ffStrbufSeparatedContainS(&options->hideFS, device->mnt_type, ':')) + continue; + //We have a valid device, add it to the list FFDisk* disk = ffListAdd(disks); disk->type = FF_DISK_VOLUME_TYPE_NONE; diff --git a/src/detection/disk/disk_sunos.c b/src/detection/disk/disk_sunos.c index 8272cc1ed7..e7b0c2f539 100644 --- a/src/detection/disk/disk_sunos.c +++ b/src/detection/disk/disk_sunos.c @@ -119,6 +119,12 @@ const char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks) else if(!isPhysicalDevice(&device)) continue; + if (options->hideFolders.length && ffDiskMatchesFolderPatterns(&options->hideFolders, device.mnt_mountp, FF_DISK_FOLDER_SEPARATOR)) + continue; + + if (options->hideFS.length && ffStrbufSeparatedContainS(&options->hideFS, device.mnt_fstype, ':')) + continue; + //We have a valid device, add it to the list FFDisk* disk = ffListAdd(disks); disk->type = FF_DISK_VOLUME_TYPE_NONE; diff --git a/src/detection/disk/disk_windows.c b/src/detection/disk/disk_windows.c index ea6766b459..524f0cd91c 100644 --- a/src/detection/disk/disk_windows.c +++ b/src/detection/disk/disk_windows.c @@ -53,6 +53,29 @@ const char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks) else if(driveType == DRIVE_NO_ROOT_DIR) continue; + if (options->hideFolders.length && ffStrbufSeparatedContain(&options->hideFolders, &buffer, FF_DISK_FOLDER_SEPARATOR)) + continue; + + wchar_t diskName[MAX_PATH + 1], diskFileSystem[MAX_PATH + 1]; + + DWORD diskFlags; + BOOL volumeInfoAvailable = GetVolumeInformationW(mountpoint, + diskName, ARRAY_SIZE(diskName), //Volume name + NULL, //Serial number + NULL, //Max component length + &diskFlags, //File system flags + diskFileSystem, ARRAY_SIZE(diskFileSystem) + ); + + FF_STRBUF_AUTO_DESTROY diskFileSystemBuf = ffStrbufCreate(); + + if (volumeInfoAvailable) + { + ffStrbufSetWS(&diskFileSystemBuf, diskFileSystem); + if (options->hideFS.length && ffStrbufSeparatedContain(&options->hideFS, &diskFileSystemBuf, ':')) + continue; + } + FFDisk* disk = ffListAdd(disks); disk->filesUsed = 0; @@ -97,20 +120,9 @@ const char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks) (PULARGE_INTEGER)&disk->bytesFree ); - wchar_t diskName[MAX_PATH + 1], diskFileSystem[MAX_PATH + 1]; - - DWORD diskFlags; - BOOL result = GetVolumeInformationW(mountpoint, - diskName, ARRAY_SIZE(diskName), //Volume name - NULL, //Serial number - NULL, //Max component length - &diskFlags, //File system flags - diskFileSystem, ARRAY_SIZE(diskFileSystem) - ); - - if(result) + if(volumeInfoAvailable) { - ffStrbufSetWS(&disk->filesystem, diskFileSystem); + ffStrbufInitMove(&disk->filesystem, &diskFileSystemBuf); ffStrbufSetWS(&disk->name, diskName); if(diskFlags & FILE_READ_ONLY_VOLUME) disk->type |= FF_DISK_VOLUME_TYPE_READONLY_BIT; diff --git a/src/detection/gpu/gpu.h b/src/detection/gpu/gpu.h index bad28d9280..d2ca916bde 100644 --- a/src/detection/gpu/gpu.h +++ b/src/detection/gpu/gpu.h @@ -58,7 +58,7 @@ typedef struct FFGpuDriverPciBusId uint32_t func; } FFGpuDriverPciBusId; -#if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__HAIKU__) +#if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__HAIKU__) || defined(__GNU__) void ffGPUFillVendorAndName(uint8_t subclass, uint16_t vendor, uint16_t device, FFGPUResult* gpu); void ffGPUQueryAmdGpuName(uint16_t deviceId, uint8_t revisionId, FFGPUResult* gpu); @@ -73,3 +73,14 @@ const char* ffDrmDetectNouveau(FFGPUResult* gpu, int fd); const char* ffGPUDetectDriverSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFGpuDriverPciBusId pciBusId); #endif // defined(XXX) + +static inline uint64_t ffGPUPciAddr2Id(uint64_t domain, uint64_t bus, uint64_t device, uint64_t function) +{ + return (domain << 16) | (bus << 8) | (device << 3) | function; +} + +static inline uint64_t ffGPUGeneral2Id(uint64_t originalId) +{ + // Note: originalId may already have the MSB set + return (1ULL << 63) | originalId; +} diff --git a/src/detection/gpu/gpu_bsd.c b/src/detection/gpu/gpu_bsd.c index d7d9606be1..6684a2ae05 100644 --- a/src/detection/gpu/gpu_bsd.c +++ b/src/detection/gpu/gpu_bsd.c @@ -30,7 +30,7 @@ static void fillGPUTypeGeneric(FFGPUResult* gpu) else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_INTEL) { // 0000:00:02.0 is reserved for Intel integrated graphics - gpu->type = gpu->deviceId == 20 ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE; + gpu->type = gpu->deviceId == ffGPUPciAddr2Id(0, 0, 2, 0) ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE; } } } @@ -80,7 +80,7 @@ static const char* detectByDrm(const FFGPUOptions* options, FFlist* gpus) { case DRM_BUS_PCI: ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(dev->deviceinfo.pci->vendor_id)); - gpu->deviceId = (dev->businfo.pci->domain * 100000ull) + (dev->businfo.pci->bus * 1000ull) + (dev->businfo.pci->dev * 10ull) + dev->businfo.pci->func; + gpu->deviceId = ffGPUPciAddr2Id(dev->businfo.pci->domain, dev->businfo.pci->bus, dev->businfo.pci->dev, dev->businfo.pci->func); break; case DRM_BUS_HOST1X: ffStrbufSetS(&gpu->name, dev->deviceinfo.host1x->compatible[0]); @@ -179,6 +179,9 @@ static const char* detectByPci(const FFGPUOptions* options, FFlist* gpus) { struct pci_conf* pc = &confs[i]; + if (pc->pc_sel.pc_func > 0 && pc->pc_subclass == 0x80 /*PCI_CLASS_DISPLAY_OTHER*/) + continue; // Likely an auxiliary display controller (#2034) + FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus); ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(pc->pc_vendor)); ffStrbufInit(&gpu->name); @@ -191,7 +194,7 @@ static const char* detectByPci(const FFGPUOptions* options, FFlist* gpus) gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; gpu->type = FF_GPU_TYPE_UNKNOWN; gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET; - gpu->deviceId = (pc->pc_sel.pc_domain * 100000ull) + (pc->pc_sel.pc_bus * 1000ull) + (pc->pc_sel.pc_dev * 10ull) + pc->pc_sel.pc_func; + gpu->deviceId = ffGPUPciAddr2Id(pc->pc_sel.pc_domain, pc->pc_sel.pc_bus, pc->pc_sel.pc_dev, pc->pc_sel.pc_func); gpu->frequency = FF_GPU_FREQUENCY_UNSET; ffGPUDetectDriverSpecific(options, gpu, (FFGpuDriverPciBusId) { diff --git a/src/detection/gpu/gpu_drm.c b/src/detection/gpu/gpu_drm.c index a1c9cd0c30..aee70ca4ab 100644 --- a/src/detection/gpu/gpu_drm.c +++ b/src/detection/gpu/gpu_drm.c @@ -313,7 +313,7 @@ const char* ffDrmDetectAsahi(FFGPUResult* gpu, int fd) // They removed `unstable_uabi_version` from the struct. Hopefully they won't introduce new ABI changes. gpu->coreCount = (int32_t) (paramsGlobal.num_clusters_total * paramsGlobal.num_cores_per_cluster); gpu->frequency = paramsGlobal.max_frequency_khz / 1000; - gpu->deviceId = paramsGlobal.chip_id; + gpu->deviceId = ffGPUGeneral2Id(paramsGlobal.chip_id); if (!gpu->name.length) { diff --git a/src/detection/gpu/gpu_general.c b/src/detection/gpu/gpu_general.c index 7fab697ad4..504509fa2e 100644 --- a/src/detection/gpu/gpu_general.c +++ b/src/detection/gpu/gpu_general.c @@ -14,6 +14,7 @@ const char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pciaccess, pci_slot_match_iterator_create) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pciaccess, pci_device_next) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pciaccess, pci_system_cleanup) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pciaccess, pci_iterator_destroy) { // Requires root access @@ -29,6 +30,11 @@ const char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* if (dev->device_class >> 16 != 0x03 /*PCI_BASE_CLASS_DISPLAY*/) continue; + uint8_t subclass = (dev->device_class >> 8) & 0xFF; + + if (dev->func > 0 && subclass == 0x80 /*PCI_CLASS_DISPLAY_OTHER*/) + continue; // Likely an auxiliary display controller (#2034) + FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus); ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(dev->vendor_id)); ffStrbufInit(&gpu->name); @@ -40,15 +46,16 @@ const char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; gpu->type = FF_GPU_TYPE_UNKNOWN; gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET; - gpu->deviceId = ((uint64_t) dev->domain << 6) | ((uint64_t) dev->bus << 4) | ((uint64_t) dev->dev << 2) | (uint64_t) dev->func; + gpu->deviceId = ffGPUPciAddr2Id(dev->domain, dev->bus, dev->dev, dev->func); gpu->frequency = FF_GPU_FREQUENCY_UNSET; if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) ffGPUQueryAmdGpuName(dev->device_id, dev->revision, gpu); if (gpu->name.length == 0) - ffGPUFillVendorAndName((dev->device_class >> 8) & 0xFF, dev->vendor_id, dev->device_id, gpu); + ffGPUFillVendorAndName(subclass, dev->vendor_id, dev->device_id, gpu); } + ffpci_iterator_destroy(iter); ffpci_system_cleanup(); return NULL; diff --git a/src/detection/gpu/gpu_haiku.c b/src/detection/gpu/gpu_haiku.c index 09c34e70f9..3368f70643 100644 --- a/src/detection/gpu/gpu_haiku.c +++ b/src/detection/gpu/gpu_haiku.c @@ -19,6 +19,9 @@ const char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* if (dev.class_base != 0x03 /*PCI_BASE_CLASS_DISPLAY*/) continue; + if (dev.function > 0 && dev.class_sub == 0x80 /*PCI_CLASS_DISPLAY_OTHER*/) + continue; // Likely an auxiliary display controller (#2034) + FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus); ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(dev.vendor_id)); ffStrbufInit(&gpu->name); @@ -30,7 +33,7 @@ const char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; gpu->type = FF_GPU_TYPE_UNKNOWN; gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET; - gpu->deviceId = ((uint64_t) dev.bus << 4) | ((uint64_t) dev.device << 2) | (uint64_t) dev.function; + gpu->deviceId = ffGPUPciAddr2Id(0, dev.bus, dev.device, dev.function); gpu->frequency = FF_GPU_FREQUENCY_UNSET; if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) diff --git a/src/detection/gpu/gpu_linux.c b/src/detection/gpu/gpu_linux.c index a43018e35f..6fad45f4cd 100644 --- a/src/detection/gpu/gpu_linux.c +++ b/src/detection/gpu/gpu_linux.c @@ -198,7 +198,7 @@ static void pciDetectIntelSpecific(const FFGPUOptions* options, FFGPUResult* gpu // https://patchwork.kernel.org/project/intel-gfx/patch/1422039866-11572-3-git-send-email-ville.syrjala@linux.intel.com/ // 0000:00:02.0 is reserved for Intel integrated graphics - gpu->type = gpu->deviceId == 20 ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE; + gpu->type = gpu->deviceId == ffGPUPciAddr2Id(0, 0, 2, 0) ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE; if (!drmKey) return; @@ -342,6 +342,9 @@ static const char* detectPci(const FFGPUOptions* options, FFlist* gpus, FFstrbuf if (sscanf(pPciPath, "%" SCNx32 ":%" SCNx32 ":%" SCNx32 ".%" SCNx32, &pciDomain, &pciBus, &pciDevice, &pciFunc) != 4) return "Invalid PCI device path"; + if (pciFunc > 0 && subclassId == 0x80 /*PCI_CLASS_DISPLAY_OTHER*/) + return "Likely an auxiliary display controller"; // #2034 + FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus); ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString((uint16_t) vendorId)); ffStrbufInit(&gpu->name); @@ -354,7 +357,7 @@ static const char* detectPci(const FFGPUOptions* options, FFlist* gpus, FFstrbuf gpu->coreCount = FF_GPU_CORE_COUNT_UNSET; gpu->type = FF_GPU_TYPE_UNKNOWN; gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET; - gpu->deviceId = (pciDomain * 100000ull) + (pciBus * 1000ull) + (pciDevice * 10ull) + pciFunc; + gpu->deviceId = ffGPUPciAddr2Id(pciDomain, pciBus, pciDevice, pciFunc); gpu->frequency = FF_GPU_FREQUENCY_UNSET; char drmKeyBuffer[8]; diff --git a/src/detection/gpu/gpu_nbsd.c b/src/detection/gpu/gpu_nbsd.c new file mode 100644 index 0000000000..888c86af63 --- /dev/null +++ b/src/detection/gpu/gpu_nbsd.c @@ -0,0 +1,113 @@ +#include "gpu.h" +#include "common/io/io.h" + +#include +#include +#include +#include +#include +#include + +static inline int pciReadConf(int fd, uint32_t bus, uint32_t device, uint32_t func, uint32_t reg, uint32_t* result) +{ + struct pciio_bdf_cfgreg bdfr = { + .bus = bus, + .device = device, + .function = func, + .cfgreg = { + .reg = reg, + }, + }; + + if (ioctl(fd, PCI_IOC_BDF_CFGREAD, &bdfr) == -1) + return -1; + + *result = bdfr.cfgreg.val; + return 0; +} + +const char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* gpus) +{ + char pciDevPath[] = "/dev/pciXXX"; + + for (uint32_t idev = 0; idev <= 255; idev++) + { + snprintf(pciDevPath + strlen("/dev/pci"), 4, "%u", idev); + + FF_AUTO_CLOSE_FD int pcifd = open(pciDevPath, O_RDONLY | O_CLOEXEC); + if (pcifd < 0) + { + if (errno == ENOENT) + break; // No more /dev/pciN devices + return "open(\"/dev/pciN\", O_RDONLY | O_CLOEXEC) failed"; + } + + struct pciio_businfo businfo; + if (ioctl(pcifd, PCI_IOC_BUSINFO, &businfo) != 0) + continue; + + uint32_t bus = businfo.busno; + for (uint32_t dev = 0; dev < businfo.maxdevs; dev++) + { + uint32_t maxfuncs = 0; + for (uint32_t func = 0; func <= maxfuncs; func++) + { + uint32_t pciid, pciclass; + if (pciReadConf(pcifd, bus, dev, func, PCI_ID_REG, &pciid) != 0) + continue; + + if (PCI_VENDOR(pciid) == PCI_VENDOR_INVALID || PCI_VENDOR(pciid) == 0) + continue; + + if (pciReadConf(pcifd, bus, dev, func, PCI_CLASS_REG, &pciclass) != 0) + continue; + + if (func == 0) + { + // For some reason, pciReadConf returns success even for non-existing devices. + // So we need to check for `PCI_VENDOR(pciid) == PCI_VENDOR_INVALID` above to filter them out. + uint32_t bhlcr; + if (pciReadConf(pcifd, bus, dev, 0, PCI_BHLC_REG, &bhlcr) != 0) + continue; + + if (PCI_HDRTYPE_MULTIFN(bhlcr)) maxfuncs = 7; + } + + if (PCI_CLASS(pciclass) != PCI_CLASS_DISPLAY) + continue; + + if (func > 0 && PCI_SUBCLASS(pciclass) == PCI_SUBCLASS_DISPLAY_MISC) + continue; // Likely an auxiliary display controller (#2034) + + FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus); + ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(PCI_VENDOR(pciid))); + ffStrbufInit(&gpu->name); + ffStrbufInit(&gpu->driver); + ffStrbufInitS(&gpu->platformApi, pciDevPath); + ffStrbufInit(&gpu->memoryType); + gpu->index = FF_GPU_INDEX_UNSET; + gpu->temperature = FF_GPU_TEMP_UNSET; + gpu->coreCount = FF_GPU_CORE_COUNT_UNSET; + gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; + gpu->type = FF_GPU_TYPE_UNKNOWN; + gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET; + gpu->deviceId = ffGPUPciAddr2Id(0, bus, dev, func); + gpu->frequency = FF_GPU_FREQUENCY_UNSET; + + if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) + ffGPUQueryAmdGpuName(PCI_PRODUCT(pciid), PCI_REVISION(pciid), gpu); + if (gpu->name.length == 0) + ffGPUFillVendorAndName(PCI_SUBCLASS(pciclass), PCI_VENDOR(pciid), PCI_PRODUCT(pciid), gpu); + + struct pciio_drvname drvname = { + .device = dev, + .function = func, + }; + if (ioctl(pcifd, PCI_IOC_DRVNAME, &drvname) == 0) + ffStrbufInitS(&gpu->driver, drvname.name); + } + } + } + + return NULL; +} diff --git a/src/detection/gpu/gpu_obsd.c b/src/detection/gpu/gpu_obsd.c new file mode 100644 index 0000000000..2055c47bd6 --- /dev/null +++ b/src/detection/gpu/gpu_obsd.c @@ -0,0 +1,95 @@ +#include "gpu.h" +#include "common/io/io.h" + +#include +#include +#include +#include +#include +#include + +static inline int pciReadConf(int fd, uint32_t bus, uint32_t device, uint32_t func, uint32_t reg, uint32_t* result) +{ + struct pci_io bdfr = { + .pi_sel = { + .pc_bus = bus, + .pc_dev = device, + .pc_func = func, + }, + .pi_reg = reg, + .pi_width = 4, + }; + + if (ioctl(fd, PCIOCREAD, &bdfr) == -1) + return -1; + + *result = bdfr.pi_data; + return 0; +} + +const char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* gpus) +{ + char pciDevPath[] = "/dev/pci0"; + FF_AUTO_CLOSE_FD int pcifd = open(pciDevPath, O_RDONLY | O_CLOEXEC); + if (pcifd < 0) + return "open(\"/dev/pci0\", O_RDONLY | O_CLOEXEC) failed"; + + for (uint32_t bus = 0; bus <= 255; bus++) + { + for (uint32_t dev = 0; dev < 32; dev++) + { + uint32_t maxfuncs = 0; + for (uint32_t func = 0; func <= maxfuncs; func++) + { + uint32_t pciid, pciclass; + if (pciReadConf(pcifd, bus, dev, func, PCI_ID_REG, &pciid) != 0) + continue; + + if (PCI_VENDOR(pciid) == PCI_VENDOR_INVALID || PCI_VENDOR(pciid) == 0) + continue; + + if (pciReadConf(pcifd, bus, dev, func, PCI_CLASS_REG, &pciclass) != 0) + continue; + + if (func == 0) + { + // For some reason, pciReadConf returns success even for non-existing devices. + // So we need to check for `PCI_VENDOR(pciid) == PCI_VENDOR_INVALID` above to filter them out. + uint32_t bhlcr; + if (pciReadConf(pcifd, bus, dev, 0, PCI_BHLC_REG, &bhlcr) != 0) + continue; + + if (PCI_HDRTYPE_MULTIFN(bhlcr)) maxfuncs = 7; + } + + if (PCI_CLASS(pciclass) != PCI_CLASS_DISPLAY) + continue; + + if (func > 0 && PCI_SUBCLASS(pciclass) == PCI_SUBCLASS_DISPLAY_MISC) + continue; // Likely an auxiliary display controller (#2034) + + FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus); + ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(PCI_VENDOR(pciid))); + ffStrbufInit(&gpu->name); + ffStrbufInit(&gpu->driver); + ffStrbufInitS(&gpu->platformApi, "/dev/pci0"); + ffStrbufInit(&gpu->memoryType); + gpu->index = FF_GPU_INDEX_UNSET; + gpu->temperature = FF_GPU_TEMP_UNSET; + gpu->coreCount = FF_GPU_CORE_COUNT_UNSET; + gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; + gpu->type = FF_GPU_TYPE_UNKNOWN; + gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET; + gpu->deviceId = ffGPUPciAddr2Id(0, bus, dev, func); + gpu->frequency = FF_GPU_FREQUENCY_UNSET; + + if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) + ffGPUQueryAmdGpuName(PCI_PRODUCT(pciid), PCI_REVISION(pciid), gpu); + if (gpu->name.length == 0) + ffGPUFillVendorAndName(PCI_SUBCLASS(pciclass), PCI_VENDOR(pciid), PCI_PRODUCT(pciid), gpu); + } + } + } + + return NULL; +} diff --git a/src/detection/gpu/gpu_windows.c b/src/detection/gpu/gpu_windows.c index 944e1030b8..1cde2376d5 100644 --- a/src/detection/gpu/gpu_windows.c +++ b/src/detection/gpu/gpu_windows.c @@ -95,7 +95,7 @@ const char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* { pciDev = (pciAddr >> 16) & 0xFFFF; pciFunc = pciAddr & 0xFFFF; - gpu->deviceId = (pciBus * 1000ull) + (pciDev * 10ull) + pciFunc; + gpu->deviceId = ffGPUPciAddr2Id(0, pciBus, pciDev, pciFunc); pciAddr = 1; // Set to 1 to indicate that the device is a PCI device FF_DEBUG("PCI location - Bus: %u, Device: %u, Function: %u, DeviceID: %llu", pciBus, pciDev, pciFunc, gpu->deviceId); } @@ -182,7 +182,7 @@ const char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* if (ffRegReadUint64(hDirectxKey, L"AdapterLuid", &adapterLuid, NULL)) { FF_DEBUG("Found adapter LUID: %llu", adapterLuid); - if (!gpu->deviceId) gpu->deviceId = adapterLuid; + if (!gpu->deviceId) gpu->deviceId = ffGPUGeneral2Id(adapterLuid); } uint32_t featureLevel = 0; @@ -449,7 +449,7 @@ const char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* FF_DEBUG("Using fallback GPU type detection"); if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_INTEL) { - gpu->type = gpu->deviceId == 20 ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE; + gpu->type = gpu->deviceId == ffGPUPciAddr2Id(0, 0, 2, 0) ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE; FF_DEBUG("Intel GPU type determined: %s", gpu->type == FF_GPU_TYPE_INTEGRATED ? "Integrated" : "Discrete"); } else if (gpu->dedicated.total != FF_GPU_VMEM_SIZE_UNSET) diff --git a/src/detection/gpu/gpu_wsl.cpp b/src/detection/gpu/gpu_wsl.cpp index 1ab5bb27d8..738a025466 100644 --- a/src/detection/gpu/gpu_wsl.cpp +++ b/src/detection/gpu/gpu_wsl.cpp @@ -79,6 +79,13 @@ const char* ffGPUDetectByDirectX(FF_MAYBE_UNUSED const FFGPUOptions* options, FF gpu->deviceId = 0; ffStrbufInitStatic(&gpu->platformApi, "DXCore"); + LUID luid; + if (SUCCEEDED(adapter->GetProperty(DXCoreAdapterProperty::InstanceLuid, sizeof(luid), &luid))) + { + static_assert(sizeof(luid) == sizeof(uint64_t), "LUID size mismatch"); + gpu->deviceId = ffGPUGeneral2Id(*(uint64_t*)&luid); + } + ffStrbufInit(&gpu->driver); uint64_t value = 0; if (SUCCEEDED(adapter->GetProperty(DXCoreAdapterProperty::DriverVersion, sizeof(value), &value))) diff --git a/src/detection/host/host_mac.c b/src/detection/host/host_mac.c index b84bec8023..a2c8d3e737 100644 --- a/src/detection/host/host_mac.c +++ b/src/detection/host/host_mac.c @@ -181,7 +181,7 @@ const char* ffHostGetMacProductNameWithHwModel(const FFstrbuf* hwModel) #ifdef __x86_64__ bool ffHostDetectMac(FFHostResult* host) { - if (ffStrbufEqualS(&host->family, "Mac") && ffStrbufEqualS(&host->vendor, "Apple Inc.")) + if (ffStrbufStartsWithS(&host->family, "Mac") && ffStrbufEqualS(&host->vendor, "Apple Inc.")) { const char* productName = ffHostGetMacProductNameWithHwModel(&host->name); if (productName) diff --git a/src/detection/icons/icons_windows.c b/src/detection/icons/icons_windows.c index dc5d36cf09..d7d9684e21 100644 --- a/src/detection/icons/icons_windows.c +++ b/src/detection/icons/icons_windows.c @@ -16,6 +16,9 @@ const char* ffDetectIcons(FFIconsResult* result) ffRegReadUint(hKey, L"{645FF040-5081-101B-9F08-00AA002F954E}", &RecycleBin, NULL); ffRegReadUint(hKey, L"{5399E694-6CE5-4D6C-8FCE-1D8870FDCBA0}", &ControlPanel, NULL); + if (ThisPC && UsersFiles && RemoteNetwork && RecycleBin && ControlPanel) + return "All icons are hidden"; + if (!ThisPC) ffStrbufAppendS(&result->icons1, "This PC, "); if (!UsersFiles) diff --git a/src/detection/media/media.c b/src/detection/media/media.c index 585ccc48e4..3c1288fe14 100644 --- a/src/detection/media/media.c +++ b/src/detection/media/media.c @@ -1,11 +1,21 @@ #include "media.h" +#include "common/io/io.h" -void ffDetectMediaImpl(FFMediaResult* media); +void ffDetectMediaImpl(FFMediaResult* media, bool saveCover); -const FFMediaResult* ffDetectMedia(void) +static FFMediaResult result; + +static void removeMediaCoverFile(void) { - static FFMediaResult result; + if (result.cover.length > 0) + { + ffRemoveFile(result.cover.chars); + ffStrbufDestroy(&result.cover); + } +} +const FFMediaResult* ffDetectMedia(bool saveCover) +{ if (result.error.chars == NULL) { ffStrbufInit(&result.error); @@ -16,7 +26,9 @@ const FFMediaResult* ffDetectMedia(void) ffStrbufInit(&result.album); ffStrbufInit(&result.url); ffStrbufInit(&result.status); - ffDetectMediaImpl(&result); + ffStrbufInit(&result.cover); + result.removeCoverAfterUse = false; + ffDetectMediaImpl(&result, saveCover); if(result.song.length == 0 && result.error.length == 0) ffStrbufAppendS(&result.error, "No media found"); @@ -24,6 +36,9 @@ const FFMediaResult* ffDetectMedia(void) ffStrbufTrimRightSpace(&result.artist); ffStrbufTrimRightSpace(&result.album); ffStrbufTrimRightSpace(&result.player); + + if (saveCover && result.removeCoverAfterUse) + atexit(removeMediaCoverFile); } return &result; diff --git a/src/detection/media/media.h b/src/detection/media/media.h index 598bbf55a5..41ca65f55c 100644 --- a/src/detection/media/media.h +++ b/src/detection/media/media.h @@ -13,6 +13,8 @@ typedef struct FFMediaResult FFstrbuf album; FFstrbuf url; FFstrbuf status; + FFstrbuf cover; + bool removeCoverAfterUse; } FFMediaResult; -const FFMediaResult* ffDetectMedia(); +const FFMediaResult* ffDetectMedia(bool saveCover); diff --git a/src/detection/media/media_apple.m b/src/detection/media/media_apple.m index 57e4bcd23e..d822b94354 100644 --- a/src/detection/media/media_apple.m +++ b/src/detection/media/media_apple.m @@ -5,6 +5,7 @@ #import #import +#import // https://github.com/andrewwiik/iOS-Blocks/blob/master/Widgets/Music/MediaRemote.h extern void MRMediaRemoteGetNowPlayingInfo(dispatch_queue_t dispatcher, void(^callback)(_Nullable CFDictionaryRef info)) __attribute__((weak_import)); @@ -12,7 +13,7 @@ extern void MRMediaRemoteGetNowPlayingApplicationDisplayID(dispatch_queue_t queue, void (^callback)(_Nullable CFStringRef displayID)) __attribute__((weak_import)); extern void MRMediaRemoteGetNowPlayingApplicationDisplayName(int unknown, dispatch_queue_t queue, void (^callback)(_Nullable CFStringRef name)) __attribute__((weak_import)); -static const char* getMediaByMediaRemote(FFMediaResult* result) +static const char* getMediaByMediaRemote(FFMediaResult* result, bool saveCover) { #define FF_TEST_FN_EXISTANCE(fn) if (!fn) return "MediaRemote function " #fn " is not available" FF_TEST_FN_EXISTANCE(MRMediaRemoteGetNowPlayingInfo); @@ -30,6 +31,25 @@ ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoTitle"), &result->song); ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoArtist"), &result->artist); ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoAlbum"), &result->album); + + if (saveCover) + { + NSData* artworkData = (__bridge NSData*) CFDictionaryGetValue(info, CFSTR("kMRMediaRemoteNowPlayingInfoArtworkData")); + if (artworkData) + { + CFStringRef mime = (CFStringRef) CFDictionaryGetValue(info, CFSTR("kMRMediaRemoteNowPlayingInfoArtworkMIMEType")); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + FF_CFTYPE_AUTO_RELEASE CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mime, NULL); + FF_CFTYPE_AUTO_RELEASE CFStringRef ext = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension); +#pragma clang diagnostic pop + NSString *tmpDir = NSTemporaryDirectory(); + NSString *uuid = NSUUID.UUID.UUIDString; + NSString *path = [tmpDir stringByAppendingPathComponent:[NSString stringWithFormat:@"ff_%@.%@", uuid, ext ? (__bridge NSString *) ext : @"img"]]; + if ([artworkData writeToFile:path atomically:NO]) + ffStrbufSetS(&result->cover, path.UTF8String); + } + } } else error = "MRMediaRemoteGetNowPlayingInfo() failed"; @@ -71,7 +91,7 @@ } __attribute__((visibility("default"), used)) -int ffPrintMediaByMediaRemote(void) +int ffPrintMediaByMediaRemote(bool saveCover) { FFMediaResult media = { .status = ffStrbufCreate(), @@ -80,8 +100,9 @@ int ffPrintMediaByMediaRemote(void) .album = ffStrbufCreate(), .playerId = ffStrbufCreate(), .player = ffStrbufCreate(), + .cover = ffStrbufCreate(), }; - if (getMediaByMediaRemote(&media) != NULL) + if (getMediaByMediaRemote(&media, saveCover) != NULL) return 1; ffStrbufPutTo(&media.status, stdout); ffStrbufPutTo(&media.song, stdout); @@ -89,19 +110,21 @@ int ffPrintMediaByMediaRemote(void) ffStrbufPutTo(&media.album, stdout); ffStrbufPutTo(&media.playerId, stdout); ffStrbufPutTo(&media.player, stdout); + ffStrbufWriteTo(&media.cover, stdout); ffStrbufDestroy(&media.status); ffStrbufDestroy(&media.song); ffStrbufDestroy(&media.artist); ffStrbufDestroy(&media.album); ffStrbufDestroy(&media.playerId); ffStrbufDestroy(&media.player); + ffStrbufDestroy(&media.cover); return 0; } -static const char* getMediaByAuthorizedProcess(FFMediaResult* result) +static const char* getMediaByAuthorizedProcess(FFMediaResult* result, bool saveCover) { // #1737 - FF_STRBUF_AUTO_DESTROY script = ffStrbufCreateF("import ctypes;ctypes.CDLL('%s').ffPrintMediaByMediaRemote()", instance.state.platform.exePath.chars); + FF_STRBUF_AUTO_DESTROY script = ffStrbufCreateF("import ctypes;ctypes.CDLL('%s').ffPrintMediaByMediaRemote(%s)", instance.state.platform.exePath.chars, saveCover ? "True" : "False"); FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); const char* error = ffProcessAppendStdOut(&buffer, (char* const[]) { @@ -114,7 +137,7 @@ int ffPrintMediaByMediaRemote(void) if (buffer.length == 0) return "No media found"; // status\ntitle\nartist\nalbum\nbundleName\nappName - FFstrbuf* const varList[] = { &result->status, &result->song, &result->artist, &result->album, &result->playerId, &result->player }; + FFstrbuf* const varList[] = { &result->status, &result->song, &result->artist, &result->album, &result->playerId, &result->player, &result->cover }; char* line = NULL; size_t len = 0; for (uint32_t i = 0; i < ARRAY_SIZE(varList) && ffStrbufGetline(&line, &len, &buffer); ++i) @@ -122,13 +145,13 @@ int ffPrintMediaByMediaRemote(void) return NULL; } -void ffDetectMediaImpl(FFMediaResult* media) +void ffDetectMediaImpl(FFMediaResult* media, bool saveCover) { const char* error; if (@available(macOS 15.4, *)) - error = getMediaByAuthorizedProcess(media); + error = getMediaByAuthorizedProcess(media, saveCover); else - error = getMediaByMediaRemote(media); + error = getMediaByMediaRemote(media, saveCover); if (error) ffStrbufAppendS(&media->error, error); else if (media->player.length == 0 && media->playerId.length > 0) @@ -137,5 +160,7 @@ void ffDetectMediaImpl(FFMediaResult* media) if (ffStrbufStartsWithIgnCaseS(&media->player, "com.")) ffStrbufSubstrAfter(&media->player, strlen("com.") - 1); ffStrbufReplaceAllC(&media->player, '.', ' '); + if (media->cover.length > 0) + media->removeCoverAfterUse = true; } } diff --git a/src/detection/media/media_linux.c b/src/detection/media/media_linux.c index adc600f0fd..37b19048ea 100644 --- a/src/detection/media/media_linux.c +++ b/src/detection/media/media_linux.c @@ -1,6 +1,8 @@ +#include "common/io/io.h" #include "fastfetch.h" #include "detection/media/media.h" #include "util/stringUtils.h" +#include "util/unused.h" #include @@ -52,21 +54,56 @@ static bool parseMprisMetadata(FFDBusData* data, DBusMessageIter* rootIterator, data->lib->ffdbus_message_iter_next(&dictIterator); - if(!ffStrStartsWith(key, "xesam:")) - FF_DBUS_ITER_CONTINUE(data, &arrayIterator) - - key += strlen("xesam:"); - if(ffStrEquals(key, "title")) - ffDBusGetString(data, &dictIterator, &result->song); - else if(ffStrEquals(key, "album")) - ffDBusGetString(data, &dictIterator, &result->album); - else if(ffStrEquals(key, "artist")) - ffDBusGetString(data, &dictIterator, &result->artist); - else if(ffStrEquals(key, "url")) - ffDBusGetString(data, &dictIterator, &result->url); - - if(result->song.length > 0 && result->artist.length > 0 && result->album.length > 0 && result->url.length > 0) - break; + if(ffStrStartsWith(key, "xesam:")) + { + const char* xesam = key + strlen("xesam:"); + if(ffStrEquals(xesam, "title")) + ffDBusGetString(data, &dictIterator, &result->song); + else if(ffStrEquals(xesam, "album")) + ffDBusGetString(data, &dictIterator, &result->album); + else if(ffStrEquals(xesam, "artist")) + ffDBusGetString(data, &dictIterator, &result->artist); + else if(ffStrEquals(xesam, "url")) + ffDBusGetString(data, &dictIterator, &result->url); + + if(result->song.length > 0 && result->artist.length > 0 && result->album.length > 0 && result->url.length > 0) + break; + } + else if (ffStrStartsWith(key, "mpris:")) + { + const char* xesam = key + strlen("mpris:"); + if(ffStrEquals(xesam, "artUrl")) + { + FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); + ffDBusGetString(data, &dictIterator, &path); + if (ffStrbufStartsWithS(&path, "file:///")) + { + ffStrbufEnsureFree(&result->cover, path.length - (uint32_t) strlen("file://")); + for (uint32_t i = (uint32_t) strlen("file://"); i < path.length; ++i) + { + if (path.chars[i] == '%') + { + if (i + 2 >= path.length) + break; + char str[] = { path.chars[i + 1], path.chars[i + 2], 0 }; + char* end = NULL; + const char decodedChar = (char) strtoul(str, &end, 16); + if (end == &str[2]) + { + i += 2; + ffStrbufAppendC(&result->cover, decodedChar); + } + else + ffStrbufAppendC(&result->cover, '%'); + } + else + { + ffStrbufAppendC(&result->cover, path.chars[i]); + } + } + } + } + } FF_DBUS_ITER_CONTINUE(data, &arrayIterator) } @@ -241,8 +278,9 @@ static const char* getMedia(FFMediaResult* result) #endif -void ffDetectMediaImpl(FFMediaResult* media) +void ffDetectMediaImpl(FFMediaResult* media, bool saveCover) { + FF_UNUSED(saveCover); // We don't save the cover to a file for Mpris implementation #ifdef FF_HAVE_DBUS const char* error = getMedia(media); ffStrbufAppendS(&media->error, error); diff --git a/src/detection/media/media_windows.c b/src/detection/media/media_windows.c index 54175aa3f9..899b6551ab 100644 --- a/src/detection/media/media_windows.c +++ b/src/detection/media/media_windows.c @@ -3,7 +3,7 @@ #include "media.h" #include "media_windows.dll.h" -static const char* getMedia(FFMediaResult* media) +static const char* getMedia(FFMediaResult* media, bool saveCover) { FF_LIBRARY_LOAD(libffwinrt, "dlopen libffwinrt" FF_LIBRARY_EXTENSION " failed", "libffwinrt" FF_LIBRARY_EXTENSION, 0) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libffwinrt, ffWinrtDetectMedia) @@ -11,7 +11,7 @@ static const char* getMedia(FFMediaResult* media) FFWinrtMediaResult result = {}; - const char* error = ffffWinrtDetectMedia(&result); + const char* error = ffffWinrtDetectMedia(&result, saveCover); if (error) { ffStrbufSetStatic(&media->error, error); @@ -32,12 +32,15 @@ static const char* getMedia(FFMediaResult* media) ffStrbufSetWS(&media->song, result.song); ffStrbufSetWS(&media->artist, result.artist); ffStrbufSetWS(&media->album, result.album); + ffStrbufSetWS(&media->cover, result.cover); ffStrbufSetStatic(&media->status, result.status); + if (media->cover.length > 0) + media->removeCoverAfterUse = true; return NULL; } -void ffDetectMediaImpl(FFMediaResult* media) +void ffDetectMediaImpl(FFMediaResult* media, bool saveCover) { - const char* error = getMedia(media); + const char* error = getMedia(media, saveCover); ffStrbufAppendS(&media->error, error); } diff --git a/src/detection/media/media_windows.dll.cpp b/src/detection/media/media_windows.dll.cpp index 34f46219be..b8a23b9e8d 100644 --- a/src/detection/media/media_windows.dll.cpp +++ b/src/detection/media/media_windows.dll.cpp @@ -1,13 +1,16 @@ +#include #include #include -#include +#include +#include #include +#include extern "C" { #include "media_windows.dll.h" -const char* ffWinrtDetectMedia(FFWinrtMediaResult* result) +const char* ffWinrtDetectMedia(FFWinrtMediaResult* result, bool saveCover) { // C++/WinRT requires Windows 8.1+ and C++ runtime (std::string, exceptions and other stuff) // Make it a separate dll in order not to break Windows 7 support @@ -47,15 +50,55 @@ const char* ffWinrtDetectMedia(FFWinrtMediaResult* result) } ::wcsncpy(result->playerId, session.SourceAppUserModelId().data(), FF_MEDIA_WIN_RESULT_BUFLEN); + result->playerId[FF_MEDIA_WIN_RESULT_BUFLEN - 1] = L'\0'; ::wcsncpy(result->song, mediaProps.Title().data(), FF_MEDIA_WIN_RESULT_BUFLEN); + result->song[FF_MEDIA_WIN_RESULT_BUFLEN - 1] = L'\0'; ::wcsncpy(result->artist, mediaProps.Artist().data(), FF_MEDIA_WIN_RESULT_BUFLEN); + result->artist[FF_MEDIA_WIN_RESULT_BUFLEN - 1] = L'\0'; ::wcsncpy(result->album, mediaProps.AlbumTitle().data(), FF_MEDIA_WIN_RESULT_BUFLEN); + result->album[FF_MEDIA_WIN_RESULT_BUFLEN - 1] = L'\0'; try { // Only works for UWP apps ::wcsncpy(result->playerName, AppInfo::GetFromAppUserModelId(session.SourceAppUserModelId()).DisplayInfo().DisplayName().data(), FF_MEDIA_WIN_RESULT_BUFLEN); + result->playerName[FF_MEDIA_WIN_RESULT_BUFLEN - 1] = L'\0'; } catch (...) { } + if (saveCover) + { + using namespace winrt::Windows::Storage; + using namespace winrt::Windows::Storage::Streams; + if (auto thumbRef = mediaProps.Thumbnail()) + { + try + { + if (auto stream = thumbRef.OpenReadAsync().get()) + { + if (stream.Size() > 0) + { + Buffer buffer(static_cast(stream.Size())); + stream.ReadAsync(buffer, buffer.Capacity(), InputStreamOptions::None).get(); + + wchar_t tempPath[MAX_PATH]; + if (GetTempPathW(MAX_PATH, tempPath) > 0) + { + auto tempFolder = StorageFolder::GetFolderFromPathAsync(tempPath).get(); + auto tempFile = tempFolder.CreateFileAsync(L"ff_thumb.img", CreationCollisionOption::GenerateUniqueName).get(); + FileIO::WriteBufferAsync(tempFile, buffer).get(); + + ::wcsncpy(result->cover, tempFile.Path().data(), FF_MEDIA_WIN_RESULT_BUFLEN); + result->cover[FF_MEDIA_WIN_RESULT_BUFLEN - 1] = L'\0'; + } + } + } + } + catch (...) + { + // Ignore thumbnail errors + } + } + } + return NULL; } catch (...) diff --git a/src/detection/media/media_windows.dll.h b/src/detection/media/media_windows.dll.h index aae081e88a..9d4e37755c 100644 --- a/src/detection/media/media_windows.dll.h +++ b/src/detection/media/media_windows.dll.h @@ -9,8 +9,9 @@ typedef struct FFWinrtMediaResult wchar_t song[FF_MEDIA_WIN_RESULT_BUFLEN]; wchar_t artist[FF_MEDIA_WIN_RESULT_BUFLEN]; wchar_t album[FF_MEDIA_WIN_RESULT_BUFLEN]; + wchar_t cover[FF_MEDIA_WIN_RESULT_BUFLEN]; const char* status; } FFWinrtMediaResult; __attribute__((__dllexport__)) -const char* ffWinrtDetectMedia(FFWinrtMediaResult* result); +const char* ffWinrtDetectMedia(FFWinrtMediaResult* result, bool saveCover); diff --git a/src/detection/os/os_linux.c b/src/detection/os/os_linux.c index e66e1fe62e..23b9568323 100644 --- a/src/detection/os/os_linux.c +++ b/src/detection/os/os_linux.c @@ -240,7 +240,7 @@ FF_MAYBE_UNUSED static bool detectDebianDerived(FFOSResult* result) ffStrbufSetStatic(&result->id, "raspbian"); ffStrbufSetStatic(&result->idLike, "debian"); ffStrbufSetStatic(&result->name, "Raspberry Pi OS"); - ffStrbufSetStatic(&result->prettyName, "Raspberry Pi OS"); + getDebianVersion(result); return true; } else if (ffPathExists("/boot/dietpi/.version", FF_PATHTYPE_FILE)) diff --git a/src/detection/publicip/publicip.c b/src/detection/publicip/publicip.c index 7fed00cc29..2f8fdcbe06 100644 --- a/src/detection/publicip/publicip.c +++ b/src/detection/publicip/publicip.c @@ -70,6 +70,10 @@ const char* ffDetectPublicIp(FFPublicIPOptions* options, FFPublicIpResult* resul FF_STRBUF_AUTO_DESTROY response = ffStrbufCreateA(4096); const char* error = ffNetworkingRecvHttpResponse(state, &response); + + *state = (FFNetworkingState){}; + *status = FF_UNITIALIZED; + if (error == NULL) ffStrbufSubstrAfterFirstS(&response, "\r\n\r\n"); else diff --git a/src/detection/terminalshell/terminalshell.c b/src/detection/terminalshell/terminalshell.c index 48476b63a7..5ffae36916 100644 --- a/src/detection/terminalshell/terminalshell.c +++ b/src/detection/terminalshell/terminalshell.c @@ -6,6 +6,7 @@ #include "util/binary.h" #include +#include #ifdef __FreeBSD__ #include #ifndef _PATH_LOCALBASE @@ -85,8 +86,10 @@ static bool getShellVersionFish(FFstrbuf* exe, FFstrbuf* version) if(!getExeVersionRaw(exe, version)) return false; - //fish, version 4.0.2-1 (Built by MSYS2 project) - ffStrbufSubstrAfterFirstS(version, "version "); + //fish, version 4.0.2-1 (Built by MSYS2 project) // version can be localized if LC_ALL is set + if (version->length < strlen("fish, v") || !ffStrbufStartsWithS(version, "fish")) return false; + uint32_t index = ffStrbufNextIndexC(version, strlen("fish, "), ' '); + ffStrbufSubstrAfter(version, index); ffStrbufSubstrBeforeFirstC(version, ' '); return true; } @@ -291,6 +294,8 @@ bool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, FFstrbuf* exePath, return getShellVersionAsh(exe, version); if(ffStrEqualsIgnCase(exeName, "xonsh")) return getShellVersionXonsh(exe, version); + if(ffStrEqualsIgnCase(exeName, "brush")) + return getExeVersionGeneral(exe, version); // brush 0.2.23 (git:2835487) #ifdef _WIN32 if(ffStrEqualsIgnCase(exeName, "powershell") || ffStrEqualsIgnCase(exeName, "powershell_ise")) diff --git a/src/detection/terminalshell/terminalshell_linux.c b/src/detection/terminalshell/terminalshell_linux.c index 03b4711d50..e39ea9ef56 100644 --- a/src/detection/terminalshell/terminalshell_linux.c +++ b/src/detection/terminalshell/terminalshell_linux.c @@ -57,6 +57,9 @@ static pid_t getShellInfo(FFShellResult* result, pid_t pid) ffStrbufEqualS(&result->processName, "flashfetch") || ffStrbufEqualS(&result->processName, "proot") || ffStrbufEqualS(&result->processName, "script") || + #ifdef __linux__ + ffStrbufEqualS(&result->processName, "run-parts") || + #endif ffStrbufContainS(&result->processName, "debug") || ffStrbufContainS(&result->processName, "command-not-") || ffStrbufEndsWithS(&result->processName, ".sh") @@ -113,6 +116,7 @@ static pid_t getTerminalInfo(FFTerminalResult* result, pid_t pid) #ifdef __linux__ ffStrbufStartsWithS(&result->processName, "Relay(") || // Unknown process in WSL2 ffStrbufStartsWithS(&result->processName, "flatpak-") || // #707 + ffStrbufEqualS(&result->processName, "run-parts") || // #2048 #endif ffStrbufEndsWithS(&result->processName, ".sh") ) diff --git a/src/detection/weather/weather.c b/src/detection/weather/weather.c index 8f1a2accba..e244d6319a 100644 --- a/src/detection/weather/weather.c +++ b/src/detection/weather/weather.c @@ -44,6 +44,10 @@ const char* ffDetectWeather(FFWeatherOptions* options, FFstrbuf* result) ffStrbufEnsureFree(result, 4095); const char* error = ffNetworkingRecvHttpResponse(&state, result); + + state = (FFNetworkingState){}; + status = FF_UNITIALIZED; + if (error == NULL) { ffStrbufSubstrAfterFirstS(result, "\r\n\r\n"); diff --git a/src/fastfetch.c b/src/fastfetch.c index b3845246e4..6a742a5e74 100644 --- a/src/fastfetch.c +++ b/src/fastfetch.c @@ -3,6 +3,7 @@ #include "common/init.h" #include "common/io/io.h" #include "common/jsonconfig.h" +#include "common/time.h" #include "detection/version/version.h" #include "logo/logo.h" #include "util/stringUtils.h" @@ -670,6 +671,8 @@ static void parseCommand(FFdata* data, char* key, char* value) {}, })); } + else if(ffStrEqualsIgnCase(key, "--dynamic-interval")) + instance.state.dynamicInterval = ffOptionParseUInt32(key, value); // seconds to milliseconds else return; @@ -768,15 +771,31 @@ static void run(FFdata* data) if (!instance.config.display.noBuffer) fflush(stdout); #endif - if (useJsonConfig) - ffPrintJsonConfig(false, instance.state.resultDoc); - else - ffPrintCommandOption(data, instance.state.resultDoc); + while (true) + { + if (useJsonConfig) + ffPrintJsonConfig(false, instance.state.resultDoc); + else + ffPrintCommandOption(data, instance.state.resultDoc); + + if (instance.state.dynamicInterval > 0) + { + fflush(stdout); + ffTimeSleep(instance.state.dynamicInterval); + fputs("\e[H", stdout); + } + else + break; + } if (instance.state.resultDoc) yyjson_mut_write_fp(stdout, instance.state.resultDoc, YYJSON_WRITE_INF_AND_NAN_AS_NULL | YYJSON_WRITE_PRETTY_TWO_SPACES | YYJSON_WRITE_NEWLINE_AT_END, NULL, NULL); else + { + if (instance.config.logo.printRemaining) + ffLogoPrintRemaining(); ffFinish(); + } } static void writeConfigFile(FFdata* data) @@ -835,6 +854,12 @@ int main(int argc, char** argv) }; parseArguments(&data, argc, argv, parseCommand); + if(instance.state.dynamicInterval && instance.state.resultDoc) + { + fprintf(stderr, "Error: --dynamic-interval cannot be used with --json\n"); + exit(400); + } + if(!data.configLoaded && !getenv("NO_CONFIG")) parseConfigFiles(); parseArguments(&data, argc, argv, (void*) parseOption); diff --git a/src/fastfetch.h b/src/fastfetch.h index f38bb864d3..bb5197d6c9 100644 --- a/src/fastfetch.h +++ b/src/fastfetch.h @@ -46,6 +46,7 @@ typedef struct FFstate yyjson_mut_doc* resultDoc; FFstrbuf genConfigPath; bool fullConfig; + uint32_t dynamicInterval; } FFstate; typedef struct FFinstance diff --git a/src/flashfetch.c b/src/flashfetch.c index f4256cff63..e4815686c4 100644 --- a/src/flashfetch.c +++ b/src/flashfetch.c @@ -1,6 +1,7 @@ #include "fastfetch.h" #include "common/init.h" +#include "logo/logo.h" #include "modules/modules.h" // A dirty replicate of neofetch @@ -136,6 +137,7 @@ int main(void) ffPrintColors(&options); } + ffLogoPrintRemaining(); ffFinish(); ffDestroyInstance(); return 0; diff --git a/src/logo/ascii/macaronios.txt b/src/logo/ascii/macaronios.txt new file mode 100644 index 0000000000..930a360fba --- /dev/null +++ b/src/logo/ascii/macaronios.txt @@ -0,0 +1,11 @@ + .::-::::. :======---:. + .-=++++++++==-:. .:-++===--=**+=: +.=. ...:::::--=****+++=-:. -- +-: $2.-==-.$1 .::. :: +. $2:*%@@@@@#. .:::. + :#%%%%%@%#+. :*##%%%%#+. + *%%%%%%*. =. +*#%%%%%%%#: + *%%%%%%- +%%%%%%#@= + :%%%%%%#-. .:+%%%%%%%%+ + :*%%%%%%+. .-+*###*=: + :=+=-. \ No newline at end of file diff --git a/src/logo/ascii/vincentos.txt b/src/logo/ascii/vincentos.txt new file mode 100644 index 0000000000..9672efdad8 --- /dev/null +++ b/src/logo/ascii/vincentos.txt @@ -0,0 +1,16 @@ + __agggg + _g@@@@@@@P + a@@@@@@@@F + _@@@@@@@@F + $2a@@@@@@_ _@@@@@@_$1` + $2_a@@@~@@@@, @@@@~M@@&_ +_@@@~ `@@@@ @@@@~ `@@@_ +@@@" ~@@@@ @@@@~ "@@@ +@@@ ~@@@@ @@@@F @@@ +@@@ 4@@@@ @@@@F @@@ +@@@_ 4@@@b @@@@F _@@@ +"@@@_ 4@@@yy@@@F _@@@" + "R@@g_ 5@@@@@@@ _g@@@" + ~@@@gy_ @@@@@@ _yg@@@~ + `T@@@@gyyy@@@@yyag@@@@F~ + ~~FRR@@@@@@RPF~~ \ No newline at end of file diff --git a/src/logo/builtin.c b/src/logo/builtin.c index 0fe3b03245..7c05d41d39 100644 --- a/src/logo/builtin.c +++ b/src/logo/builtin.c @@ -2856,6 +2856,17 @@ static const FFlogo L[] = { }; static const FFlogo M[] = { + // Macaroni + { + .names = {"Macaroni"}, + .lines = FASTFETCH_DATATEXT_LOGO_MACARONIOS, + .colors = { + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_WHITE, + }, + .colorKeys = FF_COLOR_FG_YELLOW, + .colorTitle = FF_COLOR_FG_GREEN, + }, // MacOS { .names = {"macos", "mac"}, @@ -5200,6 +5211,15 @@ static const FFlogo V[] = { FF_COLOR_FG_BLUE, }, }, + // VincentOS + { + .names = {"VincentOS"}, + .lines = FASTFETCH_DATATEXT_LOGO_VINCENTOS, + .colors = { + FF_COLOR_FG_GREEN, + FF_COLOR_FG_DEFAULT, + }, + }, // Vnux { .names = {"Vnux"}, diff --git a/src/logo/logo.c b/src/logo/logo.c index a0d64b72f5..618e6ae1aa 100644 --- a/src/logo/logo.c +++ b/src/logo/logo.c @@ -2,12 +2,14 @@ #include "common/io/io.h" #include "common/printing.h" #include "common/processing.h" +#include "detection/media/media.h" #include "detection/os/os.h" #include "detection/terminalshell/terminalshell.h" #include "util/textModifier.h" #include "util/stringUtils.h" #include +#include #include typedef enum __attribute__((__packed__)) FFLogoSize @@ -483,6 +485,15 @@ static bool updateLogoPath(void) if (ffStrbufEqualS(&options->source, "-")) // stdin return true; + if (ffStrbufIgnCaseEqualS(&options->source, "media-cover")) + { + const FFMediaResult* media = ffDetectMedia(true); + if (media->cover.length == 0) + return false; + ffStrbufSet(&options->source, &media->cover); + return true; + } + FF_STRBUF_AUTO_DESTROY fullPath = ffStrbufCreate(); if (ffPathExpandEnv(options->source.chars, &fullPath) && ffPathExists(fullPath.chars, FF_PATHTYPE_FILE)) { @@ -706,6 +717,9 @@ void ffLogoPrintLine(void) if(instance.state.logoWidth > 0) printf("\033[%uC", instance.state.logoWidth); + if (instance.state.dynamicInterval > 0) + fputs("\033[K", stdout); + ++instance.state.keysHeight; } diff --git a/src/modules/command/command.c b/src/modules/command/command.c index a46d771151..59f6b49519 100644 --- a/src/modules/command/command.c +++ b/src/modules/command/command.c @@ -1,30 +1,20 @@ #include "common/printing.h" #include "common/jsonconfig.h" -#include "common/processing.h" #include "modules/command/command.h" -#include "util/stringUtils.h" +#include "detection/command/command.h" bool ffPrintCommand(FFCommandOptions* options) { FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate(); - const char* error = ffProcessAppendStdOut(&result, options->param.length ? (char* const[]){ - options->shell.chars, - options->param.chars, - options->text.chars, - NULL - } : (char* const[]){ - options->shell.chars, - options->text.chars, - NULL - }); + const char* error = ffDetectCommand(options, &result); - if(error) + if (error) { ffPrintError(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } - if(!result.length) + if (!result.length) { ffPrintError(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No result generated"); return false; @@ -72,6 +62,18 @@ void ffParseCommandJsonObject(FFCommandOptions* options, yyjson_val* module) continue; } + if (unsafe_yyjson_equals_str(key, "useStdErr")) + { + options->useStdErr = yyjson_get_bool(val); + continue; + } + + if (unsafe_yyjson_equals_str(key, "parallel")) + { + options->parallel = yyjson_get_bool(val); + continue; + } + ffPrintError(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } @@ -81,25 +83,16 @@ void ffGenerateCommandJsonConfig(FFCommandOptions* options, yyjson_mut_doc* doc, ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_strbuf(doc, module, "shell", &options->shell); - yyjson_mut_obj_add_strbuf(doc, module, "param", &options->param); - yyjson_mut_obj_add_strbuf(doc, module, "text", &options->text); + yyjson_mut_obj_add_bool(doc, module, "useStdErr", options->useStdErr); + yyjson_mut_obj_add_bool(doc, module, "parallel", options->parallel); } bool ffGenerateCommandJsonResult(FF_MAYBE_UNUSED FFCommandOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate(); - const char* error = ffProcessAppendStdOut(&result, options->param.length ? (char* const[]){ - options->shell.chars, - options->param.chars, - options->text.chars, - NULL - } : (char* const[]){ - options->shell.chars, - options->text.chars, - NULL - }); + const char* error = ffDetectCommand(options, &result); if(error) { @@ -137,6 +130,8 @@ void ffInitCommandOptions(FFCommandOptions* options) #endif ); ffStrbufInit(&options->text); + options->useStdErr = false; + options->parallel = true; } void ffDestroyCommandOptions(FFCommandOptions* options) diff --git a/src/modules/command/command.h b/src/modules/command/command.h index 204553747e..e0e143e016 100644 --- a/src/modules/command/command.h +++ b/src/modules/command/command.h @@ -4,6 +4,8 @@ #define FF_COMMAND_MODULE_NAME "Command" +bool ffPrepareCommand(FFCommandOptions* options); + bool ffPrintCommand(FFCommandOptions* options); void ffInitCommandOptions(FFCommandOptions* options); void ffDestroyCommandOptions(FFCommandOptions* options); diff --git a/src/modules/command/option.h b/src/modules/command/option.h index 62fbeb9818..e85a65f150 100644 --- a/src/modules/command/option.h +++ b/src/modules/command/option.h @@ -9,6 +9,8 @@ typedef struct FFCommandOptions FFstrbuf shell; FFstrbuf param; FFstrbuf text; + bool useStdErr; + bool parallel; } FFCommandOptions; static_assert(sizeof(FFCommandOptions) <= FF_OPTION_MAX_SIZE, "FFCommandOptions size exceeds maximum allowed size"); diff --git a/src/modules/disk/disk.c b/src/modules/disk/disk.c index 3369287c2e..947f7482f3 100644 --- a/src/modules/disk/disk.c +++ b/src/modules/disk/disk.c @@ -189,30 +189,6 @@ static void printDisk(FFDiskOptions* options, const FFDisk* disk, uint32_t index } } -static inline bool isMatchFolders(FFstrbuf* folders, const FFstrbuf* path, char separator) -{ - #ifndef _WIN32 - uint32_t startIndex = 0; - while(startIndex < folders->length) - { - uint32_t colonIndex = ffStrbufNextIndexC(folders, startIndex, separator); - - char savedColon = folders->chars[colonIndex]; // Can be '\0' if at end - folders->chars[colonIndex] = '\0'; - - bool matched = fnmatch(&folders->chars[startIndex], path->chars, 0) == 0; - folders->chars[colonIndex] = savedColon; - - if (matched) return true; - - startIndex = colonIndex + 1; - } - return false; - #else - return ffStrbufSeparatedContain(folders, path, separator); - #endif -} - bool ffPrintDisk(FFDiskOptions* options) { FF_LIST_AUTO_DESTROY disks = ffListCreate(sizeof (FFDisk)); @@ -224,18 +200,18 @@ bool ffPrintDisk(FFDiskOptions* options) return false; } + if(disks.length == 0) + { + ffPrintError(FF_DISK_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No disks found"); + return false; + } + uint32_t index = 0; FF_LIST_FOR_EACH(FFDisk, disk, disks) { if(__builtin_expect(options->folders.length == 0, 1) && (disk->type & ~options->showTypes)) continue; - if (options->hideFolders.length && isMatchFolders(&options->hideFolders, &disk->mountpoint, FF_DISK_FOLDER_SEPARATOR)) - continue; - - if (options->hideFS.length && ffStrbufSeparatedContain(&options->hideFS, &disk->filesystem, ':')) - continue; - printDisk(options, disk, ++index); } @@ -407,7 +383,7 @@ bool ffGenerateDiskJsonResult(FFDiskOptions* options, yyjson_mut_doc* doc, yyjso if(error) { - yyjson_mut_obj_add_str(doc, module, "result", error); + yyjson_mut_obj_add_str(doc, module, "error", error); return false; } diff --git a/src/modules/gpu/gpu.c b/src/modules/gpu/gpu.c index 64fb143807..723a2cebea 100644 --- a/src/modules/gpu/gpu.c +++ b/src/modules/gpu/gpu.c @@ -386,9 +386,12 @@ bool ffGenerateGPUJsonResult(FFGPUOptions* options, yyjson_mut_doc* doc, yyjson_ { case FF_GPU_TYPE_INTEGRATED: type = "Integrated"; break; case FF_GPU_TYPE_DISCRETE: type = "Discrete"; break; - default: type = "Unknown"; break; + default: type = NULL; break; } - yyjson_mut_obj_add_str(doc, obj, "type", type); + if (type) + yyjson_mut_obj_add_str(doc, obj, "type", type); + else + yyjson_mut_obj_add_null(doc, obj, "type"); yyjson_mut_obj_add_strbuf(doc, obj, "vendor", &gpu->vendor); diff --git a/src/modules/media/media.c b/src/modules/media/media.c index f82b4b8567..ab1aafc3a5 100644 --- a/src/modules/media/media.c +++ b/src/modules/media/media.c @@ -43,7 +43,7 @@ static bool artistInSongTitle(const FFstrbuf* song, const FFstrbuf* artist) bool ffPrintMedia(FFMediaOptions* options) { - const FFMediaResult* media = ffDetectMedia(); + const FFMediaResult* media = ffDetectMedia(false); if(media->error.length > 0) { @@ -128,7 +128,7 @@ void ffGenerateMediaJsonConfig(FFMediaOptions* options, yyjson_mut_doc* doc, yyj bool ffGenerateMediaJsonResult(FF_MAYBE_UNUSED FFMediaOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { - const FFMediaResult* media = ffDetectMedia(); + const FFMediaResult* media = ffDetectMedia(false); if(media->error.length > 0) { @@ -143,6 +143,10 @@ bool ffGenerateMediaJsonResult(FF_MAYBE_UNUSED FFMediaOptions* options, yyjson_m yyjson_mut_obj_add_strbuf(doc, song, "artist", &media->artist); yyjson_mut_obj_add_strbuf(doc, song, "album", &media->album); yyjson_mut_obj_add_strbuf(doc, song, "status", &media->status); + if (media->cover.length > 0) + yyjson_mut_obj_add_strbuf(doc, song, "cover", &media->cover); + else + yyjson_mut_obj_add_null(doc, song, "cover"); yyjson_mut_val* player = yyjson_mut_obj_add_obj(doc, obj, "player"); yyjson_mut_obj_add_strbuf(doc, player, "name", &media->player); diff --git a/src/modules/player/player.c b/src/modules/player/player.c index 69002cb488..62592dced3 100644 --- a/src/modules/player/player.c +++ b/src/modules/player/player.c @@ -10,7 +10,7 @@ bool ffPrintPlayer(FFPlayerOptions* options) { - const FFMediaResult* media = ffDetectMedia(); + const FFMediaResult* media = ffDetectMedia(false); if(media->error.length > 0) { diff --git a/src/options/display.h b/src/options/display.h index 4c29845e11..55a4e66682 100644 --- a/src/options/display.h +++ b/src/options/display.h @@ -2,6 +2,7 @@ #include "common/percent.h" #include "util/FFstrbuf.h" +#include "util/FFlist.h" typedef enum __attribute__((__packed__)) FFSizeBinaryPrefixType { diff --git a/src/util/apple/cf_helpers.c b/src/util/apple/cf_helpers.c index e63df97278..8156d3779e 100644 --- a/src/util/apple/cf_helpers.c +++ b/src/util/apple/cf_helpers.c @@ -40,11 +40,9 @@ const char* ffCfNumGetInt(CFTypeRef cf, int32_t* result) const char* ffCfStrGetString(CFTypeRef cf, FFstrbuf* result) { + ffStrbufClear(result); if (!cf) - { - ffStrbufClear(result); return NULL; - } if (CFGetTypeID(cf) == CFStringGetTypeID()) { @@ -56,6 +54,8 @@ const char* ffCfStrGetString(CFTypeRef cf, FFstrbuf* result) else { uint32_t length = (uint32_t) CFStringGetLength(cfStr); + if (length == 0) + return NULL; ffStrbufEnsureFixedLengthFree(result, (uint32_t) CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8)); if(!CFStringGetCString(cfStr, result->chars, result->allocated, kCFStringEncodingUTF8)) return "CFStringGetCString() failed"; @@ -68,7 +68,9 @@ const char* ffCfStrGetString(CFTypeRef cf, FFstrbuf* result) { CFDataRef cfData = (CFDataRef)cf; uint32_t length = (uint32_t)CFDataGetLength(cfData); - ffStrbufEnsureFree(result, length + 1); + if (length == 0) + return NULL; + ffStrbufEnsureFixedLengthFree(result, length + 1); CFDataGetBytes(cfData, CFRangeMake(0, length), (uint8_t*)result->chars); result->length = (uint32_t)strnlen(result->chars, length); result->chars[result->length] = '\0'; @@ -79,6 +81,29 @@ const char* ffCfStrGetString(CFTypeRef cf, FFstrbuf* result) return NULL; } +const char* ffCfDataGetDataAsString(CFTypeRef cf, FFstrbuf* result) +{ + ffStrbufClear(result); + if (!cf) + return NULL; + + if (CFGetTypeID(cf) == CFDataGetTypeID()) + { + CFDataRef cfData = (CFDataRef)cf; + uint32_t length = (uint32_t)CFDataGetLength(cfData); + if (length == 0) + return NULL; + ffStrbufEnsureFixedLengthFree(result, length + 1); + CFDataGetBytes(cfData, CFRangeMake(0, length), (uint8_t*)result->chars); + result->length = length; + result->chars[result->length] = '\0'; + } + else + return "TypeID is not 'CFData'"; + + return NULL; +} + const char* ffCfDictGetString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result) { CFTypeRef cf = (CFTypeRef)CFDictionaryGetValue(dict, key); @@ -88,6 +113,15 @@ const char* ffCfDictGetString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* r return ffCfStrGetString(cf, result); } +const char* ffCfDictGetDataAsString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result) +{ + CFTypeRef cf = (CFTypeRef)CFDictionaryGetValue(dict, key); + if(cf == NULL) + return "CFDictionaryGetValue() failed"; + + return ffCfDataGetDataAsString(cf, result); +} + const char* ffCfDictGetBool(CFDictionaryRef dict, CFStringRef key, bool* result) { CFBooleanRef cf = (CFBooleanRef)CFDictionaryGetValue(dict, key); diff --git a/src/util/apple/cf_helpers.h b/src/util/apple/cf_helpers.h index 5f6cb291ce..c41a397dcc 100644 --- a/src/util/apple/cf_helpers.h +++ b/src/util/apple/cf_helpers.h @@ -8,11 +8,13 @@ const char* ffCfStrGetString(CFTypeRef cf, FFstrbuf* result); const char* ffCfNumGetInt(CFTypeRef cf, int32_t* result); const char* ffCfNumGetInt64(CFTypeRef cf, int64_t* result); +const char* ffCfDataGetDataAsString(CFTypeRef cf, FFstrbuf* result); const char* ffCfDictGetString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result); const char* ffCfDictGetBool(CFDictionaryRef dict, CFStringRef key, bool* result); const char* ffCfDictGetInt(CFDictionaryRef dict, CFStringRef key, int* result); const char* ffCfDictGetInt64(CFDictionaryRef dict, CFStringRef key, int64_t* result); const char* ffCfDictGetData(CFDictionaryRef dict, CFStringRef key, uint32_t offset, uint32_t size, uint8_t* result, uint32_t* length); +const char* ffCfDictGetDataAsString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result); const char* ffCfDictGetDict(CFDictionaryRef dict, CFStringRef key, CFDictionaryRef* result); static inline CFNumberRef ffCfCreateInt(int value) @@ -22,6 +24,7 @@ static inline CFNumberRef ffCfCreateInt(int value) static inline void cfReleaseWrapper(void* type) { + assert(type); if (*(CFTypeRef*) type) CFRelease(*(CFTypeRef*) type); } diff --git a/src/util/smbiosHelper.c b/src/util/smbiosHelper.c index 593527a72f..cb1c751a52 100644 --- a/src/util/smbiosHelper.c +++ b/src/util/smbiosHelper.c @@ -402,7 +402,7 @@ const FFSmbiosHeaderTable* ffGetSmbiosHeaderTable() #endif FF_DEBUG("Parsing SMBIOS table structures"); - int structureCount = 0; + FF_MAYBE_UNUSED int structureCount = 0; for ( const FFSmbiosHeader* header = (const FFSmbiosHeader*) buffer.chars; (const uint8_t*) header < (const uint8_t*) buffer.chars + buffer.length;