diff --git a/.clang-format b/.clang-format index bbcb3665..61c2e565 100644 --- a/.clang-format +++ b/.clang-format @@ -3,8 +3,8 @@ BasedOnStyle: Google # Align assignments and similar statements -AlignConsecutiveAssignments: true -AlignConsecutiveDeclarations: true +AlignConsecutiveAssignments: Consecutive +AlignConsecutiveDeclarations: Consecutive # Align the * in declarations DerivePointerAlignment: false @@ -23,7 +23,7 @@ BreakBeforeBraces: Custom BraceWrapping: AfterCaseLabel: true AfterClass: true - AfterControlStatement: true + AfterControlStatement: Always AfterEnum: true AfterFunction: true AfterNamespace: true @@ -32,6 +32,7 @@ BraceWrapping: BeforeCatch: true BeforeElse: true IndentBraces: false + SplitEmptyFunction: false # Control spaces around various constructs #SpacesInParens: Custom @@ -61,7 +62,7 @@ NamespaceIndentation: None ReflowComments: true # Additional settings to match Source SDK 2013 style -AllowShortIfStatementsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false AllowShortFunctionsOnASingleLine: All diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index d5c8d6d6..c93d6dfd 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -2,9 +2,9 @@ name: CMake CI (Test customfetch) on: push: - branches: [ "main" ] + branches: [ "main", "plugins" ] pull_request: - branches: [ "main" ] + branches: [ "main", "plugins" ] jobs: build-ubuntu_22-04: @@ -45,7 +45,7 @@ jobs: printf "getting 0x5353 hexcode\n" && grep -nri "5353" /sys/class/ || true - name: Test customfetch - run: customfetch --wrap-lines + run: LD_LIBRARY_PATH="./build:$LD_LIBRARY_PATH" customfetch --wrap-lines - name: Upload to github artifacts uses: actions/upload-artifact@v4 @@ -83,7 +83,7 @@ jobs: run: neofetch - name: Test customfetch - run: customfetch-gui --version + run: LD_LIBRARY_PATH="./build:$LD_LIBRARY_PATH" customfetch-gui --version - name: Upload to github artifacts uses: actions/upload-artifact@v4 @@ -136,7 +136,7 @@ jobs: printf "/etc/os-release\n" && cat /etc/os-release - name: Test customfetch - run: customfetch --wrap-lines + run: LD_LIBRARY_PATH="./build:$LD_LIBRARY_PATH" customfetch --wrap-lines build-macos: runs-on: macos-latest @@ -169,7 +169,7 @@ jobs: run: fastfetch - name: Test customfetch - run: ./build/customfetch -D assets + run: LD_LIBRARY_PATH="./build:$LD_LIBRARY_PATH" ./build/customfetch -D assets - name: Upload to github artifacts uses: actions/upload-artifact@v4 diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml index d0a3db03..8451fe60 100644 --- a/.github/workflows/makefile.yml +++ b/.github/workflows/makefile.yml @@ -2,9 +2,9 @@ name: Makefile CI (Test customfetch) on: push: - branches: [ "main" ] + branches: [ "main", "plugins" ] pull_request: - branches: [ "main" ] + branches: [ "main", "plugins" ] jobs: build-deb: @@ -232,7 +232,7 @@ jobs: run: make distclean - name: Compile and install - run: make install DEBUG=1 VENDOR_TEST=1 GUI_APP=0 + run: make install DEBUG=1 GUI_APP=0 - name: Test fastfetch run: fastfetch @@ -284,24 +284,24 @@ jobs: name: customfetch-macos path: ./build/debug/customfetch - test-suitcase: - runs-on: ubuntu-22.04 - permissions: - contents: read - - steps: - - uses: actions/checkout@v4 - - - name: Install Packages - run: | - sudo apt-get purge firefox # Slows down the installation alot, fuck snap - sudo apt-get update && sudo apt upgrade -y - sudo apt-get install neofetch tree build-essential g++-11 libwayland-dev gettext libgtk-3-dev pkg-config libdconf-dev libglib2.0-dev libgtkmm-3.0-dev libarchive-tools -y - - - name: Clean - run: make distclean - - - name: Compile and install - run: | - cd tests && make - find . -type f -executable -exec {} \; + # test-suitcase: + # runs-on: ubuntu-22.04 + # permissions: + # contents: read + # + # steps: + # - uses: actions/checkout@v4 + # + # - name: Install Packages + # run: | + # sudo apt-get purge firefox # Slows down the installation alot, fuck snap + # sudo apt-get update && sudo apt upgrade -y + # sudo apt-get install neofetch tree build-essential g++-11 libwayland-dev gettext libgtk-3-dev pkg-config libdconf-dev libglib2.0-dev libgtkmm-3.0-dev libarchive-tools -y + # + # - name: Clean + # run: make distclean + # + # - name: Compile and install + # run: | + # cd tests && make + # find . -type f -executable -exec {} \; diff --git a/.gitignore b/.gitignore index 914ced4e..42a5f20c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,18 @@ build*/ assets/distro_ascii* -cufetch +/cufetch /*.txt pci.ids output/ useless_stuff/ -customfetch -customfetch_r -cufetch_r +/customfetch +/customfetch_r +/cufetch_r locale/ usr/ -scripts/ascii_parser.py -scripts/dict_to_files.py -scripts/test* +/scripts/ascii_parser.py +/scripts/dict_to_files.py +/scripts/test* tests/test* !tests/*.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d171899..63080df0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,43 +6,72 @@ cmake_minimum_required(VERSION 3.10) project(customfetch) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -ggdb3 -O0 -DDEBUG=1 -Wall -Wextra -Wpedantic -Wno-unused-parameter") +set(CMAKE_CXX_VISIBILITY_PRESET hidden) +set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) include_directories(include) +include_directories(include/libcufetch) +include_directories(include/libs) + +set_source_files_properties( + "src/libs/toml++/toml.cpp" + "src/libs/tiny-process-library/process.cpp" + PROPERTIES COMPILE_FLAGS "-fvisibility=default" +) file(GLOB SRC "src/*.cpp" - "src/query/linux/*.cpp" - "src/query/android/*.cpp" - "src/query/macos/*.cpp" - "src/query/linux/utils/*.cpp" + "src/core-modules/*.cc" + "src/core-modules/linux/*.cc" + "src/core-modules/android/*.cc" + "src/core-modules/macos/*.cc" + "src/core-modules/linux/utils/*.cc" "src/libs/json/json.cpp" - "src/libs/toml++/toml.cpp") + "src/libs/toml++/toml.cpp" + "src/libs/getopt_port/getopt.c") + +function(enable_lto target) + if (NOT TARGET ${target}) + message(FATAL_ERROR "enable_lto called on non-existent target: ${target}") + endif() + + if (CMAKE_BUILD_TYPE STREQUAL "Release") + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # GCC supports -ffat-lto-objects for archives + target_compile_options(${target} PRIVATE -flto=auto -ffat-lto-objects) + target_link_options(${target} PRIVATE -flto=auto) + elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # Use ThinLTO on Clang (better incremental builds) + target_compile_options(${target} PRIVATE -flto=thin) + target_link_options(${target} PRIVATE -flto=thin) + else() + message(WARNING "LTO not configured for compiler: ${CMAKE_CXX_COMPILER_ID}") + endif() + endif() +endfunction() if(GUI_APP) - set(TARGET_NAME customfetch-gui) + set(TARGET_NAME customfetch-gui) else() - set(TARGET_NAME customfetch) + set(TARGET_NAME customfetch) endif() add_executable(${TARGET_NAME} ${SRC}) +enable_lto(${TARGET_NAME}) # Get git info hash and branch execute_process(COMMAND ./scripts/generateVersion.sh WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) -target_compile_definitions(${TARGET_NAME} PRIVATE - VERSION="1.0.0" -) - # https://github.com/libcpr/cpr/blob/5f475522597b8f3721e2440daddeced7a969f24c/CMakeLists.txt#L39 -macro(add_option OPTION_NAME OPTION_TEXT OPTION_DEFAULT) +macro(add_option OPTION_NAME OPTION_TEXT OPTION_DEFAULT DEFINE_OPTION) option(${OPTION_NAME} ${OPTION_TEXT} ${OPTION_DEFAULT}) if(DEFINED ENV{${OPTION_NAME}}) # Allow overriding the option through an environment variable set(${OPTION_NAME} $ENV{${OPTION_NAME}}) endif() - if(${OPTION_NAME}) + if(${OPTION_NAME} AND DEFINE_OPTION) add_definitions(-D${OPTION_NAME}) endif() message(STATUS " ${OPTION_NAME}: ${${OPTION_NAME}}") @@ -50,9 +79,9 @@ endmacro() message(STATUS "Set flags:") message(STATUS "=================") -add_option(GUI_APP "Build GTK3 application" 0) -add_option(USE_DCONF "Compile customfetch with dconf support" 1) -add_option(VARS "Add additional flags at CXXFLAGS" "") +add_option(GUI_APP "Build GTK3 application" 0 1) +add_option(USE_DCONF "Compile customfetch with dconf support" 1 0) +add_option(VARS "Add additional flags at CXXFLAGS" "" 1) message(STATUS "=================") if(GUI_APP) @@ -69,7 +98,6 @@ if(USE_DCONF) if(DCONF_FOUND) message(STATUS "Found dconf - enabling dconf support") target_compile_definitions(${TARGET_NAME} PRIVATE USE_DCONF=1) - target_link_libraries(${TARGET_NAME} PUBLIC ${DCONF_LIBRARIES}) target_include_directories(${TARGET_NAME} PUBLIC ${DCONF_INCLUDE_DIRS}) target_compile_options(${TARGET_NAME} PUBLIC ${DCONF_CFLAGS_OTHER}) else() @@ -85,8 +113,71 @@ add_library(fmt STATIC "src/libs/fmt/os.cc" "src/libs/fmt/format.cc") +set_target_properties(fmt PROPERTIES POSITION_INDEPENDENT_CODE ON) +enable_lto(fmt) target_link_libraries(${TARGET_NAME} PUBLIC fmt) +# tiny-process-library (integrated from its own CMakeLists.txt) +add_library(tiny-process-library STATIC + "src/libs/tiny-process-library/process.cpp") +add_library(tiny-process-library::tiny-process-library ALIAS tiny-process-library) + +if(MSVC) + target_compile_definitions(tiny-process-library PRIVATE /D_CRT_SECURE_NO_WARNINGS) +else() + target_compile_options(tiny-process-library PRIVATE -std=c++11 -Wall -Wextra) +endif() + +if(WIN32) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + target_sources(tiny-process-library PRIVATE "src/libs/tiny-process-library/process_win.cpp") + # If compiled using MSYS2, use sh to run commands + if(MSYS) + target_compile_definitions(tiny-process-library PUBLIC MSYS_PROCESS_USE_SH) + endif() +else() + target_sources(tiny-process-library PRIVATE "src/libs/tiny-process-library/process_unix.cpp") +endif() + +set_target_properties(tiny-process-library PROPERTIES + POSITION_INDEPENDENT_CODE ON +) + +find_package(Threads REQUIRED) +target_link_libraries(tiny-process-library Threads::Threads) +target_include_directories(tiny-process-library PUBLIC + $ + $ +) +enable_lto(tiny-process-library) +target_link_libraries(${TARGET_NAME} PUBLIC tiny-process-library) + +# libcufetch +set(CUFETCH_HEADERS + include/libcufetch/config.hh + include/libcufetch/common.hh + include/libcufetch/cufetch.hh +) + +add_library(cufetch SHARED + libcufetch/cufetch.cc + libcufetch/parse.cc + src/libs/toml++/toml.cpp + src/util.cpp +) + +set_target_properties(cufetch PROPERTIES + VERSION 1.0.0 + SOVERSION 1 + OUTPUT_NAME "cufetch" + POSITION_INDEPENDENT_CODE ON +) + +target_link_libraries(cufetch PUBLIC tiny-process-library) +target_link_libraries(cufetch PUBLIC fmt) +target_link_libraries(${TARGET_NAME} PUBLIC cufetch) + +# locale add_custom_target(locale COMMAND ${CMAKE_SOURCE_DIR}/scripts/make_mo.sh ${CMAKE_SOURCE_DIR}/locale/ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} @@ -101,10 +192,12 @@ set(APPPREFIX "${CMAKE_INSTALL_PREFIX}/share/applications" CACHE PATH "Applicati set(LOCALEDIR "${CMAKE_INSTALL_PREFIX}/share/locale" CACHE PATH "Locale files directory") set(ICONPREFIX "${CMAKE_INSTALL_PREFIX}/share/pixmaps" CACHE PATH "Icon installation directory") target_compile_definitions(${TARGET_NAME} PRIVATE + VERSION="1.0.0" MANPREFIX="${MANPREFIX}" - APPPREFIX="${APPPREFIX}" + APPPREFIX="${APPPREFIX}" LOCALEDIR="${LOCALEDIR}" ICONPREFIX="${ICONPREFIX}" + $<$:GUI_APP=1> ) # Install executable @@ -124,6 +217,22 @@ install(FILES ${CMAKE_SOURCE_DIR}/LICENSE DESTINATION share/licenses/customfetch COMPONENT documentation) +install(FILES $ + DESTINATION lib + RENAME libcufetch-fmt.a + COMPONENT runtime +) + +install(TARGETS cufetch + LIBRARY DESTINATION lib + COMPONENT runtime +) + +install(DIRECTORY include/libcufetch/ + DESTINATION include/libcufetch + COMPONENT development +) + install(DIRECTORY ${CMAKE_SOURCE_DIR}/assets/ascii/ DESTINATION share/customfetch/ascii COMPONENT runtime) @@ -140,6 +249,15 @@ install(DIRECTORY ${CMAKE_SOURCE_DIR}/locale/ DESTINATION share/locale COMPONENT runtime) +install(CODE " + execute_process( + COMMAND ${CMAKE_COMMAND} -E create_symlink libcufetch.so.1 ${CMAKE_INSTALL_PREFIX}/lib/libcufetch.so + ) + execute_process( + COMMAND ${CMAKE_COMMAND} -E create_symlink libcufetch.so.1.0.0 ${CMAKE_INSTALL_PREFIX}/lib/libcufetch.so.1 + ) +") + install(CODE " execute_process( COMMAND ${CMAKE_SOURCE_DIR}/scripts/make_mo.sh ${CMAKE_SOURCE_DIR}/locale/ diff --git a/Makefile b/Makefile index 5c93acc4..e8c169d3 100644 --- a/Makefile +++ b/Makefile @@ -6,19 +6,30 @@ APPPREFIX ?= $(PREFIX)/share/applications LOCALEDIR ?= $(PREFIX)/share/locale ICONPREFIX ?= $(PREFIX)/share/pixmaps VARS ?= -DENABLE_NLS=1 +CXXSTD ?= c++20 DEBUG ?= 1 GUI_APP ?= 0 -VENDOR_TEST ?= 0 -DEVICE_TEST ?= 0 - USE_DCONF ?= 1 + +COMPILER := $(shell $(CXX) --version | head -n1) + +ifeq ($(findstring GCC,$(COMPILER)),GCC) + export LTO_FLAGS = -flto=auto -ffat-lto-objects +else ifeq ($(findstring clang,$(COMPILER)),clang) + export LTO_FLAGS = -flto=thin +else + $(warning Unknown compiler: $(COMPILER). No LTO flags applied.) +endif + # https://stackoverflow.com/a/1079861 # WAY easier way to build debug and release builds ifeq ($(DEBUG), 1) - BUILDDIR = build/debug - CXXFLAGS := -ggdb3 -Wall -Wextra -Wpedantic -Wno-unused-parameter -DDEBUG=1 -fsanitize=address $(DEBUG_CXXFLAGS) $(CXXFLAGS) - LDFLAGS += -fsanitize=address + BUILDDIR := build/debug + LTO_FLAGS = -fno-lto + CXXFLAGS := -ggdb3 -Wall -Wextra -pedantic -Wno-unused-parameter -fsanitize=address \ + -DDEBUG=1 -fno-omit-frame-pointer $(DEBUG_CXXFLAGS) $(CXXFLAGS) + LDFLAGS += -fsanitize=address -fno-lto -Wl,-rpath,$(BUILDDIR) else # Check if an optimization flag is not already set ifneq ($(filter -O%,$(CXXFLAGS)),) @@ -26,15 +37,8 @@ else else CXXFLAGS := -O3 $(CXXFLAGS) endif - BUILDDIR = build/release -endif - -ifeq ($(VENDOR_TEST), 1) - VARS += -DVENDOR_TEST=1 -endif - -ifeq ($(DEVICE_TEST), 1) - VARS += -DDEVICE_TEST=1 + LDFLAGS += $(LTO_FLAGS) + BUILDDIR := build/release endif ifeq ($(GUI_APP), 1) @@ -56,30 +60,47 @@ NAME = customfetch TARGET ?= $(NAME) OLDVERSION = 0.10.2 VERSION = 1.0.0 -SRC = $(wildcard src/*.cpp src/query/linux/*.cpp src/query/android/*.cpp src/query/macos/*.cpp src/query/linux/utils/*.cpp) -OBJ = $(SRC:.cpp=.o) -LDFLAGS += -L./$(BUILDDIR)/fmt -lfmt -ldl +SRC_CPP = $(wildcard src/*.cpp) +SRC_CC = $(wildcard src/core-modules/*.cc src/core-modules/linux/*.cc src/core-modules/linux/utils/*.cc src/core-modules/android/*.cc src/core-modules/macos/*.cc) +OBJ_CPP = $(SRC_CPP:.cpp=.o) +OBJ_CC = $(SRC_CC:.cc=.o) +OBJ = $(OBJ_CPP) $(OBJ_CC) +LDFLAGS += -L$(BUILDDIR) +LDLIBS += $(BUILDDIR)/libfmt.a $(BUILDDIR)/libtiny-process-library.a -lcufetch -ldl CXXFLAGS ?= -mtune=generic -march=native -CXXFLAGS += -fvisibility=hidden -Iinclude -std=c++20 $(VARS) -DVERSION=\"$(VERSION)\" -DLOCALEDIR=\"$(LOCALEDIR)\" -DICONPREFIX=\"$(ICONPREFIX)\" +CXXFLAGS += $(LTO_FLAGS) -fvisibility-inlines-hidden -fvisibility=hidden -Iinclude -Iinclude/libcufetch -Iinclude/libs -std=$(CXXSTD) $(VARS) -DVERSION=\"$(VERSION)\" -DLOCALEDIR=\"$(LOCALEDIR)\" -DICONPREFIX=\"$(ICONPREFIX)\" + +all: genver fmt toml tpl getopt-port json libcufetch $(TARGET) -all: genver fmt toml json $(TARGET) +libcufetch: fmt tpl toml +ifeq ($(wildcard $(BUILDDIR)/libcufetch.so),) + $(MAKE) -C libcufetch BUILDDIR=$(BUILDDIR) GUI_APP=$(GUI_APP) CXXSTD=$(CXXSTD) +endif fmt: -ifeq ($(wildcard $(BUILDDIR)/fmt/libfmt.a),) - mkdir -p $(BUILDDIR)/fmt - make -C src/libs/fmt BUILDDIR=$(BUILDDIR)/fmt +ifeq ($(wildcard $(BUILDDIR)/libfmt.a),) + mkdir -p $(BUILDDIR) + $(MAKE) -C src/libs/fmt BUILDDIR=$(BUILDDIR) CXXSTD=$(CXXSTD) endif toml: -ifeq ($(wildcard $(BUILDDIR)/toml++/toml.o),) - mkdir -p $(BUILDDIR)/toml++ - make -C src/libs/toml++ BUILDDIR=$(BUILDDIR)/toml++ +ifeq ($(wildcard $(BUILDDIR)/toml.o),) + $(MAKE) -C src/libs/toml++ BUILDDIR=$(BUILDDIR) CXXSTD=$(CXXSTD) +endif + +tpl: +ifeq ($(wildcard $(BUILDDIR)/libtiny-process-library.a),) + $(MAKE) -C src/libs/tiny-process-library BUILDDIR=$(BUILDDIR) CXXSTD=$(CXXSTD) +endif + +getopt-port: +ifeq ($(wildcard $(BUILDDIR)/getopt.o),) + $(MAKE) -C src/libs/getopt_port BUILDDIR=$(BUILDDIR) endif json: -ifeq ($(wildcard $(BUILDDIR)/json/json.o),) - mkdir -p $(BUILDDIR)/json - make -C src/libs/json BUILDDIR=$(BUILDDIR)/json +ifeq ($(wildcard $(BUILDDIR)/json.o),) + $(MAKE) -C src/libs/json BUILDDIR=$(BUILDDIR) CXXSTD=$(CXXSTD) endif genver: ./scripts/generateVersion.sh @@ -87,10 +108,10 @@ ifeq ($(wildcard include/version.h),) ./scripts/generateVersion.sh endif -$(TARGET): genver fmt toml json $(OBJ) +$(TARGET): genver fmt toml tpl getopt-port json libcufetch $(OBJ) mkdir -p $(BUILDDIR) sh ./scripts/generateVersion.sh - $(CXX) $(OBJ) $(BUILDDIR)/toml++/toml.o $(BUILDDIR)/json/json.o -o $(BUILDDIR)/$(TARGET) $(LDFLAGS) + $(CXX) -o $(BUILDDIR)/$(TARGET) $(OBJ) $(BUILDDIR)/*.o $(LDFLAGS) $(LDLIBS) cd $(BUILDDIR)/ && ln -sf $(TARGET) cufetch locale: @@ -105,24 +126,24 @@ endif usr-dist: $(TARGET) locale mkdir -p $(PWD)/usr - make install DESTDIR=$(PWD) PREFIX=/usr + $(MAKE) install DESTDIR=$(PWD) PREFIX=/usr $(TAR) -zcf $(NAME)-v$(VERSION).tar.gz usr/ rm -rf usr/ clean: - rm -rf $(BUILDDIR)/$(TARGET) $(BUILDDIR)/lib$(NAME).a $(OBJ) + rm -rf $(BUILDDIR)/$(TARGET) $(BUILDDIR)/libcufetch.so $(OBJ) libcufetch/*.o distclean: rm -rf $(BUILDDIR) ./tests/$(BUILDDIR) $(OBJ) - find . -type f -name "*.tar.gz" -exec rm -rf "{}" \; - find . -type f -name "*.o" -exec rm -rf "{}" \; - find . -type f -name "*.a" -exec rm -rf "{}" \; + find . -type f -name "*.tar.gz" -delete + find . -type f -name "*.o" -delete + find . -type f -name "*.a" -delete install: install-common $(TARGET) install $(BUILDDIR)/$(TARGET) -Dm 755 -v $(DESTDIR)$(PREFIX)/bin/$(TARGET) cd $(DESTDIR)$(PREFIX)/bin/ && ln -sf $(TARGET) cufetch -install-common: locale +install-common: libcufetch locale mkdir -p $(DESTDIR)$(MANPREFIX)/man1/ sed -e "s/@VERSION@/$(VERSION)/g" -e "s/@BRANCH@/$(BRANCH)/g" < $(NAME).1 > $(DESTDIR)$(MANPREFIX)/man1/$(NAME).1 chmod 644 $(DESTDIR)$(MANPREFIX)/man1/$(NAME).1 @@ -131,6 +152,10 @@ install-common: locale cd assets/icons && find . -type f -exec install -Dm 644 "{}" "$(DESTDIR)$(ICONPREFIX)/$(NAME)/{}" \; find examples/ -type f -exec install -Dm 644 "{}" "$(DESTDIR)$(PREFIX)/share/$(NAME)/{}" \; find locale/ -type f -exec install -Dm 644 "{}" "$(DESTDIR)$(PREFIX)/share/{}" \; + mkdir -p $(DESTDIR)$(PREFIX)/include/libcufetch/ + cd include/libcufetch && find . -type f -exec install -Dm 644 "{}" "$(DESTDIR)$(PREFIX)/include/libcufetch/{}" \; + install -Dm 755 $(BUILDDIR)/libcufetch.so $(DESTDIR)$(PREFIX)/lib/libcufetch.so.1 + install -Dm 755 $(BUILDDIR)/libfmt.a $(DESTDIR)$(PREFIX)/lib/libcufetch-fmt.a ifeq ($(GUI_APP), 1) mkdir -p $(DESTDIR)$(APPPREFIX) cp -f $(NAME).desktop $(DESTDIR)$(APPPREFIX)/$(NAME).desktop @@ -142,6 +167,7 @@ uninstall: rm -f $(DESTDIR)$(APPPREFIX)/$(NAME).desktop rm -rf $(DESTDIR)$(PREFIX)/share/licenses/$(NAME)/ rm -rf $(DESTDIR)$(PREFIX)/share/$(NAME)/ + rm -rf $(DESTDIR)$(PREFIX)/include/cufetch/ rm -rf $(DESTDIR)$(ICONPREFIX)/$(NAME)/ find $(DESTDIR)$(LOCALEDIR) -type f -path "$(DESTDIR)$(LOCALEDIR)/*/LC_MESSAGES/$(NAME).mo" -exec rm -f {} \; @@ -152,4 +178,4 @@ updatever: sed -i "s#$(OLDVERSION)#$(VERSION)#g" $(wildcard .github/workflows/*.yml) compile_flags.txt sed -i "s#Project-Id-Version: $(NAME) $(OLDVERSION)#Project-Id-Version: $(NAME) $(VERSION)#g" po/* -.PHONY: $(TARGET) updatever remove uninstall delete dist distclean fmt toml install all locale +.PHONY: $(TARGET) updatever remove uninstall delete dist distclean fmt toml libcufetch install all locale diff --git a/README.md b/README.md index 2b91b82f..a0f45b54 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

- A system information fetch tool (or neofetch-like program), which its focus point is the performance and customizability + A modular information fetching (neofetch-like) tool, which its focus point is the performance and customizability

@@ -34,8 +34,8 @@ ## Key Features * Run customfetch as a **terminal** or **GTK3 application** or even as an [android widget](https://github.com/Toni500github/customfetch-android-app) -* Really easy to customize (check [Config (with explanation)](#config-with-explanation) section) -* Fast (maybe) as [fastfetch](https://github.com/fastfetch-cli/fastfetch) +* Really easy to [customize](#How-to-customize) +* Incredibly extensible information fetchings via external plugins * Super lightweight, 3.3MB max (GTK3 application) # Dependencies @@ -73,7 +73,7 @@ yay -S customfetch-gui-bin ## General Distros (Manual installation) Download the latest `.tar.gz` tarball file in [releases](https://github.com/Toni500github/customfetch/releases/latest) \ -It contains the `/usr` directory where you'll install it in your distro. Useful for package managers too +It contains the `/usr` directory where you'll install it in your distro. Useful for package managers too. ## Arch and based (AUR) (source) ```bash @@ -96,7 +96,7 @@ yay -S customfetch-gui-git ## Compile from (source) (unstable) ```bash # clone the git dir -git clone https://github.com/Toni500github/customfetch +git clone --depth=1 https://github.com/Toni500github/customfetch cd customfetch # DEBUG=0 for release build @@ -107,7 +107,7 @@ make install DEBUG=0 GUI_APP=0 customfetch ``` -## Config (with explanation) +## How to customize Read the manual `customfetch.1` or execute customfetch with the arg `-w` for knowing more about the configuration in customfetch.\ This is only an explaination about tags and preview, that can be always found in the documentation. @@ -122,7 +122,7 @@ Here's an example using my config # The array for displaying the system infos layout = [ "$", - "$<title_sep>", + "$<title.sep>", "${auto}OS: $<os.name> $<system.arch>", "${auto}Host: $<system.host>", "${auto}Kernel: $<os.kernel>", @@ -130,12 +130,12 @@ layout = [ "${auto}Terminal: $<user.terminal>", "${auto}Shell: $<user.shell>", "${auto}Packages: $<os.pkgs>", - "${auto}Theme: $<theme-gtk-all.name>", - "${auto}Icons: $<theme-gtk-all.icons>", - "${auto}Font: $<theme-gtk-all.font>", + "${auto}Theme: $<theme.gtk.all.name>", + "${auto}Icons: $<theme.gtk.all.icons>", + "${auto}Font: $<theme.gtk.all.font>", "${auto}Cursor: $<theme.cursor>", - "${auto}WM: $<user.wm_name> $<user.wm_version>", - "${auto}DE: $<user.de_name> $<user.de_version>", + "${auto}WM: $<user.wm.name> $<user.wm.version>", + "${auto}DE: $<user.de.name> $<user.de.version>", "$<auto.disk>", "${auto}Swap: $<swap>", "${auto}CPU: $<cpu>", @@ -143,7 +143,7 @@ layout = [ "${auto}RAM: $<ram>", "", "$<colors>", # normal colors palette - "$<colors_light>" # light colors palette + "$<colors.light>" # light colors palette ] @@ -151,16 +151,16 @@ layout = [ In the config we got an array variable called "layout". That's the variable where you customize how the infos should be displayed.\ There are 5 tags: -* `$<module.member>` - Used for printing the system info value of a member of a module. -* `${color}` - Used for displaying text in a specific color. +* `$<info.module>` - Used for printing the value of a module or its submembers. +* `${color}` - Used for displaying text in a specific color after it. * `$(bash command)` - Used to execute bash commands and print the output. * `$[something,equalToSomethingElse,iftrue,ifalse]` - Conditional tag to display different outputs based on the comparison. * `$%n1,n2%` - Used to print the percentage and print with colors They can be used in the ascii art text file and layout, but how to use them? -* **The info tag (`$<>`)** will print a value of a member of a module\ - e.g `$<user.name>` will print the username, `$<os.kernel_version>` will print the kernel version and so on.\ +* **The info tag (`$<>`)** will print a value of a member of a module.\ + e.g `$<user.name>` will print the username, `$<os.kernel.version>` will print the kernel version and so on.\ All the modules and their members are listed in the `--list-modules` argument * **The bash command tag (`$()`)** let's you execute bash commands and print the output\ @@ -214,8 +214,8 @@ They can be used in the ascii art text file and layout, but how to use them? Any `$` or brackets can be escaped with a backslash `\`. You need to escape backslashes too :(\ **NOTE:** For having compatibility with the GUI app, you need to escape `<` (EXCEPT if you are using in a info tag, like `$<os.name>`) and `&`\ -e.g `the number 50 is \< than 100 \& 98` -Won't affect the printing in terminal +e.g `the number 50 is \\< than 100 \\&\\& 98` +Won't affect the printing in terminal. ## Star History @@ -228,8 +228,8 @@ Won't affect the printing in terminal </a> # TODOs -* ~~release v1.0.0~~ -Empty so far! +* release v2.0.0 +* work on the android app (later) # Thanks I would like to thanks: diff --git a/compile_flags.txt b/compile_flags.txt index aadd9066..c08552c6 100644 --- a/compile_flags.txt +++ b/compile_flags.txt @@ -1,4 +1,5 @@ -I./include +-I./include/libs -Wall -Wextra -Wpedantic diff --git a/cufetchpm/Makefile b/cufetchpm/Makefile new file mode 100644 index 00000000..4b2cf7bc --- /dev/null +++ b/cufetchpm/Makefile @@ -0,0 +1,73 @@ +CXX ?= g++ +PREFIX ?= /usr +MANPREFIX ?= $(PREFIX)/share/man +VARS ?= -DENABLE_NLS=1 +CXXSTD ?= c++20 + +DEBUG ?= 1 +# https://stackoverflow.com/a/1079861 +# WAY easier way to build debug and release builds +ifeq ($(DEBUG), 1) + BUILDDIR = build/debug + CXXFLAGS := -ggdb3 -Wall -Wextra -pedantic -Wno-unused-parameter -fsanitize=address \ + -DDEBUG=1 -fno-omit-frame-pointer $(DEBUG_CXXFLAGS) $(CXXFLAGS) + LDFLAGS += -fsanitize=address -fno-lto +else + # Check if an optimization flag is not already set + ifneq ($(filter -O%,$(CXXFLAGS)),) + $(info Keeping the existing optimization flag in CXXFLAGS) + else + CXXFLAGS := -O3 $(CXXFLAGS) + endif + BUILDDIR = build/release +endif + +NAME = cufetchpm +TARGET ?= $(NAME) +OLDVERSION = 0.0.0 +VERSION = 0.0.1 +SRC = $(wildcard src/*.cpp src/manager/*.cpp ../src/util.cpp) +OBJ = $(SRC:.cpp=.o) +LDLIBS += $(BUILDDIR)/libfmt.a $(BUILDDIR)/libtiny-process-library.a +CXXFLAGS ?= -mtune=generic -march=native +CXXFLAGS += -fvisibility=hidden -I../include -I../include/libs/ -Iinclude -std=c++20 $(VARS) -DVERSION=\"$(VERSION)\" + +all: fmt toml getopt-port tpl $(TARGET) + +fmt: +ifeq ($(wildcard $(BUILDDIR)/libfmt.a),) + mkdir -p $(BUILDDIR) + make -C ../src/libs/fmt BUILDDIR=cufetchpm/$(BUILDDIR) CXXSTD=$(CXXSTD) +endif + +toml: +ifeq ($(wildcard $(BUILDDIR)/toml.o),) + mkdir -p $(BUILDDIR) + make -C ../src/libs/toml++ BUILDDIR=cufetchpm/$(BUILDDIR) CXXSTD=$(CXXSTD) +endif + +getopt-port: +ifeq ($(wildcard $(BUILDDIR)/getopt.o),) + mkdir -p $(BUILDDIR) + make -C ../src/libs/getopt_port BUILDDIR=cufetchpm/$(BUILDDIR) +endif + +tpl: +ifeq ($(wildcard $(BUILDDIR)/libtiny-process-library.a),) + mkdir -p $(BUILDDIR) + make -C ../src/libs/tiny-process-library BUILDDIR=cufetchpm/$(BUILDDIR) CXXSTD=$(CXXSTD) +endif + +$(TARGET): fmt toml getopt-port tpl $(OBJ) + mkdir -p $(BUILDDIR) + $(CXX) $(OBJ) $(BUILDDIR)/*.o -o $(BUILDDIR)/$(TARGET) $(LDFLAGS) $(LDLIBS) + +clean: + rm -rf $(BUILDDIR)/$(TARGET) $(OBJ) + +distclean: + find .. -type f -name "*.tar.gz" -exec rm -rf "{}" \; + find .. -type f -name "*.o" -exec rm -rf "{}" \; + find .. -type f -name "*.a" -exec rm -rf "{}" \; + +.PHONY: $(TARGET) fmt toml all diff --git a/cufetchpm/compile_flags.txt b/cufetchpm/compile_flags.txt new file mode 100644 index 00000000..abd51790 --- /dev/null +++ b/cufetchpm/compile_flags.txt @@ -0,0 +1,13 @@ +-I../include +-I../include/libs +-I../src/libs/ +-Iinclude +-Wall +-Wextra +-Wpedantic +-std=c++20 +-DVERSION="1.0.0" +-DGUI_APP=1 +-DUSE_DCONF=1 +-DDEBUG=1 +-DENABLE_NLS=1 diff --git a/cufetchpm/include/manifest.hpp b/cufetchpm/include/manifest.hpp new file mode 100644 index 00000000..3c47d76f --- /dev/null +++ b/cufetchpm/include/manifest.hpp @@ -0,0 +1,156 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _MANIFEST_HPP_ +#define _MANIFEST_HPP_ + +#include <filesystem> +#include <string> +#include <string_view> +#include <vector> + +#include "platform.hpp" +#include "toml++/toml.hpp" + +#if CF_LINUX +constexpr char const PLATFORM[] = "linux"; +#elif CF_MACOS +constexpr char const PLATFORM[] = "macos"; +#elif CF_ANDROID +constexpr char const PLATFORM[] = "android"; +#endif + +struct plugin_t +{ + // The plugin name. + // It must be conform to the function is_valid_name() + std::string name; + + // The plugin description. + std::string description; + + // The plugin build directory, + // where we'll retrive the built plugin libraries. + std::string output_dir; + + // The plugin multiple SPDX License Identifier (MIT, GPL-2.0, ...) + // NOTE: it doesn't actually check if they are correct or not. + std::vector<std::string> licenses; + + // The plugin authors. + std::vector<std::string> authors; + + // A list of commands to be executed for building the plugin. + // Kinda like a Makefile target instructions. + // Each command will be executed from a different shell session. + std::vector<std::string> build_steps; + + // A list of registered root modules that the plugin will be used for querying its submodules. + // For example: 'github.followers' the root module is indeed 'github' and 'followers' is the submodule. + std::vector<std::string> prefixes; + + // Platforms that are supported by the plugin. + // Make it a string and put 'all' for being cross-platform. + std::vector<std::string> platforms; +}; + +struct manifest_t +{ + // The repository name. + // It must be conform to the function is_valid_name() + std::string name; + + // The repository git url + std::string url; + + // NOTE: INTERNAL ONLY + // The repository latest commit hash. + std::string git_hash; + + // An array of all the plugins that are declared in the manifest + std::vector<plugin_t> plugins; + + // An array for storing the dependencies for 'all' and current platforms. + // first -> platform string name + // seconds -> platform dependencies vector names + std::vector<std::string> dependencies; +}; + +constexpr char const MANIFEST_NAME[] = "cufetchpm.toml"; + +namespace ManifestSpace +{ +std::string getStrValue(const toml::table& tbl, const std::string_view name, const std::string_view key); +std::string getStrValue(const toml::table& tbl, const std::string_view path); +std::vector<std::string> getStrArrayValue(const toml::table& tbl, const std::string_view name, + const std::string_view value); +std::vector<std::string> getStrArrayValue(const toml::table& tbl, const std::string_view path); +} // namespace ManifestSpace + +class CManifest +{ +public: + CManifest(const std::filesystem::path& path); + + plugin_t get_plugin(const std::string_view name); + + const std::string& get_repo_name() const + { return m_repo.name; } + + const std::string& get_repo_url() const + { return m_repo.url; } + + const std::string& get_repo_hash() const + { return m_repo.git_hash; } + + const std::vector<plugin_t>& get_all_plugins() const + { return m_repo.plugins; } + + const std::vector<std::string>& get_dependencies() const + { return m_repo.dependencies; } + +private: + toml::table m_tbl; + manifest_t m_repo; + + void parse_manifest(const std::filesystem::path& path); + + std::string getStrValue(const std::string_view name, const std::string_view key) const + { + return ManifestSpace::getStrValue(m_tbl, name, key); + } + + std::string getStrValue(const std::string_view path) const + { + return ManifestSpace::getStrValue(m_tbl, path); + } + + std::vector<std::string> getStrArrayValue(const std::string_view name, const std::string_view value) const + { + return ManifestSpace::getStrArrayValue(m_tbl, name, value); + } +}; + +#endif // !_MANIFEST_HPP_; diff --git a/cufetchpm/include/pluginManager.hpp b/cufetchpm/include/pluginManager.hpp new file mode 100644 index 00000000..9b2b871c --- /dev/null +++ b/cufetchpm/include/pluginManager.hpp @@ -0,0 +1,78 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _PLUGIN_MANAGER_HPP_ +#define _PLUGIN_MANAGER_HPP_ + +#include <filesystem> +#include <string> +#include <string_view> +#include <utility> + +#include "stateManager.hpp" +#include "util.hpp" + +namespace fs = std::filesystem; + +inline struct operations_t +{ + bool install_force = false; + bool install_shut_up = false; + bool list_verbose = false; + std::vector<std::string> arguments; +} options; + +template <typename... Args> +void success(const std::string_view fmt, Args&&... args) noexcept +{ + fmt::print("\033[1;32m==> {}\033[0m\n", fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); +} + +template <typename... Args> +void status(const std::string_view fmt, Args&&... args) noexcept +{ + fmt::print("\033[1;34m==> {} ...\033[0m\n", fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); +} + +class PluginManager +{ +public: + PluginManager(const StateManager& state_manager) : m_state_manager(state_manager) {} + PluginManager(StateManager&& state_manager) : m_state_manager(std::move(state_manager)) {} + + void add_plugins_repo(const std::string& repo); + void build_plugins(const fs::path& working_dir); + bool add_plugin(const std::string&); + void update_repos(); + bool is_plugin_conflicting(const plugin_t& plugin); + void remove_repo(const std::string& repo_name); + +private: + StateManager m_state_manager; + fs::path m_config_path{ getConfigDir() / "plugins" }; + fs::path m_cache_path{ getHomeCacheDir() / "cufetchpm" }; +}; + +#endif diff --git a/cufetchpm/include/stateManager.hpp b/cufetchpm/include/stateManager.hpp new file mode 100644 index 00000000..a5d25a95 --- /dev/null +++ b/cufetchpm/include/stateManager.hpp @@ -0,0 +1,91 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _STATE_MANAGER_HPP_ +#define _STATE_MANAGER_HPP_ + +#include <filesystem> + +#include "manifest.hpp" +#include "toml++/toml.hpp" +#include "util.hpp" + +namespace fs = std::filesystem; + +bool writeState(const std::string& str, const std::string& to); +class StateManager +{ +public: + StateManager(); + StateManager(StateManager&&) = default; + StateManager(const StateManager&) = default; + ~StateManager() = default; + + void add_new_repo(const CManifest& manifest); + void remove_repo(const std::string& repo); + std::vector<manifest_t> get_all_repos(); + + template <typename T> + void insert_or_assign_at_plugin(const std::string_view repo_name, const std::string_view plugin_name, + const std::string_view key, T&& value); + + const toml::table& get_state() const + { return m_state; } + +private: + const fs::path m_path{ getConfigDir() / "plugins" / "state.toml" }; + toml::table m_state; +}; + +template <typename T> +void StateManager::insert_or_assign_at_plugin(const std::string_view repo_name, const std::string_view plugin_name, + const std::string_view key, T&& value) +{ + auto* repo_plugins_arr = m_state["repositories"][repo_name]["plugins"].as_array(); + if (!repo_plugins_arr) + die("Couldn't find an array of plugins from repository '{}'", repo_name); + + for (auto&& plugins_node : *repo_plugins_arr) + { + auto* plugin_tbl = plugins_node.as_table(); + if (!plugin_tbl) + continue; + const std::string& name = ManifestSpace::getStrValue(*plugin_tbl, "name"); + if (name != plugin_name) + continue; + + (*plugin_tbl).insert_or_assign(key, std::forward<T>(value)); + std::stringstream ss; + ss << "# AUTO-GENERATED FILE. DO NOT EDIT THIS FILE.\n"; + ss << "# YOU GONNA MESS SHIT UP. unless you know what you doing ofc\n"; + ss << m_state; + + if (!writeState(ss.str(), m_path)) + die("Failed to write plugin state of repository '{}'", repo_name); + break; + } +} + +#endif diff --git a/cufetchpm/src/main.cpp b/cufetchpm/src/main.cpp new file mode 100644 index 00000000..1bed2731 --- /dev/null +++ b/cufetchpm/src/main.cpp @@ -0,0 +1,440 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <cstdlib> +#include <filesystem> +#include <unordered_map> + +#include "fmt/compile.h" +#include "fmt/os.h" +#include "fmt/ranges.h" +#include "libcufetch/common.hh" +#include "manifest.hpp" +#include "pluginManager.hpp" +#include "stateManager.hpp" +#include "texts.hpp" +#include "util.hpp" + +#if (!__has_include("version.h")) +#error "version.h not found, please generate it with ../scripts/generateVersion.sh" +#else +#include "version.h" +#endif + +#include "getopt_port/getopt.h" + +enum OPs +{ + NONE, + INSTALL, + UPDATE, + LIST, + ENABLE, + DISABLE, + UNINSTALL, + GEN_MANIFEST, + HELP +} op = NONE; + +const std::unordered_map<std::string_view, OPs> map{ + { "install", INSTALL }, { "update", UPDATE }, { "list", LIST }, { "help", HELP }, + { "enable", ENABLE }, { "disable", DISABLE }, { "uninstall", UNINSTALL }, { "gen-manifest", GEN_MANIFEST }, +}; + +OPs str_to_enum(const std::string_view name) +{ + if (auto it = map.find(name); it != map.end()) + return it->second; + return NONE; +} + +void version() +{ + fmt::print( + "cufetchpm {} built from branch '{}' at {} commit '{}' ({}).\n" + "Date: {}\n" + "Tag: {}\n", + VERSION, GIT_BRANCH, GIT_DIRTY, GIT_COMMIT_HASH, GIT_COMMIT_MESSAGE, GIT_COMMIT_DATE, GIT_TAG); + + // if only everyone would not return error when querying the program version :( + std::exit(EXIT_SUCCESS); +} + +void help(int invalid_opt = false) +{ + fmt::print(FMT_COMPILE("{}"), cufetchpm_help); + + std::exit(invalid_opt); +} + +void help_install(int invalid_opt = false) +{ + fmt::print(FMT_COMPILE("{}"), cufetchpm_help_install); + + std::exit(invalid_opt); +} + +void help_list(int invalid_opt = false) +{ + fmt::print(FMT_COMPILE("{}"), cufetchpm_help_list); + + std::exit(invalid_opt); +} + +bool parse_install_args(int argc, char* argv[]) +{ + // clang-format off + const struct option long_opts[] = { + {"force", no_argument, nullptr, 'f'}, + {"help", no_argument, nullptr, 'h'}, + {"yes", no_argument, nullptr, 'y'}, + {0, 0, 0, 0} + }; + // clang-format on + + int opt; + while ((opt = getopt_long(argc, argv, "+wfh", long_opts, nullptr)) != -1) + { + switch (opt) + { + case 'h': help_install(EXIT_SUCCESS); break; + case '?': help_install(EXIT_FAILURE); break; + + case 'f': options.install_force = true; break; + case 'y': options.install_shut_up = true; break; + } + } + + for (int i = optind; i < argc; ++i) + options.arguments.emplace_back(argv[i]); + + if (options.arguments.empty()) + die("install: no repositories/paths given"); + + return true; +} + +bool parse_list_args(int argc, char* argv[]) +{ + // clang-format off + const struct option long_opts[] = { + {"verbose", no_argument, nullptr, 'v'}, + {"help", no_argument, nullptr, 'h'}, + {0, 0, 0, 0} + }; + // clang-format on + + int opt; + while ((opt = getopt_long(argc, argv, "+vh", long_opts, nullptr)) != -1) + { + switch (opt) + { + case 'v': options.list_verbose = true; break; + case 'h': help_list(EXIT_SUCCESS); break; + case '?': help_list(EXIT_FAILURE); break; + } + } + + return true; +} + +bool parse_general_command_args(int argc, char* argv[]) +{ + // clang-format off + const struct option long_opts[] = { + {"help", no_argument, nullptr, 'h'}, + {0, 0, 0, 0} + }; + // clang-format on + + int opt; + while ((opt = getopt_long(argc, argv, "+h", long_opts, nullptr)) != -1) + { + switch (opt) + { + case 'h': help(EXIT_SUCCESS); break; + case '?': help_install(EXIT_FAILURE); break; + } + } + + for (int i = optind; i < argc; ++i) + options.arguments.emplace_back(argv[i]); + + return true; +} + +static bool parseargs(int argc, char* argv[]) +{ + // clang-format off + int opt = 0; + int option_index = 0; + const char *optstring = "+Vh"; + static const struct option opts[] = { + {"version", no_argument, 0, 'V'}, + {"help", no_argument, 0, 'h'}, + + {0,0,0,0} + }; + + // clang-format on + optind = 1; + while ((opt = getopt_long(argc, argv, optstring, opts, &option_index)) != -1) + { + switch (opt) + { + case 0: break; + case '?': help(EXIT_FAILURE); break; + + case 'V': version(); break; + case 'h': help(); break; + default: return false; + } + } + + if (optind >= argc) + help(EXIT_FAILURE); // no subcommand + + std::string_view cmd = argv[optind]; + int sub_argc = argc - optind - 1; + char** sub_argv = argv + optind + 1; + + op = str_to_enum(cmd); + switch (op) + { + case INSTALL: optind = 0; return parse_install_args(sub_argc, sub_argv); + case LIST: optind = 0; return parse_list_args(sub_argc, sub_argv); + case HELP: break; + default: optind = 0; return parse_general_command_args(sub_argc, sub_argv); + } + + if (op == HELP) + { + if (sub_argc >= 1) + { + std::string_view target = sub_argv[0]; + if (target == "install") + help_install(); + else if (target == "list") + help_list(); + else + die("Couldn't find help text for subcommand '{}'", cmd); + } + else + { + help(EXIT_FAILURE); + } + } + + return true; +} + +void switch_plugin(StateManager&& state, bool switch_) +{ + const char* switch_str = switch_ ? "Enabl" : "Disabl"; // e/ed/ing + const toml::table& tbl = state.get_state(); + + for (const std::string& arg : options.arguments) + { + const size_t pos = arg.find('/'); + if (pos == arg.npos) + die("Plugin to {}e '{}' doesn't have a slash '/' to separate repository and plugin", switch_str, arg); + + const std::string& repo = arg.substr(0, pos); + const std::string& plugin = arg.substr(pos + 1); + + const auto* repo_tbl = tbl["repositories"][repo].as_table(); + if (!repo_tbl) + die("No such repository '{}'", repo); + if (const auto* plugins_arr_tbl = repo_tbl->get_as<toml::array>("plugins")) + { + for (const auto& plugin_node : *plugins_arr_tbl) + { + const toml::table* plugin_tbl = plugin_node.as_table(); + if (!plugin_tbl || ManifestSpace::getStrValue(*plugin_tbl, "name") != plugin) + continue; + + for (fs::path base_path : ManifestSpace::getStrArrayValue(*plugin_tbl, "libraries")) + { + if (base_path.extension() == ".disabled") + base_path.replace_extension(); // normalize to enabled form + + const fs::path& enabled_path = base_path; + const fs::path& disabled_path = base_path.string() + ".disabled"; + + fs::path current_path; + if (fs::exists(enabled_path)) + current_path = enabled_path; + else if (fs::exists(disabled_path)) + current_path = disabled_path; + else + { + warn("Plugin library '{}' not found. Skipping", base_path.string()); + continue; + } + + const fs::path& target_path = switch_ ? enabled_path : disabled_path; + if (current_path == target_path) + { + warn("{} is already {}ed", arg, switch_str); + continue; + } + + fs::rename(current_path, target_path); + info("{}ed {}!", switch_str, arg); + } + } + } + } +} + +void list_all_plugins(StateManager&& state) +{ + const auto& is_plugin_disabled = [&](const std::string& manifest_name, const std::string& plugin_name) { + const auto* repo_tbl = state.get_state()["repositories"][manifest_name].as_table(); + if (!repo_tbl) + die("No such repository '{}'", manifest_name); + if (const auto* plugins_arr_tbl = repo_tbl->get_as<toml::array>("plugins")) + { + for (const auto& plugin_node : *plugins_arr_tbl) + { + const toml::table* plugin_tbl = plugin_node.as_table(); + if (!plugin_tbl || ManifestSpace::getStrValue(*plugin_tbl, "name") != plugin_name) + continue; + + for (fs::path base_path : ManifestSpace::getStrArrayValue(*plugin_tbl, "libraries")) + if (fs::exists(base_path += ".disabled")) + return true; + } + } + return false; + }; + + if (options.list_verbose) + { + for (const manifest_t& manifest : state.get_all_repos()) + { + fmt::println("\033[1;32mRepository:\033[0m {}", manifest.name); + fmt::println("\033[1;33mURL:\033[0m {}", manifest.url); + fmt::println("\033[1;34mPlugins:"); + for (const plugin_t& plugin : manifest.plugins) + { + fmt::println("\033[1;34m - {}\033[0m", plugin.name); + fmt::println("\t\033[1;35mDescription:\033[0m {}", plugin.description); + fmt::println("\t\033[1;36mAuthor(s):\033[0m {}", fmt::join(plugin.authors, ", ")); + fmt::println("\t\033[1;38;2;255;100;220mDisabled:\033[0m {}", + is_plugin_disabled(manifest.name, plugin.name)); + fmt::println("\t\033[1;38;2;220;220;220mLicense(s):\033[0m {}", fmt::join(plugin.licenses, ", ")); + fmt::println("\t\033[1;38;2;144;238;144mPrefixe(s):\033[0m {}", fmt::join(plugin.prefixes, ", ")); + } + fmt::print("\033[0m"); + } + } + else + { + for (const manifest_t& manifest : state.get_all_repos()) + { + fmt::println("\033[1;32mRepository:\033[0m {} (\033[1;33m{}\033[0m)", manifest.name, manifest.url); + fmt::println("\033[1;34mPlugins:"); + for (const plugin_t& plugin : manifest.plugins) + { + fmt::print(" \033[1;34m{} - \033[1;35m{}", plugin.name, plugin.description); + if (is_plugin_disabled(manifest.name, plugin.name)) + fmt::print(" \033[1;31m(DISABLED)"); + fmt::print("\n"); + } + fmt::print("\033[0m"); + } + } +} + +int main(int argc, char* argv[]) +{ + if (!parseargs(argc, argv)) + return -1; + + fs::create_directories({ getHomeCacheDir() / "cufetchpm" }); + fs::create_directories({ getConfigDir() / "plugins" }); + StateManager state; + switch (op) + { + case INSTALL: + { + if (options.arguments.size() < 1) + die("Please provide a plugin repository to install"); + PluginManager plugin_manager(std::move(state)); + for (const std::string& arg : options.arguments) + { + if (fs::exists(arg)) + plugin_manager.build_plugins(arg); + else + plugin_manager.add_plugins_repo(arg); + } + break; + } + case LIST: + { + list_all_plugins(std::move(state)); + break; + } + case GEN_MANIFEST: + { + if (fs::exists(MANIFEST_NAME) && !askUserYorN(false, "{} already exists. Overwrite it?", MANIFEST_NAME)) + return EXIT_FAILURE; + auto f = fmt::output_file(MANIFEST_NAME, fmt::file::CREATE | fmt::file::WRONLY | fmt::file::TRUNC); + f.print("{}", AUTO_MANIFEST); + f.close(); + break; + } + case ENABLE: + { + switch_plugin(std::move(state), true); + break; + } + case DISABLE: + { + switch_plugin(std::move(state), false); + break; + } + case UPDATE: + { + PluginManager plugin_manager(std::move(state)); + plugin_manager.update_repos(); + break; + } + case UNINSTALL: + { + if (options.arguments.size() < 1) + die("Please provide a plugin repository to uninstall"); + + PluginManager plugin_manager(std::move(state)); + for (const std::string& arg : options.arguments) + plugin_manager.remove_repo(arg); + break; + } + default: warn("uh?"); + } + + return EXIT_SUCCESS; +} diff --git a/cufetchpm/src/manifest.cpp b/cufetchpm/src/manifest.cpp new file mode 100644 index 00000000..d1134fd2 --- /dev/null +++ b/cufetchpm/src/manifest.cpp @@ -0,0 +1,173 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "manifest.hpp" + +#include <algorithm> +#include <cctype> +#include <filesystem> +#include <vector> + +#include "libcufetch/common.hh" +#include "tiny-process-library/process.hpp" +#include "util.hpp" + +namespace fs = std::filesystem; + +static bool is_valid_name(const std::string_view n) +{ + return std::ranges::all_of(n, + [](const unsigned char c) { return (isalnum(c) || c == '-' || c == '_' || c == '='); }); +} + +std::string ManifestSpace::getStrValue(const toml::table& tbl, const std::string_view name, const std::string_view key) +{ + const std::optional<std::string>& ret = tbl[name][key].value<std::string>(); + return ret.value_or(UNKNOWN); +} + +std::string ManifestSpace::getStrValue(const toml::table& tbl, const std::string_view path) +{ + const std::optional<std::string>& ret = tbl.at_path(path).value<std::string>(); + return ret.value_or(UNKNOWN); +} + +std::vector<std::string> ManifestSpace::getStrArrayValue(const toml::table& tbl, const std::string_view path) +{ + std::vector<std::string> ret; + + // https://stackoverflow.com/a/78266628 + if (const toml::array* array_it = tbl.at_path(path).as_array()) + { + array_it->for_each([&ret](auto&& el) { + if (const toml::value<std::string>* str_elem = el.as_string()) + ret.push_back((*str_elem)->data()); + }); + + return ret; + } + return {}; +} + +std::vector<std::string> ManifestSpace::getStrArrayValue(const toml::table& tbl, const std::string_view name, + const std::string_view value) +{ + std::vector<std::string> ret; + + // https://stackoverflow.com/a/78266628 + if (const toml::array* array_it = tbl[name][value].as_array()) + { + array_it->for_each([&ret](auto&& el) { + if (const toml::value<std::string>* str_elem = el.as_string()) + ret.push_back((*str_elem)->data()); + }); + + return ret; + } + return {}; +} + +CManifest::CManifest(const fs::path& path) +{ + try + { + this->m_tbl = toml::parse_file(path.string()); + } + catch (const toml::parse_error& err) + { + die(_("Failed to parse manifest file at '{}':\n" + "{}\n" + "\t(error occurred at line {} column {})"), + path.string(), err.description(), err.source().begin.line, err.source().begin.column); + } + + parse_manifest(path); +} + +void CManifest::parse_manifest(const fs::path& path) +{ + m_repo.name = getStrValue("repository", "name"); + m_repo.url = getStrValue("repository", "url"); + if (m_repo.name == UNKNOWN) + die("Couldn't find manifest repository name"); + if (!is_valid_name(m_repo.name)) + die("Manifest repository name '{}' is invalid. Only alphanumeric and '-', '_', '=' are allowed in the name", + m_repo.name); + + TinyProcessLib::Process proc(fmt::format("git -C {} rev-parse HEAD", path.parent_path().string()), "", + [&](const char* buf, size_t len) { m_repo.git_hash.assign(buf, len); }); + if (proc.get_exit_status() != 0) + die("manifest: Failed to get repository hash"); + m_repo.git_hash.erase(std::remove(m_repo.git_hash.begin(), m_repo.git_hash.end(), '\n'), m_repo.git_hash.end()); + + if (auto* deps = m_tbl["dependencies"].as_table()) + { + // Collect "all" dependencies + if (auto arr = (*deps)["all"].as_array()) + { + for (auto&& pkg : *arr) + if (auto s = pkg.value<std::string>()) + m_repo.dependencies.push_back(*s); + } + + // Collect platform-specific dependencies + if (auto arr = (*deps)[PLATFORM].as_array()) + { + for (auto&& pkg : *arr) + if (auto s = pkg.value<std::string>()) + m_repo.dependencies.push_back(*s); + } + } + + for (const auto& [name, _] : m_tbl) + { + if (name.str() == "repository" || name.str() == "dependencies") + continue; + + if (!is_valid_name(name.str())) + { + warn("Plugin '{}' has an invalid name. Only alphanumeric and '-', '_', '=' are allowed in the name", + name.str()); + continue; + } + + m_repo.plugins.push_back(get_plugin(name)); + } +} + +plugin_t CManifest::get_plugin(const std::string_view name) +{ + // if (!m_tbl[name].is_table()) + // die("Couldn't find such plugin '{}' in manifest", name); + + return { .name = name.data(), + .description = getStrValue(name, "description"), + .output_dir = getStrValue(name, "output-dir"), + .licenses = getStrArrayValue(name, "licenses"), + .authors = getStrArrayValue(name, "authors"), + .build_steps = getStrArrayValue(name, "build-steps"), + .prefixes = getStrArrayValue(name, "prefixes"), + .platforms = getStrArrayValue(name, "platforms") }; +} diff --git a/cufetchpm/src/pluginManager.cpp b/cufetchpm/src/pluginManager.cpp new file mode 100644 index 00000000..0aea603a --- /dev/null +++ b/cufetchpm/src/pluginManager.cpp @@ -0,0 +1,328 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "pluginManager.hpp" + +#include <algorithm> +#include <cstdio> +#include <cstdlib> +#include <filesystem> +#include <random> +#include <string> +#include <string_view> +#include <system_error> +#include <vector> + +#include "fmt/format.h" +#include "fmt/ranges.h" +#include "libcufetch/common.hh" +#include "manifest.hpp" +#include "tiny-process-library/process.hpp" +#include "util.hpp" + +static const std::vector<std::string> core_dependencies = { "git" }; // expand in the future, maybe + +using namespace TinyProcessLib; + +static bool has_deps(const std::vector<std::string>& dependencies) +{ + for (const std::string& bin : dependencies) + { + Process proc( + fmt::format("command -v {}", bin), "", [](const char*, size_t) {}, // discard stdout + [](const char*, size_t) {}); // discard stderr + if (proc.get_exit_status() != 0) + return false; + } + + return true; +} + +static bool find_plugin_prefix(const plugin_t& plugin, const plugin_t& pending_plugin) +{ + for (const std::string& prefix : pending_plugin.prefixes) + if (std::find(plugin.prefixes.begin(), plugin.prefixes.end(), prefix) != plugin.prefixes.end()) + return true; + return false; +} + +static bool is_update = false; + +void PluginManager::add_plugins_repo(const std::string& repo) +{ + if (!has_deps(core_dependencies)) + die("Some core dependencies are not installed. You'll need to install: {}", fmt::join(core_dependencies, ", ")); + + static std::random_device rd; + static std::mt19937 gen(rd()); + static std::uniform_int_distribution<> dist(0, 999999); + + // create temponary directory + const fs::path& working_dir = m_cache_path / ("plugin_" + std::to_string(dist(gen))); + fs::create_directories(working_dir); + + // and lets clone the repository + status("Cloning repository '{}' at '{}'", repo, working_dir.string()); + if (Process({ "git", "clone", "--recursive", repo, working_dir.string() }).get_exit_status() != 0) + { + fs::remove_all(working_dir); + die("Failed to clone at directory '{}'", working_dir.string()); + } + success("Successfully cloned. Changing current directory to '{}'", working_dir.string()); + build_plugins(working_dir); +} + +bool PluginManager::is_plugin_conflicting(const plugin_t& pending_plugin) +{ + for (const auto& manifest : m_state_manager.get_all_repos()) + for (const auto& plugin : manifest.plugins) + if (find_plugin_prefix(plugin, pending_plugin)) + return true; + return false; +} + +void PluginManager::update_repos() +{ + for (const manifest_t& repo : m_state_manager.get_all_repos()) + { + std::string output; + auto func = [&](const char* buf, size_t len) { output.assign(buf, len); }; + // the user didn't remove the cache directory, right? + if (fs::exists(m_cache_path / repo.name)) + { + debug("Repo '{}' cache path exists", repo.name); + fs::current_path(m_cache_path / repo.name); + if (Process({ "git", "pull", "--rebase" }, "", func, func).get_exit_status() != 0) + die("Failed to 'git pull --rebase' repository {}: {}", repo.name, output); + debug("git output = {}", output); + + std::string remote; + if (Process( + { "git", "rev-parse", "@{u}" }, "", [&](const char* buf, size_t len) { remote.assign(buf, len); }, + func) + .get_exit_status() != 0) + die("Failed to retrieve upstream hash from repository {}: {}", repo.name, output); + + debug("remote = {} && git_hash = {}", remote, repo.git_hash); + // let's avoid any spaces or newlines + if (hasStart(remote, repo.git_hash)) + { + info("{} is already up-to-date.", repo.name); + continue; + } + + status("Updating {}", repo.name); + is_update = true; + build_plugins(m_cache_path / repo.name); + } + // they did, dammit + else + { + debug("Repo '{}' cache path got deleted/not found", repo.name); + if (Process({ "git", "ls-remote", repo.url, "HEAD" }, "", func, func).get_exit_status() != 0) + die("Failed to retrieve latest commit from url {}: {}", repo.url, output); + + debug("git output = {}", output); + // let's avoid any spaces or newlines + if (hasStart(output, repo.git_hash)) + { + info("{} is already up-to-date.", repo.name); + continue; + } + status("Cloning and then updating {}", repo.name); + is_update = true; + add_plugins_repo(repo.url); + } + } +} + +void PluginManager::build_plugins(const fs::path& working_dir) +{ + std::vector<std::string> non_supported_plugins; + + // cd to the working directory and parse its manifest + fs::current_path(working_dir); + CManifest manifest(working_dir / MANIFEST_NAME); + + // though lets check if we have already installed the plugin in the cache + const fs::path& repo_cache_path = (m_cache_path / manifest.get_repo_name()); + if (fs::exists(repo_cache_path) && !is_update) + { + if (!options.install_force) + { + warn("Repository '{}' already exists in '{}'", manifest.get_repo_name(), repo_cache_path.string()); + fs::remove_all(working_dir); + return; + } + } + + if (!options.install_shut_up) + { + warn("{}", + "You should never blindly trust anything in life that you never saw/know about.\n" + " Right now you are installing something that can be a \033[1;31mPOTENTIAL trojan or any " + "malware.\033[0m\n" + " \033[1;36mPlease make sure that you trust every plugin you put to compile and install.\n" + " \033[1;33mYOU ARE THE SOLE RESPONSABLE FOR ANY DAMAGES DONE ON YOUR MACHINE."); + if (!askUserYorN(false, "Do you want to continue installing these plugins?")) + die("Operation cancelled from the user"); + } + + // So we don't have any plugins in the manifest uh + if (manifest.get_all_plugins().empty()) + { + fs::remove_all(working_dir); + die("Looks like there are no plugins to build in repository '{}'", manifest.get_repo_name()); + } + + if (!manifest.get_dependencies().empty()) + { + info("The plugin repository {} requires the following dependencies, check if you have them installed:\n {}", + manifest.get_repo_name(), fmt::join(manifest.get_dependencies(), ", ")); + if (!options.install_shut_up && !askUserYorN(true, "Are these dependencies installed?")) + die("Balling out, re-install the repository again after installing all dependencies."); + } + + // build each plugin from the manifest + // and add the infos to the state.toml + for (const plugin_t& plugin : manifest.get_all_plugins()) + { + bool found_platform = false; + + if (!plugin.platforms.empty() && plugin.platforms.at(0) != "all") + { + for (const std::string& plugin_platform : plugin.platforms) + if (plugin_platform == PLATFORM) + found_platform = true; + + if (!found_platform) + { + warn("Plugin '{}' doesn't support the platform '{}'. Skipping", plugin.name, PLATFORM); + non_supported_plugins.push_back(plugin.name); + continue; + } + } + + if (is_plugin_conflicting(plugin) && !is_update) + { + warn("Plugin '{}' has conflicting prefixes with other plugins.", plugin.name); + warn("Check with 'cufetchpm list' the plugins that have one of the following prefixes: {}", + fmt::join(plugin.prefixes, ", ")); + if (!options.install_shut_up && !askUserYorN(false, "Wanna continue?")) + { + fs::remove_all(working_dir); + die("Balling out"); + } + } + + status("Trying to build plugin '{}'", plugin.name); + // make the shell stop at the first failure + Process process({ "bash", "-c", fmt::format("set -e; {}", fmt::join(plugin.build_steps, " && ")) }, ""); + if (process.get_exit_status() != 0) + { + fs::remove_all(working_dir); + die("Failed to build plugin '{}'", plugin.name); + } + + success("Successfully built '{}' into '{}'", plugin.name, plugin.output_dir); + } + m_state_manager.add_new_repo(manifest); + + // we built all plugins. let's rename the working directory to its actual manifest name, + success("Repository plugins are successfully built!", repo_cache_path.string()); + status("Renaming directory working directory to '{}'", repo_cache_path.string()); + fs::remove_all(repo_cache_path); + fs::create_directories(repo_cache_path); + fs::rename(working_dir, repo_cache_path); + + // and then we move each plugin built library from its output-dir + // and we'll declare all plugins we have moved. + const fs::path& manifest_config_path = (m_config_path / manifest.get_repo_name()); + fs::create_directories(manifest_config_path); + status("Moving each built plugin to '{}'", manifest_config_path.string()); + for (const plugin_t& plugin : manifest.get_all_plugins()) + { + // already told before + if (std::find(non_supported_plugins.begin(), non_supported_plugins.end(), plugin.name) != + non_supported_plugins.end()) + continue; + + // ugh, devs fault. Report this error to them + if (!fs::exists(plugin.output_dir)) + { + error("Plugin '{}' output-dir '{}' doesn't exist", plugin.name, plugin.output_dir); + continue; + } + + toml::array built_libraries; + for (const auto& library : fs::directory_iterator{ plugin.output_dir }) + { + // ~/.config/customfetch/plugins/<manifest-directory>/<plugin-filename> + const fs::path& library_config_path = manifest_config_path / library.path().filename(); + if (fs::exists(library_config_path) && (!options.install_force || !is_update)) + { + if (options.install_shut_up || askUserYorN(false, "Plugin '{}' already exists. Replace it?", library_config_path.string())) + fs::remove_all(library_config_path); + else + continue; + } + + if (library.is_regular_file() || library.is_symlink()) + { + std::error_code er; + fs::rename(fs::canonical(library), library_config_path, er); + if (er) + { + error("Failed to move '{}' to '{}': {}", fs::canonical(library).string(), + library_config_path.string(), er.message()); + continue; + } + built_libraries.push_back(library_config_path.string()); + } + else + { + error("Built library '{}' is not a regular file", library.path().string()); + } + } + m_state_manager.insert_or_assign_at_plugin(manifest.get_repo_name(), plugin.name, "libraries", + std::move(built_libraries)); + } + success("Enjoy the new plugins from {}", manifest.get_repo_name()); +} + +void PluginManager::remove_repo(const std::string& repo_name) +{ + std::error_code ec; + fs::remove_all(m_cache_path / repo_name, ec); + if (ec) + warn("Failed to remove plugin repository cache path '{}'", (m_cache_path / repo_name).string()); + + fs::remove_all(m_config_path / repo_name, ec); + if (ec) + warn("Failed to remove plugin repository config path '{}'", (m_config_path / repo_name).string()); + + m_state_manager.remove_repo(repo_name); + success("Removed plugin repository '{}'", repo_name); +} diff --git a/cufetchpm/src/stateManager.cpp b/cufetchpm/src/stateManager.cpp new file mode 100644 index 00000000..f38b74fa --- /dev/null +++ b/cufetchpm/src/stateManager.cpp @@ -0,0 +1,189 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "stateManager.hpp" + +#include <filesystem> +#include <fstream> +#include <sstream> +#include <string_view> +#include <utility> + +#include "fmt/base.h" +#include "fmt/os.h" +#include "libcufetch/common.hh" +#include "manifest.hpp" + +using namespace ManifestSpace; + +// https://github.com/hyprwm/Hyprland/blob/2d2a5bebff72c73cd27db3b9e954b8fa2a7623e8/hyprpm/src/core/DataState.cpp#L24 +bool writeState(const std::string& str, const std::string& to) +{ + // create temp file in a safe temp root + const fs::path& temp_state = (fs::temp_directory_path() / ".temp-state"); + std::ofstream of(temp_state, std::ios::trunc); + if (!of.good()) + return false; + + of << str; + of.close(); + + return fs::copy_file(temp_state, to, fs::copy_options::overwrite_existing); +} + +// Ensures a sub-table exists for a given key. Returns a reference to the sub-table. +static toml::table& ensure_table(toml::table& parent, std::string_view key) +{ + if (toml::node* node = parent[key].node()) + if (auto* tbl = node->as_table()) + return *tbl; + + auto [it, inserted] = parent.insert(key, toml::table{}); + return *it->second.as_table(); +} + +// Converts a vector of strings to a toml::array +static toml::array vector_to_array(const std::vector<std::string>& vec) +{ + toml::array ret; + for (const std::string& str : vec) + ret.push_back(str); + return ret; +} + +StateManager::StateManager() +{ + if (!fs::exists(m_path)) + { + auto f = fmt::output_file(m_path.string(), fmt::file::WRONLY | fmt::file::TRUNC | fmt::file::CREATE); + f.print(R"(# AUTO-GENERATED FILE. DO NOT EDIT THIS FILE. +# YOU GONNA MESS SHIT UP. unless you know what you doing ofc + )"); + f.close(); + } + + try + { + if (m_state.empty()) + m_state = toml::parse_file(m_path.string()); + } + catch (const toml::parse_error& err) + { + die(_("Failed to parse state file at '{}':\n" + "{}\n" + "\t(error occurred at line {} column {})"), + m_path.string(), err.description(), err.source().begin.line, err.source().begin.column); + } +} + +void StateManager::add_new_repo(const CManifest& manifest) +{ + toml::table& repositories = ensure_table(m_state, "repositories"); + toml::table& repo = ensure_table(repositories, manifest.get_repo_name()); + repo.insert_or_assign("url", manifest.get_repo_url()); + repo.insert_or_assign("git-hash", manifest.get_repo_hash()); + + toml::array plugins_arr; + for (const plugin_t& plugin : manifest.get_all_plugins()) + { + // will be inserted in alphabetical order + toml::table entry{ { "name", plugin.name }, + { "description", plugin.description }, + { "authors", vector_to_array(plugin.authors) }, + { "licenses", vector_to_array(plugin.licenses) }, + { "prefixes", vector_to_array(plugin.prefixes) } }; + + plugins_arr.push_back(std::move(entry)); + } + + repo.insert_or_assign("plugins", std::move(plugins_arr)); + std::stringstream ss; + ss << "# AUTO-GENERATED FILE. DO NOT EDIT THIS FILE.\n"; + ss << "# YOU GONNA MESS SHIT UP. unless you know what you doing ofc\n"; + ss << m_state; + + if (!writeState(ss.str(), m_path)) + die("Failed to write plugin state of repository '{}'", manifest.get_repo_name()); +} + +std::vector<manifest_t> StateManager::get_all_repos() +{ + const toml::table* repositories = m_state["repositories"].as_table(); + if (!repositories) + return {}; + + std::vector<manifest_t> manifests; + for (const auto& [repo_name, repo_node] : *repositories) + { + const toml::table* repo_tbl = repo_node.as_table(); + if (!repo_tbl) + continue; + + manifest_t manifest; + manifest.name = repo_name.str(); + manifest.url = getStrValue(*repo_tbl, "url"); + manifest.git_hash = getStrValue(*repo_tbl, "git-hash"); + + if (const toml::array* plugins = repo_tbl->get_as<toml::array>("plugins")) + { + for (const auto& plugin_node : *plugins) + { + const toml::table* plugin_tbl = plugin_node.as_table(); + if (!plugin_tbl) + continue; + + plugin_t plugin; + plugin.name = getStrValue(*plugin_tbl, "name"); + plugin.description = getStrValue(*plugin_tbl, "description"); + plugin.output_dir = getStrValue(*plugin_tbl, "output-dir"); + plugin.authors = getStrArrayValue(*plugin_tbl, "authors"); + plugin.licenses = getStrArrayValue(*plugin_tbl, "licenses"); + plugin.prefixes = getStrArrayValue(*plugin_tbl, "prefixes"); + + manifest.plugins.push_back(std::move(plugin)); + } + } + + manifests.push_back(std::move(manifest)); + } + + return manifests; +} + +void StateManager::remove_repo(const std::string& repo) +{ + toml::table* repo_tbl = m_state["repositories"].as_table(); + if (!repo_tbl->contains(repo)) + return; + + repo_tbl->erase(repo); + std::stringstream ss; + ss << "# AUTO-GENERATED FILE. DO NOT EDIT THIS FILE.\n"; + ss << "# YOU GONNA MESS SHIT UP. unless you know what you doing ofc\n"; + ss << m_state; + + if (!writeState(ss.str(), m_path)) + die("Failed to write plugin state of repository '{}'", repo); +} diff --git a/examples/mod-library.cc b/examples/mod-library.cc new file mode 100644 index 00000000..916ada0a --- /dev/null +++ b/examples/mod-library.cc @@ -0,0 +1,66 @@ +/* This is an example for a plugin you could install in ~/.config/customfetch/plugins/ + +Plugins are essentially custom(fetch) libraries where you can create modules that you can implement yourself and register using libcufetch! +They have to be compiled as shared libraries **with no name mangling!!**, with one start() and finish() functions. Scroll down for more details on both functions. + +To compile this, just run `g++ -I../include -shared -fPIC mod-library.cc -o mod-library.so`. +*/ + +#include <stdio.h> +#include <cstdlib> +#include <string> + +#include <libcufetch/common.hh> +#include <libcufetch/config.hh> +#include <libcufetch/cufetch.hh> + +/* The handler that we'll use for our module, Handlers return `std::string` + * and take as input the `const callbackInfo_t*` struct + */ +std::string test_func(const callbackInfo_t* _) { + return "Hello!"; +} + +const char *useless_malloc; + +/* Start function. + +The start function takes in a handle, This handle is going to be the libcufetch library. +And also take the config class ConfigBase, instance loaded from customfetch. + +It gets called once we have loaded the plugin shared library. + +*/ +APICALL EXPORT PLUGIN_INIT(void *handle, const ConfigBase& config) { + + /* Our goal in this example is to create a `modification.test` module, This will just return "Hello!" and nothing else. */ + /* The way we'll do this is we will create the test module, with no submodules and a handler (that just returns "Hello!"). */ + /* But then we will also create the modification module, which won't do anything other than hold the test module as a submodule. */ + /* We will then register the modification module. We won't register the test module because it is already a submodule and will be implicitly added. */ + + /* Here we create the 'test' submodule. in customfetch there's no practical difference between a parent module and a submodule. So we just define it like any other module. */ + /* We will not register this, it will be implicitly added through its parent module (so we can't directly invoke `test`, we can only invoke `modification.test`) */ + module_t test_module = {"test", "a generic submodule description", {}, test_func}; + + /* And here we create the 'modification' module. This is what we're actually going to register and it will include the test module as a submodule. */ + /* This module doesn't have a handler, so it can't be used in the config (`modification` won't work). We'll instead use `modification.test` in the config (which does have a handler). */ + module_t modification_module = { "modification", "root module description", { std::move(test_module) }, NULL }; + + /* Register the module. */ + /* This will take the modification module, recursively add it and its submodules to the list, and continue until its finished everything. */ + cfRegisterModule(modification_module); + + /* Lookup the finish function on why this */ + useless_malloc = reinterpret_cast<const char*>(malloc(16)); + + /* And done, after this customfetch will call our handler whenever our test module is invoked in the layout. */ +} + +/* Finish function. + * + * The finish function gets called when customfetch exists. + * You should put free here any thing you have manually allocated to avoid memory leaks. + */ +APICALL EXPORT PLUGIN_FINISH() { + std::free((void*)useless_malloc); +} diff --git a/include/config.hpp b/include/config.hpp index 9feecfd5..c21015ba 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -1,90 +1,71 @@ /* * Copyright 2025 Toni500git - * + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #ifndef _CONFIG_HPP #define _CONFIG_HPP +#undef TOML_HEADER_ONLY #define TOML_HEADER_ONLY 0 -#include <cstdint> -#include <type_traits> -#include <unordered_map> - -#include "toml++/toml.hpp" -#include "util.hpp" - -enum types -{ - STR, - BOOL, - INT -}; - -struct override_configs_types -{ - types value_type; - std::string string_value = ""; - bool bool_value = false; - int int_value = 0; -}; - -// config colors -// those without gui_ prefix are for the terminal -struct colors_t -{ - std::string black; - std::string red; - std::string green; - std::string blue; - std::string cyan; - std::string yellow; - std::string magenta; - std::string white; - - std::string gui_black; - std::string gui_red; - std::string gui_green; - std::string gui_blue; - std::string gui_cyan; - std::string gui_yellow; - std::string gui_magenta; - std::string gui_white; -}; +#include <filesystem> +#include "libcufetch/config.hh" -class Config +class Config : public ConfigBase { public: // Create .config directories and files and load the config file (args or default) - Config(const std::string_view configFile, const std::string_view configDir); + Config(const std::filesystem::path& configFile, const std::filesystem::path& configDir); + + // config colors + // those without gui_ prefix are for the terminal + struct colors_t + { + std::string black; + std::string red; + std::string green; + std::string blue; + std::string cyan; + std::string yellow; + std::string magenta; + std::string white; + + std::string gui_black; + std::string gui_red; + std::string gui_green; + std::string gui_blue; + std::string gui_cyan; + std::string gui_yellow; + std::string gui_magenta; + std::string gui_white; + } colors; // Variables of config file in [config] table std::vector<std::string> layout; std::vector<std::string> percentage_colors; std::vector<std::string> colors_name, colors_value; std::string source_path; - std::string font; std::string data_dir; std::string sep_reset; std::string title_sep; @@ -106,7 +87,7 @@ class Config // modules specific configs // [auto.disk] std::string auto_disks_fmt; - int auto_disks_types = 0; + int auto_disks_types = 0; bool auto_disks_show_dupl = false; // [os.uptime] @@ -126,14 +107,12 @@ class Config std::vector<std::string> args_layout; std::string args_custom_distro; std::string args_image_backend; - std::uint16_t m_offset_calc = 0; - bool m_display_distro = true; - bool args_disable_source = false; - bool args_disable_colors = false; - bool args_disallow_commands = false; - bool args_print_logo_only = false; - - std::unordered_map<std::string, override_configs_types> overrides; + std::uint16_t m_offset_calc = 0; + bool m_display_distro = true; + bool args_disable_source = false; + bool args_disable_colors = false; + bool args_disallow_commands = false; + bool args_print_logo_only = false; /** * Load config file and parse every config variables @@ -141,13 +120,13 @@ class Config * @param colors The colors struct where we'll put the default config colors. * It doesn't include the colors in config.alias-colors */ - void loadConfigFile(const std::string_view filename, colors_t& colors); + void loadConfigFile(const std::filesystem::path& filename); /** * Generate the default config file at path * @param filename The config file path */ - void generateConfig(const std::string_view filename); + void generateConfig(const std::filesystem::path& filename); /** * Add alias values to colors_name and colors_value. @@ -159,262 +138,40 @@ class Config /** * Override a config value from --override - * @param str The value to override. + * @param opt The value to override. * Must have a '=' for separating the name and value to override. * NO spaces between - */ + */ void overrideOption(const std::string& opt); -private: - // Parsed config from loadConfigFile() - toml::table tbl; - /** - * Get value of config variables - * @param value The config variable "path" (e.g "config.source-path") - * @param fallback Default value if couldn't retrive value + * Override a config value from --override + * @param key The value name to override. + * Must have a '=' for separating the name and value to override. + * NO spaces between + * @param value The value that will overwrite */ template <typename T> - T getValue(const std::string_view value, const T&& fallback, bool dont_expand_var = false) const + void overrideOption(const std::string& key, const T& value) { - const auto& overridePos = overrides.find(value.data()); - - // user wants a bool (overridable), we found an override matching the name, and the override is a bool. - if constexpr (std::is_same<T, bool>()) - if (overridePos != overrides.end() && overrides.at(value.data()).value_type == BOOL) - return overrides.at(value.data()).bool_value; - - // user wants a str (overridable), we found an override matching the name, and the override is a str. - if constexpr (std::is_same<T, std::string>()) - if (overridePos != overrides.end() && overrides.at(value.data()).value_type == STR) - return overrides.at(value.data()).string_value; - - if constexpr (std::is_same<T, std::uint16_t>()) - if (overridePos != overrides.end() && overrides.at(value.data()).value_type == INT) - return overrides.at(value.data()).int_value; - - const std::optional<T>& ret = this->tbl.at_path(value).value<T>(); - if constexpr (toml::is_string<T>) // if we want to get a value that's a string - return ret ? expandVar(ret.value(), dont_expand_var) : expandVar(fallback, dont_expand_var); - else - return ret.value_or(fallback); + override_configs_types o; + if constexpr (std::is_same_v<T, bool>) + { + o.value_type = BOOL; + o.bool_value = value; + } + else if constexpr (std::is_convertible_v<T, std::string>) + { + o.value_type = STR; + o.string_value = value; + } + else if constexpr (std::is_convertible_v<T, int>) + { + o.value_type = INT; + o.int_value = value; + } + overrides[key] = std::move(o); } - - /** - * getValue() but don't want to specify the template, so it's std::string, - * and because of the name, only used when retriving the colors for terminal and GUI - * @param value The config variable "path" (e.g "config.gui-red") - * @param fallback Default value if couldn't retrive value - */ - std::string getThemeValue(const std::string_view value, const std::string_view fallback) const; - - /** - * Get value of config array of string variables - * @param value The config variable "path" (e.g "config.gui-red") - * @param fallback Default value if couldn't retrive value - */ - std::vector<std::string> getValueArrayStr(const std::string_view value, const std::vector<std::string>& fallback); }; -// default config -inline constexpr std::string_view AUTOCONFIG = R"#([config] - -# For more information on how customfetch works and the layout, -# Read either: -# * -w or --how-it-works -# * the manual customfetch.1 -# * if on the android app, click the button "how it works" during widget configuration -layout = [ - "$<title>", - "$<title_sep>", - "${auto}OS: $<os.name> $<system.arch>", - "${auto}Host: $<system.host>", - "${auto}Kernel: $<os.kernel>", - "${auto}Uptime: $<os.uptime>",)#" -#if !CF_ANDROID - R"#( - "${auto}Theme: $<theme-gtk-all.name>", - "${auto}Icons: $<theme-gtk-all.icons>", - "${auto}Font: $<theme-gtk-all.font>", - "${auto}Cursor: $<theme.cursor>", - "${auto}WM: $<user.wm_name>", - "${auto}DE: $<user.de_name>",)#" -#endif - R"#( - "$<auto.disk>", - "${auto}Swap: $<swap>", - "${auto}CPU: $<cpu>", - "${auto}GPU: $<gpu>", - "${auto}RAM: $<ram>", - "", - "$<colors>", # normal colors - "$<colors_light>" # light colors -] - -# display ascii-art or image/gif (GUI only) near layout -# put "os" for displaying the OS ascii-art -# or the "/path/to/file" for displaying custom files -# or "off" for disabling ascii-art or image displaying -source-path = "os" - -# Path to where we'll take all the distros/OSs ascii arts. -# note: it MUST contain an "ascii" subdirectory -data-dir = "/usr/share/customfetch" - -# The type of ASCII art to apply ("small", "old"). -# Basically will add "_<type>" to the logo filename. -# It will return the regular linux ascii art if it doesn't exist. -# Leave empty it for regular. -ascii-logo-type = "" - -# A char (or string) to use in $<title_sep> -title-sep = "-" - -# A separator (or string) that when encountered, will automatically -# reset color, aka. automatically add ${0} (only in layout) -# Make it empty for disabling -sep-reset = ":" - -# Should we reset color after or before the separator? -# true = after ("test ->${0} ") -# false = before ("test ${0}-> ") -sep-reset-after = false - -# Where the logo should be displayed. -# Values: "top" or "left" or "bottom" -logo-position = "left" - -# Offset between the ascii art and the layout -# Can also be rapresented as a %, but super unstable sometimes. -offset = "5" - -# Padding between the start and the ascii art -logo-padding-left = 0 - -# Padding of the ascii art from the top -logo-padding-top = 0 - -# Padding of the layout from the top -layout-padding-top = 0 - -# Usually in neofetch/fastfetch, when your terminal size is too small, -# to render some text in 1 line, they don't wrap those lines, instead they truncate them. -# Enable/Disable if you want this -wrap-lines = false - -# Used in disk, ram and swap modules. -# If true, we're going to use the SI standard byte unit (1kB == 1000 bytes) -# Else if false, we using the IEC byte unit (1KiB == 1024 bibytes) -# Really nerdy stuff -use-SI-byte-unit = false - -# Warn against tradeoffs between slower queries for availability -# e.g. falling back to gsettings when we can't find the config file for GTK -slow-query-warnings = false - -# Colors in the terminal (for Desktop/Android app, use the ones under [gui]) -black = "\e[1;30m" -red = "\e[1;31m" -green = "\e[1;32m" -yellow = "\e[1;33m" -blue = "\e[1;34m" -magenta = "\e[1;35m" -cyan = "\e[1;36m" -white = "\e[1;37m" - -# Alias colors. Basically more color variables. -# They can be used as like as the color tag. -# This is as like as using the --add-color argument -# Syntax must be "name=value", e.g "purple=magenta" or "orange=!#F08000" -alias-colors = ["purple=magenta"] - -# Colors to be used in percentage tag and modules members. -# They are used as if you're using the color tag. -# It's an array just for "convenience" -# 1st color for good -# 2nd color for normal -# 3rd color for bad -percentage-colors = ["green", "yellow", "red"] - -# $<auto.disk> config -[auto.disk] -# Format for displaying the auto detected disks infos -# %1 = mount directory -# %2 = device path -# %3 = type of filesystem -# %4 = total amount of storage -# %5 = free amount of storage -# %6 = used amount of storage -# %7 = percentage of used storage -# %8 = percentage of free storage -fmt = "${auto}Disk (%1): $<disk(%1)>" - -# Only print disks that matches the description -# of the following types: -# regular = Regular disks (internel M.2 SSD, ...) (won't be specified) -# external = External disks (USB, SATA, ...) -# read-only = Disks with read-only filesystems -# hidden = Disks that are not really mounted by the user -display-types = ["regular", "external", "read-only"] - -# In some OSes such as NixOS or Android, there might be some directories that are bind mounted. -# Bind mounted directories create an additional view of an existing directory, -# and `statfs()` on the mount point will return the filesystem statistics of the original directory. -show-duplicated = false - -# $<os.uptime> config -[os.uptime] -# how to display the name of the uptime -# e.g: hours = "hrs" -> "Uptime: 3hrs" -days = " days" -hours = " hours" -mins = " mins" -secs = " secs" - -# $<os.pkgs> config -[os.pkgs] -# Ordered list of which packages installed count should be displayed in $<os.pkgs> -# remember to not enter the same name twice, else the world will finish -# Choices: pacman, flatpak, dpkg, apk -# -# Pro-tip: if your package manager isn't listed here, yet, -# use the bash command tag in the layout -# e.g "Packages: $(pacman -Q | wc -l) (pacman)" -pkg-managers = ["pacman", "dpkg", "flatpak"] - -# Distros and package manager specific -# package manager paths for getting the packages count from path. -# They are arrays so you can add multiple paths. -# -# If you don't know what these ares, leave them by default settings -pacman-dirs = ["/var/lib/pacman/local/"] -dpkg-files = ["/var/lib/dpkg/status", "/data/data/com.termux/files/usr/var/lib/dpkg/status"] -flatpak-dirs = ["/var/lib/flatpak/app/", "~/.local/share/flatpak/app/"] -apk-files = ["/var/lib/apk/db/installed"] - -# Desktop/Android app options -[gui] - -# These are the colors you can use in the GUI mode. -# They overwrite the terminal colors from above. -# They can only have hexcodes colors and its modifiers -black = "!#000005" -red = "!#ff2000" -green = "!#00ff00" -blue = "!#00aaff" -cyan = "!#00ffff" -yellow = "!#ffff00" -magenta = "!#f881ff" -white = "!#ffffff" - -# Path to image as a background. -# put "disable" for disabling and use the theme color as background. -bg-image = "disable" - -# Path to gtk css file to be used. -# put "disable" for disabling. -gtk-css = "disable" - -)#"; - #endif // _CONFIG_HPP diff --git a/include/core-modules.hh b/include/core-modules.hh new file mode 100644 index 00000000..18951027 --- /dev/null +++ b/include/core-modules.hh @@ -0,0 +1,144 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#pragma once + +#include <pwd.h> +#include <sys/utsname.h> + +#include "config.hpp" +#include "libcufetch/cufetch.hh" + +#define MODFUNC(name) std::string name(__attribute__((unused)) const callbackInfo_t* callbackInfo) + +// system.cc +MODFUNC(arch); +MODFUNC(host); +MODFUNC(host_name); +MODFUNC(host_version); +MODFUNC(host_vendor); + +// os.cc +inline utsname g_uname_infos; +inline std::FILE* os_release; +MODFUNC(os_name); +MODFUNC(os_pretty_name); +MODFUNC(os_name_id); +MODFUNC(os_version_id); +MODFUNC(os_version_codename); +unsigned long os_uptime(); +MODFUNC(os_kernel_name); +MODFUNC(os_kernel_version); +MODFUNC(os_hostname); +MODFUNC(os_initsys_name); +MODFUNC(os_initsys_version); + +// cpu.cc +inline std::FILE* cpuinfo; +MODFUNC(cpu_freq_cur); +MODFUNC(cpu_freq_max); +MODFUNC(cpu_freq_min); +MODFUNC(cpu_freq_bios); +float cpu_temp(); +MODFUNC(cpu_nproc); +MODFUNC(cpu_name); +MODFUNC(android_cpu_vendor); +MODFUNC(android_cpu_model_name); + +// user.cc +inline struct passwd* g_pwd; +inline bool is_tty = false; +inline std::string term_pid, term_name, wm_name, de_name, wm_path_exec; +std::string get_terminal_name(); +std::string get_terminal_pid(); +MODFUNC(user_name); +MODFUNC(user_shell_path); +MODFUNC(user_shell_name); +MODFUNC(user_shell_version); +MODFUNC(user_term_name); +MODFUNC(user_term_version); +MODFUNC(user_wm_name); +MODFUNC(user_wm_version); +MODFUNC(user_de_name); +MODFUNC(user_de_version); + +// ram.cc and swap.cc +inline std::FILE* meminfo; +double ram_free(); +double ram_total(); +double ram_used(); +double swap_free(); +double swap_total(); +double swap_used(); + +// disk.cc +enum +{ + DISK_VOLUME_TYPE_HIDDEN = 1 << 2, + DISK_VOLUME_TYPE_REGULAR = 1 << 3, + DISK_VOLUME_TYPE_EXTERNAL = 1 << 4, + DISK_VOLUME_TYPE_READ_ONLY = 1 << 5, +}; + +inline std::FILE* mountsFile; +MODFUNC(disk_fsname); +MODFUNC(disk_device); +MODFUNC(disk_mountdir); +MODFUNC(disk_types); +MODFUNC(auto_disk); +double disk_total(const callbackInfo_t* callbackInfo); +double disk_free(const callbackInfo_t* callbackInfo); +double disk_used(const callbackInfo_t* callbackInfo); + +// battery.cc +MODFUNC(battery_modelname); +MODFUNC(battery_perc); +MODFUNC(battery_status); +MODFUNC(battery_capacity_level); +MODFUNC(battery_technology); +MODFUNC(battery_vendor); +double battery_temp(); + +// gpu.cc +MODFUNC(gpu_name); +MODFUNC(gpu_vendor); + +// theme.cc +MODFUNC(theme_gtk_name); +MODFUNC(theme_gtk_icon); +MODFUNC(theme_gtk_font); +MODFUNC(theme_cursor_name); +MODFUNC(theme_cursor_size); +MODFUNC(theme_gtk_all_name); +MODFUNC(theme_gtk_all_icon); +MODFUNC(theme_gtk_all_font); +MODFUNC(theme_gsettings_name); +MODFUNC(theme_gsettings_icon); +MODFUNC(theme_gsettings_font); +MODFUNC(theme_gsettings_cursor_name); +MODFUNC(theme_gsettings_cursor_size); + +void core_plugins_start(const Config& config); +void core_plugins_finish(); diff --git a/include/display.hpp b/include/display.hpp index a3b9efd3..d0169116 100644 --- a/include/display.hpp +++ b/include/display.hpp @@ -1,25 +1,25 @@ /* * Copyright 2025 Toni500git - * + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ @@ -30,6 +30,7 @@ #include <vector> #include "config.hpp" +#include "libcufetch/cufetch.hh" #include "platform.hpp" #if CF_MACOS @@ -84,12 +85,11 @@ namespace Display /* * Render the layout along side the source file and return the vector * @param config The config class - * @param colors The colors * @param already_analyzed_path If already checked that the source path is not a binary file * @param path Path to source file */ -std::vector<std::string> render(const Config& config, const colors_t& colors, const bool already_analyzed_path, - const std::string_view path); +std::vector<std::string> render(const Config& config, const bool already_analyzed_path, + const std::filesystem::path& path, const moduleMap_t& moduleMap); /* * Display the rendered result (or just display a normal vector of string diff --git a/include/gui.hpp b/include/gui.hpp index a7ded3f3..0f855720 100644 --- a/include/gui.hpp +++ b/include/gui.hpp @@ -1,25 +1,25 @@ /* * Copyright 2025 Toni500git - * + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ @@ -38,6 +38,7 @@ #include "gtkmm/label.h" #include "gtkmm/overlay.h" #include "gtkmm/window.h" +#include "libcufetch/cufetch.hh" namespace GUI { @@ -48,18 +49,17 @@ class Window : public Gtk::Window /** * Initialize and create everything and parse layout with source path. * @param config The config class - * @param colors The non-alias colors struct * @param path The logo source path */ - Window(const Config& config, const colors_t& colors, const std::string_view path); + Window(const Config& config, const std::filesystem::path& path, const moduleMap_t& moduleMap); // Destroy the window, handled by GTK virtual ~Window(); private: - const Config& m_config; - const colors_t& m_colors; - const std::string_view m_path; - bool m_isImage; + const Config& m_config; + const std::filesystem::path& m_path; + const moduleMap_t& m_moduleMap; + bool m_isImage; Gtk::Overlay m_overlay; Gtk::Box m_box; diff --git a/include/libcufetch/common.hh b/include/libcufetch/common.hh new file mode 100644 index 00000000..a80949ce --- /dev/null +++ b/include/libcufetch/common.hh @@ -0,0 +1,102 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#pragma once + +#include <cstdlib> + +#include "fmt/core.h" + +constexpr const char NOCOLOR[] = "\033[0m"; +constexpr const char NOCOLOR_BOLD[] = "\033[0m\033[1m"; + +// Didn't find what you were looking for. +constexpr const char UNKNOWN[] = "(unknown)"; + +// Usually in neofetch/fastfetch when some infos couldn't be queried, they remove it from the display. +// With customfetch is kinda difficult to know when to remove the info to display, +// since it's all modular with tags, so I have created a "magic line" to be sure that I don't cut the wrong line. +// +// Every instance of this string found in a layout line, the whole line will be erased. +constexpr const char MAGIC_LINE[] = "(cut this line NOW!! RAHHH)"; + +#define APICALL extern "C" +#define EXPORT __attribute__((visibility("default"))) +#define PLUGIN_INIT void start +#define PLUGIN_FINISH void finish + +#if DEBUG +inline bool debug_print = true; +#else +inline bool debug_print = false; +#endif + +// std::format function arguments +// Print to stderr an error with header 'ERROR:' in red +template <typename... Args> +void error(const std::string_view fmt, Args&&... args) noexcept +{ + fmt::print(stderr, "\033[1;31mERROR: {}\033[0m\n", + fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); +} + +// std::format function arguments +// Print to stderr an error with header 'FATAL:' in red and exit with failure code +template <typename... Args> +void die(const std::string_view fmt, Args&&... args) noexcept +{ + fmt::print(stderr, "\033[1;31mFATAL: {}\033[0m\n", + fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); + std::exit(EXIT_FAILURE); +} + +// std::format function arguments +// Print to stdout a debug msg with header '[DEBUG]' in hot-pink color +// only if debug_print is set (do not modify it). +template <typename... Args> +void debug(const std::string_view fmt, Args&&... args) noexcept +{ + if (debug_print) + fmt::print(stdout, "\033[1;38;2;255;105;180m[DEBUG]:\033[0m {}\n", + fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); +} + +// std::format function arguments +// Print to stderr a warning with header 'WARNING:' in yellow +template <typename... Args> +void warn(const std::string_view fmt, Args&&... args) noexcept +{ + fmt::print(stderr, "\033[1;33mWARNING: {}\033[0m\n", + fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); +} + +// std::format function arguments +// Print to stdout an info msg with header 'INFO:' in cyan +template <typename... Args> +void info(const std::string_view fmt, Args&&... args) noexcept +{ + fmt::print(stdout, "\033[1;36mINFO: {}\033[0m\n", + fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); +} diff --git a/include/libcufetch/config.hh b/include/libcufetch/config.hh new file mode 100644 index 00000000..b1743bc4 --- /dev/null +++ b/include/libcufetch/config.hh @@ -0,0 +1,111 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#pragma once + +#include <string> +#include <unordered_map> + +#define TOML_HEADER_ONLY 0 +#include "libcufetch/common.hh" +#include "toml++/toml.hpp" + +enum types +{ + STR, + BOOL, + INT +}; + +struct override_configs_types +{ + types value_type; + std::string string_value = ""; + bool bool_value = false; + int int_value = 0; +}; + +class EXPORT ConfigBase +{ +public: + /** + * Get value of config variables + * @param value The config variable "path" (e.g "config.source-path") + * @param fallback Default value if couldn't retrive value + */ + template <typename T> + T getValue(const std::string_view value, const T& fallback) const + { + const auto& overridePos = overrides.find(value.data()); + + // user wants a bool (overridable), we found an override matching the name, and the override is a bool. + if constexpr (std::is_convertible_v<T, bool>) + if (overridePos != overrides.end() && overrides.at(value.data()).value_type == BOOL) + return overrides.at(value.data()).bool_value; + + if constexpr (std::is_convertible_v<T, std::string>) + if (overridePos != overrides.end() && overrides.at(value.data()).value_type == STR) + return overrides.at(value.data()).string_value; + + if constexpr (std::is_convertible_v<T, int>) + if (overridePos != overrides.end() && overrides.at(value.data()).value_type == INT) + return overrides.at(value.data()).int_value; + + const std::optional<T>& ret = this->tbl.at_path(value).value<T>(); + return ret.value_or(fallback); + } + + /** + * Get value of config array of string variables + * @param value The config variable "path" (e.g "config.gui-red") + * @param fallback Default value if couldn't retrive value + */ + std::vector<std::string> getValueArrayStr(const std::string_view value, + const std::vector<std::string>& fallback) const + { + std::vector<std::string> ret; + + // https://stackoverflow.com/a/78266628 + if (const toml::array* array_it = tbl.at_path(value).as_array()) + { + array_it->for_each([&ret](auto&& el) { + if (const toml::value<std::string>* str_elem = el.as_string()) + ret.push_back((*str_elem)->data()); + }); + + return ret; + } + else + { + return fallback; + } + } + +protected: + std::unordered_map<std::string, override_configs_types> overrides; + + // Parsed config from loadConfigFile() + toml::table tbl; +}; diff --git a/include/libcufetch/cufetch.hh b/include/libcufetch/cufetch.hh new file mode 100644 index 00000000..68f19e49 --- /dev/null +++ b/include/libcufetch/cufetch.hh @@ -0,0 +1,93 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#pragma once + +#include <functional> +#include <string> +#include <vector> + +#include "libcufetch/parse.hh" + +/* A linked list including module arguments. An argument may be specified for any part of the module path (e.g. + * `disk(/).used(GiB)`, `test.hi(a)`) */ +struct moduleArgs_t +{ + struct moduleArgs_t* prev = nullptr; + + std::string name; + std::string value; + + struct moduleArgs_t* next = nullptr; +}; + +// Struct used in modules callback functions (handler in module_t) +struct callbackInfo_t +{ + const moduleArgs_t* moduleArgs; + parse_args_t& parse_args; +}; + +/* Main struct for declaring a customfetch module. + * + * Submodules are referenced with '.' in their path. + * Example: $<parent.child> -> parent = `name`, child = `submodules[x].name`. + * + * WARN: Do not pass submodules to cfRegisterModule. + * It registers recursively and will include them automatically. + * + * Real example: $<github.profile.following> + * - github = root module + * - profile = submodule of github + * - following = submodule of profile + * + * The handler is executed when the module is invoked in the layout. + * If it's NULL, it returns "(unknown/invalid module)" + * + * Code example: + * module_t submodule_foo = {"idk", "description", {}, submodule_foo_callback}; + * module_t foo = {"foo", "description", {std::move(submodule_foo)}, foo_callback}; + * cfRegisterModule(foo); // you can call $<foo> and $<foo.idk> from the layout. + */ +struct module_t +{ + std::string name; + std::string description; + std::vector<module_t> submodules; /* Use std::move() for efficiency when adding. */ + std::function<std::string(const callbackInfo_t*)> handler; +}; + +// C ABI is needed to prevent symbol mangling, but we don't actually need C compatibility, +// so we ignore this warning about return types that are potentially incompatible with C. +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreturn-type-c-linkage" +#endif + +/* Register a module, and its submodules, to customfetch. */ +APICALL EXPORT void cfRegisterModule(const module_t& module); + +/* Get a list of all modules registered. */ +APICALL EXPORT const std::vector<module_t>& cfGetModules(); diff --git a/include/fmt/args.h b/include/libcufetch/fmt/args.h similarity index 100% rename from include/fmt/args.h rename to include/libcufetch/fmt/args.h diff --git a/include/fmt/base.h b/include/libcufetch/fmt/base.h similarity index 100% rename from include/fmt/base.h rename to include/libcufetch/fmt/base.h diff --git a/include/fmt/chrono.h b/include/libcufetch/fmt/chrono.h similarity index 100% rename from include/fmt/chrono.h rename to include/libcufetch/fmt/chrono.h diff --git a/include/fmt/color.h b/include/libcufetch/fmt/color.h similarity index 100% rename from include/fmt/color.h rename to include/libcufetch/fmt/color.h diff --git a/include/fmt/compile.h b/include/libcufetch/fmt/compile.h similarity index 100% rename from include/fmt/compile.h rename to include/libcufetch/fmt/compile.h diff --git a/include/fmt/core.h b/include/libcufetch/fmt/core.h similarity index 100% rename from include/fmt/core.h rename to include/libcufetch/fmt/core.h diff --git a/include/fmt/format-inl.h b/include/libcufetch/fmt/format-inl.h similarity index 100% rename from include/fmt/format-inl.h rename to include/libcufetch/fmt/format-inl.h diff --git a/include/fmt/format.h b/include/libcufetch/fmt/format.h similarity index 100% rename from include/fmt/format.h rename to include/libcufetch/fmt/format.h diff --git a/include/fmt/os.h b/include/libcufetch/fmt/os.h similarity index 100% rename from include/fmt/os.h rename to include/libcufetch/fmt/os.h diff --git a/include/fmt/ostream.h b/include/libcufetch/fmt/ostream.h similarity index 100% rename from include/fmt/ostream.h rename to include/libcufetch/fmt/ostream.h diff --git a/include/fmt/ranges.h b/include/libcufetch/fmt/ranges.h similarity index 100% rename from include/fmt/ranges.h rename to include/libcufetch/fmt/ranges.h diff --git a/include/fmt/std.h b/include/libcufetch/fmt/std.h similarity index 100% rename from include/fmt/std.h rename to include/libcufetch/fmt/std.h diff --git a/include/libcufetch/parse.hh b/include/libcufetch/parse.hh new file mode 100644 index 00000000..c71cfc2c --- /dev/null +++ b/include/libcufetch/parse.hh @@ -0,0 +1,99 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#pragma once + +#include <string> +#include <unordered_map> +#include <vector> + +#include "libcufetch/common.hh" +#include "libcufetch/config.hh" + +// C ABI is needed to prevent symbol mangling, but we don't actually need C compatibility, +// so we ignore this warning about return types that are potentially incompatible with C. +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreturn-type-c-linkage" +#endif + +struct module_t; + +// Map from a modules name to its pointer. +using moduleMap_t = std::unordered_map<std::string, const module_t&>; + +/* Context struct used when parsing tags in strings. + * @param input The string to parse + * @param modulesInfo The system infos + * @param pureOutput The output of the string but without tags + * @param layout The layout of customfetch + * @param tmp_layout The temponary layout to be used for multiple-line modules + * @param config The config + * @param no_more_reset If we are recursively parsing, e.g we are inside tags + */ +struct EXPORT parse_args_t +{ + const moduleMap_t& modulesInfo; + std::string& pureOutput; + std::vector<std::string>& layout; + std::vector<std::string>& tmp_layout; + const ConfigBase& config; + bool parsingLayout; + bool firstrun_clr = true; + bool no_more_reset = false; +}; + +/* Parse input, in-place, with data from modulesInfo. + * Documentation on formatting is in the flag -w or the customfetch.1 manual. + * @param input The string to parse + * @param modulesInfo The system infos + * @param pureOutput The output of the string but without tags + * @param layout The layout of customfetch + * @param tmp_layout The temponary layout to be used for multiple-line modules + * @param config The config + * @param parsingLayout If we are parsing layout or not + * @param no_more_reset If we are recursively parsing, e.g we are inside tags + */ +std::string parse(std::string input, const moduleMap_t& modulesInfo, std::string& pureOutput, + std::vector<std::string>& layout, std::vector<std::string>& tmp_layout, const ConfigBase& config, + const bool parsingLayout, bool& no_more_reset); + +/* Parse input, in-place, with data from modulesInfo. + * Documentation on formatting is in the flag -w or the customfetch.1 manual. + * @param input The string to parse + * @param parse_args The parse arguments to be used (parse_args_t) + */ +APICALL EXPORT std::string parse(const std::string& input, parse_args_t& parse_args); + +/* + * Create a colored percentage from parse() + * @param n1 The first number + * @param n2 The second number + * @param parse_args The parse() parameters + * @param invert Is the result high number bad or good? + * @return The colored percentage with ending % + */ +APICALL EXPORT std::string get_and_color_percentage(const float n1, const float n2, parse_args_t& parse_args, + const bool invert = false); diff --git a/include/toml++/toml.hpp b/include/libcufetch/toml++/toml.hpp similarity index 100% rename from include/toml++/toml.hpp rename to include/libcufetch/toml++/toml.hpp diff --git a/include/libs/fmt b/include/libs/fmt new file mode 120000 index 00000000..1149671d --- /dev/null +++ b/include/libs/fmt @@ -0,0 +1 @@ +../libcufetch/fmt \ No newline at end of file diff --git a/include/libs/getopt_port/getopt.h b/include/libs/getopt_port/getopt.h new file mode 100644 index 00000000..ac2f991b --- /dev/null +++ b/include/libs/getopt_port/getopt.h @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2012-2023, Kim Grasman <kim.grasman@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Kim Grasman nor the + * names of contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL KIM GRASMAN BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ******************************************************************************/ + +#ifndef INCLUDED_GETOPT_PORT_H +#define INCLUDED_GETOPT_PORT_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +extern char* optarg; +extern int optind, opterr, optopt; + +struct option { + const char* name; + int has_arg; + int* flag; + int val; +}; + +int getopt(int argc, char* const argv[], const char* optstring); + +int getopt_long(int argc, char* const argv[], + const char* optstring, const struct option* longopts, int* longindex); + +#if defined(__cplusplus) +} +#endif + +#endif // INCLUDED_GETOPT_PORT_H diff --git a/include/json.h b/include/libs/json.h similarity index 100% rename from include/json.h rename to include/libs/json.h diff --git a/include/rapidxml-1.13/rapidxml.hpp b/include/libs/rapidxml-1.13/rapidxml.hpp similarity index 100% rename from include/rapidxml-1.13/rapidxml.hpp rename to include/libs/rapidxml-1.13/rapidxml.hpp diff --git a/include/rapidxml-1.13/rapidxml_iterators.hpp b/include/libs/rapidxml-1.13/rapidxml_iterators.hpp similarity index 100% rename from include/rapidxml-1.13/rapidxml_iterators.hpp rename to include/libs/rapidxml-1.13/rapidxml_iterators.hpp diff --git a/include/rapidxml-1.13/rapidxml_print.hpp b/include/libs/rapidxml-1.13/rapidxml_print.hpp similarity index 100% rename from include/rapidxml-1.13/rapidxml_print.hpp rename to include/libs/rapidxml-1.13/rapidxml_print.hpp diff --git a/include/rapidxml-1.13/rapidxml_utils.hpp b/include/libs/rapidxml-1.13/rapidxml_utils.hpp similarity index 100% rename from include/rapidxml-1.13/rapidxml_utils.hpp rename to include/libs/rapidxml-1.13/rapidxml_utils.hpp diff --git a/include/stb_image.h b/include/libs/stb_image.h similarity index 100% rename from include/stb_image.h rename to include/libs/stb_image.h diff --git a/include/switch_fnv1a.hpp b/include/libs/switch_fnv1a.hpp similarity index 100% rename from include/switch_fnv1a.hpp rename to include/libs/switch_fnv1a.hpp diff --git a/include/libs/tiny-process-library/process.hpp b/include/libs/tiny-process-library/process.hpp new file mode 100644 index 00000000..49999e6d --- /dev/null +++ b/include/libs/tiny-process-library/process.hpp @@ -0,0 +1,184 @@ +#ifndef TINY_PROCESS_LIBRARY_HPP_ +#define TINY_PROCESS_LIBRARY_HPP_ +#include <functional> +#include <memory> +#include <mutex> +#include <string> +#include <thread> +#include <unordered_map> +#include <vector> +#ifndef _WIN32 +#include <sys/wait.h> +#endif + +namespace TinyProcessLib { +/// Additional parameters to Process constructors. +struct Config { + /// Buffer size for reading stdout and stderr. Default is 131072 (128 kB). + std::size_t buffer_size = 131072; + /// Set to true to inherit file descriptors from parent process. Default is false. + /// On Windows: has no effect unless read_stdout==nullptr, read_stderr==nullptr and open_stdin==false. + bool inherit_file_descriptors = false; + + /// If set, invoked when process stdout is closed. + /// This call goes after last call to read_stdout(). + std::function<void()> on_stdout_close = nullptr; + /// If set, invoked when process stderr is closed. + /// This call goes after last call to read_stderr(). + std::function<void()> on_stderr_close = nullptr; + + /// On Windows only: controls how the process is started, mimics STARTUPINFO's wShowWindow. + /// See: https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/ns-processthreadsapi-startupinfoa + /// and https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-showwindow + enum class ShowWindow { + hide = 0, + show_normal = 1, + show_minimized = 2, + maximize = 3, + show_maximized = 3, + show_no_activate = 4, + show = 5, + minimize = 6, + show_min_no_active = 7, + show_na = 8, + restore = 9, + show_default = 10, + force_minimize = 11 + }; + /// On Windows only: controls how the window is shown. + ShowWindow show_window{ShowWindow::show_default}; + + /// Set to true to break out of flatpak sandbox by prepending all commands with `/usr/bin/flatpak-spawn --host` + /// which will execute the command line on the host system. + /// Requires the flatpak `org.freedesktop.Flatpak` portal to be opened for the current sandbox. + /// See https://docs.flatpak.org/en/latest/flatpak-command-reference.html#flatpak-spawn. + bool flatpak_spawn_host = false; +}; + +/// Platform independent class for creating processes. +/// Note on Windows: it seems not possible to specify which pipes to redirect. +/// Thus, at the moment, if read_stdout==nullptr, read_stderr==nullptr and open_stdin==false, +/// the stdout, stderr and stdin are sent to the parent process instead. +class Process { +public: +#ifdef _WIN32 + typedef unsigned long id_type; // Process id type + typedef void *fd_type; // File descriptor type +#ifdef UNICODE + typedef std::wstring string_type; +#else + typedef std::string string_type; +#endif +#else + typedef pid_t id_type; + typedef int fd_type; + typedef std::string string_type; +#endif + typedef std::unordered_map<string_type, string_type> environment_type; + +private: + class Data { + public: + Data() noexcept; + id_type id; +#ifdef _WIN32 + void *handle{nullptr}; +#endif + int exit_status{-1}; + }; + +public: + /// Starts a process with the environment of the calling process. + Process(const std::vector<string_type> &arguments, const string_type &path = string_type(), + std::function<void(const char *bytes, size_t n)> read_stdout = nullptr, + std::function<void(const char *bytes, size_t n)> read_stderr = nullptr, + bool open_stdin = false, + const Config &config = {}) noexcept; + /// Starts a process with the environment of the calling process. + Process(const string_type &command, const string_type &path = string_type(), + std::function<void(const char *bytes, size_t n)> read_stdout = nullptr, + std::function<void(const char *bytes, size_t n)> read_stderr = nullptr, + bool open_stdin = false, + const Config &config = {}) noexcept; + + /// Starts a process with specified environment. + Process(const std::vector<string_type> &arguments, + const string_type &path, + const environment_type &environment, + std::function<void(const char *bytes, size_t n)> read_stdout = nullptr, + std::function<void(const char *bytes, size_t n)> read_stderr = nullptr, + bool open_stdin = false, + const Config &config = {}) noexcept; + /// Starts a process with specified environment. + Process(const string_type &command, + const string_type &path, + const environment_type &environment, + std::function<void(const char *bytes, size_t n)> read_stdout = nullptr, + std::function<void(const char *bytes, size_t n)> read_stderr = nullptr, + bool open_stdin = false, + const Config &config = {}) noexcept; +#ifndef _WIN32 + /// Starts a process with the environment of the calling process. + /// Supported on Unix-like systems only. + /// Since the command line is not known to the Process object itself, + /// this overload does not support the flatpak_spawn_host configuration. + Process(const std::function<void()> &function, + std::function<void(const char *bytes, size_t n)> read_stdout = nullptr, + std::function<void(const char *bytes, size_t n)> read_stderr = nullptr, + bool open_stdin = false, + const Config &config = {}); +#endif + __attribute__((visibility("default"))) ~Process() noexcept; + + /// Get the process id of the started process. + id_type get_id() const noexcept; + /// Wait until process is finished, and return exit status. + int get_exit_status() noexcept; + /// If process is finished, returns true and sets the exit status. Returns false otherwise. + bool try_get_exit_status(int &exit_status) noexcept; + /// Write to stdin. + bool write(const char *bytes, size_t n); + /// Write to stdin. Convenience function using write(const char *, size_t). + bool write(const std::string &str); + /// Close stdin. If the process takes parameters from stdin, use this to notify that all parameters have been sent. + void close_stdin() noexcept; + + /// Kill the process. force=true is only supported on Unix-like systems. + void kill(bool force = false) noexcept; + /// Kill a given process id. Use kill(bool force) instead if possible. force=true is only supported on Unix-like systems. + static void kill(id_type id, bool force = false) noexcept; +#ifndef _WIN32 + /// Send the signal signum to the process. + void signal(int signum) noexcept; +#endif + +private: + Data data; + bool closed; + std::mutex close_mutex; + std::function<void(const char *bytes, size_t n)> read_stdout; + std::function<void(const char *bytes, size_t n)> read_stderr; +#ifndef _WIN32 + std::thread stdout_stderr_thread; +#else + std::thread stdout_thread, stderr_thread; +#endif + bool open_stdin; + std::mutex stdin_mutex; + + Config config; + + std::unique_ptr<fd_type> stdout_fd, stderr_fd, stdin_fd; + + id_type open(const std::vector<string_type> &arguments, const string_type &path, const environment_type *environment = nullptr) noexcept; + id_type open(const string_type &command, const string_type &path, const environment_type *environment = nullptr) noexcept; +#ifndef _WIN32 + id_type open(const std::function<void()> &function) noexcept; +#endif + void async_read() noexcept; + void close_fds() noexcept; +}; + +} // namespace TinyProcessLib + +#endif // TINY_PROCESS_LIBRARY_HPP_ diff --git a/include/libs/toml++ b/include/libs/toml++ new file mode 120000 index 00000000..f28312c2 --- /dev/null +++ b/include/libs/toml++ @@ -0,0 +1 @@ +../libcufetch/toml++ \ No newline at end of file diff --git a/include/utf8/checked.h b/include/libs/utf8/checked.h similarity index 97% rename from include/utf8/checked.h rename to include/libs/utf8/checked.h index 13311551..41e25960 100644 --- a/include/utf8/checked.h +++ b/include/libs/utf8/checked.h @@ -265,7 +265,13 @@ namespace utf8 // The iterator class template <typename octet_iterator> - class iterator : public std::iterator <std::bidirectional_iterator_tag, uint32_t> { + class iterator { + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = uint32_t; + using difference_type = std::ptrdiff_t; + using pointer = uint32_t*; + using reference = uint32_t&; octet_iterator it; octet_iterator range_start; octet_iterator range_end; diff --git a/include/utf8/core.h b/include/libs/utf8/core.h similarity index 100% rename from include/utf8/core.h rename to include/libs/utf8/core.h diff --git a/include/parse.hpp b/include/parse.hpp index 2f23926a..ded8f4d9 100644 --- a/include/parse.hpp +++ b/include/parse.hpp @@ -1,123 +1,42 @@ /* * Copyright 2025 Toni500git - * + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #ifndef _PARSE_HPP #define _PARSE_HPP -#include <string> -#include <unordered_map> -#include <variant> -#include <vector> - -#include "config.hpp" +#include "libcufetch/parse.hh" -// from query.hpp -using systemInfo_t = - std::unordered_map<std::string, std::unordered_map<std::string, std::variant<std::string, size_t, double>>>; - -/* The additional args that parse() needs for getting the necessary infos/configs. - * Only used for making the argument passing more clear. - * Always pass it non-const and by reference - */ -struct parse_args_t -{ - systemInfo_t& systemInfo; - std::string& pureOutput; - std::vector<std::string>& layout; - std::vector<std::string>& tmp_layout; - const Config& config; - const colors_t& colors; - bool parsingLayout; - bool firstrun_clr = true; - bool no_more_reset = false; -}; - -/* Parse input, in-place, with data from systemInfo. - * Documentation on formatting is in the flag -w or the customfetch.1 manual. - * @param input The string to parse - * @param systemInfo The system infos - * @param pureOutput The output of the string but without tags - * @param layout The layout of customfetch - * @param tmp_layout The temponary layout to be used for $<auto> modules - * @param config The config - * @param colors The colors - * @param parsingLayout If we are parsing layout or not - * @param no_more_reset If we are recursively parsing, e.g we are inside tags - */ -std::string parse(std::string input, systemInfo_t& systemInfo, std::string& pureOutput, - std::vector<std::string>& layout, std::vector<std::string>& tmp_layout, - const Config& config, const colors_t& colors, const bool parsingLayout, bool& no_more_reset); - -// parse() for parse_args_t& arguments -std::string parse(const std::string& input, parse_args_t& parse_args); // some times we don't want to use the original pureOutput, // so we have to create a tmp string just for the sake of the function arguments std::string parse(const std::string& input, std::string& _, parse_args_t& parse_args); -/* Set module members values to a systemInfo_t map. - * If the name of said module matches any module name, it will be added - * else, error out. - * @param moduleName The module name - * @param moduleMemberName The module member name - * @param parse_args The parse() like arguments - */ -void addValueFromModuleMember(const std::string& moduleName, const std::string& moduleMemberName, - parse_args_t& parse_args); - -/* Set module only values to a systemInfo_t map. - * If the name of said module matches any module name, it will be added - * else, error out. - * @param moduleName The module name - * @param parse_args The parse() like arguments - */ -void addValueFromModule(const std::string& moduleName, parse_args_t& parse_args); - /* - * Return an info module member value - * @param systemInfo The systemInfo_t map + * Return an info module value + * @param parse_args The parse() like arguments * @param moduleName The module name - * @param moduleMemberName The module member name - */ -std::string getInfoFromName(const systemInfo_t& systemInfo, const std::string_view moduleName, - const std::string_view moduleMemberName); - -/* - * Create a colored percentage from parse() - * @param n1 The first number - * @param n2 The second number - * @param parse_args The parse() parameters - * @param invert Is the result high number bad or good? - * @return The colored percentage with ending % */ -std::string get_and_color_percentage(const float n1, const float n2, parse_args_t& parse_args, - const bool invert = false); - -template <typename... Styles> -void append_styles(fmt::text_style& current_style, Styles&&... styles) -{ - current_style |= (styles | ...); -} +std::string getInfoFromName(parse_args_t& parse_args, const std::string& moduleName); #endif diff --git a/include/pci.ids.hpp b/include/pci.ids.hpp index 581ee461..f84a10e0 100644 --- a/include/pci.ids.hpp +++ b/include/pci.ids.hpp @@ -39893,13 +39893,13 @@ C ff Unassigned class )"; } -inline const std::string& all_ids = get_pci_ids(); +extern const std::string& all_ids; inline constexpr std::array<std::string_view, 2424> pci_vendors_array = get_pci_vendors_array(); inline constexpr std::array<int, 2424> pci_vendors_location_array = get_pci_vendors_location_array(); #else -inline const std::string& all_ids = {}; +extern const std::string& all_ids; inline constexpr std::array<std::string_view, 2424> pci_vendors_array = {}; inline constexpr std::array<int, 2424> pci_vendors_location_array = {}; diff --git a/include/platform.hpp b/include/platform.hpp index f6dc7e28..4656d13a 100644 --- a/include/platform.hpp +++ b/include/platform.hpp @@ -1,3 +1,28 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + #ifndef _PLATFORM_H_ #define _PLATFORM_H_ @@ -19,20 +44,23 @@ # define CF_LINUX 0 #endif -#if (defined(__MACOS__) || defined(__MACH__) || defined(TARGET_OS_MAC) || defined(TARGET_OS_OSX) || __is_target_vendor(apple) || __is_target_os(darwin) || __is_target_os(MacOSX)) +#if (defined(__MACOS__) || defined(__MACH__) || defined(TARGET_OS_MAC) || defined(TARGET_OS_OSX) || \ + __is_target_vendor(apple) || __is_target_os(darwin) || __is_target_os(MacOSX)) # define CF_MACOS 1 #else # define CF_MACOS 0 #endif -#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__)) +#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__CYGWIN__) || \ + defined(__MINGW32__) || defined(__MINGW64__)) # define CF_WINDOWS 1 #else # define CF_WINDOWS 0 #endif #if !(CF_LINUX || CF_ANDROID || CF_MACOS) || CF_WINDOWS -# warning "Platform currently may not be supported, only Linux, Android and MacOS. Please feel free to report any compilation errors" +# warning \ + "Platform currently may not be supported, only Linux, Android and MacOS. Please feel free to report any compilation errors" #endif -#endif // _PLATFORM_H_ +#endif // _PLATFORM_H_ diff --git a/include/query.hpp b/include/query.hpp deleted file mode 100644 index 9408593b..00000000 --- a/include/query.hpp +++ /dev/null @@ -1,355 +0,0 @@ -/* - * Copyright 2025 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#ifndef _QUERY_HPP -#define _QUERY_HPP - -#include <cstdint> -#include <fstream> -#include <string> -#include <unordered_map> -#include <variant> -#include <vector> - -#include "config.hpp" -#include "parse.hpp" -#include "util.hpp" - -extern "C" { -#if !CF_MACOS -# include <mntent.h> -# include <sys/statfs.h> -#else -# include <sys/param.h> -# include <sys/mount.h> -#endif -#include <pwd.h> -#include <sys/stat.h> -#include <sys/statvfs.h> -#include <sys/utsname.h> -#include <unistd.h> -} - -// Special variable for storing info modules values -using systemInfo_t = - std::unordered_map<std::string, std::unordered_map<std::string, std::variant<std::string, size_t, double>>>; -// used in systemInfo_t most of the time -using variant = std::variant<std::string, size_t, double>; - -inline bool is_live_mode = false; - -#define CHECK_INIT(x) if (!x || is_live_mode) {x = true;} else {return;} - -namespace Query -{ - -enum -{ - DISK_VOLUME_TYPE_HIDDEN = 1 << 2, - DISK_VOLUME_TYPE_REGULAR = 1 << 3, - DISK_VOLUME_TYPE_EXTERNAL = 1 << 4, - DISK_VOLUME_TYPE_READ_ONLY = 1 << 5, -}; - -class System -{ -public: - struct System_t - { - std::string os_pretty_name{ UNKNOWN }; - std::string os_name{ UNKNOWN }; - std::string os_id{ UNKNOWN }; - std::string os_version_id{ UNKNOWN }; - std::string os_version_codename{ UNKNOWN }; - std::string os_initsys_name{ UNKNOWN }; - std::string os_initsys_version{ UNKNOWN }; - - std::string host_modelname{ UNKNOWN }; - std::string host_version{ UNKNOWN }; - std::string host_vendor{ UNKNOWN }; - - std::string pkgs_installed{ UNKNOWN }; - }; - - System(); - - std::string kernel_name() noexcept; - std::string kernel_version() noexcept; - std::string hostname() noexcept; - std::string arch() noexcept; - std::string& os_pretty_name() noexcept; - std::string& os_name() noexcept; - std::string& os_id() noexcept; - std::string& os_initsys_name(); - std::string& os_initsys_version(); - std::string& os_versionid() noexcept; - std::string& os_version_codename() noexcept; - unsigned long& uptime() noexcept; - - // motherboard (host) - std::string& host_modelname() noexcept; - std::string& host_vendor() noexcept; - std::string& host_version() noexcept; - - std::string& pkgs_installed(const Config& config); - -private: - static System_t m_system_infos; - static bool m_bInit; - static struct utsname m_uname_infos; - static unsigned long m_uptime; -}; - -class User -{ -public: - struct User_t - { - std::string shell_path{ MAGIC_LINE }; - std::string shell_name{ MAGIC_LINE }; - std::string shell_version{ UNKNOWN }; - std::string wm_name{ MAGIC_LINE }; - std::string wm_version{ UNKNOWN }; - std::string de_name{ MAGIC_LINE }; - std::string de_version{ UNKNOWN }; - std::string term_name{ MAGIC_LINE }; - std::string term_version{ MAGIC_LINE }; - // private: - std::string m_wm_path; - }; - - User() noexcept; - - std::string name() noexcept; - std::string shell_path() noexcept; - std::string& shell_name() noexcept; - std::string& shell_version(const std::string_view shell_name); - std::string& wm_name(bool dont_query_dewm, const std::string_view term_name); - std::string& wm_version(bool dont_query_dewm, const std::string_view term_name); - std::string& de_name(bool dont_query_dewm, const std::string_view term_name, const std::string_view wm_name); - std::string& de_version(const std::string_view de_name); - std::string& term_name(); - std::string& term_version(const std::string_view term_name); - - static bool m_bDont_query_dewm; - -private: - static bool m_bInit; - static User_t m_users_infos; - static struct passwd* m_pPwd; -}; - -class Theme -{ -public: - struct Theme_t - { - std::string gtk_theme_name{ MAGIC_LINE }; - std::string gtk_icon_theme{ MAGIC_LINE }; - std::string gtk_font{ MAGIC_LINE }; - std::string cursor{ MAGIC_LINE }; - std::string cursor_size{ UNKNOWN }; - }; - - Theme(const std::uint8_t ver, systemInfo_t& queried_themes, const std::string& theme_name_version, - const Config& config, const bool gsettings_only = false); - - Theme(systemInfo_t& queried_themes, const Config& config, const bool gsettings_only = false); - - std::string gtk_theme() noexcept; - std::string gtk_icon_theme() noexcept; - std::string gtk_font() noexcept; - std::string& cursor() noexcept; - std::string& cursor_size() noexcept; - -private: - User query_user; - std::string m_wmde_name; - std::string m_theme_name; - systemInfo_t& m_queried_themes; - static Theme_t m_theme_infos; -}; - -class CPU -{ -public: - struct CPU_t - { - std::string name{ MAGIC_LINE }; - std::string nproc{ MAGIC_LINE }; - - double freq_max = 0; - double freq_min = 0; - double freq_cur = 0; - double freq_bios_limit = 0; - double temp = 0; - - // private: - double freq_max_cpuinfo = 0; - // only in Android - std::string modelname; - std::string vendor; - }; - - CPU() noexcept; - - std::string& name() noexcept; - std::string& nproc() noexcept; - - // only in Android - std::string& vendor() noexcept; - std::string& modelname() noexcept; - - double& freq_max() noexcept; - double& freq_min() noexcept; - double& freq_cur() noexcept; - double& freq_bios_limit() noexcept; - double& temp() noexcept; - -private: - static bool m_bInit; - static CPU_t m_cpu_infos; -}; - -class GPU -{ -public: - struct GPU_t - { - std::string name{ MAGIC_LINE }; - std::string vendor{ MAGIC_LINE }; - }; - - GPU(const std::string& id, systemInfo_t& queried_gpus); - - std::string& name() noexcept; - std::string& vendor() noexcept; - -private: - uint16_t m_vendor_id; - uint16_t m_device_id; - std::string m_vendor_id_s; - std::string m_device_id_s; - - static GPU_t m_gpu_infos; -}; - -class Battery -{ -public: - struct Battery_t - { - std::string modelname{ MAGIC_LINE }; - std::string vendor{ MAGIC_LINE }; - std::string status{ MAGIC_LINE }; - std::string technology{ UNKNOWN }; - std::string capacity_level{ UNKNOWN }; - double temp = 0; - double perc = 0; - }; - - Battery(); - - std::string& modelname() noexcept; - std::string& vendor() noexcept; - std::string& status() noexcept; - std::string& technology() noexcept; - std::string& capacity_level() noexcept; - double& perc() noexcept; - double& temp() noexcept; - -private: - static bool m_bInit; - static Battery_t m_battery_infos; -}; - -class Disk -{ -public: - struct Disk_t - { - std::string typefs { MAGIC_LINE }; - std::string device { MAGIC_LINE }; - std::string mountdir{ MAGIC_LINE }; - double total_amount = 0; - double free_amount = 0; - double used_amount = 0; - int types_disk = 0; - }; - - Disk(const std::string& path, systemInfo_t& queried_paths, parse_args_t& parse_args, - const bool auto_module = false); - - double& total_amount() noexcept; - double& free_amount() noexcept; - double& used_amount() noexcept; - int& types_disk() noexcept; - std::string& typefs() noexcept; - std::string& device() noexcept; - std::string& mountdir() noexcept; - - std::vector<std::string>& disks_formats() noexcept - { return m_disks_formats; } - -private: - std::vector<std::string> m_disks_formats, m_queried_devices; - static Disk_t m_disk_infos; -}; - -class RAM -{ -public: - struct RAM_t - { - double total_amount = 0; - double free_amount = 0; - double used_amount = 0; - double swap_free_amount = 0; - double swap_used_amount = 0; - double swap_total_amount = 0; - }; - - RAM() noexcept; - - double& total_amount() noexcept; - double& free_amount() noexcept; - double& used_amount() noexcept; - double& swap_free_amount() noexcept; - double& swap_used_amount() noexcept; - double& swap_total_amount() noexcept; - -private: - static bool m_bInit; - static RAM_t m_memory_infos; -}; - -} // namespace Query - -// inline Query::System query_system; -// inline Query::CPU query_cpu; -// inline Query::GPU query_gpu; -// inline Query::RAM query_ram; - -#endif diff --git a/include/texts.hpp b/include/texts.hpp new file mode 100644 index 00000000..1bef9adc --- /dev/null +++ b/include/texts.hpp @@ -0,0 +1,536 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _TEXTS_HPP_ +#define _TEXTS_HPP_ + +#include "platform.hpp" +#include <string_view> + +// cufetchpm +inline constexpr std::string_view cufetchpm_help = (R"(Usage: cufetchpm <COMMAND> [OPTIONS]... +Manage plugins for customfetch. + +Terms: + REPO: + - With install: a Git repository, either URL or local path, both containing plugins and a 'cufetchpm.toml' manifest. + - With enable OR disable: the name of a repository already installed (as listed with 'cufetchpm list'). + +Examples: + Install a plugin repository from GitHub: + cufetchpm install https://github.com/Toni500github/customfetch-plugins-github + Disable a plugin from an installed repository: + cufetchpm disable customfetch-plugins-github/github-user-fetch + Uninstall an entire plugin repository: + cufetchpm uninstall customfetch-plugins-github + +Commands: + help <COMMAND> Show help for a specific command. + install [OPTIONS] <REPO(s)>... Install one or more plugin repository from a Git repo or local path. + uninstall <REPO(s)>... Uninstall one or more installed plugin repository. + enable <REPO/PLUGIN>... Enable one or more plugins from an installed repository. + disable <REPO/PLUGIN>... Disable one or more plugins from an installed repository. + list Show all plugins installed via state.toml. + update Update and upgrade all repositories + gen-manifest Generate a template 'cufetchpm.toml' file. + +Global options: + -h, --help Show this help message. + -V, --version Show version and build information. + +)"); + +inline constexpr std::string_view cufetchpm_help_install = (R"(Usage: cufetchpm install [OPTIONS] <REPO>... + +Install one or more plugin repositories. If a given argument exists on disk, +it is treated as a local directory. Otherwise, it is treated as a Git +repository URL and will be cloned. + +All plugins found within the repository will be installed. + +Options: + -f, --force Force installation, even if already installed. + -h, --help Show help for this command. +)"); + +inline constexpr std::string_view cufetchpm_help_list = (R"(Usage: cufetchpm list [options] +List all installed plugins. + +Options: + -v, --verbose Show detailed plugin information. + -h, --help Show help for this command. +)"); + +// customfetch + +inline constexpr std::string_view customfetch_help = (R"(Usage: customfetch [OPTIONS]... +A command-line, GUI app, and Android widget system information tool (like neofetch) focused on customizability and performance. + +NOTE: Boolean flags [<BOOL>] accept: "true", 1, "enable", or empty. Any other value is treated as false. + +GENERAL OPTIONS: + -h, --help Print this help menu. + -V, --version Print version and other infos about the build. + -C, --config <PATH> Path to the config file (default: ~/.config/customfetch/config.toml). + + --gen-config [<PATH>] Generate default config file. If PATH is omitted, saves to default location. + Prompts before overwriting. + +LOGO OPTIONS: + -n, --no-logo Disable logo display. + -L, --logo-only Print only the logo (skip layout completely). + -s, --source-path <PATH> Path to custom ASCII art/image file. + + -a, --ascii-logo-type <TYPE> + Type of ASCII art (typically "small", "old", or empty for default). + Example: "-d arch -a older" looks for "arch_older.txt". + + -D, --data-dir <PATH> Path to data directory containing "ascii/" subfolder with distro logos. + -d, --distro <NAME> Use a custom distro logo (case-insensitive, e.g., "windows 11" or "ArCh"). + -p, --logo-position <POS> Logo position: "top" (default), "left", or "bottom". + -o, --offset <NUM> Space between logo and layout (default: 5). + --logo-padding-top <NUM> Logo padding from top (default: 0). + --logo-padding-left <NUM> Logo padding from left (default: 0). + +LAYOUT & FORMATTING: + -m, --layout-line <STRING> Override config layout with custom line(s). + Example: `-m "${auto}OS: $<os.name>" -m "${auto}CPU: $<cpu>"`. + + -N, --no-color Disable all colors (useful for pipes/scripts). + --layout-padding-top <NUM> Layout padding from top (default: 0). + --wrap-lines=[<BOOL>] Enable terminal line wrapping (default: false). + --title-sep <STRING> String to use for $<title.sep> (default: "-"). + --sep-reset <STRING> String that resets color (default: ":"). + --sep-reset-after=[<BOOL>] Reset color after (default) or before 'sep-reset'. + +GUI/TERMINAL OPTIONS: + -f, --font <STRING> GUI font (format: "FAMILY STYLE SIZE", e.g., "Liberation Mono Normal 12"). + -i, --image-backend <NAME> Terminal image backend ("kitty" or "viu"). + --bg-image <PATH> GUI background image path ("disable" to turn off). + +CONFIG: + -O, --override <STRING> Override a config value (non-array). Syntax: "name=value" (no spaces around "="). + Example: "auto.disk.fmt='Disk(%1): %6'". + Note: Names without dots (e.g., "sep-reset-after") gets auto-appended to "config.". + + --color <STRING> Replace a color globally. Syntax: "name=value" (no spaces around "="). + Example: "--color magenta=#FF00FF". + + --disallow-command-tag Do not allow command tags $() to be executed. + This is a safety measure for preventing malicious code to be executed because you didn't want to check the config first. + +INFORMATIONAL: + -l, --list-modules List all available info tag modules (e.g., $<cpu> or $<os.name>). + -w, --how-it-works Explain tags and general customization. + --list-logos List available ASCII logos in --data-dir. + +LIVE MODE: + --loop-ms <NUM> Run in live mode, updating every <NUM> milliseconds (min: 50). + Use inferior <NUM> than 200 to disable. Press 'q' to exit. + +EXAMPLES: + 1. Minimal output with default logo: + customfetch --no-color + + 2. Custom distro logo with live updates: + customfetch --distro "arch" --loop-ms 1000 + + 3. Override layout and colors: + customfetch -m "${magenta}OS: $<os.name>" --color "magenta=#FF00FF" + +For details, see `man customfetch` or run `--how-it-works`. +)"); + +constexpr std::string_view explain_customfetch = (R"( +customfetch is designed for maximum customizability, allowing users to display system information exactly how they want it. +The layout and logo is controlled through special tags that can output system info, execute commands, apply conditional logic, add colors, and calculate percentages with some colors. + +Tag References: +1. Information Tag ($<>) + Retrieves system information from modules. + + Syntax: $<module.submodule.sub...> or $<module> + + Examples: + - $<user.name> # Displays login username + - $<os.kernel.name> # Shows kernel name only + - $<ram> # Shows formatted RAM usage + + Use `--list-modules` to see all available modules and members. + +2. Bash Command Tag ($()) + Executes shell commands and outputs the result. + Supports full shell syntax including pipes and redirection. + + Syntax: $(command) + + Examples: + - $(echo "hello") # Outputs: hello + - $(date +%F) # Shows current date + - $(uname -r | cut -d'-' -f1) # Shows kernel version number only + +3. Conditional Tag ($[]) + Displays different outputs based on conditions. + + Syntax: $[condition,comparison,true_output,false_output] + + Examples: + - $[$<user.name>,toni,Welcome back!,Access denied] + - $[$(date +%m-%d),12-25,Merry Christmas!,] + - $[$<os.name.id>,arch,${green}I use arch btw,${red}Non-arch user] + +4. Color Tag (${}) + Applies colors and text formatting. + + Basic syntax: ${color} or ${modifiers#RRGGBB} + + Color options: + - Named colors from config + - Hex colors: ${#ff00cc} + - Special colors: ${auto} (uses logo colors) + - Reset styles: ${0} (normal), ${1} (bold reset) + + Formatting modifiers (prefix before hexcolor): + - ! = Bold + - u = Underline + - i = Italic + - s = Strikethrough + - l = Blink (terminal only) + - b = Background color + + Advanced GUI-only modifiers: + - o = Overline + - a(value) = Foreground alpha (1-65536 or 0%-100%) + - L(value) = Underline style (none/single/double/low/error) + - U(color) = Underline color (hex) + - B(color) = Background color (hex) + - S(color) = Strikethrough color (hex) + - O(color) = Overline color (hex) + - A(value) = Background alpha (1-65536 or 0%-100%) + - w(value) = Font weight (light/normal/bold/ultrabold or 100-1000) + + Examples: +GUI App only: + ${oU(#ff0000)L(double)}Error # Double red underline + ${a(50%)#00ff00}Semi-transparent green + Cross-platform: + ${\e[1;33m}Bold yellow + ${b#222222}${white}White on gray + ${auto3}The 3rd logo color + + Notes: + - customfetch will try to convert ANSI escape codes to GUI app equivalent + - customfetch will ignore GUI-specific modifiers on terminal. + - if you're using the GUI app and want to display a custom logo that's an image, all the auto colors will be the same colors as the distro ones. + +5. Percentage Tag ($%%) + Calculates and displays colored percentages. + + Syntax: $%value,total% or $%!value,total% (inverted colors) + + Examples: + - $%$<ram.used>,$<ram.total>% + - $%!50,100% (shows red if low) + - $%$(cat /sys/class/power_supply/BAT0/capacity),100% + +Pro Tip: +- Combine tags for powerful formatting: + ${u#5522dd}$[$(date +%H),12,Good ${yellow}morning,Good ${#ff8800}afternoon] + +FAQ: +Q: Why do special characters (&, <) break the GUI display? +A: Escape these characters with \\ (e.g replace "<" with "\\<" from both config and ASCII art): + This doesn't affect terminal output. + +Q: How can I use cbonsai as ASCII art? +A: 1. Create a text file containing: $(!cbonsai -p) + 2. Use: customfetch -s "/path/to/file.txt" + 3. Adjust offset for proper alignment + +Q: Does customfetch support nested tags? +A: Yes! Complex nesting is supported, for example: + $<disk($<disk($[1,1,$(echo -n $<disk(/).mountdir>),23]).mountdir>)> +)"); + +// default customfetch config +inline constexpr std::string_view AUTOCONFIG = R"#([config] + +# For more information on how customfetch works and the layout, +# Read either: +# * -w or --how-it-works +# * the manual customfetch.1 +# * if on the android app, click the button "how it works" during widget configuration +layout = [ + "$<title>", + "$<title.sep>", + "${auto}OS: $<os.name> $<system.arch>", + "${auto}Host: $<system.host>", + "${auto}Kernel: $<os.kernel>", + "${auto}Uptime: $<os.uptime>", + "${auto}Terminal: $<user.terminal>", + "${auto}Shell: $<user.shell>",)#" +#if !CF_ANDROID + R"#( + "${auto}Theme: $<theme.gtk.all.name>", + "${auto}Icons: $<theme.gtk.all.icon>", + "${auto}Font: $<theme.gtk.all.font>", + "${auto}Cursor: $<theme.cursor>", + "${auto}WM: $<user.wm.name>", + "${auto}DE: $<user.de.name>",)#" +#endif + R"#( + "$<auto.disk>", + "${auto}Swap: $<swap>", + "${auto}CPU: $<cpu>", + "${auto}GPU: $<gpu>", + "${auto}RAM: $<ram>", + "", + "$<colors>", # normal colors + "$<colors.light>" # light colors +] + +# display ascii-art or image/gif (GUI only) near layout +# put "os" for displaying the OS ascii-art +# or the "/path/to/file" for displaying custom files +# or "off" for disabling ascii-art or image displaying +source-path = "os" + +# Path to where we'll take all the distros/OSs ascii arts. +# note: it MUST contain an "ascii" subdirectory +data-dir = "/usr/share/customfetch" + +# The type of ASCII art to apply ("small", "old"). +# Basically will add "_<type>" to the logo filename. +# It will return the regular linux ascii art if it doesn't exist. +# Leave empty it for regular. +ascii-logo-type = "" + +# A char (or string) to use in $<title_sep> +title-sep = "-" + +# A separator (or string) that when encountered, will automatically +# reset color, aka. automatically add ${0} (only in layout) +# Make it empty for disabling +sep-reset = ":" + +# Should we reset color after or before the separator? +# true = after ("test ->${0} ") +# false = before ("test ${0}-> ") +sep-reset-after = false + +# Where the logo should be displayed. +# Values: "top" or "left" or "bottom" +logo-position = "left" + +# Offset between the ascii art and the layout +# Can also be rapresented as a %, but super unstable sometimes. +offset = "5" + +# Padding between the start and the ascii art +logo-padding-left = 0 + +# Padding of the ascii art from the top +logo-padding-top = 0 + +# Padding of the layout from the top +layout-padding-top = 0 + +# Usually in neofetch/fastfetch, when your terminal size is too small, +# to render some text in 1 line, they don't wrap those lines, instead they truncate them. +# Enable/Disable if you want this +wrap-lines = false + +# Used in disk, ram and swap modules. +# If true, we're going to use the SI standard byte unit (1kB == 1000 bytes) +# Else if false, we using the IEC byte unit (1KiB == 1024 bibytes) +# Really nerdy stuff +use-SI-byte-unit = false + +# Warn against tradeoffs between slower queries for availability +# e.g. falling back to gsettings when we can't find the config file for GTK +slow-query-warnings = false + +# Colors in the terminal (for Desktop/Android app, use the ones under [gui]) +black = "\e[1;30m" +red = "\e[1;31m" +green = "\e[1;32m" +yellow = "\e[1;33m" +blue = "\e[1;34m" +magenta = "\e[1;35m" +cyan = "\e[1;36m" +white = "\e[1;37m" + +# Alias colors. Basically more color variables. +# They can be used as like as the color tag. +# This is as like as using the --add-color argument +# Syntax must be "name=value", e.g "purple=magenta" or "orange=!#F08000" +alias-colors = ["purple=magenta"] + +# Colors to be used in percentage tag and modules members. +# They are used as if you're using the color tag. +# It's an array just for "convenience" +# 1st color for good +# 2nd color for normal +# 3rd color for bad +percentage-colors = ["green", "yellow", "red"] + +# $<auto.disk> config +[auto.disk] +# Format for displaying the auto detected disks infos +# %1 = mount directory +# %2 = device path +# %3 = type of filesystem +# %4 = total amount of storage +# %5 = free amount of storage +# %6 = used amount of storage +# %7 = percentage of used storage +# %8 = percentage of free storage +fmt = "${auto}Disk (%1): $<disk(%1)>" + +# Only print disks that matches the description +# of the following types: +# regular = Regular disks (internel M.2 SSD, ...) (won't be specified) +# external = External disks (USB, SATA, ...) +# read-only = Disks with read-only filesystems +# hidden = Disks that are not really mounted by the user +display-types = ["regular", "external", "read-only"] + +# In some OSes such as NixOS or Android, there might be some directories that are bind mounted. +# Bind mounted directories create an additional view of an existing directory, +# and `statfs()` on the mount point will return the filesystem statistics of the original directory. +show-duplicated = false + +# $<os.uptime> config +[os.uptime] +# how to display the name of the uptime +# e.g: hours = "hrs" -> "Uptime: 3hrs" +days = " days" +hours = " hours" +mins = " mins" +secs = " secs" + +# $<os.pkgs> config +[os.pkgs] +# Ordered list of which packages installed count should be displayed in $<os.pkgs> +# remember to not enter the same name twice, else the world will finish +# Choices: pacman, flatpak, dpkg, apk +# +# Pro-tip: if your package manager isn't listed here, yet, +# use the bash command tag in the layout +# e.g "Packages: $(pacman -Q | wc -l) (pacman)" +pkg-managers = ["pacman", "dpkg", "flatpak"] + +# Distros and package manager specific +# package manager paths for getting the packages count from path. +# They are arrays so you can add multiple paths. +# +# If you don't know what these ares, leave them by default settings +pacman-dirs = ["/var/lib/pacman/local/"] +dpkg-files = ["/var/lib/dpkg/status", "/data/data/com.termux/files/usr/var/lib/dpkg/status"] +flatpak-dirs = ["/var/lib/flatpak/app/", "~/.local/share/flatpak/app/"] +apk-files = ["/var/lib/apk/db/installed"] + +# Desktop/Android app options +[gui] + +# These are the colors you can use in the GUI mode. +# They overwrite the terminal colors from above. +# They can only have hexcodes colors and its modifiers +black = "!#000005" +red = "!#ff2000" +green = "!#00ff00" +blue = "!#00aaff" +cyan = "!#00ffff" +yellow = "!#ffff00" +magenta = "!#f881ff" +white = "!#ffffff" + +# Path to image as a background. +# put "disable" for disabling and use the theme color as background. +bg-image = "disable" + +# Path to gtk css file to be used. +# put "disable" for disabling. +gtk-css = "disable" + +)#"; + +inline constexpr std::string_view AUTO_MANIFEST = R"([repository] +# The repositry name. +# It must contain only alpha-numeric characters and symbols such as '-' or '_' +name = "repo_name" + +# The repository git clone / homepage url. +url = "https://github.com/user/repo" + +# Platform-dependent packages required to build all plugins in this repository. +# NOTE: This will ONLY tell the user which packages are missing; +# it will not actually install them. +# +# Use the "all" key for dependencies common to every platform. +# Current platforms: all, linux, macos, android +[dependencies] +all = ["pkg-config", "cmake"] +linux = ["wayland-protocols", "xorg-dev"] +android = ["ndk-build"] +macos = ["gtk+3"] + +# From now on, each table that is neiter "repository" nor "dependencies" will be treated as a plugin entry. +# The tables' names still must conform alpha-numeric characters and symbols such as '-' or '_' +[test-plugin] + +# The plugin description. +description = "Test plugin" + +# The plugin authors. +authors = ["user1", "friend_user1"] + +# The plugin SPDX License Identifiers (not validated) +licenses = ["MIT", "GPL-2.0"] + +# A list of registered root modules the plugin can provide. +prefixes = ["github", "git"] + +# What platforms are supported by the plugin (case-sensitive). +# Use just ["all"] for cross-platform plugins. +# Current platforms: all, linux, macos, android +platforms = ["all"] + +# The directory where the final plugin output shall be seen. +# It will be evaluated/checked after building the plugin library. +# The path is relative to the repository root unless absolute. +output-dir = "build/plugin-dir/" + +# A list of commands to be executed for building the plugin. +# All commands are executed in a single shared shell session, +# so environment variables, `cd`, and other shell state persist across steps. +# Commands are executed in order and stop at the first failure. +build-steps = [ + "make -C ./test-plugin-entry/", + "mkdir -p ./build/plugin-dir/", + "mv ./test-plugin-entry/library.so ./build/plugin-dir/library.so" +])"; + +#endif // !_TEXTS_HPP_ diff --git a/include/util.hpp b/include/util.hpp index c8d6cee5..221e2db6 100644 --- a/include/util.hpp +++ b/include/util.hpp @@ -1,25 +1,25 @@ /* * Copyright 2025 Toni500git - * + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ @@ -31,12 +31,14 @@ #include <sys/types.h> #include <cstdlib> +#include <filesystem> #include <iostream> #include <string> #include <vector> #include "fmt/base.h" #include "fmt/color.h" +#include "libcufetch/common.hh" #include "platform.hpp" // clang-format off @@ -62,81 +64,71 @@ struct byte_units_t #define _(s) (char*)s #endif -constexpr const char NOCOLOR[] = "\033[0m"; -constexpr const char NOCOLOR_BOLD[] = "\033[0m\033[1m"; -constexpr const char UNKNOWN[] = "(unknown)"; - -// Usually in neofetch/fastfetch when some infos couldn't be queried, -// they remove it from the display. With customfetch is kinda difficult to know when to remove -// the info to display, since it's all modular with tags, so I have created -// magic line to be sure that I don't cut the wrong line. -// -// Every instance of this string in a layout line, the whole line will be erased. -constexpr const char MAGIC_LINE[] = "(cut this line NOW!! RAHHH)"; +/* lib = library to load (string) */ +#define LOAD_LIBRARY(lib) dlopen(lib, RTLD_NOW); -/* lib = library to load (string) - * code = code to execute if anything goes wrong - */ -#define LOAD_LIBRARY(lib, code) \ - void* handle = dlopen(lib, RTLD_LAZY); \ - if (!handle) \ - code; - -/* ret_type = type of what the function returns +/* handler = the library handle + * ret_type = type of what the function returns * func = the function name * ... = the arguments in a function if any */ -#define LOAD_LIB_SYMBOL(ret_type, func, ...) \ - typedef ret_type (*func##_t)(__VA_ARGS__); \ - func##_t func = reinterpret_cast<func##_t>(dlsym(handle, #func)); +#define LOAD_LIB_SYMBOL(handler, ret_type, func, ...) \ + typedef ret_type (*func##_t)(__VA_ARGS__); \ + func##_t func = reinterpret_cast<func##_t>(dlsym(handler, #func)); + +#define UNLOAD_LIBRARY(handle) dlclose(handle); -#define UNLOAD_LIBRARY() dlclose(handle); +#if CF_WINDOWS + constexpr char LIBRARY_EXTENSION[] = ".dll"; +#elif CF_MACOS + constexpr char LIBRARY_EXTENSION[] = ".dylib"; +#else + constexpr char LIBRARY_EXTENSION[] = ".so"; +#endif -#define BOLD_COLOR(x) (fmt::emphasis::bold | fmt::fg(x)) +inline bool is_live_mode = false; /* https://stackoverflow.com/questions/874134/find-out-if-string-ends-with-another-string-in-c#874160 * Check if substring exists at the end * @param fullString The string to lookup * @param ending The string to check at the end of fullString */ -bool hasEnding(const std::string_view fullString, const std::string_view ending); +EXPORT bool hasEnding(const std::string_view fullString, const std::string_view ending); /* https://stackoverflow.com/questions/874134/find-out-if-string-ends-with-another-string-in-c#874160 * Check if substring exists at the start * @param fullString The string to lookup * @param start The string to check at the start of fullString */ -bool hasStart(const std::string_view fullString, const std::string_view start); +EXPORT bool hasStart(const std::string_view fullString, const std::string_view start); -std::vector<std::string> split(const std::string_view text, char delim); +/* Spilt a string into a vector using a delimeter + * @param text The string to split + * @param delim The delimeter used for spliting the text + */ +EXPORT std::vector<std::string> split(const std::string_view text, const char delim); /* Get device name from `all_ids` in pci.ids.hpp * @param dev_entry_pos Line position from where the device is located */ -std::string name_from_entry(size_t dev_entry_pos); +EXPORT std::string name_from_entry(size_t dev_entry_pos); /* Get vendor device name from `all_ids` in pci.ids.hpp * @param vendor_entry_pos Line position from where the device is located * @param vendor_id_s The vendor ID (e.g 10de) */ -std::string vendor_from_entry(const size_t vendor_entry_pos, const std::string_view vendor_id_s); +EXPORT std::string vendor_from_entry(const size_t vendor_entry_pos, const std::string_view vendor_id_s); /* Function to perform binary search on the pci vendors array to find a device from a vendor. * @param vendor_id_s The vendor ID (e.g 10de) * @param pci_id_s The device ID (e.g 1f82) */ -std::string binarySearchPCIArray(const std::string_view vendor_id_s, const std::string_view pci_id_s); +EXPORT std::string binarySearchPCIArray(const std::string_view vendor_id_s, const std::string_view pci_id_s); /* Function to perform binary search on the pci vendors array to find a vendor. * @param vendor_id_s The vendor ID (e.g 10de) */ -std::string binarySearchPCIArray(const std::string_view vendor_id_s); - -/* http://stackoverflow.com/questions/478898/ddg#478960 - * Execute shell command and read its output from stdout. - * @param cmd The command to execute - */ -std::string read_shell_exec(const std::string_view cmd); +EXPORT std::string binarySearchPCIArray(const std::string_view vendor_id_s); /* Get file value from a file and trim quotes and double-quotes * @param iterIndex The iteration index used for getting the necessary value only tot times @@ -144,45 +136,38 @@ std::string read_shell_exec(const std::string_view cmd); * @param str The string to assign the trimmed value, inline * @param amount The amount to be used in the line.substr() (should be used with something like "foobar"_len) */ -void getFileValue(u_short& iterIndex, const std::string_view line, std::string& str, const size_t& amount); +EXPORT void getFileValue(u_short& iterIndex, const std::string_view line, std::string& str, const size_t& amount); /* Covert bytes (or bibytes) to be accurate to the max prefix (maxprefix or YB/YiB) * @param num The number to convert * @param base Base to devide (1000 = bytes OR 1024 = bibytes) * @param maxprefix The maxinum prefix we can go up to (empty for ignore) */ -byte_units_t auto_devide_bytes(const double num, const std::uint16_t base, const std::string_view maxprefix = ""); +EXPORT byte_units_t auto_divide_bytes(const double num, const std::uint16_t base, const std::string_view maxprefix = ""); /* Covert bytes (or bibytes) to be accurate to a specific prefix * @param num The number to convert * @param prefix The prefix we want to convert to (GiB, MB, etc.) */ -byte_units_t devide_bytes(const double num, const std::string_view prefix); +EXPORT byte_units_t divide_bytes(const double num, const std::string_view prefix); /* Check if file is image (static or gif). * Doesn't check for mp4, mp3 or other binary formats * @param bytes The header bytes of the file */ -bool is_file_image(const unsigned char* bytes); +EXPORT bool is_file_image(const unsigned char* bytes); /* Write error message and exit if EOF (or CTRL-D most of the time) * @param cin The std::cin used for getting the input */ -void ctrl_d_handler(const std::istream& cin); +EXPORT void ctrl_d_handler(const std::istream& cin); /* Replace special symbols such as ~ and $ (at the begging) in std::string * @param str The string * @param dont Don't do it * @return The modified string */ -std::string expandVar(std::string ret, bool dont = false); - -/* Executes commands with execvp() and keep the program running without existing - * @param cmd_str The command to execute - * @param exitOnFailure Whether to call exit(1) on command failure. - * @return true if the command successed, else false - */ -bool taur_exec(const std::vector<std::string_view> cmd_str, const bool noerror_print = true); +EXPORT std::string expandVar(std::string ret, bool dont = false); /* Get a relative path from an enviroment variable (PATH, XDG_DATA_DIRS, ...) * Either path of an executable, directory, etc... @@ -191,22 +176,22 @@ bool taur_exec(const std::vector<std::string_view> cmd_str, const bool noerror_p * @param mode Mode of the file/directory using the enums declared in sys/stat.h * Such as S_IXUSR for executables, S_IFDIR for directories, etc. */ -std::string get_relative_path(const std::string_view relative_path, const std::string_view env, const long long mode); +EXPORT std::string get_relative_path(const std::string_view relative_path, const std::string_view env, const long long mode); /* Simulate behaviour of the command `which` * @param command The command to lookup in the $PATH */ -std::string which(const std::string_view command); +EXPORT std::string which(const std::string_view command); /* Get file path from $XDG_DATA_DIRS * @param file The file to lookup in the env */ -std::string get_data_path(const std::string_view file); +EXPORT std::string get_data_path(const std::string_view file); /* Get directory path from $XDG_DATA_DIRS * @param dir The directory to lookup in the env */ -std::string get_data_dir(const std::string_view dir); +EXPORT std::string get_data_dir(const std::string_view dir); /* Read a binary file and get its current line, * which simulates the behaviour of the command `strings` but one line at the time @@ -228,7 +213,7 @@ std::string get_data_dir(const std::string_view dir); * } * return false; */ -bool read_binary_file(std::ifstream& f, std::string& ret); +EXPORT bool read_binary_file(std::ifstream& f, std::string& ret); /* https://gist.github.com/GenesisFR/cceaf433d5b42dcdddecdddee0657292 * Replace every instances (inplace) of a substring @@ -236,7 +221,7 @@ bool read_binary_file(std::ifstream& f, std::string& ret); * @param from The substring to lookup to be replaced * @param to The substring to replace in instances of `from` */ -void replace_str(std::string& str, const std::string_view from, const std::string_view to); +EXPORT void replace_str(std::string& str, const std::string_view from, const std::string_view to); /* Executes commands with execvp() and read its output * either from stdout or stderr @@ -246,47 +231,47 @@ void replace_str(std::string& str, const std::string_view from, const std::strin * @param noerror_print Print errors (default true) * @return true if the command successed, else false */ -bool read_exec(std::vector<const char*> cmd, std::string& output, bool useStdErr = false, bool noerror_print = true); +EXPORT bool read_exec(std::vector<std::string> cmd, std::string& output, bool useStdErr = false, bool noerror_print = true); /* Make whole string lowercase * @param str The string to use */ -std::string str_tolower(std::string str); +EXPORT std::string str_tolower(std::string str); /* Make whole string uppercase * @param str The string to use */ -std::string str_toupper(std::string str); +EXPORT std::string str_toupper(std::string str); /* Remove all white spaces (' ', '\t', '\n') from string inplace! * @param input The string to strip * @param padding_only Just trim the string */ -void strip(std::string& input, bool padding_only = true); +EXPORT void strip(std::string& input, bool padding_only = true); /* Read file content (usually from /sys) * and return its first (and only) line, else UNKNOWN * @param path The path of the file to read * @param report_error Report error if any */ -std::string read_by_syspath(const std::string_view path, bool report_error = false); +EXPORT std::string read_by_syspath(const std::string_view path, bool report_error = false); /* Convert hex color (#255224) to a fmt::rgb * @param hexstr The hex color string (must have a '#' at the start) */ -fmt::rgb hexStringToColor(const std::string_view hexstr); +EXPORT fmt::rgb hexStringToColor(const std::string_view hexstr); /* Abbreviate the vendors names * @param vendor The vendor name */ -std::string shorten_vendor_name(std::string vendor); +EXPORT std::string shorten_vendor_name(std::string vendor); /* * Get the user config directory * either from $XDG_CONFIG_HOME or from $HOME/.config/ * @return user's config directory */ -std::string getHomeConfigDir(); +EXPORT std::filesystem::path getHomeConfigDir(); /* * Get the customfetch config directory @@ -294,58 +279,28 @@ std::string getHomeConfigDir(); * from getHomeConfigDir() * @return customfetch's config directory */ -std::string getConfigDir(); +EXPORT std::filesystem::path getConfigDir(); + +/* + * Get the user cache directory + * either from $XDG_CACHE_HOME or from $HOME/.cache/ + * @return user's cache directory + */ +EXPORT std::filesystem::path getHomeCacheDir(); + +/* + * Get the customfetch cache directory + * @return customfetch's cache directory + */ +EXPORT std::filesystem::path getCacheDir(); #if CF_ANDROID /* Get android property name such as "ro.product.marketname" * @param name The property name */ -std::string get_android_property(const std::string_view name); +EXPORT std::string get_android_property(const std::string_view name); #endif -template <typename... Args> -void error(const std::string_view fmt, Args&&... args) noexcept -{ - fmt::print(stderr, BOLD_COLOR(fmt::rgb(fmt::color::red)), "ERROR: {}\033[0m\n", - fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); -} - -template <typename... Args> -void die(const std::string_view fmt, Args&&... args) noexcept -{ - fmt::print(stderr, BOLD_COLOR(fmt::rgb(fmt::color::red)), "FATAL: {}\033[0m\n", - fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); - std::exit(1); -} - -#if DEBUG -inline bool debug_print = true; -#else -inline bool debug_print = false; -#endif - -template <typename... Args> -void debug(const std::string_view fmt, Args&&... args) noexcept -{ - if (debug_print) - fmt::print(BOLD_COLOR((fmt::rgb(fmt::color::hot_pink))), "[DEBUG]:\033[0m {}\n", - fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); -} - -template <typename... Args> -void warn(const std::string_view fmt, Args&&... args) noexcept -{ - fmt::print(BOLD_COLOR((fmt::rgb(fmt::color::yellow))), "WARNING: {}\033[0m\n", - fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); -} - -template <typename... Args> -void info(const std::string_view fmt, Args&&... args) noexcept -{ - fmt::print(BOLD_COLOR((fmt::rgb(fmt::color::cyan))), "INFO: {}\033[0m\n", - fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); -} - /** Ask the user a yes or no question. * @param def The default result * @param fmt The format string @@ -353,7 +308,7 @@ void info(const std::string_view fmt, Args&&... args) noexcept * @returns the result, y = true, n = false, only returns def if the result is def */ template <typename... Args> -bool askUserYorN(bool def, const std::string_view fmt, Args&&... args) +EXPORT bool askUserYorN(bool def, const std::string_view fmt, Args&&... args) { const std::string& inputs_str = fmt::format(" [{}]: ", def ? "Y/n" : "y/N"); std::string result; @@ -361,7 +316,7 @@ bool askUserYorN(bool def, const std::string_view fmt, Args&&... args) fmt::print("{}", inputs_str); while (std::getline(std::cin, result) && (result.length() > 1)) - fmt::print(BOLD_COLOR(fmt::rgb(fmt::color::yellow)), "Please answear y or n,{}", inputs_str); + warn("Please answear y or n {}", inputs_str); ctrl_d_handler(std::cin); diff --git a/libcufetch/Makefile b/libcufetch/Makefile new file mode 100644 index 00000000..0fe2fbba --- /dev/null +++ b/libcufetch/Makefile @@ -0,0 +1,44 @@ +UNAME_S := $(shell uname -s) + +# is macos? +ifeq ($(UNAME_S),Darwin) + LIBNAME := libcufetch.dylib + INSTALL_NAME := -Wl,-install_name,@rpath/$(LIBNAME) + SHARED_FLAG := -dynamiclib + SONAME_FLAGS := +else + LIBNAME := libcufetch.so.1.0.0 + INSTALL_NAME := -Wl,-soname,libcufetch.so.1 + SHARED_FLAG := -shared + SONAME_FLAGS := -Wl,--export-dynamic +endif + +CXX ?= g++ +GUI_APP ?= 0 +SRC = $(wildcard *.cc) +OBJ = $(SRC:.cc=.o) ../$(BUILDDIR)/toml.o +LDLIBS := ../$(BUILDDIR)/libfmt.a ../$(BUILDDIR)/libtiny-process-library.a +OUTPUT := ../$(BUILDDIR)/$(LIBNAME) +CXXFLAGS += -fvisibility-inlines-hidden -fvisibility=hidden -std=$(CXXSTD) -I../include -I../include/libs -fPIC -DGUI_APP=$(GUI_APP) + +all: $(OUTPUT) + @if [ "$(UNAME_S)" = "Linux" ]; then \ + ln -sf libcufetch.so.1.0.0 ../$(BUILDDIR)/libcufetch.so.1; \ + ln -sf libcufetch.so.1.0.0 ../$(BUILDDIR)/libcufetch.so; \ + elif [ "$(UNAME_S)" = "Darwin" ]; then \ + ln -sf libcufetch.dylib ../$(BUILDDIR)/libcufetch.1.dylib; \ + ln -sf libcufetch.dylib ../$(BUILDDIR)/libcufetch.1.0.0.dylib; \ + fi + +%.o: %.cc + $(CXX) $(CXXFLAGS) -c -o $@ $< + +$(OUTPUT): $(OBJ) $(LDLIBS) + $(CXX) $(SHARED_FLAG) $(CXXFLAGS) $(OBJ) \ + $(SONAME_FLAGS) $(INSTALL_NAME) \ + -o $@ $(LDLIBS) + +clean: + rm -f *.o *.so *.a ../$(BUILDDIR)/libcufetch.so + +.PHONY: clean all libcufetch diff --git a/libcufetch/cufetch.cc b/libcufetch/cufetch.cc new file mode 100644 index 00000000..039ef503 --- /dev/null +++ b/libcufetch/cufetch.cc @@ -0,0 +1,17 @@ +#include "libcufetch/cufetch.hh" + +static std::vector<module_t> modules; + +static void addModule(const module_t& module, const std::string& prefix = "") +{ + modules.emplace_back(module).name = prefix + module.name; + + for (const module_t& submodule : module.submodules) + addModule(submodule, prefix + module.name + "."); +} + +/* Register a module, and its submodules, to customfetch. */ +APICALL EXPORT void cfRegisterModule(const module_t& module) { addModule(module); } + +/* Get a list of all modules registered. */ +APICALL EXPORT const std::vector<module_t>& cfGetModules() { return modules; } diff --git a/libcufetch/parse.cc b/libcufetch/parse.cc new file mode 100644 index 00000000..29652593 --- /dev/null +++ b/libcufetch/parse.cc @@ -0,0 +1,833 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "parse.hpp" + +#include <unistd.h> + +#include <algorithm> +#include <array> +#include <cstdlib> +#include <ios> +#include <optional> +#include <sstream> +#include <string> +#include <string_view> +#include <vector> + +#include "tiny-process-library/process.hpp" +#include "libcufetch/common.hh" +#include "libcufetch/config.hh" +#include "libcufetch/cufetch.hh" +#include "fmt/format.h" +#include "switch_fnv1a.hpp" +#include "util.hpp" + +class Parser +{ +public: + Parser(const std::string_view src, std::string& pureOutput) : src{ src }, pureOutput{ pureOutput } {} + + bool try_read(const char c) + { + if (is_eof()) + return false; + + if (src[pos] == c) + { + ++pos; + return true; + } + + return false; + } + + char read_char(const bool add_pureOutput = false) + { + if (is_eof()) + return 0; + + if (add_pureOutput) + pureOutput += src[pos]; + + ++pos; + return src[pos - 1]; + } + + bool is_eof() + { return pos >= src.length(); } + + void rewind(const size_t count = 1) + { pos -= std::min(pos, count); } + + const std::string_view src; + std::string& pureOutput; + size_t dollar_pos = 0; + size_t pos = 0; +}; + +// useless useful tmp string for parse() without using the original +// pureOutput +std::string _; + +#if GUI_APP +// Get span tags from an ANSI escape color such as \e[0;31m +// @param noesc_str The ansi color without \\e[ or \033[ +// @param colors The colors struct we'll look at +// @return An array of 3 span tags elements in the follow: color, weight, type +static std::array<std::string, 3> get_ansi_color(const std::string_view noesc_str, const ConfigBase& config) +{ + const size_t first_m = noesc_str.rfind('m'); + if (first_m == std::string::npos) + die(_("Parser: failed to parse layout/ascii art: missing 'm' while using ANSI color escape code in '{}'"), + noesc_str); + + std::string col{ noesc_str.data() }; + col.erase(first_m); // 1;42 + + std::string weight{ hasStart(col, "1;") ? "bold" : "normal" }; + std::string type{ "fgcolor" }; // either fgcolor or bgcolor + + if (hasStart(col, "1;") || hasStart(col, "0;")) + col.erase(0, 2); + + debug("col = {}", col); + const int n = std::stoi(col); + + if ((n >= 100 && n <= 107) || (n >= 40 && n <= 47)) + type = "bgcolor"; + + // last number + // clang-format off + switch (col.back()) + { + case '0': col = config.getValue<std::string>("gui.black", "!#000005"); break; + case '1': col = config.getValue<std::string>("gui.red", "!#ff2000"); break; + case '2': col = config.getValue<std::string>("gui.green", "!#00ff00"); break; + case '3': col = config.getValue<std::string>("gui.yellow", "!#ffff00"); break; + case '4': col = config.getValue<std::string>("gui.blue", "!#00aaff"); break; + case '5': col = config.getValue<std::string>("gui.magenta", "!#ff11cc"); break; + case '6': col = config.getValue<std::string>("gui.cyan", "!#00ffff"); break; + case '7': col = config.getValue<std::string>("gui.white", "!#ffffff"); break; + } + + if (col.at(0) != '#') + col.erase(0, col.find('#')); + + if ((n >= 100 && n <= 107) || (n >= 90 && n <= 97)) + { + const fmt::rgb color = hexStringToColor(col); + const uint r = color.r * 0.65f + 0xff * 0.35f; + const uint b = color.b * 0.65f + 0xff * 0.35f; + const uint g = color.g * 0.65f + 0xff * 0.35f; + const uint result = (r << 16) | (g << 8) | (b); + + std::stringstream ss; + ss << std::hex << result; + col = ss.str(); + col.insert(0, "#"); + } + + return { col, weight, type }; + // clang-format on +} + +// Convert an ANSI escape RGB color, such as \e[38;2;132;042;231m +// into an hex color string +// @param noesc_str The ansi color without \\e[ or \033[ +// @return The hex equivalent string +static std::string convert_ansi_escape_rgb(const std::string_view noesc_str) +{ + if (std::count(noesc_str.begin(), noesc_str.end(), ';') < 4) + die(_("ANSI escape code color '\\e[{}' should have an rgb type value\n" + "e.g \\e[38;2;255;255;255m"), + noesc_str); + + if (noesc_str.rfind('m') == std::string::npos) + die(_("Parser: failed to parse layout/ascii art: missing 'm' while using ANSI color escape code in '\\e[{}'"), + noesc_str); + + const std::vector<std::string>& rgb_str = split(noesc_str.substr(5), ';'); + + const uint r = std::stoul(rgb_str.at(0)); + const uint g = std::stoul(rgb_str.at(1)); + const uint b = std::stoul(rgb_str.at(2)); + const uint result = (r << 16) | (g << 8) | (b); + + std::stringstream ss; + ss << std::hex << result; + return ss.str(); +} +#endif + +EXPORT std::string parse(const std::string& input, std::string& _, parse_args_t& parse_args) +{ + return parse(input, parse_args.modulesInfo, _, parse_args.layout, parse_args.tmp_layout, parse_args.config, + parse_args.parsingLayout, parse_args.no_more_reset); +} + +EXPORT std::string parse(const std::string& input, parse_args_t& parse_args) +{ + return parse(input, parse_args.modulesInfo, parse_args.pureOutput, parse_args.layout, parse_args.tmp_layout, + parse_args.config, parse_args.parsingLayout, parse_args.no_more_reset); +} + +std::string get_and_color_percentage(const float n1, const float n2, parse_args_t& parse_args, const bool invert) +{ + const std::vector<std::string>& percentage_colors = parse_args.config.getValueArrayStr("config.percentage-colors", {"green", "yellow", "red"}); + const float result = n1 / n2 * static_cast<float>(100); + + std::string color; + if (!invert) + { + if (result <= 45) + color = "${" + percentage_colors.at(0) + "}"; + else if (result <= 80) + color = "${" + percentage_colors.at(1) + "}"; + else + color = "${" + percentage_colors.at(2) + "}"; + } + else + { + if (result <= 45) + color = "${" + percentage_colors.at(2) + "}"; + else if (result <= 80) + color = "${" + percentage_colors.at(1) + "}"; + else + color = "${" + percentage_colors.at(0) + "}"; + } + + return parse(fmt::format("{}{:.2f}%${{0}}", color, result), _, parse_args); +} + +std::string getInfoFromName(parse_args_t& parse_args, const std::string& moduleName) +{ + std::string name; + name.reserve(moduleName.size()); + + /* true when we find a '(' */ + bool collecting = false; + + /* current position */ + size_t i = -1; + size_t stripped_char_count = 0; /* amount of chars stripped from `name` */ + + /* position of start, resets every separator */ + size_t start_pos = 0; + + moduleArgs_t* moduleArgs = new moduleArgs_t; + + /* argument that's collected from what's between the parenthesis in "module(...).test" */ + std::string arg; + arg.reserve(moduleName.size()); + for (const char c : moduleName) + { + i++; + if (c == '(' && !collecting) + { + collecting = true; + continue; + } + + if ((c == '.' || i + 1 == moduleName.size())) + { + if (collecting) + { + if (arg.back() != ')' && c != ')') + die("Module name `{}` is invalid. Arguments must end with )", moduleName); + + if (arg.back() == ')') + arg.pop_back(); + + moduleArgs_t* moduleArg = moduleArgs; + while (moduleArg->next != nullptr) + moduleArg = moduleArg->next; + + moduleArg->name = std::string{ name.begin() + start_pos, name.end() }; + moduleArg->value = arg; + moduleArg->next = new moduleArgs_t; + moduleArg->next->prev = moduleArg; + + if (c == '.') + { + name.push_back('.'); + stripped_char_count++; + } + } + else + { + name.push_back(c); + } + + start_pos = i + 1 - stripped_char_count; + arg = ""; + collecting = false; + + continue; + } + + if (!collecting) + { + name.push_back(c); + } + else + { + stripped_char_count++; + arg.push_back(c); + } + } + + std::string result = "(unknown/invalid module)"; + if (const auto& it = parse_args.modulesInfo.find(name); it != parse_args.modulesInfo.end()) + { + struct callbackInfo_t callbackInfo = { moduleArgs, parse_args }; + + result = it->second.handler(&callbackInfo); + } + + while (moduleArgs) + { + moduleArgs_t* next = moduleArgs->next; + + delete moduleArgs; + + moduleArgs = next; + } + + return result; +} + +std::string parse(Parser& parser, parse_args_t& parse_args, const bool evaluate = true, const char until = 0); + +std::optional<std::string> parse_conditional_tag(Parser& parser, parse_args_t& parse_args, const bool evaluate) +{ + if (!parser.try_read('[')) + return {}; + + const std::string& condA = parse(parser, parse_args, evaluate, ','); + const std::string& condB = parse(parser, parse_args, evaluate, ','); + + const bool cond = (condA == condB); + + const std::string& condTrue = parse(parser, parse_args, cond, ','); + const std::string& condFalse = parse(parser, parse_args, !cond, ']'); + + return cond ? condTrue : condFalse; +} + +std::optional<std::string> parse_command_tag(Parser& parser, parse_args_t& parse_args, const bool evaluate) +{ + if (!parser.try_read('(')) + return {}; + + std::string command = parse(parser, parse_args, evaluate, ')'); + + if (!evaluate) + return {}; + + if (parse_args.config.getValue("intern.args.disallow-commands", false)) + die(_("Trying to execute command $({}) but --disallow-command-tag is set"), command); + + const bool removetag = (command.front() == '!'); + if (removetag) + command.erase(0, 1); + + std::string cmd_output; + TinyProcessLib::Process proc(command, "", [&](const char* bytes, size_t n){cmd_output.assign(bytes, n);}); + if (!parse_args.parsingLayout && !removetag && parser.dollar_pos != std::string::npos) + parse_args.pureOutput.replace(parser.dollar_pos, command.length() + "$()"_len, cmd_output); + + return cmd_output; +} + +template <typename... Styles> +static void append_styles(fmt::text_style& current_style, Styles&&... styles) +{ + current_style |= (styles | ...); +} + +std::optional<std::string> parse_color_tag(Parser& parser, parse_args_t& parse_args, const bool evaluate) +{ + if (!parser.try_read('{')) + return {}; + + std::string color = parse(parser, parse_args, evaluate, '}'); + + if (!evaluate) + return {}; + + std::string output; + const size_t taglen = color.length() + "${}"_len; + const ConfigBase& config = parse_args.config; + const std::string endspan = !parse_args.firstrun_clr ? "</span>" : ""; + + if (config.getValue("intern.args.disable-colors", false)) + { + if (parser.dollar_pos != std::string::npos) + parse_args.pureOutput.erase(parser.dollar_pos, taglen); + return ""; + } + + // if at end there a '$', it will make the end output "$</span>" and so it will confuse + // addValueFromModule() and so let's make it "$ </span>". this is geniunenly stupid +#if GUI_APP + if (output[0] == '$') + output += ' '; +#endif + + static std::vector<std::string> alias_colors_name, alias_colors_value; + const std::vector<std::string>& alias_colors = config.getValueArrayStr("config.alias-colors", {}); + if (!alias_colors.empty() && (alias_colors_name.empty() && alias_colors_value.empty())) + { + for (const std::string& str : alias_colors) + { + const size_t pos = str.find('='); + if (pos == std::string::npos) + die(_("alias color '{}' does NOT have an equal sign '=' for separating color name and value\n" + "For more check with --help"), str); + + alias_colors_name.push_back(str.substr(0, pos)); + alias_colors_value.push_back(str.substr(pos + 1)); + } + + const auto& it_name = std::find(alias_colors_name.begin(), alias_colors_name.end(), color); + if (it_name != alias_colors_name.end()) + { + const size_t index = std::distance(alias_colors_name.begin(), it_name); + color = alias_colors_value.at(index); + } + } + + static std::vector<std::string> auto_colors; + if (hasStart(color, "auto")) + { + int ver = color.length() > 4 ? std::stoi(color.substr(4)) - 1 : 0; + + if (auto_colors.empty()) + auto_colors.push_back(NOCOLOR_BOLD); + + if (ver < 0 || static_cast<size_t>(ver) >= auto_colors.size()) + ver = 0; + + color = auto_colors.at(ver); + } + +#if GUI_APP + if (color == "1") + { + output += endspan + "<span weight='bold'>"; + } + else if (color == "0") + { + output += endspan + "<span>"; + } +#else + if (color == "1") + { + output += NOCOLOR_BOLD; + } + else if (color == "0") + { + output += NOCOLOR; + } +#endif + else + { + std::string str_clr; +#if GUI_APP + switch (fnv1a16::hash(color)) + { + case "black"_fnv1a16: str_clr = config.getValue<std::string>("gui.black", "!#000005"); break; + case "red"_fnv1a16: str_clr = config.getValue<std::string>("gui.red", "!#ff2000"); break; + case "green"_fnv1a16: str_clr = config.getValue<std::string>("gui.green", "!#00ff00"); break; + case "yellow"_fnv1a16: str_clr = config.getValue<std::string>("gui.yellow", "!#ffff00"); break; + case "blue"_fnv1a16: str_clr = config.getValue<std::string>("gui.blue", "!#00aaff"); break; + case "magenta"_fnv1a16: str_clr = config.getValue<std::string>("gui.magenta", "!#ff11cc"); break; + case "cyan"_fnv1a16: str_clr = config.getValue<std::string>("gui.cyan", "!#00ffff"); break; + case "white"_fnv1a16: str_clr = config.getValue<std::string>("gui.white", "!#ffffff"); break; + default: str_clr = color; break; + } + + const size_t pos = str_clr.rfind('#'); + if (pos != std::string::npos) + { + std::string tagfmt = "span "; + const std::string& opt_clr = str_clr.substr(0, pos); + + size_t argmode_pos = 0; + const auto& append_argmode = [&](const std::string_view fmt, const std::string_view mode) -> size_t { + if (opt_clr.at(argmode_pos + 1) == '(') + { + const size_t closebrak = opt_clr.find(')', argmode_pos); + if (closebrak == std::string::npos) + die(_("'{}' mode in color '{}' doesn't have close bracket"), mode, str_clr); + + const std::string& value = opt_clr.substr(argmode_pos + 2, closebrak - argmode_pos - 2); + tagfmt += fmt.data() + value + "' "; + + return closebrak; + } + return 0; + }; + + bool bgcolor = false; + for (size_t i = 0; i < opt_clr.length(); ++i) + { + switch (opt_clr.at(i)) + { + case 'b': + bgcolor = true; + tagfmt += "bgcolor='" + str_clr.substr(pos) + "' "; + break; + case '!': tagfmt += "weight='bold' "; break; + case 'u': tagfmt += "underline='single' "; break; + case 'i': tagfmt += "style='italic' "; break; + case 'o': tagfmt += "overline='single' "; break; + case 's': tagfmt += "strikethrough='true' "; break; + + case 'a': + argmode_pos = i; + i += append_argmode("fgalpha='", "fgalpha"); + break; + + case 'A': + argmode_pos = i; + i += append_argmode("bgalpha='", "bgalpha"); + break; + + case 'L': + argmode_pos = i; + i += append_argmode("underline='", "underline option"); + break; + + case 'U': + argmode_pos = i; + i += append_argmode("underline_color='", "colored underline"); + break; + + case 'B': + argmode_pos = i; + i += append_argmode("bgcolor='", "bgcolor"); + break; + + case 'w': + argmode_pos = i; + i += append_argmode("weight='", "font weight style"); + break; + + case 'O': + argmode_pos = i; + i += append_argmode("overline_color='", "overline color"); + break; + + case 'S': + argmode_pos = i; + i += append_argmode("strikethrough_color='", "color of strikethrough line"); + break; + } + } + + if (!bgcolor) + tagfmt += "fgcolor='" + str_clr.substr(pos) + "' "; + + tagfmt.pop_back(); + output += endspan + "<" + tagfmt + ">"; + } + + // "\\e" is for checking in the ascii_art, \033 in the config + else if (hasStart(str_clr, "\\e") || hasStart(str_clr, "\033")) + { + const std::string& noesc_str = hasStart(str_clr, "\033") ? str_clr.substr(2) : str_clr.substr(3); + debug("noesc_str = {}", noesc_str); + + if (hasStart(noesc_str, "38;2;") || hasStart(noesc_str, "48;2;")) + { + const std::string& hexclr = convert_ansi_escape_rgb(noesc_str); + output += + fmt::format("{}<span {}gcolor='#{}'>", endspan, hasStart(noesc_str, "38") ? 'f' : 'b', hexclr); + } + else if (hasStart(noesc_str, "38;5;") || hasStart(noesc_str, "48;5;")) + { + die(_("256 true color '{}' works only in terminal"), noesc_str); + } + else + { + const std::array<std::string, 3>& clrs = get_ansi_color(noesc_str, config); + const std::string_view color = clrs.at(0); + const std::string_view weight = clrs.at(1); + const std::string_view type = clrs.at(2); + output += fmt::format("{}<span {}='{}' weight='{}'>", endspan, type, color, weight); + } + } + + else + { + error(_("PARSER: failed to parse line with color '{}'"), str_clr); + if (!parse_args.parsingLayout && parser.dollar_pos != std::string::npos) + parse_args.pureOutput.erase(parser.dollar_pos, taglen); + return output; + } + +// #if !GUI_APP +#else + switch (fnv1a16::hash(color)) + { + case "black"_fnv1a16: str_clr = config.getValue<std::string>("config.black", "\033[1;30m"); break; + case "red"_fnv1a16: str_clr = config.getValue<std::string>("config.red", "\033[1;31m"); break; + case "green"_fnv1a16: str_clr = config.getValue<std::string>("config.green", "\033[1;32m"); break; + case "yellow"_fnv1a16: str_clr = config.getValue<std::string>("config.yellow", "\033[1;33m"); break; + case "blue"_fnv1a16: str_clr = config.getValue<std::string>("config.blue", "\033[1;34m"); break; + case "magenta"_fnv1a16: str_clr = config.getValue<std::string>("config.magenta", "\033[1;35m"); break; + case "cyan"_fnv1a16: str_clr = config.getValue<std::string>("config.cyan", "\033[1;36m"); break; + case "white"_fnv1a16: str_clr = config.getValue<std::string>("config.white", "\033[1;37m"); break; + default: str_clr = color; break; + } + + const size_t pos = str_clr.rfind('#'); + if (pos != std::string::npos) + { + const std::string& opt_clr = str_clr.substr(0, pos); + + fmt::text_style style; + + const auto& skip_gui_argmode = [&opt_clr](const size_t index) -> size_t { + if (opt_clr.at(index + 1) == '(') + { + const size_t closebrak = opt_clr.find(')', index); + if (closebrak == std::string::npos) + return 0; + + return closebrak; + } + return 0; + }; + + bool bgcolor = false; + for (size_t i = 0; i < opt_clr.length(); ++i) + { + switch (opt_clr.at(i)) + { + case 'b': + bgcolor = true; + append_styles(style, fmt::bg(hexStringToColor(str_clr.substr(pos)))); + break; + case '!': append_styles(style, fmt::emphasis::bold); break; + case 'u': append_styles(style, fmt::emphasis::underline); break; + case 'i': append_styles(style, fmt::emphasis::italic); break; + case 'l': append_styles(style, fmt::emphasis::blink); break; + case 's': append_styles(style, fmt::emphasis::strikethrough); break; + + case 'U': + case 'B': + case 'S': + case 'a': + case 'w': + case 'O': + case 'A': + case 'L': i += skip_gui_argmode(i); break; + } + } + + if (!bgcolor) + append_styles(style, fmt::fg(hexStringToColor(str_clr.substr(pos)))); + + // you can't fmt::format(style, ""); ughh + if (style.has_emphasis()) + { + fmt::detail::ansi_color_escape<char> emph(style.get_emphasis()); + output += emph.begin(); + } + if (style.has_background() || style.has_foreground()) + { + const uint32_t rgb_num = + bgcolor ? style.get_background().value.rgb_color : style.get_foreground().value.rgb_color; + fmt::rgb rgb(rgb_num); + fmt::detail::ansi_color_escape<char> ansi(rgb, bgcolor ? "\x1B[48;2;" : "\x1B[38;2;"); + output += ansi.begin(); + } + } + + // "\\e" is for checking in the ascii_art, \033 in the config + else if (hasStart(str_clr, "\\e") || hasStart(str_clr, "\033")) + { + output += "\033["; + output += hasStart(str_clr, "\033") ? str_clr.substr(2) : str_clr.substr(3); + } + + else + { + error(_("PARSER: failed to parse line with color '{}'"), str_clr); + if (!parse_args.parsingLayout && parser.dollar_pos != std::string::npos) + parse_args.pureOutput.erase(parser.dollar_pos, taglen); + return output; + } +#endif + + if (!parse_args.parsingLayout && std::find(auto_colors.begin(), auto_colors.end(), color) == auto_colors.end()) + auto_colors.push_back(color); + } + + if (!parse_args.parsingLayout && parser.dollar_pos != std::string::npos) + parse_args.pureOutput.erase(parser.dollar_pos, taglen); + + parse_args.firstrun_clr = false; + + return output; +} + +std::optional<std::string> parse_info_tag(Parser& parser, parse_args_t& parse_args, const bool evaluate) +{ + if (!parser.try_read('<')) + return {}; + + const std::string& module = parse(parser, parse_args, evaluate, '>'); + + if (!evaluate) + return {}; + + const std::string& info = getInfoFromName(parse_args, module); + + if (parser.dollar_pos != std::string::npos) + parse_args.pureOutput.replace(parser.dollar_pos, module.length() + "$<>"_len, info); + return info; +} + +std::optional<std::string> parse_perc_tag(Parser& parser, parse_args_t& parse_args, const bool evaluate) +{ + if (!parser.try_read('%')) + return {}; + + const std::string& command = parse(parser, parse_args, evaluate, '%'); + + if (!evaluate) + return {}; + + const size_t comma_pos = command.find(','); + if (comma_pos == std::string::npos) + die(_("percentage tag '{}' doesn't have a comma for separating the 2 numbers"), command); + + const bool invert = (command.front() == '!'); + + const float n1 = std::stof(parse(command.substr(invert ? 1 : 0, comma_pos), _, parse_args)); + const float n2 = std::stof(parse(command.substr(comma_pos + 1), _, parse_args)); + + return get_and_color_percentage(n1, n2, parse_args, invert); +} + +std::optional<std::string> parse_tags(Parser& parser, parse_args_t& parse_args, const bool evaluate) +{ + if (!parser.try_read('$')) + return {}; + + if (parser.dollar_pos != std::string::npos) + parser.dollar_pos = parser.pureOutput.find('$', parser.dollar_pos); + + if (const auto& color_tag = parse_color_tag(parser, parse_args, evaluate)) + return color_tag; + + if (const auto& module_tag = parse_info_tag(parser, parse_args, evaluate)) + return module_tag; + + if (const auto& command_tag = parse_command_tag(parser, parse_args, evaluate)) + return command_tag; + + if (const auto& ifTag = parse_conditional_tag(parser, parse_args, evaluate)) + return ifTag; + + if (const auto& perc_tag = parse_perc_tag(parser, parse_args, evaluate)) + return perc_tag; + + parser.rewind(); + return {}; +} + +std::string parse(Parser& parser, parse_args_t& parse_args, const bool evaluate, const char until) +{ + std::string result; + + while (until == 0 ? !parser.is_eof() : !parser.try_read(until)) + { + if (until != 0 && parser.is_eof()) + { + error(_("PARSER: Missing tag close bracket {} in string '{}'"), until, parser.src); + return result; + } + + if (parser.try_read('\\')) + { + result += parser.read_char(until == 0); + } + else if (const auto& tagStr = parse_tags(parser, parse_args, evaluate)) + { + result += *tagStr; + } + else + { + result += parser.read_char(until == 0); + } + } + + return result; +} + +EXPORT std::string parse(std::string input, const moduleMap_t& modulesInfo, std::string& pureOutput, + std::vector<std::string>& layout, std::vector<std::string>& tmp_layout, const ConfigBase& config, + const bool parsingLayout, bool& no_more_reset) +{ + const std::string& sep_reset = config.getValue<std::string>("config.sep-reset", ":"); + if (!sep_reset.empty() && parsingLayout && !no_more_reset) + { + if (config.getValue("config.sep-reset-after", false)) + replace_str(input, sep_reset, sep_reset + "${0}"); + else + replace_str(input, sep_reset, "${0}" + sep_reset); + + no_more_reset = true; + } + + parse_args_t parse_args{ modulesInfo, pureOutput, layout, tmp_layout, config, parsingLayout, true, no_more_reset }; + Parser parser{ input, pureOutput }; + + std::string ret{ parse(parser, parse_args) }; + +#if GUI_APP + if (!parse_args.firstrun_clr) + ret += "</span>"; + + replace_str(parse_args.pureOutput, " ", " "); + + // escape pango markup + // https://gitlab.gnome.org/GNOME/glib/-/blob/main/glib/gmarkup.c#L2150 + // workaround: just put "\<" or "\&" in the config, e.g "$<os.kernel> \<- Kernel" + replace_str(ret, "\\<", "<"); + replace_str(ret, "\\&", "&"); + replace_str(ret, "&", "&"); +#else + replace_str(ret, "\\<", "<"); + replace_str(ret, "\\&", "&"); +#endif + + return ret; +} diff --git a/libcufetch/util.cc b/libcufetch/util.cc new file mode 120000 index 00000000..0b83e603 --- /dev/null +++ b/libcufetch/util.cc @@ -0,0 +1 @@ +../src/util.cpp \ No newline at end of file diff --git a/src/config.cpp b/src/config.cpp index 9c47205a..7ab55ad5 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1,25 +1,25 @@ /* * Copyright 2025 Toni500git - * + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ @@ -29,38 +29,37 @@ #include <filesystem> #include <string> +#include "texts.hpp" #include "fmt/os.h" -#include "query.hpp" -#include "switch_fnv1a.hpp" #include "util.hpp" -Config::Config(const std::string_view configFile, const std::string_view configDir) +Config::Config(const std::filesystem::path& configFile, const std::filesystem::path& configDir) { if (!std::filesystem::exists(configDir)) { - warn(_("customfetch config folder was not found, Creating folders at {}!"), configDir); + warn(_("customfetch config folder was not found, Creating folders at {}!"), configDir.string()); std::filesystem::create_directories(configDir); } if (!std::filesystem::exists(configFile)) { - warn(_("config file {} not found, generating new one"), configFile); + warn(_("config file {} not found, generating new one"), configFile.string()); this->generateConfig(configFile); } } -void Config::loadConfigFile(const std::string_view filename, colors_t& colors) +void Config::loadConfigFile(const std::filesystem::path& filename) { try { - this->tbl = toml::parse_file(filename); + this->tbl = toml::parse_file(filename.string()); } catch (const toml::parse_error& err) { die(_("Parsing config file '{}' failed:\n" "{}\n" "\t(error occurred at line {} column {})"), - filename, err.description(), + filename.string(), err.description(), err.source().begin.line, err.source().begin.column); } @@ -72,27 +71,23 @@ void Config::loadConfigFile(const std::string_view filename, colors_t& colors) this->sep_reset_after = getValue<bool>("config.sep-reset-after", false); this->use_SI_unit = getValue<bool>("config.use-SI-byte-unit", false); this->wrap_lines = getValue<bool>("config.wrap-lines", false); - this->offset = getValue<std::string>("config.offset", "5"); this->logo_padding_left = getValue<std::uint16_t>("config.logo-padding-left", 0); this->layout_padding_top = getValue<std::uint16_t>("config.layout-padding-top", 0); this->logo_padding_top = getValue<std::uint16_t>("config.logo-padding-top", 0); - this->sep_reset = getValue<std::string>("config.sep-reset", ":"); - this->ascii_logo_type = getValue<std::string>("config.ascii-logo-type", ""); - this->source_path = getValue<std::string>("config.source-path", "os"); - this->logo_position = getValue<std::string>("config.logo-position", "left"); - this->data_dir = getValue<std::string>("config.data-dir", get_data_dir("customfetch")); - this->title_sep = getValue<std::string>("config.title-sep", "-"); - this->font = getValue<std::string>("gui.font", "Liberation Mono Normal 12"); - this->gui_bg_image = getValue<std::string>("gui.bg-image", "disable"); - this->gui_css_file = getValue<std::string>("gui.gtk-css", "disable"); - - this->auto_disks_fmt = getValue<std::string>("auto.disk.fmt", "${auto}Disk (%1): $<disk(%1)>", true); - this->auto_disks_show_dupl= getValue<bool>("auto.disk.show-duplicated", false); - - this->uptime_d_fmt = getValue<std::string>("os.uptime.days", " days"); - this->uptime_h_fmt = getValue<std::string>("os.uptime.hours", " hours"); - this->uptime_m_fmt = getValue<std::string>("os.uptime.mins", " mins"); - this->uptime_s_fmt = getValue<std::string>("os.uptime.secs", " secs"); + this->offset = expandVar(getValue<std::string>("config.offset", "5")); + this->sep_reset = expandVar(getValue<std::string>("config.sep-reset", ":")); + this->ascii_logo_type = expandVar(getValue<std::string>("config.ascii-logo-type", "")); + this->source_path = expandVar(getValue<std::string>("config.source-path", "os")); + this->logo_position = expandVar(getValue<std::string>("config.logo-position", "left")); + this->data_dir = expandVar(getValue<std::string>("config.data-dir", get_data_dir("customfetch"))); + this->title_sep = expandVar(getValue<std::string>("config.title-sep", "-")); + this->gui_bg_image = expandVar(getValue<std::string>("gui.bg-image", "disable")); + this->gui_css_file = expandVar(getValue<std::string>("gui.gtk-css", "disable")); + + this->uptime_d_fmt = expandVar(getValue<std::string>("os.uptime.days", " days")); + this->uptime_h_fmt = expandVar(getValue<std::string>("os.uptime.hours", " hours")); + this->uptime_m_fmt = expandVar(getValue<std::string>("os.uptime.mins", " mins")); + this->uptime_s_fmt = expandVar(getValue<std::string>("os.uptime.secs", " secs")); this->pkgs_managers= getValueArrayStr("os.pkgs.pkg-managers", {}); this->pacman_dirs = getValueArrayStr("os.pkgs.pacman-dirs", {"/var/lib/pacman/local"}); @@ -100,23 +95,23 @@ void Config::loadConfigFile(const std::string_view filename, colors_t& colors) this->flatpak_dirs = getValueArrayStr("os.pkgs.flatpak-dirs", {"/var/lib/flatpak/app", "~/.local/share/flatpak/app"}); this->apk_files = getValueArrayStr("os.pkgs.apk-files", {"/var/lib/apk/db/installed"}); - colors.black = getThemeValue("config.black", "\033[1;30m"); - colors.red = getThemeValue("config.red", "\033[1;31m"); - colors.green = getThemeValue("config.green", "\033[1;32m"); - colors.yellow = getThemeValue("config.yellow", "\033[1;33m"); - colors.blue = getThemeValue("config.blue", "\033[1;34m"); - colors.magenta = getThemeValue("config.magenta", "\033[1;35m"); - colors.cyan = getThemeValue("config.cyan", "\033[1;36m"); - colors.white = getThemeValue("config.white", "\033[1;37m"); - - colors.gui_black = getThemeValue("gui.black", "!#000005"); - colors.gui_red = getThemeValue("gui.red", "!#ff2000"); - colors.gui_green = getThemeValue("gui.green", "!#00ff00"); - colors.gui_blue = getThemeValue("gui.blue", "!#00aaff"); - colors.gui_cyan = getThemeValue("gui.cyan", "!#00ffff"); - colors.gui_yellow = getThemeValue("gui.yellow", "!#ffff00"); - colors.gui_magenta = getThemeValue("gui.magenta", "!#ff11cc"); - colors.gui_white = getThemeValue("gui.white", "!#ffffff"); + colors.black = getValue<std::string>("config.black", "\033[1;30m"); + colors.red = getValue<std::string>("config.red", "\033[1;31m"); + colors.green = getValue<std::string>("config.green", "\033[1;32m"); + colors.yellow = getValue<std::string>("config.yellow", "\033[1;33m"); + colors.blue = getValue<std::string>("config.blue", "\033[1;34m"); + colors.magenta = getValue<std::string>("config.magenta", "\033[1;35m"); + colors.cyan = getValue<std::string>("config.cyan", "\033[1;36m"); + colors.white = getValue<std::string>("config.white", "\033[1;37m"); + + colors.gui_black = getValue<std::string>("gui.black", "!#000005"); + colors.gui_red = getValue<std::string>("gui.red", "!#ff2000"); + colors.gui_green = getValue<std::string>("gui.green", "!#00ff00"); + colors.gui_blue = getValue<std::string>("gui.blue", "!#00aaff"); + colors.gui_cyan = getValue<std::string>("gui.cyan", "!#00ffff"); + colors.gui_yellow = getValue<std::string>("gui.yellow", "!#ffff00"); + colors.gui_magenta = getValue<std::string>("gui.magenta", "!#ff11cc"); + colors.gui_white = getValue<std::string>("gui.white", "!#ffffff"); if (this->percentage_colors.size() < 3) { @@ -125,22 +120,6 @@ void Config::loadConfigFile(const std::string_view filename, colors_t& colors) this->percentage_colors = {"green", "yellow", "red"}; } - for (const std::string& str : this->getValueArrayStr("auto.disk.display-types", {"external", "regular", "read-only"})) - { - switch (fnv1a16::hash(str)) - { - case "removable"_fnv1a16: // deprecated - case "external"_fnv1a16: - this->auto_disks_types |= Query::DISK_VOLUME_TYPE_EXTERNAL; break; - case "regular"_fnv1a16: - this->auto_disks_types |= Query::DISK_VOLUME_TYPE_REGULAR; break; - case "read-only"_fnv1a16: - this->auto_disks_types |= Query::DISK_VOLUME_TYPE_READ_ONLY; break; - case "hidden"_fnv1a16: - this->auto_disks_types |= Query::DISK_VOLUME_TYPE_HIDDEN; break; - } - } - for (const std::string& str : this->getValueArrayStr("config.alias-colors", {})) this->addAliasColors(str); @@ -149,36 +128,6 @@ void Config::loadConfigFile(const std::string_view filename, colors_t& colors) this->args_disable_colors = true; } -std::string Config::getThemeValue(const std::string_view value, const std::string_view fallback) const -{ - return this->tbl.at_path(value).value<std::string>().value_or(fallback.data()); -} - -std::vector<std::string> Config::getValueArrayStr(const std::string_view value, - const std::vector<std::string>& fallback) -{ - std::vector<std::string> ret; - - // https://stackoverflow.com/a/78266628 - const auto& array = tbl.at_path(value); - if (const toml::array* array_it = array.as_array()) - { - array_it->for_each( - [&ret, value](auto&& el) - { - if (const toml::value<std::string>* str_elem = el.as_string()) - ret.push_back((*str_elem)->data()); - else - warn(_("an element of the array '{}' is not a string"), value); - } - ); - - return ret; - } - else - return fallback; -} - void Config::addAliasColors(const std::string& str) { const size_t pos = str.find('='); @@ -218,27 +167,27 @@ void Config::overrideOption(const std::string& opt) name.insert(0, "config."); if (value == "true") - overrides[name] = {.value_type = BOOL, .bool_value = true}; + overrides[name] = {BOOL, "", true, 0}; else if (value == "false") - overrides[name] = {.value_type = BOOL, .bool_value = false}; + overrides[name] = {BOOL, "", false, 0}; else if ((value[0] == '"' && value.back() == '"') || (value[0] == '\'' && value.back() == '\'')) - overrides[name] = {.value_type = STR, .string_value = value.substr(1, value.size()-2)}; + overrides[name] = {STR, value.substr(1, value.size()-2), false, 0}; else if (is_str_digital(value)) - overrides[name] = {.value_type = INT, .int_value = std::stoi(value)}; + overrides[name] = {INT, "", false, std::stoi(value)}; else die(_("looks like override value '{}' from '{}' is neither a bool, int or string value"), value, name); } -void Config::generateConfig(const std::string_view filename) +void Config::generateConfig(const std::filesystem::path &filename) { if (std::filesystem::exists(filename)) { - if (!askUserYorN(false, "WARNING: config file '{}' already exists. Do you want to overwrite it?", filename)) + if (!askUserYorN(false, "WARNING: config file '{}' already exists. Do you want to overwrite it?", filename.string())) std::exit(1); } - auto f = fmt::output_file(filename.data()); + auto f = fmt::output_file(filename.c_str()); f.print("{}", AUTOCONFIG); } diff --git a/src/core-modules/android/battery.cc b/src/core-modules/android/battery.cc new file mode 100644 index 00000000..1a63add9 --- /dev/null +++ b/src/core-modules/android/battery.cc @@ -0,0 +1,148 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_ANDROID + +#include <string> +#include <string_view> +#include <vector> + +#include "core-modules.hh" +#include "json.h" +#include "libcufetch/common.hh" +#include "libcufetch/fmt/format.h" +#include "switch_fnv1a.hpp" +#include "util.hpp" + +static json::jobject doc; +static std::vector<std::string> dumpsys; + +static bool assert_doc() +{ + if (doc.size() <= 0) + { + std::string result; + if (!read_exec({ "/data/data/com.termux/files/usr/libexec/termux-api", "BatteryStatus" }, result)) + return false; + if (!json::jobject::tryparse(result.c_str(), doc)) + return false; + } + return true; +} + +static bool assert_dumpsys() +{ + if (dumpsys.size() <= 0) + { + std::string result; + if (!read_exec({ "/system/bin/dumpsys", "battery" }, result)) + return false; + dumpsys = split(result, '\n'); + if (dumpsys[0] != "Current Battery Service state:") + return false; + } + return true; +} + +static std::string read_value_dumpsys(const std::string_view name, const bool is_status = false) +{ + if (!assert_dumpsys()) + return MAGIC_LINE; + + for (size_t i = 1; i < dumpsys.size(); ++i) + { + const size_t pos = dumpsys.at(i).rfind(':'); + const std::string& key = dumpsys.at(i).substr(2, pos); + const std::string& value = dumpsys.at(i).substr(pos + 2); + + if (is_status) + { + if (key.find("powered") != key.npos && value == "true") + return key; + } + + else if (key == name) + return value; + } + + return MAGIC_LINE; +} + +// clang-format off +MODFUNC(battery_modelname) +{ return MAGIC_LINE; } + +MODFUNC(battery_vendor) +{ return MAGIC_LINE; } + +MODFUNC(battery_capacity_level) +{ return MAGIC_LINE; } + +// clang-format on +MODFUNC(battery_status) +{ + if (!assert_doc()) + return read_value_dumpsys("powered", true); + + std::string charge_status{ str_tolower(doc["status"].as_string()) }; + charge_status.at(0) = toupper(charge_status.at(0)); + switch (fnv1a16::hash(doc["plugged"].as_string())) + { + case "PLUGGED_AC"_fnv1a16: return "AC Connected, " + charge_status; + case "PLUGGED_USB"_fnv1a16: return "USB Connected, " + charge_status; + case "PLUGGED_WIRELESS"_fnv1a16: return "Wireless Connected, " + charge_status; + default: return charge_status; + } +} + +MODFUNC(battery_technology) +{ + if (!assert_doc()) + return read_value_dumpsys("technology"); + return MAGIC_LINE; +} + +MODFUNC(battery_perc) +{ + if (!assert_doc()) + { + double level = std::stod(read_value_dumpsys("level")); + double scale = std::stod(read_value_dumpsys("scale")); + if (level > 0 && scale > 0) + return fmt::to_string(level * 100 / scale); + } + + return doc["percentage"]; +} + +double battery_temp() +{ + if (!assert_doc()) + return std::stod(read_value_dumpsys("temperature")) / 10; + return doc["temperature"]; +} + +#endif diff --git a/src/query/android/gpu.cpp b/src/core-modules/android/gpu.cc similarity index 92% rename from src/query/android/gpu.cpp rename to src/core-modules/android/gpu.cc index 5c4fd7a8..c6d0a395 100644 --- a/src/query/android/gpu.cpp +++ b/src/core-modules/android/gpu.cc @@ -28,11 +28,9 @@ #include <string> -#include "query.hpp" +#include "core-modules.hh" +#include "libcufetch/common.hh" #include "switch_fnv1a.hpp" -#include "util.hpp" - -using namespace Query; // https://en.wikipedia.org/wiki/List_of_Qualcomm_Snapdragon_systems_on_chips static std::string detect_adreno(const std::string& cpu_model_name) @@ -195,25 +193,17 @@ static std::string detect_adreno(const std::string& cpu_model_name) return MAGIC_LINE; } -GPU::GPU(const std::string& id, systemInfo_t& queried_gpus) +// clang-format off +MODFUNC(gpu_name) { - CPU query_cpu; - if (query_cpu.vendor() == "QUALCOMM" || query_cpu.vendor() == "QTI") - { - m_gpu_infos.vendor = "Qualcomm"; - m_gpu_infos.name = detect_adreno(query_cpu.modelname()); - } - else - { - m_gpu_infos.name = m_gpu_infos.vendor = MAGIC_LINE; - } -} + const std::string& vendor = android_cpu_vendor(nullptr); + if (vendor == "QUALCOMM" || vendor == "QTI") + return detect_adreno(android_cpu_model_name(nullptr)); -// clang-format off -std::string& GPU::name() noexcept -{ return m_gpu_infos.name; } + return MAGIC_LINE; +} -std::string& GPU::vendor() noexcept -{ return m_gpu_infos.vendor; } +MODFUNC(gpu_vendor) +{ return android_cpu_vendor(nullptr); } #endif diff --git a/src/query/android/theme.cpp b/src/core-modules/android/os.cc similarity index 59% rename from src/query/android/theme.cpp rename to src/core-modules/android/os.cc index 5422c4ea..0da4ae4d 100644 --- a/src/query/android/theme.cpp +++ b/src/core-modules/android/os.cc @@ -26,39 +26,47 @@ #include "platform.hpp" #if CF_ANDROID -#include "query.hpp" +#include "core-modules.hh" #include "util.hpp" -using namespace Query; +// clang-format off +MODFUNC(os_name) +{ return "Android"; } -Theme::Theme(const std::uint8_t ver, systemInfo_t& queried_themes, const std::string& theme_name_version, - const Config& config, const bool gsettings_only) - : m_queried_themes(queried_themes) -{ - m_theme_infos.cursor = m_theme_infos.gtk_font = m_theme_infos.cursor_size = m_theme_infos.gtk_theme_name = m_theme_infos.gtk_icon_theme - = MAGIC_LINE; -} +MODFUNC(os_pretty_name) +{ return "Android " + os_version_codename(NULL) + " " + os_version_id(NULL); } -Theme::Theme(systemInfo_t& queried_themes, const Config& config, const bool gsettings_only) : m_queried_themes(queried_themes) -{ - m_theme_infos.cursor = m_theme_infos.gtk_font = m_theme_infos.cursor_size = m_theme_infos.gtk_theme_name = m_theme_infos.gtk_icon_theme - = MAGIC_LINE; -} +MODFUNC(os_name_id) +{ return "android"; } -// clang-format off -std::string Theme::gtk_theme() noexcept -{ return m_theme_infos.gtk_theme_name; } +MODFUNC(os_version_id) +{ return get_android_property("ro.build.version.release"); } -std::string Theme::gtk_icon_theme() noexcept -{ return m_theme_infos.gtk_icon_theme; } +MODFUNC(os_version_codename) +{ return get_android_property("ro.build.version.codename"); } -std::string Theme::gtk_font() noexcept -{ return m_theme_infos.gtk_font; } +MODFUNC(os_kernel_name) +{ return g_uname_infos.sysname; } -std::string& Theme::cursor() noexcept -{ return m_theme_infos.cursor; } +MODFUNC(os_kernel_version) +{ return g_uname_infos.release; } -std::string& Theme::cursor_size() noexcept -{ return m_theme_infos.cursor_size; } +MODFUNC(os_hostname) +{ return g_uname_infos.nodename; } + +MODFUNC(os_initsys_name) +{ return "init"; } + +MODFUNC(os_initsys_version) +{ return ""; } + +unsigned long os_uptime() +{ + struct std::timespec uptime; + if (clock_gettime(CLOCK_BOOTTIME, &uptime) != 0) + return 0; + + return (uint64_t)uptime.tv_sec + (uint64_t)uptime.tv_nsec / 1000000; +} #endif diff --git a/src/query/macos/theme.cpp b/src/core-modules/android/system.cc similarity index 58% rename from src/query/macos/theme.cpp rename to src/core-modules/android/system.cc index b3be51ae..197c0598 100644 --- a/src/query/macos/theme.cpp +++ b/src/core-modules/android/system.cc @@ -24,41 +24,44 @@ */ #include "platform.hpp" -#if CF_MACOS -#include "query.hpp" +#if CF_ANDROID +#include <string> +#include <string_view> + +#include "core-modules.hh" +#include "libcufetch/common.hh" #include "util.hpp" -using namespace Query; +static constexpr std::array<std::string_view, 9> vendors_prop_names = { + "ro.product.marketname", "ro.vendor.product.display", "ro.vivo.market.name", + "ro.config.devicename", "ro.config.marketing_name", "ro.product.vendor.model", + "ro.product.oppo_model", "ro.oppo.market.name", "ro.product.brand" +}; -Theme::Theme(const std::uint8_t ver, systemInfo_t& queried_themes, const std::string& theme_name_version, - const Config& config, const bool gsettings_only) - : m_queried_themes(queried_themes) +MODFUNC(host_name) { - m_theme_infos.cursor = m_theme_infos.gtk_font = m_theme_infos.cursor_size = m_theme_infos.gtk_theme_name = m_theme_infos.gtk_icon_theme - = MAGIC_LINE; -} + for (const std::string_view name : vendors_prop_names) + { + const std::string& model_name = get_android_property(name); + if (!model_name.empty()) + return model_name; + } -Theme::Theme(systemInfo_t& queried_themes, const Config& config, const bool gsettings_only) : m_queried_themes(queried_themes) -{ - m_theme_infos.cursor = m_theme_infos.gtk_font = m_theme_infos.cursor_size = m_theme_infos.gtk_theme_name = m_theme_infos.gtk_icon_theme - = MAGIC_LINE; + return UNKNOWN; } // clang-format off -std::string Theme::gtk_theme() noexcept -{ return m_theme_infos.gtk_theme_name; } - -std::string Theme::gtk_icon_theme() noexcept -{ return m_theme_infos.gtk_icon_theme; } +MODFUNC(host_version) +{ return get_android_property("ro.product.model"); } -std::string Theme::gtk_font() noexcept -{ return m_theme_infos.gtk_font; } +MODFUNC(host_vendor) +{ return get_android_property("ro.product.manufacturer"); } -std::string& Theme::cursor() noexcept -{ return m_theme_infos.cursor; } +MODFUNC(host) +{ return host_vendor(NULL) + " " + host_name(NULL) + " " + host_version(NULL); } -std::string& Theme::cursor_size() noexcept -{ return m_theme_infos.cursor_size; } +MODFUNC(arch) +{ return g_uname_infos.machine; } #endif diff --git a/src/core-modules/android/theme.cc b/src/core-modules/android/theme.cc new file mode 100644 index 00000000..9fc2e589 --- /dev/null +++ b/src/core-modules/android/theme.cc @@ -0,0 +1,46 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_ANDROID + +#include "core-modules.hh" +#include "libcufetch/common.hh" + +MODFUNC(theme_gtk_name) { return MAGIC_LINE; } +MODFUNC(theme_gtk_icon) { return MAGIC_LINE; } +MODFUNC(theme_gtk_font) { return MAGIC_LINE; } +MODFUNC(theme_cursor_name) { return MAGIC_LINE; } +MODFUNC(theme_cursor_size) { return MAGIC_LINE; } +MODFUNC(theme_gtk_all_name) { return MAGIC_LINE; } +MODFUNC(theme_gtk_all_icon) { return MAGIC_LINE; } +MODFUNC(theme_gtk_all_font) { return MAGIC_LINE; } +MODFUNC(theme_gsettings_name) { return MAGIC_LINE; } +MODFUNC(theme_gsettings_icon) { return MAGIC_LINE; } +MODFUNC(theme_gsettings_font) { return MAGIC_LINE; } +MODFUNC(theme_gsettings_cursor_name) { return MAGIC_LINE; } +MODFUNC(theme_gsettings_cursor_size) { return MAGIC_LINE; } + +#endif // CF_ANDROID diff --git a/src/core-modules/android/user.cc b/src/core-modules/android/user.cc new file mode 100644 index 00000000..8d3f9ed7 --- /dev/null +++ b/src/core-modules/android/user.cc @@ -0,0 +1,87 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_ANDROID + +#include <linux/limits.h> + +#include <string> + +#include "core-modules.hh" +#include "tiny-process-library/process.hpp" +#include "util.hpp" + +using namespace TinyProcessLib; + +MODFUNC(user_shell_path) +{ + char buf[PATH_MAX]; + return realpath(fmt::format("/proc/{}/exe", getppid()).c_str(), buf); +} + +MODFUNC(user_shell_name) +{ + return user_shell_path(callbackInfo).substr(user_shell_path(callbackInfo).rfind('/') + 1); +} + +MODFUNC(user_shell_version) +{ + const std::string& shell_name = user_shell_name(callbackInfo); + std::string ret; + + if (shell_name == "nu") + Process("nu -c \"version | get version\"", "", [&](const char* bytes, size_t n) { ret.assign(bytes, n); }); + else + Process(fmt::format("{} -c 'echo \"${}_VERSION\"'", shell_name, str_toupper(shell_name.data())), "", + [&](const char* bytes, size_t n) { ret.assign(bytes, n); }); + + strip(ret); + return ret; +} + +// clang-format off +MMODFUNC(user_name) +{ return g_pwd->pw_name; } + +ODFUNC(user_term_name) +{ return "Termux"; } + +MODFUNC(user_term_version) +{ return getenv("TERMUX_VERSION"); } + +MODFUNC(user_wm_name) +{ return MAGIC_LINE; } + +MODFUNC(user_wm_version) +{ return MAGIC_LINE; } + +MODFUNC(user_de_name) +{ return MAGIC_LINE; } + +MODFUNC(user_de_version) +{ return MAGIC_LINE; } + +#endif diff --git a/src/core-modules/core-modules.cc b/src/core-modules/core-modules.cc new file mode 100644 index 00000000..e22d9ebf --- /dev/null +++ b/src/core-modules/core-modules.cc @@ -0,0 +1,601 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "core-modules.hh" + +#include <dlfcn.h> +#include <unistd.h> + +#include <algorithm> +#include <array> +#include <cstdio> +#include <string> +#include <string_view> +#include <utility> + +#include "config.hpp" +#include "fmt/format.h" +#include "libcufetch/cufetch.hh" +#include "linux/utils/packages.hh" +#include "platform.hpp" +#include "switch_fnv1a.hpp" +#include "util.hpp" + +#if !CF_MACOS +#include <mntent.h> +#endif + +using unused = const callbackInfo_t*; + +std::string amount(const double amount, const moduleArgs_t* moduleArgs) +{ + constexpr std::array<std::string_view, 32> sorted_valid_prefixes = { "B", "EB", "EiB", "GB", "GiB", "kB", + "KiB", "MB", "MiB", "PB", "PiB", "TB", + "TiB", "YB", "YiB", "ZB", "ZiB" }; + + if (!moduleArgs->next || moduleArgs->next->value.empty()) + { + byte_units_t amount_unit = auto_divide_bytes(amount, 1024); + return fmt::format("{:.2f} {}", amount_unit.num_bytes, amount_unit.unit); + } + + const std::string& prefix = moduleArgs->next->value; + if (std::binary_search(sorted_valid_prefixes.begin(), sorted_valid_prefixes.end(), prefix)) + return fmt::format("{:.5f}", divide_bytes(amount, prefix).num_bytes); + return "0"; +} + +static std::string get_auto_uptime(const std::uint16_t days, const std::uint16_t hours, const std::uint16_t mins, + const std::uint16_t secs, const Config& config) +{ + if (days == 0 && hours == 0 && mins == 0) + return fmt::format("{}{}", secs, config.uptime_s_fmt); + + std::string ret; + + if (days > 0) + ret += fmt::format("{}{}, ", days, config.uptime_d_fmt); + + if (hours > 0) + ret += fmt::format("{}{}, ", hours, config.uptime_h_fmt); + + if (mins > 0) + ret += fmt::format("{}{}, ", mins, config.uptime_m_fmt); + + ret.erase(ret.length() - 2); // the last ", " + + return ret; +} + +static std::string get_colors_symbol(const callbackInfo_t* callback, bool is_light) +{ + const moduleArgs_t* symbolArg; + for (symbolArg = callback->moduleArgs; symbolArg && symbolArg->name != "symbol"; symbolArg = symbolArg->next) + ; + if (symbolArg->value.empty()) + die( + _("color symbol palette argument module is empty.\n" + "Must be used like 'colors_symbol(`symbol for printing the color palette`)'")); + + if (is_light) + return parse( + fmt::format("${{\033[90m}} {0} ${{\033[91m}} {0} ${{\033[92m}} {0} ${{\033[93m}} {0} ${{\033[94m}} " + "{0} ${{\033[95m}} {0} ${{\033[96m}} {0} ${{\033[97m}} {0} ${{0}}", + symbolArg->value), + callback->parse_args); + else + return parse( + fmt::format("${{\033[30m}} {0} ${{\033[31m}} {0} ${{\033[32m}} {0} ${{\033[33m}} {0} ${{\033[34m}} " + "{0} ${{\033[35m}} {0} ${{\033[36m}} {0} ${{\033[37m}} {0} ${{0}}", + symbolArg->value), + callback->parse_args); +} + +static std::string prettify_de_name(const std::string_view de_name) +{ + switch (fnv1a16::hash(str_tolower(de_name.data()))) + { + case "kde"_fnv1a16: + case "plasma"_fnv1a16: + case "plasmashell"_fnv1a16: + case "plasmawayland"_fnv1a16: return "KDE Plasma"; + + case "gnome"_fnv1a16: + case "gnome-shell"_fnv1a16: return "GNOME"; + + case "xfce"_fnv1a16: + case "xfce4"_fnv1a16: + case "xfce4-session"_fnv1a16: return "Xfce4"; + + case "mate"_fnv1a16: + case "mate-session"_fnv1a16: return "Mate"; + + case "lxqt"_fnv1a16: + case "lxqt-session"_fnv1a16: return "LXQt"; + } + + return de_name.data(); +} + +static std::string prettify_term_name(const std::string_view term_name) +{ + switch (fnv1a16::hash(str_tolower(term_name.data()))) + { + case "gnome-terminal"_fnv1a16: + case "gnome terminal"_fnv1a16: return "GNOME Terminal"; + + case "gnome-console"_fnv1a16: + case "gnome console"_fnv1a16: return "GNOME console"; + } + return term_name.data(); +} + +MODFUNC(disk_fmt) +{ + const callbackInfo_t* callback = callbackInfo; + const double used = disk_used(callback); + const double total = disk_total(callback); + const std::string& perc = get_and_color_percentage(used, total, callback->parse_args, false); + + // clang-format off + std::string result {fmt::format("{} / {} {}", + amount(used, callback->moduleArgs), + amount(total, callback->moduleArgs), + parse("${0}(" + perc + ")", callback->parse_args)) + }; + // clang-format on + const std::string& fsname = disk_fsname(callback); + if (fsname != MAGIC_LINE) + result += " - " + fsname; + + const std::string& types = disk_types(callback); + if (!types.empty()) + result += " [" + types + "]"; + + return result; +} + +MODFUNC(ram_fmt) +{ + const callbackInfo_t* callback = callbackInfo; + const double used = ram_used(); + const double total = ram_total(); + const std::string& perc = get_and_color_percentage(used, total, callback->parse_args, false); + + // clang-format off + return fmt::format("{} / {} {}", + amount(used, callback->moduleArgs), + amount(total, callback->moduleArgs), + parse("${0}(" + perc + ")", callback->parse_args)) + ; + // clang-format on +} + +MODFUNC(swap_fmt) +{ + const callbackInfo_t* callback = callbackInfo; + const double used = swap_used(); + const double total = swap_total(); + if (used < 1) + return "Disabled"; + + const std::string& perc = get_and_color_percentage(used, total, callback->parse_args, false); + + // clang-format off + return fmt::format("{} / {} {}", + amount(used, callback->moduleArgs), + amount(total, callback->moduleArgs), + parse("${0}(" + perc + ")", callback->parse_args)) + ; + // clang-format on +} + +MODFUNC(battery_fmt) +{ + return fmt::format( + "{} [{}]", get_and_color_percentage(std::stod(battery_perc(callbackInfo)), 100, callbackInfo->parse_args, true), + battery_status(callbackInfo)); +} + +MODFUNC(theme_cursor_fmt) +{ + const std::string& size = theme_cursor_size(callbackInfo); + const std::string& name = theme_cursor_name(callbackInfo); + if (size == UNKNOWN || size == MAGIC_LINE) + return name; + + return fmt::format("{} ({}px)", name, size); +} + +void core_plugins_start(const Config& config) +{ + // ------------ INIT STUFF ------------ + const size_t uptime_secs = os_uptime(); + const size_t uptime_mins = uptime_secs / (60); + const size_t uptime_hours = uptime_secs / (60 * 60); + const size_t uptime_days = uptime_secs / (60 * 60 * 24); + + if (uname(&g_uname_infos) != 0) + die(_("uname() failed: {}\nCould not get system infos"), std::strerror(errno)); + + if (g_pwd = getpwuid(getuid()), !g_pwd) + die(_("getpwent failed: {}\nCould not get user infos"), std::strerror(errno)); + + term_pid = get_terminal_pid(); + term_name = get_terminal_name(); + if (hasStart(str_tolower(term_name), "login") || hasStart(term_name, "init") || hasStart(term_name, "(init)")) + { + is_tty = true; + term_name = ttyname(STDIN_FILENO); + } +#if !CF_MACOS + os_release = fopen("/etc/os-release", "r"); + cpuinfo = fopen("/proc/cpuinfo", "r"); + meminfo = fopen("/proc/meminfo", "r"); + mountsFile = setmntent("/proc/mounts", "r"); +#endif + +#if CF_ANDROID + is_tty = true; +#endif + + // ------------ MODULES REGISTERING ------------ + module_t os_name_pretty_module = {"pretty", "OS pretty name [Ubuntu 22.04.4 LTS; Arch Linux]", {}, os_pretty_name}; + module_t os_name_id_module = {"id", "OS id name [ubuntu, arch]", {}, os_name_id}; + module_t os_name_module = { "name", "OS basic name [Ubuntu]", { + std::move(os_name_pretty_module), + std::move(os_name_id_module) + }, os_name }; + + module_t os_uptime_s_module = {"secs", "uptime of the system in seconds [45]", {}, [=](unused) {return fmt::to_string(uptime_secs % 60);}}; + module_t os_uptime_m_module = {"mins", "uptime of the system in minutes [12]", {}, [=](unused) {return fmt::to_string(uptime_mins % 60);}}; + module_t os_uptime_h_module = {"hours", "uptime of the system in hours [34]", {}, [=](unused) {return fmt::to_string(uptime_hours % 24);}}; + module_t os_uptime_d_module = {"days", "uptime of the system in days [2]", {}, [=](unused) {return fmt::to_string(uptime_days);}}; + module_t os_uptime_module = {"uptime", "(auto) uptime of the system [36 mins, 3 hours, 23 days]", { + std::move(os_uptime_s_module), + std::move(os_uptime_m_module), + std::move(os_uptime_h_module), + std::move(os_uptime_d_module), + }, [=](unused) { return get_auto_uptime(uptime_days, uptime_hours % 24, uptime_mins % 60, + uptime_secs % 60, config); }}; + + module_t os_hostname_module = {"hostname", "hostname of the OS [myMainPC]", {}, os_hostname}; + + module_t os_kernel_name_module = {"name", "kernel name [Linux]", {}, os_kernel_name}; + module_t os_kernel_version_module = {"version", "kernel version [6.9.3-zen1-1-zen]", {}, os_kernel_version}; + module_t os_kernel_module = {"kernel", "kernel name and version [Linux 6.9.3-zen1-1-zen]", { + std::move(os_kernel_name_module), + std::move(os_kernel_version_module) + }, [](unused _) {return os_kernel_name(_) + " " + os_kernel_version(_);}}; + + module_t os_initsys_name_module = {"name", "Init system name [systemd]", {}, os_initsys_name}; + module_t os_initsys_version_module = {"version", "Init system version [256.5-1-arch]", {}, os_initsys_version}; + module_t os_initsys_module = {"initsys", "Init system name and version [systemd 256.5-1-arch]", { + std::move(os_initsys_name_module), + std::move(os_initsys_version_module), + }, [](unused _) {return os_initsys_name(_) + " " + os_initsys_version(_);}}; + + module_t os_pkgs_module = {"pkgs", "Count of system packages", {}, [&](unused){ return get_all_pkgs(config); }}; + + // $<os> + module_t os_module = { "os", "OS modules", { + std::move(os_name_module), + std::move(os_uptime_module), + std::move(os_kernel_module), + std::move(os_hostname_module), + std::move(os_initsys_module), + std::move(os_pkgs_module), + }, NULL}; + cfRegisterModule(os_module); + + // $<system> + module_t host_name_module = {"name", "Host (aka. Motherboard) model name [PRO B550M-P GEN3 (MS-7D95)]", {}, host_name}; + module_t host_version_module = {"version", "Host (aka. Motherboard) model version [1.0]", {}, host_version}; + module_t host_vendor_module = {"vendor", "Host (aka. Motherboard) model vendor [Micro-Star International Co., Ltd.]", {}, host_vendor}; + module_t host_module = {"host", "Host (aka. Motherboard) model name with vendor and version [MSI PRO B550M-P GEN3 (MS-7D95) 1.0]", { + std::move(host_name_module), + std::move(host_version_module), + std::move(host_vendor_module) }, + host}; + + module_t arch_module = {"arch", "the architecture of the machine [x86_64, aarch64]", {}, arch}; + + module_t system_module = { "system", "System modules", { + std::move(host_module), + std::move(arch_module), + }, NULL }; + cfRegisterModule(system_module); + + // $<cpu> + module_t cpu_name_module = {"name", "CPU model name [AMD Ryzen 5 5500]", {}, cpu_name}; + module_t cpu_nproc_module = {"nproc" , "CPU number of virtual processors [12]", {}, cpu_nproc}; + + module_t cpu_freq_cur_module = {"current", "CPU current frequency (in GHz) [3.42]", {}, cpu_freq_cur}; + module_t cpu_freq_max_module = {"max", "CPU maximum frequency (in GHz) [4.90]", {}, cpu_freq_max}; + module_t cpu_freq_min_module = {"min", "CPU minimum frequency (in GHz) [2.45]", {}, cpu_freq_min}; + module_t cpu_freq_bios_module = {"bios_limit", "CPU frequency limited by bios (in GHz) [4.32]", {}, cpu_freq_bios}; + module_t cpu_freq_module = {"freq", "CPU frequency info (GHz)", { + std::move(cpu_freq_cur_module), + std::move(cpu_freq_max_module), + std::move(cpu_freq_min_module), + std::move(cpu_freq_bios_module), + }, cpu_freq_max}; + + module_t cpu_temp_C_module = {"C", "CPU temperature in Celsius [40.62]", {}, [](unused) {return fmt::format("{:.2f}°C", cpu_temp());}}; + module_t cpu_temp_F_module = {"F", "CPU temperature in Fahrenheit [105.12]", {}, [](unused) {return fmt::format("{:.2f}°F", cpu_temp() * 1.8 + 34);}}; + module_t cpu_temp_K_module = {"K", "CPU temperature in Kelvin [313.77]", {}, [](unused) {return fmt::format("{:.2f}°K", cpu_temp() + 273.15);}}; + module_t cpu_temp_module = {"temp", "CPU temperature (by the chosen unit) [40.62]", { + std::move(cpu_temp_C_module), + std::move(cpu_temp_F_module), + std::move(cpu_temp_K_module), + }, [](unused) {return fmt::format("{:.2f}°C", cpu_temp());}}; + + module_t cpu_module = {"cpu", "CPU model name with number of virtual processors and max freq [AMD Ryzen 5 5500 (12) @ 4.90 GHz]",{ + std::move(cpu_name_module), + std::move(cpu_nproc_module), + std::move(cpu_freq_module), + std::move(cpu_temp_module), + }, [](unused _) { + return fmt::format("{} ({}) @ {} GHz", cpu_name(_), cpu_nproc(_), cpu_freq_max(_)); + }}; + cfRegisterModule(cpu_module); + + // $<user> + module_t user_name_module = {"name", "name you are currently logged in (not real name) [toni69]", {}, user_name}; + + module_t user_shell_path_module = {"path", "login shell (with path) [/bin/zsh]", {}, user_shell_path}; + module_t user_shell_name_module = {"name", "login shell [zsh]", {}, user_shell_name}; + module_t user_shell_version_module = {"version", "login shell version (may be not correct) [5.9]", {}, user_shell_version}; + module_t user_shell_module = {"shell", "login shell name and version [zsh 5.9]", { + std::move(user_shell_name_module), + std::move(user_shell_path_module), + std::move(user_shell_version_module), + }, [](unused _) {return user_shell_name(_) + " " + user_shell_version(_);}}; + + module_t user_term_name_module = {"name", "terminal name [alacritty]", {}, [](unused _){ return prettify_term_name(user_term_name(_));}}; + module_t user_term_version_module = {"version", "terminal version [0.13.2]", {}, user_shell_version}; + module_t user_term_module = {"terminal", "terminal name and version [alacritty 0.13.2]", { + std::move(user_term_version_module), + std::move(user_term_name_module) + }, [](unused _) {return user_term_name(_) + " " + user_term_version(_);}}; + + module_t user_wm_name_module = {"name", "Window Manager current session name [dwm; xfwm4]", {}, user_wm_name}; + module_t user_wm_version_module = {"version", "Window Manager version (may not work correctly) [6.2; 4.18.0]", {}, user_wm_version}; + module_t user_wm_module = {"wm", "Window Manager current session name and version", { + std::move(user_wm_version_module), + std::move(user_wm_name_module) + }, [](unused _) {return user_wm_name(_) + " " + user_wm_version(_);}}; + + module_t user_de_name_module = {"name", "Desktop Environment current session name [Plasma]", {}, [](unused _){ return prettify_de_name(user_de_name(_)); }}; + module_t user_de_version_module = {"version", "Desktop Environment version (if available)", {}, user_de_version}; + module_t user_de_module = {"de", "Desktop Environment current session name and version", { + std::move(user_de_version_module), + std::move(user_de_name_module) + }, [](unused _) {return user_de_name(_) + " " + user_de_version(_);}}; + + module_t user_module = {"user", "User modules", { + std::move(user_name_module), + std::move(user_shell_module), + std::move(user_term_module), + std::move(user_wm_module), + std::move(user_de_module), + }, NULL}; + cfRegisterModule(user_module); + + // $<ram> + module_t ram_free_perc_module = {"perc", "percentage of available amount of RAM in total [82.31%]", {}, [](const callbackInfo_t *callback) {return get_and_color_percentage(ram_free(), ram_total(), callback->parse_args, true);}}; + module_t ram_used_perc_module = {"perc", "percentage of used amount of RAM in total [17.69%]", {}, [](const callbackInfo_t *callback) {return get_and_color_percentage(ram_used(), ram_total(), callback->parse_args, false);}}; + module_t ram_free_module = {"free", "available amount of RAM (auto) [10.46 GiB]", {std::move(ram_free_perc_module)}, [](const callbackInfo_t *callback) { return amount(ram_free() * 1024, callback->moduleArgs); }}; + module_t ram_used_module = {"used", "used amount of RAM (auto) [2.81 GiB]", {std::move(ram_used_perc_module)}, [](const callbackInfo_t *callback) { return amount(ram_used() * 1024, callback->moduleArgs); }}; + module_t ram_total_module = {"total", "total amount of RAM (auto) [15.88 GiB]", {}, [](const callbackInfo_t *callback) { return amount(ram_total() * 1024, callback->moduleArgs); }}; + + module_t ram_module = {"ram", "used and total amount of RAM (auto) with used percentage [2.81 GiB / 15.88 GiB (5.34%)]", { + std::move(ram_free_module), + std::move(ram_used_module), + std::move(ram_total_module) + }, ram_fmt}; + cfRegisterModule(ram_module); + + // $<swap> + module_t swap_free_perc_module = {"perc", "percentage of available amount of the swapfile in total [6.71%]", {}, [](const callbackInfo_t *callback) {return get_and_color_percentage(swap_free(), swap_total(), callback->parse_args, true);}}; + module_t swap_used_perc_module = {"perc", "percentage of used amount of the swapfile in total [93.29%]", {}, [](const callbackInfo_t *callback) {return get_and_color_percentage(swap_used(), swap_total(), callback->parse_args, false);}}; + module_t swap_free_module = {"free", "available amount of the swapfile (auto) [34.32 MiB]", {std::move(swap_free_perc_module)}, [](const callbackInfo_t *callback) { return amount(swap_free() * 1024, callback->moduleArgs); }}; + module_t swap_used_module = {"used", "used amount of the swapfile (auto) [477.68 MiB]", {std::move(swap_used_perc_module)}, [](const callbackInfo_t *callback) { return amount(swap_used() * 1024, callback->moduleArgs); }}; + module_t swap_total_module = {"total", "total amount of the swapfile (auto) [512.00 MiB]", {}, [](const callbackInfo_t *callback) { return amount(swap_total() * 1024, callback->moduleArgs); }}; + + module_t swap_module = {"swap", "used and total amount of the swapfile (auto) with used percentage [477.68 MiB / 512.00 MiB (88.45%)]", { + std::move(swap_free_module), + std::move(swap_used_module), + std::move(swap_total_module) + }, swap_fmt}; + cfRegisterModule(swap_module); + + // $<disk> + module_t disk_fsname_module = {"fs", "type of filesystem [ext4]", {}, disk_fsname}; + module_t disk_device_module = {"device", "path to device [/dev/sda5]", {}, disk_device}; + module_t disk_mountdir_module = {"mountdir", "path to the device mount point [/]", {}, disk_mountdir}; + module_t disk_types_module = {"types", "an array of type options (pretty format) [Regular, External]", {}, disk_types}; + + module_t disk_free_perc_module = {"perc", "percentage of available amount of the disk in total [17.82%]", {}, [](const callbackInfo_t *callback) {return get_and_color_percentage(disk_free(callback), disk_total(callback), callback->parse_args, true);}}; + module_t disk_used_perc_module = {"perc", "percentage of used amount of the disk in total [82.18%]", {}, [](const callbackInfo_t *callback) {return get_and_color_percentage(disk_used(callback), disk_total(callback), callback->parse_args, false);}}; + module_t disk_free_module = {"free", "available amount of disk space (auto) [438.08 GiB]", {std::move(disk_free_perc_module)}, [](const callbackInfo_t *callback) { return amount(disk_free(callback), callback->moduleArgs); }}; + module_t disk_used_module = {"used", "used amount of disk space (auto) [360.02 GiB]", {std::move(disk_used_perc_module)}, [](const callbackInfo_t *callback) { return amount(disk_used(callback), callback->moduleArgs); }}; + module_t disk_total_module = {"total", "total amount of disk space (auto) [100.08 GiB]", {}, [](const callbackInfo_t *callback) { return amount(disk_total(callback), callback->moduleArgs); }}; + + module_t disk_module = {"disk", "used and total amount of disk space (auto) with type of filesystem and used percentage [379.83 GiB / 438.08 GiB (86.70%) - ext4]", { + std::move(disk_fsname_module), + std::move(disk_device_module), + std::move(disk_mountdir_module), + std::move(disk_types_module), + std::move(disk_free_module), + std::move(disk_used_module), + std::move(disk_total_module), + }, disk_fmt}; + cfRegisterModule(disk_module); + + // $<battery> + module_t battery_modelname_module = {"name", "battery model name", {}, battery_modelname}; + module_t battery_status_module = {"status", "battery current status [Discharging, AC Connected]", {}, battery_status}; + module_t battery_capacity_module = {"capacity", "battery capacity level [Normal, Critical]", {}, battery_capacity_level}; + module_t battery_technology_module = {"technology", "battery technology [Li-lion]", {}, battery_technology}; + module_t battery_vendor_module = {"manufacturer", "battery manufacturer name", {}, battery_vendor}; + module_t battery_perc_module = {"perc", "battery current percentage", {}, battery_perc}; + + module_t battery_temp_C_module = {"C", "battery temperature in Celsius [e.g. 37.12°C]", {}, [](unused) {return fmt::format("{:.2f}°C", battery_temp());}}; + module_t battery_temp_F_module = {"F", "battery temperature in Fahrenheit [e.g. 98.81°F]", {}, [](unused) {return fmt::format("{:.2f}°F", battery_temp() * 1.8 + 34);}}; + module_t battery_temp_K_module = {"K", "battery temperature in Kelvin [e.g. 310.27°K]", {}, [](unused) {return fmt::format("{:.2f}°K", battery_temp() + 273.15);}}; + module_t battery_temp_module = {"temp", "battery temperature (by the chosen unit)", { + std::move(battery_temp_C_module), + std::move(battery_temp_F_module), + std::move(battery_temp_K_module), + }, [](unused) {return fmt::format("{:.2f}°C", battery_temp());}}; + + module_t battery_module = {"battery", "battery current percentage and status [50.00% [Discharging]]", { + std::move(battery_modelname_module), + std::move(battery_status_module), + std::move(battery_capacity_module), + std::move(battery_technology_module), + std::move(battery_vendor_module), + std::move(battery_perc_module), + std::move(battery_temp_module), + }, battery_fmt}; + cfRegisterModule(battery_module); + + // $<theme> + module_t theme_gtk_all_name_module = {"name", "all GTK theme name [Arc-Dark [GTK2/3/4]]", {}, theme_gtk_all_name}; + module_t theme_gtk_all_font_module = {"font", "all GTK icons theme name [Papirus-Dark [GTK2/3], Qogir [GTK4]]", {}, theme_gtk_all_font}; + module_t theme_gtk_all_icon_module = {"icon", "all GTK fonts theme name [Hack Nerd Font 13 [GTK2], Noto Sans 10 [GTK3/4]]", {}, theme_gtk_all_icon}; + module_t theme_gtk_all_module = {"all", "Auto format module for all GTK versions", { + std::move(theme_gtk_all_name_module), + std::move(theme_gtk_all_font_module), + std::move(theme_gtk_all_icon_module) + }, NULL}; + + module_t theme_gtk_name_module = {"name", "GTK theme name [Arc-Dark]", {}, theme_gtk_name}; + module_t theme_gtk_font_module = {"font", "GTK icons theme name [Qogir-Dark]", {}, theme_gtk_font}; + module_t theme_gtk_icon_module = {"icon", "GTK font theme name [Noto Sans 10]", {}, theme_gtk_icon}; + module_t theme_gtk_module = {"gtk", "GTK module (take as argument the desidered GTK version to query (2,3,4))", { + std::move(theme_gtk_name_module), + std::move(theme_gtk_font_module), + std::move(theme_gtk_icon_module), + std::move(theme_gtk_all_module) + }, NULL}; + + module_t theme_gsettings_name_module = {"name", "Gsettings theme name [Decay-Green]", {}, theme_gsettings_name}; + module_t theme_gsettings_font_module = {"font", "Gsettings icons theme name [Papirus-Dark]", {}, theme_gsettings_font}; + module_t theme_gsettings_icon_module = {"icon", "Gsettings font theme name [Cantarell 10]", {}, theme_gsettings_icon}; + module_t theme_gsettings_cursor_name_module = {"name", "Gsettings cursor name [Bibata-Modern-Ice]", {}, theme_gsettings_cursor_name}; + module_t theme_gsettings_cursor_size_module = {"size", "Gsettings cursor size (in px) [16]", {}, theme_gsettings_cursor_size}; + module_t theme_gsettings_cursor_module = {"cursor", "Gsettings cursor name with its size (if queried) [Bibata-Modern-Ice (16px)]", { + std::move(theme_gsettings_cursor_size_module), + std::move(theme_gsettings_cursor_name_module), + }, NULL}; + module_t theme_gsettings_module = {"gsettings", "If USE_DCONF flag is set, then we're going to use dconf, else backing up to gsettings command", { + std::move(theme_gsettings_name_module), + std::move(theme_gsettings_font_module), + std::move(theme_gsettings_icon_module), + std::move(theme_gsettings_cursor_module) + }, NULL}; + + module_t theme_cursor_name_module = {"name", "cursor name [Bibata-Modern-Ice]", {}, theme_cursor_name}; + module_t theme_cursor_size_module = {"size", "cursor size (in px) [16]", {}, theme_cursor_size}; + module_t theme_cursor_module = {"cursor", "Gsettings cursor name with its size (if queried) [Bibata-Modern-Ice (16px)]", { + std::move(theme_cursor_size_module), + std::move(theme_cursor_name_module), + }, theme_cursor_fmt}; + + module_t theme_module = {"theme", "module used for generic theme stuff such as cursor", { + std::move(theme_gtk_module), + std::move(theme_gsettings_module), + std::move(theme_cursor_module), + }, NULL}; + cfRegisterModule(theme_module); + + // $<gpu> + module_t gpu_name_module = {"name", "GPU model name [GeForce GTX 1650]", {}, gpu_name}; + module_t gpu_vendor_short_module = {"short", "GPU short vendor name [NVIDIA]", {}, [](const callbackInfo_t *callback) { + return shorten_vendor_name(gpu_vendor(callback)); + }}; + module_t gpu_vendor_module = {"vendor", "GPU vendor name [NVIDIA Corporation]", { + std::move(gpu_vendor_short_module) + }, gpu_vendor}; + module_t gpu_module = {"gpu", "GPU shorter vendor name and model name [NVIDIA GeForce GTX 1650]", { + std::move(gpu_name_module), + std::move(gpu_vendor_module) + }, [](const callbackInfo_t *callback) {return shorten_vendor_name(gpu_vendor(callback)) + " " + gpu_name(callback);}}; + cfRegisterModule(gpu_module); + + // $<auto> + module_t auto_disk_module = {"disk", "Query all disks based on auto.disk.display-types", {}, auto_disk}; + module_t auto_module = {"auto", "", {std::move(auto_disk_module)}, NULL}; + cfRegisterModule(auto_module); + + // $<title> + module_t title_sep_module = { "sep", "separator between the title and the system infos (with the title length) [--------]", {}, [&](unused _) { + const size_t title_len = + std::string_view(user_name(_) + "@" + os_hostname(_)).length(); + + std::string str; + str.reserve(config.title_sep.length() * title_len); + for (size_t i = 0; i < title_len; i++) + str += config.title_sep; + + return str; + } }; + module_t title_module = { "title", "user and hostname colored with ${auto2} [toni@arch2]", { std::move(title_sep_module) }, [](const callbackInfo_t* callback) { + return parse("${auto2}$<user.name>${0}@${auto2}$<os.hostname>", callback->parse_args); + } }; + cfRegisterModule(title_module); + + // $<colors> + module_t colors_light_symbol_module = { "symbol", "light color palette with specific symbol", {}, [](const callbackInfo_t* callback) { return get_colors_symbol(callback, true); } }; + module_t colors_symbol_module = { "symbol", "color palette with specific symbol", {}, [](const callbackInfo_t* callback) { return get_colors_symbol(callback, false); }}; + module_t colors_light_module = { "light", "light color palette with background spaces", { std::move(colors_light_symbol_module) }, [](const callbackInfo_t* callback) { + return parse( + "${\033[100m} ${\033[101m} ${\033[102m} ${\033[103m} ${\033[104m} " + " ${\033[105m} ${\033[106m} ${\033[107m} ${0}", callback->parse_args); + } }; + module_t colors_module = { "colors", "color palette with background spaces", + { std::move(colors_symbol_module), std::move(colors_light_module) }, + [](const callbackInfo_t* callback) { + return parse( + "${\033[40m} ${\033[41m} ${\033[42m} ${\033[43m} ${\033[44m} " + "${\033[45m} ${\033[46m} ${\033[47m} ${0}", + callback->parse_args); + } }; + cfRegisterModule(colors_module); +} + +void core_plugins_finish() +{ + if (mountsFile) + fclose(mountsFile); + if (os_release) + fclose(os_release); + if (meminfo) + fclose(meminfo); + if (cpuinfo) + fclose(cpuinfo); +} diff --git a/src/core-modules/linux/battery.cc b/src/core-modules/linux/battery.cc new file mode 100644 index 00000000..eab5aa8c --- /dev/null +++ b/src/core-modules/linux/battery.cc @@ -0,0 +1,102 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_LINUX + +#include <unistd.h> + +#include <string> + +#include "core-modules.hh" +#include "libcufetch/common.hh" +#include "util.hpp" + +static std::string read_strip_syspath(const std::string_view path) +{ + std::string str = read_by_syspath(path); + debug("str = {} || path = {}", str, path); + + // optimization + if (str.back() == '\n') + str.pop_back(); + else if (str != UNKNOWN) + strip(str); + + return str; +} + +static std::string get_battery_info(const std::string& file) +{ + if (access("/sys/class/power_supply/", F_OK) != 0) + return MAGIC_LINE; + + for (const auto& dir_entry : std::filesystem::directory_iterator{ "/sys/class/power_supply/" }) + { + const std::string& path = dir_entry.path().string() + "/"; + debug("battery path = {}", path); + + const std::string& tmp = read_strip_syspath(path + "type"); + if (tmp == UNKNOWN || tmp != "Battery") + continue; + if (read_strip_syspath(path + "scope") == "Device") + continue; + + debug("battery found yeappyy"); + return read_strip_syspath(path + file); + } + + return UNKNOWN; +} + +// clang-format off +MODFUNC(battery_modelname) +{ return get_battery_info("model_name"); } + +MODFUNC(battery_perc) +{ return get_battery_info("capacity"); } + +MODFUNC(battery_status) +{ return get_battery_info("status"); } + +MODFUNC(battery_capacity_level) +{ return get_battery_info("capacity_level"); } + +MODFUNC(battery_technology) +{ return get_battery_info("technology"); } + +MODFUNC(battery_vendor) +{ return get_battery_info("manufacturer"); } + +double battery_temp() +{ + const std::string& temp = get_battery_info("temp"); + if (temp != UNKNOWN || temp != MAGIC_LINE) + return std::stod(temp) / 10; + + return 0; +} + +#endif diff --git a/src/query/linux/cpu.cpp b/src/core-modules/linux/cpu.cc similarity index 69% rename from src/query/linux/cpu.cpp rename to src/core-modules/linux/cpu.cc index cf5ca425..8abcd80e 100644 --- a/src/query/linux/cpu.cpp +++ b/src/core-modules/linux/cpu.cc @@ -1,46 +1,17 @@ -/* - * Copyright 2025 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - #include "platform.hpp" #if CF_ANDROID || CF_LINUX +#include <unistd.h> -#include <sys/types.h> - -#include <array> -#include <cstdlib> -#include <filesystem> -#include <fstream> +#include <cstring> #include <string> -#include <string_view> +#include "core-modules.hh" #include "fmt/format.h" -#include "query.hpp" +#include "libcufetch/common.hh" #include "switch_fnv1a.hpp" #include "util.hpp" -using namespace Query; +const std::string freq_dir = "/sys/devices/system/cpu/cpu0/cpufreq"; #if CF_ANDROID // https://en.wikipedia.org/wiki/List_of_Qualcomm_Snapdragon_systems_on_chips @@ -490,148 +461,63 @@ static std::string detect_mediatek(const std::string& model_name) } #endif // CF_ANDROID -static std::string get_from_text(std::string& line) +static void trim(char* str) { - std::string amount = line.substr(line.find(':') + 1); - strip(amount); - return amount; + if (!str) + return; + + // Trim leading space + char* p = str; + while (isspace((unsigned char)*p)) + ++p; + memmove(str, p, strlen(p) + 1); + + // Trim trailing space + p = str + strlen(str) - 1; + while (p >= str && isspace((unsigned char)*p)) + --p; + p[1] = '\0'; } -static CPU::CPU_t get_cpu_infos() +static bool read_value(const char* name, size_t n, bool do_rewind, char* buf, size_t buf_size) { - CPU::CPU_t ret; - debug("calling in CPU {}", __PRETTY_FUNCTION__); - constexpr std::string_view cpuinfo_path = "/proc/cpuinfo"; - std::ifstream file(cpuinfo_path.data()); - if (!file.is_open()) - { - error(_("Could not open {}"), cpuinfo_path); - return ret; - } - - std::string line; - float cpu_mhz = -1; - while (std::getline(file, line)) - { - if (hasStart(line, "model name")) - ret.name = get_from_text(line); - - else if (hasStart(line, "processor")) - ret.nproc = get_from_text(line); - - else if (hasStart(line, "cpu MHz")) - { - double tmp = std::stof(get_from_text(line)); - if (tmp > cpu_mhz) - cpu_mhz = tmp; - } - } - -#if CF_ANDROID - if (ret.name == UNKNOWN) + if (!cpuinfo || !buf || !buf_size) + return false; + if (do_rewind) + rewind(cpuinfo); + + char* line = NULL; + size_t len = 0; + bool found = false; + while (getline(&line, &len, cpuinfo) != -1) { - ret.modelname = get_android_property("ro.soc.model"); - if (ret.modelname.empty()) - { - ret.vendor = "MTK"; - ret.modelname = get_android_property("ro.mediatek.platform"); - } - if (ret.vendor.empty()) - { - ret.vendor = get_android_property("ro.soc.manufacturer"); - if (ret.vendor.empty()) - ret.vendor = get_android_property("ro.product.product.manufacturer"); - } - - if ((ret.vendor == "QTI" || ret.vendor == "QUALCOMM") && - (hasStart(ret.modelname, "SM") || hasStart(ret.modelname, "APQ") || hasStart(ret.modelname, "MSM") || - hasStart(ret.modelname, "SDM") || hasStart(ret.modelname, "QM"))) - ret.name = fmt::format("Qualcomm {} [{}]", detect_qualcomm(ret.modelname), ret.modelname); - else if (ret.vendor == "Samsung") - ret.name = fmt::format("Samsung {} [{}]", detect_exynos(ret.modelname), ret.modelname); - else if (ret.vendor == "MTK") - ret.name = fmt::format("Mediatek {} [{}]", detect_mediatek(ret.modelname), ret.modelname); - else - ret.name = ret.vendor + " " + ret.modelname; - } -#endif + if (strncmp(line, name, n)) + continue; - // sometimes /proc/cpuinfo at model name - // the name will contain the min freq - // happens on intel cpus especially - const size_t pos = ret.name.rfind('@'); - if (pos != std::string::npos) - ret.name.erase(pos - 1); + char* colon = strchr(line, ':'); + if (!colon) + continue; - cpu_mhz /= 1000; - ret.freq_max_cpuinfo = cpu_mhz; + // Extract and trim value + char* val = colon + 1; + while (isspace((unsigned char)*val)) + ++val; + trim(val); - // add 1 to the nproc - ret.nproc = fmt::to_string(std::stoi(ret.nproc) + 1); + // Safe copy to buffer + strncpy(buf, val, buf_size - 1); + buf[buf_size - 1] = '\0'; - const std::string freq_dir = "/sys/devices/system/cpu/cpu0/cpufreq"; - if (std::filesystem::exists(freq_dir)) - { - std::ifstream cpu_bios_limit_f(freq_dir + "/bios_limit"); - std::ifstream cpu_scaling_cur_f(freq_dir + "/scaling_cur_freq"); - std::ifstream cpu_scaling_max_f(freq_dir + "/scaling_max_freq"); - std::ifstream cpu_scaling_min_f(freq_dir + "/scaling_min_freq"); - - std::string freq_bios_limit, freq_cpu_scaling_cur, freq_cpu_scaling_max, freq_cpu_scaling_min; - - std::getline(cpu_bios_limit_f, freq_bios_limit); - std::getline(cpu_scaling_cur_f, freq_cpu_scaling_cur); - std::getline(cpu_scaling_max_f, freq_cpu_scaling_max); - std::getline(cpu_scaling_min_f, freq_cpu_scaling_min); - - ret.freq_bios_limit = freq_bios_limit.empty() ? 0 : (std::stof(freq_bios_limit) / 1000000); - ret.freq_cur = freq_cpu_scaling_cur.empty() ? 0 : (std::stof(freq_cpu_scaling_cur) / 1000000); - ret.freq_max = freq_cpu_scaling_max.empty() ? 0 : (std::stof(freq_cpu_scaling_max) / 1000000); - ret.freq_min = freq_cpu_scaling_min.empty() ? 0 : (std::stof(freq_cpu_scaling_min) / 1000000); + found = true; + break; } - return ret; + free(line); + return found; } -static double get_cpu_temp() +float cpu_temp() { -#if CF_ANDROID - // https://github.com/kamgurgul/cpu-info/blob/master/shared/src/androidMain/kotlin/com/kgurgul/cpuinfo/data/provider/TemperatureProvider.android.kt#L119 - constexpr std::array<std::string_view, 20> temp_paths = { - "/sys/devices/system/cpu/cpu0/cpufreq/cpu_temp", - "/sys/devices/system/cpu/cpu0/cpufreq/FakeShmoo_cpu_temp", - "/sys/class/thermal/thermal_zone0/temp", - "/sys/class/i2c-adapter/i2c-4/4-004c/temperature", - "/sys/devices/platform/tegra-i2c.3/i2c-4/4-004c/temperature", - "/sys/devices/platform/omap/omap_temp_sensor.0/temperature", - "/sys/devices/platform/tegra_tmon/temp1_input", - "/sys/kernel/debug/tegra_thermal/temp_tj", - "/sys/devices/platform/s5p-tmu/temperature", - "/sys/class/thermal/thermal_zone1/temp", - "/sys/class/hwmon/hwmon0/device/temp1_input", - "/sys/devices/virtual/thermal/thermal_zone1/temp", - "/sys/devices/virtual/thermal/thermal_zone0/temp", - "/sys/class/thermal/thermal_zone3/temp", - "/sys/class/thermal/thermal_zone4/temp", - "/sys/class/hwmon/hwmonX/temp1_input", - "/sys/devices/platform/s5p-tmu/curr_temp", - "/sys/htc/cpu_temp", - "/sys/devices/platform/tegra-i2c.3/i2c-4/4-004c/ext_temperature", - "/sys/devices/platform/tegra-tsensor/tsensor_temperature", - }; - for (const std::string_view path : temp_paths) - { - debug("checking {}", path); - if (!std::filesystem::exists(path)) - continue; - - const double ret = std::stod(read_by_syspath(path)) / 1000.0; - debug("cpu temp ret = {}", ret); - - if (ret >= -1.0 && ret <= 250.0) - return ret; - } -#else for (const auto& dir : std::filesystem::directory_iterator{ "/sys/class/hwmon/" }) { const std::string& name = read_by_syspath((dir.path() / "name").string()); @@ -639,62 +525,115 @@ static double get_cpu_temp() if (name != "cpu" && name != "k10temp" && name != "coretemp") continue; - const std::string& temp_file = std::filesystem::exists(dir.path() / "temp1_input") - ? dir.path() / "temp1_input" - : dir.path() / "device/temp1_input"; - if (!std::filesystem::exists(temp_file)) + const std::string& temp_file = (access((dir.path() / "temp1_input").string().c_str(), F_OK) != 0) + ? dir.path() / "device/temp1_input" + : dir.path() / "temp1_input"; + if (access(temp_file.c_str(), F_OK) != 0) continue; - const double ret = std::stod(read_by_syspath(temp_file)); + const float ret = std::stof(read_by_syspath(temp_file)); debug("cpu temp ret = {}", ret); - return ret / 1000.0; + return ret / 1000.0f; } -#endif - - return 0.0; + return 0.0f; } -CPU::CPU() noexcept -{ - CHECK_INIT(m_bInit); - - m_cpu_infos = get_cpu_infos(); -} +#if CF_ANDROID +MODFUNC(android_cpu_model_name) +{ return get_android_property("ro.soc.model"); } -// clang-format off -std::string& CPU::name() noexcept -{ return m_cpu_infos.name; } +MODFUNC(android_cpu_vendor) +{ + if (android_cpu_model_name(nullptr).empty()) + return "MTK"; -std::string& CPU::nproc() noexcept -{ return m_cpu_infos.nproc; } + std::string vendor = get_android_property("ro.soc.manufacturer"); + if (vendor.empty()) + vendor = get_android_property("ro.product.product.manufacturer"); -std::string& CPU::vendor() noexcept -{ return m_cpu_infos.vendor; } + return vendor; +} +#endif -std::string& CPU::modelname() noexcept -{ return m_cpu_infos.modelname; } +MODFUNC(cpu_name) +{ + char name[4096]; +#if CF_LINUX + if (!read_value("model name", "model name"_len, true, name, sizeof(name))) + return UNKNOWN; +#elif CF_ANDROID + if (!read_value("model name", "model name"_len, true, name, sizeof(name))) + { + const std::string& vendor = android_cpu_vendor(nullptr); + const std::string& model_name = android_cpu_model_name(nullptr); + if (vendor == "QTI" || vendor == "QUALCOMM") + strcpy(name, fmt::format("Qualcomm {} [{}]", detect_qualcomm(model_name), model_name).c_str()); + else if (vendor == "Samsung") + strcpy(name, fmt::format("Samsung {} [{}]", detect_exynos(model_name), model_name).c_str()); + else if (vendor == "MTK") + strcpy(name, fmt::format("Mediatek {} [{}]", detect_mediatek(model_name), model_name).c_str()); + else + strcpy(name, (vendor + " " + model_name).c_str()); + } +#endif + // sometimes /proc/cpuinfo at model name + // the name will contain the min freq + // happens on intel cpus especially + char* at = strrchr(name, '@'); + if (!at) + return name; + if (at > name && *(at - 1) == ' ') + *(at - 1) = '\0'; + else + *at = '\0'; + + trim(name); + return name; +} -double& CPU::freq_bios_limit() noexcept -{ return m_cpu_infos.freq_bios_limit; } +MODFUNC(cpu_nproc) +{ + uint nproc = 0; + rewind(cpuinfo); -double& CPU::freq_cur() noexcept -{ return m_cpu_infos.freq_cur; } + char* line = NULL; + size_t len = 0; + while (getline(&line, &len, cpuinfo) != -1) + { + if (strncmp(line, "processor", "processor"_len) == 0) + nproc++; + } + free(line); + return fmt::to_string(nproc); +} -double& CPU::freq_max() noexcept -{ return (m_cpu_infos.freq_max <= 0) ? m_cpu_infos.freq_max_cpuinfo : m_cpu_infos.freq_max; } +MODFUNC(cpu_freq_cur) +{ + if (access((freq_dir + "/scaling_cur_freq").c_str(), F_OK) != 0) + return "0"; + return fmt::format("{:.2f}", std::stof(read_by_syspath(freq_dir + "/scaling_cur_freq")) / 1000000); +} -double& CPU::freq_min() noexcept -{ return m_cpu_infos.freq_min; } +MODFUNC(cpu_freq_max) +{ + if (access((freq_dir + "/scaling_max_freq").c_str(), F_OK) != 0) + return "0"; + return fmt::format("{:.2f}", std::stof(read_by_syspath(freq_dir + "/scaling_max_freq")) / 1000000); +} -double& CPU::temp() noexcept +MODFUNC(cpu_freq_min) { - static bool done = false; - if (!done) - m_cpu_infos.temp = get_cpu_temp(); - done = true; + if (access((freq_dir + "/scaling_min_freq").c_str(), F_OK) != 0) + return "0"; + return fmt::format("{:.2f}", std::stof(read_by_syspath(freq_dir + "/scaling_min_freq")) / 1000000); +} - return m_cpu_infos.temp; +MODFUNC(cpu_freq_bios) +{ + if (access((freq_dir + "/bios_limit").c_str(), F_OK) != 0) + return "0"; + return fmt::format("{:.2f}", std::stof(read_by_syspath(freq_dir + "/bios_limit")) / 1000000); } -#endif // CF_ANDROID || CF_LINUX +#endif diff --git a/src/core-modules/linux/disk.cc b/src/core-modules/linux/disk.cc new file mode 100644 index 00000000..765e0c3c --- /dev/null +++ b/src/core-modules/linux/disk.cc @@ -0,0 +1,290 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_LINUX || CF_ANDROID + +#include <mntent.h> +#include <sys/stat.h> +#include <sys/statvfs.h> +#include <unistd.h> + +#include <cstdio> + +#include "core-modules.hh" +#include "libcufetch/common.hh" +#include "libcufetch/config.hh" +#include "switch_fnv1a.hpp" +#include "util.hpp" + +// https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/disk/disk_linux.c +static bool is_physical_device(const mntent* device) +{ +#if !CF_ANDROID // On Android, `/dev` is not accessible, so that the following checks always fail + + // Always show the root path + if (strcmp(device->mnt_dir, "/") == 0) + return true; + + if (strcmp(device->mnt_fsname, "none") == 0) + return false; + + // DrvFs is a filesystem plugin to WSL that was designed to support interop between WSL and the Windows filesystem. + if (strcmp(device->mnt_type, "9p") == 0) + return std::string_view(device->mnt_opts).find("aname=drvfs") != std::string_view::npos; + + // ZFS pool + if (strcmp(device->mnt_type, "zfs") == 0) + return true; + + // Pseudo filesystems don't have a device in /dev + if (!hasStart(device->mnt_fsname, "/dev/")) + return false; + + // #731 + if (strcmp(device->mnt_type, "bcachefs") == 0) + return true; + + if (hasStart(device->mnt_fsname + 5, "loop") || // Ignore loop devices + hasStart(device->mnt_fsname + 5, "ram") || // Ignore ram devices + hasStart(device->mnt_fsname + 5, "fd") // Ignore fd devices + ) + return false; + + struct stat deviceStat; + if (stat(device->mnt_fsname, &deviceStat) != 0) + return false; + + // Ignore all devices that are not block devices + if (!S_ISBLK(deviceStat.st_mode)) + return false; + +#else + + // Pseudo filesystems don't have a device in /dev + if (!hasStart(device->mnt_fsname, "/dev/")) + return false; + + if (hasStart(device->mnt_fsname + 5, "loop") || // Ignore loop devices + hasStart(device->mnt_fsname + 5, "ram") || // Ignore ram devices + hasStart(device->mnt_fsname + 5, "fd") // Ignore fd devices + ) + return false; + + // https://source.android.com/docs/core/ota/apex?hl=zh-cn + if (hasStart(device->mnt_dir, "/apex/")) + return false; + +#endif // !CF_ANDROID + + return true; +} + +static bool is_removable(const mntent* device) +{ + if (!hasStart(device->mnt_fsname, "/dev/")) + return false; + + // like device->mnt_fsname.substr(5); + std::string sys_block_partition{ fmt::format("/sys/class/block/{}", (device->mnt_fsname + "/dev/"_len)) }; + // check if it's like /dev/sda1 + if (sys_block_partition.back() >= '0' && sys_block_partition.back() <= '9') + sys_block_partition.pop_back(); + + return read_by_syspath(sys_block_partition + "/removable") == "1"; +} + +static int get_disk_type(const mntent* device) +{ +#if CF_LINUX + int ret = 0; + + if (hasStart(device->mnt_dir, "/boot") || hasStart(device->mnt_dir, "/efi")) + ret = DISK_VOLUME_TYPE_HIDDEN; + else if (is_removable(device)) + ret = DISK_VOLUME_TYPE_EXTERNAL; + else + ret = DISK_VOLUME_TYPE_REGULAR; + + if (hasmntopt(device, MNTOPT_RO)) + ret |= DISK_VOLUME_TYPE_READ_ONLY; + + return ret; +#else // CF_ANDROID + if (strcmp(device->mnt_dir, "/") == 0 || strcmp(device->mnt_dir, "/storage/emulated") == 0) + return DISK_VOLUME_TYPE_REGULAR; + + if (hasStart(device->mnt_dir, "/mnt/media_rw/")) + return DISK_VOLUME_TYPE_EXTERNAL; + + return DISK_VOLUME_TYPE_HIDDEN; +#endif +} + +static std::string format_auto_query_string(std::string str, const struct mntent* device) +{ + replace_str(str, "%1", device->mnt_dir); + replace_str(str, "%2", device->mnt_fsname); + replace_str(str, "%3", device->mnt_type); + + replace_str(str, "%4", fmt::format("$<disk({}).total>", device->mnt_dir)); + replace_str(str, "%5", fmt::format("$<disk({}).free>", device->mnt_dir)); + replace_str(str, "%6", fmt::format("$<disk({}).used>", device->mnt_dir)); + replace_str(str, "%7", fmt::format("$<disk({}).used_perc>", device->mnt_dir)); + replace_str(str, "%8", fmt::format("$<disk({}).free_perc>", device->mnt_dir)); + + return str; +} + +static struct mntent* get_disk_info(const callbackInfo_t* callbackInfo) +{ + if (callbackInfo->moduleArgs->name != "disk" || + (callbackInfo->moduleArgs->name == "disk" && callbackInfo->moduleArgs->value.empty())) + die("Module disk doesn't have an argmument to the path/device to query"); + + const std::string& path = callbackInfo->moduleArgs->value; + if (access(path.c_str(), F_OK) != 0 || !mountsFile) + die("Failed to query disk at path: '{}", path); + + struct mntent* pDevice; + while ((pDevice = getmntent(mountsFile))) + { + debug("pDevice->mnt_dir = {:<50} && pDevice->mnt_fsname = {}", pDevice->mnt_dir, pDevice->mnt_fsname); + if (path == pDevice->mnt_dir || path == pDevice->mnt_fsname) + break; + } + + rewind(mountsFile); + return pDevice; +} + +static bool get_disk_usage_info(const callbackInfo_t* callbackInfo, struct statvfs* fs) +{ + struct mntent* pDevice = get_disk_info(callbackInfo); + const std::string& path = callbackInfo->moduleArgs->value; + const std::string& statpath = (hasStart(path, "/dev") && pDevice) ? pDevice->mnt_dir : path; + + return (statvfs(statpath.c_str(), fs) == 0); +} + +// clang-format off +// don't get confused by the name pls +MODFUNC(disk_fsname) +{ return get_disk_info(callbackInfo)->mnt_type; } + +MODFUNC(disk_device) +{ return get_disk_info(callbackInfo)->mnt_fsname; } + +MODFUNC(disk_mountdir) +{ return get_disk_info(callbackInfo)->mnt_dir; } + +// clang-format on +MODFUNC(disk_types) +{ + const int types = get_disk_type(get_disk_info(callbackInfo)); + std::string str; + if (types & DISK_VOLUME_TYPE_EXTERNAL) + str += "External, "; + if (types & DISK_VOLUME_TYPE_HIDDEN) + str += "Hidden, "; + if (types & DISK_VOLUME_TYPE_READ_ONLY) + str += "Read-only, "; + + if (!str.empty()) + str.erase(str.length() - 2); + + return str; +} + +MODFUNC(auto_disk) +{ + static std::vector<std::string> queried_devices; + const ConfigBase& config = callbackInfo->parse_args.config; + const std::string& auto_disks_fmt = config.getValue<std::string>("auto.disk.fmt", "${auto}Disk (%1): $<disk(%1)>"); + int auto_disks_types = 0; + for (const std::string& str : + config.getValueArrayStr("auto.disk.display-types", { "external", "regular", "read-only" })) + { + switch (fnv1a16::hash(str)) + { + case "removable"_fnv1a16: // deprecated + case "external"_fnv1a16: auto_disks_types |= DISK_VOLUME_TYPE_EXTERNAL; break; + case "regular"_fnv1a16: auto_disks_types |= DISK_VOLUME_TYPE_REGULAR; break; + case "read-only"_fnv1a16: auto_disks_types |= DISK_VOLUME_TYPE_READ_ONLY; break; + case "hidden"_fnv1a16: auto_disks_types |= DISK_VOLUME_TYPE_HIDDEN; break; + } + } + + long old_position = ftell(mountsFile); + if (old_position == -1L) + die("Failed to get initial file position"); + + struct mntent* pDevice; + while ((pDevice = getmntent(mountsFile))) + { + if (!is_physical_device(pDevice)) + continue; + + if (!(auto_disks_types & get_disk_type(pDevice))) + continue; + + old_position = ftell(mountsFile); + if (old_position == -1L) + break; + + debug("AUTO: pDevice->mnt_dir = {} && pDevice->mnt_fsname = {}", pDevice->mnt_dir, pDevice->mnt_fsname); + callbackInfo->parse_args.no_more_reset = false; + callbackInfo->parse_args.tmp_layout.push_back( + parse(format_auto_query_string(auto_disks_fmt, pDevice), callbackInfo->parse_args)); + if (fseek(mountsFile, old_position, SEEK_SET) == -1) + die("Failed to seek back to saved position"); + } + return ""; +} + +double disk_total(const callbackInfo_t* callbackInfo) +{ + struct statvfs fs; + if (!get_disk_usage_info(callbackInfo, &fs)) + return 0; + + return static_cast<double>(fs.f_blocks * fs.f_frsize); +} + +double disk_free(const callbackInfo_t* callbackInfo) +{ + struct statvfs fs; + if (!get_disk_usage_info(callbackInfo, &fs)) + return 0; + + return static_cast<double>(fs.f_bfree * fs.f_frsize); +} + +double disk_used(const callbackInfo_t *callbackInfo) +{ + return disk_total(callbackInfo) - disk_free(callbackInfo); +} + +#endif diff --git a/src/core-modules/linux/gpu.cc b/src/core-modules/linux/gpu.cc new file mode 100644 index 00000000..e3aee59d --- /dev/null +++ b/src/core-modules/linux/gpu.cc @@ -0,0 +1,93 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_LINUX + +#include <cstdint> +#include <filesystem> +#include <string> + +#include "core-modules.hh" +#include "fmt/format.h" +#include "util.hpp" + +static std::string get_name(const std::string_view m_vendor_id_s, const std::string_view m_device_id_s) +{ + const std::string& name = binarySearchPCIArray(m_vendor_id_s, m_device_id_s); + debug("GPU binarySearchPCIArray name = {}", name); + const size_t first_bracket = name.find('['); + const size_t last_bracket = name.rfind(']'); + + // remove the chips name "TU106 [GeForce GTX 1650]" + // This should work for AMD and Intel too. + if (first_bracket != std::string::npos && last_bracket != std::string::npos) + return name.substr(first_bracket + 1, last_bracket - first_bracket - 1); + + return name; +} + +static std::string get_vendor(const std::string_view m_vendor_id_s) +{ return binarySearchPCIArray(m_vendor_id_s); } + +static std::string get_gpu_syspath(const std::string& id) +{ + const std::uint16_t max_iter = 10; + std::uint16_t id_iter = std::stoi(id); + std::string sys_path; + int i = 0; + for (; i <= max_iter; i++) + { + sys_path = "/sys/class/drm/card" + fmt::to_string(id_iter); + if (std::filesystem::exists(sys_path + "/device/device") && + std::filesystem::exists(sys_path + "/device/vendor")) + return sys_path; + else + id_iter++; + } + + error(_("Failed to parse GPU infos on the path /sys/class/drm/")); + return UNKNOWN; +} + +MODFUNC(gpu_name) +{ + const std::string& id = (callbackInfo && callbackInfo->moduleArgs->name.length() > 3) + ? callbackInfo->moduleArgs->name.substr(3) + : "0"; + const std::string& sys_path = get_gpu_syspath(id); + return get_name(read_by_syspath(sys_path + "/device/vendor"), read_by_syspath(sys_path + "/device/device")); +} + +MODFUNC(gpu_vendor) +{ + const std::string& id = (callbackInfo && callbackInfo->moduleArgs->name.length() > 3) + ? callbackInfo->moduleArgs->name.substr(3) + : "0"; + const std::string& sys_path = get_gpu_syspath(id); + return get_vendor(read_by_syspath(sys_path + "/device/vendor")); +} + +#endif diff --git a/src/core-modules/linux/os.cc b/src/core-modules/linux/os.cc new file mode 100644 index 00000000..65854a9f --- /dev/null +++ b/src/core-modules/linux/os.cc @@ -0,0 +1,155 @@ +#include "platform.hpp" +#if CF_LINUX + +#include <cassert> +#include <cstdio> +#include <cstring> +#include <fstream> +#include <string> +#include <string_view> + +#include "core-modules.hh" +#include "libcufetch/common.hh" +#include "switch_fnv1a.hpp" +#include "util.hpp" + +static std::string read_value(const std::string_view name) +{ + if (!os_release) + return UNKNOWN; + + rewind(os_release); + + std::string result{ UNKNOWN }; + char* line = nullptr; + size_t len = 0; + + while (getline(&line, &len, os_release) != -1) + { + if (name.length() > len || strncmp(line, name.data(), name.length()) != 0) + continue; + + char* start = strchr(line + name.length(), '"'); /* Get first occurence of " */ + if (start) + start++; /* Get after the " */ + else + start = line + name.length(); /* No ", get the start. */ + + char* end = strrchr(start, '"'); /* Get last occurence of " */ + if (!end) + end = line + strlen(line) - 1; /* Set to the end of the string -- no newline. */ + + result.assign(start, end - start); + break; + } + + free(line); + return result; +} + +unsigned long os_uptime() +{ + const std::string& buf = read_by_syspath("/proc/uptime"); + if (buf != UNKNOWN) + return std::stoul(buf.substr(0, buf.find('.'))); // 19065.18 190952.06 + + struct std::timespec uptime; + if (clock_gettime(CLOCK_BOOTTIME, &uptime) != 0) + return 0; + + return (unsigned long)uptime.tv_sec * 1000 + (unsigned long)uptime.tv_nsec / 1000000; +} + +MODFUNC(os_name) +{ return read_value("NAME="); } + +MODFUNC(os_pretty_name) +{ return read_value("PRETTY_NAME="); } + +MODFUNC(os_name_id) +{ return read_value("ID="); } + +MODFUNC(os_version_id) +{ return read_value("VERSION_ID="); } + +MODFUNC(os_version_codename) +{ return read_value("VERSION_CODENAME="); } + +MODFUNC(os_kernel_name) +{ return g_uname_infos.sysname; } + +MODFUNC(os_kernel_version) +{ return g_uname_infos.release; } + +MODFUNC(os_hostname) +{ return g_uname_infos.nodename; } + +MODFUNC(os_initsys_name) +{ + // there's no way PID 1 doesn't exist. + // This will always succeed (because we are on linux) + std::ifstream f_initsys("/proc/1/comm", std::ios::binary); + if (!f_initsys.is_open()) + die(_("/proc/1/comm doesn't exist! (what?)")); + + std::string initsys; + std::getline(f_initsys, initsys); + size_t pos = 0; + + if ((pos = initsys.find('\0')) != std::string::npos) + initsys.erase(pos); + + if ((pos = initsys.rfind('/')) != std::string::npos) + initsys.erase(0, pos + 1); + + return initsys; +} + +MODFUNC(os_initsys_version) +{ + std::string os_initsys_version; + std::string path; + char buf[PATH_MAX]; + if (realpath(which("init").c_str(), buf)) + path = buf; + + std::ifstream f(path, std::ios::in); + std::string line; + + const std::string& name = str_tolower(os_initsys_name(nullptr)); + switch (fnv1a16::hash(name)) + { + case "systemd"_fnv1a16: + case "systemctl"_fnv1a16: + { + while (read_binary_file(f, line)) + { + if (hasEnding(line, "running in %ssystem mode (%s)")) + { + os_initsys_version = line.substr("systemd "_len); + os_initsys_version.erase(os_initsys_version.find(' ')); + break; + } + } + } + break; + case "openrc"_fnv1a16: + { + std::string tmp; + while (read_binary_file(f, line)) + { + if (line == "RC_VERSION") + { + os_initsys_version = tmp; + break; + } + tmp = line; + } + } + break; + } + + return os_initsys_version; +} + +#endif diff --git a/src/core-modules/linux/ram.cc b/src/core-modules/linux/ram.cc new file mode 100644 index 00000000..27f8873e --- /dev/null +++ b/src/core-modules/linux/ram.cc @@ -0,0 +1,89 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_LINUX || CF_ANDROID + +#include <cstdio> +#include <string> +#include <string_view> + +#include "core-modules.hh" +#include "libcufetch/common.hh" + +static double read_value(const std::string_view key) +{ + if (!meminfo) + return 0.0; + + std::string result{ UNKNOWN }; + char* line = nullptr; + size_t len = 0; + + while (getline(&line, &len, meminfo) != -1) + { + if (strncmp(line, key.data(), key.length()) != 0) + continue; + + // Skip colon and whitespace + char* value = line + key.length(); + while (isspace(*value)) + value++; + + // Find end of numeric value (stop at first non-digit or '.') + char* end = value; + while (*end && (isdigit(*end) || *end == '.')) + end++; + + if (value != end) + result.assign(value, end - value); + break; + } + + free(line); + rewind(meminfo); + return std::stod(result) * 1024.0f; +} + +// clang-format off +double ram_free() +{ return read_value("MemAvailable:"); } + +double ram_total() +{ return read_value("MemTotal:"); } + +double ram_used() +{ return ram_total() - ram_free(); } + +double swap_free() +{ return read_value("SwapFree:"); } + +double swap_total() +{ return read_value("SwapTotal:"); } + +double swap_used() +{ return swap_total() - swap_free(); } + +#endif diff --git a/src/core-modules/linux/system.cc b/src/core-modules/linux/system.cc new file mode 100644 index 00000000..adf73019 --- /dev/null +++ b/src/core-modules/linux/system.cc @@ -0,0 +1,121 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_LINUX + +#include <sys/utsname.h> + +#include <filesystem> +#include <string> +#include <string_view> + +#include "core-modules.hh" +#include "libcufetch/common.hh" +#include "util.hpp" + +MODFUNC(host) +{ + const std::string syspath = "/sys/devices/virtual/dmi/id"; + + std::string board_name{ UNKNOWN }, board_version{ UNKNOWN }, board_vendor{ UNKNOWN }; + + if (std::filesystem::exists(syspath + "/board_name")) + { + board_name = read_by_syspath(syspath + "/board_name"); + board_version = read_by_syspath(syspath + "/board_version"); + board_vendor = read_by_syspath(syspath + "/board_vendor"); + + if (board_vendor == "Micro-Star International Co., Ltd.") + board_vendor = "MSI"; + } + else if (std::filesystem::exists(syspath + "/product_name")) + { + board_name = read_by_syspath(syspath + "/product_name"); + + static constexpr std::string_view standard_pc_name = "Standard PC"; + if (board_name.substr(0, standard_pc_name.size()) == standard_pc_name) + { + // everyone does it like "KVM/QEMU Standard PC (...) (host_version)" so why not + board_vendor = "KVM/QEMU"; + board_version = std::string_view('(' + read_by_syspath(syspath + "/product_version") + ')').data(); + } + else + board_version = read_by_syspath(syspath + "/product_version"); + } + + return board_vendor + " " + board_name + " " + board_version; +} + +MODFUNC(host_name) +{ + const std::string syspath = "/sys/devices/virtual/dmi/id"; + + if (std::filesystem::exists(syspath + "/board_name")) + return read_by_syspath(syspath + "/board_name"); + else if (std::filesystem::exists(syspath + "/product_name")) + return read_by_syspath(syspath + "/product_name"); + + return UNKNOWN; +} + +MODFUNC(host_version) +{ + const std::string syspath = "/sys/devices/virtual/dmi/id"; + + if (std::filesystem::exists(syspath + "/board_name")) + return read_by_syspath(syspath + "/board_version"); + else if (std::filesystem::exists(syspath + "/product_name")) + return read_by_syspath(syspath + "/product_version"); + + return UNKNOWN; +} + +MODFUNC(host_vendor) +{ + const std::string syspath = "/sys/devices/virtual/dmi/id"; + + std::string board_vendor{ UNKNOWN }; + + if (std::filesystem::exists(syspath + "/board_name")) + { + board_vendor = read_by_syspath(syspath + "/board_vendor"); + } + else if (std::filesystem::exists(syspath + "/product_name")) + { + const std::string& board_name = read_by_syspath(syspath + "/product_name"); + if (hasStart(board_name, "Standard PC")) + board_vendor = "KVM/QEMU"; + } + + return board_vendor; +} + +MODFUNC(arch) +{ + return g_uname_infos.machine; +} + +#endif diff --git a/src/core-modules/linux/theme.cc b/src/core-modules/linux/theme.cc new file mode 100644 index 00000000..de6eb50d --- /dev/null +++ b/src/core-modules/linux/theme.cc @@ -0,0 +1,559 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_LINUX + +#include <algorithm> +#include <array> +#include <cstdint> +#include <fstream> +#include <functional> + +#include "core-modules.hh" +#include "fmt/format.h" +#include "rapidxml-1.13/rapidxml.hpp" +#include "switch_fnv1a.hpp" +#include "util.hpp" + +#if USE_DCONF +#include <client/dconf-client.h> +#include <glib/gvariant.h> +#endif + +using ThemeInfo = std::array<std::string, 3>; // [theme, icon_theme, font] +using CursorInfo = std::array<std::string, 2>; // [name, size] + +enum +{ + THEME_NAME, + THEME_ICON, + THEME_FONT +}; + +enum +{ + CURSOR_NAME, + CURSOR_SIZE +}; + +const std::string& configDir = getHomeConfigDir(); +const std::string gsetting_interface = (user_de_name(NULL) == "cinnamon") ? "org.cinnamon.desktop.interface" + : (de_name == "mate") ? "org.mate.interface" + : "org.gnome.desktop.interface"; + +const std::string dconf_interface = (user_de_name(NULL) == "cinnamon") ? "/org/cinnamon/desktop/interface" + : (de_name == "mate") ? "/org/mate/interface" + : "/org/gnome/desktop/interface"; + +static std::string get_xsettings_xfce4(const std::string_view property, const std::string_view subproperty) +{ + static bool done = false; + static rapidxml::xml_document<> doc; + static std::string buffer; + + if (!done) + { + const std::string& path = configDir + "/xfce4/xfconf/xfce-perchannel-xml/xsettings.xml"; + std::ifstream f(path, std::ios::in); + if (!f.is_open()) + return MAGIC_LINE; + + buffer.assign(std::istreambuf_iterator<char>{ f }, std::istreambuf_iterator<char>()); + buffer.push_back('\0'); + + doc.parse<0>(&buffer[0]); + done = true; + } + + rapidxml::xml_node<>* node1 = doc.first_node("channel")->first_node("property"); + for (; node1 && std::string_view(node1->first_attribute("name")->value()) != property; + node1 = node1->next_sibling("property")) + ; + + rapidxml::xml_node<>* node2 = node1->first_node("property"); + for (; node2; node2 = node2->next_sibling()) + { + if (std::string_view(node2->first_attribute("name")->value()) == subproperty && node2->first_attribute("value")) + return node2->first_attribute("value")->value(); + } + + return MAGIC_LINE; +} + +static std::string get_auto_gtk_format(const std::string_view gtk2, const std::string_view gtk3, + const std::string_view gtk4) +{ + if ((gtk2 != MAGIC_LINE && gtk3 != MAGIC_LINE && gtk4 != MAGIC_LINE)) + { + if (gtk2 == gtk3 && gtk2 == gtk4) + return fmt::format("{} [GTK2/3/4]", gtk4); + else if (gtk2 == gtk3) + return fmt::format("{} [GTK2/3], {} [GTK4]", gtk2, gtk4); + else if (gtk4 == gtk3) + return fmt::format("{} [GTK2], {} [GTK3/4]", gtk2, gtk4); + else + return fmt::format("{} [GTK2], {} [GTK3], {} [GTK4]", gtk2, gtk3, gtk4); + } + + else if (gtk3 != MAGIC_LINE && gtk4 != MAGIC_LINE) + { + if (gtk3 == gtk4) + return fmt::format("{} [GTK3/4]", gtk4); + else + return fmt::format("{} [GTK3], {} [GTK4]", gtk3, gtk4); + } + + else if (gtk2 != MAGIC_LINE && gtk3 != MAGIC_LINE) + { + if (gtk2 == gtk3) + return fmt::format("{} [GTK2/3]", gtk3); + else + return fmt::format("{} [GTK2], {} [GTK3]", gtk2, gtk3); + } + + else if (gtk4 != MAGIC_LINE) + return fmt::format("{} [GTK4]", gtk4); + else if (gtk3 != MAGIC_LINE) + return fmt::format("{} [GTK3]", gtk3); + else if (gtk2 != MAGIC_LINE) + return fmt::format("{} [GTK2]", gtk2); + + return MAGIC_LINE; +} + +// +// +// 1. Cursor +// +static CursorInfo get_cursor_xresources() +{ + std::string cursor_name{ MAGIC_LINE }, cursor_size{ MAGIC_LINE }; + const std::string& path = expandVar("~/.Xresources"); + std::ifstream f(path, std::ios::in); + if (!f.is_open()) + return { cursor_name, cursor_size }; + + std::uint16_t iter_index = 0; + std::string line; + while (std::getline(f, line) && iter_index < 2) + { + if (hasStart(line, "Xcursor.theme:")) + { + getFileValue(iter_index, line, cursor_name, "Xcursor.theme:"_len); + strip(cursor_name); + } + + else if (hasStart(line, "Xcursor.size:")) + { + getFileValue(iter_index, line, cursor_size, "Xcursor.size:"_len); + strip(cursor_size); + } + } + + return { cursor_name, cursor_size }; +} + +static CursorInfo get_cursor_dconf() +{ + std::string cursor{ MAGIC_LINE }, cursor_size{ MAGIC_LINE }; +#if USE_DCONF + void* handle = LOAD_LIBRARY("libdconf.so"); + if (!handle) + return { MAGIC_LINE, MAGIC_LINE }; + + LOAD_LIB_SYMBOL(handle, DConfClient*, dconf_client_new, void); + LOAD_LIB_SYMBOL(handle, GVariant*, dconf_client_read, DConfClient*, const char*); + LOAD_LIB_SYMBOL(handle, const gchar*, g_variant_get_string, GVariant*, gsize*); + LOAD_LIB_SYMBOL(handle, gint32, g_variant_get_int32, GVariant*); + + debug("calling {}", __PRETTY_FUNCTION__); + DConfClient* client = dconf_client_new(); + GVariant* variant; + + variant = dconf_client_read(client, (dconf_interface + "cursor-theme").c_str()); + if (variant) + cursor = g_variant_get_string(variant, NULL); + + variant = dconf_client_read(client, (dconf_interface + "cursor-size").c_str()); + if (variant) + cursor_size = fmt::to_string(g_variant_get_int32(variant)); +#endif + return { cursor, cursor_size }; +} + +static CursorInfo get_cursor_gsettings() +{ + const CursorInfo& dconf = get_cursor_dconf(); + if (dconf != CursorInfo{ MAGIC_LINE, MAGIC_LINE }) + return dconf; + + std::string cursor; + read_exec({ "gsettings", "get", gsetting_interface.c_str(), "cursor-theme" }, cursor); + cursor.erase(std::remove(cursor.begin(), cursor.end(), '\''), cursor.end()); + + std::string cursor_size; + read_exec({ "gsettings", "get", gsetting_interface.c_str(), "cursor-size" }, cursor_size); + cursor_size.erase(std::remove(cursor_size.begin(), cursor_size.end(), '\''), cursor_size.end()); + + if (cursor.empty()) + cursor = MAGIC_LINE; + if (cursor_size.empty()) + cursor_size = MAGIC_LINE; + return { cursor, cursor_size }; +} + +static CursorInfo get_gtk_cursor_config(const std::string_view path) +{ + std::string cursor{ MAGIC_LINE }, cursor_size{ MAGIC_LINE }; + std::ifstream f(path.data(), std::ios::in); + if (!f.is_open()) + return { cursor, cursor_size }; + + std::string line; + std::uint16_t iter_index = 0; + while (std::getline(f, line) && iter_index < 2) + { + if (hasStart(line, "gtk-cursor-theme-name=")) + getFileValue(iter_index, line, cursor, "gtk-cursor-theme-name="_len); + + else if (hasStart(line, "gtk-cursor-theme-size=")) + getFileValue(iter_index, line, cursor_size, "gtk-cursor-theme-size="_len); + } + + return { cursor, cursor_size }; +} + +static CursorInfo get_cursor_from_gtk_configs(const std::uint8_t ver) +{ + const std::array<std::string, 6> paths = { fmt::format("{}/gtk-{}.0/settings.ini", configDir, ver), + fmt::format("{}/gtk-{}.0/gtkrc", configDir, ver), + fmt::format("{}/gtkrc-{}.0", configDir, ver), + fmt::format("{}/.gtkrc-{}.0", std::getenv("HOME"), ver), + fmt::format("{}/.gtkrc-{}.0-kde", std::getenv("HOME"), ver), + fmt::format("{}/.gtkrc-{}.0-kde4", std::getenv("HOME"), ver) }; + + for (const std::string& path : paths) + { + const CursorInfo& result = get_gtk_cursor_config(path); + if (result != CursorInfo{ MAGIC_LINE, MAGIC_LINE }) + return result; + } + return { MAGIC_LINE, MAGIC_LINE }; +} + +static CursorInfo get_de_cursor(const std::string_view de_name) +{ + switch (fnv1a16::hash(str_tolower(de_name.data()))) + { + case "xfce"_fnv1a16: + case "xfce4"_fnv1a16: + { + debug("getting info on xfce4"); + return { get_xsettings_xfce4("Gtk", "CursorThemeName"), get_xsettings_xfce4("Gtk", "CursorThemeSize") }; + } + } + return { MAGIC_LINE, MAGIC_LINE }; +} + +// +// +// 2. GTK theme +// +static ThemeInfo get_gtk_theme_config(const std::string_view path) +{ + std::string theme{ MAGIC_LINE }, icon_theme{ MAGIC_LINE }, font{ MAGIC_LINE }; + std::ifstream f(path.data(), std::ios::in); + if (!f.is_open()) + return { theme, icon_theme, font }; + + std::string line; + std::uint16_t iter_index = 0; + while (std::getline(f, line) && iter_index < 3) + { + if (hasStart(line, "gtk-theme-name=")) + getFileValue(iter_index, line, theme, "gtk-theme-name="_len); + + else if (hasStart(line, "gtk-icon-theme-name=")) + getFileValue(iter_index, line, icon_theme, "gtk-icon-theme-name="_len); + + else if (hasStart(line, "gtk-font-name=")) + getFileValue(iter_index, line, font, "gtk-font-name="_len); + } + + return { theme, icon_theme, font }; +} + +static ThemeInfo get_gtk_theme_dconf() +{ + std::string theme{ MAGIC_LINE }, icon_theme{ MAGIC_LINE }, font{ MAGIC_LINE }; +#if USE_DCONF + void* handle = LOAD_LIBRARY("libdconf.so"); + if (!handle) + return { theme, icon_theme, font }; + + LOAD_LIB_SYMBOL(handle, DConfClient*, dconf_client_new, void); + LOAD_LIB_SYMBOL(handle, GVariant*, dconf_client_read, DConfClient * client, const char*); + LOAD_LIB_SYMBOL(handle, const gchar*, g_variant_get_string, GVariant* value, gsize* lenght); + + debug("calling {}", __PRETTY_FUNCTION__); + DConfClient* client = dconf_client_new(); + GVariant* variant; + + variant = dconf_client_read(client, (dconf_interface + "gtk-theme").c_str()); + if (variant) + theme = g_variant_get_string(variant, NULL); + + variant = dconf_client_read(client, (dconf_interface + "icon-theme").c_str()); + if (variant) + icon_theme = g_variant_get_string(variant, NULL); + + variant = dconf_client_read(client, (dconf_interface + "font-name").c_str()); + if (variant) + font = g_variant_get_string(variant, NULL); + +#endif + return { theme, icon_theme, font }; +} + +static ThemeInfo get_gtk_theme_gsettings() +{ + const ThemeInfo& dconf = get_gtk_theme_dconf(); + if (dconf != ThemeInfo{ MAGIC_LINE, MAGIC_LINE, MAGIC_LINE }) + return dconf; + + std::string theme, icon_theme, font; + + read_exec({ "gsettings", "get", gsetting_interface.c_str(), "gtk-theme" }, theme); + theme.erase(std::remove(theme.begin(), theme.end(), '\''), theme.end()); + + read_exec({ "gsettings", "get", gsetting_interface.c_str(), "icon-theme" }, icon_theme); + icon_theme.erase(std::remove(icon_theme.begin(), icon_theme.end(), '\''), icon_theme.end()); + + read_exec({ "gsettings", "get", gsetting_interface.c_str(), "font-name" }, font); + font.erase(std::remove(font.begin(), font.end(), '\''), font.end()); + + if (theme.empty()) + theme = MAGIC_LINE; + if (icon_theme.empty()) + icon_theme = MAGIC_LINE; + if (font.empty()) + font = MAGIC_LINE; + return { theme, icon_theme, font }; +} + +static ThemeInfo get_gtk_theme_from_configs(const std::uint8_t ver) +{ + const std::array<std::string, 6> paths = { fmt::format("{}/gtk-{}.0/settings.ini", configDir, ver), + fmt::format("{}/gtk-{}.0/gtkrc", configDir, ver), + fmt::format("{}/gtkrc-{}.0", configDir, ver), + fmt::format("{}/.gtkrc-{}.0", std::getenv("HOME"), ver), + fmt::format("{}/.gtkrc-{}.0-kde", std::getenv("HOME"), ver), + fmt::format("{}/.gtkrc-{}.0-kde4", std::getenv("HOME"), ver) }; + + for (const auto& path : paths) + { + const ThemeInfo& result = get_gtk_theme_config(path); + if (result != ThemeInfo{ MAGIC_LINE, MAGIC_LINE, MAGIC_LINE }) + return result; + } + return get_gtk_theme_gsettings(); +} + +static ThemeInfo get_de_gtk_theme(const std::string_view de_name, const std::uint8_t ver) +{ + switch (fnv1a16::hash(str_tolower(de_name.data()))) + { + case "xfce"_fnv1a16: + case "xfce4"_fnv1a16: + { + debug("getting info on xfce4"); + return { get_xsettings_xfce4("Net", "ThemeName"), get_xsettings_xfce4("Net", "IconThemeName"), + get_xsettings_xfce4("Gtk", "FontName") }; + } + } + return get_gtk_theme_from_configs(ver); +} + +const std::string& wmde_name = + (de_name != MAGIC_LINE && de_name == wm_name) || de_name == MAGIC_LINE ? wm_name : de_name; + +MODFUNC(theme_gtk_name) +{ + const moduleArgs_t* moduleArg = callbackInfo->moduleArgs; + for (; moduleArg && moduleArg->name != "gtk"; moduleArg = moduleArg->next) + ; + if (!moduleArg) + die("GTK version not provided"); + int ver = std::stoi(moduleArg->value); + + const ThemeInfo& result = is_tty ? get_gtk_theme_from_configs(ver) : get_de_gtk_theme(wmde_name, ver); + + return result[THEME_NAME]; +} + +MODFUNC(theme_gtk_icon) +{ + const moduleArgs_t* moduleArg = callbackInfo->moduleArgs; + for (; moduleArg && moduleArg->name != "gtk"; moduleArg = moduleArg->next) + ; + if (!moduleArg) + die("GTK version not provided"); + int ver = std::stoi(moduleArg->value); + + const ThemeInfo& result = is_tty ? get_gtk_theme_from_configs(ver) : get_de_gtk_theme(wmde_name, ver); + + return result[THEME_ICON]; +} + +MODFUNC(theme_gtk_font) +{ + const moduleArgs_t* moduleArg = callbackInfo->moduleArgs; + for (; moduleArg && moduleArg->name != "gtk"; moduleArg = moduleArg->next) + ; + if (!moduleArg) + die("GTK version not provided"); + int ver = std::stoi(moduleArg->value); + + const ThemeInfo& result = is_tty ? get_gtk_theme_from_configs(ver) : get_de_gtk_theme(wmde_name, ver); + + return result[THEME_FONT]; +} + +MODFUNC(theme_gtk_all_name) +{ + const ThemeInfo& result_gtk2 = is_tty ? get_gtk_theme_from_configs(2) : get_de_gtk_theme(wmde_name, 2); + const ThemeInfo& result_gtk3 = is_tty ? get_gtk_theme_from_configs(3) : get_de_gtk_theme(wmde_name, 3); + const ThemeInfo& result_gtk4 = is_tty ? get_gtk_theme_from_configs(4) : get_de_gtk_theme(wmde_name, 4); + + return get_auto_gtk_format(result_gtk2[THEME_NAME], result_gtk3[THEME_NAME], result_gtk4[THEME_NAME]); +} + +MODFUNC(theme_gtk_all_icon) +{ + const ThemeInfo& result_gtk2 = is_tty ? get_gtk_theme_from_configs(2) : get_de_gtk_theme(wmde_name, 2); + const ThemeInfo& result_gtk3 = is_tty ? get_gtk_theme_from_configs(3) : get_de_gtk_theme(wmde_name, 3); + const ThemeInfo& result_gtk4 = is_tty ? get_gtk_theme_from_configs(4) : get_de_gtk_theme(wmde_name, 4); + + return get_auto_gtk_format(result_gtk2[THEME_ICON], result_gtk3[THEME_ICON], result_gtk4[THEME_ICON]); +} + +MODFUNC(theme_gtk_all_font) +{ + const ThemeInfo& result_gtk2 = is_tty ? get_gtk_theme_from_configs(2) : get_de_gtk_theme(wmde_name, 2); + const ThemeInfo& result_gtk3 = is_tty ? get_gtk_theme_from_configs(3) : get_de_gtk_theme(wmde_name, 3); + const ThemeInfo& result_gtk4 = is_tty ? get_gtk_theme_from_configs(4) : get_de_gtk_theme(wmde_name, 4); + + return get_auto_gtk_format(result_gtk2[THEME_FONT], result_gtk3[THEME_FONT], result_gtk4[THEME_FONT]); +} + +MODFUNC(theme_gsettings_name) +{ + const ThemeInfo& result = get_gtk_theme_gsettings(); + return result[THEME_NAME]; +} + +MODFUNC(theme_gsettings_icon) +{ + const ThemeInfo& result = get_gtk_theme_gsettings(); + return result[THEME_ICON]; +} + +MODFUNC(theme_gsettings_font) +{ + const ThemeInfo& result = get_gtk_theme_gsettings(); + return result[THEME_FONT]; +} + +MODFUNC(theme_gsettings_cursor_name) +{ + const CursorInfo& result = get_cursor_gsettings(); + return result[CURSOR_NAME]; +} + +MODFUNC(theme_gsettings_cursor_size) +{ + const CursorInfo& result = get_cursor_gsettings(); + return result[CURSOR_SIZE]; +} + +const std::array<std::function<CursorInfo()>, 6> funcs{ + std::function<CursorInfo()>{ []() { return get_de_cursor(wmde_name); } }, + std::function<CursorInfo()>{ []() { return get_cursor_from_gtk_configs(4); } }, + std::function<CursorInfo()>{ []() { return get_cursor_from_gtk_configs(3); } }, + std::function<CursorInfo()>{ []() { return get_cursor_from_gtk_configs(2); } }, + std::function<CursorInfo()>{ []() { return get_cursor_xresources(); } }, + std::function<CursorInfo()>{ []() { return get_cursor_gsettings(); } } +}; + +MODFUNC(theme_cursor_name) +{ + CursorInfo result; + + for (const auto& method : funcs) + { + result = method(); + if (result != CursorInfo{ MAGIC_LINE, MAGIC_LINE }) + break; + } + + if (result[CURSOR_NAME] == MAGIC_LINE) + return MAGIC_LINE; + + std::string& cursor_name = result[CURSOR_NAME]; + size_t pos = 0; + if ((pos = cursor_name.rfind("cursor")) != std::string::npos) + cursor_name.erase(pos); + if ((pos = cursor_name.rfind('_')) != std::string::npos) + cursor_name.erase(pos - 1); + + return cursor_name; +} + +MODFUNC(theme_cursor_size) +{ + CursorInfo result; + + for (const auto& method : funcs) + { + result = method(); + if (result != CursorInfo{ MAGIC_LINE, MAGIC_LINE }) + break; + } + + if (result[CURSOR_SIZE] == MAGIC_LINE) + return MAGIC_LINE; + + std::string& cursor_size = result[CURSOR_SIZE]; + size_t pos = 0; + if ((pos = cursor_size.rfind("cursor")) != std::string::npos) + cursor_size.erase(pos); + if ((pos = cursor_size.rfind('_')) != std::string::npos) + cursor_size.erase(pos - 1); + + return cursor_size; +} + +#endif // CF_LINUX diff --git a/src/core-modules/linux/user.cc b/src/core-modules/linux/user.cc new file mode 100644 index 00000000..3163efcc --- /dev/null +++ b/src/core-modules/linux/user.cc @@ -0,0 +1,464 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_LINUX || CF_MACOS + +#include <unistd.h> + +#include <fstream> + +#include "core-modules.hh" +#include "fmt/format.h" +#include "libcufetch/common.hh" +#include "switch_fnv1a.hpp" +#include "tiny-process-library/process.hpp" +#include "util.hpp" +#include "utils/dewm.hh" +#include "utils/term.hh" + +#if __has_include(<sys/socket.h>) && __has_include(<wayland-client.h>) +#include <sys/socket.h> +#include <wayland-client.h> +#endif + +using namespace TinyProcessLib; + +// clang-format off +static std::string get_term_name_env(bool get_default = false) +{ + if (getenv("SSH_TTY") != NULL) + return getenv("SSH_TTY"); + + if (getenv("KITTY_PID") != NULL || + getenv("KITTY_INSTALLATION_DIR") != NULL || + getenv("KITTY_PUBLIC_KEY") != NULL || + getenv("KITTY_WINDOW_ID") != NULL) + return "kitty"; + + if (getenv("ALACRITTY_SOCKET") != NULL || + getenv("ALACRITTY_LOG") != NULL || + getenv("ALACRITTY_WINDOW_ID") != NULL) + return "alacritty"; + + if (getenv("TERMUX_VERSION") != NULL || + getenv("TERMUX_MAIN_PACKAGE_FORMAT") != NULL) + return "com.termux"; + + if(getenv("KONSOLE_VERSION") != NULL) + return "konsole"; + + if (getenv("GNOME_TERMINAL_SCREEN") != NULL || + getenv("GNOME_TERMINAL_SERVICE") != NULL) + return "gnome-terminal"; + + if (get_default) + { + char *env = getenv("TERM_PROGRAM"); + if (env != NULL) + { + if (hasStart(env, "Apple")) + return "Apple Terminal"; + + return env; + } + + env = getenv("TERM"); + if (env != NULL) + return env; + } + + return UNKNOWN; +} + +MODFUNC(user_name) +{ return g_pwd->pw_name; } + +MODFUNC(user_shell_path) +{ return g_pwd->pw_shell; } + +// clang-format on +MODFUNC(user_shell_name) +{ + return user_shell_path(callbackInfo).substr(user_shell_path(callbackInfo).rfind('/') + 1); +} + +MODFUNC(user_shell_version) +{ + const std::string& shell_name = user_shell_name(callbackInfo); + std::string ret; + + if (shell_name == "nu") + Process("nu -c \"version | get version\"", "", [&](const char* bytes, size_t n) { ret.assign(bytes, n); }); + else + Process(fmt::format("{} -c 'echo \"${}_VERSION\"'", shell_name, str_toupper(shell_name.data())), "", + [&](const char* bytes, size_t n) { ret.assign(bytes, n); }); + + strip(ret); + return ret; +} + +std::string get_terminal_pid() +{ + // customfetch -> shell -> terminal + const pid_t ppid = getppid(); + std::ifstream ppid_f(fmt::format("/proc/{}/status", ppid), std::ios::in); + std::string line, term_pid{ "0" }; + while (std::getline(ppid_f, line)) + { + if (hasStart(line, "PPid:")) + { + term_pid = line.substr("PPid:"_len); + strip(term_pid); + break; + } + } + debug("term_pid = {}", term_pid); + + if (std::stoi(term_pid) < 1) + return MAGIC_LINE; + + return term_pid; +} + +std::string get_terminal_name() +{ + if (term_pid == MAGIC_LINE) + return get_term_name_env(true); + + std::ifstream f("/proc/" + term_pid + "/comm", std::ios::in); + std::string term_name; + if (f.is_open()) + std::getline(f, term_name); + else + term_name = get_term_name_env(true); + + return term_name; +} + +MODFUNC(user_term_name) +{ + if (is_tty) + return term_name; + + // st (suckless terminal) + if (term_name == "exe") + term_name = "st"; + // either gnome-console or "gnome-terminal-" + // I hope this is not super stupid + else if (hasStart(term_name, "gnome-console")) + term_name.erase("gnome-console"_len + 1); + else if (hasStart(term_name, "gnome-terminal")) + term_name.erase("gnome-terminal"_len + 1); + + const std::string& osname = os_name(callbackInfo); + // let's try to get the real terminal name + // on NixOS, instead of returning the -wrapped name. + // tested on gnome-console, kitty, st and alacritty + // hope now NixOS users will know the terminal they got, along the version if possible + if (osname.find("NixOS") != osname.npos || (hasEnding(term_name, "wrapped") && which("nix") != UNKNOWN)) + { + // /nix/store/sha256string-gnome-console-0.31.0/bin/.kgx-wrapped + char buf[PATH_MAX]; + std::string tmp_name = realpath(("/proc/" + term_pid + "/exe").c_str(), buf); + + size_t pos; + if ((pos = tmp_name.find('-')) != std::string::npos) + tmp_name.erase(0, pos + 1); // gnome-console-0.31.0/bin/.kgx-wrapped + + if ((pos = tmp_name.find('/')) != std::string::npos) + tmp_name.erase(pos); // gnome-console-0.31.0 + + if ((pos = tmp_name.rfind('-')) != std::string::npos) + tmp_name.erase(pos); // gnome-console EZ + + term_name = tmp_name; + } + + // sometimes may happen that the terminal name from /comm + // at the end has some letfover characters from /cmdline + if (!std::isalnum(term_name.back())) + { + size_t i = term_name.size(); + while (i > 0) + { + char ch = term_name[i - 1]; + // stop when we find an a num or alpha char + // example with "gnome-terminal-" + if (std::isalnum(static_cast<unsigned char>(ch))) + break; + term_name.erase(--i, 1); + } + } + + return term_name; +} + +MODFUNC(user_term_version) +{ + if (is_tty) + return ""; + + const std::string& term_name = user_term_name(callbackInfo); + if (term_name.empty()) + return UNKNOWN; + + bool remove_term_name = true; + std::string ret; + + switch (fnv1a16::hash(str_tolower(term_name.data()))) + { + case "st"_fnv1a16: + if (fast_detect_st_ver(ret)) + remove_term_name = false; + break; + + case "konsole"_fnv1a16: + if (fast_detect_konsole_ver(ret)) + remove_term_name = false; + break; + + case "xterm"_fnv1a16: get_term_version_exec(term_name, ret, true); break; + + default: get_term_version_exec(term_name, ret); + } + + debug("get_term_version ret = {}", ret); + + if (ret.empty()) + return UNKNOWN; + + if (hasStart(ret, "# GNOME")) + { + if (hasStart(ret, "# GNOME Console ")) + ret.erase(0, "# GNOME Console"_len); + else if (hasStart(ret, "# GNOME Terminal ")) + ret.erase(0, "# GNOME Terminal "_len); + debug("gnome ret = {}", ret); + remove_term_name = false; + } + // Xterm(388) + else if (term_name == "xterm") + { + ret.erase(0, term_name.length() + 1); // 388) + ret.pop_back(); // 388 + return ret; + } + + if (remove_term_name) + ret.erase(0, term_name.length() + 1); + + const size_t pos = ret.find(' '); + if (pos != std::string::npos) + ret.erase(pos); + + debug("get_term_version ret after = {}", ret); + return ret; +} + +std::string get_wm_name(std::string& wm_path_exec) +{ + std::string path, proc_name, wm_name; + const uid_t uid = getuid(); + +#if !CF_MACOS + for (auto const& dir_entry : std::filesystem::directory_iterator{ "/proc/" }) + { + if (!std::isdigit((dir_entry.path().string().at(6)))) // /proc/5 + continue; + + path = dir_entry.path() / "loginuid"; + std::ifstream f_uid(path, std::ios::binary); + std::string s_uid; + std::getline(f_uid, s_uid); + if (std::stoul(s_uid) != uid) + continue; + + path = dir_entry.path() / "cmdline"; + std::ifstream f_cmdline(path, std::ios::binary); + std::getline(f_cmdline, proc_name); + + size_t pos = 0; + if ((pos = proc_name.find('\0')) != std::string::npos) + proc_name.erase(pos); + + if ((pos = proc_name.rfind('/')) != std::string::npos) + proc_name.erase(0, pos + 1); + + debug("WM proc_name = {}", proc_name); + + if ((wm_name = prettify_wm_name(proc_name)) == MAGIC_LINE) + continue; + + char buf[PATH_MAX]; + if (realpath((dir_entry.path().string() + "/exe").c_str(), buf)) + wm_path_exec = buf; + else + wm_path_exec = UNKNOWN; + + break; + } +#endif + + debug("wm_name = {}", wm_name); + if (wm_name.empty()) + return MAGIC_LINE; + + return wm_name; +} + +static std::string get_wm_wayland_name(std::string& wm_path_exec) +{ +#if __has_include(<sys/socket.h>) && __has_include(<wayland-client.h>) + void* handle = LOAD_LIBRARY("libwayland-client.so"); + if (!handle) + return get_wm_name(wm_path_exec); + + LOAD_LIB_SYMBOL(handle, wl_display*, wl_display_connect, const char* name) + LOAD_LIB_SYMBOL(handle, void, wl_display_disconnect, wl_display* display) + LOAD_LIB_SYMBOL(handle, int, wl_display_get_fd, wl_display* display) + + std::string ret = MAGIC_LINE; + + struct wl_display* display = wl_display_connect(NULL); + + struct ucred ucred; + socklen_t len = sizeof(struct ucred); + if (getsockopt(wl_display_get_fd(display), SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) + return MAGIC_LINE; + + std::ifstream f(fmt::format("/proc/{}/comm", ucred.pid), std::ios::in); + f >> ret; + wl_display_disconnect(display); + + char buf[PATH_MAX]; + wm_path_exec = realpath(fmt::format("/proc/{}/exe", ucred.pid).c_str(), buf); + + UNLOAD_LIBRARY(handle) + + return prettify_wm_name(ret); +#else + return get_wm_name(wm_path_exec); +#endif +} + +MODFUNC(user_wm_name) +{ + if (!wm_name.empty()) + return wm_name; + if (is_tty) + return MAGIC_LINE; + + const char* env = std::getenv("WAYLAND_DISPLAY"); + if (env != nullptr && env[0] != '\0') + wm_name = get_wm_wayland_name(wm_path_exec); + else + wm_name = get_wm_name(wm_path_exec); + + if (de_name == wm_name) + de_name = MAGIC_LINE; + + return wm_name; +} + +MODFUNC(user_wm_version) +{ + if (is_tty) + return MAGIC_LINE; + user_wm_name(callbackInfo); // populate wm_path_exec if haven't already + std::string wm_version; + if (wm_name == "Xfwm4" && get_fast_xfwm4_version(wm_version, wm_path_exec)) + return wm_version; + + if (wm_name == "dwm") + read_exec({ wm_path_exec.c_str(), "-v" }, wm_version, true); + else + read_exec({ wm_path_exec.c_str(), "--version" }, wm_version); + + if (wm_name == "Xfwm4") + wm_version.erase(0, "\tThis is xfwm4 version "_len); // saying only "xfwm4 4.18.2 etc." no? + else + wm_version.erase(0, wm_name.length() + 1); + + const size_t pos = wm_version.find(' '); + if (pos != std::string::npos) + wm_version.erase(pos); + + return prettify_wm_name(wm_version); +} + +MODFUNC(user_de_name) +{ + if (is_tty || ((de_name != MAGIC_LINE && wm_name != MAGIC_LINE) && de_name == wm_name)) + { + de_name = MAGIC_LINE; + return de_name; + } + + de_name = parse_de_env(); + debug("get_de_name = {}", de_name); + if (hasStart(de_name, "X-")) + de_name.erase(0, 2); + + if (de_name == wm_name) + de_name = MAGIC_LINE; + + return de_name; +} + +MODFUNC(user_de_version) +{ + if (is_tty || de_name == UNKNOWN || de_name == MAGIC_LINE || de_name.empty()) + return UNKNOWN; + + switch (fnv1a16::hash(str_tolower(de_name))) + { + case "mate"_fnv1a16: return get_mate_version(); + case "cinnamon"_fnv1a16: return get_cinnamon_version(); + + case "kde"_fnv1a16: return get_kwin_version(); + + case "xfce"_fnv1a16: + case "xfce4"_fnv1a16: return get_xfce4_version(); + + case "gnome"_fnv1a16: + case "gnome-shell"_fnv1a16: + { + std::string ret; + read_exec({ "gnome-shell", "--version" }, ret); + ret.erase(0, ret.rfind(' ')); + return ret; + } + default: + { + std::string ret; + read_exec({ de_name.data(), "--version" }, ret); + ret.erase(0, ret.rfind(' ')); + return ret; + } + } +} + +#endif diff --git a/src/query/linux/utils/dewm.cpp b/src/core-modules/linux/utils/dewm.cc similarity index 92% rename from src/query/linux/utils/dewm.cpp rename to src/core-modules/linux/utils/dewm.cc index 2c3626d1..09eb2b93 100644 --- a/src/query/linux/utils/dewm.cpp +++ b/src/core-modules/linux/utils/dewm.cc @@ -1,25 +1,25 @@ /* * Copyright 2025 Toni500git - * + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ @@ -43,12 +43,12 @@ * SOFTWARE. */ - -#include "dewm.hpp" +#include "dewm.hh" #include <cstdlib> #include <fstream> +#include "libcufetch/common.hh" #include "rapidxml-1.13/rapidxml.hpp" #include "switch_fnv1a.hpp" #include "util.hpp" @@ -191,7 +191,7 @@ std::string get_mate_version() return ret; } - std::string buffer(std::istreambuf_iterator<char>{f}, std::istreambuf_iterator<char>{}); + std::string buffer(std::istreambuf_iterator<char>{ f }, std::istreambuf_iterator<char>{}); buffer.push_back('\0'); rapidxml::xml_document<> doc; @@ -277,10 +277,13 @@ std::string get_cinnamon_version() static std::string get_xfce4_version_lib() { - LOAD_LIBRARY("libxfce4util.so", return UNKNOWN) - LOAD_LIB_SYMBOL(const char*, xfce_version_string, void) + void* handle = LOAD_LIBRARY("libxfce4util.so"); + if (!handle) + return UNKNOWN; + + LOAD_LIB_SYMBOL(handle, const char*, xfce_version_string, void) const std::string& ret = xfce_version_string(); - UNLOAD_LIBRARY() + UNLOAD_LIBRARY(handle) return ret; } diff --git a/src/query/linux/utils/dewm.hpp b/src/core-modules/linux/utils/dewm.hh similarity index 72% rename from src/query/linux/utils/dewm.hpp rename to src/core-modules/linux/utils/dewm.hh index 002f6f17..3af8fa59 100644 --- a/src/query/linux/utils/dewm.hpp +++ b/src/core-modules/linux/utils/dewm.hh @@ -1,32 +1,31 @@ /* * Copyright 2025 Toni500git - * + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #ifndef _DEWM_HPP #define _DEWM_HPP - #include <string> #include <string_view> @@ -36,6 +35,6 @@ std::string get_mate_version(); std::string get_xfce4_version(); std::string get_cinnamon_version(); std::string get_kwin_version(); -bool get_fast_xfwm4_version(std::string& ret, const std::string& exec_path); +bool get_fast_xfwm4_version(std::string& ret, const std::string& exec_path); -#endif // _DEWM_HPP +#endif // _DEWM_HPP diff --git a/src/query/linux/utils/packages.cpp b/src/core-modules/linux/utils/packages.cc similarity index 87% rename from src/query/linux/utils/packages.cpp rename to src/core-modules/linux/utils/packages.cc index d894ce76..a323fe32 100644 --- a/src/query/linux/utils/packages.cpp +++ b/src/core-modules/linux/utils/packages.cc @@ -1,29 +1,29 @@ /* * Copyright 2025 Toni500git - * + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ -#include "packages.hpp" +#include "packages.hh" #include <algorithm> #include <filesystem> @@ -31,6 +31,7 @@ #include <string> #include "switch_fnv1a.hpp" +#include "util.hpp" static size_t get_num_count_dir(const std::string_view path) { @@ -59,15 +60,15 @@ static size_t get_num_string_file(const std::string_view path, const std::string return ret; } +#define ADD_PKGS_COUNT(pkgman) \ + if (pkgs_count.pkgman > 0) \ + ret += fmt::format("{} ({}), ", pkgs_count.pkgman, #pkgman); + std::string get_all_pkgs(const Config& config) { std::string ret; pkgs_managers_count_t pkgs_count; -#define ADD_PKGS_COUNT(pkgman) \ - if (pkgs_count.pkgman > 0) \ - ret += fmt::format("{} ({}), ", pkgs_count.pkgman, #pkgman); - for (const std::string& name : config.pkgs_managers) { switch (fnv1a16::hash(name)) @@ -105,3 +106,5 @@ std::string get_all_pkgs(const Config& config) return ret; } + +#undef ADD_PKGS_COUNT diff --git a/src/query/linux/utils/packages.hpp b/src/core-modules/linux/utils/packages.hh similarity index 76% rename from src/query/linux/utils/packages.hpp rename to src/core-modules/linux/utils/packages.hh index 84d162fd..8266f803 100644 --- a/src/query/linux/utils/packages.hpp +++ b/src/core-modules/linux/utils/packages.hh @@ -1,25 +1,25 @@ /* * Copyright 2025 Toni500git - * + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ diff --git a/src/query/linux/utils/term.cpp b/src/core-modules/linux/utils/term.cc similarity index 99% rename from src/query/linux/utils/term.cpp rename to src/core-modules/linux/utils/term.cc index 5b1e55b7..721b655a 100644 --- a/src/query/linux/utils/term.cpp +++ b/src/core-modules/linux/utils/term.cc @@ -18,7 +18,7 @@ * SOFTWARE. */ -#include "term.hpp" +#include "term.hh" #include <fstream> diff --git a/src/core-modules/linux/utils/term.hh b/src/core-modules/linux/utils/term.hh new file mode 100644 index 00000000..47397aee --- /dev/null +++ b/src/core-modules/linux/utils/term.hh @@ -0,0 +1,36 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _TERM_HPP +#define _TERM_HPP + +#include <string> + +void get_term_version_exec(const std::string_view term, std::string& ret, bool _short = false, bool _stderr = false); + +bool fast_detect_konsole_ver(std::string& ret); +bool fast_detect_st_ver(std::string& ret); + +#endif // _TERM_HPP diff --git a/src/core-modules/macos/battery.cc b/src/core-modules/macos/battery.cc new file mode 100644 index 00000000..36775e26 --- /dev/null +++ b/src/core-modules/macos/battery.cc @@ -0,0 +1,54 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_MACOS + +#include "core-modules.hh" +#include "libcufetch/common.hh" + +// clang-format off +MODFUNC(battery_modelname) +{ return MAGIC_LINE; } + +MODFUNC(battery_vendor) +{ return MAGIC_LINE; } + +MODFUNC(battery_capacity_level) +{ return MAGIC_LINE; } + +MODFUNC(battery_technology) +{ return MAGIC_LINE; } + +MODFUNC(battery_status) +{ return MAGIC_LINE; } + +MODFUNC(battery_perc) +{ return MAGIC_LINE; } + +double battery_temp() +{ return 0; } + +#endif diff --git a/src/query/macos/cpu.cpp b/src/core-modules/macos/cpu.cc similarity index 51% rename from src/query/macos/cpu.cpp rename to src/core-modules/macos/cpu.cc index 7bc72ef9..0bc25a5f 100644 --- a/src/query/macos/cpu.cpp +++ b/src/core-modules/macos/cpu.cc @@ -26,92 +26,72 @@ #include "platform.hpp" #if CF_MACOS -#include <ratio> -#include <string> #include <sys/sysctl.h> #include <unistd.h> -#include "query.hpp" -#include "util.hpp" +#include <cstdint> +#include <ratio> +#include <string> -using namespace Query; +#include "core-modules.hh" -static bool get_sysctl(int name[2], void *ret, size_t *oldlenp) +static bool get_sysctl(int name[2], void* ret, size_t* oldlenp) { return (sysctl(name, 2, ret, oldlenp, NULL, 0) == 0); } -static bool get_sysctl(const char *name, void *ret, size_t *oldlenp) +static bool get_sysctl(const char* name, void* ret, size_t* oldlenp) { return (sysctlbyname(name, ret, oldlenp, NULL, 0) == 0); } -static CPU::CPU_t get_cpu_infos() +float cpu_temp() { return 0; } + +MODFUNC(cpu_name) { - CPU::CPU_t ret; - debug("calling in CPU {}", __PRETTY_FUNCTION__); - char buf[1024]; + char buf[1024]; size_t len = sizeof(buf); - get_sysctl("machdep.cpu.brand_string", &buf, &len); - ret.name = buf; - - if (!(get_sysctl("machdep.cpu.vendor", &buf, &len)) && hasStart(ret.name, "Apple")) - ret.vendor = "Apple"; - else - ret.vendor = buf; + return buf; +} +MODFUNC(cpu_nproc) +{ + char buf[1024]; + size_t len = sizeof(buf); if (!get_sysctl("hw.logicalcpu_max", &buf, &len)) get_sysctl("hw.ncpu", &buf, &len); - ret.nproc = buf; - - uint64_t freq_cur = 0, freq_max = 0, freq_min; - size_t length = sizeof(freq_cur); - get_sysctl("hw.cpufrequency_max", &freq_max, &length); - get_sysctl("hw.cpufrequency_min", &freq_min, &length); - - if (!get_sysctl("hw.cpufrequency", &freq_cur, &length)) - get_sysctl((int[]){ CTL_HW, HW_CPU_FREQ }, &freq_cur, &length); - - ret.freq_cur = static_cast<double>(freq_cur) / std::giga().num; - ret.freq_max = static_cast<double>(freq_max) / std::giga().num; - ret.freq_min = static_cast<double>(freq_min) / std::giga().num; - return ret; + return buf; } -CPU::CPU() noexcept +MODFUNC(cpu_freq_cur) { - CHECK_INIT(m_bInit); + std::uint64_t freq = 0; + size_t length = sizeof(freq); + if (!get_sysctl("hw.cpufrequency", &freq, &length)) + get_sysctl((int[2]){ CTL_HW, HW_CPU_FREQ }, &freq, &length); - m_cpu_infos = get_cpu_infos(); + return fmt::to_string(static_cast<double>(freq) / std::giga().num); } -// clang-format off -std::string& CPU::name() noexcept -{ return m_cpu_infos.name; } - -std::string& CPU::nproc() noexcept -{ return m_cpu_infos.nproc; } - -std::string& CPU::vendor() noexcept -{ return m_cpu_infos.vendor; } - -std::string& CPU::modelname() noexcept -{ return m_cpu_infos.modelname; } - -double& CPU::temp() noexcept -{ return m_cpu_infos.temp; } +MODFUNC(cpu_freq_min) +{ + std::uint64_t freq = 0; + size_t length = sizeof(freq); + get_sysctl("hw.cpufrequency_min", &freq, &length); -double& CPU::freq_bios_limit() noexcept -{ return m_cpu_infos.freq_bios_limit; } + return fmt::to_string(static_cast<double>(freq) / std::giga().num); +} -double& CPU::freq_cur() noexcept -{ return m_cpu_infos.freq_cur; } +MODFUNC(cpu_freq_max) +{ + std::uint64_t freq = 0; + size_t length = sizeof(freq); + get_sysctl("hw.cpufrequency_max", &freq, &length); -double& CPU::freq_max() noexcept -{ return (m_cpu_infos.freq_max <= 0) ? m_cpu_infos.freq_max_cpuinfo : m_cpu_infos.freq_max; } + return fmt::to_string(static_cast<double>(freq) / std::giga().num); +} -double& CPU::freq_min() noexcept -{ return m_cpu_infos.freq_min; } +MODFUNC(cpu_freq_bios) { return MAGIC_LINE; } -#endif // CF_MACOS +#endif diff --git a/src/core-modules/macos/disk.cc b/src/core-modules/macos/disk.cc new file mode 100644 index 00000000..1a3efaf7 --- /dev/null +++ b/src/core-modules/macos/disk.cc @@ -0,0 +1,155 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_MACOS + +#include <sys/mount.h> +#include <sys/param.h> +#include <sys/types.h> + +#include <string> + +#include "core-modules.hh" +#include "fmt/format.h" +#include "libcufetch/common.hh" +#include "util.hpp" + +static std::string format_auto_query_string(std::string str, const struct statfs* fs) +{ + replace_str(str, "%1", fs->f_mntonname); + replace_str(str, "%2", fs->f_mntfromname); + replace_str(str, "%3", fs->f_fstypename); + + replace_str(str, "%4", fmt::format("$<disk({}).total>", fs->f_mntonname)); + replace_str(str, "%5", fmt::format("$<disk({}).free>", fs->f_mntonname)); + replace_str(str, "%6", fmt::format("$<disk({}).used>", fs->f_mntonname)); + replace_str(str, "%7", fmt::format("$<disk({}).used_perc>", fs->f_mntonname)); + replace_str(str, "%8", fmt::format("$<disk({}).free_perc>", fs->f_mntonname)); + + return str; +} + +static int get_disk_type(const int flags) +{ + int type = 0; + if (flags & MNT_DONTBROWSE) + type = DISK_VOLUME_TYPE_HIDDEN; + else if (flags & MNT_REMOVABLE || !(flags & MNT_LOCAL)) + type = DISK_VOLUME_TYPE_EXTERNAL; + else + type = DISK_VOLUME_TYPE_REGULAR; + + if (flags & MNT_RDONLY) + type |= DISK_VOLUME_TYPE_READ_ONLY; + + return type; +} + +static bool get_disk_info(const callbackInfo_t* callbackInfo, struct statfs* fs) +{ + if (callbackInfo->moduleArgs->name != "disk" || + (callbackInfo->moduleArgs->name == "disk" && callbackInfo->moduleArgs->value.empty())) + die("Module disk doesn't have an argmument to the path/device to query"); + + const std::string& path = callbackInfo->moduleArgs->value; + return (statfs(path.c_str(), fs) != 0); +} + +MODFUNC(disk_fsname) +{ + struct statfs fs; + if (!get_disk_info(callbackInfo, &fs)) + return MAGIC_LINE; + + return fs.f_fstypename; +} + +MODFUNC(disk_device) +{ + struct statfs fs; + if (!get_disk_info(callbackInfo, &fs)) + return MAGIC_LINE; + + return fs.f_mntfromname; +} + +MODFUNC(disk_mountdir) +{ + struct statfs fs; + if (!get_disk_info(callbackInfo, &fs)) + return MAGIC_LINE; + + return fs.f_mntonname; +} + +MODFUNC(auto_disk) +{ return MAGIC_LINE; } + +MODFUNC(disk_types) +{ + struct statfs fs; + if (!get_disk_info(callbackInfo, &fs)) + return MAGIC_LINE; + + const int types = get_disk_type(fs.f_flags); + std::string str; + if (types & DISK_VOLUME_TYPE_EXTERNAL) + str += "External, "; + if (types & DISK_VOLUME_TYPE_HIDDEN) + str += "Hidden, "; + if (types & DISK_VOLUME_TYPE_READ_ONLY) + str += "Read-only, "; + + if (!str.empty()) + str.erase(str.length() - 2); + + return str; +} + +double disk_total(const callbackInfo_t* callbackInfo) +{ + struct statfs fs; + if (!get_disk_info(callbackInfo, &fs)) + return 0; + + return static_cast<double>(fs.f_blocks * fs.f_bsize); +} + +double disk_free(const callbackInfo_t* callbackInfo) +{ + struct statfs fs; + if (!get_disk_info(callbackInfo, &fs)) + return 0; + + return static_cast<double>(fs.f_bfree * fs.f_bsize); +} + +double disk_used(const callbackInfo_t *callbackInfo) +{ + return disk_total(callbackInfo) - disk_free(callbackInfo); +} + +#endif diff --git a/src/query/macos/gpu.cpp b/src/core-modules/macos/gpu.cc similarity index 85% rename from src/query/macos/gpu.cpp rename to src/core-modules/macos/gpu.cc index c7d79c2e..54f40c18 100644 --- a/src/query/macos/gpu.cpp +++ b/src/core-modules/macos/gpu.cc @@ -26,19 +26,13 @@ #include "platform.hpp" #if CF_MACOS -#include "query.hpp" +#include "core-modules.hh" +#include "libcufetch/common.hh" -using namespace Query; +MODFUNC(gpu_name) +{ return MAGIC_LINE; } -GPU::GPU(const std::string& id, systemInfo_t& queried_gpus) -{ -} - -// clang-format off -std::string& GPU::name() noexcept -{ return m_gpu_infos.name; } - -std::string& GPU::vendor() noexcept -{ return m_gpu_infos.vendor; } +MODFUNC(gpu_vendor) +{ return "Intel"; } #endif diff --git a/src/core-modules/macos/os.cc b/src/core-modules/macos/os.cc new file mode 100644 index 00000000..1245027e --- /dev/null +++ b/src/core-modules/macos/os.cc @@ -0,0 +1,163 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_MACOS + +#include <sys/sysctl.h> + +#include <fstream> +#include <string> + +#include "core-modules.hh" +#include "libcufetch/common.hh" +#include "rapidxml-1.13/rapidxml.hpp" +#include "switch_fnv1a.hpp" +#include "util.hpp" + +std::ifstream f("/System/Library/CoreServices/SystemVersion.plist", std::ios::in); +std::string buffer(std::istreambuf_iterator<char>{ f }, std::istreambuf_iterator<char>{}); +rapidxml::xml_document<> doc; + +static std::string get_codename(const std::string_view os_version_id) +{ + std::string major; + std::string minor{ UNKNOWN }; + size_t pos1 = os_version_id.find('.'); + if (pos1 != os_version_id.npos) + { + major = os_version_id.substr(0, pos1); + size_t pos2 = os_version_id.find('.', pos1 + 1); + if (pos2 != os_version_id.npos) + minor = os_version_id.substr(pos1 + 1, pos2); + } + + switch (fnv1a16::hash(major)) + { + case "15"_fnv1a16: return "Sequoia"; + case "14"_fnv1a16: return "Sonoma"; + case "13"_fnv1a16: return "Ventura"; + case "12"_fnv1a16: return "Monterey"; + case "11"_fnv1a16: return "Big Sur"; + case "10"_fnv1a16: + { + switch (fnv1a16::hash(minor)) + { + case "16"_fnv1a16: return "Big Sur"; + case "15"_fnv1a16: return "Catalina"; + case "14"_fnv1a16: return "Mojave"; + case "13"_fnv1a16: return "High Sierra"; + case "12"_fnv1a16: return "Sierra"; + case "11"_fnv1a16: return "El Capitan"; + case "10"_fnv1a16: return "Yosemite"; + case "9"_fnv1a16: return "Mavericks"; + case "8"_fnv1a16: return "Mountain Lion"; + case "7"_fnv1a16: return "Lion"; + case "6"_fnv1a16: return "Snow Leopard"; + case "5"_fnv1a16: return "Leopard"; + case "4"_fnv1a16: return "Tiger"; + case "3"_fnv1a16: return "Panther"; + case "2"_fnv1a16: return "Jaguar"; + case "1"_fnv1a16: return "Puma"; + case "0"_fnv1a16: return "Cheetah"; + } + } + } + + return UNKNOWN; +} + +static void assert_doc() +{ + if (!doc.first_node("plist")) + { + buffer.push_back('\0'); + doc.parse<0>(&buffer[0]); + } +} + +static std::string get_plist_value(const std::string_view name) +{ + assert_doc(); + rapidxml::xml_node<>* root_node = doc.first_node("plist")->first_node("dict")->first_node("key"); + for (; root_node; root_node = root_node->next_sibling()) + { + const std::string_view key = root_node->value(); // <key>ProductName</key> + root_node = root_node->next_sibling(); + const std::string_view value = root_node->value(); // <string>macOS</string> + if (key == name) + return value.data(); + } + return UNKNOWN; +} + +MODFUNC(os_pretty_name) +{ + const std::string& codename = os_version_codename(nullptr); + if (codename != UNKNOWN) + return os_name(nullptr) + " " + os_version_id(nullptr) + " (" + codename + ")"; + return os_name(nullptr) + " " + os_version_id(nullptr); +} + +unsigned long os_uptime() +{ + struct timeval boot_time; + size_t size = sizeof(boot_time); + int name[] = { CTL_KERN, KERN_BOOTTIME }; + if (sysctl(name, 2, &boot_time, &size, NULL, 0) != 0) + die(_("failed to get uptime")); + + return time(NULL) - boot_time.tv_sec; +} + +// clang-format off +MODFUNC(os_name) +{ return get_plist_value("ProductName"); } + +MODFUNC(os_name_id) +{ return "macos"; } + +MODFUNC(os_version_id) +{ return get_plist_value("ProductUserVisibleVersion"); } + +MODFUNC(os_version_codename) +{ return get_codename(os_version_id(nullptr)); } + +MODFUNC(os_kernel_name) +{ return g_uname_infos.sysname; } + +MODFUNC(os_kernel_version) +{ return g_uname_infos.release; } + +MODFUNC(os_hostname) +{ return g_uname_infos.nodename; } + +MODFUNC(os_initsys_name) +{ return MAGIC_LINE; } + +MODFUNC(os_initsys_version) +{ return UNKNOWN; } + +#endif diff --git a/src/core-modules/macos/ram.cc b/src/core-modules/macos/ram.cc new file mode 100644 index 00000000..24e8c4c7 --- /dev/null +++ b/src/core-modules/macos/ram.cc @@ -0,0 +1,104 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_MACOS + +#include <mach/mach.h> +#include <sys/sysctl.h> +#include <unistd.h> + +#include <cstdint> + +#include "core-modules.hh" + +xsw_usage xsw; +size_t xsw_length{ sizeof(xsw) }; + +static bool populate_xsw() +{ + int name[2] = { CTL_VM, VM_SWAPUSAGE }; + return (sysctl(name, 2, &xsw, &xsw_length, NULL, 0) != 0); +} + +double ram_total() +{ + int name[2] = { CTL_HW, HW_MEMSIZE }; + uint64_t amount = 0; + size_t length = sizeof(amount); + if (sysctl(name, 2, &amount, &length, NULL, 0) != 0) + return 0.0; + return static_cast<double>(amount); +} + +double ram_used() +{ + int name[2] = { CTL_HW, HW_PAGESIZE }; + uint64_t amount = 0, page_size = 0; + size_t length = sizeof(amount); + mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; + vm_statistics64_data_t vmstat; + + sysctl(name, 2, &page_size, &length, NULL, 0); + if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)(&vmstat), &count) != KERN_SUCCESS) + return 0.0; + + return static_cast<double>( + ((uint64_t) + + vmstat.active_count + + vmstat.inactive_count + + vmstat.speculative_count + + vmstat.wire_count + + vmstat.compressor_page_count + - vmstat.purgeable_count + - vmstat.external_page_count + ) * page_size); +} + +double ram_free() +{ return ram_total() - ram_used(); } + +double swap_used() +{ + if (!populate_xsw()) + return 0.0; + return xsw.xsu_used; +} + +double swap_free() +{ + if (!populate_xsw()) + return 0.0; + return xsw.xsu_avail; +} + +double swap_total() +{ + if (!populate_xsw()) + return 0.0; + return xsw.xsu_total; +} + +#endif diff --git a/src/core-modules/macos/system.cc b/src/core-modules/macos/system.cc new file mode 100644 index 00000000..c6f074c9 --- /dev/null +++ b/src/core-modules/macos/system.cc @@ -0,0 +1,287 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_MACOS + +#include <sys/sysctl.h> + +#include <regex> // -100000000 runtime/compile-time +#include <string> +#include <string_view> + +#include "core-modules.hh" +#include "libcufetch/common.hh" +#include "switch_fnv1a.hpp" +#include "util.hpp" + +static bool get_sysctl(const char* name, void* ret, size_t* oldlenp) +{ + return (sysctlbyname(name, ret, oldlenp, NULL, 0) == 0); +} + +// https://github.com/fastfetch-cli/fastfetch/blob/a734f18fd56014f5c0b9fb388727b778e2bc05d1/src/detection/host/host_mac.c#L4 +const std::string get_host_from_family(const std::string_view host_family) +{ + // Macbook Pro: https://support.apple.com/en-us/HT201300 + // Macbook Air: https://support.apple.com/en-us/HT201862 + // Mac mini: https://support.apple.com/en-us/HT201894 + // iMac: https://support.apple.com/en-us/HT201634 + // Mac Pro: https://support.apple.com/en-us/HT202888 + // Mac Studio: https://support.apple.com/en-us/HT213073 + + if (hasStart(host_family, "MacBookPro")) + { + const std::string_view version = host_family.substr("MacBookPro"_len); + switch (fnv1a16::hash(version.data())) + { + case "18,3"_fnv1a16: + case "18,4"_fnv1a16: return "MacBook Pro (14-inch, 2021)"; + case "18,1"_fnv1a16: + case "18,2"_fnv1a16: return "MacBook Pro (16-inch, 2021)"; + case "17,1"_fnv1a16: return "MacBook Pro (13-inch, M1, 2020)"; + case "16,3"_fnv1a16: return "MacBook Pro (13-inch, 2020, Two Thunderbolt 3 ports)"; + case "16,2"_fnv1a16: return "MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)"; + case "16,4"_fnv1a16: + case "16,1"_fnv1a16: return "MacBook Pro (16-inch, 2019)"; + case "15,4"_fnv1a16: return "MacBook Pro (13-inch, 2019, Two Thunderbolt 3 ports)"; + case "15,3"_fnv1a16: return "MacBook Pro (15-inch, 2019)"; + case "15,2"_fnv1a16: return "MacBook Pro (13-inch, 2018/2019, Four Thunderbolt 3 ports)"; + case "15,1"_fnv1a16: return "MacBook Pro (15-inch, 2018/2019)"; + case "14,3"_fnv1a16: return "MacBook Pro (15-inch, 2017)"; + case "14,2"_fnv1a16: return "MacBook Pro (13-inch, 2017, Four Thunderbolt 3 ports)"; + case "14,1"_fnv1a16: return "MacBook Pro (13-inch, 2017, Two Thunderbolt 3 ports)"; + case "13,3"_fnv1a16: return "MacBook Pro (15-inch, 2016)"; + case "13,2"_fnv1a16: return "MacBook Pro (13-inch, 2016, Four Thunderbolt 3 ports)"; + case "13,1"_fnv1a16: return "MacBook Pro (13-inch, 2016, Two Thunderbolt 3 ports)"; + case "12,1"_fnv1a16: return "MacBook Pro (Retina, 13-inch, Early 2015)"; + case "11,4"_fnv1a16: + case "11,5"_fnv1a16: return "MacBook Pro (Retina, 15-inch, Mid 2015)"; + case "11,2"_fnv1a16: + case "11,3"_fnv1a16: return "MacBook Pro (Retina, 15-inch, Late 2013/Mid 2014)"; + case "11,1"_fnv1a16: return "MacBook Pro (Retina, 13-inch, Late 2013/Mid 2014)"; + case "10,2"_fnv1a16: return "MacBook Pro (Retina, 13-inch, Late 2012/Early 2013)"; + case "10,1"_fnv1a16: return "MacBook Pro (Retina, 15-inch, Mid 2012/Early 2013)"; + case "9,2"_fnv1a16: return "MacBook Pro (13-inch, Mid 2012)"; + case "9,1"_fnv1a16: return "MacBook Pro (15-inch, Mid 2012)"; + case "8,3"_fnv1a16: return "MacBook Pro (17-inch, 2011)"; + case "8,2"_fnv1a16: return "MacBook Pro (15-inch, 2011)"; + case "8,1"_fnv1a16: return "MacBook Pro (13-inch, 2011)"; + case "7,1"_fnv1a16: return "MacBook Pro (13-inch, Mid 2010)"; + case "6,2"_fnv1a16: return "MacBook Pro (15-inch, Mid 2010)"; + case "6,1"_fnv1a16: return "MacBook Pro (17-inch, Mid 2010)"; + case "5,5"_fnv1a16: return "MacBook Pro (13-inch, Mid 2009)"; + case "5,3"_fnv1a16: return "MacBook Pro (15-inch, Mid 2009)"; + case "5,2"_fnv1a16: return "MacBook Pro (17-inch, Mid/Early 2009)"; + case "5,1"_fnv1a16: return "MacBook Pro (15-inch, Late 2008)"; + case "4,1"_fnv1a16: return "MacBook Pro (17/15-inch, Early 2008)"; + } + } + else if (hasStart(host_family, "MacBookAir")) + { + const std::string_view version = host_family.substr("MacBookAir"_len); + switch (fnv1a16::hash(version.data())) + { + case "10,1"_fnv1a16: return "MacBook Air (M1, 2020)"; + case "9,1"_fnv1a16: return "MacBook Air (Retina, 13-inch, 2020)"; + case "8,2"_fnv1a16: return "MacBook Air (Retina, 13-inch, 2019)"; + case "8,1"_fnv1a16: return "MacBook Air (Retina, 13-inch, 2018)"; + case "7,2"_fnv1a16: return "MacBook Air (13-inch, Early 2015/2017)"; + case "7,1"_fnv1a16: return "MacBook Air (11-inch, Early 2015)"; + case "6,2"_fnv1a16: return "MacBook Air (13-inch, Mid 2013/Early 2014)"; + case "6,1"_fnv1a16: return "MacBook Air (11-inch, Mid 2013/Early 2014)"; + case "5,2"_fnv1a16: return "MacBook Air (13-inch, Mid 2012)"; + case "5,1"_fnv1a16: return "MacBook Air (11-inch, Mid 2012)"; + case "4,2"_fnv1a16: return "MacBook Air (13-inch, Mid 2011)"; + case "4,1"_fnv1a16: return "MacBook Air (11-inch, Mid 2011)"; + case "3,2"_fnv1a16: return "MacBook Air (13-inch, Late 2010)"; + case "3,1"_fnv1a16: return "MacBook Air (11-inch, Late 2010)"; + case "2,1"_fnv1a16: return "MacBook Air (Mid 2009)"; + } + } + else if (hasStart(host_family, "Macmini")) + { + const std::string_view version = host_family.substr("Macmini"_len); + switch (fnv1a16::hash(version.data())) + { + case "9,1"_fnv1a16: return "Mac mini (M1, 2020)"; + case "8,1"_fnv1a16: return "Mac mini (2018)"; + case "7,1"_fnv1a16: return "Mac mini (Mid 2014)"; + case "6,1"_fnv1a16: + case "6,2"_fnv1a16: return "Mac mini (Late 2012)"; + case "5,1"_fnv1a16: + case "5,2"_fnv1a16: return "Mac mini (Mid 2011)"; + case "4,1"_fnv1a16: return "Mac mini (Mid 2010)"; + case "3,1"_fnv1a16: return "Mac mini (Early/Late 2009)"; + } + } + else if (hasStart(host_family, "MacBook")) + { + const std::string_view version = host_family.substr("MacBook"_len); + switch (fnv1a16::hash(version.data())) + { + case "10,1"_fnv1a16: return "MacBook (Retina, 12-inch, 2017)"; + case "9,1"_fnv1a16: return "MacBook (Retina, 12-inch, Early 2016)"; + case "8,1"_fnv1a16: return "MacBook (Retina, 12-inch, Early 2015)"; + case "7,1"_fnv1a16: return "MacBook (13-inch, Mid 2010)"; + case "6,1"_fnv1a16: return "MacBook (13-inch, Late 2009)"; + case "5,2"_fnv1a16: return "MacBook (13-inch, Early/Mid 2009)"; + } + } + else if (hasStart(host_family, "MacPro")) + { + const std::string_view version = host_family.substr("MacPro"_len); + switch (fnv1a16::hash(version.data())) + { + case "7,1"_fnv1a16: return "Mac Pro (2019)"; + case "6,1"_fnv1a16: return "Mac Pro (Late 2013)"; + case "5,1"_fnv1a16: return "Mac Pro (Mid 2010 - Mid 2012)"; + case "4,1"_fnv1a16: return "Mac Pro (Early 2009)"; + } + } + else if (hasStart(host_family, "Mac")) + { + const std::string_view version = host_family.substr("Mac"_len); + switch (fnv1a16::hash(version.data())) + { + case "16,13"_fnv1a16: return "MacBook Air (15-inch, M4, 2025)"; + case "16,12"_fnv1a16: return "MacBook Air (13-inch, M4, 2025)"; + case "16,11"_fnv1a16: + case "16,10"_fnv1a16: return "Mac Mini (2024)"; + case "16,9"_fnv1a16: return "Mac Studio (M4 Max, 2025)"; + case "16,3"_fnv1a16: return "iMac (24-inch, 2024, Four Thunderbolt / USB 4 ports)"; + case "16,2"_fnv1a16: return "iMac (24-inch, 2024, Two Thunderbolt / USB 4 ports)"; + case "16,1"_fnv1a16: return "MacBook Pro (14-inch, 2024, Three Thunderbolt 4 ports)"; + case "16,6"_fnv1a16: + case "16,8"_fnv1a16: return "MacBook Pro (14-inch, 2024, Three Thunderbolt 5 ports)"; + case "16,7"_fnv1a16: + case "16,5"_fnv1a16: return "MacBook Pro (16-inch, 2024, Three Thunderbolt 5 ports)"; + case "15,14"_fnv1a16: return "Mac Studio (M3 Ultra, 2025)"; + case "15,13"_fnv1a16: return "MacBook Air (15-inch, M3, 2024)"; + case "15,12"_fnv1a16: return "MacBook Air (13-inch, M3, 2024)"; + case "15,3"_fnv1a16: return "MacBook Pro (14-inch, Nov 2023, Two Thunderbolt / USB 4 ports)"; + case "15,4"_fnv1a16: return "iMac (24-inch, 2023, Two Thunderbolt / USB 4 ports)"; + case "15,5"_fnv1a16: return "iMac (24-inch, 2023, Two Thunderbolt / USB 4 ports, Two USB 3 ports)"; + case "15,6"_fnv1a16: + case "15,8"_fnv1a16: + case "15,10"_fnv1a16: return "MacBook Pro (14-inch, Nov 2023, Three Thunderbolt 4 ports)"; + case "15,7"_fnv1a16: + case "15,9"_fnv1a16: + case "15,11"_fnv1a16: return "MacBook Pro (16-inch, Nov 2023, Three Thunderbolt 4 ports)"; + case "14,15"_fnv1a16: return "MacBook Air (15-inch, M2, 2023)"; + case "14,14"_fnv1a16: return "Mac Studio (M2 Ultra, 2023, Two Thunderbolt 4 front ports)"; + case "14,13"_fnv1a16: return "Mac Studio (M2 Max, 2023, Two USB-C front ports)"; + case "14,8"_fnv1a16: return "Mac Pro (2023)"; + case "14,6"_fnv1a16: + case "14,10"_fnv1a16: return "MacBook Pro (16-inch, 2023)"; + case "14,5"_fnv1a16: + case "14,9"_fnv1a16: return "MacBook Pro (14-inch, 2023)"; + case "14,3"_fnv1a16: return "Mac mini (M2, 2023, Two Thunderbolt 4 ports)"; + case "14,12"_fnv1a16: return "Mac mini (M2, 2023, Four Thunderbolt 4 ports)"; + case "14,7"_fnv1a16: return "MacBook Pro (13-inch, M2, 2022)"; + case "14,2"_fnv1a16: return "MacBook Air (M2, 2022)"; + case "13,1"_fnv1a16: return "Mac Studio (M1 Max, 2022, Two USB-C front ports)"; + case "13,2"_fnv1a16: return "Mac Studio (M1 Ultra, 2022, Two Thunderbolt 4 front ports)"; + } + } + else if (hasStart(host_family, "iMac")) + { + const std::string_view version = host_family.substr("iMac"_len); + switch (fnv1a16::hash(version.data())) + { + case "21,1"_fnv1a16: return "iMac (24-inch, M1, 2021, Two Thunderbolt / USB 4 ports, Two USB 3 ports)"; + case "21,2"_fnv1a16: return "iMac (24-inch, M1, 2021, Two Thunderbolt / USB 4 ports)"; + case "20,1"_fnv1a16: + case "20,2"_fnv1a16: return "iMac (Retina 5K, 27-inch, 2020)"; + case "19,1"_fnv1a16: return "iMac (Retina 5K, 27-inch, 2019)"; + case "19,2"_fnv1a16: return "iMac (Retina 4K, 21.5-inch, 2019)"; + case "Pro1,1"_fnv1a16: return "iMac Pro (2017)"; + case "18,3"_fnv1a16: return "iMac (Retina 5K, 27-inch, 2017)"; + case "18,2"_fnv1a16: return "iMac (Retina 4K, 21.5-inch, 2017)"; + case "18,1"_fnv1a16: return "iMac (21.5-inch, 2017)"; + case "17,1"_fnv1a16: return "iMac (Retina 5K, 27-inch, Late 2015)"; + case "16,2"_fnv1a16: return "iMac (Retina 4K, 21.5-inch, Late 2015)"; + case "16,1"_fnv1a16: return "iMac (21.5-inch, Late 2015)"; + case "15,1"_fnv1a16: return "iMac (Retina 5K, 27-inch, Late 2014 - Mid 2015)"; + case "14,4"_fnv1a16: return "iMac (21.5-inch, Mid 2014)"; + case "14,2"_fnv1a16: return "iMac (27-inch, Late 2013)"; + case "14,1"_fnv1a16: return "iMac (21.5-inch, Late 2013)"; + case "13,2"_fnv1a16: return "iMac (27-inch, Late 2012)"; + case "13,1"_fnv1a16: return "iMac (21.5-inch, Late 2012)"; + case "12,2"_fnv1a16: return "iMac (27-inch, Mid 2011)"; + case "12,1"_fnv1a16: return "iMac (21.5-inch, Mid 2011)"; + case "11,3"_fnv1a16: return "iMac (27-inch, Mid 2010)"; + case "11,2"_fnv1a16: return "iMac (21.5-inch, Mid 2010)"; + case "10,1"_fnv1a16: return "iMac (27/21.5-inch, Late 2009)"; + case "9,1"_fnv1a16: return "iMac (24/20-inch, Early 2009)"; + } + } + + return UNKNOWN; +} + +MODFUNC(host_vendor) +{ return "Apple"; } + +MODFUNC(host_name) +{ + char buf[4096]; + size_t len = sizeof(buf); + if (!get_sysctl("hw.model", buf, &len)) + return MAGIC_LINE; + + const std::string host = get_host_from_family(buf); + return host.substr(0, host.find('(') - 1); +} + +MODFUNC(host_version) +{ + char buf[4096]; + size_t len = sizeof(buf); + if (!get_sysctl("hw.model", buf, &len)) + return UNKNOWN; + + const std::string& host = get_host_from_family(buf); + std::regex year_regex(R"(\b(19|20)\d{2}\b)"); // Matches 1900–2099 + std::smatch match; + if (std::regex_search(host, match, year_regex)) + return match.str(0); + return UNKNOWN; +} + +MODFUNC(host) +{ + char buf[4096]; + size_t len = sizeof(buf); + if (!get_sysctl("hw.model", buf, &len)) + return MAGIC_LINE; + + return get_host_from_family(buf); +} + +MODFUNC(arch) +{ return g_uname_infos.machine; } + +#endif diff --git a/src/core-modules/macos/theme.cc b/src/core-modules/macos/theme.cc new file mode 100644 index 00000000..1c137f8a --- /dev/null +++ b/src/core-modules/macos/theme.cc @@ -0,0 +1,46 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_MACOS + +#include "core-modules.hh" +#include "libcufetch/common.hh" + +MODFUNC(theme_gtk_name) { return MAGIC_LINE; } +MODFUNC(theme_gtk_icon) { return MAGIC_LINE; } +MODFUNC(theme_gtk_font) { return MAGIC_LINE; } +MODFUNC(theme_cursor_name) { return MAGIC_LINE; } +MODFUNC(theme_cursor_size) { return MAGIC_LINE; } +MODFUNC(theme_gtk_all_name) { return MAGIC_LINE; } +MODFUNC(theme_gtk_all_icon) { return MAGIC_LINE; } +MODFUNC(theme_gtk_all_font) { return MAGIC_LINE; } +MODFUNC(theme_gsettings_name) { return MAGIC_LINE; } +MODFUNC(theme_gsettings_icon) { return MAGIC_LINE; } +MODFUNC(theme_gsettings_font) { return MAGIC_LINE; } +MODFUNC(theme_gsettings_cursor_name) { return MAGIC_LINE; } +MODFUNC(theme_gsettings_cursor_size) { return MAGIC_LINE; } + +#endif // CF_MACOS diff --git a/src/display.cpp b/src/display.cpp index c187ddc8..7f6ab76a 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -1,37 +1,40 @@ /* * Copyright 2025 Toni500git - * + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ // Implementation of the system behind displaying/rendering the information #include "display.hpp" -#include "platform.hpp" + #include <cstddef> #include <cstdio> +#include <string> + +#include "platform.hpp" #ifndef GUI_APP -# define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION #endif #if CF_MACOS @@ -51,19 +54,19 @@ #include <iostream> #include <vector> -#include "config.hpp" +#include "core-modules.hh" #include "fmt/core.h" #include "fmt/format.h" #include "parse.hpp" #include "platform.hpp" -#include "query.hpp" #include "stb_image.h" +#include "tiny-process-library/process.hpp" #include "utf8/checked.h" #include "util.hpp" std::string Display::detect_distro(const Config& config) { - //debug("/etc/os-release = \n{}", read_shell_exec("cat /etc/os-release 2> /dev/null")); + // debug("/etc/os-release = \n{}", read_shell_exec("cat /etc/os-release 2> /dev/null")); if (!config.args_custom_distro.empty()) { @@ -71,14 +74,14 @@ std::string Display::detect_distro(const Config& config) } else { - Query::System system; - std::string format; - - format = fmt::format("{}/ascii/{}.txt", config.data_dir, str_tolower(system.os_id())); + std::string format; + format = fmt::format("{}/ascii/{}.txt", config.data_dir, str_tolower(os_name_id(nullptr))); + debug("checking path in {}: {}", __func__, format); if (std::filesystem::exists(format)) return format; - format = fmt::format("{}/ascii/{}.txt", config.data_dir, str_tolower(system.os_name())); + format = fmt::format("{}/ascii/{}.txt", config.data_dir, str_tolower(os_name(nullptr))); + debug("checking path in {}: {}", __func__, format); if (std::filesystem::exists(format)) return format; } @@ -92,42 +95,42 @@ std::string Display::detect_distro(const Config& config) #endif } -static std::vector<std::string> render_with_image(systemInfo_t& systemInfo, std::vector<std::string>& layout, - const Config& config, const colors_t& colors, - const std::string_view path, const std::uint16_t font_width, - const std::uint16_t font_height) +static std::vector<std::string> render_with_image(const moduleMap_t& modulesInfo, std::vector<std::string>& layout, + const Config& config, const std::filesystem::path& path, + const std::uint16_t font_width, const std::uint16_t font_height) { int image_width, image_height, channels; // load the image and get its width and height - unsigned char* img = stbi_load(path.data(), &image_width, &image_height, &channels, 0); + unsigned char* img = stbi_load(path.c_str(), &image_width, &image_height, &channels, 0); if (!img) - die(_("Unable to load image '{}'"), path); + die(_("Unable to load image '{}'"), path.string()); stbi_image_free(img); if (Display::ascii_logo_fd != -1) { - remove(path.data()); + remove(path.c_str()); close(Display::ascii_logo_fd); } - std::string _; + std::string _; std::vector<std::string> tmp_layout; - parse_args_t parse_args{ systemInfo, _, layout, tmp_layout, config, colors, true }; + parse_args_t parse_args{ modulesInfo, _, layout, tmp_layout, config, true }; for (size_t i = 0; i < layout.size(); ++i) { - layout[i] = parse(layout[i], parse_args); + layout[i] = parse(layout[i], parse_args); parse_args.no_more_reset = false; - #if !GUI_APP +#if !GUI_APP if (!config.args_disable_colors) layout[i].insert(0, NOCOLOR); - #endif +#endif if (!tmp_layout.empty()) { - layout.erase(layout.begin()+i); - layout.insert(layout.begin()+i, tmp_layout.begin(), tmp_layout.end()); + layout.erase(layout.begin() + i); + layout.insert(layout.begin() + i, tmp_layout.begin(), tmp_layout.end()); + i += tmp_layout.size() - 1; tmp_layout.clear(); } } @@ -142,11 +145,11 @@ static std::vector<std::string> render_with_image(systemInfo_t& systemInfo, std: const size_t height = image_height / font_height; if (config.args_image_backend == "kitty") - taur_exec({ "kitty", "+kitten", "icat", + TinyProcessLib::Process({ "kitty", "+kitten", "icat", "--align", (config.logo_position == "top" ? "center" : config.logo_position), - "--place", fmt::format("{}x{}@0x0", width, height), path }); + "--place", fmt::format("{}x{}@0x0", width, height), path.string() }); else if (config.args_image_backend == "viu") - taur_exec({ "viu", "-t", "-w", fmt::to_string(width), "-h", fmt::to_string(height), path }); + TinyProcessLib::Process({ "viu", "-t", "-w", fmt::to_string(width), "-h", fmt::to_string(height), path.string() }); else die(_("The image backend '{}' isn't supported, only 'kitty' and 'viu'.\n" "Please currently use the GUI mode for rendering the image/gif (use -h for more details)"), @@ -160,8 +163,10 @@ static std::vector<std::string> render_with_image(systemInfo_t& systemInfo, std: return layout; } - const unsigned int offset = (config.offset.back() == '%') ? Display::calc_perc(std::stof(config.offset.substr(0,config.offset.size()-1)), width, 0) : - std::stoi(config.offset); + const unsigned int offset = + (config.offset.back() == '%') + ? Display::calc_perc(std::stof(config.offset.substr(0, config.offset.size() - 1)), width, 0) + : std::stoi(config.offset); for (std::string& str : layout) for (size_t _ = 0; _ < width + offset; ++_) @@ -175,8 +180,8 @@ static std::vector<std::string> render_with_image(systemInfo_t& systemInfo, std: static bool get_pos(int& y, int& x) { std::array<char, 32> buf; - int ret, i, pow; - char ch; + int ret, i, pow; + char ch; y = 0; x = 0; @@ -217,21 +222,20 @@ static bool get_pos(int& y, int& x) return true; } -std::vector<std::string> Display::render(const Config& config, const colors_t& colors, const bool already_analyzed_file, - const std::string_view path) +std::vector<std::string> Display::render(const Config& config, const bool already_analyzed_file, + const std::filesystem::path& path, const moduleMap_t& moduleMap) { - systemInfo_t systemInfo{}; std::vector<std::string> asciiArt{}, layout{ config.args_layout.empty() ? config.layout : config.args_layout }; - debug("Display::render path = {}", path); + debug("Display::render path = {}", path.string()); bool isImage = false; std::ifstream file; if (!config.args_disable_source) { - file.open(path.data(), std::ios::binary); + file.open(path.string(), std::ios::binary); if (!file.is_open()) - die(_("Could not open logo file '{}'"), path); + die(_("Could not open logo file '{}'"), path.string()); // first check if the file is an image // without even using the same library that "file" uses @@ -263,10 +267,10 @@ std::vector<std::string> Display::render(const Config& config, const colors_t& c debug("{} distro_path = {}", __FUNCTION__, distro_path); // this is just for parse() to auto add the distro colors - std::ifstream distro_file(distro_path); - std::string line, _; + std::ifstream distro_file(distro_path); + std::string line, _; std::vector<std::string> tmp_layout; - parse_args_t parse_args{ systemInfo, _, layout, tmp_layout, config, colors, false }; + parse_args_t parse_args{ moduleMap, _, layout, tmp_layout, config, false }; while (std::getline(distro_file, line)) { @@ -276,8 +280,8 @@ std::vector<std::string> Display::render(const Config& config, const colors_t& c } std::vector<size_t> pureAsciiArtLens; - int maxLineLength = -1; - + size_t maxLineLength = 0; + struct winsize win; ioctl(STDOUT_FILENO, TIOCGWINSZ, &win); @@ -294,12 +298,12 @@ std::vector<std::string> Display::render(const Config& config, const colors_t& c get_pos(y, x); fmt::print("\033[{};{}H", y, x); - return render_with_image(systemInfo, layout, config, colors, path, font_width, font_height); + return render_with_image(moduleMap, layout, config, path, font_width, font_height); } if (Display::ascii_logo_fd != -1) { - remove(path.data()); + remove(path.c_str()); close(Display::ascii_logo_fd); } @@ -317,27 +321,27 @@ std::vector<std::string> Display::render(const Config& config, const colors_t& c std::string line; while (std::getline(file, line)) { - std::string pureOutput; + std::string pureOutput; std::vector<std::string> tmp_layout; - parse_args_t parse_args{ systemInfo, pureOutput, layout, tmp_layout, config, colors, false }; + parse_args_t parse_args{ moduleMap, pureOutput, layout, tmp_layout, config, false }; std::string asciiArt_s = parse(line, parse_args); parse_args.no_more_reset = false; - #if !GUI_APP +#if !GUI_APP if (!config.args_disable_colors) asciiArt_s += NOCOLOR; - #else +#else // check parse.cpp const size_t pos = asciiArt_s.rfind("$ </"); if (pos != std::string::npos) asciiArt_s.replace(pos, 2, "$"); - #endif +#endif asciiArt.push_back(asciiArt_s); const size_t pureOutputLen = utf8::distance(pureOutput.begin(), pureOutput.end()); - if (static_cast<int>(pureOutputLen) > maxLineLength) - maxLineLength = static_cast<int>(pureOutputLen); + if (pureOutputLen > maxLineLength) + maxLineLength = pureOutputLen; pureAsciiArtLens.push_back(pureOutputLen); debug("asciiArt_s = {}", asciiArt_s); @@ -346,23 +350,23 @@ std::vector<std::string> Display::render(const Config& config, const colors_t& c if (config.args_print_logo_only) return asciiArt; - std::string _; + std::string _; std::vector<std::string> tmp_layout; - parse_args_t parse_args{ systemInfo, _, layout, tmp_layout, config, colors, true }; + parse_args_t parse_args{ moduleMap, _, layout, tmp_layout, config, true }; for (size_t i = 0; i < layout.size(); ++i) { - layout[i] = parse(layout[i], parse_args); + layout[i] = parse(layout[i], parse_args); parse_args.no_more_reset = false; - #if !GUI_APP +#if !GUI_APP if (!config.args_disable_colors) layout[i].insert(0, NOCOLOR); - #endif +#endif if (!tmp_layout.empty()) { - layout.erase(layout.begin()+i); - layout.insert(layout.begin()+i, tmp_layout.begin(), tmp_layout.end()); - i += tmp_layout.size()-1; + layout.erase(layout.begin() + i); + layout.insert(layout.begin() + i, tmp_layout.begin(), tmp_layout.end()); + i += tmp_layout.size() - 1; tmp_layout.clear(); } } @@ -375,13 +379,15 @@ std::vector<std::string> Display::render(const Config& config, const colors_t& c if (config.logo_position == "top" || config.logo_position == "bottom") { if (!asciiArt.empty()) - layout.insert(config.logo_position == "top" ? layout.begin() : layout.end(), - asciiArt.begin(), asciiArt.end()); + layout.insert(config.logo_position == "top" ? layout.begin() : layout.end(), asciiArt.begin(), + asciiArt.end()); return layout; } - const unsigned int offset = (config.offset.back() == '%') ? calc_perc(std::stof(config.offset.substr(0,config.offset.size()-1)), win.ws_col, maxLineLength) : - std::stoi(config.offset); + const unsigned int offset = + (config.offset.back() == '%') + ? calc_perc(std::stof(config.offset.substr(0, config.offset.size() - 1)), win.ws_col, maxLineLength) + : std::stoi(config.offset); size_t i; for (i = 0; i < layout.size(); i++) @@ -389,7 +395,7 @@ std::vector<std::string> Display::render(const Config& config, const colors_t& c size_t origin = config.logo_padding_left; // The user-specified offset to be put before the logo - for (size_t j = 0; j < config.logo_padding_left; j++) + for (size_t j = 0; j < config.logo_padding_left; ++j) layout.at(i).insert(0, " "); if (i < asciiArt.size()) @@ -406,20 +412,15 @@ std::vector<std::string> Display::render(const Config& config, const colors_t& c for (size_t j = 0; j < spaces; j++) layout.at(i).insert(origin, " "); - #if !GUI_APP +#if !GUI_APP if (!config.args_disable_colors) layout.at(i) += NOCOLOR; - #endif +#endif } - for (; i < asciiArt.size(); i++) + for (; i < asciiArt.size(); ++i) { - std::string line; - line.reserve(config.logo_padding_left + asciiArt.at(i).length()); - - for (size_t j = 0; j < config.logo_padding_left; j++) - line += " "; - + std::string line(config.logo_padding_left, ' '); line += asciiArt.at(i); layout.push_back(line); diff --git a/src/gui.cpp b/src/gui.cpp index 3851e11d..7a8e3ce5 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -1,25 +1,25 @@ /* * Copyright 2025 Toni500git - * + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ @@ -34,11 +34,13 @@ #include <filesystem> #include <fstream> -#include "config.hpp" #include "display.hpp" #include "fmt/ranges.h" -#include "parse.hpp" -#include "query.hpp" +#include "gdkmm/pixbufanimation.h" +#include "glibmm/main.h" +#include "glibmm/refptr.h" +#include "gtkmm/cssprovider.h" +#include "gtkmm/enums.h" #include "stb_image.h" #include "util.hpp" @@ -62,10 +64,10 @@ using namespace GUI; }*/ // Display::render but only for images on GUI -static std::vector<std::string> render_with_image(const Config& config, const colors_t& colors) +static std::vector<std::string> render_with_image(const Config& config) { std::string path{ Display::detect_distro(config) }; - systemInfo_t systemInfo{}; + moduleMap_t modulesInfo{}; std::vector<std::string> layout{ config.args_layout.empty() ? config.layout : config.args_layout }; int image_width, image_height, channels; @@ -89,10 +91,10 @@ static std::vector<std::string> render_with_image(const Config& config, const co } // this is just for parse() to auto add the distro colors - std::ifstream file(path, std::ios::binary); - std::string line, _; + std::ifstream file(path, std::ios::binary); + std::string line, _; std::vector<std::string> tmp_layout; - parse_args_t parse_args{ systemInfo, _, layout, tmp_layout, config, colors, false }; + parse_args_t parse_args{ modulesInfo, _, layout, tmp_layout, config, false }; while (std::getline(file, line)) { parse(line, parse_args); @@ -102,13 +104,14 @@ static std::vector<std::string> render_with_image(const Config& config, const co parse_args.parsingLayout = true; for (size_t i = 0; i < layout.size(); ++i) { - layout[i] = parse(layout[i], parse_args); + layout[i] = parse(layout[i], parse_args); parse_args.no_more_reset = false; if (!tmp_layout.empty()) { - layout.erase(layout.begin()+i); - layout.insert(layout.begin()+i, tmp_layout.begin(), tmp_layout.end()); + layout.erase(layout.begin() + i); + layout.insert(layout.begin() + i, tmp_layout.begin(), tmp_layout.end()); + i += tmp_layout.size() - 1; tmp_layout.clear(); } } @@ -118,8 +121,10 @@ static std::vector<std::string> render_with_image(const Config& config, const co [](const std::string_view str) { return str.find(MAGIC_LINE) != std::string::npos; }), layout.end()); - const unsigned int offset = (config.offset.back() == '%') ? Display::calc_perc(std::stof(config.offset.substr(0,config.offset.size()-1)), image_width, 0) : - std::stoi(config.offset); + const unsigned int offset = + (config.offset.back() == '%') + ? Display::calc_perc(std::stof(config.offset.substr(0, config.offset.size() - 1)), image_width, 0) + : std::stoi(config.offset); for (size_t i = 0; i < layout.size(); i++) for (size_t _ = 0; _ < offset; _++) // I use _ because we don't need it @@ -133,20 +138,17 @@ bool Window::set_layout_markup() if (m_isImage) { if (!m_config.args_print_logo_only) - m_label.set_markup(fmt::format("{}", fmt::join(render_with_image(m_config, m_colors), "\n"))); + m_label.set_markup(fmt::format("{}", fmt::join(render_with_image(m_config), "\n"))); } else { - m_label.set_markup(fmt::format("{}", fmt::join(Display::render(m_config, m_colors, true, m_path), "\n"))); + m_label.set_markup(fmt::format("{}", fmt::join(Display::render(m_config, true, m_path, m_moduleMap), "\n"))); } return true; } -Window::Window(const Config& config, const colors_t& colors, const std::string_view path) : - m_config(config), - m_colors(colors), - m_path(path), - m_isImage(false) +Window::Window(const Config& config, const std::filesystem::path& path, const moduleMap_t& moduleMap) + : m_config(config), m_path(path), m_moduleMap(moduleMap), m_isImage(false) { set_title("customfetch - Higly customizable and fast neofetch like program"); set_default_size(1000, 600); @@ -154,7 +156,7 @@ Window::Window(const Config& config, const colors_t& colors, const std::string_v set_icon_from_file(ICONPREFIX "/customfetch/Thumbnail.png"); debug("Window::Window analyzing file"); - std::ifstream f(path.data()); + std::ifstream f(path.c_str()); std::array<unsigned char, 32> buffer; f.read(reinterpret_cast<char*>(&buffer.at(0)), buffer.size()); if (is_file_image(buffer.data())) @@ -163,7 +165,7 @@ Window::Window(const Config& config, const colors_t& colors, const std::string_v // useImage can be either a gif or an image if (m_isImage && !config.args_disable_source) { - const auto& img = Gdk::PixbufAnimation::create_from_file(path.data()); + const auto& img = Gdk::PixbufAnimation::create_from_file(path.c_str()); m_img = Gtk::manage(new Gtk::Image(img)); m_img->set(img); m_img->set_alignment(Gtk::ALIGN_CENTER); @@ -203,7 +205,7 @@ Window::Window(const Config& config, const colors_t& colors, const std::string_v die(_("Path to gtk css file '{}' doesn't exist"), config.gui_css_file); Glib::RefPtr<Gtk::CssProvider> css_provider = Gtk::CssProvider::create(); - Glib::RefPtr<Gdk::Screen> screen = Gdk::Screen::get_default(); + Glib::RefPtr<Gdk::Screen> screen = Gdk::Screen::get_default(); try { css_provider->load_from_path(config.gui_css_file); @@ -213,12 +215,12 @@ Window::Window(const Config& config, const colors_t& colors, const std::string_v die(_("Failed to load CSS: {}"), ex.gobj()->message); } - m_overlay.get_style_context()->add_provider_for_screen(screen, css_provider, GTK_STYLE_PROVIDER_PRIORITY_USER); + m_box.get_style_context()->add_provider_for_screen(screen, css_provider, GTK_STYLE_PROVIDER_PRIORITY_USER); } if (Display::ascii_logo_fd != -1) { - ::remove(path.data()); + ::remove(path.c_str()); ::close(Display::ascii_logo_fd); } diff --git a/src/libs/fmt/CMakeLists.txt b/src/libs/fmt/CMakeLists.txt index ab0077d4..1606e886 100644 --- a/src/libs/fmt/CMakeLists.txt +++ b/src/libs/fmt/CMakeLists.txt @@ -5,9 +5,6 @@ if (${CMAKE_VERSION} VERSION_LESS 3.12) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) endif () -# https://github.com/Toni500github/customfetch/issues/3 -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--build-id=none") - # Determine if fmt is built as a subproject (using add_subdirectory) # or if it is the master project. if (NOT DEFINED FMT_MASTER_PROJECT) diff --git a/src/libs/fmt/Makefile b/src/libs/fmt/Makefile index c9198c0f..1d291a58 100644 --- a/src/libs/fmt/Makefile +++ b/src/libs/fmt/Makefile @@ -3,19 +3,18 @@ # cmd: cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF CXX ?= g++ +CXXSTD ?= c++20 # CXX_DEFINES = -DFMT_LIB_EXPORT -Dfmt_EXPORTS -CXX_INCLUDES = -I../../../include -CXX_FLAGS = -O3 -DNDEBUG -fvisibility=hidden -fvisibility-inlines-hidden +CXXFLAGS = -I../../../include/libs -O3 -std=$(CXXSTD) -DNDEBUG $(LTO_FLAGS) -fvisibility=hidden -fvisibility-inlines-hidden -fPIC all: fmt fmt: format.cc os.cc - $(CXX) $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -c format.cc -o format.cc.o - $(CXX) $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -c os.cc -o os.cc.o + $(CXX) $(CXX_DEFINES) $(CXXFLAGS) -c format.cc -o format.cc.o + $(CXX) $(CXX_DEFINES) $(CXXFLAGS) -c os.cc -o os.cc.o ar qc libfmt.a os.cc.o format.cc.o ranlib libfmt.a mv -f libfmt.a ../../../$(BUILDDIR)/libfmt.a - # $(CXX) -fPIC -O3 -DNDEBUG -shared -Wl,-soname,libfmt.so.10 -o libfmt.so format.cc.o os.cc.o clean: rm -f *.o *.so *.a ../../../$(BUILDDIR)/fmt/.*a diff --git a/src/libs/getopt_port/Makefile b/src/libs/getopt_port/Makefile new file mode 100644 index 00000000..d369ff97 --- /dev/null +++ b/src/libs/getopt_port/Makefile @@ -0,0 +1,14 @@ +CC ?= cc +SRC = getopt.c +TARGET = getopt.o +CFLAGS = -I../../../include/libs $(LTO_FLAGS) -fvisibility=hidden -fPIC + +all: $(TARGET) + +$(TARGET): + $(CC) $(SRC) $(CFLAGS) -c -o ../../../$(BUILDDIR)/$@ + +clean: + rm -rf ../../../$(BUILDDIR)/getopt_port/$(TARGET) + +.PHONY: $(TARGET) clean all diff --git a/src/libs/getopt_port/getopt.c b/src/libs/getopt_port/getopt.c new file mode 100644 index 00000000..6bac813e --- /dev/null +++ b/src/libs/getopt_port/getopt.c @@ -0,0 +1,239 @@ +/******************************************************************************* + * Copyright (c) 2012-2023, Kim Grasman <kim.grasman@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Kim Grasman nor the + * names of contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL KIM GRASMAN BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ******************************************************************************/ + +#include "getopt.h" + +#include <stddef.h> +#include <string.h> + +char* optarg; +int optopt; +/* The variable optind [...] shall be initialized to 1 by the system. */ +int optind = 1; +int opterr; + +static char* optcursor = NULL; + +/* Implemented based on [1] and [2] for optional arguments. + optopt is handled FreeBSD-style, per [3]. + Other GNU and FreeBSD extensions are purely accidental. + +[1] http://pubs.opengroup.org/onlinepubs/000095399/functions/getopt.html +[2] http://www.kernel.org/doc/man-pages/online/pages/man3/getopt.3.html +[3] http://www.freebsd.org/cgi/man.cgi?query=getopt&sektion=3&manpath=FreeBSD+9.0-RELEASE +*/ +int getopt(int argc, char* const argv[], const char* optstring) { + int optchar = -1; + const char* optdecl = NULL; + + optarg = NULL; + opterr = 0; + optopt = 0; + + /* Unspecified, but we need it to avoid overrunning the argv bounds. */ + if (optind >= argc) + goto no_more_optchars; + + /* If, when getopt() is called argv[optind] is a null pointer, getopt() + shall return -1 without changing optind. */ + if (argv[optind] == NULL) + goto no_more_optchars; + + /* If, when getopt() is called *argv[optind] is not the character '-', + getopt() shall return -1 without changing optind. */ + if (*argv[optind] != '-') + goto no_more_optchars; + + /* If, when getopt() is called argv[optind] points to the string "-", + getopt() shall return -1 without changing optind. */ + if (strcmp(argv[optind], "-") == 0) + goto no_more_optchars; + + /* If, when getopt() is called argv[optind] points to the string "--", + getopt() shall return -1 after incrementing optind. */ + if (strcmp(argv[optind], "--") == 0) { + ++optind; + goto no_more_optchars; + } + + if (optcursor == NULL || *optcursor == '\0') + optcursor = argv[optind] + 1; + + optchar = *optcursor; + + /* FreeBSD: The variable optopt saves the last known option character + returned by getopt(). */ + optopt = optchar; + + /* The getopt() function shall return the next option character (if one is + found) from argv that matches a character in optstring, if there is + one that matches. */ + optdecl = strchr(optstring, optchar); + if (optdecl) { + /* [I]f a character is followed by a colon, the option takes an + argument. */ + if (optdecl[1] == ':') { + optarg = ++optcursor; + if (*optarg == '\0') { + /* GNU extension: Two colons mean an option takes an + optional arg; if there is text in the current argv-element + (i.e., in the same word as the option name itself, for example, + "-oarg"), then it is returned in optarg, otherwise optarg is set + to zero. */ + if (optdecl[2] != ':') { + /* If the option was the last character in the string pointed to by + an element of argv, then optarg shall contain the next element + of argv, and optind shall be incremented by 2. If the resulting + value of optind is greater than argc, this indicates a missing + option-argument, and getopt() shall return an error indication. + + Otherwise, optarg shall point to the string following the + option character in that element of argv, and optind shall be + incremented by 1. + */ + if (++optind < argc) { + optarg = argv[optind]; + } else { + /* If it detects a missing option-argument, it shall return the + colon character ( ':' ) if the first character of optstring + was a colon, or a question-mark character ( '?' ) otherwise. + */ + optarg = NULL; + optchar = (optstring[0] == ':') ? ':' : '?'; + } + } else { + optarg = NULL; + } + } + + optcursor = NULL; + } + } else { + /* If getopt() encounters an option character that is not contained in + optstring, it shall return the question-mark ( '?' ) character. */ + optchar = '?'; + } + + if (optcursor == NULL || *++optcursor == '\0') + ++optind; + + return optchar; + +no_more_optchars: + optcursor = NULL; + return -1; +} + +/* Implementation based on [1]. + +[1] http://www.kernel.org/doc/man-pages/online/pages/man3/getopt.3.html +*/ +int getopt_long(int argc, char* const argv[], const char* optstring, + const struct option* longopts, int* longindex) { + const struct option* o = longopts; + const struct option* match = NULL; + int num_matches = 0; + size_t argument_name_length = 0; + size_t option_length = 0; + const char* current_argument = NULL; + int retval = -1; + + optarg = NULL; + optopt = 0; + + if (optind >= argc) + return -1; + + if (strlen(argv[optind]) < 3 || strncmp(argv[optind], "--", 2) != 0) + return getopt(argc, argv, optstring); + + /* It's an option; starts with -- and is longer than two chars. */ + current_argument = argv[optind] + 2; + argument_name_length = strcspn(current_argument, "="); + for (; o->name; ++o) { + /* Check for exact match first. */ + option_length = strlen(o->name); + if (option_length == argument_name_length && + strncmp(o->name, current_argument, option_length) == 0) { + match = o; + num_matches = 1; + break; + } + + /* If not exact, count the number of abbreviated matches. */ + if (strncmp(o->name, current_argument, argument_name_length) == 0) { + match = o; + ++num_matches; + } + } + + if (num_matches == 1) { + /* If longindex is not NULL, it points to a variable which is set to the + index of the long option relative to longopts. */ + if (longindex) + *longindex = (int)(match - longopts); + + /* If flag is NULL, then getopt_long() shall return val. + Otherwise, getopt_long() returns 0, and flag shall point to a variable + which shall be set to val if the option is found, but left unchanged if + the option is not found. */ + if (match->flag) + *(match->flag) = match->val; + + retval = match->flag ? 0 : match->val; + + if (match->has_arg != no_argument) { + optarg = strchr(argv[optind], '='); + if (optarg != NULL) + ++optarg; + + if (match->has_arg == required_argument) { + /* Only scan the next argv for required arguments. Behavior is not + specified, but has been observed with Ubuntu and Mac OSX. */ + if (optarg == NULL && ++optind < argc) { + optarg = argv[optind]; + } + + if (optarg == NULL) + retval = ':'; + } + } else if (strchr(argv[optind], '=')) { + /* An argument was provided to a non-argument option. + I haven't seen this specified explicitly, but both GNU and BSD-based + implementations show this behavior. + */ + retval = '?'; + } + } else { + /* Unknown option or ambiguous match. */ + retval = '?'; + } + + ++optind; + return retval; +} diff --git a/src/libs/json/Makefile b/src/libs/json/Makefile index b18adcf3..4adb0147 100644 --- a/src/libs/json/Makefile +++ b/src/libs/json/Makefile @@ -1,12 +1,13 @@ CXX ?= g++ SRC = json.cpp TARGET = json.o -CXXFLAGS = -I../../../include -std=c++20 +CXXSTD ?= c++20 +CXXFLAGS = -I../../../include/libs $(LTO_FLAGS) -std=$(CXXSTD) -fPIC all: $(TARGET) $(TARGET): - ${CXX} $(SRC) $(CXXFLAGS) -c -o ../../../$(BUILDDIR)/$@ + $(CXX) $(SRC) $(CXXFLAGS) -c -o ../../../$(BUILDDIR)/$@ clean: rm -rf ../../$(BUILDDIR)/json/$(TARGET) diff --git a/src/libs/tiny-process-library/LICENSE b/src/libs/tiny-process-library/LICENSE new file mode 100644 index 00000000..955aa68b --- /dev/null +++ b/src/libs/tiny-process-library/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015-2020 Ole Christian Eidheim + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/src/libs/tiny-process-library/Makefile b/src/libs/tiny-process-library/Makefile new file mode 100644 index 00000000..688e74fb --- /dev/null +++ b/src/libs/tiny-process-library/Makefile @@ -0,0 +1,36 @@ +CXX ?= g++ +CXXSTD ?= c++20 +CXXFLAGS = -I../../../include/libs/tiny-process-library -std=$(CXXSTD) -O3 -DNDEBUG $(LTO_FLAGS) -fvisibility=default -fvisibility-inlines-hidden -fPIC + +UNAME_S := $(shell uname -s) + +ifeq ($(UNAME_S),Windows) + CXXFLAGS += -D_CRT_SECURE_NO_WARNINGS + SRC += process_win.cpp + # Check if we're in MSYS environment + ifneq (,$(findstring MSYS,$(shell uname -o))) + CXXFLAGS += -DMSYS_PROCESS_USE_SH + endif + LIBNAME = tiny-process-library.lib +else + CXXFLAGS += -Wall -Wextra + SRC += process_unix.cpp + LIBNAME = libtiny-process-library.a +endif + +SRC += process.cpp +OBJ = $(SRC:.cpp=.o) + +all: $(LIBNAME) + +$(LIBNAME): $(OBJ) + ar rcs $@ $^ + mv -f $(LIBNAME) ../../../$(BUILDDIR)/$(LIBNAME) + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +clean: + rm -f $(OBJ) $(LIBNAME) + +.PHONY: all clean diff --git a/src/libs/tiny-process-library/process.cpp b/src/libs/tiny-process-library/process.cpp new file mode 100644 index 00000000..a68e5181 --- /dev/null +++ b/src/libs/tiny-process-library/process.cpp @@ -0,0 +1,55 @@ +#include "process.hpp" + +namespace TinyProcessLib { + +Process::Process(const std::vector<string_type> &arguments, const string_type &path, + std::function<void(const char *bytes, size_t n)> read_stdout, + std::function<void(const char *bytes, size_t n)> read_stderr, + bool open_stdin, const Config &config) noexcept + : closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) { + open(arguments, path); + async_read(); +} + +Process::Process(const string_type &command, const string_type &path, + std::function<void(const char *bytes, size_t n)> read_stdout, + std::function<void(const char *bytes, size_t n)> read_stderr, + bool open_stdin, const Config &config) noexcept + : closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) { + open(command, path); + async_read(); +} + +Process::Process(const std::vector<string_type> &arguments, const string_type &path, + const environment_type &environment, + std::function<void(const char *bytes, size_t n)> read_stdout, + std::function<void(const char *bytes, size_t n)> read_stderr, + bool open_stdin, const Config &config) noexcept + : closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) { + open(arguments, path, &environment); + async_read(); +} + +Process::Process(const string_type &command, const string_type &path, + const environment_type &environment, + std::function<void(const char *bytes, size_t n)> read_stdout, + std::function<void(const char *bytes, size_t n)> read_stderr, + bool open_stdin, const Config &config) noexcept + : closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) { + open(command, path, &environment); + async_read(); +} + +Process::~Process() noexcept { + close_fds(); +} + +Process::id_type Process::get_id() const noexcept { + return data.id; +} + +bool Process::write(const std::string &str) { + return write(str.c_str(), str.size()); +} + +} // namespace TinyProcessLib diff --git a/src/libs/tiny-process-library/process.hpp b/src/libs/tiny-process-library/process.hpp new file mode 120000 index 00000000..f3906d31 --- /dev/null +++ b/src/libs/tiny-process-library/process.hpp @@ -0,0 +1 @@ +../../../include/libs/tiny-process-library/process.hpp \ No newline at end of file diff --git a/src/libs/tiny-process-library/process_unix.cpp b/src/libs/tiny-process-library/process_unix.cpp new file mode 100644 index 00000000..da4242ce --- /dev/null +++ b/src/libs/tiny-process-library/process_unix.cpp @@ -0,0 +1,535 @@ +#include "process.hpp" +#include <algorithm> +#include <bitset> +#include <cstdlib> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <poll.h> +#include <set> +#include <signal.h> +#include <stdexcept> +#include <string.h> +#include <unistd.h> + +namespace TinyProcessLib { + +static int portable_execvpe(const char *file, char *const argv[], char *const envp[]) { +#ifdef __GLIBC__ + // Prefer native implementation. + return execvpe(file, argv, envp); +#else + if(!file || !*file) { + errno = ENOENT; + return -1; + } + + if(strchr(file, '/') != nullptr) { + // If file contains a slash, no search is needed. + return execve(file, argv, envp); + } + + const char *path = getenv("PATH"); + char cspath[PATH_MAX + 1] = {}; + if(!path) { +// small patch for android +#ifndef __ANDROID__ + // If env variable is not set, use static path string. + confstr(_CS_PATH, cspath, sizeof(cspath)); + path = cspath; +#else + // we are certainly on termux, thus android, + // and it doesn't have _CS_PATH, so here's a fix/workaround + char *prefix = getenv("PREFIX"); + if (prefix) { + strncat(prefix, "/bin", 6); + path = prefix; + } + else { + path = "/data/data/com.termux/files/usr/bin"; + } +#endif + } + + const size_t path_len = strlen(path); + const size_t file_len = strlen(file); + + if(file_len > NAME_MAX) { + errno = ENAMETOOLONG; + return -1; + } + + // Indicates whether we encountered EACCESS at least once. + bool eacces = false; + + const char *curr = nullptr; + const char *next = nullptr; + + for(curr = path; *curr; curr = *next ? next + 1 : next) { + next = strchr(curr, ':'); + if(!next) { + next = path + path_len; + } + + const size_t sz = (next - curr); + if(sz > PATH_MAX) { + // Path is too long. Proceed to next path in list. + continue; + } + + char exe_path[PATH_MAX + 1 + NAME_MAX + 1]; // 1 byte for slash + 1 byte for \0 + memcpy(exe_path, curr, sz); + exe_path[sz] = '/'; + memcpy(exe_path + sz + 1, file, file_len); + exe_path[sz + 1 + file_len] = '\0'; + + execve(exe_path, argv, envp); + + switch(errno) { + case EACCES: + eacces = true; + case ENOENT: + case ESTALE: + case ENOTDIR: + case ENODEV: + case ETIMEDOUT: + // The errors above indicate that the executable was not found. + // The list of errors replicates one from glibc. + // In this case we proceed to next path in list. + break; + + default: + // Other errors indicate that executable was found but failed + // to execute. In this case we return error. + return -1; + } + } + + if(eacces) { + // If search failed, and at least one iteration reported EACCESS, it means + // that the needed executable exists but does not have suitable permissions. + // In this case we report EACEESS for user. + errno = EACCES; + } + // Otherwise we just keep last encountered errno. + return -1; +#endif +} + +Process::Data::Data() noexcept : id(-1) { +} + +Process::Process(const std::function<void()> &function, + std::function<void(const char *, size_t)> read_stdout, + std::function<void(const char *, size_t)> read_stderr, + bool open_stdin, const Config &config) + : closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) { + if(config.flatpak_spawn_host) + throw std::invalid_argument("Cannot break out of a flatpak sandbox with this overload."); + open(function); + async_read(); +} + +Process::id_type Process::open(const std::function<void()> &function) noexcept { + if(open_stdin) + stdin_fd = std::unique_ptr<fd_type>(new fd_type); + if(read_stdout) + stdout_fd = std::unique_ptr<fd_type>(new fd_type); + if(read_stderr) + stderr_fd = std::unique_ptr<fd_type>(new fd_type); + + int stdin_p[2], stdout_p[2], stderr_p[2]; + + if(stdin_fd && pipe(stdin_p) != 0) + return -1; + if(stdout_fd && pipe(stdout_p) != 0) { + if(stdin_fd) { + close(stdin_p[0]); + close(stdin_p[1]); + } + return -1; + } + if(stderr_fd && pipe(stderr_p) != 0) { + if(stdin_fd) { + close(stdin_p[0]); + close(stdin_p[1]); + } + if(stdout_fd) { + close(stdout_p[0]); + close(stdout_p[1]); + } + return -1; + } + + id_type pid = fork(); + + if(pid < 0) { + if(stdin_fd) { + close(stdin_p[0]); + close(stdin_p[1]); + } + if(stdout_fd) { + close(stdout_p[0]); + close(stdout_p[1]); + } + if(stderr_fd) { + close(stderr_p[0]); + close(stderr_p[1]); + } + return pid; + } + else if(pid == 0) { + if(stdin_fd) + dup2(stdin_p[0], 0); + if(stdout_fd) + dup2(stdout_p[1], 1); + if(stderr_fd) + dup2(stderr_p[1], 2); + if(stdin_fd) { + close(stdin_p[0]); + close(stdin_p[1]); + } + if(stdout_fd) { + close(stdout_p[0]); + close(stdout_p[1]); + } + if(stderr_fd) { + close(stderr_p[0]); + close(stderr_p[1]); + } + + if(!config.inherit_file_descriptors) { + // Optimization on some systems: using 8 * 1024 (Debian's default _SC_OPEN_MAX) as fd_max limit + int fd_max = std::min(8192, static_cast<int>(sysconf(_SC_OPEN_MAX))); // Truncation is safe + if(fd_max < 0) + fd_max = 8192; + for(int fd = 3; fd < fd_max; fd++) + close(fd); + } + + setpgid(0, 0); + // TODO: See here on how to emulate tty for colors: http://stackoverflow.com/questions/1401002/trick-an-application-into-thinking-its-stdin-is-interactive-not-a-pipe + // TODO: One solution is: echo "command;exit"|script -q /dev/null + + if(function) + function(); + + _exit(EXIT_FAILURE); + } + + if(stdin_fd) + close(stdin_p[0]); + if(stdout_fd) + close(stdout_p[1]); + if(stderr_fd) + close(stderr_p[1]); + + if(stdin_fd) + *stdin_fd = stdin_p[1]; + if(stdout_fd) + *stdout_fd = stdout_p[0]; + if(stderr_fd) + *stderr_fd = stderr_p[0]; + + closed = false; + data.id = pid; + return pid; +} + +Process::id_type Process::open(const std::vector<string_type> &arguments, const string_type &path, const environment_type *environment) noexcept { + return open([this, &arguments, &path, &environment] { + if(arguments.empty()) + exit(127); + + std::vector<const char *> argv_ptrs; + + if(config.flatpak_spawn_host) { + // break out of sandbox, execute on host + argv_ptrs.reserve(arguments.size() + 3); + argv_ptrs.emplace_back("/usr/bin/flatpak-spawn"); + argv_ptrs.emplace_back("--host"); + } + else + argv_ptrs.reserve(arguments.size() + 1); + + for(auto &argument : arguments) + argv_ptrs.emplace_back(argument.c_str()); + argv_ptrs.emplace_back(nullptr); + + if(!path.empty()) { + if(chdir(path.c_str()) != 0) + exit(1); + } + + if(!environment) + execvp(argv_ptrs[0], const_cast<char *const *>(argv_ptrs.data())); + else { + std::vector<std::string> env_strs; + std::vector<const char *> env_ptrs; + env_strs.reserve(environment->size()); + env_ptrs.reserve(environment->size() + 1); + for(const auto &e : *environment) { + env_strs.emplace_back(e.first + '=' + e.second); + env_ptrs.emplace_back(env_strs.back().c_str()); + } + env_ptrs.emplace_back(nullptr); + + portable_execvpe(argv_ptrs[0], const_cast<char *const *>(argv_ptrs.data()), const_cast<char *const *>(env_ptrs.data())); + } + }); +} + +Process::id_type Process::open(const std::string &command, const std::string &path, const environment_type *environment) noexcept { + return open([this, &command, &path, &environment] { + auto command_c_str = command.c_str(); + std::string cd_path_and_command; + if(!path.empty()) { + auto path_escaped = path; + size_t pos = 0; + // Based on https://www.reddit.com/r/cpp/comments/3vpjqg/a_new_platform_independent_process_library_for_c11/cxsxyb7 + while((pos = path_escaped.find('\'', pos)) != std::string::npos) { + path_escaped.replace(pos, 1, "'\\''"); + pos += 4; + } + cd_path_and_command = "cd '" + path_escaped + "' && " + command; // To avoid resolving symbolic links + command_c_str = cd_path_and_command.c_str(); + } + + if(!environment) { + if(config.flatpak_spawn_host) + // break out of sandbox, execute on host + execl("/usr/bin/flatpak-spawn", "/usr/bin/flatpak-spawn", "--host", "/bin/sh", "-c", command_c_str, nullptr); + else + execl("/bin/sh", "/bin/sh", "-c", command_c_str, nullptr); + } + else { + std::vector<std::string> env_strs; + std::vector<const char *> env_ptrs; + env_strs.reserve(environment->size()); + env_ptrs.reserve(environment->size() + 1); + for(const auto &e : *environment) { + env_strs.emplace_back(e.first + '=' + e.second); + env_ptrs.emplace_back(env_strs.back().c_str()); + } + env_ptrs.emplace_back(nullptr); + if(config.flatpak_spawn_host) + // break out of sandbox, execute on host + execle("/usr/bin/flatpak-spawn", "/usr/bin/flatpak-spawn", "--host", "/bin/sh", "-c", command_c_str, nullptr, env_ptrs.data()); + else + execle("/bin/sh", "/bin/sh", "-c", command_c_str, nullptr, env_ptrs.data()); + } + }); +} + +void Process::async_read() noexcept { + if(data.id <= 0 || (!stdout_fd && !stderr_fd)) + return; + + stdout_stderr_thread = std::thread([this] { + std::vector<pollfd> pollfds; + std::bitset<2> fd_is_stdout; + if(stdout_fd) { + fd_is_stdout.set(pollfds.size()); + pollfds.emplace_back(); + pollfds.back().fd = fcntl(*stdout_fd, F_SETFL, fcntl(*stdout_fd, F_GETFL) | O_NONBLOCK) == 0 ? *stdout_fd : -1; + pollfds.back().events = POLLIN; + } + if(stderr_fd) { + pollfds.emplace_back(); + pollfds.back().fd = fcntl(*stderr_fd, F_SETFL, fcntl(*stderr_fd, F_GETFL) | O_NONBLOCK) == 0 ? *stderr_fd : -1; + pollfds.back().events = POLLIN; + } + auto buffer = std::unique_ptr<char[]>(new char[config.buffer_size]); + bool any_open = !pollfds.empty(); + while(any_open && (poll(pollfds.data(), static_cast<nfds_t>(pollfds.size()), -1) > 0 || errno == EINTR)) { + any_open = false; + for(size_t i = 0; i < pollfds.size(); ++i) { + if(pollfds[i].fd >= 0) { + if(pollfds[i].revents & POLLIN) { + const ssize_t n = read(pollfds[i].fd, buffer.get(), config.buffer_size); + if(n > 0) { + if(fd_is_stdout[i]) + read_stdout(buffer.get(), static_cast<size_t>(n)); + else + read_stderr(buffer.get(), static_cast<size_t>(n)); + } + else if(n == 0 || (n < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK)) { + if(fd_is_stdout[i]) { + if(config.on_stdout_close) + config.on_stdout_close(); + } + else { + if(config.on_stderr_close) + config.on_stderr_close(); + } + pollfds[i].fd = -1; + continue; + } + } + else if(pollfds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { + if(fd_is_stdout[i]) { + if(config.on_stdout_close) + config.on_stdout_close(); + } + else { + if(config.on_stderr_close) + config.on_stderr_close(); + } + pollfds[i].fd = -1; + continue; + } + any_open = true; + } + } + } + }); +} + +int Process::get_exit_status() noexcept { + if(data.id <= 0) + return -1; + + int exit_status; + id_type pid; + do { + pid = waitpid(data.id, &exit_status, 0); + } while(pid < 0 && errno == EINTR); + + if(pid < 0 && errno == ECHILD) { + // PID doesn't exist anymore, return previously sampled exit status (or -1) + return data.exit_status; + } + else { + // Store exit status for future calls + if(exit_status >= 256) + exit_status = exit_status >> 8; + data.exit_status = exit_status; + } + + { + std::lock_guard<std::mutex> lock(close_mutex); + closed = true; + } + close_fds(); + + return exit_status; +} + +bool Process::try_get_exit_status(int &exit_status) noexcept { + if(data.id <= 0) { + exit_status = -1; + return true; + } + + const id_type pid = waitpid(data.id, &exit_status, WNOHANG); + if(pid < 0 && errno == ECHILD) { + // PID doesn't exist anymore, set previously sampled exit status (or -1) + exit_status = data.exit_status; + return true; + } + else if(pid <= 0) { + // Process still running (p==0) or error + return false; + } + else { + // store exit status for future calls + if(exit_status >= 256) + exit_status = exit_status >> 8; + data.exit_status = exit_status; + } + + { + std::lock_guard<std::mutex> lock(close_mutex); + closed = true; + } + close_fds(); + + return true; +} + +void Process::close_fds() noexcept { + if(stdout_stderr_thread.joinable()) + stdout_stderr_thread.join(); + + if(stdin_fd) + close_stdin(); + if(stdout_fd) { + if(data.id > 0) + close(*stdout_fd); + stdout_fd.reset(); + } + if(stderr_fd) { + if(data.id > 0) + close(*stderr_fd); + stderr_fd.reset(); + } +} + +bool Process::write(const char *bytes, size_t n) { + if(!open_stdin) + throw std::invalid_argument("Can't write to an unopened stdin pipe. Please set open_stdin=true when constructing the process."); + + std::lock_guard<std::mutex> lock(stdin_mutex); + if(stdin_fd) { + while(n != 0) { + const ssize_t ret = ::write(*stdin_fd, bytes, n); + if(ret < 0) { + if(errno == EINTR) + continue; + else + return false; + } + bytes += static_cast<size_t>(ret); + n -= static_cast<size_t>(ret); + } + return true; + } + return false; +} + +void Process::close_stdin() noexcept { + std::lock_guard<std::mutex> lock(stdin_mutex); + if(stdin_fd) { + if(data.id > 0) + close(*stdin_fd); + stdin_fd.reset(); + } +} + +void Process::kill(bool force) noexcept { + std::lock_guard<std::mutex> lock(close_mutex); + if(data.id > 0 && !closed) { + if(force) { + ::kill(-data.id, SIGTERM); + ::kill(data.id, SIGTERM); // Based on comment in https://gitlab.com/eidheim/tiny-process-library/-/merge_requests/29#note_1146144166 + } + else { + ::kill(-data.id, SIGINT); + ::kill(data.id, SIGINT); + } + } +} + +void Process::kill(id_type id, bool force) noexcept { + if(id <= 0) + return; + + if(force) { + ::kill(-id, SIGTERM); + ::kill(id, SIGTERM); + } + else { + ::kill(-id, SIGINT); + ::kill(id, SIGINT); + } +} + +void Process::signal(int signum) noexcept { + std::lock_guard<std::mutex> lock(close_mutex); + if(data.id > 0 && !closed) { + ::kill(-data.id, signum); + ::kill(data.id, signum); + } +} + +} // namespace TinyProcessLib diff --git a/src/libs/tiny-process-library/process_win.cpp b/src/libs/tiny-process-library/process_win.cpp new file mode 100644 index 00000000..dd1c3b5e --- /dev/null +++ b/src/libs/tiny-process-library/process_win.cpp @@ -0,0 +1,424 @@ +#include "process.hpp" +// clang-format off +#include <windows.h> +// clang-format on +#include <cstring> +#include <stdexcept> +#include <tlhelp32.h> + +namespace TinyProcessLib { + +Process::Data::Data() noexcept : id(0) {} + +// Simple HANDLE wrapper to close it automatically from the destructor. +class Handle { +public: + Handle() noexcept : handle(INVALID_HANDLE_VALUE) {} + ~Handle() noexcept { + close(); + } + void close() noexcept { + if(handle != INVALID_HANDLE_VALUE) + CloseHandle(handle); + } + HANDLE detach() noexcept { + HANDLE old_handle = handle; + handle = INVALID_HANDLE_VALUE; + return old_handle; + } + operator HANDLE() const noexcept { return handle; } + HANDLE *operator&() noexcept { return &handle; } + +private: + HANDLE handle; +}; + +template <class Char> +struct CharSet; + +template <> +struct CharSet<char> { + static constexpr const char *whitespace = " \t\n\v\""; + static constexpr char space = ' '; + static constexpr char doublequote = '"'; + static constexpr char backslash = '\\'; +}; + +template <> +struct CharSet<wchar_t> { + static constexpr const wchar_t *whitespace = L" \t\n\v\""; + static constexpr wchar_t space = L' '; + static constexpr wchar_t doublequote = L'"'; + static constexpr wchar_t backslash = L'\\'; +}; + +// Based on blog post https://learn.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way +template <class Char> +static std::basic_string<Char> join_arguments(const std::vector<std::basic_string<Char>> &args) { + using charset = CharSet<Char>; + + std::basic_string<Char> ret; + + for(const auto &arg : args) { + if(!ret.empty()) + ret.push_back(charset::space); + + if(!arg.empty() && arg.find_first_of(charset::whitespace) == arg.npos) { + ret.append(arg); + continue; + } + + ret.push_back(charset::doublequote); + + for(auto it = arg.begin();; ++it) { + size_t n_backslashes = 0; + + while(it != arg.end() && *it == charset::backslash) { + ++it; + ++n_backslashes; + } + + if(it == arg.end()) { + // Escape all backslashes, but let the terminating double quotation mark + // we add below be interpreted as a metacharacter. + ret.append(n_backslashes * 2, charset::backslash); + break; + } + else if(*it == charset::doublequote) { + // Escape all backslashes and the following double quotation mark. + ret.append(n_backslashes * 2 + 1, charset::backslash); + ret.push_back(*it); + } + else { + // Backslashes aren't special here. + ret.append(n_backslashes, charset::backslash); + ret.push_back(*it); + } + } + + ret.push_back(charset::doublequote); + } + + return ret; +} + +// Based on the discussion thread: https://www.reddit.com/r/cpp/comments/3vpjqg/a_new_platform_independent_process_library_for_c11/cxq1wsj +std::mutex create_process_mutex; + +Process::id_type Process::open(const std::vector<string_type> &arguments, const string_type &path, const environment_type *environment) noexcept { + return open(join_arguments(arguments), path, environment); +} + +// Based on the example at https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx. +Process::id_type Process::open(const string_type &command, const string_type &path, const environment_type *environment) noexcept { + if(open_stdin) + stdin_fd = std::unique_ptr<fd_type>(new fd_type(nullptr)); + if(read_stdout) + stdout_fd = std::unique_ptr<fd_type>(new fd_type(nullptr)); + if(read_stderr) + stderr_fd = std::unique_ptr<fd_type>(new fd_type(nullptr)); + + Handle stdin_rd_p; + Handle stdin_wr_p; + Handle stdout_rd_p; + Handle stdout_wr_p; + Handle stderr_rd_p; + Handle stderr_wr_p; + + SECURITY_ATTRIBUTES security_attributes; + + security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); + security_attributes.bInheritHandle = TRUE; + security_attributes.lpSecurityDescriptor = nullptr; + + std::lock_guard<std::mutex> lock(create_process_mutex); + if(stdin_fd) { + if(!CreatePipe(&stdin_rd_p, &stdin_wr_p, &security_attributes, 0) || + !SetHandleInformation(stdin_wr_p, HANDLE_FLAG_INHERIT, 0)) + return 0; + } + if(stdout_fd) { + if(!CreatePipe(&stdout_rd_p, &stdout_wr_p, &security_attributes, 0) || + !SetHandleInformation(stdout_rd_p, HANDLE_FLAG_INHERIT, 0)) { + return 0; + } + } + if(stderr_fd) { + if(!CreatePipe(&stderr_rd_p, &stderr_wr_p, &security_attributes, 0) || + !SetHandleInformation(stderr_rd_p, HANDLE_FLAG_INHERIT, 0)) { + return 0; + } + } + + PROCESS_INFORMATION process_info; + STARTUPINFO startup_info; + + ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION)); + + ZeroMemory(&startup_info, sizeof(STARTUPINFO)); + startup_info.cb = sizeof(STARTUPINFO); + startup_info.hStdInput = stdin_rd_p; + startup_info.hStdOutput = stdout_wr_p; + startup_info.hStdError = stderr_wr_p; + if(stdin_fd || stdout_fd || stderr_fd) + startup_info.dwFlags |= STARTF_USESTDHANDLES; + + if(config.show_window != Config::ShowWindow::show_default) { + startup_info.dwFlags |= STARTF_USESHOWWINDOW; + startup_info.wShowWindow = static_cast<WORD>(config.show_window); + } + + auto process_command = command; +#ifdef MSYS_PROCESS_USE_SH + size_t pos = 0; + while((pos = process_command.find('\\', pos)) != string_type::npos) { + process_command.replace(pos, 1, "\\\\\\\\"); + pos += 4; + } + pos = 0; + while((pos = process_command.find('\"', pos)) != string_type::npos) { + process_command.replace(pos, 1, "\\\""); + pos += 2; + } + process_command.insert(0, "sh -c \""); + process_command += "\""; +#endif + + DWORD creation_flags = stdin_fd || stdout_fd || stderr_fd ? CREATE_NO_WINDOW : 0; // CREATE_NO_WINDOW cannot be used when stdout or stderr is redirected to parent process + string_type environment_str; + if(environment) { +#ifdef UNICODE + for(const auto &e : *environment) + environment_str += e.first + L'=' + e.second + L'\0'; + if(!environment_str.empty()) + environment_str += L'\0'; + creation_flags |= CREATE_UNICODE_ENVIRONMENT; +#else + for(const auto &e : *environment) + environment_str += e.first + '=' + e.second + '\0'; + if(!environment_str.empty()) + environment_str += '\0'; +#endif + } + BOOL bSuccess = CreateProcess(nullptr, process_command.empty() ? nullptr : &process_command[0], nullptr, nullptr, + stdin_fd || stdout_fd || stderr_fd || config.inherit_file_descriptors, // Cannot be false when stdout, stderr or stdin is used + creation_flags, + environment_str.empty() ? nullptr : &environment_str[0], + path.empty() ? nullptr : path.c_str(), + &startup_info, &process_info); + + if(!bSuccess) + return 0; + else + CloseHandle(process_info.hThread); + + if(stdin_fd) + *stdin_fd = stdin_wr_p.detach(); + if(stdout_fd) + *stdout_fd = stdout_rd_p.detach(); + if(stderr_fd) + *stderr_fd = stderr_rd_p.detach(); + + closed = false; + data.id = process_info.dwProcessId; + data.handle = process_info.hProcess; + return process_info.dwProcessId; +} + +void Process::async_read() noexcept { + if(data.id == 0) + return; + + if(stdout_fd) { + stdout_thread = std::thread([this]() { + DWORD n; + std::unique_ptr<char[]> buffer(new char[config.buffer_size]); + for(;;) { + BOOL bSuccess = ReadFile(*stdout_fd, static_cast<CHAR *>(buffer.get()), static_cast<DWORD>(config.buffer_size), &n, nullptr); + if(!bSuccess || n == 0) { + if(config.on_stdout_close) + config.on_stdout_close(); + break; + } + read_stdout(buffer.get(), static_cast<size_t>(n)); + } + }); + } + if(stderr_fd) { + stderr_thread = std::thread([this]() { + DWORD n; + std::unique_ptr<char[]> buffer(new char[config.buffer_size]); + for(;;) { + BOOL bSuccess = ReadFile(*stderr_fd, static_cast<CHAR *>(buffer.get()), static_cast<DWORD>(config.buffer_size), &n, nullptr); + if(!bSuccess || n == 0) { + if(config.on_stderr_close) + config.on_stderr_close(); + break; + } + read_stderr(buffer.get(), static_cast<size_t>(n)); + } + }); + } +} + +int Process::get_exit_status() noexcept { + if(data.id == 0) + return -1; + + if(!data.handle) + return data.exit_status; + + WaitForSingleObject(data.handle, INFINITE); + + DWORD exit_status; + if(!GetExitCodeProcess(data.handle, &exit_status)) + data.exit_status = -1; // Store exit status for future calls + else + data.exit_status = static_cast<int>(exit_status); // Store exit status for future calls + + { + std::lock_guard<std::mutex> lock(close_mutex); + CloseHandle(data.handle); + data.handle = nullptr; + closed = true; + } + close_fds(); + + return data.exit_status; +} + +bool Process::try_get_exit_status(int &exit_status) noexcept { + if(data.id == 0) { + exit_status = -1; + return true; + } + + if(!data.handle) { + exit_status = data.exit_status; + return true; + } + + DWORD wait_status = WaitForSingleObject(data.handle, 0); + if(wait_status == WAIT_TIMEOUT) + return false; + + DWORD exit_status_tmp; + if(!GetExitCodeProcess(data.handle, &exit_status_tmp)) + exit_status = -1; + else + exit_status = static_cast<int>(exit_status_tmp); + data.exit_status = exit_status; // Store exit status for future calls + + { + std::lock_guard<std::mutex> lock(close_mutex); + CloseHandle(data.handle); + data.handle = nullptr; + closed = true; + } + close_fds(); + + return true; +} + +void Process::close_fds() noexcept { + if(stdout_thread.joinable()) + stdout_thread.join(); + if(stderr_thread.joinable()) + stderr_thread.join(); + + if(stdin_fd) + close_stdin(); + if(stdout_fd) { + if(*stdout_fd != nullptr) + CloseHandle(*stdout_fd); + stdout_fd.reset(); + } + if(stderr_fd) { + if(*stderr_fd != nullptr) + CloseHandle(*stderr_fd); + stderr_fd.reset(); + } +} + +bool Process::write(const char *bytes, size_t n) { + if(!open_stdin) + throw std::invalid_argument("Can't write to an unopened stdin pipe. Please set open_stdin=true when constructing the process."); + + std::lock_guard<std::mutex> lock(stdin_mutex); + if(stdin_fd) { + DWORD written; + BOOL bSuccess = WriteFile(*stdin_fd, bytes, static_cast<DWORD>(n), &written, nullptr); + if(!bSuccess || written == 0) { + return false; + } + else { + return true; + } + } + return false; +} + +void Process::close_stdin() noexcept { + std::lock_guard<std::mutex> lock(stdin_mutex); + if(stdin_fd) { + if(*stdin_fd != nullptr) + CloseHandle(*stdin_fd); + stdin_fd.reset(); + } +} + +// Based on http://stackoverflow.com/a/1173396 +void Process::kill(bool /*force*/) noexcept { + std::lock_guard<std::mutex> lock(close_mutex); + if(data.id > 0 && !closed) { + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if(snapshot) { + PROCESSENTRY32 process; + ZeroMemory(&process, sizeof(process)); + process.dwSize = sizeof(process); + if(Process32First(snapshot, &process)) { + do { + if(process.th32ParentProcessID == data.id) { + HANDLE process_handle = OpenProcess(PROCESS_TERMINATE, FALSE, process.th32ProcessID); + if(process_handle) { + TerminateProcess(process_handle, 2); + CloseHandle(process_handle); + } + } + } while(Process32Next(snapshot, &process)); + } + CloseHandle(snapshot); + } + TerminateProcess(data.handle, 2); + } +} + +// Based on http://stackoverflow.com/a/1173396 +void Process::kill(id_type id, bool /*force*/) noexcept { + if(id == 0) + return; + + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if(snapshot) { + PROCESSENTRY32 process; + ZeroMemory(&process, sizeof(process)); + process.dwSize = sizeof(process); + if(Process32First(snapshot, &process)) { + do { + if(process.th32ParentProcessID == id) { + HANDLE process_handle = OpenProcess(PROCESS_TERMINATE, FALSE, process.th32ProcessID); + if(process_handle) { + TerminateProcess(process_handle, 2); + CloseHandle(process_handle); + } + } + } while(Process32Next(snapshot, &process)); + } + CloseHandle(snapshot); + } + HANDLE process_handle = OpenProcess(PROCESS_TERMINATE, FALSE, id); + if(process_handle) + TerminateProcess(process_handle, 2); +} + +} // namespace TinyProcessLib diff --git a/src/libs/toml++/Makefile b/src/libs/toml++/Makefile index f200e231..811bdc39 100644 --- a/src/libs/toml++/Makefile +++ b/src/libs/toml++/Makefile @@ -1,12 +1,13 @@ CXX ?= g++ +CXXSTD ?= c++20 SRC = toml.cpp TARGET = toml.o -CXXFLAGS = -I../../../include -std=c++20 +CXXFLAGS = -I../../../include/libs $(LTO_FLAGS) -fvisibility-inlines-hidden -fvisibility=default -fPIC -std=$(CXXSTD) all: $(TARGET) $(TARGET): - ${CXX} $(SRC) $(CXXFLAGS) -c -o ../../../$(BUILDDIR)/$@ + $(CXX) $(SRC) $(CXXFLAGS) -c -o ../../../$(BUILDDIR)/$@ clean: rm -rf ../../../$(BUILDDIR)/toml++/$(TARGET) diff --git a/src/main.cpp b/src/main.cpp index 6f98de53..d7b703de 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,29 +1,31 @@ /* * Copyright 2025 Toni500git - * + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ -#include <getopt.h> +#include <dlfcn.h> +#include <stdlib.h> +#include <termios.h> #include <unistd.h> #include <termios.h> #include <stdlib.h> @@ -35,20 +37,24 @@ #include <filesystem> #include <string> #include <thread> +#include <vector> -#include "config.hpp" +#include "libcufetch/fmt/compile.h" +#include "texts.hpp" +#include "getopt_port/getopt.h" +#include "core-modules.hh" #include "display.hpp" +#include "fmt/base.h" #include "fmt/ranges.h" #include "gui.hpp" -#include "query.hpp" #include "platform.hpp" #include "switch_fnv1a.hpp" #include "util.hpp" #if (!__has_include("version.h")) -# error "version.h not found, please generate it with ./scripts/generateVersion.sh" +#error "version.h not found, please generate it with ./scripts/generateVersion.sh" #else -# include "version.h" +#include "version.h" #endif // clang-format off @@ -63,6 +69,9 @@ using namespace std::string_view_literals; +bool display_modules = false; +bool display_list_logos = false; + struct termios orig_termios; static void disable_raw_mode() @@ -82,8 +91,9 @@ static void enable_raw_mode() static int kbhit() { - struct timeval tv = {0L, 0L}; - fd_set fds; + struct timeval tv = { 0L, 0L }; + fd_set fds; + FD_ZERO(&fds); FD_SET(STDIN_FILENO, &fds); return select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv) > 0; @@ -92,10 +102,11 @@ static int kbhit() // Print the version and some other infos, then exit successfully static void version() { - std::string version{ fmt::format("customfetch {} built from branch {} at {} commit {} ({}).\n" - "Date: {}\n" - "Tag: {}", - VERSION, GIT_BRANCH, GIT_DIRTY, GIT_COMMIT_HASH, GIT_COMMIT_MESSAGE, GIT_COMMIT_DATE, GIT_TAG) }; + std::string version{ fmt::format( + "customfetch {} built from branch '{}' at {} commit '{}' ({}).\n" + "Date: {}\n" + "Tag: {}", + VERSION, GIT_BRANCH, GIT_DIRTY, GIT_COMMIT_HASH, GIT_COMMIT_MESSAGE, GIT_COMMIT_DATE, GIT_TAG) }; #if !(USE_DCONF) version += "\n\nNO flags were set\n"; @@ -106,7 +117,8 @@ static void version() #endif #endif - fmt::print("{}\n", version); + fmt::print("{}", version); + fmt::print("\n"); // if only everyone would not return error when querying the program version :( std::exit(EXIT_SUCCESS); @@ -115,439 +127,29 @@ static void version() // Print the args help menu, then exit with code depending if it's from invalid or -h arg static void help(bool invalid_opt = false) { - constexpr std::string_view help( -R"(Usage: customfetch [OPTIONS]... -A command-line, GUI, and Android widget system information tool (like neofetch) focused on customizability and performance. - -NOTE: Boolean flags [<BOOL>] accept: "true", 1, "enable", or empty. Any other value is treated as false. - -GENERAL OPTIONS: - -h, --help Print this help menu. - -V, --version Print version and Git branch info. - -C, --config <PATH> Path to the config file (default: ~/.config/customfetch.conf). - - --gen-config [<PATH>] Generate default config file. If PATH is omitted, saves to default location. - Prompts before overwriting. - -LOGO OPTIONS: - -n, --no-logo Disable logo display. - -L, --logo-only Print only the logo (skip info layout). - -s, --source-path <PATH> Path to custom ASCII art/image file. - - -a, --ascii-logo-type <TYPE> - Type of ASCII art (typically "small", "old", or empty for default). - Example: "--ascii-logo-type small" looks for "logo_small.txt". - - -D, --data-dir <PATH> Path to data directory containing "ascii/" subfolder with distro logos. - -d, --distro <NAME> Use a custom distro logo (case-insensitive, e.g., "windows 11" or "ArCh"). - -p, --logo-position <POS> Logo position: "top" (default), "left", or "bottom". - -o, --offset <NUM> Space between logo and layout (default: 5). - --logo-padding-top <NUM> Logo padding from top (default: 0). - --logo-padding-left <NUM> Logo padding from left (default: 0). - -LAYOUT & FORMATTING: - -m, --layout-line <STRING> Override config layout with custom line(s). - Example: `-m "${auto}OS: $<os.name>" -m "${auto}CPU: $<cpu>"`. - - -N, --no-color Disable all colors (useful for pipes/scripts). - --wrap-lines=[<BOOL>] Enable line wrapping (default: false). - --title-sep <STRING> String to use for $<title_sep> (default: "-"). - --sep-reset <STRING> String that resets color (default: ":"). - --sep-reset-after=[<BOOL>] Reset color after (default) or before 'sep-reset'. - -GUI/TERMINAL OPTIONS: - -f, --font <STRING> GUI font (format: "FAMILY STYLE SIZE", e.g., "Liberation Mono Normal 12"). - -i, --image-backend <NAME> (Experimental) Terminal image backend ("kitty" or "viu"). - --bg-image <PATH> GUI background image path ("disable" to turn off). - -ADVANCED/CONFIG: - -O, --override <STRING> Override a config value (non-array). Syntax: "name=value" (no spaces around "="). - Example: "auto.disk.fmt='Disk(%1): %6'". - Note: Names without dots (e.g., "sep-reset-after") gets mapped to "config.<name>". - - --color <STRING> Replace a color globally. Syntax: "name=hex" (no spaces around "="). - Example: "--color magenta=#FF00FF". - - --disallow-command-tag Do not allow command tags $() to be executed in the config or -m args. - This is a safety measure for preventing malicious code to be executed because you didn't want to check the config first. - -INFORMATIONAL: - -l, --list-modules List all available info tag modules (e.g., $<cpu> or $<os.name>). - -w, --how-it-works Explain layout variables and customization. - --list-logos List available ASCII logos in --data-dir. - -LIVE MODE: - --loop-ms <NUM> Run in live mode, updating every <NUM> milliseconds (min: 200). - Press 'q' to exit. - -EXAMPLES: - 1. Minimal output with default logo: - customfetch --no-color - - 2. Custom distro logo with live updates: - customfetch --distro "arch" --loop-ms 1000 - - 3. Override layout and colors: - customfetch -m "${auto}OS: $<os.name>" --color "magenta=#FF00FF" - -For details, see `man customfetch` or run `--how-it-works`. -)"); - - fmt::print("{}\n", help); - std::exit(invalid_opt); -} - -// Print all info modules you can put in $<>, then exit successfully -static void modules_list() -{ - constexpr std::string_view list(R"( ---------------------------------------------------------[ MODULE ONLY ]------------------------------------------------------------------------ -Should be used as like as $<module> -NOTE: module "title_sep" as an extended name version called "title_separator" - -Syntax: -# maybe comments of the module -module: - description [example of what it prints] - -ram: - used and total amount of RAM (auto) with used percentage [2.81 GiB / 15.88 GiB (5.34%)] - -swap: - used and total amount of the swapfile (auto) with used percentage [477.68 MiB / 512.00 MiB (88.45%)] - -# note: the module can have either a device path -# or a filesystem path -# e.g disk(/) or disk(/dev/sda5) -disk(/path/to/fs): - used and total amount of disk space (auto) with type of filesystem and used percentage [379.83 GiB / 438.08 GiB (86.70%) - ext4] - -# usually people have 1 GPU in their PC, -# but if you got more than 1 and want to query it, -# you should call gpu module with a number, e.g gpu1 (default gpu0). -# Infos are gotten from `/sys/class/drm/` and on each cardN directory -gpu: - GPU shorter vendor name and model name [NVIDIA GeForce GTX 1650] - -cpu: - CPU model name with number of virtual processors and max freq [AMD Ryzen 5 5500 (12) @ 4.90 GHz] - -battery: - battery current percentage and status [50.00% [Discharging]] - -title: - user and hostname colored with ${auto2} [toni@arch2] - -title_sep: - separator between the title and the system infos (with the title length) [--------] - -colors: - color palette with background spaces - -colors_light: - light color palette with background spaces - -# with `symbol` I mean a symbol to be used for the -# view of the color palette -colors_symbol(symbol): - color palette with specific symbol - -# with `symbol` I mean a symbol to be used for the -# view of the color palette -colors_light_symbol(symbol): - light color palette with specific symbol - ---------------------------------------------------------[ MODULE MEMBERS ]------------------------------------------------------------------------ - -Should be used as like as $<module.member> -NOTE: module members such as "os.pkgs" or "disk.used_perc" have an extended name version - "os.pkgs" == "os.packages" - any module member that has "perc" can be replaced with "percentage" - -Syntax: -# maybe comments of the module -module - member: description [example of what it prints; maybe another] - -os - name: OS name (pretty name) [Ubuntu 22.04.4 LTS; Arch Linux] - name_id: OS name id [ubuntu, arch] - kernel: kernel name and version [Linux 6.9.3-zen1-1-zen] - kernel_name: kernel name [Linux] - kernel_version: kernel version [6.9.3-zen1-1-zen] - version_id: OS version id [22.04.4, 20240101.0.204074] - version_codename: OS version codename [jammy] - pkgs: count of the installed packages by a package manager [1869 (pacman), 4 (flatpak)] - uptime: (auto) uptime of the system [36 mins, 3 hours, 23 days] - uptime_secs: uptime of the system in seconds (should be used along with others uptime_ members) [45] - uptime_mins: uptime of the system in minutes (should be used along with others uptime_ members) [12] - uptime_hours: uptime of the system in hours (should be used along with others uptime_ members) [34] - uptime_days: uptime of the system in days (should be used along with others uptime_ members) [2] - hostname: hostname of the OS [myMainPC] - initsys_name: Init system name [systemd] - initsys_version: Init system version [256.5-1-arch] - -user - name: name you are currently logged in (not real name) [toni69] - shell: login shell name and version [zsh 5.9] - shell_name: login shell [zsh] - shell_path: login shell (with path) [/bin/zsh] - shell_version: login shell version (may be not correct) [5.9] - de_name: Desktop Environment current session name [Plasma] - wm_name: Window Manager current session name [dwm; xfwm4] - wm_version: Window Manager version (may not work correctly) [6.2; 4.18.0] - terminal: terminal name and version [alacritty 0.13.2] - terminal_name: terminal name [alacritty] - terminal_version: terminal version [0.13.2] - -# this module is just for generic theme stuff -# such as indeed cursor -# because it is not GTK-Qt specific -theme - cursor: cursor name with its size (auto add the size if queried) [Bibata-Modern-Ice (16px)] - cursor_name: cursor name [Bibata-Modern-Ice] - cursor_size: cursor size [16] - -# If USE_DCONF flag is set, then we're going to use -# dconf, else backing up to gsettings -theme-gsettings - name: gsettings theme name [Decay-Green] - icons: gsettings icons theme name [Papirus-Dark] - font: gsettings font theme name [Cantarell 10] - cursor: gsettings cursor name with its size (auto add the size if queried) [Bibata-Modern-Ice (16px)] - cursor_name: gsettings cursor name [Bibata-Modern-Ice] - cursor_size: gsettings cursor size [16] - -# the N stands for the gtk version number to query -# so for example if you want to query the gtk3 theme name -# write it like "theme-gtk3.name" -# note: may be slow because of calling "gsettings" if couldn't read from configs. -# Read theme-gsettings module comments -theme-gtkN - name: gtk theme name [Arc-Dark] - icons: gtk icons theme name [Qogir-Dark] - font: gtk font theme name [Noto Sans 10] - -# basically as like as the "theme-gtkN" module above -# but with gtk{2,3,4} and auto format gkt version -# note: may be slow because of calling "gsettings" if couldn't read from configs. -# Read theme-gsettings module comments -theme-gtk-all - name: gtk theme name [Arc-Dark [GTK2/3/4]] - icons: gtk icons theme name [Papirus-Dark [GTK2/3], Qogir [GTK4]] - font: gtk font theme name [Hack Nerd Font 13 [GTK2], Noto Sans 10 [GTK3/4]] - -# note: these members are auto displayed in from B to YB (depending if using SI byte unit or not(IEC)). -# they all (except those that has the same name as the module or that ends with "_perc") -# have variants from -B to -YB and -B to -YiB -# example: if you want to show your 512MiB of used RAM in GiB -# use the `used-GiB` variant (they don't print the unit tho) -ram - used: used amount of RAM (auto) [2.81 GiB] - free: available amount of RAM (auto) [10.46 GiB] - total: total amount of RAM (auto) [15.88 GiB] - used_perc: percentage of used amount of RAM in total [17.69%] - free_perc: percentage of available amount of RAM in total [82.31%] - -# same comments as RAM (above) -swap - used: used amount of the swapfile (auto) [477.68 MiB] - free: available amount of the swapfile (auto) [34.32 MiB] - total: total amount of the swapfile (auto) [512.00 MiB] - used_perc: percentage of used amount of the swapfile in total [93.29%] - free_perc: percentage of available amount of the swapfile in total [6.71%] - -# same comments as RAM (above) -# note: the module can have either a device path -# or a filesystem path -# e.g disk(/) or disk(/dev/sda5) -disk(/path/to/fs) - used: used amount of disk space (auto) [360.02 GiB] - free: available amount of disk space (auto) [438.08 GiB] - total: total amount of disk space (auto) [100.08 GiB] - used_perc: percentage of used amount of the disk in total [82.18%] - free_perc: percentage of available amount of the disk in total [17.82%] - fs: type of filesystem [ext4] - device: path to device [/dev/sda5] - types: an array of type options (pretty format) [Regular, External] - mountdir: path to the device mount point [/] - -# usually people have 1 GPU in their PC, -# but if you got more than 1 and want to query it, -# you should call gpu module with a number, e.g gpu1 (default gpu0). -# Infos are gotten from `/sys/class/drm/` and on each cardN directory -gpu - name: GPU model name [GeForce GTX 1650] - vendor: GPU short vendor name [NVIDIA] - vendor_long: GPU vendor name [NVIDIA Corporation] - -# cpu module has a member called "temp" and it has 3 variant units: -# "temp_C" (Celsius) "temp_F" (Fahrenheit) "temp_K" (Kelvin) -cpu - name: CPU model name [AMD Ryzen 5 5500] - temp: CPU temperature (by the chosen unit) [40.62] - nproc: CPU number of virtual processors [12] - freq_cur: CPU current frequency (in GHz) [3.42] - freq_min: CPU minimum frequency (in GHz) [2.45] - freq_max: CPU maximum frequency (in GHz) [4.90] - freq_bios_limit: CPU frequency limited by bios (in GHz) [4.32] - -# battery module has a member called "temp" and it has 3 variant units: -# "temp_C" (Celsius) "temp_F" (Fahrenheit) "temp_K" (Kelvin) -battery - name: battery model name - temp: battery temperature (by the chosen unit) - perc: battery current percentage - vendor: battery manufacturer name - status: battery current status [Discharging, AC Connected] - technology: battery technology [Li-lion] - capacity_level: battery capacity level [Normal, Critical] - -system - host: Host (aka. Motherboard) model name with vendor and version [Micro-Star International Co., Ltd. PRO B550M-P GEN3 (MS-7D95) 1.0] - host_name: Host (aka. Motherboard) model name [PRO B550M-P GEN3 (MS-7D95)] - host_version: Host (aka. Motherboard) model version [1.0] - host_vendor: Host (aka. Motherboard) model vendor [Micro-Star International Co., Ltd.] - arch: the architecture of the machine [x86_64, aarch64] - -)"); - - fmt::print("{}", list); + fmt::print(FMT_COMPILE("{}"), customfetch_help); fmt::print("\n"); - std::exit(EXIT_SUCCESS); + std::exit(invalid_opt); } // Print how customfetch works, then exit successfully static void explain_how_this_works() { - constexpr std::string_view str( -R"( -customfetch is designed for maximum customizability, allowing users to display system information exactly how they want it. -The layout and logo is controlled through special tags that can output system info, execute commands, apply conditional logic, add colors, and calculate percentages with some colors. - -Tag References: -1. Information Tag ($<>) - Retrieves system information from modules. - - Syntax: $<module.member> or $<module> - - Examples: - - $<user.name> # Displays login username - - $<os.kernel> # Shows kernel version - - $<ram> # Shows formatted RAM usage - - Use `--list-modules` to see all available modules and members. - -2. Bash Command Tag ($()) - Executes shell commands and outputs the result. - - Syntax: $(command) - - Examples: - - $(echo "hello") # Outputs: hello - - $(date +%F) # Shows current date - - $(uname -r | cut -d'-' -f1) # Shows kernel version number only - - Supports full shell syntax including pipes and redirection. - -3. Conditional Tag ($[]) - Displays different outputs based on conditions. - - Syntax: $[condition,comparison,true_output,false_output] - - Examples: - - $[$<user.name>,toni,Welcome back!,Access denied] - - $[$(date +%m-%d),12-25,Merry Christmas!,] - - $[$<os.name_id>,arch,${green}I use arch btw,${red}Non-arch user] - -4. Color Tag (${}) - Applies colors and text formatting. - - Basic syntax: ${color} or ${modifiers#RRGGBB} - - Color options: - - Named colors from config - - Hex colors: ${#ff00cc} - - Special colors: ${auto} (uses logo colors) - - Reset styles: ${0} (normal), ${1} (bold reset) - - Formatting modifiers (prefix before color): - - ! = Bold - - u = Underline - - i = Italic - - s = Strikethrough - - l = Blink (terminal only) - - b = Background color - - Advanced GUI-only modifiers: - - o = Overline - - a(value) = Foreground alpha (1-65536 or 0%-100%) - - L(value) = Underline style (none/single/double/low/error) - - U(color) = Underline color (hex) - - B(color) = Background color (hex) - - S(color) = Strikethrough color (hex) - - O(color) = Overline color (hex) - - A(value) = Background alpha (1-65536 or 0%-100%) - - w(value) = Font weight (light/normal/bold/ultrabold or 100-1000) - - Examples: - GUI App only: - ${oU(#ff0000)L(double)}Error # Double red underline - ${a(50%)#00ff00}Semi-transparent green - Cross-platform: - ${\e[1;33m}Bold yellow - ${b#222222}${white}White on gray - ${auto3}The 3rd logo color - - Notes: - - customfetch will try to convert ANSI escape codes to GUI app equivalent - - customfetch will ignore GUI-specific modifiers on terminal. - - if you're using the GUI app and want to display a custom logo that's an image, all the auto colors will be the same colors as the distro ones. - -5. Percentage Tag ($%%) - Calculates and displays colored percentages. - - Syntax: $%value,total% or $%!value,total% (inverted colors) - - Examples: - - $%$<ram.used>,$<ram.total>% - - $%!50,100% (shows red if low) - - $%$(cat /sys/class/power_supply/BAT0/capacity),100% - -Pro Tip: -- Combine tags for powerful formatting: - ${u#5522dd}$[$(date +%H),12,Good ${yellow}morning,Good ${#ff8800}afternoon] - -FAQ: -Q: Why do special characters (&, <) break the GUI display? -A: Escape these characters with \\ (e.g replace "<" with "\\<" from both config and ASCII art): - This doesn't affect terminal output. - -Q: How can I use cbonsai as ASCII art? -A: 1. Create a text file containing: $(!cbonsai -p) - 2. Use: customfetch -s "/path/to/file.txt" - 3. Adjust offset for proper alignment - -Q: Does customfetch support nested tags? -A: Yes! Complex nesting is supported, for example: - $<disk($<disk($[1,1,$(echo -n $<disk(/).mountdir>),23]).mountdir>)> -)"); - - fmt::print("{}", str); + fmt::print(FMT_COMPILE("{}"), explain_customfetch); fmt::print("\n"); std::exit(EXIT_SUCCESS); } -// Print a sorted list of ascii logos you can use at a "data-dir" +// Print a modules list of ascii logos you can use at a "data-dir" // @param data_dir The data directory static void list_logos(const std::string& data_dir) { - debug("data = {}", data_dir); + debug("data-dir = {}", data_dir); if (access(data_dir.c_str(), F_OK) != 0) die("failed to access data directory '{}'", data_dir); std::vector<std::string> list; - for (const auto& logo : std::filesystem::directory_iterator{data_dir}) + for (const auto& logo : std::filesystem::directory_iterator{ data_dir }) { if (logo.is_regular_file()) list.push_back(logo.path().stem()); @@ -555,20 +157,75 @@ static void list_logos(const std::string& data_dir) std::sort(list.begin(), list.end()); - fmt::print("{}\n", fmt::join(list, "\n")); - std::exit(EXIT_SUCCESS); + fmt::print("{}", fmt::join(list, "\n")); + fmt::print("\n"); } +// Print all info modules you can put in $<>, then exit successfully +static void modules_list() +{ + fmt::print("{}", R"(=============================================================================================== +| Syntax: | +| when "[NO QUERY]" is present, it means you should call only the children modules (if any) | +| | +| module - Root module description [NO QUERY] | +| foo - Submodule with no children description | +| subfoo - Submodule with children description | +| subbar - Submodule children of "subfoo" | +=============================================================================================== +)"); + + for (const module_t& module : cfGetModules()) + { + std::vector<std::string> parts; + + // Split name into parts (e.g., "os.name.pretty" -> ["os", "name", "pretty"]) + size_t start = 0, end = module.name.find('.'); + bool new_module = true; + while (end != std::string::npos) + { + new_module = false; + parts.push_back(module.name.substr(start, end - start)); + start = end + 1; + end = module.name.find('.', start); + } + parts.push_back(module.name.substr(start)); + if (new_module) + fmt::print("\n"); + + // Generate indentation + for (size_t depth = 0; depth < parts.size(); ++depth) + { + if (depth == parts.size() - 1) + { + if (new_module) + fmt::print("{} - {}", parts[depth], module.description); + else + fmt::print("{:<6} \t- {}", parts[depth], module.description); + + if (!module.handler) + fmt::print(" [NO QUERY]"); + } + else + { + fmt::print(" "); + } + } + + fmt::print("\n"); + } +} + +// clang-format off // Return true if optarg says something true static bool str_to_bool(const std::string_view str) { return (str == "true" || str == "1" || str == "enable"); } -// clang-format off // parseargs() but only for parsing the user config path trough args // and so we can directly construct Config -static std::string parse_config_path(int argc, char* argv[], const std::string& configDir) +static std::filesystem::path parse_config_path(int argc, char* argv[], const std::filesystem::path &configDir) { int opt = 0; int option_index = 0; @@ -595,10 +252,10 @@ static std::string parse_config_path(int argc, char* argv[], const std::string& } } - return configDir + "/config.toml"; + return configDir / "config.toml"; } -static bool parseargs(int argc, char* argv[], Config& config, const std::string_view configFile) +static bool parseargs(int argc, char* argv[], Config& config, const std::filesystem::path& configFile) { int opt = 0; int option_index = 0; @@ -644,7 +301,7 @@ static bool parseargs(int argc, char* argv[], Config& config, const std::string_ }; /* parse operation */ - optind = 0; + optind = 1; while ((opt = getopt_long(argc, argv, optstring, opts, &option_index)) != -1) { switch (opt) @@ -659,27 +316,27 @@ static bool parseargs(int argc, char* argv[], Config& config, const std::string_ case 'h': help(); break; case 'l': - modules_list(); break; + display_modules = true; break; case 'w': explain_how_this_works(); break; case "list-logos"_fnv1a16: - list_logos(config.data_dir+"/ascii"); break; + display_list_logos = true; break; case 'f': - config.overrides["gui.font"] = {.value_type = STR, .string_value = optarg}; break; + config.overrideOption("gui.font", optarg); break; case 'o': - config.overrides["config.offset"] = {.value_type = STR, .string_value = optarg}; break; + config.overrideOption("config.offset", optarg); break; case 'C': // we have already did it in parse_config_path() break; case 'D': - config.overrides["config.data-dir"] = {.value_type = STR, .string_value = optarg}; break; + config.overrideOption("config.data-dir", optarg); break; case 'd': config.args_custom_distro = str_tolower(optarg); break; case 'm': config.args_layout.push_back(optarg); break; case 'p': - config.overrides["config.logo-position"] = {.value_type = STR, .string_value = optarg}; break; + config.overrideOption("config.logo-position", optarg); break; case 's': - config.overrides["config.source-path"] = {.value_type = STR, .string_value = optarg}; break; + config.overrideOption("config.source-path", optarg); break; case 'i': config.args_image_backend = optarg; break; case 'O': @@ -687,7 +344,7 @@ static bool parseargs(int argc, char* argv[], Config& config, const std::string_ case 'N': config.args_disable_colors = true; break; case 'a': - config.overrides["config.ascii-logo-type"] = {.value_type = STR, .string_value = optarg}; break; + config.overrideOption("config.ascii-logo-type", optarg); break; case 'n': config.args_disable_source = true; break; case 'L': @@ -697,13 +354,13 @@ static bool parseargs(int argc, char* argv[], Config& config, const std::string_ config.args_disallow_commands = true; break; case "logo-padding-top"_fnv1a16: - config.overrides["config.logo-padding-top"] = {.value_type = INT, .int_value = std::stoi(optarg)}; break; + config.overrideOption("config.logo-padding-top", std::stoi(optarg)); break; case "logo-padding-left"_fnv1a16: - config.overrides["config.logo-padding-left"] = {.value_type = INT, .int_value = std::stoi(optarg)}; break; + config.overrideOption("config.logo-padding-left", std::stoi(optarg)); break; case "layout-padding-top"_fnv1a16: - config.overrides["config.layout-padding-top"] = {.value_type = INT, .int_value = std::stoi(optarg)}; break; + config.overrideOption("config.layout-padding-top", std::stoi(optarg)); break; case "loop-ms"_fnv1a16: config.loop_ms = std::stoul(optarg); break; @@ -712,22 +369,22 @@ static bool parseargs(int argc, char* argv[], Config& config, const std::string_ config.addAliasColors(optarg); break; case "sep-reset"_fnv1a16: - config.overrides["config.sep-reset"] = {.value_type = STR, .string_value = optarg}; break; + config.overrideOption("config.sep-reset", optarg); break; case "title-sep"_fnv1a16: - config.overrides["config.title-sep"] = {.value_type = STR, .string_value = optarg}; break; + config.overrideOption("config.title-sep", optarg); break; case "bg-image"_fnv1a16: - config.overrides["gui.bg-image"] = {.value_type = STR, .string_value = optarg}; break; + config.overrideOption("gui.bg-image", optarg); break; case "gtk-css"_fnv1a16: - config.overrides["gui.gtk-css"] = {.value_type = STR, .string_value = optarg}; break; + config.overrideOption("gui.gtk-css", optarg); break; case "wrap-lines"_fnv1a16: if (OPTIONAL_ARGUMENT_IS_PRESENT) - config.overrides["config.wrap-lines"] = {.value_type = BOOL, .bool_value = str_to_bool(optarg)}; + config.overrideOption("config.wrap-lines", str_to_bool(optarg)); else - config.overrides["config.wrap-lines"] = {.value_type = BOOL, .bool_value = true}; + config.overrideOption("config.wrap-lines", true); break; case "debug"_fnv1a16: @@ -746,9 +403,9 @@ static bool parseargs(int argc, char* argv[], Config& config, const std::string_ case "sep-reset-after"_fnv1a16: if (OPTIONAL_ARGUMENT_IS_PRESENT) - config.overrides["config.sep-reset-after"] = {.value_type = BOOL, .bool_value = str_to_bool(optarg)}; + config.overrideOption("config.sep-reset-after", str_to_bool(optarg)); else - config.overrides["config.sep-reset-after"] = {.value_type = BOOL, .bool_value = true}; + config.overrideOption("config.sep-reset-after", true); break; default: @@ -756,6 +413,13 @@ static bool parseargs(int argc, char* argv[], Config& config, const std::string_ } } + config.overrideOption("intern.args.print-logo-only", config.args_print_logo_only); + config.overrideOption("intern.args.disable-logo", config.args_disable_source); + config.overrideOption("intern.args.disallow-commands", config.args_disallow_commands); + config.overrideOption("intern.args.custom-distro", config.args_custom_distro); + config.overrideOption("intern.args.image-backend", config.args_image_backend); + config.overrideOption("intern.args.disable-colors", config.args_disable_colors); + return true; } @@ -782,40 +446,77 @@ static void localize(void) #endif } -int main(int argc, char *argv[]) +// clang-format on +int main(int argc, char* argv[]) { + const std::filesystem::path& configDir = getConfigDir(); + const std::filesystem::path& configFile = parse_config_path(argc, argv, configDir); -#ifdef VENDOR_TEST - // test - fmt::println("=== VENDOR TEST! ==="); + localize(); - fmt::println("Intel: {}", binarySearchPCIArray("8086")); - fmt::println("AMD: {}", binarySearchPCIArray("1002")); - fmt::println("NVIDIA: {}", binarySearchPCIArray("10de")); -#endif + Config config(configFile, configDir); + if (!parseargs(argc, argv, config, configFile)) + return 1; + config.loadConfigFile(configFile); + + std::vector<void*> plugins_handle; + const std::filesystem::path pluginDir = configDir / "plugins"; + std::filesystem::create_directories(pluginDir); + for (const auto& entry : std::filesystem::recursive_directory_iterator{ pluginDir }) + { + if (entry.is_regular_file() && entry.path().has_extension() && entry.path().extension() == LIBRARY_EXTENSION){} + else {continue;} -#ifdef DEVICE_TEST - // test - fmt::println("=== DEVICE TEST! ==="); + debug("loading plugin at {}!", entry.path().string()); - fmt::println("an Intel iGPU: {}", binarySearchPCIArray("8086", "0f31")); - fmt::println("RX 7700 XT: {}", binarySearchPCIArray("1002", "747e")); - fmt::println("GTX 1650: {}", binarySearchPCIArray("10de", "1f0a")); - fmt::println("?: {}", binarySearchPCIArray("1414", "0006")); -#endif + void* handle = LOAD_LIBRARY(std::filesystem::absolute(entry.path()).c_str()); + if (!handle) + { + // dlerror() is pretty formatted + warn("Failed to load plugin at '{}': {}", entry.path().string(), dlerror()); + dlerror(); + continue; + } + + LOAD_LIB_SYMBOL(handle, void, start, void*, const ConfigBase&); + if (dlerror()) + { + warn("Failed to load plugin at '{}': Missing function 'start'", entry.path().string()); + dlclose(handle); + continue; + } - // clang-format on - colors_t colors; + start(handle, config); + plugins_handle.push_back(handle); + } - const std::string& configDir = getConfigDir(); - const std::string& configFile = parse_config_path(argc, argv, configDir); + // The "conflicting" modules won't be overwritten by the main ones. + // First the external modules, then the core ones. + core_plugins_start(config); - localize(); + if (display_modules) + { + modules_list(); + return 0; + } + else if (display_list_logos) + { + list_logos(config.data_dir + "/ascii"); + return 0; + } - Config config(configFile, configDir); - if (!parseargs(argc, argv, config, configFile)) - return 1; - config.loadConfigFile(configFile, colors); + const std::vector<module_t>& modules = cfGetModules(); + moduleMap_t moduleMap; + + debug("modules count: {}", modules.size()); + for (const module_t& module : modules) + { + debug("adding module {} (has handler: {})", module.name, module.handler != NULL); + if (!module.handler) + continue; + + moduleMap.emplace(module.name, module); + } is_live_mode = (config.loop_ms >= 200); @@ -844,7 +545,7 @@ int main(int argc, char *argv[]) if (!std::filesystem::exists(path) && !config.args_disable_source) { - path = std::filesystem::temp_directory_path() / "customfetch_ascii_logo-XXXXXX"; + path = std::filesystem::temp_directory_path() / "customfetch_ascii_logo-XXXXXX"; Display::ascii_logo_fd = mkstemp(path.data()); if (Display::ascii_logo_fd < 0) die("Failed to create temp path at {}: {}", path, strerror(errno)); @@ -853,7 +554,7 @@ int main(int argc, char *argv[]) #if GUI_APP const auto& app = Gtk::Application::create("org.toni.customfetch"); - GUI::Window window(config, colors, path); + GUI::Window window(config, path, moduleMap); return app->run(window); #endif // GUI_APP @@ -889,18 +590,33 @@ int main(int argc, char *argv[]) write(STDOUT_FILENO, "\33[H\33[2J", 7); fmt::print("\033[0;0H"); - Display::display(Display::render(config, colors, false, path)); + Display::display(Display::render(config, false, path, moduleMap)); std::this_thread::sleep_for(sleep_ms); } } else { - Display::display(Display::render(config, colors, false, path)); + Display::display(Display::render(config, false, path, moduleMap)); } // enable both of them again if (!config.wrap_lines) enable_cursor(); + core_plugins_finish(); + for (void* handle : plugins_handle) + { + LOAD_LIB_SYMBOL(handle, void, finish, void*); + if (dlerror()) + { + dlclose(handle); + continue; + } + + finish(handle); + UNLOAD_LIBRARY(handle); + } + plugins_handle.clear(); + return 0; } diff --git a/src/parse.cpp b/src/parse.cpp deleted file mode 100644 index 15286dc7..00000000 --- a/src/parse.cpp +++ /dev/null @@ -1,1729 +0,0 @@ -/* - * Copyright 2025 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "parse.hpp" - -#include <unistd.h> - -#include <algorithm> -#include <array> -#include <chrono> -#include <cstdint> -#include <cstdlib> -#include <ios> -#include <optional> -#include <sstream> -#include <string> -#include <string_view> -#include <vector> - -#include "config.hpp" -#include "fmt/color.h" -#include "fmt/format.h" -#include "fmt/ranges.h" -#include "query.hpp" -#include "switch_fnv1a.hpp" -#include "util.hpp" - -class Parser -{ -public: - Parser(const std::string_view src, std::string& pureOutput) : src{ src }, pureOutput{ pureOutput } {} - - bool try_read(const char c) - { - if (is_eof()) - return false; - - if (src[pos] == c) - { - ++pos; - return true; - } - - return false; - } - - char read_char(const bool add_pureOutput = false) - { - if (is_eof()) - return 0; - - if (add_pureOutput) - pureOutput += src[pos]; - - ++pos; - return src[pos - 1]; - } - - bool is_eof() - { return pos >= src.length(); } - - void rewind(const size_t count = 1) - { pos -= std::min(pos, count); } - - const std::string_view src; - std::string& pureOutput; - size_t dollar_pos = 0; - size_t pos = 0; -}; - -// declarations of static members in query.hpp -Query::System::System_t Query::System::m_system_infos; -Query::Theme::Theme_t Query::Theme::m_theme_infos; -Query::User::User_t Query::User::m_users_infos; -Query::Battery::Battery_t Query::Battery::m_battery_infos; -Query::CPU::CPU_t Query::CPU::m_cpu_infos; -Query::RAM::RAM_t Query::RAM::m_memory_infos; -Query::GPU::GPU_t Query::GPU::m_gpu_infos; -Query::Disk::Disk_t Query::Disk::m_disk_infos; - -struct utsname Query::System::m_uname_infos; -struct passwd* Query::User::m_pPwd; -unsigned long Query::System::m_uptime; - -bool Query::System::m_bInit = false; -bool Query::RAM::m_bInit = false; -bool Query::CPU::m_bInit = false; -bool Query::User::m_bInit = false; -bool Query::Battery::m_bInit = false; -bool Query::User::m_bDont_query_dewm = false; - -// useless useful tmp string for parse() without using the original -// pureOutput -std::string _; - -#if GUI_APP -// Get span tags from an ANSI escape color such as \e[0;31m -// @param noesc_str The ansi color without \\e[ or \033[ -// @param colors The colors struct we'll look at -// @return An array of 3 span tags elements in the follow: color, weight, type -static std::array<std::string, 3> get_ansi_color(const std::string_view noesc_str, const colors_t& colors) -{ - const size_t first_m = noesc_str.rfind('m'); - if (first_m == std::string::npos) - die(_("Parser: failed to parse layout/ascii art: missing 'm' while using ANSI color escape code in '{}'"), noesc_str); - - std::string col {noesc_str.data()}; - col.erase(first_m); // 1;42 - - std::string weight {hasStart(col, "1;") ? "bold" : "normal"}; - std::string type {"fgcolor"}; // either fgcolor or bgcolor - - if (hasStart(col, "1;") || hasStart(col, "0;")) - col.erase(0, 2); - - debug("col = {}", col); - const int n = std::stoi(col); - - if ((n >= 100 && n <= 107) || (n >= 40 && n <= 47)) - type = "bgcolor"; - - // last number - // clang-format off - switch (col.back()) - { - case '0': col = colors.gui_black; break; - case '1': col = colors.gui_red; break; - case '2': col = colors.gui_green; break; - case '3': col = colors.gui_yellow; break; - case '4': col = colors.gui_blue; break; - case '5': col = colors.gui_magenta; break; - case '6': col = colors.gui_cyan; break; - case '7': col = colors.gui_white; break; - } - - if (col.at(0) != '#') - col.erase(0, col.find('#')); - - if ((n >= 100 && n <= 107) || (n >= 90 && n <= 97)) - { - const fmt::rgb color = hexStringToColor(col); - const uint r = color.r * 0.65f + 0xff * 0.35f; - const uint b = color.b * 0.65f + 0xff * 0.35f; - const uint g = color.g * 0.65f + 0xff * 0.35f; - const uint result = (r << 16) | (g << 8) | (b); - - std::stringstream ss; - ss << std::hex << result; - col = ss.str(); - col.insert(0, "#"); - } - - return { col, weight, type }; - // clang-format on -} - -// Convert an ANSI escape RGB color, such as \e[38;2;132;042;231m -// into an hex color string -// @param noesc_str The ansi color without \\e[ or \033[ -// @return The hex equivalent string -static std::string convert_ansi_escape_rgb(const std::string_view noesc_str) -{ - if (std::count(noesc_str.begin(), noesc_str.end(), ';') < 4) - die(_("ANSI escape code color '\\e[{}' should have an rgb type value\n" - "e.g \\e[38;2;255;255;255m"), - noesc_str); - - if (noesc_str.rfind('m') == std::string::npos) - die(_("Parser: failed to parse layout/ascii art: missing 'm' while using ANSI color escape code in '\\e[{}'"), - noesc_str); - - const std::vector<std::string>& rgb_str = split(noesc_str.substr(5), ';'); - - const uint r = std::stoul(rgb_str.at(0)); - const uint g = std::stoul(rgb_str.at(1)); - const uint b = std::stoul(rgb_str.at(2)); - const uint result = (r << 16) | (g << 8) | (b); - - std::stringstream ss; - ss << std::hex << result; - return ss.str(); -} -#endif - -std::string parse(const std::string& input, std::string& _, parse_args_t& parse_args) -{ - return parse(input, parse_args.systemInfo, _, parse_args.layout, parse_args.tmp_layout, - parse_args.config, parse_args.colors, parse_args.parsingLayout, parse_args.no_more_reset); -} - -std::string parse(const std::string& input, parse_args_t& parse_args) -{ - return parse(input, parse_args.systemInfo, parse_args.pureOutput, parse_args.layout, parse_args.tmp_layout, - parse_args.config, parse_args.colors, parse_args.parsingLayout, parse_args.no_more_reset); -} - -std::string get_and_color_percentage(const float n1, const float n2, parse_args_t& parse_args, - const bool invert) -{ - const Config& config = parse_args.config; - const float result = n1 / n2 * static_cast<float>(100); - - std::string color; - if (!invert) - { - if (result <= 45) - color = "${" + config.percentage_colors.at(0) + "}"; - else if (result <= 80) - color = "${" + config.percentage_colors.at(1) + "}"; - else - color = "${" + config.percentage_colors.at(2) + "}"; - } - else - { - if (result <= 45) - color = "${" + config.percentage_colors.at(2) + "}"; - else if (result <= 80) - color = "${" + config.percentage_colors.at(1) + "}"; - else - color = "${" + config.percentage_colors.at(0) + "}"; - } - - return parse(fmt::format("{}{:.2f}%${{0}}", color, result), _, parse_args); -} - -std::string getInfoFromName(const systemInfo_t& systemInfo, const std::string_view moduleName, - const std::string_view moduleMemberName) -{ - if (const auto& it1 = systemInfo.find(moduleName.data()); it1 != systemInfo.end()) - { - if (const auto& it2 = it1->second.find(moduleMemberName.data()); it2 != it1->second.end()) - { - const variant& result = it2->second; - - if (std::holds_alternative<std::string>(result)) - return std::get<std::string>(result); - - else if (std::holds_alternative<double>(result)) - return fmt::format("{:.2f}", (std::get<double>(result))); - - else - return fmt::to_string(std::get<size_t>(result)); - } - } - - return "(unknown/invalid module)"; -} - -std::string parse(Parser& parser, parse_args_t& parse_args, const bool evaluate = true, const char until = 0); - -std::optional<std::string> parse_conditional_tag(Parser& parser, parse_args_t& parse_args, const bool evaluate) -{ - if (!parser.try_read('[')) - return {}; - - const std::string& condA = parse(parser, parse_args, evaluate, ','); - const std::string& condB = parse(parser, parse_args, evaluate, ','); - - const bool cond = (condA == condB); - - const std::string& condTrue = parse(parser, parse_args, cond, ','); - const std::string& condFalse = parse(parser, parse_args, !cond, ']'); - - return cond ? condTrue : condFalse; -} - -std::optional<std::string> parse_command_tag(Parser& parser, parse_args_t& parse_args, const bool evaluate) -{ - if (!parser.try_read('(')) - return {}; - - std::string command = parse(parser, parse_args, evaluate, ')'); - - if (!evaluate) - return {}; - - if (parse_args.config.args_disallow_commands) - die(_("Trying to execute command $({}) but --disallow-command-tag is set"), command); - - const bool removetag = (command.front() == '!'); - if (removetag) - command.erase(0, 1); - - const std::string& cmd_output = read_shell_exec(command); - if (!parse_args.parsingLayout && !removetag && parser.dollar_pos != std::string::npos) - parse_args.pureOutput.replace(parser.dollar_pos, command.length() + "$()"_len, cmd_output); - - return cmd_output; -} - -std::optional<std::string> parse_color_tag(Parser& parser, parse_args_t& parse_args, const bool evaluate) -{ - if (!parser.try_read('{')) - return {}; - - std::string color = parse(parser, parse_args, evaluate, '}'); - - if (!evaluate) - return {}; - - std::string output; - const Config& config = parse_args.config; - const colors_t& colors = parse_args.colors; - const size_t taglen = color.length() + "${}"_len; - - const std::string endspan {!parse_args.firstrun_clr ? "</span>" : ""}; - - if (config.args_disable_colors) - { - if (parser.dollar_pos != std::string::npos) - parse_args.pureOutput.erase(parser.dollar_pos, taglen); - return ""; - } - - // if at end there a '$', it will make the end output "$</span>" and so it will confuse - // addValueFromModule() and so let's make it "$ </span>". this is geniunenly stupid -#if GUI_APP - if (output[0] == '$') - output += ' '; -#endif - - if (!config.colors_name.empty()) - { - const auto& it_name = std::find(config.colors_name.begin(), config.colors_name.end(), color); - if (it_name != config.colors_name.end()) - { - const size_t index = std::distance(config.colors_name.begin(), it_name); - color = config.colors_value.at(index); - } - } - - static std::vector<std::string> auto_colors; - if (hasStart(color, "auto")) - { - int ver = color.length() > 4 ? std::stoi(color.substr(4)) - 1 : 0; - - if (auto_colors.empty()) - auto_colors.push_back(NOCOLOR_BOLD); - - if (ver < 0 || static_cast<size_t>(ver) >= auto_colors.size()) - ver = 0; - - color = auto_colors.at(ver); - } - -#if GUI_APP - if (color == "1") - { - output += endspan + "<span weight='bold'>"; - } - else if (color == "0") - { - output += endspan + "<span>"; - } -#else - if (color == "1") - { - output += NOCOLOR_BOLD; - } - else if (color == "0") - { - output += NOCOLOR; - } -#endif - else - { - std::string str_clr; - #if GUI_APP - switch (fnv1a16::hash(color)) - { - case "black"_fnv1a16: str_clr = colors.gui_black; break; - case "red"_fnv1a16: str_clr = colors.gui_red; break; - case "blue"_fnv1a16: str_clr = colors.gui_blue; break; - case "green"_fnv1a16: str_clr = colors.gui_green; break; - case "cyan"_fnv1a16: str_clr = colors.gui_cyan; break; - case "yellow"_fnv1a16: str_clr = colors.gui_yellow; break; - case "magenta"_fnv1a16: str_clr = colors.gui_magenta; break; - case "white"_fnv1a16: str_clr = colors.gui_white; break; - default: str_clr = color; break; - } - - const size_t pos = str_clr.rfind('#'); - if (pos != std::string::npos) - { - std::string tagfmt = "span "; - const std::string& opt_clr = str_clr.substr(0, pos); - - size_t argmode_pos = 0; - const auto& append_argmode = [&](const std::string_view fmt, const std::string_view mode) -> size_t { - if (opt_clr.at(argmode_pos + 1) == '(') - { - const size_t closebrak = opt_clr.find(')', argmode_pos); - if (closebrak == std::string::npos) - die(_("'{}' mode in color '{}' doesn't have close bracket"), mode, str_clr); - - const std::string& value = opt_clr.substr(argmode_pos + 2, closebrak - argmode_pos - 2); - tagfmt += fmt.data() + value + "' "; - - return closebrak; - } - return 0; - }; - - bool bgcolor = false; - for (size_t i = 0; i < opt_clr.length(); ++i) - { - switch (opt_clr.at(i)) - { - case 'b': - bgcolor = true; - tagfmt += "bgcolor='" + str_clr.substr(pos) + "' "; - break; - case '!': tagfmt += "weight='bold' "; break; - case 'u': tagfmt += "underline='single' "; break; - case 'i': tagfmt += "style='italic' "; break; - case 'o': tagfmt += "overline='single' "; break; - case 's': tagfmt += "strikethrough='true' "; break; - - case 'a': - argmode_pos = i; - i += append_argmode("fgalpha='", "fgalpha"); - break; - - case 'A': - argmode_pos = i; - i += append_argmode("bgalpha='", "bgalpha"); - break; - - case 'L': - argmode_pos = i; - i += append_argmode("underline='", "underline option"); - break; - - case 'U': - argmode_pos = i; - i += append_argmode("underline_color='", "colored underline"); - break; - - case 'B': - argmode_pos = i; - i += append_argmode("bgcolor='", "bgcolor"); - break; - - case 'w': - argmode_pos = i; - i += append_argmode("weight='", "font weight style"); - break; - - case 'O': - argmode_pos = i; - i += append_argmode("overline_color='", "overline color"); - break; - - case 'S': - argmode_pos = i; - i += append_argmode("strikethrough_color='", "color of strikethrough line"); - break; - } - } - - if (!bgcolor) - tagfmt += "fgcolor='" + str_clr.substr(pos) + "' "; - - tagfmt.pop_back(); - output += endspan + "<" + tagfmt + ">"; - } - - // "\\e" is for checking in the ascii_art, \033 in the config - else if (hasStart(str_clr, "\\e") || hasStart(str_clr, "\033")) - { - const std::string& noesc_str = hasStart(str_clr, "\033") ? str_clr.substr(2) : str_clr.substr(3); - debug("noesc_str = {}", noesc_str); - - if (hasStart(noesc_str, "38;2;") || hasStart(noesc_str, "48;2;")) - { - const std::string& hexclr = convert_ansi_escape_rgb(noesc_str); - output += - fmt::format("{}<span {}gcolor='#{}'>", endspan, hasStart(noesc_str, "38") ? 'f' : 'b', hexclr); - } - else if (hasStart(noesc_str, "38;5;") || hasStart(noesc_str, "48;5;")) - { - die(_("256 true color '{}' works only in terminal"), noesc_str); - } - else - { - const std::array<std::string, 3>& clrs = get_ansi_color(noesc_str, colors); - const std::string_view color = clrs.at(0); - const std::string_view weight = clrs.at(1); - const std::string_view type = clrs.at(2); - output += fmt::format("{}<span {}='{}' weight='{}'>", endspan, type, color, weight); - } - } - - else - { - error(_("PARSER: failed to parse line with color '{}'"), str_clr); - if (!parse_args.parsingLayout && parser.dollar_pos != std::string::npos) - parse_args.pureOutput.erase(parser.dollar_pos, taglen); - return output; - } - - // #if !GUI_APP - #else - switch (fnv1a16::hash(color)) - { - case "black"_fnv1a16: str_clr = colors.black; break; - case "red"_fnv1a16: str_clr = colors.red; break; - case "blue"_fnv1a16: str_clr = colors.blue; break; - case "green"_fnv1a16: str_clr = colors.green; break; - case "cyan"_fnv1a16: str_clr = colors.cyan; break; - case "yellow"_fnv1a16: str_clr = colors.yellow; break; - case "magenta"_fnv1a16: str_clr = colors.magenta; break; - case "white"_fnv1a16: str_clr = colors.white; break; - default: str_clr = color; break; - } - - const size_t pos = str_clr.rfind('#'); - if (pos != std::string::npos) - { - const std::string& opt_clr = str_clr.substr(0, pos); - - fmt::text_style style; - - const auto& skip_gui_argmode = [&opt_clr](const size_t index) -> size_t { - if (opt_clr.at(index + 1) == '(') - { - const size_t closebrak = opt_clr.find(')', index); - if (closebrak == std::string::npos) - return 0; - - return closebrak; - } - return 0; - }; - - bool bgcolor = false; - for (size_t i = 0; i < opt_clr.length(); ++i) - { - switch (opt_clr.at(i)) - { - case 'b': - bgcolor = true; - append_styles(style, fmt::bg(hexStringToColor(str_clr.substr(pos)))); - break; - case '!': append_styles(style, fmt::emphasis::bold); break; - case 'u': append_styles(style, fmt::emphasis::underline); break; - case 'i': append_styles(style, fmt::emphasis::italic); break; - case 'l': append_styles(style, fmt::emphasis::blink); break; - case 's': append_styles(style, fmt::emphasis::strikethrough); break; - - case 'U': - case 'B': - case 'S': - case 'a': - case 'w': - case 'O': - case 'A': - case 'L': i += skip_gui_argmode(i); break; - } - } - - if (!bgcolor) - append_styles(style, fmt::fg(hexStringToColor(str_clr.substr(pos)))); - - // you can't fmt::format(style, ""); ughh - if (style.has_emphasis()) - { - fmt::detail::ansi_color_escape<char> emph(style.get_emphasis()); - output += emph.begin(); - } - if (style.has_background() || style.has_foreground()) - { - const uint32_t rgb_num = bgcolor ? style.get_background().value.rgb_color : style.get_foreground().value.rgb_color; - fmt::rgb rgb(rgb_num); - fmt::detail::ansi_color_escape<char> ansi(rgb, bgcolor ? "\x1B[48;2;" : "\x1B[38;2;"); - output += ansi.begin(); - } - } - - // "\\e" is for checking in the ascii_art, \033 in the config - else if (hasStart(str_clr, "\\e") || hasStart(str_clr, "\033")) - { - output += "\033["; - output += hasStart(str_clr, "\033") ? str_clr.substr(2) : str_clr.substr(3); - } - - else - { - error(_("PARSER: failed to parse line with color '{}'"), str_clr); - if (!parse_args.parsingLayout && parser.dollar_pos != std::string::npos) - parse_args.pureOutput.erase(parser.dollar_pos, taglen); - return output; - } - #endif - - if (!parse_args.parsingLayout && - std::find(auto_colors.begin(), auto_colors.end(), color) == auto_colors.end()) - auto_colors.push_back(color); - } - - if (!parse_args.parsingLayout && parser.dollar_pos != std::string::npos) - parse_args.pureOutput.erase(parser.dollar_pos, taglen); - - parse_args.firstrun_clr = false; - - return output; -} - -std::optional<std::string> parse_info_tag(Parser& parser, parse_args_t& parse_args, const bool evaluate) -{ - if (!parser.try_read('<')) - return {}; - - const std::string& module = parse(parser, parse_args, evaluate, '>'); - - if (!evaluate) - return {}; - - const size_t dot_pos = module.find('.'); - if (dot_pos == module.npos) - { - addValueFromModule(module, parse_args); - const std::string& info = getInfoFromName(parse_args.systemInfo, module, "module-" + module); - - if (parser.dollar_pos != std::string::npos) - parse_args.pureOutput.replace(parser.dollar_pos, module.length() + "$<>"_len, info); - - return info; - } - - const std::string& moduleName = module.substr(0, dot_pos); - const std::string& moduleMemberName = module.substr(dot_pos + 1); - addValueFromModuleMember(moduleName, moduleMemberName, parse_args); - - const std::string& info = getInfoFromName(parse_args.systemInfo, moduleName, moduleMemberName); - - if (parser.dollar_pos != std::string::npos) - parse_args.pureOutput.replace(parser.dollar_pos, module.length() + "$<>"_len, info); - return info; -} - -std::optional<std::string> parse_perc_tag(Parser& parser, parse_args_t& parse_args, const bool evaluate) -{ - if (!parser.try_read('%')) - return {}; - - const std::string& command = parse(parser, parse_args, evaluate, '%'); - - if (!evaluate) - return {}; - - const size_t comma_pos = command.find(','); - if (comma_pos == std::string::npos) - die(_("percentage tag '{}' doesn't have a comma for separating the 2 numbers"), command); - - const bool invert = (command.front() == '!'); - - const float n1 = std::stof(parse(command.substr(invert ? 1 : 0, comma_pos), _, parse_args)); - const float n2 = std::stof(parse(command.substr(comma_pos + 1), _, parse_args)); - - return get_and_color_percentage(n1, n2, parse_args, invert); -} - -std::optional<std::string> parse_tags(Parser& parser, parse_args_t& parse_args, const bool evaluate) -{ - if (!parser.try_read('$')) - return {}; - - if (parser.dollar_pos != std::string::npos) - parser.dollar_pos = parser.pureOutput.find('$', parser.dollar_pos); - - if (const auto& color_tag = parse_color_tag(parser, parse_args, evaluate)) - return color_tag; - - if (const auto& module_tag = parse_info_tag(parser, parse_args, evaluate)) - return module_tag; - - if (const auto& command_tag = parse_command_tag(parser, parse_args, evaluate)) - return command_tag; - - if (const auto& ifTag = parse_conditional_tag(parser, parse_args, evaluate)) - return ifTag; - - if (const auto& perc_tag = parse_perc_tag(parser, parse_args, evaluate)) - return perc_tag; - - parser.rewind(); - return {}; -} - -std::string parse(Parser& parser, parse_args_t& parse_args, const bool evaluate, const char until) -{ - std::string result; - - while (until == 0 ? !parser.is_eof() : !parser.try_read(until)) - { - if (until != 0 && parser.is_eof()) - { - error(_("PARSER: Missing tag close bracket {} in string '{}'"), until, parser.src); - return result; - } - - if (parser.try_read('\\')) - { - result += parser.read_char(until == 0); - } - else if (const auto& tagStr = parse_tags(parser, parse_args, evaluate)) - { - result += *tagStr; - } - else - { - result += parser.read_char(until == 0); - } - } - - return result; -} - -std::string parse(std::string input, systemInfo_t& systemInfo, std::string& pureOutput, std::vector<std::string>& layout, - std::vector<std::string>& tmp_layout, const Config& config, const colors_t& colors, const bool parsingLayout, bool& no_more_reset) -{ - if (!config.sep_reset.empty() && parsingLayout && !no_more_reset) - { - if (config.sep_reset_after) - replace_str(input, config.sep_reset, config.sep_reset + "${0}"); - else - replace_str(input, config.sep_reset, "${0}" + config.sep_reset); - - no_more_reset = true; - } - - parse_args_t parse_args{ systemInfo, pureOutput, layout, tmp_layout, config, colors, parsingLayout, true, no_more_reset }; - Parser parser{ input, pureOutput }; - - std::string ret{ parse(parser, parse_args) }; - -#if GUI_APP - if (!parse_args.firstrun_clr) - ret += "</span>"; - - replace_str(parse_args.pureOutput, " ", " "); - - // escape pango markup - // https://gitlab.gnome.org/GNOME/glib/-/blob/main/glib/gmarkup.c#L2150 - // workaround: just put "\<" or "\&" in the config, e.g "$<os.kernel> \<- Kernel" - replace_str(ret, "\\<", "<"); - replace_str(ret, "\\&", "&"); - replace_str(ret, "&", "&"); -#else - replace_str(ret, "\\<", "<"); - replace_str(ret, "\\&", "&"); -#endif - - return ret; -} - -static std::string get_auto_uptime(const std::uint16_t days, const std::uint16_t hours, const std::uint16_t mins, - const std::uint16_t secs, const Config& config) -{ - if (days == 0 && hours == 0 && mins == 0) - return fmt::format("{}{}", secs, config.uptime_s_fmt); - - std::string ret; - - if (days > 0) - ret += fmt::format("{}{}, ", days, config.uptime_d_fmt); - - if (hours > 0) - ret += fmt::format("{}{}, ", hours, config.uptime_h_fmt); - - if (mins > 0) - ret += fmt::format("{}{}, ", mins, config.uptime_m_fmt); - - ret.erase(ret.length() - 2); // the last ", " - - return ret; -} - -static std::string get_auto_gtk_format(const std::string_view gtk2, const std::string_view gtk3, - const std::string_view gtk4) -{ - if ((gtk2 != MAGIC_LINE && gtk3 != MAGIC_LINE && gtk4 != MAGIC_LINE)) - { - if (gtk2 == gtk3 && gtk2 == gtk4) - return fmt::format("{} [GTK2/3/4]", gtk4); - else if (gtk2 == gtk3) - return fmt::format("{} [GTK2/3], {} [GTK4]", gtk2, gtk4); - else if (gtk4 == gtk3) - return fmt::format("{} [GTK2], {} [GTK3/4]", gtk2, gtk4); - else - return fmt::format("{} [GTK2], {} [GTK3], {} [GTK4]", gtk2, gtk3, gtk4); - } - - else if (gtk3 != MAGIC_LINE && gtk4 != MAGIC_LINE) - { - if (gtk3 == gtk4) - return fmt::format("{} [GTK3/4]", gtk4); - else - return fmt::format("{} [GTK3], {} [GTK4]", gtk3, gtk4); - } - - else if (gtk2 != MAGIC_LINE && gtk3 != MAGIC_LINE) - { - if (gtk2 == gtk3) - return fmt::format("{} [GTK2/3]", gtk3); - else - return fmt::format("{} [GTK2], {} [GTK3]", gtk2, gtk3); - } - - else if (gtk4 != MAGIC_LINE) - return fmt::format("{} [GTK4]", gtk4); - else if (gtk3 != MAGIC_LINE) - return fmt::format("{} [GTK3]", gtk3); - else if (gtk2 != MAGIC_LINE) - return fmt::format("{} [GTK2]", gtk2); - - return MAGIC_LINE; -} - -static std::string prettify_term_name(const std::string_view term_name) -{ - switch (fnv1a16::hash(str_tolower(term_name.data()))) - { - case "gnome-terminal"_fnv1a16: - case "gnome terminal"_fnv1a16: return "GNOME Terminal"; - - case "gnome-console"_fnv1a16: - case "gnome console"_fnv1a16: return "GNOME console"; - } - return term_name.data(); -} - -static std::string prettify_de_name(const std::string_view de_name) -{ - switch (fnv1a16::hash(str_tolower(de_name.data()))) - { - case "kde"_fnv1a16: - case "plasma"_fnv1a16: - case "plasmashell"_fnv1a16: - case "plasmawayland"_fnv1a16: return "KDE Plasma"; - - case "gnome"_fnv1a16: - case "gnome-shell"_fnv1a16: return "GNOME"; - - case "xfce"_fnv1a16: - case "xfce4"_fnv1a16: - case "xfce4-session"_fnv1a16: return "Xfce4"; - - case "mate"_fnv1a16: - case "mate-session"_fnv1a16: return "Mate"; - - case "lxqt"_fnv1a16: - case "lxqt-session"_fnv1a16: return "LXQt"; - } - - return de_name.data(); -} - -systemInfo_t queried_gpus; -systemInfo_t queried_disks; -systemInfo_t queried_themes_names; -systemInfo_t queried_themes; - -// clang-format on -void addValueFromModuleMember(const std::string& moduleName, const std::string& moduleMemberName, - parse_args_t& parse_args) -{ -#define SYSINFO_INSERT(x) sysInfo.at(moduleName).insert({ moduleMemberName, variant(x) }) - - // just aliases for convention - const Config& config = parse_args.config; - systemInfo_t& sysInfo = parse_args.systemInfo; - - const auto& moduleMember_hash = fnv1a16::hash(moduleMemberName); - const std::uint16_t byte_unit = config.use_SI_unit ? 1000 : 1024; - constexpr std::array<std::string_view, 32> sorted_valid_prefixes = { "B", "EB", "EiB", "GB", "GiB", "kB", - "KiB", "MB", "MiB", "PB", "PiB", "TB", - "TiB", "YB", "YiB", "ZB", "ZiB" }; - - const auto& return_devided_bytes = [&sorted_valid_prefixes, &moduleMemberName](const double& amount) -> double { - const std::string& prefix = moduleMemberName.substr(moduleMemberName.find('-') + 1); - if (std::binary_search(sorted_valid_prefixes.begin(), sorted_valid_prefixes.end(), prefix)) - return devide_bytes(amount, prefix).num_bytes; - - return 0; - }; - - if (moduleName == "os") - { - Query::System query_system; - - const std::chrono::seconds uptime_secs(query_system.uptime()); - const std::chrono::minutes& uptime_mins = std::chrono::duration_cast<std::chrono::minutes>(uptime_secs); - const std::chrono::hours& uptime_hours = std::chrono::duration_cast<std::chrono::hours>(uptime_secs); - - // let's support a little of C++17 without any `#if __cpluscplus` stuff - const std::uint16_t uptime_days = uptime_secs.count() / (60 * 60 * 24); - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - switch (moduleMember_hash) - { - case "name"_fnv1a16: SYSINFO_INSERT(query_system.os_pretty_name()); break; - case "name_id"_fnv1a16: SYSINFO_INSERT(query_system.os_id()); break; - - case "uptime"_fnv1a16: - SYSINFO_INSERT(get_auto_uptime(uptime_days, uptime_hours.count() % 24, uptime_mins.count() % 60, - uptime_secs.count() % 60, config)); - break; - - case "uptime_secs"_fnv1a16: SYSINFO_INSERT(static_cast<size_t>(uptime_secs.count() % 60)); break; - case "uptime_mins"_fnv1a16: SYSINFO_INSERT(static_cast<size_t>(uptime_mins.count() % 60)); break; - case "uptime_hours"_fnv1a16: SYSINFO_INSERT(static_cast<size_t>(uptime_hours.count()) % 24); break; - case "uptime_days"_fnv1a16: SYSINFO_INSERT(static_cast<size_t>(uptime_days)); break; - - case "kernel"_fnv1a16: - SYSINFO_INSERT(query_system.kernel_name() + ' ' + query_system.kernel_version()); - break; - - case "kernel_name"_fnv1a16: SYSINFO_INSERT(query_system.kernel_name()); break; - case "kernel_version"_fnv1a16: SYSINFO_INSERT(query_system.kernel_version()); break; - case "packages"_fnv1a16: - case "pkgs"_fnv1a16: SYSINFO_INSERT(query_system.pkgs_installed(config)); break; - case "initsys_name"_fnv1a16: SYSINFO_INSERT(query_system.os_initsys_name()); break; - case "initsys_version"_fnv1a16: SYSINFO_INSERT(query_system.os_initsys_version()); break; - case "hostname"_fnv1a16: SYSINFO_INSERT(query_system.hostname()); break; - case "version_codename"_fnv1a16: SYSINFO_INSERT(query_system.os_version_codename()); break; - case "version_id"_fnv1a16: SYSINFO_INSERT(query_system.os_versionid()); break; - } - } - } - - else if (moduleName == "system") - { - Query::System query_system; - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - switch (moduleMember_hash) - { - case "host"_fnv1a16: - SYSINFO_INSERT(query_system.host_vendor() + ' ' + query_system.host_modelname() + ' ' + - query_system.host_version()); - break; - - case "host_name"_fnv1a16: SYSINFO_INSERT(query_system.host_modelname()); break; - case "host_vendor"_fnv1a16: SYSINFO_INSERT(query_system.host_vendor()); break; - case "host_version"_fnv1a16: SYSINFO_INSERT(query_system.host_version()); break; - case "arch"_fnv1a16: SYSINFO_INSERT(query_system.arch()); break; - } - } - } - - // clang-format on - else if (moduleName == "user") - { - Query::User query_user; - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - switch (moduleMember_hash) - { - case "name"_fnv1a16: SYSINFO_INSERT(query_user.name()); break; - - case "shell"_fnv1a16: - SYSINFO_INSERT(query_user.shell_name() + ' ' + query_user.shell_version(query_user.shell_name())); - break; - - case "shell_name"_fnv1a16: SYSINFO_INSERT(query_user.shell_name()); break; - case "shell_path"_fnv1a16: SYSINFO_INSERT(query_user.shell_path()); break; - case "shell_version"_fnv1a16: SYSINFO_INSERT(query_user.shell_version(query_user.shell_name())); break; - - case "de_name"_fnv1a16: - SYSINFO_INSERT(prettify_de_name( - query_user.de_name(query_user.m_bDont_query_dewm, query_user.term_name(), - query_user.wm_name(query_user.m_bDont_query_dewm, query_user.term_name())))); - break; - - case "de_version"_fnv1a16: - SYSINFO_INSERT(query_user.de_version( - query_user.de_name(query_user.m_bDont_query_dewm, query_user.term_name(), - query_user.wm_name(query_user.m_bDont_query_dewm, query_user.term_name())))); - break; - - case "wm_name"_fnv1a16: - SYSINFO_INSERT(query_user.wm_name(query_user.m_bDont_query_dewm, query_user.term_name())); - break; - - case "wm_version"_fnv1a16: - SYSINFO_INSERT(query_user.wm_version(query_user.m_bDont_query_dewm, query_user.term_name())); - break; - - case "terminal"_fnv1a16: - SYSINFO_INSERT(prettify_term_name(query_user.term_name()) + ' ' + - query_user.term_version(query_user.term_name())); - break; - - case "terminal_name"_fnv1a16: SYSINFO_INSERT(prettify_term_name(query_user.term_name())); break; - case "terminal_version"_fnv1a16: SYSINFO_INSERT(query_user.term_version(query_user.term_name())); break; - } - } - } - - else if (moduleName == "theme") - { - Query::Theme query_theme(queried_themes, config, false); - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - switch (moduleMember_hash) - { - case "cursor"_fnv1a16: - if (query_theme.cursor_size() == UNKNOWN) - SYSINFO_INSERT(query_theme.cursor()); - else - SYSINFO_INSERT(fmt::format("{} ({}px)", query_theme.cursor(), query_theme.cursor_size())); - break; - - case "cursor_name"_fnv1a16: SYSINFO_INSERT(query_theme.cursor()); break; - case "cursor_size"_fnv1a16: SYSINFO_INSERT(query_theme.cursor_size()); break; - } - } - } - - else if (moduleName == "theme-gsettings") - { - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - if (hasStart(moduleMemberName, "cursor")) - { - Query::Theme query_cursor(queried_themes, config, true); - switch (moduleMember_hash) - { - case "cursor"_fnv1a16: - if (query_cursor.cursor_size() == UNKNOWN) - SYSINFO_INSERT(query_cursor.cursor()); - else - SYSINFO_INSERT(fmt::format("{} ({}px)", query_cursor.cursor(), query_cursor.cursor_size())); - break; - case "cursor_name"_fnv1a16: SYSINFO_INSERT(query_cursor.cursor()); break; - case "cursor_size"_fnv1a16: SYSINFO_INSERT(query_cursor.cursor_size()); break; - } - } - else - { - Query::Theme query_theme(0, queried_themes, "gsettings", config, true); - switch (moduleMember_hash) - { - case "name"_fnv1a16: SYSINFO_INSERT(query_theme.gtk_theme()); break; - case "icons"_fnv1a16: SYSINFO_INSERT(query_theme.gtk_icon_theme()); break; - case "font"_fnv1a16: SYSINFO_INSERT(query_theme.gtk_font()); break; - } - } - } - } - - // clang-format off - else if (moduleName == "theme-gtk-all") - { - Query::Theme gtk2(2, queried_themes, "gtk2", config); - Query::Theme gtk3(3, queried_themes, "gtk3", config); - Query::Theme gtk4(4, queried_themes, "gtk4", config); - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - switch (moduleMember_hash) - { - case "name"_fnv1a16: SYSINFO_INSERT(get_auto_gtk_format(gtk2.gtk_theme(), gtk3.gtk_theme(), gtk4.gtk_theme())); break; - case "icons"_fnv1a16: SYSINFO_INSERT(get_auto_gtk_format(gtk2.gtk_icon_theme(), gtk3.gtk_icon_theme(), gtk4.gtk_icon_theme())); break; - case "font"_fnv1a16: SYSINFO_INSERT(get_auto_gtk_format(gtk2.gtk_font(), gtk3.gtk_font(), gtk4.gtk_font())); break; - } - } - } - - else if (hasStart(moduleName, "theme-gtk")) - { - const std::uint8_t ver = - static_cast<std::uint8_t>(moduleName.length() > 9 ? std::stoi(moduleName.substr(9)) : 0); - - if (ver <= 0) - die(_("seems theme-gtk module name '{}' doesn't have a version number to query.\n" - "Syntax should be like 'theme_gtkN' which N stands for the version of gtk to query (single number)"), - moduleName); - - Query::Theme query_theme(ver, queried_themes, fmt::format("gtk{}", ver), config); - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - switch (moduleMember_hash) - { - case "name"_fnv1a16: SYSINFO_INSERT(query_theme.gtk_theme()); break; - case "icons"_fnv1a16: SYSINFO_INSERT(query_theme.gtk_icon_theme()); break; - case "font"_fnv1a16: SYSINFO_INSERT(query_theme.gtk_font()); break; - } - } - } - - // clang-format on - else if (moduleName == "cpu") - { - Query::CPU query_cpu; - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - switch (moduleMember_hash) - { - case "name"_fnv1a16: SYSINFO_INSERT(query_cpu.name()); break; - case "nproc"_fnv1a16: SYSINFO_INSERT(query_cpu.nproc()); break; - case "freq_cur"_fnv1a16: SYSINFO_INSERT(query_cpu.freq_cur()); break; - case "freq_max"_fnv1a16: SYSINFO_INSERT(query_cpu.freq_max()); break; - case "freq_min"_fnv1a16: SYSINFO_INSERT(query_cpu.freq_min()); break; - case "freq_bios_limit"_fnv1a16: SYSINFO_INSERT(query_cpu.freq_bios_limit()); break; - - case "temp_C"_fnv1a16: SYSINFO_INSERT(query_cpu.temp()); break; - case "temp_F"_fnv1a16: SYSINFO_INSERT(query_cpu.temp() * 1.8 + 34); break; - case "temp_K"_fnv1a16: SYSINFO_INSERT(query_cpu.temp() + 273.15); break; - } - } - } - - else if (hasStart(moduleName, "gpu")) - { - const std::string& id = moduleName.length() > 3 ? moduleName.substr(3) : "0"; - - Query::GPU query_gpu(id, queried_gpus); - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - switch (moduleMember_hash) - { - case "name"_fnv1a16: SYSINFO_INSERT(query_gpu.name()); break; - case "vendor"_fnv1a16: SYSINFO_INSERT(shorten_vendor_name(query_gpu.vendor())); break; - case "vendor_long"_fnv1a16: SYSINFO_INSERT(query_gpu.vendor()); break; - } - } - } - - else if (hasStart(moduleName, "disk")) - { - if (moduleName.length() < "disk()"_len) - die(_("invalid disk module name '{}', must be disk(/path/to/fs) e.g: disk(/)"), moduleName); - - enum - { - USED = 0, - TOTAL, - FREE - }; - std::string path{ moduleName.data() }; - path.erase(0, 5); // disk( - path.pop_back(); // ) - debug("disk path = {}", path); - - Query::Disk query_disk(path, queried_disks, parse_args); - std::array<byte_units_t, 3> byte_units; - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - byte_units.at(TOTAL) = auto_devide_bytes(query_disk.total_amount(), byte_unit); - byte_units.at(USED) = auto_devide_bytes(query_disk.used_amount(), byte_unit); - byte_units.at(FREE) = auto_devide_bytes(query_disk.free_amount(), byte_unit); - - switch (moduleMember_hash) - { - case "fs"_fnv1a16: SYSINFO_INSERT(query_disk.typefs()); break; - case "device"_fnv1a16: SYSINFO_INSERT(query_disk.device()); break; - case "mountdir"_fnv1a16: SYSINFO_INSERT(query_disk.mountdir()); break; - - case "types"_fnv1a16: - { - std::string str; - if (query_disk.types_disk() & Query::DISK_VOLUME_TYPE_EXTERNAL) - str += "External, "; - if (query_disk.types_disk() & Query::DISK_VOLUME_TYPE_HIDDEN) - str += "Hidden, "; - if (query_disk.types_disk() & Query::DISK_VOLUME_TYPE_READ_ONLY) - str += "Read-only, "; - - if (!str.empty()) - str.erase(str.length() - 2); - SYSINFO_INSERT(str); - - } break; - - case "used"_fnv1a16: - SYSINFO_INSERT(fmt::format("{:.2f} {}", byte_units.at(USED).num_bytes, byte_units.at(USED).unit)); - break; - - case "total"_fnv1a16: - SYSINFO_INSERT(fmt::format("{:.2f} {}", byte_units.at(TOTAL).num_bytes, byte_units.at(TOTAL).unit)); - break; - - case "free"_fnv1a16: - SYSINFO_INSERT(fmt::format("{:.2f} {}", byte_units.at(FREE).num_bytes, byte_units.at(FREE).unit)); - break; - - case "free_percentage"_fnv1a16: - case "free_perc"_fnv1a16: - SYSINFO_INSERT(get_and_color_percentage(query_disk.free_amount(), query_disk.total_amount(), - parse_args, true)); - break; - - case "used_percentage"_fnv1a16: - case "used_perc"_fnv1a16: - SYSINFO_INSERT( - get_and_color_percentage(query_disk.used_amount(), query_disk.total_amount(), parse_args)); - break; - - default: - if (hasStart(moduleMemberName, "free-")) - SYSINFO_INSERT(return_devided_bytes(query_disk.free_amount())); - else if (hasStart(moduleMemberName, "used-")) - SYSINFO_INSERT(return_devided_bytes(query_disk.used_amount())); - else if (hasStart(moduleMemberName, "total-")) - SYSINFO_INSERT(return_devided_bytes(query_disk.total_amount())); - } - } - } - - else if (moduleName == "swap") - { - Query::RAM query_ram; - enum - { - USED = 0, - TOTAL, - FREE, - }; - std::array<byte_units_t, 3> byte_units; - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - // idk, trick the diviser - byte_units.at(FREE) = auto_devide_bytes(query_ram.swap_free_amount() * byte_unit, byte_unit); - byte_units.at(USED) = auto_devide_bytes(query_ram.swap_used_amount() * byte_unit, byte_unit); - byte_units.at(TOTAL) = auto_devide_bytes(query_ram.swap_total_amount() * byte_unit, byte_unit); - - switch (moduleMember_hash) - { - case "free"_fnv1a16: - SYSINFO_INSERT(fmt::format("{:.2f} {}", byte_units.at(FREE).num_bytes, byte_units.at(FREE).unit)); - break; - - case "total"_fnv1a16: - SYSINFO_INSERT(fmt::format("{:.2f} {}", byte_units.at(TOTAL).num_bytes, byte_units.at(TOTAL).unit)); - break; - - case "used"_fnv1a16: - SYSINFO_INSERT(fmt::format("{:.2f} {}", byte_units.at(USED).num_bytes, byte_units.at(USED).unit)); - break; - - case "free_percentage"_fnv1a16: - case "free_perc"_fnv1a16: - SYSINFO_INSERT(get_and_color_percentage(query_ram.swap_free_amount(), query_ram.swap_total_amount(), - parse_args, true)); - break; - - case "used_percentage"_fnv1a16: - case "used_perc"_fnv1a16: - SYSINFO_INSERT(get_and_color_percentage(query_ram.swap_used_amount(), query_ram.swap_total_amount(), - parse_args)); - break; - - default: - if (hasStart(moduleMemberName, "free-")) - SYSINFO_INSERT(return_devided_bytes(query_ram.swap_free_amount())); - else if (hasStart(moduleMemberName, "used-")) - SYSINFO_INSERT(return_devided_bytes(query_ram.swap_used_amount())); - else if (hasStart(moduleMemberName, "total-")) - SYSINFO_INSERT(return_devided_bytes(query_ram.swap_total_amount())); - } - } - } - - else if (moduleName == "ram") - { - Query::RAM query_ram; - enum - { - USED = 0, - TOTAL, - FREE, - }; - std::array<byte_units_t, 3> byte_units; - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - // idk, trick the diviser - byte_units.at(USED) = auto_devide_bytes(query_ram.used_amount() * byte_unit, byte_unit); - byte_units.at(TOTAL) = auto_devide_bytes(query_ram.total_amount() * byte_unit, byte_unit); - byte_units.at(FREE) = auto_devide_bytes(query_ram.free_amount() * byte_unit, byte_unit); - - switch (moduleMember_hash) - { - case "used"_fnv1a16: - SYSINFO_INSERT(fmt::format("{:.2f} {}", byte_units.at(USED).num_bytes, byte_units.at(USED).unit)); - break; - - case "total"_fnv1a16: - SYSINFO_INSERT(fmt::format("{:.2f} {}", byte_units.at(TOTAL).num_bytes, byte_units.at(TOTAL).unit)); - break; - - case "free"_fnv1a16: - SYSINFO_INSERT(fmt::format("{:.2f} {}", byte_units.at(FREE).num_bytes, byte_units.at(FREE).unit)); - break; - - case "free_percentage"_fnv1a16: - case "free_perc"_fnv1a16: - SYSINFO_INSERT( - get_and_color_percentage(query_ram.free_amount(), query_ram.total_amount(), parse_args, true)); - break; - - case "used_percentage"_fnv1a16: - case "used_perc"_fnv1a16: - SYSINFO_INSERT( - get_and_color_percentage(query_ram.used_amount(), query_ram.total_amount(), parse_args)); - break; - - default: - if (hasStart(moduleMemberName, "free-")) - SYSINFO_INSERT(return_devided_bytes(query_ram.free_amount())); - else if (hasStart(moduleMemberName, "used-")) - SYSINFO_INSERT(return_devided_bytes(query_ram.used_amount())); - else if (hasStart(moduleMemberName, "total-")) - SYSINFO_INSERT(return_devided_bytes(query_ram.total_amount())); - } - } - } - - else if (moduleName == "battery") - { - Query::Battery query_battery; - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - switch (moduleMember_hash) - { - case "percentage"_fnv1a16: - case "perc"_fnv1a16: - SYSINFO_INSERT(get_and_color_percentage(query_battery.perc(), 100, parse_args, true)); - break; - - case "vendor"_fnv1a16: - case "manufacturer"_fnv1a16: SYSINFO_INSERT(query_battery.vendor()); break; - case "technology"_fnv1a16: SYSINFO_INSERT(query_battery.technology()); break; - case "name"_fnv1a16: SYSINFO_INSERT(query_battery.modelname()); break; - case "status"_fnv1a16: SYSINFO_INSERT(query_battery.status()); break; - case "capacity_level"_fnv1a16: SYSINFO_INSERT(query_battery.capacity_level()); break; - - case "temp_C"_fnv1a16: SYSINFO_INSERT(query_battery.temp()); break; - case "temp_F"_fnv1a16: SYSINFO_INSERT(query_battery.temp() * 1.8 + 34); break; - case "temp_K"_fnv1a16: SYSINFO_INSERT(query_battery.temp() + 273.15); break; - } - } - } - - else if (moduleName == "auto") - { - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - switch (moduleMember_hash) - { - case "disk"_fnv1a16: - Query::Disk query_disks("", queried_disks, parse_args, true); - for (const std::string& str : query_disks.disks_formats()) - { - parse_args.tmp_layout.push_back(str); - SYSINFO_INSERT(str); - } - break; - } - } - } - - else - die(_("Invalid module name: {}"), moduleName); - -#undef SYSINFO_INSERT -} - -void addValueFromModule(const std::string& moduleName, parse_args_t& parse_args) -{ - const std::string& moduleMemberName = "module-" + moduleName; -#define SYSINFO_INSERT(x) sysInfo.at(moduleName).insert({ moduleMemberName, variant(x) }) - - // just aliases for convention - const Config& config = parse_args.config; - systemInfo_t& sysInfo = parse_args.systemInfo; - - const std::uint16_t byte_unit = config.use_SI_unit ? 1000 : 1024; - - if (moduleName == "title") - { - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - SYSINFO_INSERT(parse("${auto2}$<user.name>${0}@${auto2}$<os.hostname>", _, parse_args)); - } - } - - else if (moduleName == "title_sep" || moduleName == "title_separator") - { - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - // no need to parse anything - Query::User query_user; - Query::System query_system; - const size_t& title_len = - std::string_view(query_user.name() + '@' + query_system.hostname()).length(); - - std::string str; - str.reserve(config.title_sep.length() * title_len); - for (size_t i = 0; i < title_len; i++) - str += config.title_sep; - - SYSINFO_INSERT(str); - } - } - - else if (moduleName == "cpu") - { - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - Query::CPU query_cpu; - SYSINFO_INSERT( - fmt::format("{} ({}) @ {:.2f} GHz", query_cpu.name(), query_cpu.nproc(), query_cpu.freq_max())); - } - } - - else if (hasStart(moduleName, "gpu")) - { - const std::string& id = (moduleName.length() > 3 ? moduleName.substr(3) : "0"); - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - Query::GPU query_gpu(id, queried_gpus); - SYSINFO_INSERT(shorten_vendor_name(query_gpu.vendor()) + " " + query_gpu.name()); - } - } - - else if (hasStart(moduleName, "disk")) - { - if (moduleName.length() < "disk()"_len) - die(_("invalid disk module name '{}', must be disk(/path/to/fs) e.g: disk(/)"), moduleName); - - enum - { - USED = 0, - TOTAL, - }; - std::string path = moduleName; - path.erase(0, 5); // disk( - path.pop_back(); // ) - debug("disk path = {}", path); - - Query::Disk query_disk(path, queried_disks, parse_args); - std::array<byte_units_t, 2> byte_units; - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - byte_units.at(TOTAL) = auto_devide_bytes(query_disk.total_amount(), byte_unit); - byte_units.at(USED) = auto_devide_bytes(query_disk.used_amount(), byte_unit); - - const std::string& perc = - get_and_color_percentage(query_disk.used_amount(), query_disk.total_amount(), parse_args); - - // clang-format off - std::string result {fmt::format("{:.2f} {} / {:.2f} {} {}", - byte_units.at(USED).num_bytes, byte_units.at(USED).unit, - byte_units.at(TOTAL).num_bytes,byte_units.at(TOTAL).unit, - parse("${0}(" + perc + ")", _, parse_args) - )}; - // clang-format on - if (query_disk.typefs() != MAGIC_LINE) - result += " - " + query_disk.typefs(); - - std::string types_disk {"["}; - if (query_disk.types_disk() & Query::DISK_VOLUME_TYPE_EXTERNAL) - types_disk += "External, "; - if (query_disk.types_disk() & Query::DISK_VOLUME_TYPE_HIDDEN) - types_disk += "Hidden, "; - if (query_disk.types_disk() & Query::DISK_VOLUME_TYPE_READ_ONLY) - types_disk += "Read-only, "; - - if (types_disk.size() > 3) - { - // ", " - types_disk.erase(types_disk.size() - 2); - result += " " + types_disk + "]"; - } - - SYSINFO_INSERT(result); - } - } - - else if (moduleName == "ram") - { - Query::RAM query_ram; - enum - { - USED = 0, - TOTAL, - }; - std::array<byte_units_t, 2> byte_units; - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - // idk, trick the divider - byte_units.at(USED) = auto_devide_bytes(query_ram.used_amount() * byte_unit, byte_unit); - byte_units.at(TOTAL) = auto_devide_bytes(query_ram.total_amount() * byte_unit, byte_unit); - - const std::string& perc = - get_and_color_percentage(query_ram.used_amount(), query_ram.total_amount(), parse_args); - - // clang-format off - SYSINFO_INSERT(fmt::format("{:.2f} {} / {:.2f} {} {}", - byte_units.at(USED).num_bytes, byte_units.at(USED).unit, - byte_units.at(TOTAL).num_bytes,byte_units.at(TOTAL).unit, - parse("${0}(" + perc + ")", _, parse_args))); - // clang-format on - } - } - - else if (moduleName == "swap") - { - Query::RAM query_ram; - enum - { - USED = 0, - TOTAL, - }; - std::array<byte_units_t, 2> byte_units; - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - // idk, trick the divider - byte_units.at(USED) = auto_devide_bytes(query_ram.swap_used_amount() * byte_unit, byte_unit); - byte_units.at(TOTAL) = auto_devide_bytes(query_ram.swap_total_amount() * byte_unit, byte_unit); - - // clang-format off - if (byte_units.at(TOTAL).num_bytes < 1) - SYSINFO_INSERT("Disabled"); - else - { - const std::string& perc = get_and_color_percentage(query_ram.swap_used_amount(), query_ram.swap_total_amount(), - parse_args); - - SYSINFO_INSERT(fmt::format("{:.2f} {} / {:.2f} {} {}", - byte_units.at(USED).num_bytes, byte_units.at(USED).unit, - byte_units.at(TOTAL).num_bytes,byte_units.at(TOTAL).unit, - parse("${0}(" + perc + ")", _, parse_args))); - } - // clang-format on - } - } - - else if (moduleName == "battery") - { - Query::Battery query_battery; - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - SYSINFO_INSERT(fmt::format("{} [{}]", get_and_color_percentage(query_battery.perc(), 100, parse_args, true), - query_battery.status())); - } - } - - else if (moduleName == "colors") - { - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - SYSINFO_INSERT(parse("${\033[40m} ${\033[41m} ${\033[42m} ${\033[43m} ${\033[44m} ${\033[45m} ${\033[46m} ${\033[47m} ${0}", _, parse_args)); - } - } - - else if (moduleName == "colors_light") - { - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - SYSINFO_INSERT(parse("${\033[100m} ${\033[101m} ${\033[102m} ${\033[103m} ${\033[104m} ${\033[105m} ${\033[106m} ${\033[107m} ${0}", _, parse_args)); - } - } - - // clang-format off - // I really dislike how repetitive this code is - else if (hasStart(moduleName, "colors_symbol")) - { - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - if (moduleName.length() <= "colors_symbol()"_len) - die(_("color palette module member '{}' in invalid.\n" - "Must be used like 'colors_symbol(`symbol for printing the color palette`)'.\n" - "e.g 'colors_symbol(@)' or 'colors_symbol(string)'"), - moduleName); - - std::string symbol = moduleName; - symbol.erase(0, "colors_symbol("_len); - symbol.pop_back(); - debug("symbol = {}", symbol); - - SYSINFO_INSERT( - parse(fmt::format("${{\033[30m}} {0} ${{\033[31m}} {0} ${{\033[32m}} {0} ${{\033[33m}} {0} ${{\033[34m}} {0} ${{\033[35m}} {0} ${{\033[36m}} {0} ${{\033[37m}} {0} ${{0}}", - symbol), _, parse_args)); - } - } - - else if (hasStart(moduleName, "colors_light_symbol")) - { - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - if (moduleName.length() <= "colors_light_symbol()"_len) - die(_("light color palette module member '{}' in invalid.\n" - "Must be used like 'colors_light_symbol(`symbol for printing the color palette`)'.\n" - "e.g 'colors_light_symbol(@)' or 'colors_light_symbol(string)'"), - moduleName); - - std::string symbol = moduleName; - symbol.erase(0, "colors_light_symbol("_len); - symbol.pop_back(); - debug("symbol = {}", symbol); - - SYSINFO_INSERT( - parse(fmt::format("${{\033[90m}} {0} ${{\033[91m}} {0} ${{\033[92m}} {0} ${{\033[93m}} {0} ${{\033[94m}} {0} ${{\033[95m}} {0} ${{\033[96m}} {0} ${{\033[97m}} {0} ${{0}}", - symbol), _, parse_args)); - } - } - - else - die(_("Invalid module name: {}"), moduleName); -} diff --git a/src/query/android/battery.cpp b/src/query/android/battery.cpp deleted file mode 100644 index 243ec6a1..00000000 --- a/src/query/android/battery.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2025 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_ANDROID - -#include <cctype> -#include <string> -#include <vector> - -#include "json.h" -#include "query.hpp" -#include "switch_fnv1a.hpp" -#include "util.hpp" - -using namespace Query; - -static Battery::Battery_t get_battery_infos_termux() -{ - Battery::Battery_t infos; - std::string result, _; - if (!read_exec({ "/data/data/com.termux/files/usr/libexec/termux-api", "BatteryStatus" }, result)) - return infos; - - const auto& doc = json::jobject::parse(result); - - infos.status = doc["plugged"].as_string(); - infos.perc = doc["percentage"]; - infos.temp = doc["temperature"]; - - switch (fnv1a16::hash(infos.status)) - { - case "PLUGGED_AC"_fnv1a16: infos.status = "AC Connected, "; break; - case "PLUGGED_USB"_fnv1a16: infos.status = "USB Connected, "; break; - case "PLUGGED_WIRELESS"_fnv1a16: infos.status = "Wireless Connected, "; break; - default: infos.status.clear(); - } - - // CHARGING or DISCHARGING - std::string charge_status{ str_tolower(doc["status"].as_string()) }; - charge_status.at(0) = toupper(charge_status.at(0)); - infos.status += charge_status; - - return infos; -} - -static Battery::Battery_t get_battery_infos_dumpsys() -{ - Battery::Battery_t infos; - std::string result, _; - if (!read_exec({ "/system/bin/dumpsys", "battery" }, result)) - return infos; - - const std::vector<std::string>& vec = split(result, '\n'); - if (vec.at(0) != "Current Battery Service state:") - return infos; - - double level = 0, scale = 0; - for (size_t i = 1; i < vec.size(); ++i) - { - const size_t pos = vec.at(i).rfind(':'); - const std::string& key = vec.at(i).substr(2, pos); - const std::string& value = vec.at(i).substr(pos + 2); - - switch (fnv1a16::hash(key)) - { - case "level"_fnv1a16: level = std::stod(value); break; - case "scale"_fnv1a16: scale = std::stod(value); break; - - case "AC powered"_fnv1a16: - case "USB powered"_fnv1a16: - case "Dock powered"_fnv1a16: - case "Wireless powered"_fnv1a16: - if (value == "true") - infos.status = key; - break; - - case "temperature"_fnv1a16: infos.temp = std::stod(value) / 10; break; - case "technology"_fnv1a16: infos.technology = value; - } - } - - if (level > 0 && scale > 0) - infos.perc = level * 100 / scale; - - return infos; -} - -Battery::Battery() -{ - CHECK_INIT(m_bInit); - - m_battery_infos = get_battery_infos_termux(); - if (m_battery_infos.status == MAGIC_LINE || m_battery_infos.temp <= 0) - m_battery_infos = get_battery_infos_dumpsys(); -} - -// clang-format off -std::string& Battery::modelname() noexcept -{ return m_battery_infos.modelname; } - -std::string& Battery::status() noexcept -{ return m_battery_infos.status; } - -std::string& Battery::vendor() noexcept -{ return m_battery_infos.vendor; } - -std::string& Battery::technology() noexcept -{ return m_battery_infos.technology; } - -std::string& Battery::capacity_level() noexcept -{ return m_battery_infos.capacity_level; } - -double& Battery::perc() noexcept -{ return m_battery_infos.perc; } - -double& Battery::temp() noexcept -{ return m_battery_infos.temp; } - -#endif // CF_ANDROID diff --git a/src/query/android/system.cpp b/src/query/android/system.cpp deleted file mode 100644 index 374f0d97..00000000 --- a/src/query/android/system.cpp +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2025 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_ANDROID - -#include <sys/stat.h> -#include <unistd.h> - -#include <ctime> -#include <array> -#include <string_view> - -#include "../linux/utils/packages.hpp" -#include "query.hpp" -#include "util.hpp" - -using namespace Query; - -static System::System_t get_system_infos() -{ - System::System_t ret; - - ret.os_name = "Android"; - ret.os_id = "android"; - ret.os_version_id = get_android_property("ro.build.version.release"); - ret.os_version_codename = get_android_property("ro.build.version.codename"); - ret.os_pretty_name = "Android " + ret.os_version_codename + " " + ret.os_version_id; - - constexpr std::array<std::string_view, 8> vendors_prop_names = { - "ro.product.marketname", "ro.vendor.product.display", "ro.config.devicename", "ro.config.marketing_name", - "ro.product.vendor.model", "ro.product.oppo_model", "ro.oppo.market.name", "ro.product.brand" - }; - for (const std::string_view name : vendors_prop_names) - { - if (ret.host_modelname.empty() || ret.host_modelname == UNKNOWN) - ret.host_modelname = get_android_property(name); - else - break; - } - ret.host_vendor = get_android_property("ro.product.manufacturer"); - ret.host_version = get_android_property("ro.product.model"); - - if (access("/system/bin/init", F_OK) == 0) - { - ret.os_initsys_name = "init"; - ret.os_initsys_version.clear(); - } - - return ret; -} - -static unsigned long get_uptime() -{ - struct std::timespec uptime; - if (clock_gettime(CLOCK_BOOTTIME, &uptime) != 0) - return 0; - - return (uint64_t)uptime.tv_sec + (uint64_t)uptime.tv_nsec / 1000000; -} - -System::System() -{ - CHECK_INIT(m_bInit); - - if (uname(&m_uname_infos) != 0) - die("uname() failed: {}\nCould not get system infos", strerror(errno)); - - m_uptime = get_uptime(); - m_system_infos = get_system_infos(); -} - -// clang-format off -std::string System::kernel_name() noexcept -{ return m_uname_infos.sysname; } - -std::string System::kernel_version() noexcept -{ return m_uname_infos.release; } - -std::string System::hostname() noexcept -{ return m_uname_infos.nodename; } - -std::string System::arch() noexcept -{ return m_uname_infos.machine; } - -unsigned long& System::uptime() noexcept -{ return m_uptime; } - -std::string& System::os_pretty_name() noexcept -{ return m_system_infos.os_pretty_name; } - -std::string& System::os_name() noexcept -{ return m_system_infos.os_name; } - -std::string& System::os_id() noexcept -{ return m_system_infos.os_id; } - -std::string& System::os_versionid() noexcept -{ return m_system_infos.os_version_id; } - -std::string& System::os_version_codename() noexcept -{ return m_system_infos.os_version_codename; } - -std::string& System::host_modelname() noexcept -{ return m_system_infos.host_modelname; } - -std::string& System::host_vendor() noexcept -{ return m_system_infos.host_vendor; } - -std::string& System::host_version() noexcept -{ return m_system_infos.host_version; } - -std::string& System::os_initsys_name() -{ return m_system_infos.os_initsys_name; } - -std::string& System::os_initsys_version() -{ return m_system_infos.os_initsys_version; } - -std::string& System::pkgs_installed(const Config& config) -{ - static bool done = false; - if (!done) - { - m_system_infos.pkgs_installed = get_all_pkgs(config); - - done = true; - } - - return m_system_infos.pkgs_installed; -} - -#endif diff --git a/src/query/android/user.cpp b/src/query/android/user.cpp deleted file mode 100644 index 21d8d0d1..00000000 --- a/src/query/android/user.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2025 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_ANDROID - -#include <linux/limits.h> -#include <unistd.h> - -#include <cstdlib> -#include <string> - -#include "fmt/format.h" -#include "query.hpp" -#include "util.hpp" - -using namespace Query; - -static std::string get_shell_version(const std::string_view shell_name) -{ - std::string ret; - - if (shell_name == "nu") - ret = read_shell_exec("nu -c \"version | get version\""); - else - ret = read_shell_exec(fmt::format("{} -c 'echo \"${}_VERSION\"'", shell_name, str_toupper(shell_name.data()))); - - strip(ret); - return ret; -} - -static std::string get_shell_name(const std::string_view shell_path) -{ - return shell_path.substr(shell_path.rfind('/') + 1).data(); -} - -User::User() noexcept -{ - CHECK_INIT(m_bInit); - - if (m_pPwd = getpwuid(getuid()), !m_pPwd) - die("getpwent failed: {}\nCould not get user infos", std::strerror(errno)); - - char buf[PATH_MAX]; - if (getenv("TERMUX_VERSION") || getenv("TERMUX_MAIN_PACKAGE_FORMAT")) - { - m_users_infos.shell_path = realpath(fmt::format("/proc/{}/exe", getppid()).c_str(), buf); - m_users_infos.shell_name = get_shell_name(m_users_infos.shell_path); - m_users_infos.shell_version = get_shell_version(m_users_infos.shell_name); - m_users_infos.term_name = "Termux"; - m_users_infos.term_version = getenv("TERMUX_VERSION"); - } - else - { - m_users_infos.shell_path = m_pPwd->pw_shell; - } - - m_users_infos.wm_name = m_users_infos.wm_version = m_users_infos.de_name = m_users_infos.de_version = - m_users_infos.m_wm_path = MAGIC_LINE; -} - -// clang-format off -std::string User::name() noexcept -{ return m_pPwd->pw_name; } - -std::string User::shell_path() noexcept -{ return m_users_infos.shell_path; } - -std::string& User::shell_name() noexcept -{ return m_users_infos.shell_name; } - -std::string& User::shell_version(const std::string_view shell_name) -{ return m_users_infos.shell_version; } - -std::string& User::term_name() -{ return m_users_infos.term_name; } - -std::string& User::term_version(const std::string_view term_name) -{ return m_users_infos.term_version; } - -std::string& User::wm_name(bool dont_query_dewm, const std::string_view term_name) -{ return m_users_infos.wm_name; } - -std::string& User::wm_version(bool dont_query_dewm, const std::string_view term_name) -{ return m_users_infos.wm_version; } - -std::string& User::de_name(bool dont_query_dewm, const std::string_view term_name, const std::string_view wm_name) -{ return m_users_infos.de_name; } - -std::string& User::de_version(const std::string_view de_name) -{ return m_users_infos.de_version; } - -#endif diff --git a/src/query/linux/battery.cpp b/src/query/linux/battery.cpp deleted file mode 100644 index ba156688..00000000 --- a/src/query/linux/battery.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2025 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_LINUX - -#include <cstdlib> -#include <string> -#include <filesystem> - -#include "query.hpp" -#include "util.hpp" - -using namespace Query; - -static void read_strip_syspath(std::string& str, const std::string_view path) -{ - str = read_by_syspath(path); - debug("str = {} || path = {}", str, path); - - // optimization - if (str.back() == '\n') - str.pop_back(); - else if (str != UNKNOWN) - strip(str); -} - -static Battery::Battery_t get_battery_infos() -{ - Battery::Battery_t infos; - if (!std::filesystem::exists("/sys/class/power_supply/")) - return infos; - - for (const auto& dir_entry : std::filesystem::directory_iterator{"/sys/class/power_supply/"}) - { - const std::string& path = dir_entry.path().string() + "/"; - debug("battery path = {}", path); - std::string tmp; - - read_strip_syspath(tmp, path + "type"); - if (tmp == UNKNOWN || tmp != "Battery") - continue; - - read_strip_syspath(tmp, path + "scope"); - if (tmp == "Device") - continue; - - debug("battery found yeappyy"); - read_strip_syspath(tmp, path + "capacity"); - if (tmp != UNKNOWN) - infos.perc = std::stod(tmp); - else - continue; - - read_strip_syspath(tmp, path + "temp"); - if (tmp != UNKNOWN) - infos.temp = std::stod(tmp) / 10; - - read_strip_syspath(tmp, path + "manufacturer"); - if (tmp != UNKNOWN) - infos.vendor = tmp; - - read_strip_syspath(tmp, path + "model_name"); - if (tmp != UNKNOWN) - infos.modelname = tmp; - - read_strip_syspath(tmp, path + "technology"); - if (tmp != UNKNOWN) - infos.technology = tmp; - - read_strip_syspath(tmp, path + "status"); - if (tmp != UNKNOWN) - infos.status = tmp; - - read_strip_syspath(tmp, path + "capacity_level"); - if (tmp != UNKNOWN) - infos.capacity_level = tmp; - } - - return infos; -} - -Battery::Battery() -{ - CHECK_INIT(m_bInit); - - m_battery_infos = get_battery_infos(); -} - -// clang-format off -std::string& Battery::modelname() noexcept -{ return m_battery_infos.modelname; } - -std::string& Battery::status() noexcept -{ return m_battery_infos.status; } - -std::string& Battery::vendor() noexcept -{ return m_battery_infos.vendor; } - -std::string& Battery::technology() noexcept -{ return m_battery_infos.technology; } - -std::string& Battery::capacity_level() noexcept -{ return m_battery_infos.capacity_level; } - -double& Battery::perc() noexcept -{ return m_battery_infos.perc; } - -double& Battery::temp() noexcept -{ return m_battery_infos.temp; } - -#endif // CF_LINUX diff --git a/src/query/linux/disk.cpp b/src/query/linux/disk.cpp deleted file mode 100644 index db51cfce..00000000 --- a/src/query/linux/disk.cpp +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright 2025 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -/* - * Copyright (c) 2021-2023 Linus Dierheimer - * Copyright (c) 2022-2024 Carter Li - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "platform.hpp" -#if CF_LINUX || CF_ANDROID - -#include <mntent.h> -#include <cstdio> -#include <cstring> -#include <unistd.h> -#include <algorithm> -#include <string_view> - -#include "config.hpp" -#include "query.hpp" -#include "util.hpp" -#include "parse.hpp" - -using namespace Query; - -// https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/disk/disk_linux.c -static bool is_physical_device(const mntent* device) -{ - #if !CF_ANDROID // On Android, `/dev` is not accessible, so that the following checks always fail - - //Always show the root path - if (strcmp(device->mnt_dir, "/") == 0) - return true; - - if (strcmp(device->mnt_fsname, "none") == 0) - return false; - - //DrvFs is a filesystem plugin to WSL that was designed to support interop between WSL and the Windows filesystem. - if (strcmp(device->mnt_type, "9p") == 0) - return std::string_view(device->mnt_opts).find("aname=drvfs") != std::string_view::npos; - - //ZFS pool - if (strcmp(device->mnt_type, "zfs") == 0) - return true; - - //Pseudo filesystems don't have a device in /dev - if (!hasStart(device->mnt_fsname, "/dev/")) - return false; - - //#731 - if (strcmp(device->mnt_type, "bcachefs") == 0) - return true; - - if( - hasStart(device->mnt_fsname + 5, "loop") || //Ignore loop devices - hasStart(device->mnt_fsname + 5, "ram") || //Ignore ram devices - hasStart(device->mnt_fsname + 5, "fd") //Ignore fd devices - ) return false; - - struct stat deviceStat; - if (stat(device->mnt_fsname, &deviceStat) != 0) - return false; - - //Ignore all devices that are not block devices - if (!S_ISBLK(deviceStat.st_mode)) - return false; - - #else - - //Pseudo filesystems don't have a device in /dev - if (!hasStart(device->mnt_fsname, "/dev/")) - return false; - - if( - hasStart(device->mnt_fsname + 5, "loop") || //Ignore loop devices - hasStart(device->mnt_fsname + 5, "ram") || //Ignore ram devices - hasStart(device->mnt_fsname + 5, "fd") //Ignore fd devices - ) return false; - - // https://source.android.com/docs/core/ota/apex?hl=zh-cn - if (hasStart(device->mnt_dir, "/apex/")) - return false; - - #endif // !CF_ANDROID - - return true; -} - -static bool is_removable(const mntent* device) -{ - if (!hasStart(device->mnt_fsname, "/dev/")) - return false; - - // like str.substr(5); - std::string sys_block_partition {fmt::format("/sys/class/block/{}", (device->mnt_fsname + "/dev/"_len))}; - // check if it's like /dev/sda1 - if (sys_block_partition.back() >= '0' && sys_block_partition.back() <= '9') - sys_block_partition.pop_back(); - - return read_by_syspath(sys_block_partition + "/removable") == "1"; -} - -static int get_disk_type(const mntent* device) -{ -#if CF_LINUX - int ret = 0; - - if (hasStart(device->mnt_dir, "/boot") || hasStart(device->mnt_dir, "/efi")) - ret = DISK_VOLUME_TYPE_HIDDEN; - else if (is_removable(device)) - ret = DISK_VOLUME_TYPE_EXTERNAL; - else - ret = DISK_VOLUME_TYPE_REGULAR; - - if (hasmntopt(device, MNTOPT_RO)) - ret |= DISK_VOLUME_TYPE_READ_ONLY; - - return ret; -#else // CF_ANDROID - if (strcmp(device->mnt_dir, "/") == 0 || strcmp(device->mnt_dir, "/storage/emulated") == 0) - return DISK_VOLUME_TYPE_REGULAR; - - if (hasStart(device->mnt_dir, "/mnt/media_rw/")) - return DISK_VOLUME_TYPE_EXTERNAL; - - return DISK_VOLUME_TYPE_HIDDEN; -#endif -} - -static std::string format_auto_query_string(std::string str, const struct mntent *device) -{ - replace_str(str, "%1", device->mnt_dir); - replace_str(str, "%2", device->mnt_fsname); - replace_str(str, "%3", device->mnt_type); - - replace_str(str, "%4", fmt::format("$<disk({}).total>", device->mnt_dir)); - replace_str(str, "%5", fmt::format("$<disk({}).free>", device->mnt_dir)); - replace_str(str, "%6", fmt::format("$<disk({}).used>", device->mnt_dir)); - replace_str(str, "%7", fmt::format("$<disk({}).used_perc>", device->mnt_dir)); - replace_str(str, "%8", fmt::format("$<disk({}).free_perc>", device->mnt_dir)); - - return str; -} - -Disk::Disk(const std::string& path, systemInfo_t& queried_paths, parse_args_t& parse_args, const bool auto_module) -{ - if (queried_paths.find(path) != queried_paths.end() && !is_live_mode) - { - m_disk_infos.device = getInfoFromName(queried_paths, path, "device"); - m_disk_infos.mountdir = getInfoFromName(queried_paths, path, "mountdir"); - m_disk_infos.typefs = getInfoFromName(queried_paths, path, "typefs"); - m_disk_infos.total_amount = std::stod(getInfoFromName(queried_paths, path, "total_amount")); - m_disk_infos.used_amount = std::stod(getInfoFromName(queried_paths, path, "used_amount")); - m_disk_infos.free_amount = std::stod(getInfoFromName(queried_paths, path, "free_amount")); - return; - } - - if (access(path.data(), F_OK) != 0 && !auto_module) - { - // if user is using $<disk(path)> or $<disk(path).fs> - // then let's just "try" to remove it - m_disk_infos.typefs = MAGIC_LINE; - m_disk_infos.device = MAGIC_LINE; - m_disk_infos.mountdir = MAGIC_LINE; - return; - } - - FILE* mountsFile = setmntent("/proc/mounts", "r"); - if (mountsFile == NULL) - { - perror("setmntent"); - error(_("setmntent() failed. Could not get disk info")); - return; - } - - if (auto_module) - { - struct mntent* pDevice; - while ((pDevice = getmntent(mountsFile))) - { - if (!is_physical_device(pDevice)) - continue; - - m_disk_infos.types_disk = get_disk_type(pDevice); - if (!(parse_args.config.auto_disks_types & m_disk_infos.types_disk)) - continue; - - if (!parse_args.config.auto_disks_show_dupl) - { - const auto& it = std::find(m_queried_devices.begin(), m_queried_devices.end(), pDevice->mnt_fsname); - if (it != m_queried_devices.end()) - continue; - - m_queried_devices.push_back(pDevice->mnt_fsname); - } - - parse_args.no_more_reset = false; - debug("AUTO: pDevice->mnt_dir = {} && pDevice->mnt_fsname = {}", pDevice->mnt_dir, pDevice->mnt_fsname); - m_disks_formats.push_back( - parse(format_auto_query_string(parse_args.config.auto_disks_fmt, pDevice), parse_args) - ); - } - - endmntent(mountsFile); - return; - } - - struct mntent* pDevice; - while ((pDevice = getmntent(mountsFile))) - { - debug("pDevice->mnt_dir = {} && pDevice->mnt_fsname = {}", pDevice->mnt_dir, pDevice->mnt_fsname); - if (path == pDevice->mnt_dir || path == pDevice->mnt_fsname) - { - m_disk_infos.types_disk = get_disk_type(pDevice); - if (!(parse_args.config.auto_disks_types & m_disk_infos.types_disk)) - continue; - - m_disk_infos.typefs = pDevice->mnt_type; - m_disk_infos.device = pDevice->mnt_fsname; - m_disk_infos.mountdir = pDevice->mnt_dir; - break; - } - } - - const std::string& statpath = (hasStart(path, "/dev") && pDevice) ? pDevice->mnt_dir : path; - - struct statvfs fs; - if (statvfs(statpath.c_str(), &fs) != 0) - { - perror("statvfs"); - error(_("Failed to get disk info at {}"), statpath); - return; - } - - m_disk_infos.total_amount = static_cast<double>(fs.f_blocks * fs.f_frsize); - m_disk_infos.free_amount = static_cast<double>(fs.f_bfree * fs.f_frsize); - m_disk_infos.used_amount = m_disk_infos.total_amount - m_disk_infos.free_amount; - - endmntent(mountsFile); - queried_paths.insert( - {path, { - {"total_amount", variant(m_disk_infos.total_amount)}, - {"used_amount", variant(m_disk_infos.used_amount)}, - {"free_amount", variant(m_disk_infos.free_amount)}, - {"typefs", variant(m_disk_infos.typefs)}, - {"mountdir", variant(m_disk_infos.mountdir)}, - {"device", variant(m_disk_infos.device)} - }} - ); -} - -// clang-format off -double& Disk::total_amount() noexcept -{ return m_disk_infos.total_amount; } - -double& Disk::used_amount() noexcept -{ return m_disk_infos.used_amount; } - -double& Disk::free_amount() noexcept -{ return m_disk_infos.free_amount; } - -int& Disk::types_disk() noexcept -{ return m_disk_infos.types_disk; } - -std::string& Disk::typefs() noexcept -{ return m_disk_infos.typefs; } - -std::string& Disk::mountdir() noexcept -{ return m_disk_infos.mountdir; } - -std::string& Disk::device() noexcept -{ return m_disk_infos.device; } - -#endif // CF_LINUX || CF_ANDROID diff --git a/src/query/linux/gpu.cpp b/src/query/linux/gpu.cpp deleted file mode 100644 index fce01acf..00000000 --- a/src/query/linux/gpu.cpp +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2025 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_LINUX - -#include <cstdint> -#include <filesystem> -#include <string> -#include "fmt/format.h" - -#include "query.hpp" -#include "util.hpp" -#include "parse.hpp" - -using namespace Query; - -static std::string get_name(const std::string_view m_vendor_id_s, const std::string_view m_device_id_s) -{ - std::string name = binarySearchPCIArray(m_vendor_id_s, m_device_id_s); - debug("GPU binarySearchPCIArray name = {}", name); - const size_t first_bracket = name.find('['); - const size_t last_bracket = name.rfind(']'); - - // remove the chips name "TU106 [GeForce GTX 1650]" - // This should work for AMD and Intel too. - if (first_bracket != std::string::npos && last_bracket != std::string::npos) - name = name.substr(first_bracket + 1, last_bracket - first_bracket - 1); - - // name = this->vendor() + ' ' + name; - - // replace_str(name, "NVIDIA Corporation", "NVIDIA"); - // replace_str(name, "Advanced Micro Devices Inc.", "AMD"); - // replace_str(name, "Intel Corporation", "Intel"); - - return name; -} - -static std::string get_vendor(const std::string_view m_vendor_id_s) -{ return binarySearchPCIArray(m_vendor_id_s); } - -static GPU::GPU_t get_gpu_infos(const std::string_view m_vendor_id_s, const std::string_view m_device_id_s) -{ - debug("calling GPU {}", __func__); - GPU::GPU_t ret; - - debug("GPU m_vendor_id_s = {} || m_device_id_s = {}", m_vendor_id_s, m_device_id_s); - if (m_device_id_s == UNKNOWN || m_vendor_id_s == UNKNOWN) - return ret; - - ret.name = get_name(m_vendor_id_s, m_device_id_s); - ret.vendor = get_vendor(m_vendor_id_s); - - return ret; -} - -GPU::GPU(const std::string& id, systemInfo_t& queried_gpus) -{ - if (queried_gpus.find(id) != queried_gpus.end()) - { - m_gpu_infos.name = getInfoFromName(queried_gpus, id, "name"); - m_gpu_infos.vendor = getInfoFromName(queried_gpus, id, "vendor"); - return; - } - - const std::uint16_t max_iter = 10; - std::uint16_t id_iter = std::stoi(id); - std::string sys_path; - int i = 0; - for (; i <= max_iter; i++) - { - sys_path = "/sys/class/drm/card" + fmt::to_string(id_iter); - if (std::filesystem::exists(sys_path + "/device/device") && - std::filesystem::exists(sys_path + "/device/vendor")) - break; - else - id_iter++; - } - - if (i >= max_iter) - { - error(_("Failed to parse GPU infos on the path /sys/class/drm/")); - return; - } - - m_vendor_id_s = read_by_syspath(sys_path + "/device/vendor"); - m_device_id_s = read_by_syspath(sys_path + "/device/device"); - - m_gpu_infos = get_gpu_infos(m_vendor_id_s, m_device_id_s); - queried_gpus.insert( - {id, { - {"name", variant(m_gpu_infos.name)}, - {"vendor", variant(m_gpu_infos.vendor)}, - }} - ); -} - -// clang-format off -std::string& GPU::name() noexcept -{ return m_gpu_infos.name; } - -std::string& GPU::vendor() noexcept -{ return m_gpu_infos.vendor; } - -#endif diff --git a/src/query/linux/ram.cpp b/src/query/linux/ram.cpp deleted file mode 100644 index 006339e7..00000000 --- a/src/query/linux/ram.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2025 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_LINUX || CF_ANDROID - -#include <fstream> - -#include "query.hpp" -#include "util.hpp" - -using namespace Query; - -/*enum { - SHMEM = 0, - FREE, - BUFFER, - CACHED, - SRECLAIMABLE -};*/ - -static size_t get_from_text(std::string& line, u_short& iter_index, const std::uint16_t len) -{ - std::string amount = line.substr(len + 1); - strip(amount); - ++iter_index; - return std::stoi(amount); -} - -static RAM::RAM_t get_amount() noexcept -{ - debug("calling in RAM {}", __PRETTY_FUNCTION__); - constexpr std::string_view meminfo_path = "/proc/meminfo"; - RAM::RAM_t memory_infos; - - // std::array<size_t, 5> extra_mem_info; - std::ifstream file(meminfo_path.data()); - if (!file.is_open()) - { - error(_("Could not open {}\nFailed to get RAM infos"), meminfo_path); - return memory_infos; - } - - std::string line; - std::uint16_t iter_index = 0; - while (std::getline(file, line) && iter_index < 4) - { - if (hasStart(line, "MemAvailable:")) - memory_infos.free_amount = get_from_text(line, iter_index, "MemAvailable:"_len); - - else if (hasStart(line, "MemTotal:")) - memory_infos.total_amount = get_from_text(line, iter_index, "MemTotal:"_len); - - else if (hasStart(line, "SwapFree:")) - memory_infos.swap_free_amount = get_from_text(line, iter_index, "SwapFree:"_len); - - else if (hasStart(line, "SwapTotal:")) - memory_infos.swap_total_amount = get_from_text(line, iter_index, "SwapTotal:"_len); - - /*if (line.find("Shmem:") != std::string::npos) - extra_mem_info.at(SHMEM) = get_from_text(line); - - if (line.find("MemFree:") != std::string::npos) - extra_mem_info.at(FREE) = get_from_text(line); - - if (line.find("Buffers:") != std::string::npos) - extra_mem_info.at(BUFFER) = get_from_text(line); - - if (line.find("Cached:") != std::string::npos) - extra_mem_info.at(CACHED) = get_from_text(line); - - if (line.find("SReclaimable:") != std::string::npos) - extra_mem_info.at(SRECLAIMABLE) = get_from_text(line);*/ - } - - // https://github.com/dylanaraps/neofetch/wiki/Frequently-Asked-Questions#linux-is-neofetchs-memory-output-correct - memory_infos.used_amount = - memory_infos.total_amount - - memory_infos.free_amount; // + extra_mem_info.at(SHMEM) - extra_mem_info.at(FREE) - extra_mem_info.at(BUFFER) - - // extra_mem_info.at(CACHED) - extra_mem_info.at(SRECLAIMABLE); - - memory_infos.swap_used_amount = - memory_infos.swap_total_amount - - memory_infos.swap_free_amount; - - return memory_infos; -} - -RAM::RAM() noexcept -{ - CHECK_INIT(m_bInit); - - m_memory_infos = get_amount(); -} - -// clang-format off -double& RAM::free_amount() noexcept -{ return m_memory_infos.free_amount; } - -double& RAM::total_amount() noexcept -{ return m_memory_infos.total_amount; } - -double& RAM::used_amount() noexcept -{ return m_memory_infos.used_amount; } - -double& RAM::swap_total_amount() noexcept -{ return m_memory_infos.swap_total_amount; } - -double& RAM::swap_used_amount() noexcept -{ return m_memory_infos.swap_used_amount; } - -double& RAM::swap_free_amount() noexcept -{ return m_memory_infos.swap_free_amount; } - -#endif // CF_LINUX || CF_ANDROID diff --git a/src/query/linux/system.cpp b/src/query/linux/system.cpp deleted file mode 100644 index a44213ba..00000000 --- a/src/query/linux/system.cpp +++ /dev/null @@ -1,325 +0,0 @@ -/* - * Copyright 2025 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_LINUX - -#include <linux/limits.h> -#include <unistd.h> - -#include <ctime> -#include <array> -#include <cerrno> -#include <cstdint> -#include <cstdlib> -#include <cstring> -#include <filesystem> -#include <string> - -#include "config.hpp" -#include "query.hpp" -#include "util.hpp" -#include "switch_fnv1a.hpp" -#include "utils/packages.hpp" - -using namespace Query; - -static void get_host_paths(System::System_t& paths) -{ - const std::string syspath = "/sys/devices/virtual/dmi/id"; - - if (std::filesystem::exists(syspath + "/board_name")) - { - paths.host_modelname = read_by_syspath(syspath + "/board_name"); - paths.host_version = read_by_syspath(syspath + "/board_version"); - paths.host_vendor = read_by_syspath(syspath + "/board_vendor"); - - if (paths.host_vendor == "Micro-Star International Co., Ltd.") - paths.host_vendor = "MSI"; - } - - else if (std::filesystem::exists(syspath + "/product_name")) - { - paths.host_modelname = read_by_syspath(syspath + "/product_name"); - if (hasStart(paths.host_modelname, "Standard PC")) - { - // everyone does it like "KVM/QEMU Standard PC (...) (host_version)" so why not - paths.host_vendor = "KVM/QEMU"; - paths.host_version = std::string_view('(' + read_by_syspath(syspath + "/product_version") + ')').data(); - } - else - paths.host_version = read_by_syspath(syspath + "/product_version"); - } -} - -static System::System_t get_system_infos_lsb_releases() -{ - System::System_t ret; - - debug("calling in System {}", __PRETTY_FUNCTION__); - std::string lsb_release_path; - constexpr std::array<std::string_view, 3> lsb_paths = { "/etc/lsb-release", "/usr/lib/lsb-release" }; - for (const std::string_view path : lsb_paths) - { - if (std::filesystem::exists(path)) - { - lsb_release_path = path; - break; - } - } - - std::ifstream os_release_file(lsb_release_path, std::ios::in); - if (!os_release_file.is_open()) - { - error(_("Failed to get OS infos"), lsb_release_path); - return ret; - } - - // get OS /etc/lsb-release infos - std::uint16_t iter_index = 0; - std::string line; - while (std::getline(os_release_file, line) && iter_index < 3) - { - if (hasStart(line, "DISTRIB_DESCRIPTION=")) - getFileValue(iter_index, line, ret.os_pretty_name, "DISTRIB_DESCRIPTION="_len); - - else if (hasStart(line, "DISTRIB_ID=")) - getFileValue(iter_index, line, ret.os_id, "DISTRIB_ID="_len); - - else if (hasStart(line, "DISTRIB_CODENAME=")) - getFileValue(iter_index, line, ret.os_version_codename, "DISTRIB_CODENAME="_len); - } - - return ret; -} - -static System::System_t get_system_infos_os_releases() -{ - System::System_t ret; - - debug("calling in System {}", __PRETTY_FUNCTION__); - std::string os_release_path; - constexpr std::array<std::string_view, 3> os_paths = { "/etc/os-release", "/usr/lib/os-release", "/usr/share/os-release" }; - for (const std::string_view path : os_paths) - { - if (std::filesystem::exists(path)) - { - os_release_path = path; - break; - } - } - - std::ifstream os_release_file(os_release_path, std::ios::in); - if (!os_release_file.is_open()) - { - //error(_("Could not open '{}'\nFailed to get OS infos"), os_release_path); - return ret; - } - - // get OS /etc/os-release infos - std::uint16_t iter_index = 0; - std::string line; - while (std::getline(os_release_file, line) && iter_index < 5) - { - if (hasStart(line, "PRETTY_NAME=")) - getFileValue(iter_index, line, ret.os_pretty_name, "PRETTY_NAME="_len); - - else if (hasStart(line, "NAME=")) - getFileValue(iter_index, line, ret.os_name, "NAME="_len); - - else if (hasStart(line, "ID=")) - getFileValue(iter_index, line, ret.os_id, "ID="_len); - - else if (hasStart(line, "VERSION_ID=")) - getFileValue(iter_index, line, ret.os_version_id, "VERSION_ID="_len); - - else if (hasStart(line, "VERSION_CODENAME=")) - getFileValue(iter_index, line, ret.os_version_codename, "VERSION_CODENAME="_len); - } - - return ret; -} - -static unsigned long get_uptime() -{ - const std::string& buf = read_by_syspath("/proc/uptime"); - if (buf != UNKNOWN) - return std::stoul(buf.substr(0,buf.find('.'))); // 19065.18 190952.06 - - struct std::timespec uptime; - if (clock_gettime(CLOCK_BOOTTIME, &uptime) != 0) - return 0; - - return (unsigned long)uptime.tv_sec * 1000 + (unsigned long)uptime.tv_nsec / 1000000; -} - -System::System() -{ - CHECK_INIT(m_bInit); - - if (uname(&m_uname_infos) != 0) - die(_("uname() failed: {}\nCould not get system infos"), strerror(errno)); - - m_uptime = get_uptime(); - m_system_infos = get_system_infos_os_releases(); - if (m_system_infos.os_name == UNKNOWN || m_system_infos.os_pretty_name == UNKNOWN) - m_system_infos = get_system_infos_lsb_releases(); - - get_host_paths(m_system_infos); -} - -// clang-format off -std::string System::kernel_name() noexcept -{ return m_uname_infos.sysname; } - -std::string System::kernel_version() noexcept -{ return m_uname_infos.release; } - -std::string System::hostname() noexcept -{ return m_uname_infos.nodename; } - -std::string System::arch() noexcept -{ return m_uname_infos.machine; } - -unsigned long& System::uptime() noexcept -{ return m_uptime; } - -std::string& System::os_pretty_name() noexcept -{ return m_system_infos.os_pretty_name; } - -std::string& System::os_name() noexcept -{ return m_system_infos.os_name; } - -std::string& System::os_id() noexcept -{ return m_system_infos.os_id; } - -std::string& System::os_versionid() noexcept -{ return m_system_infos.os_version_id; } - -std::string& System::os_version_codename() noexcept -{ return m_system_infos.os_version_codename; } - -std::string& System::host_modelname() noexcept -{ return m_system_infos.host_modelname; } - -std::string& System::host_vendor() noexcept -{ return m_system_infos.host_vendor; } - -std::string& System::host_version() noexcept -{ return m_system_infos.host_version; } - -// clang-format on -std::string& System::os_initsys_name() -{ - static bool done = false; - if (done && !is_live_mode) - return m_system_infos.os_initsys_name; - - // there's no way PID 1 doesn't exist. - // This will always succeed (because we are on linux) - std::ifstream f_initsys("/proc/1/comm", std::ios::binary); - if (!f_initsys.is_open()) - die(_("/proc/1/comm doesn't exist! (what?)")); - - std::string initsys; - std::getline(f_initsys, initsys); - size_t pos = 0; - - if ((pos = initsys.find('\0')) != std::string::npos) - initsys.erase(pos); - - if ((pos = initsys.rfind('/')) != std::string::npos) - initsys.erase(0, pos + 1); - - m_system_infos.os_initsys_name = initsys; - - done = true; - - return m_system_infos.os_initsys_name; -} - -std::string& System::os_initsys_version() -{ - static bool done = false; - if (done && !is_live_mode) - return m_system_infos.os_initsys_version; - - std::string path; - char buf[PATH_MAX]; - if (realpath(which("init").c_str(), buf)) - path = buf; - - std::ifstream f(path, std::ios::in); - std::string line; - - const std::string& name = str_tolower(this->os_initsys_name()); - switch (fnv1a16::hash(name)) - { - case "systemd"_fnv1a16: - case "systemctl"_fnv1a16: - { - while (read_binary_file(f, line)) - { - if (hasEnding(line, "running in %ssystem mode (%s)")) - { - m_system_infos.os_initsys_version = line.substr("systemd "_len); - m_system_infos.os_initsys_version.erase(m_system_infos.os_initsys_version.find(' ')); - break; - } - } - } - break; - case "openrc"_fnv1a16: - { - std::string tmp; - while(read_binary_file(f, line)) - { - if (line == "RC_VERSION") - { - m_system_infos.os_initsys_version = tmp; - break; - } - tmp = line; - } - } - break; - } - done = true; - return m_system_infos.os_initsys_version; -} - -std::string& System::pkgs_installed(const Config& config) -{ - static bool done = false; - if (!done || is_live_mode) - { - m_system_infos.pkgs_installed = get_all_pkgs(config); - done = true; - } - - return m_system_infos.pkgs_installed; -} - -#endif diff --git a/src/query/linux/theme.cpp b/src/query/linux/theme.cpp deleted file mode 100644 index 5d2f06ab..00000000 --- a/src/query/linux/theme.cpp +++ /dev/null @@ -1,567 +0,0 @@ -/* - * Copyright 2025 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_LINUX - -#include <algorithm> -#include <cstdint> - -#include "config.hpp" -#include "fmt/format.h" -#include "parse.hpp" -#include "query.hpp" -#include "rapidxml-1.13/rapidxml.hpp" -#include "switch_fnv1a.hpp" -#include "util.hpp" - -#if USE_DCONF -# include <client/dconf-client.h> -# include <glib/gvariant.h> -#endif - -using namespace Query; - -const std::string& configDir = getHomeConfigDir(); - -static bool get_xsettings_xfce4(const std::string_view property, const std::string_view subproperty, std::string& ret) -{ - static bool done = false; - static rapidxml::xml_document<> doc; - static std::string buffer; - - if (!done) - { - const std::string& path = configDir + "/xfce4/xfconf/xfce-perchannel-xml/xsettings.xml"; - std::ifstream f(path, std::ios::in); - if (!f.is_open()) - return false; - - buffer.assign(std::istreambuf_iterator<char>{f}, std::istreambuf_iterator<char>()); - buffer.push_back('\0'); - - doc.parse<0>(&buffer[0]); - done = true; - } - - rapidxml::xml_node<>* node1 = doc.first_node("channel")->first_node("property"); - for (; node1 && std::string_view(node1->first_attribute("name")->value()) != property; node1 = node1->next_sibling("property")); - - rapidxml::xml_node<>* node2 = node1->first_node("property"); - for (; node2; node2 = node2->next_sibling()) - { - if (std::string_view(node2->first_attribute("name")->value()) == subproperty && - node2->first_attribute("value")) - { - ret = node2->first_attribute("value")->value(); - return true; - } - } - - return false; -} - -// -// -// 1. Cursor -// -static bool assert_cursor(Theme::Theme_t& theme) -{ - return - (theme.cursor != MAGIC_LINE && theme.cursor_size != UNKNOWN) || - (!theme.cursor.empty() && !theme.cursor_size.empty()); -} - -static bool get_cursor_xresources(Theme::Theme_t& theme) -{ - const std::string& path = expandVar("~/.Xresources"); - std::ifstream f(path, std::ios::in); - if (!f.is_open()) - { - theme.cursor = MAGIC_LINE; - theme.cursor_size = UNKNOWN; - return false; - } - - std::uint16_t iter_index = 0; - std::string line; - while (std::getline(f, line) && iter_index < 2) - { - if (hasStart(line, "Xcursor.theme:")) - { - getFileValue(iter_index, line, theme.cursor, "Xcursor.theme:"_len); - strip(theme.cursor); - } - - else if (hasStart(line, "Xcursor.size:")) - { - getFileValue(iter_index, line, theme.cursor_size, "Xcursor.size:"_len); - strip(theme.cursor_size); - } - } - - return assert_cursor(theme); -} - -static bool get_cursor_dconf(const std::string_view de_name, Theme::Theme_t& theme) -{ -#if USE_DCONF - - LOAD_LIBRARY("libdconf.so", return false); - LOAD_LIB_SYMBOL(DConfClient *, dconf_client_new, void); - LOAD_LIB_SYMBOL(GVariant *, dconf_client_read, DConfClient *, const char *); - LOAD_LIB_SYMBOL(const gchar *, g_variant_get_string, GVariant *, gsize *); - LOAD_LIB_SYMBOL(gint32, g_variant_get_int32, GVariant *); - - debug("calling {}", __PRETTY_FUNCTION__); - DConfClient *client = dconf_client_new(); - GVariant *variant; - - std::string interface; - switch(fnv1a16::hash(str_tolower(de_name.data()))) - { - case "cinnamon"_fnv1a16: interface = "/org/cinnamon/desktop/interface/"; break; - case "mate"_fnv1a16: interface = "/org/mate/interface/"; break; - - case "gnome"_fnv1a16: - case "budgie"_fnv1a16: - case "unity"_fnv1a16: - default: - interface = "/org/gnome/desktop/interface/"; - } - - - variant = dconf_client_read(client, (interface + "cursor-theme").c_str()); - if (variant) - theme.cursor = g_variant_get_string(variant, NULL); - - - - variant = dconf_client_read(client, (interface + "cursor-size").c_str()); - if (variant) - theme.cursor_size = fmt::to_string(g_variant_get_int32(variant)); - - return assert_cursor(theme); -#else - return false; -#endif -} - -static bool get_cursor_gsettings(const std::string_view de_name, Theme::Theme_t& theme, const Config& config) -{ - debug("calling {}", __PRETTY_FUNCTION__); - if (get_cursor_dconf(de_name, theme)) - return true; - - if (config.slow_query_warnings) - { - warn(_("customfetch could not detect a gtk configuration file. customfetch will use the much-slower gsettings.")); - warn(_("If there's a file in a standard location that we aren't detecting, please file an issue on our GitHub.")); - info("You can disable this warning by disabling slow-query-warnings in your config.toml file."); - } - - const char* interface; - switch(fnv1a16::hash(str_tolower(de_name.data()))) - { - case "cinnamon"_fnv1a16: interface = "org.cinnamon.desktop.interface"; break; - case "mate"_fnv1a16: interface = "org.mate.interface"; break; - - case "gnome"_fnv1a16: - case "budgie"_fnv1a16: - case "unity"_fnv1a16: - default: - interface = "org.gnome.desktop.interface"; - } - - if (theme.cursor == MAGIC_LINE || theme.cursor.empty()) - { - theme.cursor.clear(); - read_exec({ "gsettings", "get", interface, "cursor-theme" }, theme.cursor); - theme.cursor.erase(std::remove(theme.cursor.begin(), theme.cursor.end(), '\''), theme.cursor.end()); - } - - if (theme.cursor_size == UNKNOWN || theme.cursor_size.empty()) - { - theme.cursor_size.clear(); - read_exec({ "gsettings", "get", interface, "cursor-size" }, theme.cursor_size); - theme.cursor_size.erase(std::remove(theme.cursor_size.begin(), theme.cursor_size.end(), '\''), theme.cursor_size.end()); - } - - return assert_cursor(theme); -} - -static bool get_gtk_cursor_config(const std::string_view path, Theme::Theme_t& theme) -{ - std::ifstream f(path.data(), std::ios::in); - if (!f.is_open()) - return false; - - std::string line; - std::uint16_t iter_index = 0; - while (std::getline(f, line) && iter_index < 2) - { - if (hasStart(line, "gtk-cursor-theme-name=")) - getFileValue(iter_index, line, theme.cursor, "gtk-cursor-theme-name="_len); - - else if (hasStart(line, "gtk-cursor-theme-size=")) - getFileValue(iter_index, line, theme.cursor_size, "gtk-cursor-theme-size="_len); - } - - return assert_cursor(theme); -} - -static bool get_cursor_from_gtk_configs(const std::uint8_t ver, Theme::Theme_t& theme) -{ - if (get_gtk_cursor_config(fmt::format("{}/gtk-{}.0/settings.ini", configDir, ver), theme)) - return true; - - if (get_gtk_cursor_config(fmt::format("{}/gtk-{}.0/gtkrc", configDir, ver), theme)) - return true; - - if (get_gtk_cursor_config(fmt::format("{}/gtkrc-{}.0", configDir, ver), theme)) - return true; - - if (get_gtk_cursor_config(fmt::format("{}/.gtkrc-{}.0", std::getenv("HOME"), ver), theme)) - return true; - - if (get_gtk_cursor_config(fmt::format("{}/.gtkrc-{}.0-kde", std::getenv("HOME"), ver), theme)) - return true; - - if (get_gtk_cursor_config(fmt::format("{}/.gtkrc-{}.0-kde4", std::getenv("HOME"), ver), theme)) - return true; - - return false; -} - -static bool get_de_cursor(const std::string_view de_name, Theme::Theme_t& theme) -{ - switch (fnv1a16::hash(str_tolower(de_name.data()))) - { - case "xfce"_fnv1a16: - case "xfce4"_fnv1a16: - { - debug("calling {} and getting info on xfce4", __PRETTY_FUNCTION__); - get_xsettings_xfce4("Gtk", "CursorThemeName", theme.cursor); - get_xsettings_xfce4("Gtk", "CursorThemeSize", theme.cursor_size); - - return assert_cursor(theme); - - } break; - } - - return false; -} - -// -// -// 2. GTK theme -// -static bool assert_gtk_theme(Theme::Theme_t& theme) -{ - return - (theme.gtk_font != MAGIC_LINE && theme.gtk_icon_theme != MAGIC_LINE && theme.gtk_theme_name != MAGIC_LINE) || - (!theme.gtk_font.empty() && !theme.gtk_theme_name.empty() && !theme.gtk_icon_theme.empty()); -} - -static bool get_gtk_theme_config(const std::string_view path, Theme::Theme_t& theme) -{ - std::ifstream f(path.data(), std::ios::in); - if (!f.is_open()) - return false; - - std::string line; - std::uint16_t iter_index = 0; - while (std::getline(f, line) && iter_index < 3) - { - if (hasStart(line, "gtk-theme-name=")) - getFileValue(iter_index, line, theme.gtk_theme_name, "gtk-theme-name="_len); - - else if (hasStart(line, "gtk-icon-theme-name=")) - getFileValue(iter_index, line, theme.gtk_icon_theme, "gtk-icon-theme-name="_len); - - else if (hasStart(line, "gtk-font-name=")) - getFileValue(iter_index, line, theme.gtk_font, "gtk-font-name="_len); - } - - return assert_gtk_theme(theme); -} - -static bool get_gtk_theme_dconf(const std::string_view de_name, Theme::Theme_t& theme) -{ -#if USE_DCONF - - LOAD_LIBRARY("libdconf.so", return false); - LOAD_LIB_SYMBOL(DConfClient *, dconf_client_new, void); - LOAD_LIB_SYMBOL(GVariant *, dconf_client_read, DConfClient *client, const char *); - LOAD_LIB_SYMBOL(const gchar *, g_variant_get_string, GVariant *value, gsize *lenght); - - debug("calling {}", __PRETTY_FUNCTION__); - DConfClient *client = dconf_client_new(); - GVariant *variant; - - std::string interface; - switch(fnv1a16::hash(str_tolower(de_name.data()))) - { - case "cinnamon"_fnv1a16: interface = "/org/cinnamon/desktop/interface/"; break; - case "mate"_fnv1a16: interface = "/org/mate/interface/"; break; - - case "gnome"_fnv1a16: - case "budgie"_fnv1a16: - case "unity"_fnv1a16: - default: - interface = "/org/gnome/desktop/interface/"; - } - - if (theme.gtk_theme_name == MAGIC_LINE || theme.gtk_theme_name.empty()) - { - variant = dconf_client_read(client, (interface + "gtk-theme").c_str()); - if (variant) - theme.gtk_theme_name = g_variant_get_string(variant, NULL); - } - - if (theme.gtk_icon_theme == MAGIC_LINE || theme.gtk_icon_theme.empty()) - { - variant = dconf_client_read(client, (interface + "icon-theme").c_str()); - if (variant) - theme.gtk_icon_theme = g_variant_get_string(variant, NULL); - } - - if (theme.gtk_font == MAGIC_LINE || theme.gtk_font.empty()) - { - variant = dconf_client_read(client, (interface + "font-name").c_str()); - if (variant) - theme.gtk_font = g_variant_get_string(variant, NULL); - } - - return assert_gtk_theme(theme); - -#else - return false; -#endif -} - -static void get_gtk_theme_gsettings(const std::string_view de_name, Theme::Theme_t& theme, const Config& config) -{ - debug("calling {}", __PRETTY_FUNCTION__); - - if (theme.gtk_theme_name == MAGIC_LINE || theme.gtk_theme_name.empty()) - { - const char* gtk_theme_env = std::getenv("GTK_THEME"); - - if (gtk_theme_env) - theme.gtk_theme_name = gtk_theme_env; - } - - if (get_gtk_theme_dconf(de_name, theme)) - return; - - if (config.slow_query_warnings) - { - warn(_("customfetch could not detect a gtk configuration file. customfetch will use the much-slower gsettings.")); - warn(_("If there's a file in a standard location that we aren't detecting, please file an issue on our GitHub.")); - info(_("You can disable this warning by disabling slow-query-warnings in your config.toml file.")); - } - - const char* interface; - switch(fnv1a16::hash(str_tolower(de_name.data()))) - { - case "cinnamon"_fnv1a16: interface = "org.cinnamon.desktop.interface"; break; - case "mate"_fnv1a16: interface = "org.mate.interface"; break; - - case "gnome"_fnv1a16: - case "budgie"_fnv1a16: - case "unity"_fnv1a16: - default: - interface = "org.gnome.desktop.interface"; - } - - if (theme.gtk_theme_name == MAGIC_LINE || theme.gtk_theme_name.empty()) - { - theme.gtk_theme_name.clear(); - read_exec({ "gsettings", "get", interface, "gtk-theme" }, theme.gtk_theme_name); - } - - if (theme.gtk_icon_theme == MAGIC_LINE || theme.gtk_icon_theme.empty()) - { - theme.gtk_icon_theme.clear(); - read_exec({ "gsettings", "get", interface, "icon-theme" }, theme.gtk_icon_theme); - } - - if (theme.gtk_font == MAGIC_LINE || theme.gtk_font.empty()) - { - theme.gtk_font.clear(); - read_exec({ "gsettings", "get", interface, "font-name" }, theme.gtk_font); - } - - theme.gtk_theme_name.erase(std::remove(theme.gtk_theme_name.begin(), theme.gtk_theme_name.end(), '\''), theme.gtk_theme_name.end()); - theme.gtk_icon_theme.erase(std::remove(theme.gtk_icon_theme.begin(), theme.gtk_icon_theme.end(), '\''), theme.gtk_icon_theme.end()); - theme.gtk_font.erase(std::remove(theme.gtk_font.begin(), theme.gtk_font.end(), '\''), theme.gtk_font.end()); -} - -static void get_gtk_theme_from_configs(const std::uint8_t ver, const std::string_view de_name, Theme::Theme_t& theme, const Config& config) -{ - if (get_gtk_theme_config(fmt::format("{}/gtk-{}.0/settings.ini", configDir, ver), theme)) - return; - - if (get_gtk_theme_config(fmt::format("{}/gtk-{}.0/gtkrc", configDir, ver), theme)) - return; - - if (get_gtk_theme_config(fmt::format("{}/gtkrc-{}.0", configDir, ver), theme)) - return; - - if (get_gtk_theme_config(fmt::format("{}/.gtkrc-{}.0", std::getenv("HOME"), ver), theme)) - return; - - if (get_gtk_theme_config(fmt::format("{}/.gtkrc-{}.0-kde", std::getenv("HOME"), ver), theme)) - return; - - if (get_gtk_theme_config(fmt::format("{}/.gtkrc-{}.0-kde4", std::getenv("HOME"), ver), theme)) - return; - - get_gtk_theme_gsettings(de_name, theme, config); -} - -static void get_de_gtk_theme(const std::string_view de_name, const std::uint8_t ver, Theme::Theme_t& theme, const Config& config) -{ - switch (fnv1a16::hash(str_tolower(de_name.data()))) - { - case "xfce"_fnv1a16: - case "xfce4"_fnv1a16: - { - debug("calling {} and getting info on xfce4", __PRETTY_FUNCTION__); - get_xsettings_xfce4("Net", "ThemeName", theme.gtk_theme_name); - get_xsettings_xfce4("Net", "IconThemeName", theme.gtk_icon_theme); - get_xsettings_xfce4("Gtk", "FontName", theme.gtk_font); - - if (!assert_gtk_theme(theme)) - get_gtk_theme_from_configs(ver, de_name, theme, config); - - } break; - - default: - get_gtk_theme_from_configs(ver, de_name, theme, config); - } -} - -static void get_gtk_theme(const bool dont_query_dewm, const std::uint8_t ver, const std::string_view de_name, - Theme::Theme_t& theme, const Config& config, const bool gsettings_only) -{ - if (gsettings_only) - get_gtk_theme_gsettings(de_name, theme, config); - else if (dont_query_dewm) - get_gtk_theme_from_configs(ver, de_name, theme, config); - else - get_de_gtk_theme(de_name, ver, theme, config); -} - -// clang-format off -Theme::Theme(const std::uint8_t ver, systemInfo_t& queried_themes, - const std::string& theme_name_version, const Config& config, const bool gsettings_only) - : m_theme_name(theme_name_version), - m_queried_themes(queried_themes) -{ - if (queried_themes.find(theme_name_version) != queried_themes.end()) - return; - - const std::string& wm_name = query_user.wm_name(query_user.m_bDont_query_dewm, query_user.term_name()); - const std::string& de_name = query_user.de_name(query_user.m_bDont_query_dewm, query_user.term_name(), wm_name); - - if (((de_name != MAGIC_LINE && wm_name != MAGIC_LINE) && - de_name == wm_name) || de_name == MAGIC_LINE) - m_wmde_name = wm_name; - else - m_wmde_name = de_name; - - get_gtk_theme(query_user.m_bDont_query_dewm, ver, m_wmde_name, m_theme_infos, config, gsettings_only); - - if (m_theme_infos.gtk_theme_name.empty()) - m_theme_infos.gtk_theme_name = MAGIC_LINE; - - if (m_theme_infos.gtk_font.empty()) - m_theme_infos.gtk_font = MAGIC_LINE; - - if (m_theme_infos.gtk_icon_theme.empty()) - m_theme_infos.gtk_icon_theme = MAGIC_LINE; - - m_queried_themes.insert( - {m_theme_name, { - {"theme-name", variant(m_theme_infos.gtk_theme_name)}, - {"icon-theme-name", variant(m_theme_infos.gtk_icon_theme)}, - {"font-name", variant(m_theme_infos.gtk_font)}, - }} - ); -} - -// only use it for cursor -Theme::Theme(systemInfo_t& queried_themes, const Config& config, const bool gsettings_only) : m_queried_themes(queried_themes) -{ - const std::string& wm_name = query_user.wm_name(query_user.m_bDont_query_dewm, query_user.term_name()); - const std::string& de_name = query_user.de_name(query_user.m_bDont_query_dewm, query_user.term_name(), wm_name); - - if (((de_name != MAGIC_LINE && wm_name != MAGIC_LINE) && - de_name == wm_name) || de_name == MAGIC_LINE) - m_wmde_name = wm_name; - else - m_wmde_name = de_name; - - if (gsettings_only) { get_cursor_gsettings(m_wmde_name, m_theme_infos, config); } - else if (get_de_cursor(m_wmde_name, m_theme_infos)){} - else if (get_cursor_from_gtk_configs(4, m_theme_infos)){} - else if (get_cursor_from_gtk_configs(3, m_theme_infos)){} - else if (get_cursor_from_gtk_configs(2, m_theme_infos)){} - else if (get_cursor_xresources(m_theme_infos)){} - else get_cursor_gsettings(m_wmde_name, m_theme_infos, config); - - if (m_theme_infos.cursor.empty()) - m_theme_infos.cursor = MAGIC_LINE; - else - { - size_t pos = 0; - if ((pos = m_theme_infos.cursor.rfind("cursor")) != std::string::npos) - m_theme_infos.cursor.erase(pos); - - if ((pos = m_theme_infos.cursor.rfind('_')) != std::string::npos) - m_theme_infos.cursor.erase(pos - 1); - - } - -} - -std::string Theme::gtk_theme() noexcept -{ return getInfoFromName(m_queried_themes, m_theme_name, "theme-name"); } - -std::string Theme::gtk_icon_theme() noexcept -{ return getInfoFromName(m_queried_themes, m_theme_name, "icon-theme-name"); } - -std::string Theme::gtk_font() noexcept -{ return getInfoFromName(m_queried_themes, m_theme_name, "font-name"); } - -std::string& Theme::cursor() noexcept -{ return m_theme_infos.cursor; } - -std::string& Theme::cursor_size() noexcept -{ return m_theme_infos.cursor_size; } - -#endif // CF_LINUX diff --git a/src/query/linux/user.cpp b/src/query/linux/user.cpp deleted file mode 100644 index 32b306cc..00000000 --- a/src/query/linux/user.cpp +++ /dev/null @@ -1,669 +0,0 @@ -/* - * Copyright 2025 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_LINUX || CF_MACOS - -#include <dlfcn.h> -#include <unistd.h> - -#include <cctype> -#include <cstdlib> -#include <cstring> -#include <filesystem> -#include <fstream> -#include <string> - -#if __has_include(<sys/socket.h>) && __has_include(<wayland-client.h>) -#include <sys/socket.h> -#include <wayland-client.h> -#endif - -// #if __has_include(<sys/socket.h>) && __has_include(<X11/Xlib.h>) -// # include <X11/Xlib.h> -// #endif - -#include "query.hpp" -#if CF_MACOS -#include "rapidxml-1.13/rapidxml.hpp" -#endif -#include "switch_fnv1a.hpp" -#include "util.hpp" -#include "utils/dewm.hpp" -#include "utils/term.hpp" - -using namespace Query; - -static std::string get_de_name() -{ - std::string ret = parse_de_env(); - debug("get_de_name = {}", ret); - if (hasStart(ret, "X-")) - ret.erase(0, 2); - - return ret; -} - -static std::string get_wm_name(std::string& wm_path_exec) -{ - std::string path, proc_name, wm_name; - const uid_t uid = getuid(); - - for (auto const& dir_entry : std::filesystem::directory_iterator{ "/proc/" }) - { - if (!std::isdigit((dir_entry.path().string().at(6)))) // /proc/5 - continue; - - path = dir_entry.path() / "loginuid"; - std::ifstream f_uid(path, std::ios::binary); - std::string s_uid; - std::getline(f_uid, s_uid); - if (std::stoul(s_uid) != uid) - continue; - - path = dir_entry.path() / "cmdline"; - std::ifstream f_cmdline(path, std::ios::binary); - std::getline(f_cmdline, proc_name); - - size_t pos = 0; - if ((pos = proc_name.find('\0')) != std::string::npos) - proc_name.erase(pos); - - if ((pos = proc_name.rfind('/')) != std::string::npos) - proc_name.erase(0, pos + 1); - - debug("WM proc_name = {}", proc_name); - - if ((wm_name = prettify_wm_name(proc_name)) == MAGIC_LINE) - continue; - - char buf[PATH_MAX]; - wm_path_exec = realpath((dir_entry.path().string() + "/exe").c_str(), buf); - break; - } - - debug("wm_name = {}", wm_name); - if (wm_name.empty()) - return MAGIC_LINE; - - return wm_name; -} - -static std::string get_de_version(const std::string_view de_name) -{ - switch (fnv1a16::hash(str_tolower(de_name.data()))) - { - case "mate"_fnv1a16: return get_mate_version(); - case "cinnamon"_fnv1a16: return get_cinnamon_version(); - - case "kde"_fnv1a16: return get_kwin_version(); - - case "xfce"_fnv1a16: - case "xfce4"_fnv1a16: return get_xfce4_version(); - - case "gnome"_fnv1a16: - case "gnome-shell"_fnv1a16: - { - std::string ret; - read_exec({ "gnome-shell", "--version" }, ret); - ret.erase(0, ret.rfind(' ')); - return ret; - } - default: - { - std::string ret; - read_exec({ de_name.data(), "--version" }, ret); - ret.erase(0, ret.rfind(' ')); - return ret; - } - } -} - -static std::string get_wm_wayland_name(std::string& wm_path_exec) -{ -#if __has_include(<sys/socket.h>) && __has_include(<wayland-client.h>) - LOAD_LIBRARY("libwayland-client.so", return get_wm_name(wm_path_exec);) - - LOAD_LIB_SYMBOL(wl_display*, wl_display_connect, const char* name) - LOAD_LIB_SYMBOL(void, wl_display_disconnect, wl_display* display) - LOAD_LIB_SYMBOL(int, wl_display_get_fd, wl_display* display) - - std::string ret = MAGIC_LINE; - - struct wl_display* display = wl_display_connect(NULL); - - struct ucred ucred; - socklen_t len = sizeof(struct ucred); - if (getsockopt(wl_display_get_fd(display), SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) - return MAGIC_LINE; - - std::ifstream f(fmt::format("/proc/{}/comm", ucred.pid), std::ios::in); - f >> ret; - wl_display_disconnect(display); - - char buf[PATH_MAX]; - wm_path_exec = realpath(fmt::format("/proc/{}/exe", ucred.pid).c_str(), buf); - - UNLOAD_LIBRARY() - - return prettify_wm_name(ret); -#else - return get_wm_name(wm_path_exec); -#endif -} - -static std::string get_shell_version(const std::string_view shell_name) -{ - std::string ret; - - if (shell_name == "nu") - ret = read_shell_exec("nu -c \"version | get version\""); - else - ret = read_shell_exec(fmt::format("{} -c 'echo \"${}_VERSION\"'", shell_name, str_toupper(shell_name.data()))); - - strip(ret); - return ret; -} - -static std::string get_shell_name(const std::string_view shell_path) -{ - return shell_path.substr(shell_path.rfind('/') + 1).data(); -} - -// clang-format off -static std::string get_term_name_env(bool get_default = false) -{ - if (getenv("SSH_TTY") != NULL) - return getenv("SSH_TTY"); - - if (getenv("KITTY_PID") != NULL || - getenv("KITTY_INSTALLATION_DIR") != NULL || - getenv("KITTY_PUBLIC_KEY") != NULL || - getenv("KITTY_WINDOW_ID") != NULL) - return "kitty"; - - if (getenv("ALACRITTY_SOCKET") != NULL || - getenv("ALACRITTY_LOG") != NULL || - getenv("ALACRITTY_WINDOW_ID") != NULL) - return "alacritty"; - - if (getenv("TERMUX_VERSION") != NULL || - getenv("TERMUX_MAIN_PACKAGE_FORMAT") != NULL) - return "com.termux"; - - if(getenv("KONSOLE_VERSION") != NULL) - return "konsole"; - - if (getenv("GNOME_TERMINAL_SCREEN") != NULL || - getenv("GNOME_TERMINAL_SERVICE") != NULL) - return "gnome-terminal"; - - if (get_default) - { - char *env = getenv("TERM_PROGRAM"); - if (env != NULL) - { - if (hasStart(env, "Apple")) - return "Apple Terminal"; - - return env; - } - - env = getenv("TERM"); - if (env != NULL) - return env; - } - - return UNKNOWN; -} -// clang-format on - -#if CF_LINUX -static std::string get_term_name(std::string& term_ver, const std::string_view osname) -{ - // customfetch -> shell -> terminal - const pid_t ppid = getppid(); - std::ifstream ppid_f(fmt::format("/proc/{}/status", ppid), std::ios::in); - std::string line, term_pid{ "0" }; - while (std::getline(ppid_f, line)) - { - if (hasStart(line, "PPid:")) - { - term_pid = line.substr("PPid:"_len); - strip(term_pid); - break; - } - } - debug("term_pid = {}", term_pid); - - if (std::stoi(term_pid) < 1) - return MAGIC_LINE; - - std::ifstream f("/proc/" + term_pid + "/comm", std::ios::in); - std::string term_name; - if (f.is_open()) - std::getline(f, term_name); - else - term_name = get_term_name_env(true); - - // st (suckless terminal) - if (term_name == "exe") - term_name = "st"; - - // either gnome-console or "gnome-terminal-" - // I hope this is not super stupid - if (hasStart(term_name, "gnome-console")) - term_name.erase("gnome-console"_len + 1); - else if (hasStart(term_name, "gnome-terminal")) - term_name.erase("gnome-terminal"_len + 1); - - - // let's try to get the real terminal name - // on NixOS, instead of returning the -wrapped name. - // tested on gnome-console, kitty, st and alacritty - // hope now NixOS users will know the terminal they got, along the version if possible - if (osname.find("NixOS") != osname.npos || - (hasEnding(term_name, "wrapped") && which("nix") != UNKNOWN)) - { - // /nix/store/sha256string-gnome-console-0.31.0/bin/.kgx-wrapped - char buf[PATH_MAX]; - std::string tmp_name = realpath(("/proc/" + term_pid + "/exe").c_str(), buf); - - size_t pos; - if ((pos = tmp_name.find('-')) != std::string::npos) - tmp_name.erase(0, pos + 1); // gnome-console-0.31.0/bin/.kgx-wrapped - - if ((pos = tmp_name.find('/')) != std::string::npos) - tmp_name.erase(pos); // gnome-console-0.31.0 - - if ((pos = tmp_name.rfind('-')) != std::string::npos) - { - term_ver = tmp_name.substr(pos + 1); - tmp_name.erase(pos); // gnome-console EZ - } - - term_name = tmp_name; - } - - // sometimes may happen that the terminal name from /comm - // at the end has some letfover characters from /cmdline - if (!std::isalnum(term_name.back())) - { - size_t i = term_name.size(); - while (i > 0) - { - char ch = term_name[i - 1]; - // stop when we find an a num or alpha char - // example with "gnome-terminal-" - if (std::isalnum(static_cast<unsigned char>(ch))) - break; - term_name.erase(--i, 1); - } - } - - return term_name; -} -#elif CF_MACOS -#include <libproc.h> -#include <sys/proc_info.h> -#include <sys/sysctl.h> - -pid_t get_ppid(pid_t pid) -{ - struct kinfo_proc info; - size_t len = sizeof(info); - int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid }; - if (sysctl(mib, 4, &info, &len, NULL, 0) == -1) - return -1; - return info.kp_eproc.e_ppid; -} - -static std::string get_term_name(std::string& term_ver, const std::string_view osname) -{ - std::string term{ get_term_name_env() }; - if (term != UNKNOWN) - return term; - - // customfetch -> shell -> terminal - pid_t terminal_pid = 0; - pid_t current = getpid(); - while ((current = get_ppid(current)) > 1) - { - terminal_pid = current; - debug("Terminal PID: {}", terminal_pid); - } - - char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; - if (proc_pidpath(terminal_pid, pathbuf, sizeof(pathbuf)) <= 0) - return UNKNOWN; - - std::string path{ pathbuf }; - if (hasEnding(path, "Terminal.app/Contents/MacOS/Terminal")) - return "Apple Terminal"; - - size_t pos = path.rfind('/'); - if (pos != path.npos) - path.erase(0, pos + 1); - - return path; -} -#endif - -static std::string get_term_version(const std::string_view term_name) -{ - if (term_name.empty()) - return UNKNOWN; - - bool remove_term_name = true; - std::string ret; - -#if CF_MACOS - if (term_name == "Apple Terminal") - { - std::ifstream f("/System/Applications/Utilities/Terminal.app/Contents/version.plist", std::ios::in); - if (!f.is_open()) - goto skip; - - std::string buffer(std::istreambuf_iterator<char>{f}, std::istreambuf_iterator<char>{}); - buffer.push_back('\0'); - - rapidxml::xml_document<> doc; - doc.parse<0>(&buffer[0]); - rapidxml::xml_node<>* root_node = doc.first_node("plist")->first_node("dict")->first_node("key"); - - for (; root_node; root_node = root_node->next_sibling()) - { - const std::string_view key = root_node->value(); // <key>ProductName</key> - root_node = root_node->next_sibling(); - const std::string_view value = root_node->value(); // <string>macOS</string> - if (key == "CFBundleVersion") - return value.data(); - } - } - -skip: -#endif - - switch (fnv1a16::hash(str_tolower(term_name.data()))) - { - case "st"_fnv1a16: - if (fast_detect_st_ver(ret)) - remove_term_name = false; - break; - - case "konsole"_fnv1a16: - if (fast_detect_konsole_ver(ret)) - remove_term_name = false; - break; - - case "xterm"_fnv1a16: get_term_version_exec(term_name, ret, true); break; - - default: get_term_version_exec(term_name, ret); - } - - debug("get_term_version ret = {}", ret); - - if (ret.empty()) - return UNKNOWN; - - if (hasStart(ret, "# GNOME")) - { - if (hasStart(ret, "# GNOME Console ")) - ret.erase(0, "# GNOME Console"_len); - else if (hasStart(ret, "# GNOME Terminal ")) - ret.erase(0, "# GNOME Terminal "_len); - debug("gnome ret = {}", ret); - remove_term_name = false; - } - // Xterm(388) - else if (term_name == "xterm") - { - ret.erase(0, term_name.length() + 1); // 388) - ret.pop_back(); // 388 - return ret; - } - - if (remove_term_name) - ret.erase(0, term_name.length() + 1); - - const size_t pos = ret.find(' '); - if (pos != std::string::npos) - ret.erase(pos); - - debug("get_term_version ret after = {}", ret); - return ret; -} - -User::User() noexcept -{ - CHECK_INIT(m_bInit); - - if (m_pPwd = getpwuid(getuid()), !m_pPwd) - die(_("getpwent failed: {}\nCould not get user infos"), std::strerror(errno)); -} - -// clang-format off -std::string User::name() noexcept -{ return m_pPwd->pw_name; } - -std::string User::shell_path() noexcept -{ return m_pPwd->pw_shell; } - -// clang-format on -// Be ready to loose some brain cells from now on -std::string& User::shell_name() noexcept -{ - static bool done = false; - if (!done) - { - m_users_infos.shell_name = get_shell_name(this->shell_path()); - done = true; - } - - return m_users_infos.shell_name; -} - -std::string& User::shell_version(const std::string_view shell_name) -{ - if (m_users_infos.shell_name.empty()) - { - m_users_infos.shell_version = UNKNOWN; - return m_users_infos.shell_version; - } - - static bool done = false; - if (!done) - { - m_users_infos.shell_version = get_shell_version(shell_name); - done = true; - } - - return m_users_infos.shell_version; -} - -std::string& User::wm_name(bool dont_query_dewm, const std::string_view term_name) -{ - if (dont_query_dewm || hasStart(term_name, "/dev") || CF_MACOS) - { - m_users_infos.wm_name = MAGIC_LINE; - return m_users_infos.wm_name; - } - - static bool done = false; - debug("CALLING {} || done = {} && de_name = {} && wm_name = {}", __func__, done, m_users_infos.de_name, - m_users_infos.wm_name); - - if (!done) - { - const char* env = std::getenv("WAYLAND_DISPLAY"); - if (env != nullptr && env[0] != '\0') - m_users_infos.wm_name = get_wm_wayland_name(m_users_infos.m_wm_path); - else - m_users_infos.wm_name = get_wm_name(m_users_infos.m_wm_path); - - if (m_users_infos.de_name == m_users_infos.wm_name) - m_users_infos.de_name = MAGIC_LINE; - - done = true; - } - - return m_users_infos.wm_name; -} - -std::string& User::wm_version(bool dont_query_dewm, const std::string_view term_name) -{ - if (dont_query_dewm || hasStart(term_name, "/dev") || CF_MACOS) - { - m_users_infos.wm_name = MAGIC_LINE; - return m_users_infos.wm_name; - } - - static bool done = false; - if (!done) - { - m_users_infos.wm_version.clear(); - if (m_users_infos.wm_name == "Xfwm4" && - get_fast_xfwm4_version(m_users_infos.wm_version, m_users_infos.m_wm_path)) - { - done = true; - goto _return; - } - - if (m_users_infos.wm_name == "dwm") - read_exec({ m_users_infos.m_wm_path.c_str(), "-v" }, m_users_infos.wm_version, true); - else - read_exec({ m_users_infos.m_wm_path.c_str(), "--version" }, m_users_infos.wm_version); - - if (m_users_infos.wm_name == "Xfwm4") - m_users_infos.wm_version.erase(0, "\tThis is xfwm4 version "_len); // saying only "xfwm4 4.18.2 etc." no? - else - m_users_infos.wm_version.erase(0, m_users_infos.wm_name.length() + 1); - - const size_t pos = m_users_infos.wm_version.find(' '); - if (pos != std::string::npos) - m_users_infos.wm_version.erase(pos); - - done = true; - } - -_return: - return m_users_infos.wm_version; -} - -std::string& User::de_name(bool dont_query_dewm, const std::string_view term_name, const std::string_view wm_name) -{ - // first let's see if we are not in a tty or if the user doesn't want to - // if so don't even try to get the DE or WM names - // they waste times - if (dont_query_dewm || hasStart(term_name, "/dev") || CF_MACOS) - { - m_users_infos.de_name = MAGIC_LINE; - return m_users_infos.de_name; - } - - static bool done = false; - debug("CALLING {} || done = {} && de_name = {} && wm_name = {}", __func__, done, m_users_infos.de_name, - m_users_infos.wm_name); - - if (!done) - { - if ((m_users_infos.de_name != MAGIC_LINE && wm_name != MAGIC_LINE) && m_users_infos.de_name == wm_name) - { - m_users_infos.de_name = MAGIC_LINE; - done = true; - return m_users_infos.de_name; - } - - m_users_infos.de_name = get_de_name(); - if (m_users_infos.de_name == m_users_infos.wm_name) - m_users_infos.de_name = MAGIC_LINE; - - done = true; - } - - return m_users_infos.de_name; -} - -std::string& User::de_version(const std::string_view de_name) -{ - if (m_bDont_query_dewm || de_name == UNKNOWN || de_name == MAGIC_LINE || de_name.empty() || CF_MACOS) - { - m_users_infos.de_version = UNKNOWN; - return m_users_infos.de_version; - } - - static bool done = false; - if (!done) - { - m_users_infos.de_version = get_de_version(str_tolower(de_name.data())); - done = true; - } - - return m_users_infos.de_version; -} - -std::string& User::term_name() -{ - static bool done = false; - if (done || is_live_mode) - return m_users_infos.term_name; - - Query::System query_sys; - m_users_infos.term_name = get_term_name(m_users_infos.term_version, query_sys.os_name()); - if (hasStart(str_tolower(m_users_infos.term_name), "login") || hasStart(m_users_infos.term_name, "init") || - hasStart(m_users_infos.term_name, "(init)")) - { - m_users_infos.term_name = ttyname(STDIN_FILENO); - m_users_infos.term_version = "NO VERSIONS ABOSULETY"; // lets not make it unknown - m_bDont_query_dewm = true; - } - - done = true; - - return m_users_infos.term_name; -} - -std::string& User::term_version(const std::string_view term_name) -{ - static bool done = false; - if (done || is_live_mode) - return m_users_infos.term_version; - - if (m_users_infos.term_version == "NO VERSIONS ABOSULETY") - { - m_users_infos.term_version.clear(); - goto done; - } - else if (m_users_infos.term_version != MAGIC_LINE) - goto done; - - m_users_infos.term_version = get_term_version(term_name); -done: - done = true; - - return m_users_infos.term_version; -} - -#endif // CF_LINUX diff --git a/src/query/linux/utils/term.hpp b/src/query/linux/utils/term.hpp deleted file mode 100644 index 6365a407..00000000 --- a/src/query/linux/utils/term.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef _TERM_HPP -#define _TERM_HPP - -#include <string> - -void get_term_version_exec(const std::string_view term, std::string& ret, bool _short = false, bool _stderr = false); - -bool fast_detect_konsole_ver(std::string& ret); -bool fast_detect_st_ver(std::string& ret); - -#endif // _TERM_HPP diff --git a/src/query/macos/battery.cpp b/src/query/macos/battery.cpp deleted file mode 100644 index c92c5273..00000000 --- a/src/query/macos/battery.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2025 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_MACOS - -#include "query.hpp" -using namespace Query; - -Battery::Battery() -{ - CHECK_INIT(m_bInit); -} - -// clang-format off -std::string& Battery::modelname() noexcept -{ return m_battery_infos.modelname; } - -std::string& Battery::status() noexcept -{ return m_battery_infos.status; } - -std::string& Battery::vendor() noexcept -{ return m_battery_infos.vendor; } - -std::string& Battery::technology() noexcept -{ return m_battery_infos.technology; } - -std::string& Battery::capacity_level() noexcept -{ return m_battery_infos.capacity_level; } - -double& Battery::perc() noexcept -{ return m_battery_infos.perc; } - -double& Battery::temp() noexcept -{ return m_battery_infos.temp; } - -#endif // CF_LINUX diff --git a/src/query/macos/disk.cpp b/src/query/macos/disk.cpp deleted file mode 100644 index 0b52974c..00000000 --- a/src/query/macos/disk.cpp +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2025 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_MACOS - -#include <cstring> -#include <sys/types.h> - -#include "query.hpp" -#include "util.hpp" - -using namespace Query; - -static std::string format_auto_query_string(std::string str, const struct statfs* fs) -{ - replace_str(str, "%1", fs->f_mntonname); - replace_str(str, "%2", fs->f_mntfromname); - replace_str(str, "%3", fs->f_fstypename); - - replace_str(str, "%4", fmt::format("$<disk({}).total>", fs->f_mntonname)); - replace_str(str, "%5", fmt::format("$<disk({}).free>", fs->f_mntonname)); - replace_str(str, "%6", fmt::format("$<disk({}).used>", fs->f_mntonname)); - replace_str(str, "%7", fmt::format("$<disk({}).used_perc>", fs->f_mntonname)); - replace_str(str, "%8", fmt::format("$<disk({}).free_perc>", fs->f_mntonname)); - - return str; -} - -static int get_disk_type(const int flags) -{ - int type = 0; - if (flags & MNT_DONTBROWSE) - type = DISK_VOLUME_TYPE_HIDDEN; - else if (flags & MNT_REMOVABLE || !(flags & MNT_LOCAL)) - type = DISK_VOLUME_TYPE_EXTERNAL; - else - type = DISK_VOLUME_TYPE_REGULAR; - - if (flags & MNT_RDONLY) - type |= DISK_VOLUME_TYPE_READ_ONLY; - - return type; -} - -Disk::Disk(const std::string& path, systemInfo_t& queried_paths, parse_args_t& parse_args, - const bool auto_module) -{ - if (queried_paths.find(path) != queried_paths.end() && !is_live_mode) - { - m_disk_infos.device = getInfoFromName(queried_paths, path, "device"); - m_disk_infos.mountdir = getInfoFromName(queried_paths, path, "mountdir"); - m_disk_infos.typefs = getInfoFromName(queried_paths, path, "typefs"); - m_disk_infos.total_amount = std::stod(getInfoFromName(queried_paths, path, "total_amount")); - m_disk_infos.used_amount = std::stod(getInfoFromName(queried_paths, path, "used_amount")); - m_disk_infos.free_amount = std::stod(getInfoFromName(queried_paths, path, "free_amount")); - return; - } - - if (access(path.data(), F_OK) != 0 && !auto_module) - { - // if user is using $<disk(path)> or $<disk(path).fs> - // then let's just "try" to remove it - m_disk_infos.typefs = MAGIC_LINE; - m_disk_infos.device = MAGIC_LINE; - m_disk_infos.mountdir = MAGIC_LINE; - return; - } - - if (auto_module) - { - const int size = getfsstat(NULL, 0, MNT_WAIT); - if (size <= 0) - die(_("Failed to get Disk infos")); - - struct statfs* buf = reinterpret_cast<struct statfs*>(malloc(sizeof(*buf) * (unsigned)size)); - if (getfsstat(buf, (int) (sizeof(*buf) * (unsigned) size), MNT_NOWAIT) <= 0) - die(_("Failed to get Disk infos")); - - for (struct statfs* fs = buf; fs < buf + size; ++fs) - { - if (strcmp(fs->f_mntonname, "/") != 0 && !hasStart(fs->f_mntfromname, "/dev/")) - continue; - - m_disk_infos.types_disk = get_disk_type(fs->f_flags); - if (!(parse_args.config.auto_disks_types & m_disk_infos.types_disk)) - continue; - - if (!parse_args.config.auto_disks_show_dupl) - { - const auto& it = std::find(m_queried_devices.begin(), m_queried_devices.end(), fs->f_mntfromname); - if (it != m_queried_devices.end()) - continue; - - m_queried_devices.push_back(fs->f_mntfromname); - } - - parse_args.no_more_reset = false; - m_disks_formats.push_back( - parse(format_auto_query_string(parse_args.config.auto_disks_fmt, fs), parse_args) - ); - } - - free(buf); - return; - } - - struct statfs fs; - if (statfs(path.c_str(), &fs) != 0) - { - perror("statvfs"); - error(_("Failed to get disk info at {}"), path); - return; - } - - if (path != fs.f_mntonname && path != fs.f_mntfromname) - return; - - m_disk_infos.typefs = fs.f_fstypename; - m_disk_infos.device = fs.f_mntfromname; - m_disk_infos.mountdir = fs.f_mntonname; - - m_disk_infos.total_amount = static_cast<double>(fs.f_blocks * fs.f_bsize); - m_disk_infos.free_amount = static_cast<double>(fs.f_bfree * fs.f_bsize); - m_disk_infos.used_amount = m_disk_infos.total_amount - m_disk_infos.free_amount; - - queried_paths.insert( - {path, { - {"total_amount", variant(m_disk_infos.total_amount)}, - {"used_amount", variant(m_disk_infos.used_amount)}, - {"free_amount", variant(m_disk_infos.free_amount)}, - {"typefs", variant(m_disk_infos.typefs)}, - {"mountdir", variant(m_disk_infos.mountdir)}, - {"device", variant(m_disk_infos.device)} - }} - ); -} - -// clang-format off -double& Disk::total_amount() noexcept -{ return m_disk_infos.total_amount; } - -double& Disk::used_amount() noexcept -{ return m_disk_infos.used_amount; } - -double& Disk::free_amount() noexcept -{ return m_disk_infos.free_amount; } - -int& Disk::types_disk() noexcept -{ return m_disk_infos.types_disk; } - -std::string& Disk::typefs() noexcept -{ return m_disk_infos.typefs; } - -std::string& Disk::mountdir() noexcept -{ return m_disk_infos.mountdir; } - -std::string& Disk::device() noexcept -{ return m_disk_infos.device; } - -#endif // CF_MACOS diff --git a/src/query/macos/ram.cpp b/src/query/macos/ram.cpp deleted file mode 100644 index 4e901ae5..00000000 --- a/src/query/macos/ram.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2025 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_MACOS - -#include <cstdint> -#include <mach/mach.h> -#include <sys/sysctl.h> -#include <unistd.h> - -#include "query.hpp" -#include "util.hpp" - -using namespace Query; - -// https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/memory/memory_apple.c -static RAM::RAM_t get_amount() -{ - RAM::RAM_t ret; - uint64_t total = 0, used = 0, page_size = 0; - int name[2] = { CTL_HW, HW_MEMSIZE }; - size_t length = sizeof(total); - if (sysctl(name, 2, &total, &length, NULL, 0)) - die(_("Failed to get RAM infos")); - - name[0] = CTL_HW; - name[1] = HW_PAGESIZE; - sysctl(name, 2, &page_size, &length, NULL, 0); - mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; - vm_statistics64_data_t vmstat; - if(host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t) (&vmstat), &count) != KERN_SUCCESS) - die(_("Failed to read host_statistics64")); - - // https://github.com/exelban/stats/blob/master/Modules/RAM/readers.swift#L56 - used = ((uint64_t) - + vmstat.active_count - + vmstat.inactive_count - + vmstat.speculative_count - + vmstat.wire_count - + vmstat.compressor_page_count - - vmstat.purgeable_count - - vmstat.external_page_count - ) * page_size; - - ret.total_amount = static_cast<double>(total) / 1024; - ret.used_amount = static_cast<double>(used) / 1024; - // I have just now saw fastfetch doesn't have a way to know free memory - // why?? - ret.free_amount = ret.total_amount - ret.used_amount; - - struct xsw_usage xsw; - length = sizeof(xsw); - name[0] = CTL_VM; - name[1] = VM_SWAPUSAGE; - if(sysctl(name, 2, &xsw, &length, NULL, 0) != 0) - return ret; - - ret.swap_total_amount = static_cast<double>(xsw.xsu_total); - ret.swap_used_amount = static_cast<double>(xsw.xsu_used); - ret.swap_free_amount = static_cast<double>(xsw.xsu_avail); - - return ret; -} - -RAM::RAM() noexcept -{ - CHECK_INIT(m_bInit); - - m_memory_infos = get_amount(); -} - -// clang-format off -double& RAM::free_amount() noexcept -{ return m_memory_infos.free_amount; } - -double& RAM::total_amount() noexcept -{ return m_memory_infos.total_amount; } - -double& RAM::used_amount() noexcept -{ return m_memory_infos.used_amount; } - -double& RAM::swap_total_amount() noexcept -{ return m_memory_infos.swap_total_amount; } - -double& RAM::swap_used_amount() noexcept -{ return m_memory_infos.swap_used_amount; } - -double& RAM::swap_free_amount() noexcept -{ return m_memory_infos.swap_free_amount; } - -#endif // CF_MACOS diff --git a/src/query/macos/system.cpp b/src/query/macos/system.cpp deleted file mode 100644 index 48dc97c6..00000000 --- a/src/query/macos/system.cpp +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright 2025 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_MACOS - -#include <sys/sysctl.h> -#include <sys/time.h> - -#include <string_view> - -#include "util.hpp" -#include "query.hpp" -#include "rapidxml-1.13/rapidxml.hpp" -#include "switch_fnv1a.hpp" - -using namespace Query; - -static std::string get_codename(const std::string_view os_version_id) -{ - std::string major; - std::string minor{UNKNOWN}; - size_t pos1 = os_version_id.find('.'); - if (pos1 != os_version_id.npos) - { - major = os_version_id.substr(0, pos1); - size_t pos2 = os_version_id.find('.', pos1+1); - if (pos2 != os_version_id.npos) - minor = os_version_id.substr(pos1+1, pos2); - } - - switch (fnv1a16::hash(major)) - { - case "15"_fnv1a16: return "Sequoia"; - case "14"_fnv1a16: return "Sonoma"; - case "13"_fnv1a16: return "Ventura"; - case "12"_fnv1a16: return "Monterey"; - case "11"_fnv1a16: return "Big Sur"; - case "10"_fnv1a16: - { - switch (fnv1a16::hash(minor)) - { - case "16"_fnv1a16: return "Big Sur"; - case "15"_fnv1a16: return "Catalina"; - case "14"_fnv1a16: return "Mojave"; - case "13"_fnv1a16: return "High Sierra"; - case "12"_fnv1a16: return "Sierra"; - case "11"_fnv1a16: return "El Capitan"; - case "10"_fnv1a16: return "Yosemite"; - case "9"_fnv1a16: return "Mavericks"; - case "8"_fnv1a16: return "Mountain Lion"; - case "7"_fnv1a16: return "Lion"; - case "6"_fnv1a16: return "Snow Leopard"; - case "5"_fnv1a16: return "Leopard"; - case "4"_fnv1a16: return "Tiger"; - case "3"_fnv1a16: return "Panther"; - case "2"_fnv1a16: return "Jaguar"; - case "1"_fnv1a16: return "Puma"; - case "0"_fnv1a16: return "Cheetah"; - } - } - } - - return UNKNOWN; -} - -static System::System_t get_os_infos() -{ - System::System_t ret; - std::ifstream f("/System/Library/CoreServices/SystemVersion.plist", std::ios::in); - if (!f.is_open()) - die("Couldn't get MacOS base infos"); - - std::string buffer(std::istreambuf_iterator<char>{f}, std::istreambuf_iterator<char>{}); - buffer.push_back('\0'); - - rapidxml::xml_document<> doc; - doc.parse<0>(&buffer[0]); - rapidxml::xml_node<>* root_node = doc.first_node("plist")->first_node("dict")->first_node("key"); - - for (; root_node; root_node = root_node->next_sibling()) - { - const std::string_view key = root_node->value(); // <key>ProductName</key> - root_node = root_node->next_sibling(); - const std::string_view value = root_node->value(); // <string>macOS</string> - if (key == "ProductName") - ret.os_name = value; - else if (key == "ProductUserVisibleVersion") - ret.os_version_id = value; - } - ret.os_pretty_name = ret.os_name + " " + ret.os_version_id; - - ret.os_version_codename = get_codename(ret.os_version_id); - if (ret.os_version_codename != UNKNOWN) - ret.os_pretty_name += " (" + ret.os_version_codename + ")"; - - return ret; -} - -System::System() -{ - CHECK_INIT(m_bInit); - - if (uname(&m_uname_infos) != 0) - die(_("uname() failed: {}\nCould not get system infos"), strerror(errno)); - - struct timeval boot_time; - size_t size = sizeof(boot_time); - int name[] = {CTL_KERN, KERN_BOOTTIME}; - if(sysctl(name, 2, &boot_time, &size, NULL, 0) != 0) - die(_("failed to get uptime")); - - m_uptime = time(NULL) - boot_time.tv_sec; - m_system_infos = get_os_infos(); -} - -// clang-format off -std::string System::kernel_name() noexcept -{ return m_uname_infos.sysname; } - -std::string System::kernel_version() noexcept -{ return m_uname_infos.release; } - -std::string System::hostname() noexcept -{ return m_uname_infos.nodename; } - -std::string System::arch() noexcept -{ return m_uname_infos.machine; } - -unsigned long& System::uptime() noexcept -{ return m_uptime; } - -std::string& System::os_pretty_name() noexcept -{ return m_system_infos.os_pretty_name; } - -std::string& System::os_name() noexcept -{ return m_system_infos.os_name; } - -std::string& System::os_id() noexcept -{ return m_system_infos.os_id; } - -std::string& System::os_versionid() noexcept -{ return m_system_infos.os_version_id; } - -std::string& System::os_version_codename() noexcept -{ return m_system_infos.os_version_codename; } - -std::string& System::host_modelname() noexcept -{ return m_system_infos.host_modelname; } - -std::string& System::host_vendor() noexcept -{ return m_system_infos.host_vendor; } - -std::string& System::host_version() noexcept -{ return m_system_infos.host_version; } - -std::string& System::os_initsys_name() -{ return m_system_infos.os_initsys_name; } - -std::string& System::os_initsys_version() -{ return m_system_infos.os_initsys_version; } - -std::string& System::pkgs_installed(const Config& config) -{ return m_system_infos.pkgs_installed; } - -#endif // !CF_MACOS diff --git a/src/util.cpp b/src/util.cpp index 24b3746c..2824352e 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -1,25 +1,25 @@ /* * Copyright 2025 Toni500git - * + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ @@ -32,23 +32,27 @@ #include <algorithm> #include <array> -#include <cerrno> #include <cstdint> #include <cstring> #include <filesystem> #include <fstream> #include <iostream> -#include <memory> #include <sstream> #include <string> #include <string_view> -#include <tuple> #include <vector> #include "fmt/color.h" #include "fmt/ranges.h" #include "pci.ids.hpp" #include "platform.hpp" +#include "tiny-process-library/process.hpp" + +#if !CF_ANDROID +const std::string& all_ids = get_pci_ids(); +#else +const std::string& all_ids = ""; +#endif bool hasEnding(const std::string_view fullString, const std::string_view ending) { @@ -133,18 +137,18 @@ std::string read_by_syspath(const std::string_view path, bool report_error) std::string result; std::getline(f, result); - + if (!result.empty() && result.back() == '\n') result.pop_back(); return result; } -byte_units_t auto_devide_bytes(const double num, const std::uint16_t base, const std::string_view maxprefix) +byte_units_t auto_divide_bytes(const double num, const std::uint16_t base, const std::string_view maxprefix) { double size = num; - std::array<std::string_view, 10> prefixes; + std::array<std::string_view, 9> prefixes; if (base == 1024) prefixes = { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB" }; else if (base == 1000) @@ -152,34 +156,44 @@ byte_units_t auto_devide_bytes(const double num, const std::uint16_t base, const else prefixes = { "B" }; - std::uint16_t counter = 0; - if (maxprefix.empty()) - { - for (; counter < prefixes.size() && size >= base; ++counter) - size /= base; - } - else + size_t counter = 0; + const auto& max_it = !maxprefix.empty() + ? std::find(prefixes.begin(), prefixes.end(), maxprefix) + : prefixes.end(); + + while (counter + 1 < prefixes.size() && size >= base) { - for (; counter < prefixes.size() && size >= base && prefixes.at(counter) != maxprefix; ++counter) - size /= base; + if (max_it != prefixes.end() && prefixes[counter] == maxprefix) + break; + size /= base; + ++counter; } - return { prefixes.at(counter).data(), size }; + return { prefixes[counter].data(), size }; } -byte_units_t devide_bytes(const double num, const std::string_view prefix) +byte_units_t divide_bytes(const double num, const std::string_view prefix) { - if (prefix != "B") - { - // GiB - // 012 - if (prefix.size() == 3 && prefix.at(1) == 'i') - return auto_devide_bytes(num, 1024, prefix); - else - return auto_devide_bytes(num, 1000, prefix); - } + if (prefix == "B") + return { "B", num }; + + // GiB + // 012 + const std::uint16_t base = (prefix.size() == 3 && prefix[1] == 'i') ? 1024 : 1000; + std::array<std::string_view, 9> prefixes; + if (base == 1024) + prefixes = { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB" }; + else if (base == 1000) + prefixes = { "B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" }; - return auto_devide_bytes(num, 0); + const auto& it = std::find(prefixes.begin(), prefixes.end(), prefix); + if (it == prefixes.end()) + return { "B", num }; + + const size_t index = std::distance(prefixes.begin(), it); + const double value = num / std::pow(static_cast<double>(base), index); + + return { prefix.data(), value }; } bool is_file_image(const unsigned char* bytes) @@ -209,7 +223,7 @@ bool is_file_image(const unsigned char* bytes) void strip(std::string& input, bool padding_only) { - if (input.empty()) + if (input.empty()) return; if (padding_only) @@ -220,12 +234,8 @@ void strip(std::string& input, bool padding_only) } else { - input.erase( - std::remove_if(input.begin(), input.end(), - [](unsigned char c) { return std::isspace(c); } - ), - input.end() - ); + input.erase(std::remove_if(input.begin(), input.end(), [](unsigned char c) { return std::isspace(c); }), + input.end()); } } @@ -252,7 +262,7 @@ std::string shorten_vendor_name(std::string vendor) fmt::rgb hexStringToColor(const std::string_view hexstr) { std::stringstream ss; - ss << std::hex << hexstr.substr(1).data(); + ss << std::hex << ((hexstr[0] == '#') ? hexstr.substr(1).data() : hexstr.data()); uint value; ss >> value; @@ -350,107 +360,28 @@ void replace_str(std::string& str, const std::string_view from, const std::strin } } -bool read_exec(std::vector<const char*> cmd, std::string& output, bool useStdErr, bool noerror_print) +bool read_exec(std::vector<std::string> cmd, std::string& output, bool useStdErr, bool noerror_print) { debug("{} cmd = {}", __func__, cmd); - std::array<int, 2> pipeout; - - if (pipe(pipeout.data()) < 0) - die(_("pipe() failed: {}"), strerror(errno)); - - const pid_t pid = fork(); - - // we wait for the command to finish then start executing the rest - if (pid > 0) - { - close(pipeout.at(1)); - - int status; - waitpid(pid, &status, 0); // Wait for the child to finish - - if (WIFEXITED(status) && (WEXITSTATUS(status) == 0 || useStdErr)) - { - // read stdout - debug("reading stdout"); - char c; - while (read(pipeout.at(0), &c, 1) == 1) - output += c; - - close(pipeout.at(0)); - if (!output.empty() && output.back() == '\n') - output.pop_back(); - - return true; - } - else - { - if (!noerror_print) + TinyProcessLib::Process proc( + cmd, "", + [&](const char* bytes, size_t n) { + if (!useStdErr) + output += std::string(bytes, n); + }, + [&](const char* bytes, size_t n) { + if (useStdErr) + output += std::string(bytes, n); + else if (!noerror_print) error(_("Failed to execute the command: {}"), fmt::join(cmd, " ")); - } - } - else if (pid == 0) - { - int nullFile = open("/dev/null", O_WRONLY | O_CLOEXEC); - dup2(pipeout.at(1), useStdErr ? STDERR_FILENO : STDOUT_FILENO); - dup2(nullFile, useStdErr ? STDOUT_FILENO : STDERR_FILENO); + }); - setenv("LANG", "C", 1); - cmd.push_back(nullptr); - execvp(cmd.at(0), const_cast<char* const*>(cmd.data())); + if (!output.empty() && output.back() == '\n') + output.pop_back(); - die(_("An error has occurred with execvp: {}"), strerror(errno)); - } - else - { - close(pipeout.at(0)); - close(pipeout.at(1)); - die(_("fork() failed: {}"), strerror(errno)); - } - - close(pipeout.at(0)); - close(pipeout.at(1)); - - return false; + return proc.get_exit_status() == 0; } -bool taur_exec(const std::vector<std::string_view> cmd_str, const bool noerror_print) -{ - std::vector<const char*> cmd; - for (const std::string_view str : cmd_str) - cmd.push_back(str.data()); - - int pid = fork(); - - if (pid < 0) - { - die(_("fork() failed: {}"), strerror(errno)); - } - - if (pid == 0) - { - debug("running {}", cmd); - cmd.push_back(nullptr); - execvp(cmd.at(0), const_cast<char* const*>(cmd.data())); - - // execvp() returns instead of exiting when failed - die(_("An error has occurred: {}: {}"), cmd.at(0), strerror(errno)); - } - else if (pid > 0) - { // we wait for the command to finish then start executing the rest - int status; - waitpid(pid, &status, 0); // Wait for the child to finish - - if (WIFEXITED(status) && WEXITSTATUS(status) == 0) - return true; - else - { - if (!noerror_print) - error(_("Failed to execute the command: {}"), fmt::join(cmd, " ")); - } - } - - return false; -} std::string str_tolower(std::string str) { for (char& x : str) @@ -545,32 +476,6 @@ std::string binarySearchPCIArray(const std::string_view vendor_id_s) return vendor_from_entry(vendors_location, vendor_id); } -std::string read_shell_exec(const std::string_view cmd) -{ - std::array<char, 4096> buffer; - std::string result; - std::unique_ptr<FILE, void(*)(FILE*)> pipe(popen(cmd.data(), "r"), - [](FILE *f) -> void - { - // wrapper to ignore the return value from pclose(). - // Is needed with newer versions of gnu g++ - std::ignore = pclose(f); - }); - - if (!pipe) - die(_("popen() failed: {}"), std::strerror(errno)); - - result.reserve(buffer.size()); - while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) - result += buffer.data(); - - // why there is a '\n' at the end?? - if (!result.empty() && result.back() == '\n') - result.pop_back(); - - return result; -} - std::string name_from_entry(size_t dev_entry_pos) { dev_entry_pos += 6; // Offset from the first character to the actual name that we want (xxxx <device name>) @@ -605,8 +510,7 @@ std::string vendor_from_entry(const size_t vendor_entry_pos, const std::string_v return description.substr(first, (last - first + 1)); } -// clang-format off -std::string getHomeConfigDir() +std::filesystem::path getHomeConfigDir() { const char* dir = std::getenv("XDG_CONFIG_HOME"); if (dir != NULL && dir[0] != '\0' && std::filesystem::exists(dir)) @@ -622,9 +526,32 @@ std::string getHomeConfigDir() if (home == nullptr) die(_("Failed to find $HOME, set it to your home directory!")); - return std::string(home) + "/.config"; + return std::filesystem::path(home) / ".config"; + } +} + +std::filesystem::path getConfigDir() +{ return getHomeConfigDir() / "customfetch"; } + +std::filesystem::path getHomeCacheDir() +{ + const char* dir = std::getenv("XDG_CACHE_HOME"); + if (dir != NULL && dir[0] != '\0' && std::filesystem::exists(dir)) + { + std::string str_dir(dir); + if (str_dir.back() == '/') + str_dir.pop_back(); + return str_dir; + } + else + { + const char* home = std::getenv("HOME"); + if (home == nullptr) + die(_("Failed to find $HOME, set it to your home directory!")); + + return std::filesystem::path(home) / ".cache"; } } -std::string getConfigDir() -{ return getHomeConfigDir() + "/customfetch"; } +std::filesystem::path getCacheDir() +{ return getHomeCacheDir() / "customfetch"; } diff --git a/tests/test_util.cpp b/tests/test_util.cpp index ae622cb5..a5affefd 100644 --- a/tests/test_util.cpp +++ b/tests/test_util.cpp @@ -1,3 +1,28 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + #include <string> #include "util.hpp"