#include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Required for timercmp */ #define __USE_MISC #include #include #include /* Constants */ #define ELEMENT_SEPERATOR " " #define STATUS_STRBUF_SZ 512 #define ELEMENT_STRBUF_SZ 128 /* Type definitions */ typedef void (*element_function)(char*); /* Data structures */ struct element { const element_function f; 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; struct { char ip4[INET_ADDRSTRLEN]; char ip6[INET6_ADDRSTRLEN]; } address; char ssid[IW_ESSID_MAX_SIZE + 1]; char name[IFNAMSIZ]; }; /* Prototypes */ void date(char* buf); struct battery_status get_battery_status(const char* buf); void get_battery1_status(char* buf); void get_battery0_status(char* buf); void get_all_bat_status(char* buf); void get_net_link_status(char* buf); /* Data */ static char* battery_level_icon[] = { "", /* "\uf579" */ "", /* "\uf57a" */ "", /* "\uf57b" */ "", /* "\uf57c" */ "", /* "\uf57d" */ "", /* "\uf57e" */ "", /* "\uf57f" */ "", /* "\uf580" */ "", /* "\uf581" */ "", /* "\uf578" */ "", /* "\uf578" */ }; // Converts milliseconds to nanoseconds #define MSEC(msec) (msec * 1000 * 1000) static struct element statusbar[] = { /* Add status elements here */ /*{element_function, {seconds, nanoseconds}, {0}, {0}},*/ {get_net_link_status, {15, 0}, {0}, {0}}, {get_battery0_status, {60, 0}, {0}, {0}}, {get_battery1_status, {60, 0}, {0}, {0}}, { get_all_bat_status, {60, 0}, {0}, {0}}, { date, {7, MSEC(500)}, {0}, {0}}, }; struct interface_status interfaces[8]; // surely noone has more than 8 interfaces. /* Functions */ void date(char* buf) { time_t now = time(NULL); struct tm tm; tm = *localtime(&now); strftime(buf, ELEMENT_STRBUF_SZ, "%Y-%m-%d %H:%M", &tm); } 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"); bat_capacity = fopen(capacity_path, "r"); if (!bat_charge) { printf("%d: \"%s\" %s\n", errno, charge_path, strerror(errno)); return (struct battery_status){bat_unknown, -1}; } if (!bat_capacity) { printf("%d: \"%s\" %s\n", errno, capacity_path, strerror(errno)); 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}; } void get_battery1_status(char* buf) { struct battery_status s = get_battery_status("BAT1"); 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); } void get_battery0_status(char* buf) { struct battery_status s = get_battery_status("BAT0"); 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); } /* 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; size_t l = strlen(if_name); 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) { close(sock); return; } strncpy(wreq.ifr_name, if_name, l); wreq.u.essid.pointer = dst; wreq.u.essid.length = IW_ESSID_MAX_SIZE; //printf("#%s#\n", dst); // Test if we have wireless on this interface if (ioctl(sock, SIOCGIWNAME, &wreq) != -1) { // protocol stored in wreq.u.name close(sock); return; } if (ioctl(sock, SIOCGIWESSID, &wreq) < 0) { dst[0] = '\0'; } close(sock); } void get_net_link_status(char* buf) { struct ifaddrs* if_addr; size_t n = 0; size_t c = 0; getifaddrs(&if_addr); while (c < 8 && strlen(interfaces[c].name) > 0) { c++; } for (struct ifaddrs* ifa = if_addr; ifa != NULL; ifa = ifa->ifa_next) { size_t i = 0; const char* name = ifa->ifa_name; while (i < c && strcmp(name, interfaces[i].name)) i++; //if (i != c) { //printf("found %s in %d\n", name, i); //} /* We're not interested in interfaces that are not "up" */ if (!(ifa->ifa_flags & IFF_LOWER_UP)) { /* Remove it from the list */ if (i < c) { /* Don't memmove if last element */ if (c - i > 1) { memmove(&interfaces[i], &interfaces[i + 1], sizeof(struct interface_status) * (c - i)); } /* "wipe" last element */ memset(&interfaces[c - 1], 0, sizeof(struct interface_status)); c--; } continue; } /* Ignore loopback interface flag */ if (ifa->ifa_flags & IFF_LOOPBACK) continue; ///* Ignore interfaces without an address */ // if (ifa->ifa_addr == NULL) // continue; ///* Ignore everythings not an IPv4 link */ ///* (AF_PACKET might be usefull if you want to display tx) */ // if (ifa->ifa_addr->sa_family != AF_INET) // continue; /* Find the interface in status, if it exists */ struct interface_status s; // = &status[c]; memset(&s, 0, sizeof(struct interface_status)); s = interfaces[i]; 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 = s.address.ip4; } else if (family == AF_INET6) { strsize = INET6_ADDRSTRLEN; dst = s.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); } // Get the ESSID get_essid(s.name, s.ssid); /* if we got nothing out of it, don't increment */ if (ifa->ifa_addr == NULL && strlen(s.ssid) == 0) continue; /* If the interface didn't exist in interfaces before, we should add * the name:) */ if (i == c) { strncpy(s.name, name, IFNAMSIZ); c++; } memcpy(&interfaces[i], &s, sizeof(struct interface_status)); } freeifaddrs(if_addr); memset(buf, 0, ELEMENT_STRBUF_SZ); for (size_t i = 0; i < c; i++) { // int i = 0; { struct interface_status* s = &interfaces[i]; // Write the status string to the output buffer const size_t namelen = strlen(s->name); strncat(buf + n, s->name, namelen); n += namelen; size_t ssid_len = strlen(s->ssid); if (ssid_len > 0) { buf[n++] = ' '; buf[n++] = '('; strncpy(buf + n, s->ssid, ssid_len); n += ssid_len; buf[n++] = ')'; } //{ // Add the addresses // const size_t addr4_len = strlen(s->address.ip4); // if (addr4_len > 0) { // buf[n++] = ' '; // strncat(buf + n, s->address.ip4, addr4_len); // n += addr4_len; // } // const size_t addr6_len = strlen(s->address.ip6); // if (addr6_len > 0) { // buf[n++] = ' '; // strncat(buf + n, s->address.ip6, addr6_len); // n += addr6_len; // } //} if (i != c - 1) { strncat(buf + n, " ", 3); n += 2; } } } 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) { 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; 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; } int main(void) { const int num_elems = sizeof(statusbar) / sizeof(statusbar[0]); struct timespec now; const struct timespec one_minute = {60, 0}; memset(interfaces, 0, sizeof(struct interface_status[8])); while (true) { clock_gettime(CLOCK_REALTIME, &now); unsigned i; /* Stall updating for at most 1 minute */ struct timespec next_update = {0}; now = time_add(one_minute, next_update); for (i = 0; i < num_elems; i++) { struct timespec next_fire = {0}; statusbar[i].fire_previous = time_add(statusbar[i].fire_interval, next_fire); if (time_lt(now, next_fire)) { /* Check if this is the next to-be-updated element */ if (time_lt(next_fire, next_update)) { next_update = next_fire; } continue; } memset(statusbar[i].buf, 0, ELEMENT_STRBUF_SZ); statusbar[i].f(statusbar[i].buf); statusbar[i].fire_previous = now; /* Check if this element needs to be refreshed next, again */ statusbar[i].fire_previous = time_add(statusbar[i].fire_interval, next_fire); if (time_lt(next_fire, next_update)) { next_update = next_fire; } /* printf("[%ld.%ld] %s\n", * statusbar[i].fire_interval.tv_sec, * statusbar[i].fire_interval.tv_usec, * (char*)statusbar[i].buf); */ } char buf[STATUS_STRBUF_SZ]; memset(buf, 0, STATUS_STRBUF_SZ); for (i = 0; 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); next_update = time_sub(now, next_update); /* Replace NULL to get "remaining time", in case we got * interrupted / signaled */ nanosleep(&next_update, NULL); } return 0; }