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 = [
"$
",
- "$",
+ "$",
"${auto}OS: $ $",
"${auto}Host: $",
"${auto}Kernel: $",
@@ -130,12 +130,12 @@ layout = [
"${auto}Terminal: $",
"${auto}Shell: $",
"${auto}Packages: $",
- "${auto}Theme: $",
- "${auto}Icons: $",
- "${auto}Font: $",
+ "${auto}Theme: $",
+ "${auto}Icons: $",
+ "${auto}Font: $",
"${auto}Cursor: $",
- "${auto}WM: $ $",
- "${auto}DE: $ $",
+ "${auto}WM: $ $",
+ "${auto}DE: $ $",
"$",
"${auto}Swap: $",
"${auto}CPU: $",
@@ -143,7 +143,7 @@ layout = [
"${auto}RAM: $",
"",
"$", # normal colors palette
- "$" # light colors palette
+ "$" # 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:
-* `$` - Used for printing the system info value of a member of a module.
-* `${color}` - Used for displaying text in a specific color.
+* `$` - 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 `$` will print the username, `$` will print the kernel version and so on.\
+* **The info tag (`$<>`)** will print a value of a member of a module.\
+ e.g `$` will print the username, `$` 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 `$`) 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
# 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
+#include
+#include
+#include
+
+#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 licenses;
+
+ // The plugin authors.
+ std::vector 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 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 prefixes;
+
+ // Platforms that are supported by the plugin.
+ // Make it a string and put 'all' for being cross-platform.
+ std::vector 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 plugins;
+
+ // An array for storing the dependencies for 'all' and current platforms.
+ // first -> platform string name
+ // seconds -> platform dependencies vector names
+ std::vector 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 getStrArrayValue(const toml::table& tbl, const std::string_view name,
+ const std::string_view value);
+std::vector 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& get_all_plugins() const
+ { return m_repo.plugins; }
+
+ const std::vector& 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 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
+#include
+#include
+#include
+
+#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 arguments;
+} options;
+
+template
+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)...));
+}
+
+template
+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)...));
+}
+
+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
+
+#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 get_all_repos();
+
+ template
+ 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
+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(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
+#include
+#include
+
+#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 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("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("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
+#include
+#include
+#include
+
+#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& ret = tbl[name][key].value();
+ return ret.value_or(UNKNOWN);
+}
+
+std::string ManifestSpace::getStrValue(const toml::table& tbl, const std::string_view path)
+{
+ const std::optional& ret = tbl.at_path(path).value();
+ return ret.value_or(UNKNOWN);
+}
+
+std::vector ManifestSpace::getStrArrayValue(const toml::table& tbl, const std::string_view path)
+{
+ std::vector 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* str_elem = el.as_string())
+ ret.push_back((*str_elem)->data());
+ });
+
+ return ret;
+ }
+ return {};
+}
+
+std::vector ManifestSpace::getStrArrayValue(const toml::table& tbl, const std::string_view name,
+ const std::string_view value)
+{
+ std::vector 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* 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())
+ 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())
+ 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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 core_dependencies = { "git" }; // expand in the future, maybe
+
+using namespace TinyProcessLib;
+
+static bool has_deps(const std::vector& 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 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//
+ 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
+#include
+#include
+#include
+#include
+
+#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& 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 StateManager::get_all_repos()
+{
+ const toml::table* repositories = m_state["repositories"].as_table();
+ if (!repositories)
+ return {};
+
+ std::vector 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("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
+#include
+#include
+
+#include
+#include
+#include
+
+/* 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(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
-#include
-#include
-
-#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
+#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 layout;
std::vector percentage_colors;
std::vector 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 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 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
- 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())
- 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())
- if (overridePos != overrides.end() && overrides.at(value.data()).value_type == STR)
- return overrides.at(value.data()).string_value;
-
- if constexpr (std::is_same())
- if (overridePos != overrides.end() && overrides.at(value.data()).value_type == INT)
- return overrides.at(value.data()).int_value;
-
- const std::optional& ret = this->tbl.at_path(value).value();
- if constexpr (toml::is_string) // 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)
+ {
+ o.value_type = BOOL;
+ o.bool_value = value;
+ }
+ else if constexpr (std::is_convertible_v)
+ {
+ o.value_type = STR;
+ o.string_value = value;
+ }
+ else if constexpr (std::is_convertible_v)
+ {
+ 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 getValueArrayStr(const std::string_view value, const std::vector& 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 = [
- "$",
- "$",
- "${auto}OS: $ $",
- "${auto}Host: $",
- "${auto}Kernel: $",
- "${auto}Uptime: $",)#"
-#if !CF_ANDROID
- R"#(
- "${auto}Theme: $",
- "${auto}Icons: $",
- "${auto}Font: $",
- "${auto}Cursor: $",
- "${auto}WM: $",
- "${auto}DE: $",)#"
-#endif
- R"#(
- "$",
- "${auto}Swap: $",
- "${auto}CPU: $",
- "${auto}GPU: $",
- "${auto}RAM: $",
- "",
- "$", # normal colors
- "$" # 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 "_" 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 = "-"
-
-# 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"]
-
-# $ 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): $"
-
-# 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
-
-# $ 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"
-
-# $ config
-[os.pkgs]
-# Ordered list of which packages installed count should be displayed in $
-# 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
+#include
+
+#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
#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 render(const Config& config, const colors_t& colors, const bool already_analyzed_path,
- const std::string_view path);
+std::vector 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
+
+#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
+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)...));
+}
+
+// std::format function arguments
+// Print to stderr an error with header 'FATAL:' in red and exit with failure code
+template
+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)...));
+ 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
+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)...));
+}
+
+// std::format function arguments
+// Print to stderr a warning with header 'WARNING:' in yellow
+template
+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)...));
+}
+
+// std::format function arguments
+// Print to stdout an info msg with header 'INFO:' in cyan
+template
+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)...));
+}
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
+#include
+
+#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
+ 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)
+ if (overridePos != overrides.end() && overrides.at(value.data()).value_type == BOOL)
+ return overrides.at(value.data()).bool_value;
+
+ if constexpr (std::is_convertible_v)
+ if (overridePos != overrides.end() && overrides.at(value.data()).value_type == STR)
+ return overrides.at(value.data()).string_value;
+
+ if constexpr (std::is_convertible_v)
+ if (overridePos != overrides.end() && overrides.at(value.data()).value_type == INT)
+ return overrides.at(value.data()).int_value;
+
+ const std::optional& ret = this->tbl.at_path(value).value();
+ 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 getValueArrayStr(const std::string_view value,
+ const std::vector& fallback) const
+ {
+ std::vector 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* str_elem = el.as_string())
+ ret.push_back((*str_elem)->data());
+ });
+
+ return ret;
+ }
+ else
+ {
+ return fallback;
+ }
+ }
+
+protected:
+ std::unordered_map 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
+#include
+#include
+
+#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 = `name`, child = `submodules[x].name`.
+ *
+ * WARN: Do not pass submodules to cfRegisterModule.
+ * It registers recursively and will include them automatically.
+ *
+ * Real example: $
+ * - 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 $ and $ from the layout.
+ */
+struct module_t
+{
+ std::string name;
+ std::string description;
+ std::vector submodules; /* Use std::move() for efficiency when adding. */
+ std::function 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& 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
+#include
+#include
+
+#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;
+
+/* 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& layout;
+ std::vector& 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& layout, std::vector& 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
+ * 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
+#include
+#include
+#include
+#include
+#include
+#include
+#ifndef _WIN32
+#include
+#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 on_stdout_close = nullptr;
+ /// If set, invoked when process stderr is closed.
+ /// This call goes after last call to read_stderr().
+ std::function 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 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 &arguments, const string_type &path = string_type(),
+ std::function read_stdout = nullptr,
+ std::function 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 read_stdout = nullptr,
+ std::function read_stderr = nullptr,
+ bool open_stdin = false,
+ const Config &config = {}) noexcept;
+
+ /// Starts a process with specified environment.
+ Process(const std::vector &arguments,
+ const string_type &path,
+ const environment_type &environment,
+ std::function read_stdout = nullptr,
+ std::function 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 read_stdout = nullptr,
+ std::function 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 &function,
+ std::function read_stdout = nullptr,
+ std::function 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 read_stdout;
+ std::function 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 stdout_fd, stderr_fd, stdin_fd;
+
+ id_type open(const std::vector &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 &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
- class iterator : public std::iterator {
+ 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
-#include
-#include
-#include
-
-#include "config.hpp"
+#include "libcufetch/parse.hh"
-// from query.hpp
-using systemInfo_t =
- std::unordered_map>>;
-
-/* 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