Commit Diff


commit - /dev/null
commit + c2820569ceee27bab3e5efdaeba7a592f16dec4e
blob - /dev/null
blob + f450a100e11d76f70c224b585d173f1986cccbfd (mode 644)
--- /dev/null
+++ LICENSE
@@ -0,0 +1,28 @@
+Copyright (c) 2024 Gonzalo Rodriguez (gonzalo@x61.sh)
+All rights reserved.
+
+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 HOLDERS 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.
+
blob - /dev/null
blob + cd5a4b054a640aa6d7bb8f294e0f8b5b5ffc46a0 (mode 644)
--- /dev/null
+++ Makefile
@@ -0,0 +1,30 @@
+.POSIX:
+
+CC ?=		cc
+LIBS =
+OPTFLAGS =	-O3
+DBGFLAGS =	-O0 -g
+CFLAGS =	-pipe -Wall -Werror -march=native
+INCLUDEDIR =	-I.
+
+all: build
+
+build: clean
+	${CC} ${DBGFLAGS} ${CFLAGS} ${INCLUDEDIR} -o termbar ${LIBS} termbarc.c
+
+opt: clean
+	${CC} ${OPTFLAGS} ${CFLAGS} ${INCLUDEDIR} -o termbar ${LIBS} termbarc.c
+
+install:
+	install -s termbar ${HOME}/bin/termbar
+	install termbar.conf ${HOME}/.termbar.conf
+
+clean:
+	rm -f termbar
+
+uninstall:
+	rm -f ${HOME}/bin/termbar
+	rm -f ${HOME}/.termbar.conf
+
+debug: build
+	egdb -q ./termbar -ex "break main" -ex "run"
blob - /dev/null
blob + 90bdf7aa38f65e18cc634dcfe50303871601f354 (mode 644)
--- /dev/null
+++ README.md
@@ -0,0 +1,128 @@
+# Termbar in C
+
+termbar(in C) is a status bar for cwm (or other wm) on OpenBSD (no idea if works on
+linux or other OS and also I don't care).
+
+The idea was to make my original [termbar](https://github.com/gonzalo-/termbar/) in C and
+I took inspiration from [sdk's cbar](https://git.uugrn.org/sdk/cbar), it's hardly
+modified but yet, helped me a lot.
+
+CAVEATS: This is a testing and still developing version, I tested in a couple machines
+so it might not work on yours or probably put on fire your laptop, so be careful and don't
+complain much, if you have some feedback you know where you can find me.
+
+![Termbar](termbar.png)
+
+## Features
+
+termbar for now can show you, a "logo" (or name), hostname, cpu speed and temp,
+free mem, window id, load average, battery status (probably only in thinkpads), public IP,
+private IP and if you are connected to a VPN.
+
+The battery percentage it will turn red if your machine is not connected to the AC as
+the VPN output will be "No VPN" in red if there is no wg(4) present or tunnel up (still on
+testing).
+
+If your CPU has no sensors or not supported it will show an "x" next to the CPU speed, you
+will probably see this on a VM or old machine (not very much tested).
+
+## Usage
+
+termbar has now a config file (which you should have in ~/.termbar.conf after the install),
+you have an example [here](termbar.conf) with all the options available:
+
+```
+logo=termbar
+date=yes
+cpu=yes
+bat=yes
+mem=yes
+load=yes
+net=yes
+winid=yes
+hostname=yes
+interface=iwm0
+vpn=yes
+```
+
+The only 2 options you can play with are "logo" and "interface", "logo" will just print
+something you put there and "interface" will be use to get the internal ip of your
+machine.
+
+With the other options are pretty straigforward, "yes" to show the information on termbar
+and "no" to hide it.
+
+## Display
+
+For displaying termbar in your cwm or maybe another wm, you need to create a thin xterm that
+will show the output, for this you can add a similar line in your .xsession:
+
+```
+...
+# Termbar
+exec xterm -fs 12 -bg "black" -fg "grey" -name termbar -class termbar -T termbar -e ~/bin/termbar &
+...
+exec cwm
+```
+
+For cwm I usually let a gap on the top of the screen for termbar with the follow on my .cwmrc:
+
+```
+...
+gap						35 5 5 5
+...
+```
+
+The full files for cwm and .xsession are [here.](https://github.com/gonzalo-/termbar/).
+
+You probably can use termbar also in tmux as status bar, you might want to add or modify your
+~/.tmux.conf to something like:
+
+```
+...
+set -g status-right "#{exec ~/bin/termbar}"
+...
+```
+
+## Dependencies
+
+In the current state you just need curl to check your public ip.
+
+```
+$ doas pkg_add curl
+```
+
+## Build
+
+You have all you need in your OpenBSD base installation, so by cloning the repo
+and building is as easy as:
+
+```
+$ git clone https://github.com/gonzalo-/termbarc/
+$ cd termbarc
+$ make
+rm -f termbar
+cc -O0 -g -pipe -Wall -Werror -march=native -I. -o termbar  termbarc.c
+```
+
+## Installation
+
+termbar will go to your ${HOME}/bin directory, if you want to put it somewhere
+else you should modify the Makefile, otherwise make sure you have a ~/bin
+directory and then:
+
+```
+$ make install
+install -s termbar /home/gonzalo/bin/termbar
+install termbar.conf /home/gonzalo/.termbar.conf
+```
+
+## Uninstall
+
+If termbar it's not for you, you can uninstall it with:
+
+```
+$ make uninstall
+rm -f /home/gonzalo/bin/termbar
+rm -f /home/gonzalo/.termbar.conf
+```
blob - /dev/null
blob + 8c42c38ce752d381fd60c2b51cc069df3d3abcab (mode 644)
--- /dev/null
+++ termbar.conf
@@ -0,0 +1,11 @@
+logo=Termbar
+date=yes
+cpu=yes
+bat=yes
+mem=yes
+load=yes
+net=yes
+winid=yes
+hostname=no
+interface=iwm0
+vpn=yes
blob - /dev/null
blob + c6164c52fe96136bc348652c18cb28faa7ed08b8 (mode 644)
Binary files /dev/null and termbar.png differ
blob - /dev/null
blob + 7730a0abb3af12011d42b28c10b2070c8eb186b2 (mode 644)
--- /dev/null
+++ termbarc.c
@@ -0,0 +1,506 @@
+/*
+ * Copyright (c) 2024 Gonzalo Rodriguez (gonzalo@x61.sh)
+ * All rights reserved.
+ *
+ * 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 HOLDERS 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <ifaddrs.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+
+#include <sys/sysctl.h>
+#include <sys/sensors.h>
+#include <machine/apmvar.h>
+
+#include <wchar.h>
+#include <time.h>
+#include <locale.h>
+
+#include <termbarc.h>
+
+struct Config {
+        char *logo;
+        char *interface;
+        int show_hostname;
+        int show_date;
+        int show_cpu;
+        int show_mem;
+        int show_bat;
+        int show_load;
+        int show_winid;
+        int show_net;
+        int show_vpn;
+};
+
+char *extract_logo(const char *line)
+{
+        if (strstr(line, "logo=")) {
+                const char *logo_start = strchr(line, '=') + 1;
+                const char *logo_end = NULL;
+
+                // Find the end of the logo string
+                const char *cursor = logo_start;
+                while (*cursor && *cursor != ' ' && *cursor != '\n') {
+                        cursor++;
+                }
+                logo_end = cursor;
+
+                // Calculate the length of the logo string
+                size_t logo_length = logo_end - logo_start;
+
+                // Allocate memory for the logo and copy it
+                char *logo = (char *) malloc((logo_length + 1) * sizeof(char));
+                if (logo == NULL) {
+                        perror("No logo.");
+                        exit(EXIT_FAILURE);
+                }
+                strncpy(logo, logo_start, logo_length);
+                logo[logo_length] = '\0';
+                return logo;
+        }
+        return NULL;
+}
+
+struct Config config_file()
+{
+        struct Config config = { NULL, 0, 0, 0, 0, 0, 0, 0, 0 };
+        const char *home_dir = getenv("HOME");
+        if (home_dir == NULL) {
+                fprintf(stderr, "Error: HOME environment variable not set\n");
+                exit(EXIT_FAILURE);
+        }
+
+        const char *config_file_names[] = { "termbar.conf", ".termbar.conf" };
+        FILE *file = NULL;
+        char config_file_path[256];
+
+        for (int i = 0; i < 2; ++i) {
+                snprintf(config_file_path, sizeof(config_file_path), "%s/%s",
+                         home_dir, config_file_names[i]);
+                file = fopen(config_file_path, "r");
+                if (file != NULL) {
+                        break;  // File found, exit loop
+                }
+        }
+
+        if (file == NULL) {
+                fprintf(stderr, "Error: Unable to open config file\n");
+                exit(EXIT_FAILURE);
+        }
+
+        char line[MAX_LINE_LENGTH];
+
+        while (fgets(line, sizeof(line), file)) {
+                line[strcspn(line, "\n")] = '\0';
+
+                char *logo = extract_logo(line);
+                if (logo != NULL) {
+                        config.logo = logo;
+                        continue;       // Move to the next line
+                }
+                // Extract interface option
+                if (strstr(line, "interface=")) {
+                        const char *interface_start = strchr(line, '=') + 1;
+                        size_t interface_length = strlen(interface_start);
+                        config.interface = malloc(interface_length + 1);
+                        if (config.interface == NULL) {
+                                perror("Memory allocation failed");
+                                exit(EXIT_FAILURE);
+                        }
+                        strncpy(config.interface, interface_start,
+                                interface_length);
+                        config.interface[interface_length] = '\0';
+                }
+                // Check confs
+                if (strstr(line, "date=yes")) {
+                        config.show_date = 1;
+                } else if (strstr(line, "cpu=yes")) {
+                        config.show_cpu = 1;
+                } else if (strstr(line, "load=yes")) {
+                        config.show_load = 1;
+                } else if (strstr(line, "bat=yes")) {
+                        config.show_bat = 1;
+                } else if (strstr(line, "net=yes")) {
+                        config.show_net = 1;
+                } else if (strstr(line, "mem=yes")) {
+                        config.show_mem = 1;
+                } else if (strstr(line, "winid=yes")) {
+                        config.show_winid = 1;
+                } else if (strstr(line, "hostname=yes")) {
+                        config.show_hostname = 1;
+                } else if (strstr(line, "vpn=yes")) {
+                        config.show_vpn = 1;
+                }
+        }
+
+        fclose(file);
+        return config;
+}
+
+void update_public_ip()
+{
+        FILE *fp;
+        char buffer[128];
+
+        // to-do I need a better thing than curl
+        fp = popen("/usr/local/bin/curl -s ifconfig.me", "r");
+        if (fp == NULL) {
+                perror("popen");
+                exit(EXIT_FAILURE);
+        }
+
+        if (fgets(buffer, sizeof(buffer), fp) != NULL) {
+                // Copy the public IP address to the global variable
+                strncpy(public_ip, buffer, MAX_IP_LENGTH);
+                // Remove trailing newline character, if any
+                public_ip[strcspn(public_ip, "\n")] = '\0';
+        }
+        pclose(fp);
+}
+
+char *get_hostname()
+{
+        static char hostname[HOSTNAME_MAX_LENGTH];
+
+        if (gethostname(hostname, HOSTNAME_MAX_LENGTH) == -1) {
+                perror("gethostname");
+                exit(EXIT_FAILURE);
+        }
+
+        return hostname;
+}
+
+void update_internal_ip(struct Config config)
+{
+        struct ifaddrs *ifap, *ifa;
+        struct sockaddr_in *sa;
+
+        if (getifaddrs(&ifap) == -1) {
+                perror("getifaddrs");
+                exit(EXIT_FAILURE);
+        }
+        // Search for the specified interface or fallback to lo0
+        if (config.interface == NULL || strlen(config.interface) == 0) {
+                strlcpy(internal_ip, "lo0", sizeof(internal_ip));
+        } else {
+                // Search for the specified interface
+                for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
+                        if (strcmp(ifa->ifa_name, config.interface) == 0 &&
+                            ifa->ifa_addr != NULL
+                            && ifa->ifa_addr->sa_family == AF_INET) {
+                                sa = (struct sockaddr_in *) ifa->ifa_addr;
+                                inet_ntop(AF_INET, &(sa->sin_addr), internal_ip,
+                                          sizeof(internal_ip));
+                                break;
+                        }
+                }
+        }
+
+        freeifaddrs(ifap);
+}
+
+void update_vpn()
+{
+        struct ifaddrs *ifap, *ifa;
+        int has_wg_interface = 0;
+
+        if (getifaddrs(&ifap) == -1) {
+                perror("getifaddrs");
+                exit(EXIT_FAILURE);
+        }
+        // Check for wgX interfaces
+        for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
+                if (strncmp(ifa->ifa_name, "wg", 2) == 0
+                    && ifa->ifa_flags & IFF_UP) {
+                        has_wg_interface = 1;
+                        break;
+                }
+        }
+
+        freeifaddrs(ifap);
+
+        if (has_wg_interface)
+                printf(" %sVPN%s", GREEN, RESET);
+        else
+                printf(" %sNo VPN%s", RED, RESET);
+}
+
+unsigned long long update_mem()
+{
+        int mib[2];
+        size_t len;
+        unsigned long long freemem;
+
+        mib[0] = CTL_VM;
+        mib[1] = VM_UVMEXP;
+
+        len = sizeof(struct uvmexp);
+
+        struct uvmexp uvm_stats;
+
+        if (sysctl(mib, 2, &uvm_stats, &len, NULL, 0) == -1) {
+                perror("sysctl");
+                exit(EXIT_FAILURE);
+        }
+
+        freemem =
+            (unsigned long long) uvm_stats.free *
+            (unsigned long long) uvm_stats.pagesize / (1024 * 1024);
+
+        return freemem;
+}
+
+void update_cpu_base_speed()
+{
+        int temp;
+        size_t templen = sizeof(temp);
+
+        int mib[5] = { CTL_HW, HW_CPUSPEED };
+
+        if (sysctl(mib, 2, &temp, &templen, NULL, 0) == -1)
+                snprintf(cpu_base_speed, sizeof(cpu_base_speed), "no_freq");
+        else
+                snprintf(cpu_base_speed, sizeof(cpu_base_speed), "%4dMhz",
+                         temp);
+}
+
+void update_cpu_avg_speed()
+{
+        uint64_t freq;
+        size_t len = sizeof(freq);
+        int mib[2] = { CTL_HW, HW_CPUSPEED };
+
+        if (sysctl(mib, 2, &freq, &len, NULL, 0) == -1) {
+                perror("sysctl");
+                return;
+        }
+        snprintf(cpu_avg_speed, sizeof(cpu_avg_speed), "%4lluMhz", freq);
+}
+
+void update_system_load(double *load_avg)
+{
+        double load[3];         // Take 1, 5, and 15-minute load averages
+
+        if (getloadavg(load, 3) == -1) {
+                perror("getloadavg");
+                exit(EXIT_FAILURE);
+        }
+
+        for (int i = 0; i < 3; i++) {
+                load_avg[i] = load[i];
+        }
+}
+
+void update_cpu_temp()
+{
+        struct sensor sensor;
+        size_t templen = sizeof(sensor);
+        int temp = -1;
+
+        static int temp_mib = -1;
+
+        if (temp_mib == -1) {
+                for (temp_mib = 0; temp_mib < 20; temp_mib++) {
+                        int mib[5] = { CTL_HW, HW_SENSORS, temp_mib, SENSOR_TEMP, 0 };  // acpitz0.temp0 (x395)
+                        if (sysctl(mib, 5, &sensor, &templen, NULL, 0) != -1)
+                                break;
+                }
+        }
+
+        if (temp_mib != -1) {
+                int mib[5] = { CTL_HW, HW_SENSORS, temp_mib, SENSOR_TEMP, 0 };
+                if (sysctl(mib, 5, &sensor, &templen, NULL, 0) != -1) {
+                        temp = (sensor.value - 273150000) / 1000000.0;
+                        if (temp >= 0 && temp <= 100) { // hmmm could be more than 100?
+                                snprintf(cpu_temp, sizeof(cpu_temp),
+                                         "%d\302\260C", temp);
+                                return;
+                        }
+                }
+        }
+        // If no valid temperature reading found, set to "x"
+        // specially for VMs
+        snprintf(cpu_temp, sizeof(cpu_temp), "x");
+}
+
+void update_battery()
+{
+        int fd;
+        struct apm_power_info pi;
+
+        if ((fd = open("/dev/apm", O_RDONLY)) == -1 ||
+            ioctl(fd, APM_IOC_GETPOWER, &pi) == -1 || close(fd) == -1) {
+                strlcpy(battery_percent, "N/A", sizeof(battery_percent));
+                return;
+        }
+
+        if (pi.battery_state == APM_BATT_UNKNOWN ||
+            pi.battery_state == APM_BATTERY_ABSENT) {
+                strlcpy(battery_percent, "N/A", sizeof(battery_percent));
+                return;
+        }
+
+        if (pi.ac_state == APM_AC_ON) {
+                snprintf(battery_percent, sizeof(battery_percent), "%d%%",
+                         pi.battery_life);
+        } else {
+                snprintf(battery_percent, sizeof(battery_percent), "%s%d%%%s",
+                         RED, pi.battery_life, RESET);
+        }
+}
+
+void update_datetime()
+{
+        time_t rawtime;
+        struct tm *timeinfo;
+        time(&rawtime);
+        timeinfo = localtime(&rawtime);
+        strftime(datetime, sizeof(datetime), "%a %d %b %H:%M", timeinfo);
+}
+
+void update_windowid(char *window_id)
+{
+        const char *command =
+            "xprop -root 32c '\\t$0' _NET_CURRENT_DESKTOP | cut -f 2";
+
+        FILE *pipe = popen(command, "r");
+        if (pipe == NULL) {
+                fprintf(stderr,
+                        "Error: Failed to open pipe for command execution");
+                strlcpy(window_id, "N/A", MAX_OUTPUT_LENGTH);
+                return;
+        }
+
+        char output[MAX_OUTPUT_LENGTH];
+        if (fgets(output, MAX_OUTPUT_LENGTH, pipe) == NULL) {
+                fprintf(stderr, "Error: Failed to read command output");
+                strlcpy(window_id, "N/A", MAX_OUTPUT_LENGTH);
+                pclose(pipe);
+                return;
+        }
+
+        pclose(pipe);
+
+        size_t len = strlen(output);
+        if (len > 0 && output[len - 1] == '\n') {
+                output[len - 1] = '\0';
+        }
+
+        strlcpy(window_id, output, MAX_OUTPUT_LENGTH);
+}
+
+int main(int argc, const char *argv[])
+{
+        // We all want UTF8
+        setlocale(LC_CTYPE, "C");
+        setlocale(LC_ALL, "en_US.UTF-8");
+
+        // Read the config file
+        struct Config config = config_file();
+        if (config.logo == NULL) {
+                fprintf(stderr,
+                        "Error: Unable to read name from config file\n");
+                return 1;
+        }
+        // Hide cursor
+        printf("\e[?25l");
+
+        while (1) {
+                printf("\r\e[K");
+                if (config.show_winid) {
+                        update_windowid(window_id);
+                        printf("%s[%s]%s", RESET, window_id, RESET);
+                        printf("%s|%s", PURPLE, RESET);
+                }
+                if (config.logo != NULL && strlen(config.logo) > 0) {
+                        printf("%s%s%s", GREEN, config.logo, RESET);
+                        printf("%s|%s", PURPLE, RESET);
+                }
+                if (config.show_hostname) {
+                        char *hostname = get_hostname();
+                        printf(" %s ", hostname);
+                        printf("%s|%s", PURPLE, RESET);
+                }
+                if (config.show_date) {
+                        update_datetime();
+                        printf(" %s ", datetime);
+                        printf("%s|%s", PURPLE, RESET);
+                }
+                if (config.show_cpu) {
+                        update_cpu_temp();
+                        update_cpu_avg_speed();
+                        update_cpu_base_speed();
+                        printf(" %sCPU:%s %s (%s) ", GREEN, RESET,
+                               cpu_avg_speed, cpu_temp);
+                        printf("%s|%s", PURPLE, RESET);
+                }
+                if (config.show_mem) {
+                        free_memory = update_mem();
+                        printf(" %sMem:%s %.0llu MB ", GREEN, RESET,
+                               free_memory);
+                        printf("%s|%s", PURPLE, RESET);
+                }
+                if (config.show_load) {
+                        update_system_load(system_load);
+                        printf(" %sLoad:%s %.2f ", GREEN, RESET,
+                               system_load[0]);
+                        printf("%s|%s", PURPLE, RESET);
+                }
+                if (config.show_bat) {
+                        update_battery();
+                        printf(" %sBat:%s %s ", GREEN, RESET, battery_percent);
+                        printf("%s|%s", PURPLE, RESET);
+                }
+                if (config.show_net) {
+                        update_public_ip();
+                        update_internal_ip(config);
+                        printf(" %sIPs:%s %s ~ %s ", GREEN, RESET, public_ip,
+                               internal_ip);
+                        printf("%s|%s", PURPLE, RESET);
+                }
+                if (config.show_vpn) {
+                        update_vpn();
+                }
+
+                fflush(stdout);
+                if (argc == 2)
+                        if (strcmp("-1", argv[1]) >= 0)
+                                return 0;
+                usleep(3000000);
+        }
+        return 0;
+}
blob - /dev/null
blob + cf69de91a16819a6cc2b1e73dfacf9d77241652a (mode 644)
--- /dev/null
+++ termbarc.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2024 Gonzalo Rodriguez (gonzalo@x61.sh)
+ * All rights reserved.
+ *
+ * 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 HOLDERS 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.
+ */
+
+#define INET_ADDRSTRLEN 16
+#define MAX_IP_LENGTH 32
+#define MAX_LINE_LENGTH 256
+#define MAX_OUTPUT_LENGTH 16
+#define HOSTNAME_MAX_LENGTH 256
+
+// Define ANSI escape codes for color formatting
+#define PURPLE      "\x1b[38;2;138;43;226m"
+#define ORANGE      "\x1b[38;2;255;165;0m"
+#define GREEN       "\x1b[38;2;0;128;0m"
+#define YELLOW      "\x1b[38;2;255;255;0m"
+#define RED         "\x1b[38;2;255;0;0m"
+#define RESET       "\x1b[0m"
+
+static char battery_percent[32];
+static char cpu_temp[32];
+static char cpu_base_speed[32];
+static char cpu_avg_speed[32];
+static char datetime[32];
+static char public_ip[MAX_IP_LENGTH];
+static char internal_ip[INET_ADDRSTRLEN];
+char vpn_status[16];
+char mem_info[32];
+char window_id[MAX_OUTPUT_LENGTH];
+double system_load[3];
+unsigned long long free_memory;