aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPolesznyák Márk <contact@pml68.dev>2025-09-23 17:05:25 +0200
committerPolesznyák Márk <contact@pml68.dev>2025-09-23 17:05:25 +0200
commitb089cc6ed9ae82bf7d94d2b6f02aa01e21d43793 (patch)
tree216156843f171edd7ba21d55ead33dfd08733966
parentfeat(dmenu): add inlinePrompt patch (diff)
downloadsuckless-setup-b089cc6ed9ae82bf7d94d2b6f02aa01e21d43793.tar.gz
feat: switch from neofetch to Cryobs/fetcha
-rw-r--r--README.md1
-rw-r--r--fetcha/.gitignore6
-rw-r--r--fetcha/Makefile56
-rw-r--r--fetcha/README.md27
-rw-r--r--fetcha/config.def.h74
-rw-r--r--fetcha/fetcha.153
-rw-r--r--fetcha/fetcha.c522
-rw-r--r--fetcha/license.txt24
-rw-r--r--fetcha/modules.c536
-rw-r--r--fetcha/modules.h27
-rwxr-xr-xinstall.sh19
-rw-r--r--neofetch/config.conf22
12 files changed, 1336 insertions, 31 deletions
diff --git a/README.md b/README.md
index ea82ace..5818b66 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,6 @@ Replace config files
sudo cp picom.conf /etc/xdg/picom.conf
# optional
-cp neofetch -r ~/.config/
cp vis -r ~/.config/ # for https://github.com/dpayne/cli-visualizer
git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm
diff --git a/fetcha/.gitignore b/fetcha/.gitignore
new file mode 100644
index 0000000..b7d6635
--- /dev/null
+++ b/fetcha/.gitignore
@@ -0,0 +1,6 @@
+*.o
+*.out
+*.d
+
+bin/*
+config.h
diff --git a/fetcha/Makefile b/fetcha/Makefile
new file mode 100644
index 0000000..9af83ef
--- /dev/null
+++ b/fetcha/Makefile
@@ -0,0 +1,56 @@
+# Makefile for fetcha
+
+
+VERSION = 1.0.0
+
+CC = cc
+CCFLAGS = -Wall -Wextra -O2 -MMD
+LDLIBS = -lX11
+SRCS = fetcha.c modules.c
+OBJDIR = bin/obj
+OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
+DEPS = $(OBJS:.o=.d)
+BIN = bin/fetcha
+
+PREFIX = /usr/local
+DESTDIR =
+
+MANPREFIX = ${PREFIX}/share/man
+
+.PHONY: all clean install uninstall
+
+config.h:
+ cp config.def.h config.h
+
+all: $(BIN)
+
+$(BIN): $(OBJS)
+ @mkdir -p $(dir $@)
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)
+
+$(OBJDIR)/%.o: %.c
+ @mkdir -p $(OBJDIR)
+ $(CC) $(CFLAGS) -c $< -o $@
+
+-include $(DEPS)
+
+clean:
+ rm -rf $(OBJDIR) $(BIN)
+
+
+install: all
+ @echo installing executable file to ${DESTDIR}${PREFIX}/bin
+ @mkdir -p "$(DESTDIR)$(PREFIX)/bin"
+ @cp -f "$(BIN)" "$(DESTDIR)$(PREFIX)/bin/fetcha"
+ @chmod 755 "$(DESTDIR)$(PREFIX)/bin/fetcha"
+ @echo installing manual page to ${DESTDIR}${MANPREFIX}
+ @mkdir -p ${DESTDIR}${MANPREFIX}/man1
+ @sed "s/VERSION/${VERSION}/g" < fetcha.1 > ${DESTDIR}${MANPREFIX}/man1/fetcha.1
+ @chmod 644 ${DESTDIR}${MANPREFIX}/man1/fetcha.1
+
+uninstall:
+ @echo uninstalling executable file from ${DESTDIR}${PREFIX}/bin
+ @rm -f "$(DESTDIR)$(PREFIX)/bin/fetcha"
+
+ @echo uninstalling manual page from ${DESTDIR}${MANPREFIX}/man1
+ @rm -f "${DESTDIR}${MANPREFIX}/man1/fetcha.1"
diff --git a/fetcha/README.md b/fetcha/README.md
new file mode 100644
index 0000000..9ce2276
--- /dev/null
+++ b/fetcha/README.md
@@ -0,0 +1,27 @@
+# fetcha - suckless-like system info fetch
+fetcha is an fast, small and easy configurated system info fetch.
+
+![fetcha image](docs/img/fetcha.jpg)
+## Requirements
+To insatll fetcha you need `git`.
+## Installation
+1. clone the repository.
+```
+git clone https://github.com/Cryobs/fetcha
+```
+2. go to `fetcha` directory
+```
+cd fetcha
+```
+3. build fetcha (if necessary as root)
+```
+make clean install
+```
+## Running fetcha
+```
+fetcha
+```
+If you want fetcha to open together with your terminal, add the line `fetcha` to the end of `~/<your-shell>rc` (for example `.bashrc`, `.zshrc`).
+## Configuration
+The configuration is similar to suckless-type programs, i.e., copy the `config.def.h` file to `config.h`, and after customizing `config.h`, you need to recompile the source code.
+(How to configure see the man pages ( fetcha-config(5) ))
diff --git a/fetcha/config.def.h b/fetcha/config.def.h
new file mode 100644
index 0000000..99c43a5
--- /dev/null
+++ b/fetcha/config.def.h
@@ -0,0 +1,74 @@
+#include "modules.h"
+#include <stdio.h>
+
+
+static const int ascii_pad = 3; /* padding ascii/info */
+static const int info_align = 0; /* align info by separator */
+static const int header_show = 1; /* if 0 doot print header */
+static const int color_palette_show = 1;
+static const char *info_sep = ": ";
+static const int numerate_same = 1;
+
+/*
+ * colors ANSI
+ * NAME Normal Light
+ * Black : 30 (90)
+ * Red : 31 (91)
+ * Green : 32 (92)
+ * Yellow: 33 (93)
+ * Blue : 34 (94)
+ * Purple: 35 (95)
+ * Aqua : 36 (96)
+ * White : 37 (97)
+ *
+ * colorpallete:
+ * [0-4] - secondary colors
+ * [5-9] - text colors:
+ * 5 - info text
+ * 6 - info separator
+ * 7 - header separator
+ * 8 - boundary
+ * 9 - ?
+ */
+
+static const int colors[10] = {34, 34, 35, 33, 34, 97, 34, 90, 34, 34};
+
+/*
+ * character header/info
+ * if 1 char: boundary len == header len
+ * if 2 char: boundary len == 2 * header len
+ */
+static const char *boundary_char = "";
+
+/*
+ * separator for header info
+ */
+static const char *header_sep = "@";
+
+/*
+ * information
+ * Label, func
+ */
+static info_item config_items[] = {
+ { "Distro", get_os },
+ { "Kernel", get_kernel },
+ { "Packages", get_packages },
+ { "WM", get_wm },
+ { "Terminal", get_terminal },
+ { "Memory", get_memory },
+
+};
+
+const size_t config_items_len = sizeof config_items / sizeof config_items[0];
+
+
+static const char *ascii_art =
+"$2 /\\\n"
+" / \\\n"
+" /\\ \\\n"
+"$1 / \\\n"
+" / ,, \\\n"
+" / | | -\\\n"
+"/_-'' ''-_\\\n"
+;
+
diff --git a/fetcha/fetcha.1 b/fetcha/fetcha.1
new file mode 100644
index 0000000..3392cba
--- /dev/null
+++ b/fetcha/fetcha.1
@@ -0,0 +1,53 @@
+.TH FETCHA 1 fetcha\-VERSION
+.
+.SH NAME
+fetcha \- neofetch-like program, but faster and lighter
+.
+.SH SYNOPSIS
+.B fetcha
+.
+.SH DESCRIPTION
+Fetcha is a CLI system information program written in C
+following the suckless philosophy.
+It displays an ASCII image along with minimal system information.
+.
+.SH FILES
+.TP
+.I config.def.h
+Default configuration file (do not change it!).
+.TP
+.I config.h
+Configuration file, requires reconmpilation after modification.
+.TP
+.I docs/
+.br
+Documentation directory.
+.TP
+.I fetcha.c
+Main code file. Here implement most logic of the program.
+.TP
+.I modules.c
+Modules code file. Here implement basic modules.
+You can write your own (see \fBfetcha-modules\fR(5)
+.TP
+.I modules.h
+Modules header file. Here defined all modules.
+.TP
+.I license.txt
+License file.
+.TP
+.I Makefile
+Build file used to compile and install fetcha.
+Supports targets for building, cleaning, and instalation.
+.
+.SH CUSTOMIZATION
+Fetcha can be customized by creating a custom \fIconfig.h\fR and recompiling
+the source code.
+You can also write your own modules in \fImodules.c\fR and define them
+in \fImodules.h\fR.
+.PP
+NOTE: See \fBfetcha-modules\fR(5) for a module creation tutorial
+and \fBfetcha-config\fR(5) for a configuration tutorial
+.SH SEE ALSO
+\fBfetcha-modules\fR(5), \fBfetcha-config\fR(5)
+
diff --git a/fetcha/fetcha.c b/fetcha/fetcha.c
new file mode 100644
index 0000000..12b80ce
--- /dev/null
+++ b/fetcha/fetcha.c
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2025 cryobs
+ *
+ * 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.
+ *
+ * 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 <errno.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <sys/utsname.h>
+
+#include "modules.h"
+#include "config.h"
+
+#define COLORS 10
+
+
+
+struct ascii
+{
+ char *art;
+ int width;
+ int height;
+};
+
+void
+free_ascii(struct ascii *s)
+{
+ if (!s) return;
+ free(s->art);
+ s->art = NULL;
+}
+
+/* header */
+typedef struct
+{
+ char *name;
+ char *sep; /* separator name[]hostname */
+ char hostname[256];
+
+} header;
+
+
+typedef struct
+{
+ char *label;
+ char *value;
+
+} rendered_info;
+
+typedef struct
+{
+ rendered_info *entries;
+ size_t count;
+
+} info_list;
+
+void
+free_info_list(info_list *infos) {
+ if (!infos) return;
+
+ for (size_t i = 0; i < infos->count; i++) {
+ free(infos->entries[i].label);
+ free(infos->entries[i].value);
+ }
+
+ free(infos->entries);
+ infos->count = 0;
+}
+
+/*
+ * color to ansi code
+ * function that takes color [0-15] and convert it to:
+ * - [0-7] - [30-37] default colors
+ * - [8-15] - [90-97] light colors
+ * and writes to out buffer
+ */
+void
+c_to_ansi(const char *color, char *out, size_t out_size)
+{
+
+ int n = atoi(color);
+ int ansi_code = 39;
+
+ if (n >= 0 && n <= 7) {
+ ansi_code = n + 30;
+ } else if (n > 7 && n <= 15) {
+ ansi_code = n + 90 - 8;
+ }
+
+ snprintf(out, out_size, "%d", ansi_code);
+}
+
+
+
+/*
+ * function that:
+ * 1. if (align_info) add padding to label
+ * 2. return info_list, with malloc!
+ */
+info_list
+render_info(info_item infos[], size_t info_size)
+{
+ info_list res = {NULL, 0};
+ size_t maxlen = 0;
+
+ if (info_align) {
+ for(size_t i = 0; i < info_size; i++) {
+ size_t len = strlen(infos[i].label);
+ if (len > maxlen) {
+ maxlen = len;
+ }
+ }
+ }
+
+ for (size_t i = 0; i < info_size; i++) {
+ char *value = infos[i].func();
+ if (!value) value = "(null)";
+
+ /* count strings */
+ int split_count = 0;
+ {
+ char *tmp = strdup(value);
+ char *p = strtok(tmp, "\n");
+ while (p) {
+ split_count++;
+ p = strtok(NULL, "\n");
+ }
+ free(tmp);
+ }
+
+ /* split strings by \n */
+ char *line = strtok(value, "\n");
+ int number = 1;
+ while (line) {
+ res.entries = realloc(res.entries,
+ sizeof(rendered_info) * (res.count + 1));
+
+ char tmp_label[128];
+ if (split_count > 1 && numerate_same) {
+ snprintf(tmp_label, sizeof(tmp_label), "%s%d", infos[i].label, number);
+ } else {
+ snprintf(tmp_label, sizeof(tmp_label), "%s", infos[i].label);
+ }
+
+
+ if (info_align) {
+ char padded[128];
+ snprintf(padded, sizeof(padded), "%-*s", (int)maxlen, tmp_label);
+ res.entries[res.count].label = strdup(padded);
+ } else {
+ res.entries[res.count].label = strdup(tmp_label);
+ }
+
+ res.entries[res.count].value = strdup(line);
+ res.count++;
+
+ line = strtok(NULL, "\n");
+ number++;
+ }
+ free(value);
+ }
+ return res;
+}
+
+
+
+
+
+
+/*
+ * function that returns dynamic sebstring from *start to *end
+ */
+char *
+substring(const char *start, const char *end)
+{
+ if (!start || !end || end < start) return NULL;
+
+ size_t len = end - start;
+ char *out = malloc(len + 1); /* +1 for \0 */
+ if (!out) return NULL;
+
+ memcpy(out, start, len);
+ out[len] = '\0';
+
+ return out;
+}
+
+
+/* function that:
+ * - deletes all spaces from start to first char
+ * - deletes all spaces from end to first char
+ */
+char *
+trim(char *str)
+{
+ char *end;
+
+ /* skip start spaces */
+ while (isspace((unsigned char)*str)) str++;
+
+ if (*str == 0) /* blank str */
+ return str;
+
+ /* skip end spaces */
+ end = str + strlen(str) - 1;
+ while (end > str && isspace((unsigned char)*end)) end--;
+
+ /* ending string */
+ end[1] = '\0';
+
+ return str;
+}
+
+
+
+/*
+ * function that prints header
+ * returns:
+ * > 0: header length
+ * < 0: error
+ */
+int
+print_header()
+{
+ char *name = getenv("USER");
+ char hostname[256];
+ if (gethostname(hostname, sizeof(hostname)) != 0) {
+ perror("gethostname error");
+ return -1;
+ }
+
+ printf("\x1b[0m"); /* reset color */
+ printf("\x1b[%dm%s", colors[2], name);
+ printf("\x1b[%dm%s", colors[7], header_sep);
+ printf("\x1b[%dm%s", colors[2], hostname);
+
+
+ return strlen(name) + strlen(header_sep) + strlen(hostname);
+}
+
+/*
+ * function that prints boundary
+ * returns:
+ * boundary length
+ */
+int
+print_boundary(const char *c, int len)
+{
+ printf("\x1b[%dm", colors[8]);
+ for(int i = 0; i < len; i++)
+ {
+ printf("%s", c);
+ }
+ printf("\x1b[0m"); /* reset color */
+
+ return len * strlen(boundary_char);
+}
+
+/*
+ * function that reads file to buf dynamicly
+ * if returns < 0 here errors:
+ * - (-1) file doesn't exist
+ * - (-2) can't get size of the file
+ * - (-3) problem with allocating memory for file content
+ * - (-4) problem with reading file content
+ * "adds '\0' to and"
+ */
+int
+read_file(const char *file_path, char **buf)
+{
+ if (!file_path || !buf) {
+ *buf = NULL;
+ return -EINVAL;
+ }
+
+ FILE* fp = fopen(file_path, "r") ;
+ if (!fp) {
+ perror(file_path);
+ return -1;
+ }
+
+ struct stat st;
+ if (fstat(fileno(fp), &st) != 0 || !S_ISREG(st.st_mode)) {
+ fclose(fp);
+ return -2;
+ }
+
+ size_t sz = (size_t)st.st_size;
+ *buf = malloc(sz + 1);
+ if(!*buf) {
+ fclose(fp);
+ perror("malloc");
+ return -3;
+ }
+
+ size_t read = fread(*buf, 1, sz, fp);
+ if (read < sz && ferror(fp)) {
+ perror("fread");
+ free(*buf);
+ fclose(fp);
+ return -4;
+ }
+
+ (*buf)[read] = '\0';
+ fclose(fp);
+
+
+
+ return 0;
+}
+
+/*
+ * function that fill ascii.width and ascii.height fields,
+ * by counting the most long line (width),
+ * and counting lines (height);
+ * "writes to res"
+ */
+void
+get_ascii_size(struct ascii *res)
+{
+ if (!res || !res->art) {
+ res->width = res->height = 0;
+ return;
+ }
+
+ int maxw = 0;
+ int curw = 0;
+ int lines = 0;
+ char *p = res->art;
+ if (*p == '\0') {
+ res->width = res->height = 0;
+ return;
+ }
+
+ while (*p) {
+ if (*p == '\n') {
+ lines++;
+ if (curw > maxw) maxw = curw;
+ curw = 0;
+ } else {
+ curw++;
+ }
+ p++;
+ }
+
+ /* if last char is not '\n' */
+ if (curw == 0 || (res->art[0] != '\0' && res->art[0] == '\n')) {
+ lines++;
+ if (curw > maxw) maxw = curw;
+ }
+
+ res->width = maxw;
+ res->height = lines;
+}
+
+
+/*
+ * fill all struct ascii fields and return final structure
+ */
+struct ascii
+get_ascii()
+{
+ struct ascii res = {0};
+ res.art = strdup(ascii_art);
+
+ get_ascii_size(&res);
+ return res;
+}
+
+/*
+ * function that print:
+ * - ascii art with colors from config, with processing like:
+ * $1 - cfg.colors[1]
+ * - information with print_info(), takes size of infos (size_t infos_size)
+ *
+ * if return < 0:
+ * -
+ */
+int
+print_fetch(struct ascii *res)
+{
+ char *p = res->art;
+ int curw = 0;
+ char cur_color = '7';
+ int cur_info = 0;
+ int header_len = 0;
+
+
+ info_list infos = render_info(config_items, config_items_len);
+
+ while(*p || (size_t)cur_info < infos.count +
+ ((color_palette_show == 1) ? 3 : 0) ) {
+ if (*p) {
+ /* check color */
+ if(*p == '$' && isdigit(*(p + 1))){
+ cur_color = *++p;
+ printf("\x1b[%dm", colors[cur_color - '0']);
+ p++;
+ continue;
+ }
+ /* print char */
+ if(*p != '\n'){
+ putchar(*p);
+ p++;
+ curw++;
+ continue;
+ }
+ p++;
+ }
+
+ /* add padding */
+ if (res->width > 0) {
+ while(curw != res->width + ascii_pad) {
+ putchar(' ');
+ curw++;
+ }
+ }
+
+ /* print header */
+ if (header_len == 0 && header_show != 0){
+ header_len = print_header();
+ if (header_len < 0) {
+ fprintf(stderr, "Header Error: %d", header_len);
+ exit(EXIT_FAILURE);
+ }
+ goto print_fetch_end;
+ } else if (header_len > 0 && strlen(boundary_char) > 0) {
+ print_boundary(boundary_char, header_len);
+ header_len = -1;
+ goto print_fetch_end;
+ }
+
+ /* print boundary for palette */
+ if ((size_t)cur_info == infos.count
+ && color_palette_show) {
+ print_boundary(" ", 1);
+ cur_info++;
+ goto print_fetch_end;
+ }
+
+ /* print normal palette */
+ if ((size_t)cur_info == infos.count + 1
+ && color_palette_show) {
+ for (int i = 0; i < 8; i++) {
+ printf("\x1b[4%dm ", i);
+ }
+ cur_info++;
+ goto print_fetch_end;
+ }
+
+ /* print bright palette */
+ if ((size_t)cur_info == infos.count + 2
+ && color_palette_show) {
+ for (int i = 0; i < 8; i++) {
+ printf("\x1b[10%dm ", i);
+ }
+ cur_info++;
+ goto print_fetch_end;
+ }
+
+
+ /* print info */
+ if ((size_t)cur_info < infos.count) {
+ printf("\x1b[0m"); /* reset color */
+
+ printf("\x1b[%dm%s", colors[1], infos.entries[cur_info].label);
+ printf("\x1b[%dm%s", colors[6], info_sep);
+ printf("\x1b[%dm%s", colors[5], infos.entries[cur_info].value);
+ cur_info++;
+ }
+
+ /* chill I know what I'm doing */
+ print_fetch_end:
+ curw = 0;
+ printf("\x1b[0m"); /* reset color */
+ printf("\n");
+ printf("\x1b[%dm", colors[cur_color - '0']);
+ }
+ printf("\x1b[0m"); /* reset color */
+
+ free_info_list(&infos);
+ return 0;
+}
+
+int
+main(void)
+{
+ struct ascii art = get_ascii();
+
+ print_fetch(&art);
+
+ free_ascii(&art);
+ return 0;
+
+
+}
diff --git a/fetcha/license.txt b/fetcha/license.txt
new file mode 100644
index 0000000..3d3e667
--- /dev/null
+++ b/fetcha/license.txt
@@ -0,0 +1,24 @@
+BSD 2-Clause License
+
+Copyright (c) 2025, cryobs
+
+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.
+
+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.
diff --git a/fetcha/modules.c b/fetcha/modules.c
new file mode 100644
index 0000000..d9d5f35
--- /dev/null
+++ b/fetcha/modules.c
@@ -0,0 +1,536 @@
+#include <stdio.h>
+#include <string.h>
+#include <sys/utsname.h>
+#include <stdlib.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <unistd.h>
+
+/* void function placeholder */
+typedef char *(*info_func_t)(void);
+
+/* info */
+typedef struct
+{
+ const char *label;
+ info_func_t func;
+} info_item;
+
+
+static char *
+read_file_trim(const char *path)
+{
+ FILE *f = fopen(path, "r");
+ if (!f) {
+ return strdup("unknown");
+ }
+ char buf[256];
+ if (!fgets(buf, sizeof buf, f)) {
+ fclose(f);
+ return strdup("unknown");
+ }
+ /* trim newline */
+ size_t n = strlen(buf);
+ while (n && (buf[n-1] == '\n' || buf[n-1] == '\r')) buf[--n] = '\0';
+ return strdup(buf);
+}
+
+/*
+ * function that returns malloc string "OS Architecture" or NULL
+ */
+char *
+get_os(void)
+{
+ FILE *f = fopen("/etc/os-release", "r");
+ if (!f) f = fopen("/usr/lib/os-release", "r");
+ char osname[128] = "Unknown";
+ if (f) {
+ char line[256];
+ while (fgets(line, sizeof(line), f)) {
+ if (strncmp(line, "PRETTY_NAME=", 12) == 0) {
+ char *val = line + 12;
+ val[strcspn(val, "\n")] = 0;
+ if (*val == '"' && val[strlen(val)-1] == '"') {
+ val[strlen(val)-1] = 0;
+ val++;
+ }
+ strncpy(osname, val, sizeof(osname)-1);
+ break;
+ }
+ }
+ fclose(f);
+ }
+
+
+ size_t len = strlen(osname) + 1;
+ char *out = malloc(len);
+ if (!*out) return NULL;
+
+ snprintf(out, len, "%s", osname);
+
+ return out;
+}
+
+
+/*
+ * function that returns malloc string "Product Version" or NULL
+ */
+char *
+get_host(void)
+{
+ char *product = read_file_trim("/sys/class/dmi/id/product_name");
+ char *version = read_file_trim("/sys/class/dmi/id/product_version");
+ if (!product && !version) {
+ free(product);
+ free(version);
+ return strdup("unknown");
+ }
+ if (!version) version = strdup("");
+ size_t len = strlen(product ? product : "") + 1 + strlen(version) + 1 + 8;
+ char *out = malloc(len);
+ if (!out) {
+ free(product);
+ free(version);
+ return strdup("unknown");
+ }
+ if(product && version[0])
+ snprintf(out, len, "%s %s", product, version);
+ else if (product)
+ snprintf(out, len, "%s", product);
+ else
+ snprintf(out, len, "%s", version);
+
+ free(product);
+ free(version);
+
+ return out;
+}
+
+
+/*
+ * function that returns malloc string "Kernel" or NULL
+ */
+char *
+get_kernel(void)
+{
+ struct utsname buf;
+ if (uname(&buf) != 0) {
+ return strdup("unknown");
+ }
+ return strdup(buf.release);
+}
+
+
+/*
+ * function that concatinates to buf:
+ * if val != 0
+ * if val == 1: concat singular
+ * if val > 1: concat plural
+ */
+static void
+append_part(char *buf, size_t buf_size, int val,
+ const char *singular, const char *plural, int *first)
+{
+ if (val <= 0) return;
+ char part[32];
+ snprintf(part, sizeof part, "%d %s", val, (val == 1) ? singular : plural);
+ if (!*first)
+ strncat(buf, ", ", buf_size - strlen(buf) - 1);
+ strncat(buf, part, buf_size - strlen(buf) - 1);
+ *first = 0;
+}
+
+
+/*
+ * function that formats time:
+ * - with plural/singular format
+ * - if days/hours/mins == 0: dont add
+ *
+ * return malloc string
+ */
+char *
+format_uptime(int days, int hours, int mins)
+{
+ char *buf = malloc(128);
+ if (!buf) {
+ return strdup("unknown");
+ }
+ buf[0] = '\0';
+ int first = 1;
+ append_part(buf, 128, days, "day", "days", &first);
+ append_part(buf, 128, hours, "hour", "hours", &first);
+ append_part(buf, 128, mins, "min", "mins", &first);
+ if (first)
+ snprintf(buf, 128, "0 mins");
+ return buf;
+}
+
+
+char *
+get_uptime(void)
+{
+ FILE *f = fopen("/proc/uptime", "r");
+ if (!f) {
+ return strdup("unknown");
+ }
+ double seconds;
+ if (fscanf(f, "%lf", &seconds) != 1) {
+ fclose(f);
+ return strdup("unknown");
+ }
+ fclose(f);
+
+ int mins = (int)(seconds / 60) % 60;
+ int hours = (int)(seconds / 3600) % 24;
+ int days = (int) hours / 24;
+
+ return format_uptime(days, hours, mins);
+}
+
+char *
+get_packages(void)
+{
+ FILE *f = popen("pacman -Q | wc -l", "r");
+ if (!f) {
+ return "0";
+ }
+
+ char buf[20];
+ if (!fgets(buf, sizeof(buf), f)) {
+ pclose(f);
+ return "0";
+ }
+ pclose(f);
+
+ buf[strcspn(buf, "\n")] = 0; /* without \n */
+
+ size_t len = strlen(buf) + 10;
+ char *out = malloc(len);
+ if (!*out) return NULL;
+
+ snprintf(out, len, "%s (pacman)", buf);
+
+ return out;
+}
+
+
+char *
+get_memory(void) {
+ char *buf = malloc(64);
+ FILE *f = fopen("/proc/meminfo", "r");
+ if (!f) {
+ return strdup("unknown");
+ }
+
+ long mem_total = 0;
+ char *mem_total_type = "KiB";
+ long mem_free = 0;
+ long buffers = 0;
+ long cached = 0;
+
+ char key[32];
+ long value;
+ char unit[16];
+
+ while(fscanf(f, "%31s %ld %15s", key, &value, unit) == 3) {
+ if (strcmp(key, "MemTotal:") == 0) {
+ mem_total = value;
+ } else if (strcmp(key, "MemFree:") == 0) {
+ mem_free = value;
+ } else if (strcmp(key, "Buffers:") == 0) {
+ buffers = value;
+ } else if (strcmp(key, "Cached:") == 0) {
+ cached = value;
+ }
+ }
+ fclose(f);
+
+ long mem_used = mem_total - mem_free - buffers - cached;\
+ char *mem_used_type = "KiB";
+
+ if (mem_total >= 1024) {
+ mem_total /= 1024;
+ mem_total_type = "MiB";
+ }
+ if (mem_used >= 1024) {
+ mem_used /= 1024;
+ mem_used_type = "MiB";
+ }
+
+ snprintf(buf, 64, "%ld%s / %ld%s",
+ mem_used, mem_used_type, mem_total, mem_total_type);
+ return buf;
+}
+
+
+
+char *
+get_cpus(void) {
+ FILE *f = fopen("/proc/cpuinfo", "r");
+ if (!f) {
+ return strdup("unknown");
+ }
+
+ typedef struct {
+ char model[128];
+ int cores;
+ int first_logical;
+ } cpu_info;
+
+ cpu_info cpus[32] = {0};
+ int cpu_count = 0;
+
+ char line[256];
+ int logical_index = 0;
+ int current_physical = -1;
+
+ while (fgets(line, sizeof(line), f)) {
+ if (strncmp(line, "processor", 9) == 0) {
+ logical_index++;
+ } else if (strncmp(line, "physical id", 11) == 0) {
+ char *p = strchr(line, ':');
+ if (p) current_physical = atoi(p + 1);
+ if (current_physical + 1 > cpu_count) cpu_count = current_physical + 1;
+ } else if (strncmp(line, "model name", 10) == 0 && current_physical >= 0) {
+ char *p = strchr(line, ':');
+ if (!p) {
+ continue;
+ }
+
+ p++;
+ while(*p == ' ' || *p == '\t') p++;
+ char *newline = strchr(p, '\n');
+ if (newline) *newline = '\0';
+ if (cpus[current_physical].cores == 0) {
+ char *cpu_at = strstr(p, " CPU @");
+ if (cpu_at) *cpu_at = '\0';
+ strncpy(cpus[current_physical].model, p,
+ sizeof(cpus[current_physical].model)-1);
+ cpus[current_physical].first_logical = logical_index;
+ }
+ cpus[current_physical].cores++;
+ }
+ }
+
+ fclose(f);
+
+ char *buffer = malloc(4096);
+ if (!buffer) {
+ return strdup("unknown");
+ }
+
+ buffer[0] = '\0';
+
+ for (int i = 0; i < cpu_count; i++) {
+ if (cpus[i].cores == 0) continue;
+
+ /* read max freq for first cpu thread */
+ char path[128];
+ snprintf(path, sizeof(path),
+ "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq",
+ cpus[i].first_logical);
+ unsigned long max_khz = 0;
+ FILE *freq_file = fopen(path, "r");
+ if (freq_file) {
+ fscanf(freq_file, "%lu", &max_khz);
+ fclose(freq_file);
+ }
+ double max_ghz = max_khz / 1000.0 / 1000.0;
+
+ char tmp[256];
+ snprintf(tmp, sizeof(tmp), "%s (%d) @ %.2f GHz",
+ cpus[i].model, cpus[i].cores + 1, max_ghz);
+
+ if(buffer[0] != '\0') strcat(buffer, "\n");
+ strcat(buffer, tmp);
+ }
+
+ return buffer;
+}
+
+
+char *
+get_gpus(void)
+{
+ FILE *f = popen("lspci | grep -i VGA", "r");
+ typedef struct {
+ char brand[64];
+ char model[128];
+ } gpu_info;
+
+ gpu_info gpus[64] = {0};
+ char line[256];
+ int gpu_num = 0;
+
+ if (!f) {
+ return strdup("unknown");
+ }
+
+ char *buffer = malloc(4096);
+ if (!buffer) return NULL;
+ buffer[0] = '\0';
+
+ while (fgets(line, sizeof(line), f)) {
+ /* search for a brand */
+ char *brand = strstr(line, "VGA compatible controller: ");
+ if (!brand) continue;
+ brand += strlen("VGA compatible controller: ");
+
+ /* search brand type */
+ if (strstr(brand, "NVIDIA")) {
+ strcpy(gpus[gpu_num].brand, "NVIDIA");
+ } else if (strstr(brand, "Intel")) {
+ strcpy(gpus[gpu_num].brand, "Intel");
+ } else if (strstr(brand, "AMD") || strstr(brand, "ATI")) {
+ strcpy(gpus[gpu_num].brand, "AMD");
+ } else {
+ strcpy(brand, "Unknown");
+ }
+
+ char *model = strchr(brand, '[');
+ char *model_end = strchr(brand, ']');
+ if (model && model_end && model_end > model) {
+ int len = model_end - model - 1;
+ if (len > 127) len = 127;
+ strncpy(gpus[gpu_num].model, model + 1, len);
+ model[len] = '\0';
+ } else {
+ strncpy(gpus[gpu_num].model, brand, sizeof(gpus[0].model) - 1);
+ }
+
+ char tmp[256];
+ snprintf(tmp, sizeof(tmp), "%s %s\n",
+ gpus[gpu_num].brand, gpus[gpu_num].model);
+ strcat(buffer, tmp);
+ gpu_num++;
+ }
+
+ pclose(f);
+ return buffer;
+}
+
+
+
+
+char *
+get_wm(void)
+{
+ Display *dpy = XOpenDisplay(NULL);
+ if (!dpy) {
+ return strdup("unknown");
+ }
+
+ Atom wm_check = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", True);
+ Atom wm_name = XInternAtom(dpy, "_NET_WM_NAME", True);
+ if (wm_check == None || wm_name == None) {
+ return strdup("unknown");
+ }
+
+ Window root = DefaultRootWindow(dpy);
+ Atom actual_type;
+ int actual_format;
+ unsigned long nitems, bytes_after;
+ unsigned char *prop = NULL;
+
+ /* get window that have "supporting WM check" property */
+ if (XGetWindowProperty(dpy, root, wm_check, 0, 1, False, XA_WINDOW,
+ &actual_type, &actual_format, &nitems, &bytes_after,
+ &prop) != Success || !prop) {
+ return strdup("unknown");
+ }
+
+ Window wm_window = *(Window*)prop;
+ XFree(prop);
+
+ /* read WM name */
+ if (XGetWindowProperty(dpy, wm_window, wm_name, 0, (~0L), False,
+ AnyPropertyType, &actual_type, &actual_format,
+ &nitems, &bytes_after, &prop) != Success || !prop) {
+ return strdup("unknown");
+ }
+
+ char *name = strndup((char *)prop, nitems);
+ XFree(prop);
+
+ return name;
+}
+
+char *
+get_shell(void)
+{
+ char *shell = getenv("SHELL");
+ if (!shell) {
+ return strdup("unknown");
+ }
+
+ char cmd[256];
+ snprintf(cmd, sizeof(cmd), "%s --version 2>/dev/null", shell);
+
+ FILE *f = popen(cmd, "r");
+ if (!f) {
+ return strdup(shell);
+ }
+
+ char buf[256];
+ if (!fgets(buf, sizeof(buf), f)) {
+ pclose(f);
+ return strdup(shell);
+ }
+ pclose(f);
+
+ buf[strcspn(buf, "\n")] = 0; /* without \n */
+
+ /* parse: name version */
+ char *name = strtok(buf, " ,");
+ char *ver = strtok(NULL, " ,");
+
+ char out[128];
+ if (name && ver) {
+ snprintf(out, sizeof(out), "%s %s", name, ver);
+ } else {
+ snprintf(out, sizeof(out), "%s", shell);
+ }
+
+ return strdup(out);
+
+}
+
+pid_t
+get_parent_pid(pid_t pid)
+{
+ char path[64];
+ snprintf(path, sizeof(path), "/proc/%d/stat", pid);
+
+ FILE *f = fopen(path, "r");
+ if (!f) {
+ return 0;
+ }
+
+ pid_t ppid = 0;
+ fscanf(f, "%*d %*s %*c %d", &ppid);
+ fclose(f);
+ return ppid;
+
+}
+
+char *
+get_terminal(void)
+{
+ char *term = getenv("TERMINAL");
+ if (term && *term) {
+ return strdup(term);
+ }
+
+ term = getenv("TERM_PROGRAM");
+ if (term && *term) {
+ return strdup(term);
+ }
+
+ term = getenv("TERM");
+ if (term && *term) {
+ return strdup(term);
+ }
+
+ return strdup("unknown");
+
+}
diff --git a/fetcha/modules.h b/fetcha/modules.h
new file mode 100644
index 0000000..5c5469f
--- /dev/null
+++ b/fetcha/modules.h
@@ -0,0 +1,27 @@
+#ifndef MODULES_H
+#define MODULES_H
+
+
+typedef char*(*info_func_t)(void);
+
+typedef struct {
+ const char *label;
+ info_func_t func;
+} info_item;
+
+
+
+char *get_os(void);
+char *get_host(void);
+char *get_kernel(void);
+char *get_uptime(void);
+char *get_packages(void);
+char *get_memory(void);
+char *get_cpus(void);
+char *get_gpus(void);
+char *get_wm(void);
+char *get_shell(void);
+char *get_terminal(void);
+
+
+#endif
diff --git a/install.sh b/install.sh
index c266121..5b071b5 100755
--- a/install.sh
+++ b/install.sh
@@ -1,5 +1,5 @@
#!/bin/sh
-programs="dwm dmenu slstatus st"
+programs="dwm dmenu slstatus st fetcha"
command_exists() {
command -v "$1" >/dev/null 2>&1
@@ -13,20 +13,23 @@ for program in $programs; do
has_root_perms && (cd "$program" && echo "starting $program installation" && make clean install && echo "$program installation complete") || echo "Can't install $program: you must run the script as root or with sudo."
done
+if command_exists picom; then
+ if ! has_root_perms; then
+ echo "Can't install picom configuration: you must run the script as root or with sudo."
+ exit 1
+ fi
+
+ cp picom.conf /etc/xdg/picom.conf
+ echo "picom &" >>~/.xinitrc
+fi
+
if command_exists feh; then
feh --bg-fill wallpaper.jpg
echo "~/.fehbg &" >>~/.xinitrc
fi
-if command_exists picom; then
- has_root_perms && cp picom.conf /etc/xdg/picom.conf || echo "Can't install picom configuration: you must run the script as root or with sudo."
- echo "picom &" >>~/.xinitrc
-fi
-
command_exists flameshot && echo "flameshot &" >>~/.xinitrc
-command_exists neofetch && cp neofetch -r ~/.config/
-
command_exists vis && cp vis -r ~/.config/
if command_exists tmux; then
diff --git a/neofetch/config.conf b/neofetch/config.conf
deleted file mode 100644
index bcb22de..0000000
--- a/neofetch/config.conf
+++ /dev/null
@@ -1,22 +0,0 @@
-# See this wiki page for more info:
-# https://github.com/dylanaraps/neofetch/wiki/Customizing-Info
-print_info() {
- info title
-
- info "Distro" distro
- info "Kernel" kernel
- info "Packages" packages
- info "WM" wm
- info "Terminal" term
- info "Memory" memory
- info cols
-}
-
-distro_shorthand="off"
-os_arch="off"
-package_managers="on"
-seperator=""
-ascii_distro="arch_small"
-
-colors=(5 0 5 4 4 7)
-ascii_colors=(5 4)