Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add netspeed as a C module #513

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions i3status.c
Original file line number Diff line number Diff line change
Expand Up @@ -455,9 +455,19 @@ int main(int argc, char *argv[]) {
CFG_CUSTOM_SEP_BLOCK_WIDTH_OPT,
CFG_END()};

cfg_opt_t netspeed_opts[] = {
CFG_STR("format", "Down: %down/s Up: %up/s", CFGF_NONE),
CFG_CUSTOM_ALIGN_OPT,
CFG_CUSTOM_COLOR_OPTS,
CFG_CUSTOM_MIN_WIDTH_OPT,
CFG_CUSTOM_SEPARATOR_OPT,
CFG_CUSTOM_SEP_BLOCK_WIDTH_OPT,
CFG_END()};

cfg_opt_t opts[] = {
CFG_STR_LIST("order", "{}", CFGF_NONE),
CFG_SEC("general", general_opts, CFGF_NONE),
CFG_SEC("netspeed", netspeed_opts, CFGF_NONE),
CFG_SEC("run_watch", run_watch_opts, CFGF_TITLE | CFGF_MULTI),
CFG_SEC("path_exists", path_exists_opts, CFGF_TITLE | CFGF_MULTI),
CFG_SEC("wireless", wireless_opts, CFGF_TITLE | CFGF_MULTI),
Expand Down Expand Up @@ -729,6 +739,18 @@ int main(int argc, char *argv[]) {
SEC_CLOSE_MAP;
}

CASE_SEC("netspeed") {
SEC_OPEN_MAP("netspeed");
netspeed_ctx_t ctx = {
.json_gen = json_gen,
.buf = buffer,
.buflen = sizeof(buffer),
.format = cfg_getstr(sec, "format"),
};
print_netspeed(&ctx);
SEC_CLOSE_MAP;
}

CASE_SEC_TITLE("battery") {
SEC_OPEN_MAP("battery");
battery_info_ctx_t ctx = {
Expand Down
9 changes: 9 additions & 0 deletions include/i3status.h
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,15 @@ typedef struct {

void print_wireless_info(wireless_info_ctx_t *ctx);

typedef struct {
yajl_gen json_gen;
char *buf;
const size_t buflen;
const char *format;
} netspeed_ctx_t;

void print_netspeed(netspeed_ctx_t *ctx);

typedef struct {
yajl_gen json_gen;
char *buf;
Expand Down
8 changes: 8 additions & 0 deletions man/i3status.man
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,14 @@ network interface found on the system (excluding devices starting with "lo").

*Example format_down*: +E: down+

=== Netspeed

Shows the traffic per seconnd for all physical network interfaces.

*Example order*: +netspeed+

*Example format*: +%down/s↓ %up/s↑+

=== Battery

Gets the status (charging, discharging, unknown, full), percentage, remaining
Expand Down
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ i3status_srcs = [
'src/print_mem.c',
'src/print_path_exists.c',
'src/print_run_watch.c',
'src/print_netspeed.c',
'src/print_time.c',
'src/print_volume.c',
'src/print_wireless_info.c',
Expand Down
118 changes: 118 additions & 0 deletions src/print_netspeed.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>

#include "i3status.h"

#define NET_STAT_BUFFER_SIZE 100
#define RATE_BUFFER_SIZE 10

typedef struct {
unsigned long last_time;
unsigned long last_rx;
unsigned long last_tx;
char last_rate_down[RATE_BUFFER_SIZE];
char last_rate_up[RATE_BUFFER_SIZE];
} NetworkState;

static NetworkState prev_state = {
0,
0,
0,
{'0', '\0'},
{'0', '\0'}};

void readable(unsigned long bytes, char *output) {
unsigned long kb = bytes / 1000; // Convert to kilobytes (base-10)
if (kb < 1000) {
sprintf(output, "%lu KB", kb);
} else {
unsigned long mb_int = kb / 1000;
unsigned long mb_dec = (kb % 1000 * 100) / 1000; // Multiplied by 100, then divided by 1000 for 2 decimal places
sprintf(output, "%lu.%02lu MB", mb_int, mb_dec);
}
}

void print_netspeed(netspeed_ctx_t *ctx) {
char *outwalk = ctx->buf;
const char *selected_format = ctx->format;

unsigned long current_time = (unsigned long)time(NULL);

DIR *d;
d = opendir("/sys/class/net");
if (d == NULL)
return;

int ret;
char path[PATH_MAX], line[PATH_MAX];
char interface[PATH_MAX];
char resolved_path[PATH_MAX];
struct dirent *dir;
unsigned long rx = 0, tx = 0;

const char virtual_iface_path[] = "/sys/devices/virtual/net/";
size_t virtual_iface_len = strlen(virtual_iface_path);

while ((dir = readdir(d)) != NULL) {
if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0)
continue;

snprintf(interface, sizeof(interface), "/sys/class/net/%s", dir->d_name);

if (realpath(interface, resolved_path) == NULL)
continue;

size_t full_path_len = virtual_iface_len + strlen(dir->d_name) + 1;
snprintf(path, full_path_len, "%s%s", virtual_iface_path, dir->d_name);

// check if interface is virtual
if (strcmp(path, resolved_path) == 0)
continue;

ret = snprintf(path, sizeof(path), "/sys/class/net/%s/statistics/rx_bytes", dir->d_name);
if (ret >= sizeof(path) || ret < 0)
continue;

FILE *fp = fopen(path, "r");
fgets(line, NET_STAT_BUFFER_SIZE, fp);
fclose(fp);
rx += strtoul(line, NULL, 10);

ret = snprintf(path, sizeof(path), "/sys/class/net/%s/statistics/tx_bytes", dir->d_name);
if (ret >= sizeof(path) || ret < 0)
continue;

fp = fopen(path, "r");
fgets(line, NET_STAT_BUFFER_SIZE, fp);
fclose(fp);
tx += strtoul(line, NULL, 10);
}
closedir(d);

unsigned long interval = current_time - prev_state.last_time;
if (interval > 0) {
char rate_down[RATE_BUFFER_SIZE], rate_up[RATE_BUFFER_SIZE];

readable((rx - prev_state.last_rx) / interval, rate_down);
readable((tx - prev_state.last_tx) / interval, rate_up);

strncpy(prev_state.last_rate_down, rate_down, RATE_BUFFER_SIZE);
strncpy(prev_state.last_rate_up, rate_up, RATE_BUFFER_SIZE);

prev_state.last_time = current_time;
prev_state.last_rx = rx;
prev_state.last_tx = tx;
}

placeholder_t placeholders[] = {
{.name = "%down", .value = prev_state.last_rate_down},
{.name = "%up", .value = prev_state.last_rate_up}};

const size_t num = sizeof(placeholders) / sizeof(placeholder_t);
char *formatted = format_placeholders(selected_format, &placeholders[0], num);
OUTPUT_FORMATTED;
free(formatted);
OUTPUT_FULL_TEXT(ctx->buf);
}