This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stdint.h> | |
#include <string.h> | |
#include <errno.h> | |
#include <unistd.h> | |
#include <fcntl.h> | |
#include <sys/ioctl.h> | |
#include <linux/hidraw.h> | |
FILE *proc_stat_fd; | |
FILE *proc_meminfo_fd; | |
FILE *proc_diskstats_fd; | |
void open_proc_files() | |
{ | |
proc_stat_fd = fopen("/proc/stat", "r"); | |
if(!proc_stat_fd) | |
{ | |
perror("/proc/stat"); | |
exit(EXIT_FAILURE); | |
} | |
proc_meminfo_fd = fopen("/proc/meminfo", "r"); | |
if(!proc_meminfo_fd) | |
{ | |
perror("/proc/meminfo"); | |
exit(EXIT_FAILURE); | |
} | |
proc_diskstats_fd = fopen("/proc/diskstats", "r"); | |
if(!proc_diskstats_fd) | |
{ | |
perror("/proc/diskstats"); | |
exit(EXIT_FAILURE); | |
} | |
} | |
char const *hid_filename = NULL; | |
int hid_fd = -1; | |
void open_hid_file() | |
{ | |
if(hid_fd == -1) | |
{ | |
hid_fd = open(hid_filename, O_RDWR); | |
if(hid_fd == -1) | |
{ | |
perror("open"); | |
usleep(1000000); | |
} | |
} | |
} | |
void close_hid_file() | |
{ | |
if(hid_fd != -1) | |
close(hid_fd); | |
hid_fd = -1; | |
} | |
typedef struct { | |
uint8_t r, g, b; | |
} color; | |
typedef struct { | |
uint8_t key; | |
color color; | |
} key_status; | |
typedef struct { | |
uint8_t num; | |
uint8_t request[4]; | |
} report_hdr; | |
void set_key_statuses(size_t len, key_status *sts) | |
{ | |
open_hid_file(); | |
struct { report_hdr hdr; key_status sts[0x100]; } report; | |
report.hdr.num = 0x00; | |
report.hdr.request[0] = 0x0C; | |
report.hdr.request[1] = 0x00; | |
report.hdr.request[2] = 0x6E; | |
report.hdr.request[3] = 0x00; | |
memcpy(report.sts, sts, sizeof(*sts) * len); | |
if(hid_fd != -1) | |
{ | |
size_t l = sizeof report.hdr + sizeof(*sts) * len; | |
errno = 0; | |
if(ioctl(hid_fd, HIDIOCSFEATURE(l), &report) != l) | |
{ | |
perror("ioctl(HIDIOCSFEATURE)"); | |
close_hid_file(); | |
usleep(1000000); | |
} | |
} | |
} | |
typedef struct { | |
int enabled; | |
unsigned long user, nice, system, idle, iowait, irq, softirq; | |
} core_stats; | |
core_stats *get_core_stats(int *num_cores) | |
{ | |
core_stats *stats = NULL; | |
size_t num_stats = 0; | |
char *line = NULL; | |
size_t line_sz = 0; | |
rewind(proc_stat_fd); | |
getline(&line, &line_sz, proc_stat_fd); | |
while(1) | |
{ | |
if(getline(&line, &line_sz, proc_stat_fd) <= 0) | |
break; | |
core_stats st; | |
int num_core; | |
if(8 > sscanf(line, "cpu%d%lu%lu%lu%lu%lu%lu%lu", &num_core, | |
&st.user, &st.nice, &st.system, &st.idle, &st.iowait, &st.irq, &st.softirq)) | |
break; | |
st.enabled = 1; | |
if(num_core >= num_stats) | |
{ | |
stats = realloc(stats, sizeof *stats * (num_core + 1)); | |
memset(stats + num_stats, 0, sizeof *stats * (num_core + 1 - num_stats)); | |
num_stats = num_core + 1; | |
} | |
memcpy(&stats[num_core], &st, sizeof st); | |
} | |
free(line); | |
*num_cores = num_stats; | |
return stats; | |
} | |
typedef struct { | |
unsigned long total, free, available, buffers, cached, swaptotal, swapfree; | |
} mem_stats; | |
mem_stats get_mem_stats() | |
{ | |
mem_stats st; | |
memset(&st, 0, sizeof st); | |
char *line = NULL; | |
size_t line_sz = 0; | |
rewind(proc_meminfo_fd); | |
while(1) | |
{ | |
if(getline(&line, &line_sz, proc_meminfo_fd) <= 0) | |
break; | |
sscanf(line, "MemTotal:%lu", &st.total) || | |
sscanf(line, "MemFree:%lu", &st.free) || | |
sscanf(line, "MemAvailable:%lu", &st.available) || | |
sscanf(line, "Buffers:%lu", &st.buffers) || | |
sscanf(line, "Cached:%lu", &st.cached) || | |
sscanf(line, "SwapTotal:%lu", &st.swaptotal) || | |
sscanf(line, "SwapFree:%lu", &st.swapfree); | |
} | |
free(line); | |
return st; | |
} | |
typedef struct { | |
char const *dev_name; | |
int enabled; | |
unsigned long read_ms, wrote_ms; | |
} disk_stats; | |
void get_disk_stats(disk_stats *sts, size_t len) | |
{ | |
char *line = NULL; | |
size_t line_sz = 0; | |
rewind(proc_diskstats_fd); | |
for(int i = 0; i < len; i++) | |
sts[i].enabled = 0; | |
while(1) | |
{ | |
if(getline(&line, &line_sz, proc_diskstats_fd) <= 0) | |
break; | |
unsigned long read_ms, wrote_ms; | |
char *name = NULL; | |
if(3 > sscanf(line, "%*d%*d%ms%*lu%*lu%*lu%lu%*lu%*lu%*lu%lu", | |
&name, &read_ms, &wrote_ms)) | |
{ | |
free(name); | |
break; | |
} | |
for(int i = 0; i < len; i++) | |
if(!strcmp(name, sts[i].dev_name)) | |
{ | |
sts[i].enabled = 1; | |
sts[i].read_ms = read_ms; | |
sts[i].wrote_ms = wrote_ms; | |
} | |
free(name); | |
} | |
free(line); | |
} | |
int num_cores = 0; | |
double *ema_loads; | |
core_stats *last_cores; | |
void update_ema_loads() | |
{ | |
int new_cores = 0; | |
core_stats *stats = get_core_stats(&new_cores); | |
if(new_cores > num_cores) | |
{ | |
ema_loads = realloc(ema_loads, sizeof *ema_loads * new_cores); | |
memset(ema_loads + num_cores, 0, sizeof *ema_loads * (new_cores - num_cores)); | |
last_cores = realloc(last_cores, sizeof *last_cores * new_cores); | |
memcpy(last_cores + num_cores, stats + num_cores, sizeof *stats * (new_cores - num_cores)); | |
num_cores = new_cores; | |
} | |
for(int i = 0; i < new_cores; i++) | |
{ | |
if(stats[i].enabled) | |
{ | |
unsigned long duser = stats[i].user - last_cores[i].user; | |
unsigned long dnice = stats[i].nice - last_cores[i].nice; | |
unsigned long dsystem = stats[i].system - last_cores[i].system; | |
unsigned long didle = stats[i].idle - last_cores[i].idle; | |
unsigned long diowait = stats[i].iowait - last_cores[i].iowait; | |
unsigned long dirq = stats[i].irq - last_cores[i].irq; | |
unsigned long dsoftirq = stats[i].softirq - last_cores[i].softirq; | |
double load = (duser + dnice + dsystem + dirq + dsoftirq) / (double)(duser + dnice + dsystem + didle + diowait + dirq + dsoftirq); | |
if(!(load == load)) load = 0.0; | |
ema_loads[i] = load * 0.5 + ema_loads[i] * 0.5; | |
} | |
else | |
ema_loads[i] = 0; | |
} | |
for(int i = new_cores; i < num_cores; i++) | |
last_cores[i].enabled = 0; | |
memcpy(last_cores, stats, sizeof *stats * new_cores); | |
free(stats); | |
} | |
double mem_used, mem_buffers, mem_cached, mem_swapped; | |
void update_mem() | |
{ | |
mem_stats st = get_mem_stats(); | |
mem_used = (st.total - st.free - st.cached - st.buffers) / (double)st.total; | |
mem_buffers = st.buffers / (double)st.total; | |
mem_cached = st.cached / (double)st.total; | |
mem_swapped = (st.swaptotal - st.swapfree) / (double)st.swaptotal; | |
} | |
int num_disks = 0; | |
double *ema_reads; | |
double *ema_writes; | |
disk_stats *last_disks; | |
void update_ema_io(char const **disks) | |
{ | |
size_t len = 0; | |
while(disks[len]) | |
len++; | |
disk_stats *stats = calloc(sizeof *stats, len); | |
for(int i = 0; i < len; i++) | |
stats[i].dev_name = disks[i]; | |
get_disk_stats(stats, len); | |
if(len != num_disks) | |
{ | |
ema_reads = realloc(ema_reads, sizeof *ema_reads * len); | |
ema_writes = realloc(ema_writes, sizeof *ema_writes * len); | |
last_disks = realloc(last_disks, sizeof *last_disks * len); | |
if(len > num_disks) | |
{ | |
memset(ema_reads + num_disks, 0, sizeof *ema_reads * (len - num_disks)); | |
memset(ema_writes + num_disks, 0, sizeof *ema_writes * (len - num_disks)); | |
memset(last_disks + num_disks, 0, sizeof *last_disks * (len - num_disks)); | |
} | |
num_disks = len; | |
} | |
for(int i = 0; i < num_disks; i++) | |
{ | |
if(stats[i].enabled) | |
{ | |
unsigned long dread = stats[i].read_ms - last_disks[i].read_ms; | |
unsigned long dwrote = stats[i].wrote_ms - last_disks[i].wrote_ms; | |
double reads = dread / 1000.0; | |
if(reads > 1.0) reads = 1.0; | |
double writes = dwrote / 1000.0; | |
if(writes > 1.0) writes = 1.0; | |
ema_reads[i] = reads * 0.5 + ema_reads[i] * 0.5; | |
ema_writes[i] = writes * 0.5 + ema_writes[i] * 0.5; | |
} | |
else | |
{ | |
ema_reads[i] = 0; | |
ema_writes[i] = 0; | |
} | |
} | |
memcpy(last_disks, stats, sizeof *stats * num_disks); | |
free(stats); | |
} | |
color mkcolor(int r, int g, int b) | |
{ | |
color clr; | |
clr.r = r < 0 ? 0 : r > 0xFF ? 0xFF : r; | |
clr.g = g < 0 ? 0 : g > 0xFF ? 0xFF : g; | |
clr.b = b < 0 ? 0 : b > 0xFF ? 0xFF : b; | |
return clr; | |
} | |
color load_to_color(double l) | |
{ | |
if(l <= 0.5) | |
return mkcolor(l * 511, 255, 0); | |
else | |
return mkcolor(244, (1 - l) * 511, 0); | |
} | |
color io_to_color(double r, double w) | |
{ | |
return mkcolor(w * 255, 0, r * 255); | |
} | |
void mix_color(color *tgt, color src, double alpha) | |
{ | |
if(alpha < 0) alpha = 0; | |
else if(alpha > 1) alpha = 1; | |
tgt->r += src.r * alpha; | |
tgt->g += src.g * alpha; | |
tgt->b += src.b * alpha; | |
} | |
void color_line(key_status *sts, size_t len, double from, double to, color clr) | |
{ | |
from *= len; | |
to *= len; | |
for(size_t i = 0; i < len; i++) | |
{ | |
if(i >= from && i + 1 <= to) | |
sts[i].color = clr; | |
else if(i < from && i + 1 >= from && i + 1 <= to) | |
mix_color(&sts[i].color, clr, i + 1 - from); | |
else if(i >= from && i <= to && i + 1 > to) | |
mix_color(&sts[i].color, clr, to - i); | |
else if(i < from && i + 1 >= from && i <= to && i + 1 > to) | |
mix_color(&sts[i].color, clr, to - from); | |
} | |
} | |
/* | |
Esc(0x29) F1(0x3A) F2(0x3B) F3(0x3C) F4(0x3D) F5(0x3E) F6(0x3F) F7(0x40) F8(0x41) F9(0x42) F10(0x43) F11(0x44) F12(0x45) PrtScr(0x46) ScrLk(0x47) Pause(0x48) Ins(0x49) Del(0x4C) PgUp(0x4B) PgDn(0x4E) | |
`(0x35) 1(0x1E) 2(0x1F) 3(0x20) 4(0x21) 5(0x22) 6(0x23) 7(0x24) 8(0x25) 9(0x26) 0(0x27) -(0x2D) =(0x2E) BkSp(0x2A) NumLk(0x53) Num/(0x54) Num*(0x55) Num-(0x56) | |
Tab(0x2B) Q(0x14) W(0x1A) E(0x08) R(0x15) T(0x17) Y(0x1C) U(0x18) I(0x0C) O(0x12) P(0x13) [(0x2F) ](0x30) \(0x31) Num7(0x5F) Num8(0x60) Num9(0x61) Num+(0x57) | |
Caps(0x39) A(0x05) S(0x16) D(0x07) F(0x09) G(0x0A) H(0x0B) J(0x0D) K(0x0E) L(0x0F) ;(0x33) '(0x34) Enter(0x28) Num4(0x5C) Num5(0x5D) Num6(0x5E) | |
LShift(0xE1) Z(0x1D) X(0x1B) C(0x06) V(0x19) B(0x05) N(0x11) M(0x10) ,(0x36) .(0x37) /(0x38) RShift(0xE5) Up(0x52) Num1(0x59) Num2(0x5A) Num3(0x5B) NumEnter(0x58) | |
LCtrl(0xE0) Fn(0xF0) LAlt(0xE2) Space(0x2C) <(0x64) RAlt(0xE6) Win(0xE3) RCtrl(0xE4) Left(0x50) Down(0x51) Right(0x4F) Num0(0x62) Num.(0x63) | |
*/ | |
int cpu_keys[] = {0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4C}; | |
int num_cpu_keys = sizeof cpu_keys / sizeof *cpu_keys; | |
int memory_keys[] = {0x35, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x2D, 0x2E, 0x2A, 0x53, 0x54, 0x55, 0x56}; | |
int num_memory_keys = sizeof memory_keys / sizeof *memory_keys; | |
int swap_keys[] = {0x2B, 0x14, 0x1A, 0x08, 0x15, 0x17, 0x1C, 0x18, 0x0C, 0x12, 0x13, 0x2F, 0x30, 0x31, 0x5F, 0x60, 0x61, 0x57}; | |
int num_swap_keys = sizeof swap_keys / sizeof *swap_keys; | |
int disk_keys[] = {0x4B, 0x4E}; | |
int num_disk_keys = sizeof disk_keys / sizeof *disk_keys; | |
char const *disks[] = {"nvme0n1", "sda", NULL}; | |
int main(int argc, char *argv[]) | |
{ | |
hid_filename = argc > 1 ? argv[1] : "/dev/hidraw0"; | |
open_proc_files(); | |
while(1) | |
{ | |
update_ema_loads(); | |
update_mem(); | |
update_ema_io(disks); | |
key_status sts[num_cpu_keys + num_memory_keys + num_swap_keys + num_disk_keys]; | |
key_status *cpu_sts = sts; | |
key_status *mem_sts = sts + num_cpu_keys; | |
key_status *swap_sts = sts + num_cpu_keys + num_memory_keys; | |
key_status *disk_sts = sts + num_cpu_keys + num_memory_keys + num_swap_keys; | |
for(int i = 0; i < num_cpu_keys; i++) | |
{ | |
cpu_sts[i].key = cpu_keys[i]; | |
cpu_sts[i].color = (i < num_cores && last_cores[i].enabled) | |
? load_to_color(ema_loads[i]) | |
: mkcolor(0, 0, 0); | |
} | |
for(int i = 0; i < num_memory_keys; i++) | |
{ | |
mem_sts[i].key = memory_keys[i]; | |
mem_sts[i].color = mkcolor(0, 0, 0); | |
} | |
color_line(mem_sts, num_memory_keys, 0.0, mem_used, mkcolor(0, 255, 0)); | |
color_line(mem_sts, num_memory_keys, mem_used, mem_used + mem_buffers, mkcolor(0, 0, 255)); | |
color_line(mem_sts, num_memory_keys, mem_used + mem_buffers, mem_used + mem_buffers + mem_cached, mkcolor(255, 127, 0)); | |
for(int i = 0; i < num_swap_keys; i++) | |
{ | |
swap_sts[i].key = swap_keys[i]; | |
swap_sts[i].color = mkcolor(0, 0, 0); | |
} | |
color_line(swap_sts, num_swap_keys, 0.0, mem_swapped, mkcolor(255, 0, 0)); | |
for(int i = 0; i < num_disk_keys; i++) | |
{ | |
disk_sts[i].key = disk_keys[i]; | |
disk_sts[i].color = (i < num_disks && last_disks[i].enabled) | |
? io_to_color(ema_reads[i], ema_writes[i]) | |
: mkcolor(0, 0, 0); | |
} | |
set_key_statuses(num_cpu_keys + num_memory_keys + num_swap_keys + num_disk_keys, sts); | |
usleep(100000); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment