commit 40f0e25f3224d8310f62856231c8ace9d47fa0bd Author: jacekpoz Date: Fri Feb 9 12:05:06 2024 +0100 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f4d8984 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2024 jacekpoz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..40e38a8 --- /dev/null +++ b/Makefile @@ -0,0 +1,43 @@ +CC = gcc +CFLAGS = -std=c99 -O3 -Wall -Wextra -Wpedantic -Wstrict-aliasing +CFLAGS += -Wfloat-equal -Wundef -Wshadow -Wpointer-arith -Wcast-align +CFLAGS += -Wstrict-prototypes -Wstrict-overflow=5 -Wwrite-strings +CFLAGS += -Wcast-qual -Wswitch-default -Wswitch-enum +CFLAGS += -Wconversion -Wunreachable-code +# for asprintf() and getline() +CFLAGS += -D_GNU_SOURCE +LDFLAGS = + +SRC = modfetch.c +OBJ = $(BIN)/$(SRC:.c=.o) +BIN = target +TEMPMOD = $(wildcard modules/*.c) +MOD = $(TEMPMOD:modules/%=%) +PROGRAM = modfetch + +.PHONY: all clean distclean + +all: dirs $(PROGRAM) distclean + +modules: dirs $(MOD:.c=.so) + +dirs: + mkdir -p ./$(BIN) + +run: all + $(BIN)/$(PROGRAM) + +$(PROGRAM): $(OBJ) + $(CC) -o $(BIN)/$(PROGRAM) $^ $(LDFLAGS) + +$(BIN)/%.o: %.c + $(CC) -o $@ -c $< $(CFLAGS) + +%.so: modules/%.c + $(CC) -o $(BIN)/$@ $^ -shared -fPIC $(CFLAGS) + +clean: distclean + rm -rf $(BIN) + +distclean: + rm -rf $(OBJ) diff --git a/README.md b/README.md new file mode 100644 index 0000000..c4c0e14 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# modfetch + +modular fetch + +## building + +`make` for the main binary + +`make modules` for all the default modules + +write your own using `mod.h` if you want to + +## config + +by default `$XDG_CURRENT_DESKTOP/modfetch.conf` + +format: +``` +/path/to/mod1.so + +/path/to/mod2.so { + var1 = dupa + var2 = 2137 +} +``` + +## TODO + +[ ] module manager (url to module source, compiles and puts the binary somewhere) +[ ] better config parsing +[ ] more text positioning options aside from printing text vertically + +## license + +mit diff --git a/mod.h b/mod.h new file mode 100644 index 0000000..77b20a1 --- /dev/null +++ b/mod.h @@ -0,0 +1,10 @@ +#include + +uint8_t version_major(void); +uint8_t version_minor(void); +uint8_t version_patch(void); + +const char *module_name(void); +const char *get(void); + +void init(char **config); diff --git a/modfetch.c b/modfetch.c new file mode 100644 index 0000000..6952fbb --- /dev/null +++ b/modfetch.c @@ -0,0 +1,258 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *PNAME = "modfetch"; + +static const uint8_t VERSION_MAJOR = 0; +static const uint8_t VERSION_MINOR = 1; +static const uint8_t VERSION_PATCH = 0; + +static const uint8_t MAX_MODULE_NAME_LENGTH = 128; +static const uint8_t MAX_MODULES = 128; +static const uint8_t MAX_MODULE_VARS = 128; + +static const char *version_str(void) { + char *ver; + if (asprintf(&ver, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH) < 0) { + return NULL; + } + return ver; +} + +typedef struct { + // path to the binary of this module + char *path; + // name=value strings for each value in config + char **config; +} Module; + +typedef struct { + size_t module_count; + Module *modules; +} Config; + +void parsing_error(size_t line) { + fprintf(stderr, "error: failed parsing config at line %zu", line); + exit(EXIT_FAILURE); +} + +char *remove_whitespaces(const char *str) { + char *tmp = malloc(strlen(str) * sizeof(char)); + size_t i = 0; + size_t j = 0; + while (str[i] != '\0') { + if (isspace(str[i])) { + ++i; + continue; + } + tmp[j] = str[i]; + ++i; + ++j; + } + tmp[j] = '\0'; + + char *ret = malloc(j * sizeof(char)); + strncpy(ret, tmp, j); + free(tmp); + + return ret; +} + +char *resolve_env_vars(const char *str) { + size_t len = strlen(str); + char *ret = malloc(4096 * sizeof(char)); + size_t ret_offset = 0; + bool in_env_var = false; + char *env_var = malloc(4096 * sizeof(char)); + size_t env_var_offset = 0; + + for (size_t i = 0; i < len; ++i) { + if (str[i] == '~') { + char *home = getenv("HOME"); + strcat(ret, home); + ret_offset += strlen(home); + continue; + } + + if (str[i] == '$') { + in_env_var = true; + continue; + } + + if (in_env_var && !(isalpha(str[i]) || isdigit(str[i]) || str[i] == '_')) { + in_env_var = false; + + char *env = getenv(env_var); + strcat(ret, env); + ret_offset += strlen(env); + free(env_var); + env_var = malloc(4096 * sizeof(char)); + env_var_offset = 0; + } + + if (in_env_var) { + env_var[env_var_offset] = str[i]; + ++env_var_offset; + continue; + } + + ret[ret_offset] = str[i]; + ++ret_offset; + } + + return ret; +} + +Config parse_config(FILE *config_file) { + size_t len = 0; + ssize_t read; + char *line = NULL; + size_t line_index = 0; + bool in_config = false; + + char *current_path = malloc(MAX_MODULE_NAME_LENGTH * sizeof(char)); + Module current_module = { + .path = malloc(4096 * sizeof(char)), + .config = malloc(MAX_MODULE_VARS * sizeof(char)), + }; + size_t config_index = 0; + + Config config = { + .modules = malloc(MAX_MODULES * sizeof(Module)), + .module_count = 0, + }; + size_t module_index = 0; + + while ((read = getline(&line, &len, config_file)) != -1) { + // if config is passed + if (line[read - 2u] == '{') { + if (in_config) { + fclose(config_file); + parsing_error(line_index); + } + in_config = true; + strncpy(current_path, line, (size_t)read - 2); + current_module.path = resolve_env_vars(remove_whitespaces(current_path)); + free(current_path); + current_path = malloc(MAX_MODULE_NAME_LENGTH); + continue; + } + + // end of config for current module + if (line[read - 2u] == '}') { + if (!in_config) { + fclose(config_file); + parsing_error(line_index); + } + in_config = false; + current_module.config[config_index] = NULL; + config.modules[module_index] = current_module; + ++module_index; + current_module.path = malloc(4096 * sizeof(char)); + current_module.config = malloc(MAX_MODULE_VARS * sizeof(char)); + continue; + } + + if (in_config) { + current_module.config[config_index] = remove_whitespaces(line); + ++config_index; + continue; + } + + current_module.path = resolve_env_vars(remove_whitespaces(line)); + free(current_module.config); + current_module.config = malloc(sizeof(void*)); + current_module.config[0] = NULL; + config.modules[module_index] = current_module; + ++module_index; + current_module.path = malloc(4096 * sizeof(char)); + current_module.config = malloc(MAX_MODULE_VARS * sizeof(char)); + } + + config.module_count = module_index; + + return config; +} + +int main(int argc, char *argv[]) { + + char *config_path; + + if (asprintf(&config_path, "%s/modfetch.conf", getenv("XDG_CONFIG_HOME")) < 0) { + fprintf(stderr, "error: failed formatting config path (this shouldn't happen)"); + exit(EXIT_FAILURE); + } + + for (size_t i = 1; i < (size_t)argc; ++i) { + if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--config") == 0) { + if (i == (size_t)argc - 1) { + fprintf(stderr, "error: no config path passed\n"); + exit(EXIT_FAILURE); + } + + ++i; + config_path = argv[i]; + continue; + } + + if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { + fprintf(stderr, "%s: modular fetch [%s]\n", PNAME, version_str()); + fprintf(stderr, "\n"); + fprintf(stderr, "OPTIONS\n"); + fprintf(stderr, "\t-h, --help\t\t\tdisplays this help text\n"); + fprintf(stderr, "\t-c, --config /path/to/config\tchanges config path from the default ($XDG_CONFIG_HOME/%s.conf)\n", PNAME); + exit(EXIT_SUCCESS); + } + } + + FILE *config_file = fopen(config_path, "r"); + + if (config_file == NULL) { + fprintf(stderr, "error: failed to open config at: %s\n", config_path); + exit(EXIT_FAILURE); + } + + Config config = parse_config(config_file); + + fclose(config_file); + + for (size_t i = 0; i < config.module_count; ++i) { + Module current_module = config.modules[i]; + void *handle = dlopen(current_module.path, RTLD_NOW); + if (handle == NULL) { + fprintf(stderr, "error: failed opening module %s\n", current_module.path); + fprintf(stderr, "%s\n", dlerror()); + exit(EXIT_FAILURE); + } + dlerror(); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + uint8_t (*version_major)(void) = dlsym(handle, "version_major"); + uint8_t (*version_minor)(void) = dlsym(handle, "version_minor"); + uint8_t (*version_patch)(void) = dlsym(handle, "version_patch"); + + const char *(*module_name)(void) = dlsym(handle, "module_name"); + const char *(*get)(void) = dlsym(handle, "get"); + void (*init)(char**) = dlsym(handle, "init"); +#pragma GCC diagnostic pop + + init(current_module.config); + + (void)module_name; + (void)version_major; + (void)version_minor; + (void)version_patch; + //printf("%s: %d.%d.%d\n", module_name(), version_major(), version_minor(), version_patch()); + printf("%s\n", get()); + } + + return 0; +} diff --git a/modules/desktop.c b/modules/desktop.c new file mode 100644 index 0000000..ca36d35 --- /dev/null +++ b/modules/desktop.c @@ -0,0 +1,23 @@ +#include "../mod.h" +#include +#include + +uint8_t version_major(void) { return 0; } +uint8_t version_minor(void) { return 1; } +uint8_t version_patch(void) { return 0; } +const char *module_name(void) { return "desktop"; } + +void init(char **config) { + (void)config; +} + +const char *get(void) { + char *ret; + + if (asprintf(&ret, "Desktop: %s", getenv("XDG_CURRENT_DESKTOP")) < 0) { + fprintf(stderr, "error: failed formatting (this shouldn't happen)"); + exit(EXIT_FAILURE); + } + + return ret; +} diff --git a/modules/os.c b/modules/os.c new file mode 100644 index 0000000..066cf5d --- /dev/null +++ b/modules/os.c @@ -0,0 +1,53 @@ +#include "../mod.h" +#include +#include +#include +#include + +uint8_t version_major(void) { return 0; } +uint8_t version_minor(void) { return 1; } +uint8_t version_patch(void) { return 0; } +const char *module_name(void) { return "os"; } + +void init(char **config) { + (void)config; +} + +const char *get(void) { + char *ret; + + FILE *os_release = fopen("/etc/os-release", "r"); + + if (os_release == NULL) { + fprintf(stderr, "error: failed opening /etc/os-release\n"); + exit(EXIT_FAILURE); + } + + size_t len = 0; + ssize_t read; + char *line = NULL; + + char *name = malloc(1024 * sizeof(char)); + const char *name_label = "PRETTY_NAME="; + const size_t name_label_len = strlen(name_label); + + while ((read = getline(&line, &len, os_release)) != -1) { + if (strncmp(line, name_label, name_label_len) != 0) { + continue; + } + // + 1 because of " + line += name_label_len + 1; + const size_t new_len = strlen(line); + // the ending " + line[new_len - 2] = '\0'; + name = line; + break; + } + + if (asprintf(&ret, "OS: %s", name) < 0) { + fprintf(stderr, "error: failed formatting string (this shouldn't happen)"); + exit(EXIT_FAILURE); + } + + return ret; +}