diff options
| author | Polesznyák Márk <contact@pml68.dev> | 2025-09-23 17:05:25 +0200 |
|---|---|---|
| committer | Polesznyák Márk <contact@pml68.dev> | 2025-09-23 17:05:25 +0200 |
| commit | b089cc6ed9ae82bf7d94d2b6f02aa01e21d43793 (patch) | |
| tree | 216156843f171edd7ba21d55ead33dfd08733966 | |
| parent | feat(dmenu): add inlinePrompt patch (diff) | |
| download | suckless-setup-b089cc6ed9ae82bf7d94d2b6f02aa01e21d43793.tar.gz | |
feat: switch from neofetch to Cryobs/fetcha
| -rw-r--r-- | README.md | 1 | ||||
| -rw-r--r-- | fetcha/.gitignore | 6 | ||||
| -rw-r--r-- | fetcha/Makefile | 56 | ||||
| -rw-r--r-- | fetcha/README.md | 27 | ||||
| -rw-r--r-- | fetcha/config.def.h | 74 | ||||
| -rw-r--r-- | fetcha/fetcha.1 | 53 | ||||
| -rw-r--r-- | fetcha/fetcha.c | 522 | ||||
| -rw-r--r-- | fetcha/license.txt | 24 | ||||
| -rw-r--r-- | fetcha/modules.c | 536 | ||||
| -rw-r--r-- | fetcha/modules.h | 27 | ||||
| -rwxr-xr-x | install.sh | 19 | ||||
| -rw-r--r-- | neofetch/config.conf | 22 |
12 files changed, 1336 insertions, 31 deletions
@@ -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. + + +## 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 @@ -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) |
