2021-02-18 22:25:36 +01:00
#include "fastfetch.h"
2026-01-15 15:03:38 +08:00
#include "common/ffdata.h"
2023-08-21 09:22:03 +08:00
#include "detection/version/version.h"
2025-04-14 10:28:01 +08:00
#include "logo/logo.h"
2026-09-21 22:46:40 +08:00
#include "common/FFcache.h"
2026-01-06 09:48:38 +08:00
#include "common/commandoption.h"
2026-08-04 10:41:26 +08:00
#include "common/genconfig.h"
2026-01-06 09:48:38 +08:00
#include "common/init.h"
#include "common/io.h"
#include "common/jsonconfig.h"
#include "common/time.h"
2026-05-28 15:58:08 +08:00
#include "common/strutil.h"
2026-01-06 09:48:38 +08:00
#include "common/mallocHelper.h"
2023-07-28 14:12:48 +08:00
#include "fastfetch_datatext.h"
2021-02-18 22:25:36 +01:00
2022-06-18 14:25:31 +02:00
#include <stdlib.h>
2022-08-21 21:08:11 +02:00
#include <ctype.h>
2021-04-09 14:07:05 +02:00
#include <string.h>
2021-02-22 16:52:51 +01:00
2026-08-04 10:41:26 +08:00
#ifndef _WIN32
#include <unistd.h>
#else
#include <windows.h>
2026-08-04 21:05:07 +08:00
#include "common/windows/nt.h"
2026-08-04 10:41:26 +08:00
#endif
2026-07-10 21:13:03 +08:00
[[ gnu :: cold ]]
2026-03-29 09:28:49 +08:00
static void printCommandFormatHelpJson ( void ) {
2026-07-10 18:08:04 +08:00
yyjson_mut_doc * doc = yyjson_mut_doc_new ( nullptr );
2024-12-11 16:12:03 +08:00
yyjson_mut_val * root = yyjson_mut_obj ( doc );
yyjson_mut_doc_set_root ( doc , root );
2026-03-29 09:28:49 +08:00
for ( uint32_t i = 0 ; i <= 'Z' - 'A' ; ++ i ) {
for ( FFModuleBaseInfo ** modules = ffModuleInfos [ i ]; * modules ; ++ modules ) {
2024-12-11 16:12:03 +08:00
FFModuleBaseInfo * baseInfo = * modules ;
2026-03-29 09:28:49 +08:00
if ( ! baseInfo -> formatArgs . count ) {
continue ;
}
2024-12-11 16:12:03 +08:00
FF_STRBUF_AUTO_DESTROY type = ffStrbufCreateS ( baseInfo -> name );
ffStrbufLowerCase ( & type );
ffStrbufAppendS ( & type , "Format" );
yyjson_mut_val * obj = yyjson_mut_obj ( doc );
2026-03-29 09:28:49 +08:00
if ( yyjson_mut_obj_add ( root , yyjson_mut_strbuf ( doc , & type ), obj )) {
2026-04-16 18:27:11 +08:00
FF_STRBUF_AUTO_DESTROY content = ffStrbufCreateF ( "Output format for the `%s` module. See Wiki for formatting syntax \n " , baseInfo -> name );
2026-03-29 09:28:49 +08:00
for ( unsigned i = 0 ; i < baseInfo -> formatArgs . count ; i ++ ) {
2024-12-11 16:12:03 +08:00
const FFModuleFormatArg * arg = & baseInfo -> formatArgs . args [ i ];
ffStrbufAppendF ( & content , " %u. {%s}: %s \n " , i + 1 , arg -> name , arg -> desc );
}
ffStrbufTrimRight ( & content , '\n' );
yyjson_mut_obj_add_strbuf ( doc , obj , "description" , & content );
yyjson_mut_obj_add_str ( doc , obj , "type" , "string" );
}
}
}
2026-07-10 18:08:04 +08:00
yyjson_mut_write_fp ( stdout , doc , YYJSON_WRITE_PRETTY , nullptr , nullptr );
2024-12-11 16:12:03 +08:00
putchar ( '\n' );
yyjson_mut_doc_free ( doc );
}
2026-07-10 21:13:03 +08:00
[[ gnu :: cold ]]
2026-03-29 09:28:49 +08:00
static void printCommandFormatHelp ( const char * command ) {
2023-10-07 13:40:40 +08:00
FF_STRBUF_AUTO_DESTROY type = ffStrbufCreateNS (( uint32_t ) ( strlen ( command ) - strlen ( "-format" )), command );
2024-12-11 10:47:02 +08:00
ffStrbufLowerCase ( & type );
2026-03-29 09:28:49 +08:00
for ( FFModuleBaseInfo ** modules = ffModuleInfos [ toupper ( command [ 0 ]) - 'A' ]; * modules ; ++ modules ) {
2023-10-07 13:40:40 +08:00
FFModuleBaseInfo * baseInfo = * modules ;
2026-03-29 09:28:49 +08:00
if ( ffStrbufIgnCaseEqualS ( & type , baseInfo -> name )) {
if ( baseInfo -> formatArgs . count > 0 ) {
2025-08-21 23:56:14 +08:00
FF_STRBUF_AUTO_DESTROY variable = ffStrbufCreate ();
printf ( "-- In config file: { \" type \" : \" %s \" , \" format \" : \" {<format-variable>} \" } \n " , type . chars );
2024-12-11 10:47:02 +08:00
printf ( "Sets the format string for %s output. \n " , baseInfo -> name );
2025-08-21 23:56:14 +08:00
puts ( "To see how a format string is constructed, take a look at https://github.com/fastfetch-cli/fastfetch/wiki/Format-String-Guide." );
2026-09-15 22:20:17 +08:00
puts ( "Descriptions which end with a '*' are available in key format too." );
2025-08-21 23:56:14 +08:00
puts ( "The following variables are passed:" );
2024-12-11 10:47:02 +08:00
2026-04-05 01:46:25 +03:00
uint32_t maxWidth = 20 ;
for ( unsigned i = 0 ; i < baseInfo -> formatArgs . count ; i ++ ) {
uint32_t len = ( uint32_t ) strlen ( baseInfo -> formatArgs . args [ i ]. name ) + 2 ;
if ( len > maxWidth ) {
maxWidth = len ;
}
}
2026-03-29 09:28:49 +08:00
for ( unsigned i = 0 ; i < baseInfo -> formatArgs . count ; i ++ ) {
2024-12-11 10:47:02 +08:00
const FFModuleFormatArg * arg = & baseInfo -> formatArgs . args [ i ];
2025-08-21 23:56:14 +08:00
ffStrbufSetF ( & variable , "{%s}" , arg -> name );
2026-04-05 01:46:25 +03:00
printf ( "%*s: %s \n " , ( int ) maxWidth , variable . chars , arg -> desc );
2024-12-11 10:47:02 +08:00
}
2026-03-29 09:28:49 +08:00
} else {
2023-10-07 13:40:40 +08:00
fprintf ( stderr , "Error: Module '%s' doesn't support output formatting \n " , baseInfo -> name );
2026-03-29 09:28:49 +08:00
}
2023-10-07 13:40:40 +08:00
return ;
}
}
2021-03-19 20:57:07 +01:00
2023-10-07 13:40:40 +08:00
fprintf ( stderr , "Error: Module '%s' is not supported \n " , type . chars );
2021-03-11 22:15:48 +01:00
}
2026-07-10 21:13:03 +08:00
[[ gnu :: cold ]]
2026-03-29 09:28:49 +08:00
static void printFullHelp () {
2023-11-20 16:37:59 +08:00
fputs ( "Fastfetch is a neofetch-like tool for fetching system information and displaying them in a pretty way \n\n " , stdout );
2026-03-29 09:28:49 +08:00
if ( ! instance . config . display . pipe ) {
2023-11-20 22:04:03 +08:00
fputs ( "\e[1;4mUsage:\e[m \e[1mfastfetch\e[m \e[3m<?options>\e[m \n\n " , stdout );
2026-03-29 09:28:49 +08:00
} else {
2023-11-20 16:37:59 +08:00
fputs ( "Usage: fastfetch <?options> \n\n " , stdout );
2026-03-29 09:28:49 +08:00
}
2023-11-20 16:37:59 +08:00
yyjson_doc * doc = yyjson_read ( FASTFETCH_DATATEXT_JSON_HELP , strlen ( FASTFETCH_DATATEXT_JSON_HELP ), YYJSON_READ_NOFLAG );
assert ( doc );
yyjson_val * groupKey , * flagArr ;
size_t groupIdx , groupMax ;
2026-03-29 09:28:49 +08:00
yyjson_obj_foreach ( yyjson_doc_get_root ( doc ), groupIdx , groupMax , groupKey , flagArr ) {
if ( ! instance . config . display . pipe ) {
2023-11-20 22:04:03 +08:00
fputs ( "\e[1;4m" , stdout );
2026-03-29 09:28:49 +08:00
}
2023-11-20 16:37:59 +08:00
printf ( "%s options:" , yyjson_get_str ( groupKey ));
2026-03-29 09:28:49 +08:00
if ( ! instance . config . display . pipe ) {
2023-11-20 22:04:03 +08:00
fputs ( "\e[m" , stdout );
2026-03-29 09:28:49 +08:00
}
2023-11-20 16:37:59 +08:00
putchar ( '\n' );
yyjson_val * flagObj ;
size_t flagIdx , flagMax ;
2026-03-29 09:28:49 +08:00
yyjson_arr_foreach ( flagArr , flagIdx , flagMax , flagObj ) {
2023-11-20 16:37:59 +08:00
yyjson_val * shortKey = yyjson_obj_get ( flagObj , "short" );
2026-03-29 09:28:49 +08:00
if ( shortKey ) {
2023-11-20 16:37:59 +08:00
fputs ( " " , stdout );
2026-03-29 09:28:49 +08:00
if ( ! instance . config . display . pipe ) {
2023-11-20 22:04:03 +08:00
fputs ( "\e[1m" , stdout );
2026-03-29 09:28:49 +08:00
}
2023-11-20 16:37:59 +08:00
printf ( "-%s" , yyjson_get_str ( shortKey ));
2026-03-29 09:28:49 +08:00
if ( ! instance . config . display . pipe ) {
2023-11-20 22:04:03 +08:00
fputs ( "\e[m" , stdout );
2026-03-29 09:28:49 +08:00
}
2023-11-20 16:37:59 +08:00
fputs ( ", " , stdout );
2026-03-29 09:28:49 +08:00
} else {
2023-11-20 16:37:59 +08:00
fputs ( " " , stdout );
}
yyjson_val * longKey = yyjson_obj_get ( flagObj , "long" );
assert ( longKey );
2026-03-29 09:28:49 +08:00
if ( ! instance . config . display . pipe ) {
2023-11-20 22:04:03 +08:00
fputs ( "\e[1m" , stdout );
2026-03-29 09:28:49 +08:00
}
2023-11-20 16:37:59 +08:00
printf ( "--%s" , yyjson_get_str ( longKey ));
2026-03-29 09:28:49 +08:00
if ( ! instance . config . display . pipe ) {
2023-11-20 22:04:03 +08:00
fputs ( "\e[m" , stdout );
2026-03-29 09:28:49 +08:00
}
2023-11-02 14:06:53 +08:00
2023-11-20 16:37:59 +08:00
yyjson_val * argObj = yyjson_obj_get ( flagObj , "arg" );
2026-03-29 09:28:49 +08:00
if ( argObj ) {
2023-11-20 16:37:59 +08:00
yyjson_val * typeKey = yyjson_obj_get ( argObj , "type" );
assert ( typeKey );
yyjson_val * optionalKey = yyjson_obj_get ( argObj , "optional" );
bool optional = optionalKey && yyjson_get_bool ( optionalKey );
putchar ( ' ' );
2026-03-29 09:28:49 +08:00
if ( ! instance . config . display . pipe ) {
2023-11-20 22:51:01 +08:00
fputs ( "\e[3m" , stdout );
2026-03-29 09:28:49 +08:00
}
2023-11-20 16:37:59 +08:00
printf ( "<%s%s>" , optional ? "?" : "" , yyjson_get_str ( typeKey ));
2026-03-29 09:28:49 +08:00
if ( ! instance . config . display . pipe ) {
2023-11-20 22:04:03 +08:00
fputs ( "\e[m" , stdout );
2026-03-29 09:28:49 +08:00
}
2023-11-20 16:37:59 +08:00
}
2023-11-02 14:06:53 +08:00
2023-11-20 16:37:59 +08:00
yyjson_val * descKey = yyjson_obj_get ( flagObj , "desc" );
assert ( descKey );
2026-03-29 09:28:49 +08:00
if ( yyjson_is_arr ( descKey )) {
if ( instance . config . display . pipe ) {
2023-11-20 16:37:59 +08:00
putchar ( ':' );
2026-03-29 09:28:49 +08:00
}
2023-11-20 16:37:59 +08:00
yyjson_val * descStr ;
size_t descIdx , descMax ;
2026-03-29 09:28:49 +08:00
yyjson_arr_foreach ( descKey , descIdx , descMax , descStr ) {
if ( ! instance . config . display . pipe ) {
2023-11-20 16:37:59 +08:00
printf ( "\e[46G%s \n " , yyjson_get_str ( descStr ));
2026-03-29 09:28:49 +08:00
} else {
2023-11-20 16:37:59 +08:00
printf ( " %s" , yyjson_get_str ( descStr ));
2026-03-29 09:28:49 +08:00
}
2023-11-02 14:06:53 +08:00
}
2026-03-29 09:28:49 +08:00
if ( instance . config . display . pipe ) {
2023-11-20 16:37:59 +08:00
putchar ( '\n' );
2026-03-29 09:28:49 +08:00
}
} else {
if ( ! instance . config . display . pipe ) {
2023-11-20 16:37:59 +08:00
fputs ( "\e[46G" , stdout );
2026-03-29 09:28:49 +08:00
} else {
2023-11-20 16:37:59 +08:00
fputs ( ": " , stdout );
2026-03-29 09:28:49 +08:00
}
2023-11-20 16:37:59 +08:00
puts ( yyjson_get_str ( descKey ));
2023-11-02 14:06:53 +08:00
}
}
2023-11-20 16:37:59 +08:00
putchar ( '\n' );
}
yyjson_doc_free ( doc );
2023-11-22 09:32:38 +08:00
2025-04-04 18:59:15 +08:00
puts ( " \n \
2025-08-04 15:08:30 +08:00
Command flags are not case sensitive. E.g. `--print-logos` is equal to `--Print-Logos` \n \
2025-04-04 18:59:15 +08:00
If a value starts with a ?, it is optional. An optional boolean value defaults to true if not specified. \n \
More detailed help messages for each options can be printed with `-h <option_without_dash_prefix>` \n \
2025-08-04 15:08:30 +08:00
For detailed information on logo options, module configuration, and formatting, visit: \n \
https://github.com/fastfetch-cli/fastfetch/wiki/Configuration" );
2023-11-20 16:37:59 +08:00
}
2026-07-10 21:13:03 +08:00
[[ gnu :: cold ]]
2026-03-29 09:28:49 +08:00
static bool printSpecificCommandHelp ( const char * command ) {
2023-11-20 16:37:59 +08:00
yyjson_doc * doc = yyjson_read ( FASTFETCH_DATATEXT_JSON_HELP , strlen ( FASTFETCH_DATATEXT_JSON_HELP ), YYJSON_READ_NOFLAG );
assert ( doc );
yyjson_val * groupKey , * flagArr ;
size_t groupIdx , groupMax ;
2026-03-29 09:28:49 +08:00
yyjson_obj_foreach ( yyjson_doc_get_root ( doc ), groupIdx , groupMax , groupKey , flagArr ) {
2023-11-20 16:37:59 +08:00
yyjson_val * flagObj ;
size_t flagIdx , flagMax ;
2026-03-29 09:28:49 +08:00
yyjson_arr_foreach ( flagArr , flagIdx , flagMax , flagObj ) {
2023-11-20 16:37:59 +08:00
yyjson_val * pseudo = yyjson_obj_get ( flagObj , "pseudo" );
2026-03-29 09:28:49 +08:00
if ( pseudo && yyjson_get_bool ( pseudo )) {
2023-11-20 16:37:59 +08:00
continue ;
2026-03-29 09:28:49 +08:00
}
2023-11-20 16:37:59 +08:00
yyjson_val * longKey = yyjson_obj_get ( flagObj , "long" );
assert ( longKey );
2026-03-29 09:28:49 +08:00
if ( ffStrEqualsIgnCase ( command , yyjson_get_str ( longKey ))) {
2023-11-21 16:39:38 +08:00
puts ( yyjson_get_str ( yyjson_obj_get ( flagObj , "desc" )));
printf ( "%10s: " , "Usage" );
yyjson_val * shortKey = yyjson_obj_get ( flagObj , "short" );
2026-03-29 09:28:49 +08:00
if ( shortKey ) {
if ( ! instance . config . display . pipe ) {
2023-11-21 16:39:38 +08:00
fputs ( "\e[1m" , stdout );
2026-03-29 09:28:49 +08:00
}
2023-11-21 16:39:38 +08:00
printf ( "-%s" , yyjson_get_str ( shortKey ));
2026-03-29 09:28:49 +08:00
if ( ! instance . config . display . pipe ) {
2023-11-21 16:39:38 +08:00
fputs ( "\e[m" , stdout );
2026-03-29 09:28:49 +08:00
}
2023-11-21 16:39:38 +08:00
fputs ( ", " , stdout );
}
2026-03-29 09:28:49 +08:00
if ( ! instance . config . display . pipe ) {
2023-11-21 16:39:38 +08:00
fputs ( "\e[1m" , stdout );
2026-03-29 09:28:49 +08:00
}
2023-11-21 16:39:38 +08:00
printf ( "--%s" , yyjson_get_str ( longKey ));
2026-03-29 09:28:49 +08:00
if ( ! instance . config . display . pipe ) {
2023-11-21 16:39:38 +08:00
fputs ( "\e[m" , stdout );
2026-03-29 09:28:49 +08:00
}
2023-11-21 16:39:38 +08:00
yyjson_val * argObj = yyjson_obj_get ( flagObj , "arg" );
2026-03-29 09:28:49 +08:00
if ( argObj ) {
2023-11-21 16:39:38 +08:00
yyjson_val * typeKey = yyjson_obj_get ( argObj , "type" );
assert ( typeKey );
yyjson_val * optionalKey = yyjson_obj_get ( argObj , "optional" );
bool optional = optionalKey && yyjson_get_bool ( optionalKey );
putchar ( ' ' );
2026-03-29 09:28:49 +08:00
if ( ! instance . config . display . pipe ) {
2023-11-21 16:39:38 +08:00
fputs ( "\e[3m" , stdout );
2026-03-29 09:28:49 +08:00
}
2023-11-21 16:39:38 +08:00
printf ( "<%s%s>" , optional ? "?" : "" , yyjson_get_str ( typeKey ));
2026-03-29 09:28:49 +08:00
if ( ! instance . config . display . pipe ) {
2023-11-21 16:39:38 +08:00
fputs ( "\e[m" , stdout );
2026-03-29 09:28:49 +08:00
}
2023-11-21 16:39:38 +08:00
putchar ( '\n' );
yyjson_val * defaultKey = yyjson_obj_get ( argObj , "default" );
2026-03-29 09:28:49 +08:00
if ( defaultKey ) {
if ( ffStrEqualsIgnCase ( yyjson_get_str ( typeKey ), "structure" )) {
2023-11-21 16:39:38 +08:00
printf ( "%10s: %s \n " , "Default" , FASTFETCH_DATATEXT_STRUCTURE );
2026-03-29 09:28:49 +08:00
} else if ( yyjson_is_bool ( defaultKey )) {
2023-11-21 16:39:38 +08:00
printf ( "%10s: %s \n " , "Default" , yyjson_get_bool ( defaultKey ) ? "true" : "false" );
2026-03-29 09:28:49 +08:00
} else if ( yyjson_is_num ( defaultKey )) {
2023-11-22 15:36:48 +08:00
printf ( "%10s: %d \n " , "Default" , yyjson_get_int ( defaultKey ));
2026-03-29 09:28:49 +08:00
} else if ( yyjson_is_str ( defaultKey )) {
2023-11-21 16:39:38 +08:00
printf ( "%10s: %s \n " , "Default" , yyjson_get_str ( defaultKey ));
2026-03-29 09:28:49 +08:00
} else {
2023-11-21 16:39:38 +08:00
printf ( "%10s: Unknown \n " , "Default" );
2026-03-29 09:28:49 +08:00
}
2023-11-21 16:39:38 +08:00
}
yyjson_val * enumKey = yyjson_obj_get ( argObj , "enum" );
2026-03-29 09:28:49 +08:00
if ( enumKey ) {
2023-11-21 16:39:38 +08:00
printf ( "%10s: \n " , "Options" );
yyjson_val * optKey , * optVal ;
size_t optIdx , optMax ;
2026-03-29 09:28:49 +08:00
yyjson_obj_foreach ( enumKey , optIdx , optMax , optKey , optVal ) {
2023-11-21 16:39:38 +08:00
printf ( "%12s: %s \n " , yyjson_get_str ( optKey ), yyjson_get_str ( optVal ));
2026-03-29 09:28:49 +08:00
}
2023-11-21 16:39:38 +08:00
}
2026-03-29 09:28:49 +08:00
} else {
2023-11-21 16:39:38 +08:00
putchar ( '\n' );
2026-03-29 09:28:49 +08:00
}
2023-11-21 16:39:38 +08:00
yyjson_val * remarkKey = yyjson_obj_get ( flagObj , "remark" );
2026-03-29 09:28:49 +08:00
if ( remarkKey ) {
if ( yyjson_is_str ( remarkKey )) {
2024-02-14 22:07:08 +08:00
printf ( "%10s: %s \n " , "Remark" , yyjson_get_str ( remarkKey ));
2026-03-29 09:28:49 +08:00
} else if ( yyjson_is_arr ( remarkKey ) && yyjson_arr_size ( remarkKey ) > 0 ) {
2024-02-14 22:07:08 +08:00
yyjson_val * remarkStr ;
size_t remarkIdx , remarkMax ;
2026-03-29 09:28:49 +08:00
yyjson_arr_foreach ( remarkKey , remarkIdx , remarkMax , remarkStr ) {
if ( remarkIdx == 0 ) {
2024-02-14 22:07:08 +08:00
printf ( "%10s: %s \n " , "Remark" , yyjson_get_str ( remarkStr ));
2026-03-29 09:28:49 +08:00
} else {
2024-02-14 22:07:08 +08:00
printf ( " %s \n " , yyjson_get_str ( remarkStr ));
2026-03-29 09:28:49 +08:00
}
2024-02-14 22:07:08 +08:00
}
}
}
2023-11-21 16:39:38 +08:00
2024-05-20 16:45:23 +08:00
yyjson_doc_free ( doc );
2023-11-20 16:37:59 +08:00
return true ;
}
2023-11-02 14:06:53 +08:00
}
}
2024-05-20 16:45:23 +08:00
yyjson_doc_free ( doc );
2023-11-20 16:37:59 +08:00
return false ;
}
2026-07-10 21:13:03 +08:00
[[ gnu :: cold ]]
2026-03-29 09:28:49 +08:00
static void printCommandHelp ( const char * command ) {
2026-07-10 18:08:04 +08:00
if ( command == nullptr ) {
2023-11-20 16:37:59 +08:00
printFullHelp ();
2026-03-29 09:28:49 +08:00
} else if ( ffStrEqualsIgnCase ( command , "format-json" )) {
2024-12-11 16:12:03 +08:00
printCommandFormatHelpJson ();
2026-03-29 09:28:49 +08:00
} else if ( ffCharIsEnglishAlphabet ( command [ 0 ]) && ffStrEndsWithIgnCase ( command , "-format" )) { // <module>-format
2023-10-07 13:40:40 +08:00
printCommandFormatHelp ( command );
2026-03-29 09:28:49 +08:00
} else if ( ! printSpecificCommandHelp ( command )) {
2023-10-07 13:40:40 +08:00
fprintf ( stderr , "Error: No specific help for command '%s' provided \n " , command );
2026-03-29 09:28:49 +08:00
}
2021-02-20 19:32:57 +01:00
}
2026-07-10 21:13:03 +08:00
[[ gnu :: cold ]]
2026-03-29 09:28:49 +08:00
static void listAvailablePresets ( bool pretty ) {
FF_LIST_FOR_EACH ( FFstrbuf , path , instance . state . platform . dataDirs ) {
2023-01-28 17:19:45 +01:00
ffStrbufAppendS ( path , "fastfetch/presets/" );
2023-11-13 16:41:10 +08:00
ffListFilesRecursively ( path -> chars , pretty );
2021-04-25 11:48:25 +02:00
}
2024-05-24 09:18:48 +08:00
2026-03-29 09:28:49 +08:00
if ( instance . state . platform . exePath . length ) {
2024-10-15 10:31:37 +08:00
FF_STRBUF_AUTO_DESTROY absolutePath = ffStrbufCreateCopy ( & instance . state . platform . exePath );
ffStrbufSubstrBeforeLastC ( & absolutePath , '/' );
ffStrbufAppendS ( & absolutePath , "/presets/" );
ffListFilesRecursively ( absolutePath . chars , pretty );
}
2021-04-25 11:48:25 +02:00
}
2026-07-10 21:13:03 +08:00
[[ gnu :: cold ]]
2026-03-29 09:28:49 +08:00
static void listAvailableLogos ( void ) {
FF_LIST_FOR_EACH ( FFstrbuf , path , instance . state . platform . dataDirs ) {
2023-01-28 17:19:45 +01:00
ffStrbufAppendS ( path , "fastfetch/logos/" );
2023-11-13 16:41:10 +08:00
ffListFilesRecursively ( path -> chars , true );
2023-01-08 01:41:09 +01:00
}
2021-04-25 11:48:25 +02:00
}
2026-07-10 21:13:03 +08:00
[[ gnu :: cold ]]
2026-03-29 09:28:49 +08:00
static void listConfigPaths ( void ) {
FF_LIST_FOR_EACH ( FFstrbuf , folder , instance . state . platform . configDirs ) {
2023-06-10 21:34:41 +08:00
bool exists = false ;
2023-12-21 09:52:15 +08:00
uint32_t length = folder -> length + ( uint32_t ) strlen ( "fastfetch" ) + 1 /* trailing slash */ ;
2023-06-10 21:34:41 +08:00
ffStrbufAppendS ( folder , "fastfetch/config.jsonc" );
exists = ffPathExists ( folder -> chars , FF_PATHTYPE_FILE );
2023-11-14 09:58:42 +08:00
ffStrbufSubstrBefore ( folder , length );
2023-06-10 21:34:41 +08:00
printf ( "%s%s \n " , folder -> chars , exists ? " (*)" : "" );
2023-01-02 16:51:11 +08:00
}
}
2026-07-10 21:13:03 +08:00
[[ gnu :: cold ]]
2026-03-29 09:28:49 +08:00
static void listDataPaths ( void ) {
FF_LIST_FOR_EACH ( FFstrbuf , folder , instance . state . platform . dataDirs ) {
2023-01-08 01:41:09 +01:00
ffStrbufAppendS ( folder , "fastfetch/" );
puts ( folder -> chars );
}
}
2026-07-10 21:13:03 +08:00
[[ gnu :: cold ]]
2026-03-29 09:28:49 +08:00
static void listModules ( bool pretty ) {
2023-08-20 20:51:55 +08:00
unsigned count = 0 ;
2026-03-29 09:28:49 +08:00
for ( int i = 0 ; i <= 'Z' - 'A' ; ++ i ) {
for ( FFModuleBaseInfo ** modules = ffModuleInfos [ i ]; * modules ; ++ modules ) {
2023-08-20 20:51:55 +08:00
++ count ;
2026-03-29 09:28:49 +08:00
if ( pretty ) {
2024-01-22 18:50:11 +08:00
printf ( "%d)%s%-14s: %s \n " , count , count > 9 ? " " : " " , ( * modules ) -> name , ( * modules ) -> description );
2026-03-29 09:28:49 +08:00
} else {
2023-11-17 14:12:17 +08:00
printf ( "%s:%s \n " , ( * modules ) -> name , ( * modules ) -> description );
2026-03-29 09:28:49 +08:00
}
2023-08-20 20:51:55 +08:00
}
}
}
2026-03-29 09:28:49 +08:00
static bool parseJsoncFile ( FFdata * data , const char * path , yyjson_read_flag flg ) {
2026-01-15 15:03:38 +08:00
assert ( ! data -> configDoc );
2024-04-30 09:15:57 +08:00
2023-06-10 21:34:41 +08:00
{
2024-04-30 09:15:57 +08:00
yyjson_read_err error ;
2026-01-15 15:03:38 +08:00
data -> configDoc = path
2026-07-10 18:08:04 +08:00
? yyjson_read_file ( path , flg , nullptr , & error )
: yyjson_read_fp ( stdin , flg , nullptr , & error );
2026-03-29 09:28:49 +08:00
if ( ! data -> configDoc ) {
if ( error . code != YYJSON_READ_ERROR_FILE_OPEN ) {
if ( path ) {
2025-08-14 10:50:39 +08:00
size_t row = 0 , col = error . pos ;
FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate ();
2026-03-29 09:28:49 +08:00
if ( ffAppendFileBuffer ( path , & content )) {
2026-07-10 18:08:04 +08:00
yyjson_locate_pos ( content . chars , content . length , error . pos , & row , & col , nullptr );
2026-03-29 09:28:49 +08:00
}
2025-08-14 10:50:39 +08:00
fprintf ( stderr , "Error: failed to parse JSON config file `%s` at (%zu, %zu): %s \n " , path , row , col , error . msg );
2026-03-29 09:28:49 +08:00
} else {
2025-08-14 10:50:39 +08:00
fprintf ( stderr , "Error: failed to parse JSON from stdin at %zu: %s \n " , error . pos , error . msg );
2026-03-29 09:28:49 +08:00
}
2025-08-14 10:50:39 +08:00
2024-04-30 09:15:57 +08:00
exit ( 477 );
}
return false ;
}
}
{
2026-01-15 15:03:38 +08:00
yyjson_val * const root = yyjson_doc_get_root ( data -> configDoc );
2026-03-29 09:28:49 +08:00
if ( ! yyjson_is_obj ( root )) {
2026-06-19 20:19:13 +08:00
fputs ( "JsonConfig Error: Invalid JSON config format. Root value must be an object" , stderr );
exit ( 477 );
2026-03-29 09:28:49 +08:00
}
2024-04-30 09:15:57 +08:00
2026-07-10 18:08:04 +08:00
yyjson_val * problematicKey = nullptr ;
const char * error = nullptr ;
const char * problematicModule = nullptr ;
2024-04-30 09:15:57 +08:00
if (
error ||
2026-06-19 20:19:13 +08:00
(( error = ffOptionsParseLogoJsonConfig ( & instance . config . logo , root , & problematicKey )) && ( problematicModule = "logo" )) ||
(( error = ffOptionsParseGeneralJsonConfig ( & instance . config . general , root , & problematicKey )) && ( problematicModule = "general" )) ||
(( error = ffOptionsParseDisplayJsonConfig ( & instance . config . display , root , & problematicKey )) && ( problematicModule = "display" )) ||
2026-03-29 09:28:49 +08:00
false ) {
2026-06-19 20:19:13 +08:00
if ( problematicKey ) {
fprintf ( stderr , "JsonConfig Error (%s.%s): %s \n " , problematicModule , unsafe_yyjson_get_str ( problematicKey ), error );
} else {
fprintf ( stderr , "JsonConfig Error (%s): %s \n " , problematicModule , error );
}
2023-06-10 21:34:41 +08:00
exit ( 477 );
}
2023-06-17 20:39:08 +08:00
}
2023-07-05 14:21:35 +08:00
return true ;
2023-06-10 21:34:41 +08:00
}
2026-07-10 21:13:03 +08:00
[[ gnu :: cold ]]
2026-08-04 10:41:26 +08:00
static void setupGenConfigPath ( FFdata * data , const char * filePath ) {
2026-03-29 09:28:49 +08:00
if ( ! filePath ) {
if ( instance . state . platform . configDirs . length == 0 ) {
2025-08-27 15:29:38 +08:00
fprintf ( stderr , "Error: No config directory found to generate config file in. Use --gen-config <path> to specify a path \n " );
exit ( 477 );
}
2026-01-09 16:18:25 +08:00
FFstrbuf * configDir = FF_LIST_FIRST ( FFstrbuf , instance . state . platform . configDirs );
2026-01-15 15:03:38 +08:00
ffStrbufEnsureFixedLengthFree ( & data -> genConfigPath , configDir -> length + strlen ( "fastfetch/config.jsonc" ));
ffStrbufSet ( & data -> genConfigPath , configDir );
ffStrbufAppendS ( & data -> genConfigPath , "fastfetch/config.jsonc" );
2026-03-29 09:28:49 +08:00
} else {
2026-01-15 15:03:38 +08:00
ffStrbufSetS ( & data -> genConfigPath , filePath );
2023-08-07 14:15:11 +08:00
}
2026-08-04 10:41:26 +08:00
}
2023-08-07 14:15:11 +08:00
2026-08-04 10:41:26 +08:00
[[ gnu :: cold ]]
static void generateConfigFile ( FFdata * data , const char * filePath ) {
if ( data -> resultDoc ) {
fprintf ( stderr , "Error: duplicated `--gen-config` or `--format json` flags found \n " );
2023-11-10 15:47:09 +08:00
exit ( 477 );
2023-01-02 12:43:11 +08:00
}
2025-08-11 14:49:22 +08:00
2026-08-04 10:41:26 +08:00
setupGenConfigPath ( data , filePath );
if ( ffPathExists ( data -> genConfigPath . chars , FF_PATHTYPE_ANY )) {
fprintf ( stderr , "Error: file `%s` exists. Please remove it before generating a new one \n " , data -> genConfigPath . chars );
exit ( 477 );
}
data -> docType = FF_RESULT_DOC_TYPE_CONFIG ;
2026-07-10 18:08:04 +08:00
data -> resultDoc = yyjson_mut_doc_new ( nullptr );
2023-01-02 12:43:11 +08:00
}
2026-03-29 09:28:49 +08:00
static void optionParseConfigFile ( FFdata * data , const char * key , const char * value ) {
if ( data -> configLoaded ) {
2023-11-16 16:41:08 +08:00
fprintf ( stderr , "Error: only one config file can be loaded \n " );
exit ( 413 );
}
data -> configLoaded = true ;
2026-07-10 18:08:04 +08:00
if ( value == nullptr ) {
2023-11-16 16:41:08 +08:00
fprintf ( stderr , "Error: usage: %s <config> \n " , key );
2021-04-25 11:48:25 +02:00
exit ( 413 );
}
2023-07-05 14:21:35 +08:00
2026-03-29 09:28:49 +08:00
if ( value [ 0 ] == '\0' || ffStrEqualsIgnCase ( value , "none" )) {
2023-11-16 16:41:08 +08:00
return ;
2026-03-29 09:28:49 +08:00
}
2023-11-16 16:41:08 +08:00
2026-03-29 09:28:49 +08:00
if ( value [ 0 ] == '-' && value [ 1 ] == '\0' ) {
2026-07-10 18:08:04 +08:00
parseJsoncFile ( data , nullptr , false );
2025-08-14 10:50:39 +08:00
return ;
}
2026-03-29 09:28:49 +08:00
// Try to load as an absolute path
2022-08-22 12:45:20 +02:00
2026-01-09 16:18:25 +08:00
FF_STRBUF_AUTO_DESTROY absolutePath = ffStrbufCreateA ( 128 );
ffStrbufSetS ( & absolutePath , value );
2025-04-04 14:38:51 +08:00
bool strictJson = ffStrbufEndsWithIgnCaseS ( & absolutePath , ".json" );
2025-08-19 10:25:52 +08:00
bool jsonc = ! strictJson && ffStrbufEndsWithIgnCaseS ( & absolutePath , ".jsonc" );
bool json5 = ! strictJson && ! jsonc && ffStrbufEndsWithIgnCaseS ( & absolutePath , ".json5" );
bool needExtension = ! strictJson && ! jsonc && ! json5 ;
2026-03-29 09:28:49 +08:00
if ( needExtension ) {
2025-04-04 14:38:51 +08:00
ffStrbufAppendS ( & absolutePath , ".jsonc" );
2026-03-29 09:28:49 +08:00
}
2023-08-13 10:18:16 +08:00
2025-08-19 10:25:52 +08:00
yyjson_read_flag flag = strictJson
? 0
: jsonc
2026-03-29 09:28:49 +08:00
? YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS
: YYJSON_READ_JSON5 ;
2025-08-19 10:25:52 +08:00
2026-03-29 09:28:49 +08:00
if ( parseJsoncFile ( data , absolutePath . chars , flag )) {
return ;
}
2025-03-31 10:13:30 +08:00
2026-03-29 09:28:49 +08:00
// Try to load as a relative path with the config directory
2025-12-05 10:30:51 +08:00
2026-03-29 09:28:49 +08:00
FF_LIST_FOR_EACH ( FFstrbuf , path , instance . state . platform . configDirs ) {
2025-12-05 10:30:51 +08:00
ffStrbufSet ( & absolutePath , path );
ffStrbufAppendS ( & absolutePath , "fastfetch/" );
ffStrbufAppendS ( & absolutePath , value );
2026-03-29 09:28:49 +08:00
if ( needExtension ) {
2025-12-05 10:30:51 +08:00
ffStrbufAppendS ( & absolutePath , ".jsonc" );
2026-03-29 09:28:49 +08:00
}
2025-12-05 10:30:51 +08:00
2026-03-29 09:28:49 +08:00
if ( parseJsoncFile ( data , absolutePath . chars , flag )) {
return ;
}
2025-12-05 10:30:51 +08:00
}
2026-03-29 09:28:49 +08:00
// Try to load as a preset
2025-04-05 08:37:18 +08:00
2026-03-29 09:28:49 +08:00
FF_LIST_FOR_EACH ( FFstrbuf , path , instance . state . platform . dataDirs ) {
2025-04-05 08:37:18 +08:00
ffStrbufSet ( & absolutePath , path );
ffStrbufAppendS ( & absolutePath , "fastfetch/presets/" );
ffStrbufAppendS ( & absolutePath , value );
2026-03-29 09:28:49 +08:00
if ( needExtension ) {
2025-04-05 08:37:18 +08:00
ffStrbufAppendS ( & absolutePath , ".jsonc" );
2026-03-29 09:28:49 +08:00
}
2022-08-22 12:45:20 +02:00
2026-03-29 09:28:49 +08:00
if ( parseJsoncFile ( data , absolutePath . chars , flag )) {
return ;
}
2025-04-05 08:37:18 +08:00
}
2021-04-25 11:48:25 +02:00
2026-03-29 09:28:49 +08:00
// Try to load as a relative path with the directory of fastfetch binary, for Windows support
2025-03-31 10:13:30 +08:00
2026-03-29 09:28:49 +08:00
if ( instance . state . platform . exePath . length ) {
2025-04-08 09:11:08 +08:00
uint32_t lastSlash = ffStrbufLastIndexC ( & instance . state . platform . exePath , '/' ) + 1 ;
assert ( lastSlash < instance . state . platform . exePath . length );
// Try {exePath}/
ffStrbufSetNS ( & absolutePath , lastSlash , instance . state . platform . exePath . chars );
2025-04-05 08:37:18 +08:00
ffStrbufAppendS ( & absolutePath , value );
2026-03-29 09:28:49 +08:00
if ( needExtension ) {
2025-04-05 08:37:18 +08:00
ffStrbufAppendS ( & absolutePath , ".jsonc" );
2026-03-29 09:28:49 +08:00
}
if ( parseJsoncFile ( data , absolutePath . chars , flag )) {
return ;
}
2023-08-24 14:29:35 +08:00
2025-04-08 09:11:08 +08:00
// Try {exePath}/presets/
ffStrbufSubstrBefore ( & absolutePath , lastSlash );
ffStrbufAppendS ( & absolutePath , "presets/" );
ffStrbufAppendS ( & absolutePath , value );
2026-03-29 09:28:49 +08:00
if ( needExtension ) {
2025-04-08 09:11:08 +08:00
ffStrbufAppendS ( & absolutePath , ".jsonc" );
2026-03-29 09:28:49 +08:00
}
if ( parseJsoncFile ( data , absolutePath . chars , flag )) {
return ;
}
2023-08-24 14:29:35 +08:00
}
2026-03-29 09:28:49 +08:00
// File not found
2022-08-22 12:45:20 +02:00
2021-04-25 11:48:25 +02:00
fprintf ( stderr , "Error: couldn't find config: %s \n " , value );
exit ( 414 );
}
2026-07-10 21:13:03 +08:00
[[ gnu :: cold ]]
2026-03-29 09:28:49 +08:00
static void printVersion () {
2024-07-02 10:12:26 +08:00
FFVersionResult * result = & ffVersionResult ;
printf ( "%s %s%s%s (%s) \n " , result -> projectName , result -> version , result -> versionTweak , result -> debugMode ? "-debug" : "" , result -> architecture );
2023-08-17 16:00:24 +08:00
}
2023-01-19 14:57:54 +08:00
2026-03-29 09:28:49 +08:00
static void enableJsonOutput ( FFdata * data ) {
if ( data -> resultDoc ) {
2026-01-15 15:03:38 +08:00
fprintf ( stderr , "Error: duplicated `--gen-config` or `--format json` flags found \n " );
exit ( 477 );
2025-09-28 15:07:45 +08:00
}
2026-01-15 15:03:38 +08:00
2026-07-10 18:08:04 +08:00
data -> resultDoc = yyjson_mut_doc_new ( nullptr );
2026-01-15 15:03:38 +08:00
data -> docType = FF_RESULT_DOC_TYPE_JSON ;
yyjson_mut_doc_set_root ( data -> resultDoc , yyjson_mut_arr ( data -> resultDoc ));
2025-09-28 15:07:45 +08:00
}
2026-08-04 10:41:26 +08:00
static void genConfigCommon ( FFdata * data , const char * value ) {
2026-09-22 14:49:43 +08:00
if ( ! getenv ( "NO_COLOR" ) && ffIsTerminal ( STDOUT_FILENO ) && ffIsTerminal ( STDIN_FILENO )
2026-08-04 21:05:07 +08:00
#ifdef _WIN32
&& ffIsWindows10OrGreater ()
#endif
) {
2026-08-04 10:41:26 +08:00
data -> genConfigInteractive = true ;
setupGenConfigPath ( data , value );
} else {
generateConfigFile ( data , value );
}
}
2026-03-29 09:28:49 +08:00
static void parseCommand ( FFdata * data , char * key , char * value ) {
if ( ffStrEqualsIgnCase ( key , "-h" ) || ffStrEqualsIgnCase ( key , "--help" )) {
2023-08-17 16:00:24 +08:00
printCommandHelp ( value );
exit ( 0 );
2026-06-08 10:37:11 +08:00
} else if ( ffStrEqualsIgnCase ( key , "--help-raw" )) {
2023-11-22 09:14:27 +08:00
puts ( FASTFETCH_DATATEXT_JSON_HELP );
exit ( 0 );
2026-03-29 09:28:49 +08:00
} else if ( ffStrEqualsIgnCase ( key , "-v" ) || ffStrEqualsIgnCase ( key , "--version" )) {
2023-08-17 16:00:24 +08:00
printVersion ();
2022-03-20 17:12:52 +01:00
exit ( 0 );
2026-03-29 09:28:49 +08:00
} else if ( ffStrEqualsIgnCase ( key , "--version-raw" )) {
2022-03-20 13:16:04 +01:00
puts ( FASTFETCH_PROJECT_VERSION );
2021-03-03 19:59:39 +01:00
exit ( 0 );
2026-03-29 09:28:49 +08:00
} else if ( ffStrStartsWithIgnCase ( key , "--print-" )) {
2023-09-22 15:00:58 +08:00
const char * subkey = key + strlen ( "--print-" );
2026-03-29 09:28:49 +08:00
if ( ffStrEndsWithIgnCase ( subkey , "structure" )) {
2022-10-22 13:02:04 +08:00
puts ( FASTFETCH_DATATEXT_STRUCTURE );
2026-03-29 09:28:49 +08:00
} else if ( ffStrEqualsIgnCase ( subkey , "logos" )) {
2023-06-24 10:59:53 +08:00
ffLogoBuiltinPrint ();
2026-03-29 09:28:49 +08:00
} else {
2023-11-10 15:05:09 +08:00
fprintf ( stderr , "Error: unsupported print option: %s \n " , key );
exit ( 415 );
}
exit ( 0 );
2026-03-29 09:28:49 +08:00
} else if ( ffStrStartsWithIgnCase ( key , "--list-" )) {
2023-09-22 15:00:58 +08:00
const char * subkey = key + strlen ( "--list-" );
2026-03-29 09:28:49 +08:00
if ( ffStrEqualsIgnCase ( subkey , "modules" )) {
2023-11-16 14:21:24 +08:00
listModules ( ! value || ! ffStrEqualsIgnCase ( value , "autocompletion" ));
2026-03-29 09:28:49 +08:00
} else if ( ffStrEqualsIgnCase ( subkey , "presets" )) {
2023-11-16 14:21:24 +08:00
listAvailablePresets ( ! value || ! ffStrEqualsIgnCase ( value , "autocompletion" ));
2026-03-29 09:28:49 +08:00
} else if ( ffStrEqualsIgnCase ( subkey , "config-paths" )) {
2023-06-24 10:59:53 +08:00
listConfigPaths ();
2026-03-29 09:28:49 +08:00
} else if ( ffStrEqualsIgnCase ( subkey , "data-paths" )) {
2023-06-24 10:59:53 +08:00
listDataPaths ();
2026-03-29 09:28:49 +08:00
} else if ( ffStrEqualsIgnCase ( subkey , "features" )) {
2022-10-22 13:02:04 +08:00
ffListFeatures ();
2026-03-29 09:28:49 +08:00
} else if ( ffStrEqualsIgnCase ( subkey , "logos" )) {
if ( value ) {
if ( ffStrEqualsIgnCase ( value , "autocompletion" )) {
2023-11-16 14:21:24 +08:00
ffLogoBuiltinListAutocompletion ();
2026-03-29 09:28:49 +08:00
} else if ( ffStrEqualsIgnCase ( value , "builtin" )) {
2023-11-16 14:21:24 +08:00
ffLogoBuiltinList ();
2026-03-29 09:28:49 +08:00
} else if ( ffStrEqualsIgnCase ( value , "custom" )) {
2023-11-16 14:21:24 +08:00
listAvailableLogos ();
2026-03-29 09:28:49 +08:00
} else {
2023-11-16 14:21:24 +08:00
fprintf ( stderr , "Error: unsupported logo type: %s \n " , value );
exit ( 415 );
}
2026-03-29 09:28:49 +08:00
} else {
2023-11-16 14:21:24 +08:00
puts ( "Builtin logos:" );
ffLogoBuiltinList ();
puts ( " \n Custom logos:" );
listAvailableLogos ();
}
2026-03-29 09:28:49 +08:00
} else {
2023-11-10 15:05:09 +08:00
fprintf ( stderr , "Error: unsupported list option: %s \n " , key );
exit ( 415 );
2023-07-03 16:22:16 +08:00
}
2023-08-12 09:40:37 +08:00
2023-11-10 15:05:09 +08:00
exit ( 0 );
2026-03-29 09:28:49 +08:00
} else if ( ffStrEqualsIgnCase ( key , "--gen-config" )) {
2026-08-04 10:41:26 +08:00
genConfigCommon ( data , value );
2026-03-29 09:28:49 +08:00
} else if ( ffStrEqualsIgnCase ( key , "-c" ) || ffStrEqualsIgnCase ( key , "--config" )) {
2023-11-16 16:41:08 +08:00
optionParseConfigFile ( data , key , value );
2026-03-29 09:28:49 +08:00
} else if ( ffStrEqualsIgnCase ( key , "-j" ) || ffStrEqualsIgnCase ( key , "--json" )) {
if ( ffOptionParseBoolean ( value )) {
enableJsonOutput ( data );
}
} else if ( ffStrEqualsIgnCase ( key , "--format" )) {
2026-01-15 15:03:38 +08:00
if ( !! ffOptionParseEnum ( key , value , ( FFKeyValuePair []) {
2026-03-30 10:27:23 +08:00
{ "default" , false },
{ "json" , true },
2026-03-29 09:28:49 +08:00
{},
})) {
enableJsonOutput ( data );
}
} else if ( ffStrEqualsIgnCase ( key , "--dynamic-interval" )) {
2026-09-21 20:08:11 +08:00
instance . state . dynamicInterval = ffOptionParseUInt32 ( key , value ); // ms
2026-07-28 10:52:04 +08:00
} else if ( ffStrEqualsIgnCase ( key , "-w" ) || ffStrEqualsIgnCase ( key , "--watch" )) {
if ( value == nullptr ) {
instance . state . dynamicInterval = 1000 ; // default to 1 second if no value is provided
} else {
instance . state . dynamicInterval = ffOptionParseUInt32 ( key , value ) * 1000 ; // seconds to milliseconds
}
2026-03-29 09:28:49 +08:00
} else {
2023-11-10 15:05:09 +08:00
return ;
2026-03-29 09:28:49 +08:00
}
2023-11-10 15:05:09 +08:00
// Don't parse it again in parseOption.
// This is necessary because parseOption doesn't understand this option and will result in an unknown option error.
key [ 0 ] = '\0' ;
2026-03-29 09:28:49 +08:00
if ( value ) {
value [ 0 ] = '\0' ;
}
2023-11-10 15:05:09 +08:00
}
2026-03-29 09:28:49 +08:00
static void parseOption ( FFdata * data , const char * key , const char * value ) {
if ( ffStrEqualsIgnCase ( key , "-s" ) || ffStrEqualsIgnCase ( key , "--structure" )) {
2023-06-12 15:48:26 +08:00
ffOptionParseString ( key , value , & data -> structure );
2026-03-29 09:28:49 +08:00
}
2022-04-02 14:35:43 +02:00
2026-03-29 09:28:49 +08:00
else if (
2023-10-26 10:26:15 +08:00
ffOptionsParseGeneralCommandLine ( & instance . config . general , key , value ) ||
ffOptionsParseLogoCommandLine ( & instance . config . logo , key , value ) ||
2025-09-03 09:36:16 +08:00
ffOptionsParseDisplayCommandLine ( & instance . config . display , key , value ) ||
2026-03-29 09:28:49 +08:00
ffParseModuleOptions ( key , value )) {
}
2023-08-17 16:00:24 +08:00
2026-03-29 09:28:49 +08:00
else if ( ffStrEqualsIgnCase ( key , "--structure-disabled" )) {
2026-01-15 10:35:01 +08:00
ffOptionParseString ( key , value , & data -> structureDisabled );
2026-03-29 09:28:49 +08:00
}
2026-01-15 10:35:01 +08:00
2026-03-29 09:28:49 +08:00
else {
2021-04-03 11:39:59 +02:00
fprintf ( stderr , "Error: unknown option: %s \n " , key );
2021-03-05 23:00:28 +01:00
exit ( 400 );
2021-03-03 19:59:39 +01:00
}
}
2026-03-29 09:28:49 +08:00
static void parseConfigFiles ( FFdata * data ) {
if ( __builtin_expect ( data -> genConfigPath . length == 0 , true )) {
FF_LIST_FOR_EACH ( FFstrbuf , dir , instance . state . platform . configDirs ) {
2023-10-23 13:39:07 +08:00
uint32_t dirLength = dir -> length ;
2023-06-10 21:34:41 +08:00
2023-10-23 13:39:07 +08:00
ffStrbufAppendS ( dir , "fastfetch/config.jsonc" );
2026-01-15 15:03:38 +08:00
bool success = parseJsoncFile ( data , dir -> chars , YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS );
2025-08-19 10:25:52 +08:00
ffStrbufSubstrBefore ( dir , dirLength );
2026-03-29 09:28:49 +08:00
if ( success ) {
return ;
}
2025-08-19 10:25:52 +08:00
ffStrbufAppendS ( dir , "fastfetch/config.json5" );
2026-01-15 15:03:38 +08:00
success = parseJsoncFile ( data , dir -> chars , YYJSON_READ_JSON5 );
2023-10-23 13:39:07 +08:00
ffStrbufSubstrBefore ( dir , dirLength );
2026-03-29 09:28:49 +08:00
if ( success ) {
return ;
}
2023-10-23 13:39:07 +08:00
}
2023-06-10 21:34:41 +08:00
}
2021-03-03 19:59:39 +01:00
}
2026-03-29 09:28:49 +08:00
static void parseArguments ( FFdata * data , int argc , char ** argv , void ( * parser )( FFdata * data , char * key , char * value )) {
for ( int i = 1 ; i < argc ; i ++ ) {
2023-08-17 16:00:24 +08:00
const char * key = argv [ i ];
2026-03-29 09:28:49 +08:00
if ( * key == '\0' ) {
2023-11-10 15:05:09 +08:00
continue ; // has been handled by parseCommand
2026-03-29 09:28:49 +08:00
}
2023-11-10 15:05:09 +08:00
2026-03-29 09:28:49 +08:00
if ( * key != '-' ) {
2023-08-17 16:00:24 +08:00
fprintf ( stderr , "Error: invalid option: %s. An option must start with `-` \n " , key );
exit ( 400 );
}
2026-03-29 09:28:49 +08:00
if ( i == argc - 1 || ( argv [ i + 1 ][ 0 ] == '-' && argv [ i + 1 ][ 1 ] != '\0' && // `-` is used as an alias for `/dev/stdin`
! ffStrEqualsIgnCase ( argv [ i ], "--separator-string" ) // Separator string can start with a -
)) {
2026-07-10 18:08:04 +08:00
parser ( data , argv [ i ], nullptr );
2026-03-29 09:28:49 +08:00
} else {
2023-11-10 15:05:09 +08:00
parser ( data , argv [ i ], argv [ i + 1 ]);
2021-03-03 19:59:39 +01:00
++ i ;
}
}
2021-02-27 20:31:54 +01:00
}
2026-03-29 09:28:49 +08:00
static void run ( FFdata * data ) {
2026-01-15 15:03:38 +08:00
const bool useJsonConfig = data -> structure . length == 0 && data -> configDoc ;
2023-08-18 13:28:07 +08:00
2026-03-29 09:28:49 +08:00
if ( useJsonConfig ) {
2026-01-15 15:03:38 +08:00
ffPrintJsonConfig ( data , true /* prepare */ );
2026-03-29 09:28:49 +08:00
} else {
// If we don't have a custom structure, use the default one
if ( data -> structure . length == 0 ) {
2025-09-19 14:51:31 +08:00
ffStrbufAppendS ( & data -> structure , FASTFETCH_DATATEXT_STRUCTURE ); // Cannot use `ffStrbufSetStatic` here because we will modify the string
2026-03-29 09:28:49 +08:00
}
2023-10-23 13:39:07 +08:00
ffPrepareCommandOption ( data );
2025-09-19 14:51:31 +08:00
}
2022-09-29 17:14:50 +08:00
2023-06-24 10:59:53 +08:00
ffStart ();
2021-03-03 19:59:39 +01:00
2026-03-29 09:28:49 +08:00
if ( ! data -> resultDoc ) {
2026-01-15 15:03:38 +08:00
ffLogoPrint ();
2026-03-29 09:28:49 +08:00
}
2026-01-15 15:03:38 +08:00
2026-03-29 09:28:49 +08:00
#if defined(_WIN32)
if ( ! instance . config . display . noBuffer ) {
fflush ( stdout );
}
#endif
2023-01-12 00:11:25 +08:00
2026-03-29 09:28:49 +08:00
while ( true ) {
if ( useJsonConfig ) {
2026-01-15 15:03:38 +08:00
ffPrintJsonConfig ( data , false );
2026-03-29 09:28:49 +08:00
} else {
2026-01-15 15:03:38 +08:00
ffPrintCommandOption ( data );
2026-03-29 09:28:49 +08:00
}
2025-11-05 10:00:31 +08:00
2026-03-29 09:28:49 +08:00
if ( instance . state . dynamicInterval > 0 ) {
2026-06-05 14:46:27 +08:00
ffLogoPrintRemaining (); // `logoLineCacheClear` inside so that ffLogoPrintLine will use `\e[nC` to move the cursor to the right position instead of reprinting the logo
fputs ( "\e[J" , stdout ); // Clear from cursor to the end of the screen to prevent artifacts when the new output is shorter than the previous one
2025-11-05 10:00:31 +08:00
fflush ( stdout );
ffTimeSleep ( instance . state . dynamicInterval );
2026-06-05 14:46:27 +08:00
fputs ( "\e[H" , stdout ); // Move cursor to the top left corner to overwrite the previous output
2026-06-05 15:27:25 +08:00
instance . state . keysHeight = 0 ; // Reset keysHeight so `ffLogoPrintRemaining` will recalculate it
2026-09-21 22:46:40 +08:00
ffCacheInvalidateAll (); // Drop the detection results cached for the previous round, so that this round re-detects them
2026-03-29 09:28:49 +08:00
} else {
2025-11-05 10:00:31 +08:00
break ;
2026-03-29 09:28:49 +08:00
}
2026-01-16 20:02:03 +08:00
2026-03-29 09:28:49 +08:00
if ( useJsonConfig ) {
2026-01-16 20:02:03 +08:00
ffPrintJsonConfig ( data , true /* prepare */ );
2026-03-29 09:28:49 +08:00
} else {
2026-01-16 20:02:03 +08:00
ffPrepareCommandOption ( data );
2026-03-29 09:28:49 +08:00
}
2025-11-05 10:00:31 +08:00
}
2023-06-11 00:51:29 +08:00
2026-03-29 09:28:49 +08:00
if ( data -> resultDoc ) {
2026-07-10 18:08:04 +08:00
yyjson_mut_write_fp ( stdout , data -> resultDoc , YYJSON_WRITE_INF_AND_NAN_AS_NULL | YYJSON_WRITE_PRETTY_TWO_SPACES | YYJSON_WRITE_NEWLINE_AT_END , nullptr , nullptr );
2026-03-29 09:28:49 +08:00
} else {
if ( instance . config . logo . printRemaining ) {
2025-11-05 10:00:31 +08:00
ffLogoPrintRemaining ();
2026-03-29 09:28:49 +08:00
}
2023-10-23 13:39:07 +08:00
ffFinish ();
2025-11-05 10:00:31 +08:00
}
2023-10-23 13:39:07 +08:00
}
2026-07-10 21:13:03 +08:00
[[ gnu :: cold ]]
2026-03-29 09:28:49 +08:00
static void writeConfigFile ( FFdata * data ) {
2026-01-15 15:03:38 +08:00
const FFstrbuf * filename = & data -> genConfigPath ;
2025-08-11 14:49:22 +08:00
2026-01-15 15:03:38 +08:00
yyjson_mut_doc * doc = data -> resultDoc ;
2023-10-26 16:22:05 +08:00
yyjson_mut_val * root = yyjson_mut_obj ( doc );
yyjson_mut_doc_set_root ( doc , root );
2025-08-11 14:49:22 +08:00
yyjson_mut_obj_add_str ( doc , root , "$schema" , "https://github.com/fastfetch-cli/fastfetch/raw/master/doc/json_schema.json" );
2023-10-26 16:22:05 +08:00
2026-03-29 09:28:49 +08:00
if ( data -> docType == FF_RESULT_DOC_TYPE_CONFIG_FULL ) {
2026-01-15 15:03:38 +08:00
ffOptionsGenerateLogoJsonConfig ( data , & instance . config . logo );
ffOptionsGenerateDisplayJsonConfig ( data , & instance . config . display );
ffOptionsGenerateGeneralJsonConfig ( data , & instance . config . general );
2026-08-04 10:41:26 +08:00
} else if ( data -> genConfigInteractive ) {
if ( instance . config . logo . type == FF_LOGO_TYPE_NONE ) {
yyjson_mut_obj_add_null ( doc , root , "logo" );
2026-09-25 09:51:34 +08:00
} else if ( instance . config . logo . type == FF_LOGO_TYPE_SMALL && instance . config . logo . position == FF_LOGO_POSITION_LEFT ) {
yyjson_mut_obj_add_str ( doc , root , "logo" , "small" );
} else if ( instance . config . logo . position != FF_LOGO_POSITION_LEFT || instance . config . logo . type == FF_LOGO_TYPE_SMALL ) {
2026-08-04 10:41:26 +08:00
yyjson_mut_val * logo = yyjson_mut_obj ( doc );
2026-09-25 09:51:34 +08:00
if ( instance . config . logo . type == FF_LOGO_TYPE_SMALL ) {
yyjson_mut_obj_add_str ( doc , logo , "type" , "small" );
}
if ( instance . config . logo . position != FF_LOGO_POSITION_LEFT ) {
yyjson_mut_obj_add_str ( doc , logo , "position" , ffLogoPositionToString ( instance . config . logo . position ));
}
2026-08-04 10:41:26 +08:00
yyjson_mut_obj_add_val ( doc , root , "logo" , logo );
}
2025-08-11 14:49:22 +08:00
}
2026-01-15 15:03:38 +08:00
ffMigrateCommandOptionToJsonc ( data );
2023-10-26 16:22:05 +08:00
2026-03-29 09:28:49 +08:00
if ( ffStrbufEqualS ( filename , "-" )) {
2026-07-10 18:08:04 +08:00
yyjson_mut_write_fp ( stdout , doc , YYJSON_WRITE_INF_AND_NAN_AS_NULL | YYJSON_WRITE_PRETTY_TWO_SPACES | YYJSON_WRITE_NEWLINE_AT_END , nullptr , nullptr );
2026-03-29 09:28:49 +08:00
} else {
2024-04-07 20:41:01 +08:00
size_t len ;
2024-04-09 09:01:28 +08:00
FF_AUTO_FREE const char * str = yyjson_mut_write ( doc , YYJSON_WRITE_INF_AND_NAN_AS_NULL | YYJSON_WRITE_PRETTY_TWO_SPACES | YYJSON_WRITE_NEWLINE_AT_END , & len );
2026-03-29 09:28:49 +08:00
if ( ! str ) {
2024-04-07 20:41:01 +08:00
printf ( "Error: failed to generate config file \n " );
exit ( 1 );
}
2026-03-29 09:28:49 +08:00
if ( ffWriteFileData ( filename -> chars , len , str )) {
2025-08-23 20:37:29 +08:00
printf ( "✓ Configuration file generated: `%s` \n "
"* Tip: Use a JSON schema-aware editor for better editing experience \n "
2026-03-29 09:28:49 +08:00
"* Documentation: https://github.com/fastfetch-cli/fastfetch/wiki/Configuration \n " ,
filename -> chars );
} else {
2024-04-07 20:41:01 +08:00
printf ( "Error: failed to write file in `%s` \n " , filename -> chars );
exit ( 1 );
}
2023-10-26 16:22:05 +08:00
}
2023-10-25 10:18:07 +08:00
}
2026-03-29 09:28:49 +08:00
int main ( int argc , char ** argv ) {
2023-10-23 13:39:07 +08:00
ffInitInstance ();
2024-05-06 11:33:50 +08:00
atexit ( ffDestroyInstance );
2023-10-23 13:39:07 +08:00
2026-03-29 09:28:49 +08:00
// Data stores things only needed for the configuration of fastfetch
2023-10-23 13:39:07 +08:00
FFdata data = {
. structure = ffStrbufCreate (),
2026-01-15 15:03:38 +08:00
. structureDisabled = ffStrbufCreate (),
. genConfigPath = ffStrbufCreate (),
2023-10-23 13:39:07 +08:00
};
2022-09-22 22:02:58 +02:00
2023-11-10 15:05:09 +08:00
parseArguments ( & data , argc , argv , parseCommand );
2026-03-29 09:28:49 +08:00
if ( instance . state . dynamicInterval && data . resultDoc ) {
2025-11-05 10:00:31 +08:00
fprintf ( stderr , "Error: --dynamic-interval cannot be used with --json \n " );
exit ( 400 );
}
2026-03-29 09:28:49 +08:00
if ( ! data . configLoaded && ! getenv ( "NO_CONFIG" )) {
2026-01-15 15:03:38 +08:00
parseConfigFiles ( & data );
2026-03-29 09:28:49 +08:00
}
2023-11-10 15:05:09 +08:00
parseArguments ( & data , argc , argv , ( void * ) parseOption );
2023-10-23 13:39:07 +08:00
2026-03-29 09:28:49 +08:00
if ( __builtin_expect ( data . genConfigPath . length == 0 , true )) {
2023-10-25 10:18:07 +08:00
run ( & data );
2026-03-29 09:28:49 +08:00
} else {
2026-08-04 23:34:15 +08:00
// If we don't have a custom structure, use the default one
if ( data . structure . length == 0 ) {
ffStrbufSetS ( & data . structure , FASTFETCH_DATATEXT_STRUCTURE ); // Cannot use `ffStrbufSetStatic` here because we will modify the string
}
2026-08-04 10:41:26 +08:00
if ( data . genConfigInteractive && ! ffGenConfigInteractive ( & data )) {
2026-08-31 13:44:19 +08:00
// User canceled the interactive config generation
2026-08-04 10:41:26 +08:00
ffStrbufDestroy ( & data . structure );
ffStrbufDestroy ( & data . structureDisabled );
yyjson_doc_free ( data . configDoc );
ffStrbufDestroy ( & data . genConfigPath );
return 0 ;
}
2025-08-11 14:49:22 +08:00
writeConfigFile ( & data );
2026-03-29 09:28:49 +08:00
}
2022-09-22 22:02:58 +02:00
2022-09-22 21:28:20 +08:00
ffStrbufDestroy ( & data . structure );
2026-01-15 15:03:38 +08:00
ffStrbufDestroy ( & data . structureDisabled );
yyjson_doc_free ( data . configDoc );
yyjson_mut_doc_free ( data . resultDoc );
ffStrbufDestroy ( & data . genConfigPath );
2021-02-18 22:25:36 +01:00
}