#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" /* Constants */ #define ELEMENT_SEPERATOR " " #define STATUS_STRBUF_SZ 512 #define ELEMENT_STRBUF_SZ 128 #define MAX(a, b) ((a) > (b) ? (a) : (b)) /* Type definitions */ typedef int (update_func_t)(char*, char*); /* Data structures */ struct element { update_func_t* f; char* a; const struct timespec fire_interval; struct timespec fire_previous; char buf[ELEMENT_STRBUF_SZ]; }; enum battery_status_charge { bat_unknown, bat_not_charging, bat_charging, bat_discharging }; struct battery_status { enum battery_status_charge status; float charge; }; #define ADDRSTRLEN \ (INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ? INET_ADDRSTRLEN : INET6_ADDRSTRLEN struct interface_status { enum { loopback, ethernet, wifi, wan } type; bool up; struct { char ip4[INET_ADDRSTRLEN]; char ip6[INET6_ADDRSTRLEN]; } address; char ssid[IW_ESSID_MAX_SIZE + 1]; char name[IFNAMSIZ + 1]; }; /* Prototypes */ int element_date(char* buf, char* fmt); struct battery_status get_battery_status(const char* buf); int element_battery(char* buf, char* bat); void get_all_bat_status(char* buf); void get_net_link_status(char* buf); int element_wifi(char* buf, char* link_name); static void update_statusbuffer(char* buf); /* Data */ static char* battery_level_icon[] = { "󰂎", /* '\Uf008e' */ "󰁺", /* '\Uf007a' */ "󰁻", /* '\Uf007b' */ "󰁼", /* '\Uf007c' */ "󰁽", /* '\Uf007d' */ "󰁾", /* '\Uf007e' */ "󰁿", /* '\Uf007f' */ "󰂀", /* '\Uf0080' */ "󰂁", /* '\Uf0081' */ "󰂂", /* '\Uf0082' */ "󰁹", /* '\Uf0079' */ }; static struct element statusbar[] = { /* Add status elements here */ /*{update_func_t, {minutes, seconds}, {0}, {0}},*/ [ELEMENT_INVALID] = {NULL, NULL, {0}, {0}, {0}}, #define ELEMENT(name, arg, minutes, seconds) \ [ELEMENT_##name] = {element_##name, arg, {(minutes * 60) + ((int)seconds), (long)(seconds * 1000000) % 1000000}, {0}, {0}}, #include "config.def.h" #undef ELEMENT }; // "fail fast strcmp", neat to find inequalities between strings static int ffast_strcmp(char* a, const char *const b) { int i = 0; while (a[i] == b[i]) { if (a[i] == '\0') break; i++; } return a[i] - b[i]; } /* Functions */ int element_date(char* buf, char* fmt) { //if (!ffast_strcmp(fmt, "NULL")) fmt = "%Y-%m-%d %H:%M"; if (fmt == NULL) fmt = "%Y-%m-%d %H:%M"; time_t now = time(NULL); struct tm *tm = localtime(&now); strftime(buf, ELEMENT_STRBUF_SZ, fmt, tm); return 0; } struct battery_status get_battery_status(const char* buf) { const char path_prefix[] = "/sys/class/power_supply/"; char charge_path[128]; char capacity_path[128]; char charge_str[512]; char capacity_str[512]; int charge = 0; int capacity = 0; FILE* bat_charge; FILE* bat_capacity; memset(charge_path, 0, 128); memset(capacity_path, 0, 128); strcat(charge_path, path_prefix); strcat(charge_path, buf); strcat(charge_path, "/energy_now"); strcat(capacity_path, path_prefix); strcat(capacity_path, buf); strcat(capacity_path, "/energy_full"); bat_charge = fopen(charge_path, "r"); if (!bat_charge) { printf("%d: \"%s\" %s\n", errno, charge_path, strerror(errno)); return (struct battery_status){bat_unknown, -1}; } bat_capacity = fopen(capacity_path, "r"); if (!bat_capacity) { printf("%d: \"%s\" %s\n", errno, capacity_path, strerror(errno)); fclose(bat_charge); return (struct battery_status){bat_unknown, -1}; } fread(charge_str, sizeof(char), 512, bat_charge); fread(capacity_str, sizeof(char), 512, bat_capacity); fclose(bat_charge); fclose(bat_capacity); charge = atoi(charge_str); capacity = atoi(capacity_str); return (struct battery_status){bat_unknown, (float)charge / capacity}; } int element_battery(char* buf, char* bat) { struct battery_status s = get_battery_status(bat); int batlvl = (int)(s.charge * 100.f) / 10; char* batlvl_icon = battery_level_icon[batlvl]; snprintf(buf, ELEMENT_STRBUF_SZ, "%s %.1f%%", batlvl_icon, 100.f * s.charge); return 0; } /* todo, remake this to enumerate all possible batteries */ void get_all_bat_status(char* buf) { struct battery_status s = get_battery_status("BAT0"); struct battery_status t = get_battery_status("BAT1"); s.charge = (s.charge + t.charge) / 2.f; int batlvl = (int)(s.charge * 100.f) / 10; char* batlvl_icon = battery_level_icon[batlvl]; /*snprintf(buf, ELEMENT_STRBUF_SZ, "%.1f%%", 100.f * s.charge);*/ snprintf(buf, ELEMENT_STRBUF_SZ, "%s %.1f%%", batlvl_icon, 100.f * s.charge); } void get_essid(char* if_name, char* dst) { /* Get the SSID */ struct iwreq wreq; const size_t if_namelen = strlen(if_name); const size_t l = if_namelen > IFNAMSIZ ? IFNAMSIZ : if_namelen; int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock == -1) { return; } memset(&wreq, 0, sizeof(struct iwreq)); if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, if_name, l) == -1) { printf("Err: %s\n", strerror(errno)); close(sock); return; } strncpy(wreq.ifr_name, if_name, l); wreq.u.essid.pointer = dst; wreq.u.essid.length = IW_ESSID_MAX_SIZE; // May need to open a new socket, rebind, or something in-between to get both // wireless capabilities and essid?? // Test if we have wireless on this interface //if (ioctl(sock, SIOCGIWNAME, &wreq) < 0) { // // protocol stored in wreq.u.name // close(sock); // return; //} if (ioctl(sock, SIOCGIWESSID, &wreq) < 0) { dst[0] = '\0'; } close(sock); } int element_wifi(char* buf, char* link_name) { struct interface_status if_status; struct ifaddrs* if_addr; struct ifaddrs* ifa; memset(&if_status, 0, sizeof(struct interface_status)); const int nlen = strlen(link_name) > IFNAMSIZ + 1 ? IFNAMSIZ : strlen(link_name); strncpy(if_status.name, link_name, nlen); getifaddrs(&if_addr); for (ifa = if_addr; ifa != NULL; ifa = ifa->ifa_next) { size_t i = 0; const char* name = ifa->ifa_name; if (ffast_strcmp(if_status.name, name)) continue; if (ifa->ifa_flags & IFF_LOWER_UP) { if_status.up = true; if (ifa->ifa_addr != NULL) { const int family = ifa->ifa_addr->sa_family; size_t strsize = 0; char* dst = NULL; if (family == AF_INET) { strsize = INET_ADDRSTRLEN; dst = if_status.address.ip4; } else if (family == AF_INET6) { strsize = INET6_ADDRSTRLEN; dst = if_status.address.ip6; } else if (family == AF_PACKET) { continue; } else /* In this case, there's probably something horribly wrong */ continue; /* Get the IP address */ inet_ntop(family, (void*)&((struct sockaddr_in*)ifa->ifa_addr)->sin_addr, dst, strsize); } } } freeifaddrs(if_addr); // print interface if (!if_status.up) return 0; get_essid(if_status.name, if_status.ssid); strcat(buf, if_status.name); if (if_status.ssid[0] != 0) { strcat(buf, " ("); strcat(buf, if_status.ssid); strcat(buf, ")"); } //if (if_status.address.ip4[0] != 0 || if_status.address.ip6[0] != 0) { // strcat(buf, " "); // if (if_status.address.ip4[0] != 0) { // strcat(buf, "⁴"); // } // if (if_status.address.ip6[0] != 0) { // strcat(buf, "⁶"); // } //} return 0; } /* external commands */ /* I just couldn't be bothered to learn pipewire */ static struct timespec time_add(struct timespec a, struct timespec b) { struct timespec dst; dst.tv_sec = a.tv_sec + b.tv_sec; dst.tv_nsec = a.tv_nsec + b.tv_nsec; /* larger than 1 second in μsec */ if (dst.tv_nsec >= 1000000 * 1000) { dst.tv_nsec -= 1000000 * 1000; dst.tv_sec++; } return dst; } /* Assume that time_t and time64_t are signed (they are on my machine) */ static struct timespec time_sub(struct timespec a, struct timespec b) { struct timespec dst; dst.tv_sec = a.tv_sec - b.tv_sec; dst.tv_nsec = a.tv_nsec - b.tv_nsec; // Subtract a second if nanoseconds are negative if (dst.tv_nsec < 0) { dst.tv_nsec += 1000000 * 1000; dst.tv_sec--; } return dst; } static bool time_lt(struct timespec a, struct timespec b) { if (a.tv_sec == b.tv_sec) { return a.tv_nsec < b.tv_nsec; } return a.tv_sec < b.tv_sec; } struct mq_data { mqd_t mqfd; struct timespec next_update; char* dstbuffer; }; static void update_element_thread(union sigval sv) { struct mq_attr attr; ssize_t nr; struct message_t msg; struct mq_data mq_data = *((struct mq_data*)sv.sival_ptr); char* buf = mq_data.dstbuffer; if (mq_getattr(mq_data.mqfd, &attr) == -1) { fprintf(stderr, "Failed to get mq attributes: %s\n", strerror(errno)); return; } if (attr.mq_msgsize != sizeof(struct message_t)) { fprintf(stderr, "Misconfigured message queue!\n"); return; } // It is apparently the way to ensure continued notifications to register for // further notifications before emptying the queue. // Makes you wonder if you should've used a named pipe + signals instead :) struct sigevent sev = (struct sigevent){ .sigev_notify = SIGEV_THREAD, .sigev_notify_function = update_element_thread, .sigev_notify_attributes = NULL, .sigev_value = sv, }; if (mq_notify(mq_data.mqfd, &sev) == -1) { fprintf(stderr, "Failed to register mq_notify: %s\n", strerror(errno)); } // Empty queue while (mq_receive(mq_data.mqfd, (char*)&msg, sizeof(struct message_t), NULL) != -1) { fprintf(stderr, "%d on %d\n", msg.action, msg.element); if (msg.action != update || msg.element <= ELEMENT_INVALID || msg.element >= ELEMENT_MAX) { fprintf(stderr, "Invalid action/element\n"); continue; } struct element* e = &statusbar[msg.element]; assert(e->f != NULL); struct timespec now; struct timespec next_fire = time_add(e->fire_previous, e->fire_interval); clock_gettime(CLOCK_REALTIME, &now); memset(e->buf, 0, ELEMENT_STRBUF_SZ); e->f(e->buf, e->a); e->fire_previous = now; /* Check if this element needs to be refreshed next, again */ next_fire = time_add(now, e->fire_interval); if (time_lt(next_fire, ((struct mq_data*)(sv.sival_ptr))->next_update)) { ((struct mq_data*)(sv.sival_ptr))->next_update = next_fire; } } update_statusbuffer(mq_data.dstbuffer); // EAGAIN indicates the queue is empty if (errno != EAGAIN) { fprintf(stderr, "Failed to receive mq message: %s\n", strerror(errno)); } } static void update_statusbuffer(char* buf) { static const int num_elems = sizeof(statusbar) / sizeof(statusbar[0]); /* Copy the statusbar buffers into the final buffer */ memset(buf, 0, STATUS_STRBUF_SZ); for (int i = 1; i < num_elems; i++) { if (!strlen(statusbar[i].buf)) continue; strcat(buf, statusbar[i].buf); if (i != num_elems - 1) strcat(buf, ELEMENT_SEPERATOR); } /* strcat(buf, "\0"); */ puts(buf); fflush(stdout); } int main(void) { char buf[STATUS_STRBUF_SZ]; //struct message_t *ipc_message; //// ("/status", max(argv[identifier], XDG_SEAT, DISPLAY, WAYLAND_DISPLAY)) const char* queue_identifier = "/status"; const struct timespec one_minute = {60, 0}; struct timespec now; clock_gettime(CLOCK_REALTIME, &now); struct mq_attr attr = { .mq_flags = 0, // ignored for mq_open .mq_maxmsg = 10, // must be [1;10], default value can be read from /proc/sys/fs/mqueue/msg_default .mq_msgsize = sizeof(struct message_t), .mq_curmsgs = 0, // ignored for mq_open }; //printf("Attempting setting msg-size to %ld\n", sizeof(struct message_t)); struct mq_data mq_data; mq_data.dstbuffer = buf; mq_data.next_update = time_add(now, one_minute);; mq_data.mqfd = mq_open(queue_identifier, O_RDONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NONBLOCK, // 600 S_IRUSR | S_IWUSR, &attr); // O_NONBLOCK if (mq_data.mqfd == -1) { fprintf(stderr, "Failed to open mq: %s\n", strerror(errno)); // Make this check a flag if (errno == EEXIST) { if (mq_unlink(queue_identifier) == -1) { fprintf(stderr, "Failed to unlink mq: %s\n", strerror(errno)); exit(EXIT_FAILURE); } else { fprintf(stderr, "Unlinked %s\n", queue_identifier); // Retry mq_data.mqfd = mq_open(queue_identifier, O_RDONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NONBLOCK, S_IRUSR | S_IWUSR, &attr); if (mq_data.mqfd == -1) { fprintf(stderr, "Failed to open mq: %s\n", strerror(errno)); exit(EXIT_FAILURE); } } } else { exit(EXIT_FAILURE); } } struct sigevent sev = (struct sigevent){ .sigev_notify = SIGEV_THREAD, .sigev_notify_function = update_element_thread, .sigev_notify_attributes = NULL, .sigev_value.sival_ptr = &mq_data, }; // We need to run update_element_thread in the first place to make sure we // empty the queue update_element_thread((union sigval){.sival_ptr = &mq_data}); const int num_elems = sizeof(statusbar) / sizeof(statusbar[0]); while (true) { clock_gettime(CLOCK_REALTIME, &now); unsigned i; /* Stall updating for at most 1 minute */ mq_data.next_update = time_add(now, one_minute); for (i = 1; i < num_elems; i++) { struct element* e = &statusbar[i]; assert(e->f != NULL); // Next time this element updates struct timespec next_fire = time_add(e->fire_previous, e->fire_interval); /* test if this is not to be updated yet */ if (time_lt(now, next_fire)) { /* test if this element is to be updated next */ if (time_lt(next_fire, mq_data.next_update)) { mq_data.next_update = next_fire; } continue; } /* Otherwise update this element */ memset(e->buf, 0, ELEMENT_STRBUF_SZ); e->f(e->buf, e->a); e->fire_previous = now; /* Check if this element needs to be refreshed next, again */ next_fire = time_add(now, e->fire_interval); if (time_lt(next_fire, mq_data.next_update)) { mq_data.next_update = next_fire; } /* printf("[%ld.%ld] %s\n", * e->fire_interval.tv_sec, * e->fire_interval.tv_nsec, * (char*)e->buf); */ } update_statusbuffer(buf); struct timespec sleep_for = time_sub(mq_data.next_update, now); // why the fuck are we adding 500 nanoseconds here //sleep_for = time_add(sleep_for, (struct timespec){0, 500}); /* Replace NULL to get "remaining time", in case we got * interrupted / signaled */ nanosleep(&sleep_for, NULL); } return 0; }