Files
fastfetch/src/fastfetch.c
T

576 lines
19 KiB
C
Raw Normal View History

2021-02-18 22:25:36 +01:00
#include "fastfetch.h"
2021-02-22 20:36:25 +01:00
#include "fastfetch_config.h"
#include "util/FFvaluestore.h"
2021-02-18 22:25:36 +01:00
2021-02-27 20:31:54 +01:00
#include <malloc.h>
2021-02-27 20:50:13 +01:00
#include <sys/types.h>
#include <unistd.h>
2021-02-27 20:31:54 +01:00
#include <sys/stat.h>
2021-02-27 20:50:13 +01:00
#include <fcntl.h>
2021-02-22 16:52:51 +01:00
2021-03-11 17:21:18 +01:00
#define FASTFETCH_DEFAULT_STRUCTURE "Title:Seperator:OS:Host:Kernel:Uptime:Packages:Shell:Resolution:DE:WM:Theme:Icons:Font:Terminal:TerminalFont:CPU:GPU:Memory:Disk:Battery:Locale:Break:Colors"
#define FASTFETCH_DEFAULT_CONFIG \
2021-03-03 21:42:40 +01:00
"## Fastfetch configuration\n" \
"## Put arguments here to make them permanently.\n" \
"## Direct arguments will overwrite the corresponding ones in this files\n" \
"## Each line is whitespace trimmed on beginn and end.\n" \
"## Empty lines or lines starting with # are ignored.\n" \
"## There are more arguments possible than listed here, take a look at fastfetch --help!\n" \
2021-03-05 21:49:13 +01:00
"## This version of the file was shipped with "FASTFETCH_PROJECT_VERSION".\n" \
"## Use fastfetch --print-default-config > ~/.config/fastfetch/config.conf to generate a new one with current defaults.\n" \
2021-03-03 21:42:40 +01:00
"\n" \
"## General options:\n" \
"# --structure "FASTFETCH_DEFAULT_STRUCTURE"\n" \
2021-03-05 23:00:28 +01:00
"# --spacer 4\n" \
"# --seperator \": \"\n" \
2021-03-03 21:42:40 +01:00
"# --offsetx 0\n" \
"# --recache false\n" \
"# --show-errors false\n" \
"\n" \
"## Logo options:\n" \
"# --color-logo true\n" \
"\n" \
2021-03-06 11:28:38 +01:00
"## OS options:\n" \
"# --os-architecture true\n" \
"\n" \
2021-03-07 11:58:24 +01:00
"## Host options:\n" \
"# --host-version true\n" \
"\n" \
2021-03-07 20:35:17 +01:00
"## Kernel options:\n" \
"# --kernel-release true\n" \
"# --kernel-version false\n" \
"\n" \
2021-03-05 23:26:05 +01:00
"## Shell options:\n" \
"# --shell-path false\n" \
"\n" \
2021-03-03 21:42:40 +01:00
"## Battery options:\n" \
"# --battery-manufacturer true\n" \
"# --battery-model true\n" \
"# --battery-technology true\n" \
"# --battery-capacity true\n" \
"# --battery-status true\n"
//Things only needed by fastfetch
typedef struct FFdata
{
FFvaluestore valuestore;
char structure[2048];
char logoName[32];
} FFdata;
2021-02-19 16:23:41 +01:00
static void printHelp()
{
puts(
2021-03-03 21:03:12 +01:00
"Usage: fastfetch <options>\n"
2021-02-20 19:32:57 +01:00
"\n"
2021-03-03 21:03:12 +01:00
"Informative options:\n"
" -h, --help: shows this message and exits\n"
" -h <command>, --help <command>: shows help for a specific command and exits\n"
" -v --version: prints the version of fastfetch and exits\n"
" --list-logos: list available logos and exits\n"
" --print-logos: shows available logos and exits\n"
" --print-default-config: prints the default config and exits\n"
"\n"
"General options:\n"
2021-03-05 23:00:28 +01:00
" --structure <structure>: sets the structure of the fetch. Must be a colon seperated list of keys\n"
" --set <key=value>: hard set the value of an key\n"
" -c <color>, --color <color>: sets the color of the keys. Must be a linux console color code (+)\n"
" --spacer <width>: sets the distance between logo and text\n"
" -s <str>, --seperator <str>: sets the seperator between key and value. Default is a colon with a space\n"
" -x <offset>, --offsetx <offset>: sets the x offset. Can be negative to cut the logo, but no more than logo width.\n"
" --show-errors <?value>: print occuring errors\n"
" -r <?value> --recache <?value>: if set to true, no cached values will be used\n"
"\n"
2021-03-03 21:03:12 +01:00
"Logo options:\n"
2021-03-05 23:00:28 +01:00
" -l <name>, --logo <name>: sets the shown logo. Also changes the main color accordingly\n"
" --color-logo <?value>: if set to false, the logo will be black / white\n"
2021-03-03 21:03:12 +01:00
"\n"
2021-03-06 11:28:38 +01:00
"OS options:\n"
" --os-architecture <?value>: Show the architecture of the os\n"
"\n"
2021-03-07 11:58:24 +01:00
"Host options:\n"
" --host-version <?value>: Show the version of the host platform, if possible. Most likely, this will be the BIOS version\n"
"\n"
2021-03-07 20:35:17 +01:00
"Kernel options:\n"
" --kernel-release <?value>: Shows the release of the kernel\n"
" --kernel-version <?value>: Shows the build version of the kernel\n"
"\n"
2021-03-05 23:26:05 +01:00
"Shell options:\n"
" --shell-path <?value>: Show the full path of the shell\n"
"\n"
2021-03-03 21:03:12 +01:00
"Battery options:\n"
" --battery-manufacturer <?value>: Show the manufacturer of the battery, if possible\n"
" --battery-model <?value>: Show the model of the battery, if possible\n"
" --battery-technology <?value>: Show the technology of the battery, if possible\n"
" --battery-capacity <?value>: Show the capacity of the battery, if possible\n"
" --battery-status <?value>: Show the status of the battery, if possible\n"
2021-03-05 21:38:35 +01:00
" --battery-format <format>: Provide the printf format string for battery output (+)\n"
2021-03-03 21:03:12 +01:00
"\n"
"If an value starts with an ?, it is optional. \"true\" will be used if not set.\n"
2021-03-03 21:03:12 +01:00
"An (+) at the end indicates that more help can be printed with --help <option>\n"
"All options can be make permanent in $XDG_CONFIG_HOME/fastfetch/config.conf"
2021-02-19 16:23:41 +01:00
);
}
2021-03-04 20:49:12 +01:00
static inline void printCommandHelpColor()
2021-02-20 19:32:57 +01:00
{
puts(
"usage: fastfetch --color <color>\n"
"\n"
"<color> must be a color encoding for linux terminals. It is inserted between \"ESC[\" and \"m\".\n"
"Infos about them can be found here: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors.\n"
"Examples:\n"
" \"--color 35\": sets the color to pink\n"
" \"--color 4;92\": sets the color to bright Green with underline\n"
" \"--color 5;104\": blinking text on a blue background\n"
2021-02-20 20:00:06 +01:00
"If no color is set, the main color of the logo will be used.\n"
2021-02-20 19:32:57 +01:00
);
}
2021-03-04 20:49:12 +01:00
static inline void printCommandHelpBatteryFormat()
{
puts(
"usage fastfetch --battery-format <format>\n"
"\n"
"<format> is a string of maximum length 32, which is passed to printf as the format string.\n"
"The arguments passed to printf are 5 strings in following order:\n"
"manufacturer, model, technology, capacity, status\n"
"If an value was disabled via battery-* argument, or could not be determined, it will be an zero length string.\n"
"The default value is something like \"%s %s (%s) [%s; %s]\"."
);
}
2021-02-20 19:32:57 +01:00
static void printCommandHelp(const char* command)
{
2021-03-04 20:49:12 +01:00
if(strcasecmp(command, "c") == 0 || strcasecmp(command, "color") == 0)
2021-02-20 19:32:57 +01:00
printCommandHelpColor();
2021-03-04 20:49:12 +01:00
else if(strcasecmp(command, "battery-format") == 0)
printCommandHelpBatteryFormat();
2021-02-20 19:32:57 +01:00
else
printf("No specific help for command %s provided\n", command);
}
static bool parseBoolean(const char* str)
2021-02-27 20:31:54 +01:00
{
2021-03-07 20:35:17 +01:00
if(str == NULL)
return true;
return (
strcasecmp(str, "true") == 0 ||
strcasecmp(str, "yes") == 0 ||
strcasecmp(str, "1") == 0
2021-03-07 20:35:17 +01:00
);
2021-02-27 20:31:54 +01:00
}
static void parseStructureCommand(FFinstance* instance, FFdata* data, const char* line)
2021-02-27 20:31:54 +01:00
{
const char* setValue = ffValuestoreGet(&data->valuestore, line);
if(setValue != NULL)
{
ffPrintCustom(instance, line, setValue);
return;
}
2021-02-27 20:31:54 +01:00
if(strcasecmp(line, "break") == 0)
ffPrintBreak(instance);
else if(strcasecmp(line, "title") == 0)
ffPrintTitle(instance);
else if(strcasecmp(line, "seperator") == 0)
ffPrintSeperator(instance);
else if(strcasecmp(line, "os") == 0)
ffPrintOS(instance);
else if(strcasecmp(line, "host") == 0)
ffPrintHost(instance);
else if(strcasecmp(line, "kernel") == 0)
ffPrintKernel(instance);
else if(strcasecmp(line, "uptime") == 0)
ffPrintUptime(instance);
else if(strcasecmp(line, "packages") == 0)
ffPrintPackages(instance);
else if(strcasecmp(line, "shell") == 0)
ffPrintShell(instance);
else if(strcasecmp(line, "resolution") == 0)
ffPrintResolution(instance);
else if(strcasecmp(line, "desktopenvironment") == 0 || strcasecmp(line, "de") == 0)
ffPrintDesktopEnvironment(instance);
2021-03-05 21:38:35 +01:00
else if(strcasecmp(line, "windowmanager") == 0 || strcasecmp(line, "wm") == 0)
ffPrintWM(instance);
2021-02-27 20:31:54 +01:00
else if(strcasecmp(line, "theme") == 0)
ffPrintTheme(instance);
else if(strcasecmp(line, "icons") == 0)
ffPrintIcons(instance);
else if(strcasecmp(line, "font") == 0)
ffPrintFont(instance);
else if(strcasecmp(line, "terminal") == 0)
ffPrintTerminal(instance);
2021-03-11 17:21:18 +01:00
else if(strcasecmp(line, "terminalfont") == 0)
ffPrintTerminalFont(instance);
2021-02-27 20:31:54 +01:00
else if(strcasecmp(line, "cpu") == 0)
ffPrintCPU(instance);
else if(strcasecmp(line, "gpu") == 0)
ffPrintGPU(instance);
else if(strcasecmp(line, "memory") == 0)
ffPrintMemory(instance);
else if(strcasecmp(line, "disk") == 0)
ffPrintDisk(instance);
else if(strcasecmp(line, "battery") == 0)
ffPrintBattery(instance);
else if(strcasecmp(line, "locale") == 0)
ffPrintLocale(instance);
else if(strcasecmp(line, "colors") == 0)
ffPrintColors(instance);
else
ffPrintError(instance, line, "<no implementaion provided>");
}
static void parseOption(FFinstance* instance, FFdata* data, const char* key, const char* value)
2021-02-27 20:31:54 +01:00
{
if(strcasecmp(key, "-h") == 0 || strcasecmp(key, "--help") == 0)
{
if(value == NULL)
printHelp();
else
printCommandHelp(value);
exit(0);
}
else if(strcasecmp(key, "-v") == 0 || strcasecmp(key, "--version") == 0)
{
2021-03-05 21:49:13 +01:00
puts(FASTFETCH_PROJECT_NAME" "FASTFETCH_PROJECT_VERSION);
exit(0);
}
else if(strcasecmp(key, "--list-logos") == 0)
{
ffListLogos();
exit(0);
}
else if(strcasecmp(key, "--print-logos") == 0)
{
ffPrintLogos(instance->config.colorLogo);
exit(0);
}
else if(strcasecmp(key, "--print-default-config") == 0)
{
puts(FASTFETCH_DEFAULT_CONFIG);
exit(0);
}
else if(strcasecmp(key, "-l") == 0 || strcasecmp(key, "--logo") == 0)
{
if(value == NULL)
{
printf("Error: usage: %s <logo>\n", key);
2021-03-05 23:00:28 +01:00
exit(401);
}
2021-02-27 20:31:54 +01:00
strcpy(data->logoName, value);
}
else if(strcasecmp(key, "-c") == 0 || strcasecmp(key, "--color") == 0)
{
if(value == NULL)
{
printf("Error: usage: %s <color>\n", key);
2021-03-05 23:00:28 +01:00
exit(402);
}
size_t len = strlen(value);
if(len > 28)
{
printf("Error: max color string length is 28, %zu given\n", len);
2021-03-05 23:00:28 +01:00
exit(403);
}
sprintf(instance->config.color, "\033[%sm", value);
}
2021-03-05 23:00:28 +01:00
else if(strcasecmp(key, "--spacer") == 0)
{
if(value == NULL)
{
printf("Error: usage: %s <width>\n", key);
2021-03-05 23:00:28 +01:00
exit(404);
}
2021-03-05 23:00:28 +01:00
if(sscanf(value, "%hd", &instance->config.logo_spacer) != 1)
{
printf("Error: couldn't parse %s to uint16_t\n", value);
2021-03-05 23:00:28 +01:00
exit(405);
}
}
else if(strcasecmp(key, "-s") == 0 || strcasecmp(key, "--seperator") == 0)
{
if(value == NULL)
{
printf("Error: usage: %s <seperator>\n", key);
exit(406);
}
size_t len = strlen(value);
if(len > 15)
{
printf("Error: max seperator length is 15, %zu given\n", len);
exit(407);
}
2021-03-05 23:00:28 +01:00
strcpy(instance->config.seperator, value);
}
else if(strcasecmp(key, "-x") == 0 || strcasecmp(key, "--offsetx") == 0)
{
if(value == NULL)
{
printf("Error: usage: %s <offset>\n", key);
2021-03-05 23:00:28 +01:00
exit(408);
}
if(sscanf(value, "%hi", &instance->config.offsetx) != 1)
{
printf("Error: couldn't parse %s to int16_t\n", value);
2021-03-05 23:00:28 +01:00
exit(409);
}
}
else if(strcasecmp(key, "--structure") == 0)
{
if(value == NULL)
{
printf("Error: usage: %s <structure>\n", key);
2021-03-05 23:00:28 +01:00
exit(410);
}
strcpy(data->structure, value);
}
else if(strcasecmp(key, "--set") == 0)
{
if(value == NULL)
{
printf("Error: usage: %s <key=value>\n", key);
2021-03-05 23:00:28 +01:00
exit(411);
}
char* seperator = strchr(value, '=');
2021-03-05 23:00:28 +01:00
if(seperator == NULL)
{
printf("Error: usage: %s <key=value>, '=' missing\n", key);
exit(412);
}
*seperator = '\0';
ffValuestoreSet(&data->valuestore, value, seperator + 1);
}
else if(strcasecmp(key, "-r") == 0 || strcasecmp(key, "--recache") == 0)
{
2021-03-06 11:28:38 +01:00
//Set cacheSave as well, beacuse the user expects the values to be cached when expliciting using --recache
2021-03-07 20:35:17 +01:00
instance->config.recache = parseBoolean(value);
instance->config.cacheSave = instance->config.recache;
2021-03-06 11:28:38 +01:00
}
2021-03-07 20:35:17 +01:00
else if(strcasecmp(key, "--show-errors") == 0)
instance->config.showErrors = parseBoolean(value);
else if(strcasecmp(key, "--color-logo") == 0)
instance->config.colorLogo = parseBoolean(value);
2021-03-06 11:28:38 +01:00
else if(strcasecmp(key, "--os-architecture") == 0)
2021-03-07 20:35:17 +01:00
instance->config.osShowArchitecture = parseBoolean(value);
2021-03-07 11:58:24 +01:00
else if(strcasecmp(key, "--host-version") == 0)
2021-03-07 20:35:17 +01:00
instance->config.hostShowVersion = parseBoolean(value);
else if(strcasecmp(key, "--kernel-release") == 0)
instance->config.kernelShowRelease = parseBoolean(value);
else if(strcasecmp(key, "--kernel-version") == 0)
instance->config.kernelShowVersion = parseBoolean(value);
2021-03-05 23:26:05 +01:00
else if(strcasecmp(key, "--shell-path") == 0)
2021-03-07 20:35:17 +01:00
instance->config.shellShowPath = parseBoolean(value);
2021-03-03 21:03:12 +01:00
else if(strcasecmp(key, "--battery-manufacturer") == 0)
2021-03-07 20:35:17 +01:00
instance->config.batteryShowManufacturer = parseBoolean(value);
2021-03-03 21:03:12 +01:00
else if(strcasecmp(key, "--battery-model") == 0)
2021-03-07 20:35:17 +01:00
instance->config.batteryShowModel = parseBoolean(value);
2021-03-03 21:03:12 +01:00
else if(strcasecmp(key, "--battery-technology") == 0)
2021-03-07 20:35:17 +01:00
instance->config.batteryShowTechnology = parseBoolean(value);
2021-03-03 21:03:12 +01:00
else if(strcasecmp(key, "--battery-capacity") == 0)
2021-03-07 20:35:17 +01:00
instance->config.batteryShowCapacity = parseBoolean(value);
2021-03-03 21:03:12 +01:00
else if(strcasecmp(key, "--battery-status") == 0)
2021-03-07 20:35:17 +01:00
instance->config.batteryShowStatus = parseBoolean(value);
2021-03-04 20:49:12 +01:00
else if(strcasecmp(key, "--battery-format") == 0)
{
if(value == NULL)
{
printf("Error: usage: %s <format>\n", key);
2021-03-05 23:00:28 +01:00
exit(413);
2021-03-04 20:49:12 +01:00
}
size_t len = strlen(value);
if(len > 32)
{
printf("max battery format string length is 32, %zu given\n", len);
2021-03-05 23:00:28 +01:00
exit(414);
2021-03-04 20:49:12 +01:00
}
strcpy(instance->config.batteryFormat, value);
}
else
{
printf("Error: unknown option: %s\n", key);
2021-03-05 23:00:28 +01:00
exit(400);
}
}
static void parseConfigFile(FFinstance* instance, FFdata* data)
{
char fileName[256];
const char* xdgConfig = getenv("XDG_CONFIG_HOME");
if(xdgConfig == NULL)
{
strcpy(fileName, instance->state.passwd->pw_dir);
strcat(fileName, "/.config");
}
else
{
strcpy(fileName, xdgConfig);
}
mkdir(fileName, S_IRWXU | S_IXGRP | S_IRGRP | S_IXOTH | S_IROTH); //I hope everybody has a config folder but whow knews
2021-02-27 20:31:54 +01:00
strcat(fileName, "/fastfetch/");
mkdir(fileName, S_IRWXU | S_IRGRP | S_IROTH);
strcat(fileName, "config.conf");
if(access(fileName, F_OK) != 0)
{
FILE* file = fopen(fileName, "w");
fputs(FASTFETCH_DEFAULT_CONFIG, file);
fclose(file);
return;
}
2021-02-27 20:31:54 +01:00
FILE* file = fopen(fileName, "r");
2021-02-27 20:31:54 +01:00
2021-03-05 23:00:28 +01:00
char* lineStart = NULL;
size_t len = 0;
2021-02-27 20:31:54 +01:00
ssize_t read;
2021-03-05 23:00:28 +01:00
while ((read = getline(&lineStart, &len, file)) != -1) {
//We need to copy lineStart because we modify this value, but need the original for free
char* line = lineStart;
2021-02-27 20:31:54 +01:00
if(line[read - 1] == '\n')
line[read - 1] = '\0';
else
line[read] = '\0';
2021-02-27 21:04:50 +01:00
2021-02-28 17:10:12 +01:00
ffTrimTrailingWhitespace(line);
2021-03-03 21:42:40 +01:00
//This trims leading whitespace
2021-03-05 23:00:28 +01:00
while(*line == ' ')
2021-03-03 21:42:40 +01:00
++line;
if(line[0] == '\0' || line[0] == '#')
continue;
2021-03-05 23:00:28 +01:00
char* valueStart = strchr(line, ' ');
if(valueStart == NULL)
{
parseOption(instance, data, line, NULL);
}
else
{
2021-03-05 23:00:28 +01:00
//Seperate key and value by simply replacing the first space with a \0
*valueStart = '\0';
++valueStart;
//Trim whitespace at beginn of value
while(*valueStart == ' ')
++valueStart;
2021-03-05 23:26:05 +01:00
//If we want whitespace in values, we need to quote it. This is done to keep consistency with shell.
2021-03-05 23:00:28 +01:00
if(*valueStart == '"')
{
char* last = valueStart + strlen(valueStart) - 1;
if(*last == '"')
{
++valueStart;
*last = '\0';
}
}
parseOption(instance, data, line, valueStart);
}
2021-02-27 20:31:54 +01:00
}
2021-03-05 23:00:28 +01:00
if(lineStart != NULL)
free(lineStart);
fclose(file);
}
static void parseArguments(FFinstance* instance, FFdata* data, int argc, const char** argv)
{
2021-03-06 11:28:38 +01:00
//This is generally a good idea, because cached values most likely contain values generated with other arguments
//Hovwever we dont do this with arguments in the config file, because they are more likely to stay the same
//If caching is _really_ wanted (e.g. a call in .bashrc with arguments), one can still set --recache false
if(argc > 1)
{
instance->config.recache = true;
instance->config.cacheSave = false;
}
for(int i = 1; i < argc; i++)
{
if(i == argc - 1 || argv[i + 1][0] == '-')
{
parseOption(instance, data, argv[i], NULL);
}
else
{
parseOption(instance, data, argv[i], argv[i + 1]);
++i;
}
}
2021-02-27 20:31:54 +01:00
}
static void applyData(FFinstance* instance, FFdata* data)
{
//We must do this after parsing all options because of color options
if(data->logoName[0] == '\0')
ffLoadLogo(&instance->config);
else
ffLoadLogoSet(&instance->config, data->logoName);
//This must be done after loading the logo
if(instance->config.color[0] == '\0')
strcpy(instance->config.color, instance->config.logo.color);
}
static void run(FFinstance* instance, FFdata* data)
{
if(data->structure[0] == '\0')
strcpy(data->structure, FASTFETCH_DEFAULT_STRUCTURE);
char* remaining = data->structure;
char* colon = NULL;
while(true)
{
colon = strchr(remaining, ':');
if(colon != NULL)
*colon = '\0';
parseStructureCommand(instance, data, remaining);
if(colon != NULL)
remaining = colon + 1;
else
break;
}
}
int main(int argc, const char** argv)
2021-02-22 13:45:14 +01:00
{
2021-02-27 18:30:05 +01:00
FFinstance instance;
ffInitState(&instance.state);
2021-03-03 21:03:12 +01:00
ffDefaultConfig(&instance.config);
FFdata data;
ffValuestoreInit(&data.valuestore);
data.structure[0] = '\0'; //We use this in run to detect if a structure was set
2021-03-05 23:00:28 +01:00
data.logoName[0] = '\0'; //We use this in applyData to detect if a logo was set
parseConfigFile(&instance, &data);
parseArguments(&instance, &data, argc, argv);
2021-03-05 23:26:05 +01:00
applyData(&instance, &data); //Here we do things that need to be done after parsing all options
run(&instance, &data);
ffValuestoreDelete(&data.valuestore);
2021-02-18 22:25:36 +01:00
}