commit 7935044025992a1e987e104df82d74630e87318e Author: Morgan Date: Wed Oct 15 02:04:30 2025 +0900 init diff --git a/README.md b/README.md new file mode 100644 index 0000000..dbdb403 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# power-overlay + +because reading Intel RAPL requires root (and also is NOT safe to make it globally readable) + +- esc/q to exit +- +/- to change font size +- up/down to change opacity +- drag to move + +image + +### compile + +``` +gcc main.c -o power-overlay `sdl2-config --cflags --libs` -lSDL2_gfx -lm +``` + +### if you want to add a desktop file + +power-overlay.sh +```bash +#!/bin/bash +pkexec env DISPLAY=$DISPLAY XAUTHORITY=$XAUTHORITY /home/user/.local/bin/power-overlay +``` + +power-overlay.desktop +``` +[Desktop Entry] +Name=power overlay +Exec=power-overlay +Type=Application +``` diff --git a/main.c b/main.c new file mode 100644 index 0000000..8881047 --- /dev/null +++ b/main.c @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include +#include +#include + +#define ENERGY_PATH "/sys/class/powercap/intel-rapl:0/energy_uj" + +static uint64_t read_energy_uj(const char *path) { + FILE *f = fopen(path, "r"); + if (!f) return UINT64_MAX; + unsigned long long val = 0ULL; + if (fscanf(f, "%llu", &val) != 1) { + fclose(f); + return UINT64_MAX; + } + fclose(f); + return (uint64_t)val; +} + +static double cur_time() { + struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); + return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9; +} + +int running = 1; +double current_watts = 0.0; +SDL_mutex *watt_lock; + +int watt_reader(void *arg) { + const char *path = (const char *)arg; + uint64_t prev_uj = read_energy_uj(path); + double prev_t = cur_time(); + while (running) { + usleep(100000); + uint64_t curr_uj = read_energy_uj(path); + if (curr_uj == UINT64_MAX) continue; + double curr_t = cur_time(); + double dJ = (double)((curr_uj >= prev_uj) ? (curr_uj - prev_uj) : 0ULL) / 1e6; + double dt = curr_t - prev_t; + double watts = (dt > 0) ? dJ / dt : 0.0; + prev_uj = curr_uj; + prev_t = curr_t; + SDL_LockMutex(watt_lock); + current_watts = watts; + SDL_UnlockMutex(watt_lock); + } + return 0; +} + +int main(int argc, char **argv) { + const char *energy_path = (argc > 1) ? argv[1] : ENERGY_PATH; + int refresh_ms = (argc > 2) ? atoi(argv[2]) : 1000; + float window_opacity = 1.0f; + + SDL_SetHint(SDL_HINT_X11_WINDOW_TYPE, "_NET_WM_WINDOW_TYPE_DOCK"); + SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); + + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_EVENTS) != 0) return 1; + + SDL_Window *win = SDL_CreateWindow("overlay", + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + 160, 36, + SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALWAYS_ON_TOP | SDL_WINDOW_ALLOW_HIGHDPI); + + SDL_SetWindowAlwaysOnTop(win, SDL_TRUE); + + SDL_Renderer *ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!ren) ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_SOFTWARE); + + SDL_SetRenderDrawBlendMode(ren, SDL_BLENDMODE_BLEND); + SDL_SetWindowOpacity(win, window_opacity); + + int fontScale = 2; + const int pad = 8; + + watt_lock = SDL_CreateMutex(); + SDL_Thread *reader = SDL_CreateThread(watt_reader, "reader", (void *)energy_path); + + int dragging = 0, drag_off_x = 0, drag_off_y = 0; + char text[64] = "--.-- W"; + uint32_t next_tick = SDL_GetTicks(); + + while (running) { + SDL_Event e; + while (SDL_PollEvent(&e)) { + switch (e.type) { + case SDL_QUIT: running = 0; break; + case SDL_KEYDOWN: + switch (e.key.keysym.sym) { + case SDLK_ESCAPE: case SDLK_q: running = 0; break; + case SDLK_UP: window_opacity += 0.05f; if (window_opacity > 1.0f) window_opacity = 1.0f; SDL_SetWindowOpacity(win, window_opacity); break; + case SDLK_DOWN: window_opacity -= 0.05f; if (window_opacity < 0.2f) window_opacity = 0.2f; SDL_SetWindowOpacity(win, window_opacity); break; + case SDLK_RIGHT: refresh_ms += 100; if (refresh_ms > 5000) refresh_ms = 5000; break; + case SDLK_LEFT: refresh_ms -= 100; if (refresh_ms < 100) refresh_ms = 100; break; + case SDLK_EQUALS: case SDLK_PLUS: fontScale++; if (fontScale>6) fontScale=6; break; + case SDLK_MINUS: if (fontScale>1) fontScale--; break; + } + break; + case SDL_MOUSEBUTTONDOWN: + if (e.button.button == SDL_BUTTON_LEFT) { + dragging = 1; + int wx, wy; SDL_GetWindowPosition(win, &wx, &wy); + int mx, my; SDL_GetGlobalMouseState(&mx, &my); + drag_off_x = mx - wx; drag_off_y = my - wy; + } + break; + case SDL_MOUSEBUTTONUP: + if (e.button.button == SDL_BUTTON_LEFT) dragging = 0; + break; + case SDL_MOUSEMOTION: + if (dragging) { + int mx, my; SDL_GetGlobalMouseState(&mx, &my); + SDL_SetWindowPosition(win, mx - drag_off_x, my - drag_off_y); + } + break; + } + } + uint32_t now = SDL_GetTicks(); + if ((int)(now - next_tick) >= 0) { + SDL_LockMutex(watt_lock); + double watts = current_watts; + SDL_UnlockMutex(watt_lock); + snprintf(text, sizeof(text), "%.2f W", watts); + int baseW = (int)strlen(text) * 8; + int baseH = 8; + int w = baseW * fontScale + pad*2; + int h = baseH * fontScale + pad*2; + SDL_SetWindowSize(win, w, h); + SDL_SetRenderDrawColor(ren, 0, 0, 0, 0); + SDL_RenderClear(ren); + SDL_Texture *tmp = SDL_CreateTexture(ren, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, baseW, baseH); + SDL_SetTextureBlendMode(tmp, SDL_BLENDMODE_BLEND); + SDL_SetRenderTarget(ren, tmp); + SDL_SetRenderDrawColor(ren, 0,0,0,0); SDL_RenderClear(ren); + stringRGBA(ren, 0, 0, text, 255, 255, 255, 255); + SDL_SetRenderTarget(ren, NULL); + SDL_Rect dst = { pad, pad, baseW*fontScale, baseH*fontScale }; + SDL_RenderCopy(ren, tmp, NULL, &dst); + SDL_DestroyTexture(tmp); + SDL_RenderPresent(ren); + next_tick = now + (uint32_t)refresh_ms; + } + SDL_Delay(10); + } + running = 0; + SDL_WaitThread(reader, NULL); + SDL_DestroyMutex(watt_lock); + SDL_DestroyRenderer(ren); + SDL_DestroyWindow(win); + SDL_Quit(); + return 0; +}