Fastfetch: adds interactive --gen-config mode

removes `--gen-config-*` flags (merged into `--gen-config`)
This commit is contained in:
Carter Li
2026-08-04 10:41:26 +08:00
parent 2af56232a4
commit 6d795a3572
6 changed files with 1154 additions and 36 deletions
+1
View File
@@ -434,6 +434,7 @@ set(LIBFASTFETCH_SRC
src/common/impl/font.c
src/common/impl/format.c
src/common/impl/frequency.c
src/common/impl/genconfig.c
src/common/impl/init.c
src/common/impl/jsonconfig.c
src/common/impl/library.c
+2 -20
View File
@@ -107,26 +107,8 @@
},
{
"long": "gen-config",
"desc": "Generate a minimal config file at the specified path",
"remark": "Defaults to \"~/.config/fastfetch/config.jsonc\". Prints the generated config if <path> is \"-\"",
"arg": {
"type": "path",
"optional": true
}
},
{
"long": "gen-config-full",
"desc": "Generate a full config file with all optional settings at the specified path",
"remark": "Defaults to \"~/.config/fastfetch/config.jsonc\". Prints the generated config if <path> is \"-\"",
"arg": {
"type": "path",
"optional": true
}
},
{
"long": "gen-config-force",
"desc": "Generate a config file at the specified path, overwriting any existing file",
"remark": "Defaults to \"~/.config/fastfetch/config.jsonc\"",
"desc": "Interactively generate a config file at the specified path",
"remark": "Opens an interactive configuration UI when run in a terminal. Falls back to non-interactive generation when env-var `$NO_COLOR` is set or stdin/stdout is not a TTY. Defaults to \"~/.config/fastfetch/config.jsonc\". Prints the generated config if <path> is \"-\"",
"arg": {
"type": "path",
"optional": true
+1
View File
@@ -19,4 +19,5 @@ typedef struct FFdata {
FFstrbuf genConfigPath; // Path to generate configuration file
FFDataResultDocType docType; // Type of result document
bool configLoaded;
bool genConfigInteractive; // `--gen-config` entered the interactive CUI
} FFdata;
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include "common/ffdata.h"
// Runs the interactive config generation CUI.
//
// On success (the user saved the config), it sets `data->structure` to the user chosen
// module structure and `instance.config.logo.type` to the user chosen logo type, creates
// the result document and returns true. The caller is expected to call `writeConfigFile`.
// Returns false if the user quits without saving.
bool ffGenConfigInteractive(FFdata* data);
File diff suppressed because it is too large Load Diff
+47 -16
View File
@@ -3,6 +3,7 @@
#include "detection/version/version.h"
#include "logo/logo.h"
#include "common/commandoption.h"
#include "common/genconfig.h"
#include "common/init.h"
#include "common/io.h"
#include "common/jsonconfig.h"
@@ -15,6 +16,12 @@
#include <ctype.h>
#include <string.h>
#ifndef _WIN32
#include <unistd.h>
#else
#include <windows.h>
#endif
[[gnu::cold]]
static void printCommandFormatHelpJson(void) {
yyjson_mut_doc* doc = yyjson_mut_doc_new(nullptr);
@@ -432,12 +439,7 @@ static bool parseJsoncFile(FFdata* data, const char* path, yyjson_read_flag flg)
}
[[gnu::cold]]
static void generateConfigFile(FFdata* data, bool force, const char* filePath, bool fullConfig) {
if (data->resultDoc) {
fprintf(stderr, "Error: duplicated `--gen-config` or `--format json` flags found\n");
exit(477);
}
static void setupGenConfigPath(FFdata* data, const char* filePath) {
if (!filePath) {
if (instance.state.platform.configDirs.length == 0) {
fprintf(stderr, "Error: No config directory found to generate config file in. Use --gen-config <path> to specify a path\n");
@@ -451,13 +453,23 @@ static void generateConfigFile(FFdata* data, bool force, const char* filePath, b
} else {
ffStrbufSetS(&data->genConfigPath, filePath);
}
}
if (!force && ffPathExists(data->genConfigPath.chars, FF_PATHTYPE_ANY)) {
fprintf(stderr, "Error: file `%s` exists. Use `--gen-config%s-force` to overwrite\n", data->genConfigPath.chars, fullConfig ? "-full" : "");
[[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");
exit(477);
}
data->docType = fullConfig ? FF_RESULT_DOC_TYPE_CONFIG_FULL : FF_RESULT_DOC_TYPE_CONFIG;
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;
data->resultDoc = yyjson_mut_doc_new(nullptr);
}
@@ -586,6 +598,15 @@ static void enableJsonOutput(FFdata* data) {
yyjson_mut_doc_set_root(data->resultDoc, yyjson_mut_arr(data->resultDoc));
}
static void genConfigCommon(FFdata* data, const char* value) {
if (!getenv("NO_COLOR") && isatty(STDOUT_FILENO) && isatty(STDIN_FILENO)) {
data->genConfigInteractive = true;
setupGenConfigPath(data, value);
} else {
generateConfigFile(data, value);
}
}
static void parseCommand(FFdata* data, char* key, char* value) {
if (ffStrEqualsIgnCase(key, "-h") || ffStrEqualsIgnCase(key, "--help")) {
printCommandHelp(value);
@@ -647,13 +668,7 @@ static void parseCommand(FFdata* data, char* key, char* value) {
exit(0);
} else if (ffStrEqualsIgnCase(key, "--gen-config")) {
generateConfigFile(data, false, value, false);
} else if (ffStrEqualsIgnCase(key, "--gen-config-force")) {
generateConfigFile(data, true, value, false);
} else if (ffStrEqualsIgnCase(key, "--gen-config-full")) {
generateConfigFile(data, false, value, true);
} else if (ffStrEqualsIgnCase(key, "--gen-config-full-force")) {
generateConfigFile(data, true, value, true);
genConfigCommon(data, value);
} else if (ffStrEqualsIgnCase(key, "-c") || ffStrEqualsIgnCase(key, "--config")) {
optionParseConfigFile(data, key, value);
} else if (ffStrEqualsIgnCase(key, "-j") || ffStrEqualsIgnCase(key, "--json")) {
@@ -828,6 +843,14 @@ static void writeConfigFile(FFdata* data) {
ffOptionsGenerateLogoJsonConfig(data, &instance.config.logo);
ffOptionsGenerateDisplayJsonConfig(data, &instance.config.display);
ffOptionsGenerateGeneralJsonConfig(data, &instance.config.general);
} else if (data->genConfigInteractive) {
if (instance.config.logo.type == FF_LOGO_TYPE_NONE) {
yyjson_mut_obj_add_null(doc, root, "logo");
} else if (instance.config.logo.type == FF_LOGO_TYPE_SMALL) {
yyjson_mut_val* logo = yyjson_mut_obj(doc);
yyjson_mut_obj_add_str(doc, logo, "type", "small");
yyjson_mut_obj_add_val(doc, root, "logo", logo);
}
}
ffMigrateCommandOptionToJsonc(data);
@@ -877,6 +900,14 @@ int main(int argc, char** argv) {
if (__builtin_expect(data.genConfigPath.length == 0, true)) {
run(&data);
} else {
if (data.genConfigInteractive && !ffGenConfigInteractive(&data)) {
// User cancelled the interactive config generation
ffStrbufDestroy(&data.structure);
ffStrbufDestroy(&data.structureDisabled);
yyjson_doc_free(data.configDoc);
ffStrbufDestroy(&data.genConfigPath);
return 0;
}
writeConfigFile(&data);
}