#include #include #include #include #include #include #include #include "window.h" #include "log.h" #include "draw.h" #include "connect.h" #include "state.h" #include "dwm.h" #define EVEN(n) ((int)(n) - ((int)(n) % 2 != 0)) static void log_handler(const char *log_domain, GLogLevelFlags level, const char *message, gpointer log_level) { GLogLevelFlags message_level = level & G_LOG_LEVEL_MASK; if ((GLogLevelFlags)log_level < message_level) return; if (message_level <= G_LOG_LEVEL_WARNING) g_printerr("%s\n", message); else g_print("%s\n", message); } static gboolean mainloop_quit(gpointer data) { g_main_loop_quit(data); return G_SOURCE_CONTINUE; } // Taken from slstatus // XXX: Change with something more robust char *cpu_percentage(void) { static long double a[7]; long double b[7], sum; memcpy(b, a, sizeof(b)); FILE *stat = fopen("/proc/stat", "rb"); g_assert_nonnull(stat); /* cpu user nice system idle iowait irq softirq */ g_assert(7 == fscanf(stat, "%*s %Lf %Lf %Lf %Lf %Lf %Lf %Lf", &a[0], &a[1], &a[2], &a[3], &a[4], &a[5], &a[6])); fclose(stat); if (b[0] == 0) return NULL; sum = (b[0] + b[1] + b[2] + b[3] + b[4] + b[5] + b[6]) - (a[0] + a[1] + a[2] + a[3] + a[4] + a[5] + a[6]); if (sum == 0) return NULL; return g_strdup_printf(" %d%%", (int)(100 * ((b[0] + b[1] + b[2] + b[5] + b[6]) - (a[0] + a[1] + a[2] + a[5] + a[6])) / sum)); } static gboolean cpu_update(Button *btn) { char *perc = cpu_percentage(); // Don't update on error if (perc == NULL) return G_SOURCE_CONTINUE; ButtonSimple *sbtn = CAST(btn, ButtonSimple); bool update = g_strcmp0(perc, sbtn->text) != 0; button_simple_set_text(btn, perc, sbtn->text_color); if (update) state_request_redraw(sbtn->data, false); return G_SOURCE_CONTINUE; } static gboolean ram_update(Button *btn) { FILE *meminfo = fopen("/proc/meminfo", "rb"); g_assert_nonnull(meminfo); uintmax_t total, unused, buffers, cached; g_assert(5 == fscanf(meminfo, "MemTotal: %ju kB\n" "MemFree: %ju kB\n" "MemAvailable: %ju kB\n" "Buffers: %ju kB\n" "Cached: %ju kB\n", &total, &unused, &buffers, &buffers, &cached)); int usage = 100 * (total - unused - buffers - cached) / total; fclose(meminfo); ButtonSimple *sbtn = CAST(btn, ButtonSimple); char *ram = g_strdup_printf(" %d%%", usage); bool update = g_strcmp0(ram, sbtn->text) != 0; button_simple_set_text(btn, ram, sbtn->text_color); if (update) state_request_redraw(sbtn->data, false); return G_SOURCE_CONTINUE; } static gboolean temp_update(Button *btn) { FILE *thermal = fopen("/sys/class/thermal/thermal_zone2/temp", "rb"); g_assert_nonnull(thermal); uintmax_t value; g_assert(1 == fscanf(thermal, "%ju\n", &value)); fclose(thermal); ButtonSimple *sbtn = CAST(btn, ButtonSimple); char *temp = g_strdup_printf(" %d °C", (int)(value / 1000)); bool update = g_strcmp0(temp, sbtn->text) != 0; button_simple_set_text(btn, temp, sbtn->text_color); if (update) state_request_redraw(sbtn->data, false); return G_SOURCE_CONTINUE; } static gboolean disk_update(Button *btn) { struct statvfs buffer; g_assert(statvfs("/", &buffer) == 0); const double used = 1.0 - ((double)buffer.f_bavail / (double)buffer.f_blocks); ButtonSimple *sbtn = CAST(btn, ButtonSimple); char *disk = g_strdup_printf(" %d%%", (int)round(used * 100.0)); bool update = g_strcmp0(disk, sbtn->text) != 0; button_simple_set_text(btn, disk, sbtn->text_color); if (update) state_request_redraw(sbtn->data, false); return G_SOURCE_CONTINUE; } static gboolean date_update(Button *btn) { GDateTime *dt = g_date_time_new_now_local(); char *text = g_date_time_format(dt, "%A %d %H:%M"); g_date_time_unref(dt); g_assert_nonnull(text); button_simple_set_text(btn, text, CAST(btn, ButtonSimple)->text_color); struct { State *state; timer_t timer; } *date_ctx = CAST(btn, ButtonSimple)->data; log_debug("Updated date and time"); state_request_redraw(date_ctx->state, false); struct timespec current; clock_gettime(CLOCK_REALTIME, ¤t); struct itimerspec its = { 0 }; its.it_value.tv_sec = 60 - (current.tv_sec % 60); timer_settime(date_ctx->timer, 0, &its, NULL); return G_SOURCE_CONTINUE; } static void show_action(Button *btn) { log_info("Called action: %s", button_simple_get_text(btn)); } static void quit_action(Button *btn) { log_info("Quit button pressed"); g_main_loop_quit(CAST(btn, ButtonSimple)->data); } static void menu_action(Button *btn) { struct { State *state; ButtonGroup *group; char *strings[2]; GList *toggled; bool closed; } *menu_ctx = CAST(btn, ButtonSimple)->data; // NOTE: Clear the previous animation, if any g_list_free(menu_ctx->group->children); g_clear_pointer(&menu_ctx->group->btn.anim, animation_destroy); menu_ctx->group->children = g_list_copy(menu_ctx->toggled); char *text = g_strdup(menu_ctx->strings[menu_ctx->closed]); button_simple_set_text(btn, text, CAST(btn, ButtonSimple)->text_color); Animation *anim = menu_ctx->closed ? animation_group_grow_create(MILLIS(600)) : animation_group_shrink_create(MILLIS(600)); g_assert(button_set_animation((gpointer)menu_ctx->group, anim)); state_request_animation(menu_ctx->state); log_debug("%s menu", menu_ctx->closed ? "Opened" : "Closed"); menu_ctx->closed = !menu_ctx->closed; } static void register_buttons(State *state, Color color, Color text_color, Color line_color, int line_w) { // Precompute max button size int text_w; draw_compute_text_size(state->draw, " 100%", &text_w, NULL); int min_w = text_w + line_w + state->draw->height; // Cpu usage button Button *cpu_btn = button_simple_create(PANGO_ALIGN_RIGHT, color); button_simple_set_text(cpu_btn, g_strdup(" 0%"), text_color); button_simple_set_action(cpu_btn, show_action, state); button_set_line(cpu_btn, line_color, line_w); button_set_width(cpu_btn, 0, min_w); state_add_button(state, cpu_btn); cpu_update(cpu_btn); g_timeout_add(MILLIS(1), G_SOURCE_FUNC(cpu_update), cpu_btn); // Temperature button Button *temp_btn = button_simple_create(PANGO_ALIGN_RIGHT, color); button_simple_set_text(temp_btn, NULL, text_color); button_simple_set_action(temp_btn, show_action, state); button_set_line(temp_btn, line_color, line_w); button_set_width(temp_btn, 0, min_w); state_add_button(state, temp_btn); temp_update(temp_btn); g_timeout_add(MILLIS(20), G_SOURCE_FUNC(temp_update), temp_btn); // Ram usage button Button *ram_btn = button_simple_create(PANGO_ALIGN_RIGHT, color); button_simple_set_text(ram_btn, NULL, text_color); button_simple_set_action(ram_btn, show_action, state); button_set_line(ram_btn, line_color, line_w); button_set_width(ram_btn, 0, min_w); state_add_button(state, ram_btn); ram_update(ram_btn); g_timeout_add(MILLIS(10), G_SOURCE_FUNC(ram_update), ram_btn); // Disk usage button Button *disk_btn = button_simple_create(PANGO_ALIGN_RIGHT, color); button_simple_set_text(disk_btn, NULL, text_color); button_simple_set_action(disk_btn, show_action, state); button_set_line(disk_btn, line_color, line_w); button_set_width(disk_btn, 0, min_w); state_add_button(state, disk_btn); disk_update(disk_btn); g_timeout_add(MILLIS(60), G_SOURCE_FUNC(disk_update), disk_btn); } int main(int argc, char **argv) { setlocale(LC_CTYPE, ""); g_log_set_default_handler(log_handler, (gpointer)G_LOG_LEVEL_DEBUG); GMainLoop *mainloop = g_main_loop_new(NULL, FALSE); Connection *con = connect_create(); Window *win = window_create(con); int screen_width = con->screen_size->width; int screen_height = con->screen_size->height; double scale = window_get_scale(win); int height = EVEN(round(screen_height * 0.021)); int x_padding = EVEN(round(screen_width * 0.005)); int y_padding = EVEN(round(screen_height * 0.004)); log_debug("Calculated dimensions [height=%d, x_pad=%d, y_pad=%d]", height, x_padding, y_padding); Color background_all = { 0.3, 0.3, 0.3, 1 }; PangoContext *context = pango_cairo_create_context(window_get_context(win)); Drawer *draw = draw_create(); draw_set_background(draw, background_all); draw_set_separator(draw, 10); draw_set_font(draw, "Hack 13 Bold"); draw_set_context(draw, context); draw_set_size(draw, height, x_padding, x_padding, y_padding, scale); State *state = state_create(win, draw); Color color = { 0.4, 0.4, 0.4, 1 }; Color purple = { 0.502, 0.168, 0.886, 1 }; Color line_color = { 0.8, 0.8, 0.8, 1 }; Color text_color = { 0.9, 0.9, 0.9, 1 }; // Dwm tags DwmIpc *dwm = dwm_create(state, "/tmp/dwm.sock"); dwm_register_tags(dwm, color, purple, text_color); int line_w = 0; register_buttons(state, color, text_color, line_color, line_w); // Buttons with special handling struct { State *state; timer_t timer; } date_ctx = { state, 0 }; int text_w; draw_compute_text_size(state->draw, "Wednesday 31 12:34", &text_w, NULL); int min_w = text_w + line_w + state->draw->height; // Date & time button Button *date_btn = button_simple_create(PANGO_ALIGN_CENTER, color); button_simple_set_text(date_btn, NULL, text_color); button_simple_set_action(date_btn, NULL, &date_ctx); button_set_width(date_btn, 0, min_w); state_add_button(state, date_btn); struct sigevent sev = { 0 }; sev.sigev_notify = SIGEV_SIGNAL; sev.sigev_signo = SIGUSR1; g_assert(timer_create(CLOCK_REALTIME, &sev, &date_ctx.timer) == 0); date_update(date_btn); // Quit button Button *q_btn = button_simple_create(PANGO_ALIGN_RIGHT, purple); button_simple_set_text(q_btn, g_strdup(""), text_color); button_simple_set_action(q_btn, quit_action, mainloop); state_add_button(state, q_btn); // Menu button(s) Color grey = { 0.5, 0.5, 0.5, 1 }; Button *group = button_group_create(PANGO_ALIGN_LEFT, grey); Button *child1 = button_simple_create(PANGO_ALIGN_CENTER, color); button_set_padding(child1, 1, 1); button_simple_set_text(child1, g_strdup("C1"), text_color); button_simple_set_action(child1, show_action, NULL); Button *child2 = button_simple_create(PANGO_ALIGN_CENTER, color); button_set_padding(child2, 1, 1); button_simple_set_text(child2, g_strdup("C2"), text_color); button_simple_set_action(child2, show_action, NULL); Button *child3 = button_simple_create(PANGO_ALIGN_CENTER, color); button_set_padding(child3, 1, 1); button_simple_set_text(child3, g_strdup("C3"), text_color); button_simple_set_action(child3, show_action, NULL); struct { State *state; ButtonGroup *group; char *strings[2]; GList *toggled; bool closed; } menu_ctx = { state, (gpointer)group, {"", ""}, NULL, true }; Button *menu = button_simple_create(PANGO_ALIGN_LEFT, color); button_simple_set_text(menu, g_strdup(menu_ctx.strings[0]), text_color); button_simple_set_action(menu, menu_action, &menu_ctx); menu_ctx.toggled = g_list_append(menu_ctx.toggled, menu); menu_ctx.toggled = g_list_append(menu_ctx.toggled, child1); menu_ctx.toggled = g_list_append(menu_ctx.toggled, child2); menu_ctx.toggled = g_list_append(menu_ctx.toggled, child3); button_group_append(group, menu); state_add_button(state, group); state_order_button(state); connect_attach_state(con, state); connect_attach_source(con); guint source_alrm = g_unix_signal_add(SIGUSR1, G_SOURCE_FUNC(date_update), date_btn); guint source_term = g_unix_signal_add(SIGTERM, mainloop_quit, mainloop); guint source_int = g_unix_signal_add(SIGINT, mainloop_quit, mainloop); state_request_redraw(state, true); log_debug("Starting main loop"); g_main_loop_run(mainloop); log_debug("Cleaning main loop"); g_clear_pointer(&mainloop, g_main_loop_unref); g_source_remove(source_alrm); g_source_remove(source_term); g_source_remove(source_int); // NOTE: Skip the first element (the open/close button) if (menu_ctx.closed) g_list_free_full(g_list_delete_link(menu_ctx.toggled, menu_ctx.toggled), (GDestroyNotify)button_destroy); else g_list_free(menu_ctx.toggled); timer_delete(date_ctx.timer); g_object_unref(context); // NOTE: Buttons are freed by state_destroy dwm_destroy(dwm); state_destroy(state); draw_destroy(draw); window_destroy(win); connect_destroy(con); return 0; } // vim: ts=4 sw=4 et