#include "execplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include "window.h" #include "server.h" #include "panel.h" #include "timer.h" #include "common.h" #define MAX_TOOLTIP_LEN 4096 void execp_timer_callback(void *arg); char *execp_get_tooltip(void *obj); void execp_init_fonts(); int execp_compute_desired_size(void *obj); void execp_dump_geometry(void *obj, int indent); void default_execp() { } Execp *create_execp() { Execp *execp = (Execp *)calloc(1, sizeof(Execp)); execp->backend = (ExecpBackend *)calloc(1, sizeof(ExecpBackend)); execp->backend->child_pipe_stdout = -1; execp->backend->child_pipe_stderr = -1; execp->backend->cmd_pids = g_tree_new(cmp_ptr); execp->backend->interval = 30; execp->backend->cache_icon = TRUE; execp->backend->centered = TRUE; execp->backend->font_color.alpha = 0.5; INIT_TIMER(execp->backend->timer); return execp; } gpointer create_execp_frontend(gconstpointer arg, gpointer data) { Execp *execp_backend = (Execp *)arg; Execp *execp_frontend = (Execp *)calloc(1, sizeof(Execp)); execp_frontend->backend = execp_backend->backend; execp_backend->backend->instances = g_list_append(execp_backend->backend->instances, execp_frontend); execp_frontend->frontend = (ExecpFrontend *)calloc(1, sizeof(ExecpFrontend)); return execp_frontend; } void destroy_execp(void *obj) { Execp *execp = (Execp *)obj; if (execp->frontend) { // This is a frontend element execp->backend->instances = g_list_remove_all(execp->backend->instances, execp); free_and_null(execp->frontend); remove_area(&execp->area); free_area(&execp->area); free_and_null(execp); } else { // This is a backend element destroy_timer(&execp->backend->timer); free_icon(execp->backend->icon); free_and_null(execp->backend->buf_stdout); free_and_null(execp->backend->buf_stderr); free_and_null(execp->backend->text); free_and_null(execp->backend->icon_path); if (execp->backend->child) { kill(-execp->backend->child, SIGHUP); execp->backend->child = 0; } if (execp->backend->child_pipe_stdout >= 0) { close(execp->backend->child_pipe_stdout); execp->backend->child_pipe_stdout = -1; } if (execp->backend->child_pipe_stderr >= 0) { close(execp->backend->child_pipe_stderr); execp->backend->child_pipe_stderr = -1; } if (execp->backend->cmd_pids) { g_tree_destroy(execp->backend->cmd_pids); execp->backend->cmd_pids = NULL; } execp->backend->bg = NULL; pango_font_description_free(execp->backend->font_desc); execp->backend->font_desc = NULL; free_and_null(execp->backend->command); free_and_null(execp->backend->tooltip); free_and_null(execp->backend->lclick_command); free_and_null(execp->backend->mclick_command); free_and_null(execp->backend->rclick_command); free_and_null(execp->backend->dwheel_command); free_and_null(execp->backend->uwheel_command); if (execp->backend->instances) { fprintf(stderr, "tint2: Error: Attempt to destroy backend while there are still frontend instances!\n"); exit(EXIT_FAILURE); } free(execp->backend); free(execp); } } void init_execp() { GList *to_remove = panel_config.execp_list; for (int k = 0; k < strlen(panel_items_order) && to_remove; k++) { if (panel_items_order[k] == 'E') { to_remove = to_remove->next; } } if (to_remove) { if (to_remove == panel_config.execp_list) { g_list_free_full(to_remove, destroy_execp); panel_config.execp_list = NULL; } else { // Cut panel_config.execp_list if (to_remove->prev) to_remove->prev->next = NULL; to_remove->prev = NULL; // Remove all elements of to_remove and to_remove itself g_list_free_full(to_remove, destroy_execp); } } execp_init_fonts(); for (GList *l = panel_config.execp_list; l; l = l->next) { Execp *execp = l->data; // Set missing config options if (!execp->backend->bg) execp->backend->bg = &g_array_index(backgrounds, Background, 0); execp->backend->buf_stdout_capacity = 1024; execp->backend->buf_stdout = calloc(execp->backend->buf_stdout_capacity, 1); execp->backend->buf_stderr_capacity = 1024; execp->backend->buf_stderr = calloc(execp->backend->buf_stderr_capacity, 1); execp->backend->text = strdup(""); execp->backend->icon_path = NULL; } } void init_execp_panel(void *p) { Panel *panel = (Panel *)p; // Make sure this is only done once if there are multiple items if (panel->execp_list && ((Execp *)panel->execp_list->data)->frontend) return; // panel->execp_list is now a copy of the pointer panel_config.execp_list // We make it a deep copy panel->execp_list = g_list_copy_deep(panel_config.execp_list, create_execp_frontend, NULL); for (GList *l = panel->execp_list; l; l = l->next) { Execp *execp = l->data; execp->area.bg = execp->backend->bg; execp->area.paddingx = execp->backend->paddingx; execp->area.paddingy = execp->backend->paddingy; execp->area.paddingxlr = execp->backend->paddingxlr; execp->area.parent = panel; execp->area.panel = panel; execp->area._dump_geometry = execp_dump_geometry; execp->area._compute_desired_size = execp_compute_desired_size; snprintf(execp->area.name, sizeof(execp->area.name), "Execp %s", execp->backend->command ? execp->backend->command : "null"); execp->area._draw_foreground = draw_execp; execp->area.size_mode = LAYOUT_FIXED; execp->area._resize = resize_execp; execp->area._get_tooltip_text = execp_get_tooltip; execp->area._is_under_mouse = full_width_area_is_under_mouse; execp->area.has_mouse_press_effect = panel_config.mouse_effects && (execp->area.has_mouse_over_effect = execp->backend->lclick_command || execp->backend->mclick_command || execp->backend->rclick_command || execp->backend->uwheel_command || execp->backend->dwheel_command); execp->area.resize_needed = TRUE; execp->area.on_screen = TRUE; instantiate_area_gradients(&execp->area); change_timer(&execp->backend->timer, true, 10, 0, execp_timer_callback, execp); execp_update_post_read(execp); } } void execp_init_fonts() { for (GList *l = panel_config.execp_list; l; l = l->next) { Execp *execp = l->data; if (!execp->backend->font_desc) execp->backend->font_desc = pango_font_description_from_string(get_default_font()); } } void execp_default_font_changed() { gboolean needs_update = FALSE; for (GList *l = panel_config.execp_list; l; l = l->next) { Execp *execp = l->data; if (!execp->backend->has_font) { pango_font_description_free(execp->backend->font_desc); execp->backend->font_desc = NULL; needs_update = TRUE; } } if (!needs_update) return; execp_init_fonts(); for (int i = 0; i < num_panels; i++) { for (GList *l = panels[i].execp_list; l; l = l->next) { Execp *execp = l->data; if (!execp->backend->has_font) { execp->area.resize_needed = TRUE; schedule_redraw(&execp->area); } } } schedule_panel_redraw(); } void cleanup_execp() { // Cleanup frontends for (int i = 0; i < num_panels; i++) { g_list_free_full(panels[i].execp_list, destroy_execp); panels[i].execp_list = NULL; } // Cleanup backends g_list_free_full(panel_config.execp_list, destroy_execp); panel_config.execp_list = NULL; } // Called from backend functions. gboolean reload_icon(Execp *execp) { char *icon_path = execp->backend->icon_path; if (execp->backend->has_icon && icon_path) { if (execp->backend->icon) { imlib_context_set_image(execp->backend->icon); imlib_free_image(); } execp->backend->icon = load_image(icon_path, execp->backend->cache_icon); if (execp->backend->icon) { imlib_context_set_image(execp->backend->icon); int w = imlib_image_get_width(); int h = imlib_image_get_height(); if (w && h) { if (execp->backend->icon_w) { if (!execp->backend->icon_h) { h = (int)(0.5 + h * execp->backend->icon_w / (float)(w)); w = execp->backend->icon_w; } else { w = execp->backend->icon_w; h = execp->backend->icon_h; } } else { if (execp->backend->icon_h) { w = (int)(0.5 + w * execp->backend->icon_h / (float)(h)); h = execp->backend->icon_h; } } if (w < 1) w = 1; if (h < 1) h = 1; } if (w != imlib_image_get_width() || h != imlib_image_get_height()) { Imlib_Image icon_scaled = imlib_create_cropped_scaled_image(0, 0, imlib_image_get_width(), imlib_image_get_height(), w, h); imlib_context_set_image(execp->backend->icon); imlib_free_image(); execp->backend->icon = icon_scaled; } return TRUE; } } return FALSE; } void execp_compute_icon_text_geometry(Execp *execp, int *horiz_padding, int *vert_padding, int *interior_padding, int *icon_w, int *icon_h, gboolean *text_next_line, int *txt_height_ink, int *txt_height, int *txt_width, int *new_size, gboolean *resized) { Panel *panel = (Panel *)execp->area.panel; Area *area = &execp->area; *horiz_padding = (panel_horizontal ? area->paddingxlr : area->paddingy) * panel->scale; *vert_padding = (panel_horizontal ? area->paddingy : area->paddingxlr) * panel->scale; *interior_padding = area->paddingx * panel->scale; if (reload_icon(execp)) { if (execp->backend->icon) { imlib_context_set_image(execp->backend->icon); *icon_w = imlib_image_get_width(); *icon_h = imlib_image_get_height(); } else { *icon_w = *icon_h = 0; } } else { *icon_w = *icon_h = 0; } *text_next_line = !panel_horizontal && *icon_w > area->width / 2; int available_w, available_h; if (panel_horizontal) { available_w = panel->area.width; available_h = area->height - 2 * *horiz_padding - left_right_border_width(area); } else { available_w = !text_next_line ? area->width - *icon_w - (*icon_w ? *interior_padding : 0) - 2 * *horiz_padding - left_right_border_width(area) : area->width - 2 * *horiz_padding - left_right_border_width(area); available_h = panel->area.height; } get_text_size2(execp->backend->font_desc, txt_height_ink, txt_height, txt_width, available_h, available_w, execp->backend->text, strlen(execp->backend->text), PANGO_WRAP_WORD_CHAR, PANGO_ELLIPSIZE_NONE, execp->backend->has_markup, panel->scale); *resized = FALSE; if (panel_horizontal) { *new_size = *txt_width; if (*icon_w) *new_size += *interior_padding + *icon_w; *new_size += 2 * *horiz_padding + left_right_border_width(area); if (*new_size < area->width && abs(*new_size - area->width) < 6) { // we try to limit the number of resizes *new_size = area->width; *resized = TRUE; } else { *resized = *new_size != area->width; } } else { if (!*text_next_line) { *new_size = *txt_height + 2 * *vert_padding + top_bottom_border_width(area); *new_size = MAX(*new_size, *icon_h + 2 * *vert_padding + top_bottom_border_width(area)); } else { *new_size = *icon_h + *interior_padding + *txt_height + 2 * *vert_padding + top_bottom_border_width(area); } if (*new_size != area->height) { *resized = TRUE; } } } int execp_compute_desired_size(void *obj) { Execp *execp = (Execp *)obj; int horiz_padding, vert_padding, interior_padding; int icon_w, icon_h; gboolean text_next_line; int txt_height_ink, txt_height, txt_width; int new_size; gboolean resized; execp_compute_icon_text_geometry(execp, &horiz_padding, &vert_padding, &interior_padding, &icon_w, &icon_h, &text_next_line, &txt_height_ink, &txt_height, &txt_width, &new_size, &resized); return new_size; } gboolean resize_execp(void *obj) { Execp *execp = (Execp *)obj; int horiz_padding, vert_padding, interior_padding; int icon_w, icon_h; gboolean text_next_line; int txt_height_ink, txt_height, txt_width; int new_size; gboolean resized; execp_compute_icon_text_geometry(execp, &horiz_padding, &vert_padding, &interior_padding, &icon_w, &icon_h, &text_next_line, &txt_height_ink, &txt_height, &txt_width, &new_size, &resized); if (panel_horizontal) execp->area.width = new_size; else execp->area.height = new_size; execp->frontend->textw = txt_width; execp->frontend->texth = txt_height; if (execp->backend->centered) { if (icon_w) { if (!text_next_line) { execp->frontend->icony = (execp->area.height - icon_h) / 2; execp->frontend->iconx = (execp->area.width - txt_width - interior_padding - icon_w) / 2; execp->frontend->texty = (execp->area.height - txt_height) / 2; execp->frontend->textx = execp->frontend->iconx + icon_w + interior_padding; } else { execp->frontend->icony = (execp->area.height - icon_h - interior_padding - txt_height) / 2; execp->frontend->iconx = (execp->area.width - icon_w) / 2; execp->frontend->texty = execp->frontend->icony + icon_h + interior_padding; execp->frontend->textx = (execp->area.width - txt_width) / 2; } } else { execp->frontend->texty = (execp->area.height - txt_height) / 2; execp->frontend->textx = (execp->area.width - txt_width) / 2; } } else { if (icon_w) { if (!text_next_line) { execp->frontend->icony = (execp->area.height - icon_h) / 2; execp->frontend->iconx = left_border_width(&execp->area) + horiz_padding; execp->frontend->texty = (execp->area.height - txt_height) / 2; execp->frontend->textx = execp->frontend->iconx + icon_w + interior_padding; } else { execp->frontend->icony = (execp->area.height - icon_h - interior_padding - txt_height) / 2; execp->frontend->iconx = left_border_width(&execp->area) + horiz_padding; execp->frontend->texty = execp->frontend->icony + icon_h + interior_padding; execp->frontend->textx = execp->frontend->iconx; } } else { execp->frontend->texty = (execp->area.height - txt_height) / 2; execp->frontend->textx = left_border_width(&execp->area) + horiz_padding; } } schedule_redraw(&execp->area); return resized; } void draw_execp(void *obj, cairo_t *c) { Execp *execp = (Execp *)obj; Panel *panel = (Panel *)execp->area.panel; PangoContext *context = pango_cairo_create_context(c); pango_cairo_context_set_resolution(context, 96 * panel->scale); PangoLayout *layout = pango_layout_new(context); if (execp->backend->has_icon && execp->backend->icon) { imlib_context_set_image(execp->backend->icon); // Render icon render_image(execp->area.pix, execp->frontend->iconx, execp->frontend->icony); } // draw layout pango_layout_set_font_description(layout, execp->backend->font_desc); pango_layout_set_width(layout, (execp->frontend->textw + 1) * PANGO_SCALE); pango_layout_set_alignment(layout, execp->backend->centered ? PANGO_ALIGN_CENTER : PANGO_ALIGN_LEFT); pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR); pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE); if (!execp->backend->has_markup) pango_layout_set_text(layout, execp->backend->text, strlen(execp->backend->text)); else pango_layout_set_markup(layout, execp->backend->text, strlen(execp->backend->text)); pango_cairo_update_layout(c, layout); draw_text(layout, c, execp->frontend->textx, execp->frontend->texty, &execp->backend->font_color, panel_config.font_shadow); g_object_unref(layout); g_object_unref(context); } void execp_dump_geometry(void *obj, int indent) { Execp *execp = (Execp *)obj; if (execp->backend->has_icon && execp->backend->icon) { Imlib_Image tmp = imlib_context_get_image(); imlib_context_set_image(execp->backend->icon); fprintf(stderr, "tint2: %*sIcon: x = %d, y = %d, w = %d, h = %d\n", indent, "", execp->frontend->iconx, execp->frontend->icony, imlib_image_get_width(), imlib_image_get_height()); if (tmp) imlib_context_set_image(tmp); } fprintf(stderr, "tint2: %*sText: x = %d, y = %d, w = %d, align = %s, text = %s\n", indent, "", execp->frontend->textx, execp->frontend->texty, execp->frontend->textw, execp->backend->centered ? "center" : "left", execp->backend->text); } void execp_force_update(Execp *execp) { if (execp->backend->child_pipe_stdout > 0) { // Command currently running, nothing to do } else { // Run command right away change_timer(&execp->backend->timer, true, 10, 0, execp_timer_callback, execp); } } void execp_action(void *obj, int button, int x, int y, Time time) { Execp *execp = (Execp *)obj; char *command = NULL; switch (button) { case 1: command = execp->backend->lclick_command; break; case 2: command = execp->backend->mclick_command; break; case 3: command = execp->backend->rclick_command; break; case 4: command = execp->backend->uwheel_command; break; case 5: command = execp->backend->dwheel_command; break; } if (command) { setenvd("EXECP_X", x); setenvd("EXECP_Y", y); setenvd("EXECP_W", execp->area.width); setenvd("EXECP_H", execp->area.height); pid_t pid = tint_exec(command, NULL, NULL, time, obj, x, y, FALSE, TRUE); unsetenv("EXECP_X"); unsetenv("EXECP_Y"); unsetenv("EXECP_W"); unsetenv("EXECP_H"); if (pid > 0) g_tree_insert(execp->backend->cmd_pids, GINT_TO_POINTER(pid), GINT_TO_POINTER(1)); } else { execp_force_update(execp); } } void execp_cmd_completed(Execp *execp, pid_t pid) { g_tree_remove(execp->backend->cmd_pids, GINT_TO_POINTER(pid)); execp_force_update(execp); } void execp_timer_callback(void *arg) { Execp *execp = (Execp *)arg; if (!execp->backend->command) return; // Still running! if (execp->backend->child_pipe_stdout > 0) return; int pipe_fd_stdout[2]; if (pipe(pipe_fd_stdout)) { // TODO maybe write this in tooltip, but if this happens we're screwed anyways fprintf(stderr, "tint2: Execp: Creating pipe failed!\n"); return; } fcntl(pipe_fd_stdout[0], F_SETFL, O_NONBLOCK | fcntl(pipe_fd_stdout[0], F_GETFL)); int pipe_fd_stderr[2]; if (pipe(pipe_fd_stderr)) { close(pipe_fd_stdout[1]); close(pipe_fd_stdout[0]); // TODO maybe write this in tooltip, but if this happens we're screwed anyways fprintf(stderr, "tint2: Execp: Creating pipe failed!\n"); return; } fcntl(pipe_fd_stderr[0], F_SETFL, O_NONBLOCK | fcntl(pipe_fd_stderr[0], F_GETFL)); // Fork and run command, capturing stdout in pipe pid_t child = fork(); if (child == -1) { // TODO maybe write this in tooltip, but if this happens we're screwed anyways fprintf(stderr, "tint2: Fork failed.\n"); close(pipe_fd_stdout[1]); close(pipe_fd_stdout[0]); close(pipe_fd_stderr[1]); close(pipe_fd_stderr[0]); return; } else if (child == 0) { fprintf(stderr, "tint2: Executing: %s\n", execp->backend->command); // We are in the child close(pipe_fd_stdout[0]); dup2(pipe_fd_stdout[1], 1); // 1 is stdout close(pipe_fd_stdout[1]); close(pipe_fd_stderr[0]); dup2(pipe_fd_stderr[1], 2); // 2 is stderr close(pipe_fd_stderr[1]); close_all_fds(); setpgid(0, 0); execl("/bin/sh", "/bin/sh", "-c", execp->backend->command, NULL); // This should never happen! fprintf(stderr, "execl() failed\nexecl() failed\n"); exit(0); } close(pipe_fd_stdout[1]); close(pipe_fd_stderr[1]); execp->backend->child = child; execp->backend->child_pipe_stdout = pipe_fd_stdout[0]; execp->backend->child_pipe_stderr = pipe_fd_stderr[0]; execp->backend->buf_stdout_length = 0; execp->backend->buf_stdout[execp->backend->buf_stdout_length] = '\0'; execp->backend->buf_stderr_length = 0; execp->backend->buf_stderr[execp->backend->buf_stderr_length] = '\0'; execp->backend->last_update_start_time = time(NULL); } void read_from_pipe(int fd, char **buffer, ssize_t *buffer_length, ssize_t *buffer_capacity, gboolean *eof) { *eof = FALSE; while (1) { // Make sure there is free space in the buffer if (*buffer_capacity - *buffer_length < 1024) { *buffer_capacity *= 2; *buffer = (char *)realloc(*buffer, *buffer_capacity); } ssize_t count = read(fd, *buffer + *buffer_length, *buffer_capacity - *buffer_length - 1); if (count > 0) { // Successful read *buffer_length += count; (*buffer)[*buffer_length] = '\0'; continue; } else if (count == 0) { // End of file *eof = TRUE; break; } else if (errno == EAGAIN || errno == EWOULDBLOCK) { // No more data available at the moment break; } else if (errno == EINTR) { // Harmless interruption by signal continue; } else { // Error *eof = TRUE; break; } break; } } gboolean starts_with(char *s, char *prefix) { char *p, *q; for (p = s, q = prefix; *p && *q; p++, q++) { if (*p != *q) return FALSE; } return *q == '\0'; } char *last_substring(char *s, char *sub) { char *result = NULL; for (char *p = s; *p; p++) { if (starts_with(p, sub)) result = p; } return result; } void rstrip(char *s) { size_t len = strlen(s); while (len > 0) { if (s[len-1] == ' ' || s[len-1] == '\n') { s[len-1] = 0; len--; } else { break; } } } gboolean read_execp(void *obj) { Execp *execp = (Execp *)obj; if (execp->backend->child_pipe_stdout < 0) return FALSE; gboolean stdout_eof, stderr_eof; read_from_pipe(execp->backend->child_pipe_stdout, &execp->backend->buf_stdout, &execp->backend->buf_stdout_length, &execp->backend->buf_stdout_capacity, &stdout_eof); read_from_pipe(execp->backend->child_pipe_stderr, &execp->backend->buf_stderr, &execp->backend->buf_stderr_length, &execp->backend->buf_stderr_capacity, &stderr_eof); gboolean command_finished = stdout_eof && stderr_eof; if (command_finished) { execp->backend->child = 0; close(execp->backend->child_pipe_stdout); execp->backend->child_pipe_stdout = -1; close(execp->backend->child_pipe_stderr); execp->backend->child_pipe_stderr = -1; if (execp->backend->interval) change_timer(&execp->backend->timer, true, execp->backend->interval * 1000, 0, execp_timer_callback, execp); } char *ansi_clear_screen = (char*)"\x1b[2J"; if (!execp->backend->continuous && command_finished) { // Handle stdout free_and_null(execp->backend->text); free_and_null(execp->backend->icon_path); if (!execp->backend->has_icon) { execp->backend->text = strdup(execp->backend->buf_stdout); } else { char *text = strchr(execp->backend->buf_stdout, '\n'); if (text) { *text = '\0'; text++; execp->backend->text = strdup(text); } else { execp->backend->text = strdup(""); } execp->backend->icon_path = strdup(execp->backend->buf_stdout); } int len = strlen(execp->backend->text); if (len > 0 && execp->backend->text[len - 1] == '\n') execp->backend->text[len - 1] = '\0'; execp->backend->buf_stdout_length = 0; execp->backend->buf_stdout[execp->backend->buf_stdout_length] = '\0'; // Handle stderr if (!execp->backend->has_user_tooltip) { free_and_null(execp->backend->tooltip); char *start = last_substring(execp->backend->buf_stderr, ansi_clear_screen); if (start) start += strlen(ansi_clear_screen); else start = execp->backend->buf_stderr; if (*start) { execp->backend->tooltip = strdup(start); rstrip(execp->backend->tooltip); if (strlen(execp->backend->tooltip) > MAX_TOOLTIP_LEN) execp->backend->tooltip[MAX_TOOLTIP_LEN] = '\0'; } } execp->backend->buf_stderr_length = 0; execp->backend->buf_stderr[execp->backend->buf_stderr_length] = '\0'; // execp->backend->last_update_finish_time = time(NULL); execp->backend->last_update_duration = execp->backend->last_update_finish_time - execp->backend->last_update_start_time; return TRUE; } else if (execp->backend->continuous > 0) { // Handle stderr if (!execp->backend->has_user_tooltip) { free_and_null(execp->backend->tooltip); char *start = last_substring(execp->backend->buf_stderr, ansi_clear_screen); if (start) { start += strlen(ansi_clear_screen); memmove(execp->backend->buf_stderr, start, strlen(start) + 1); execp->backend->buf_stderr_length = (ssize_t)strlen(execp->backend->buf_stderr); } if (execp->backend->buf_stderr_length > MAX_TOOLTIP_LEN) { execp->backend->buf_stderr_length = MAX_TOOLTIP_LEN; execp->backend->buf_stderr[execp->backend->buf_stderr_length] = '\0'; } execp->backend->tooltip = strdup(execp->backend->buf_stderr); rstrip(execp->backend->tooltip); } else { execp->backend->buf_stderr_length = 0; execp->backend->buf_stderr[execp->backend->buf_stderr_length] = '\0'; } // Handle stdout // Count lines in buffer int num_lines = 0; char *end = NULL; for (char *c = execp->backend->buf_stdout; *c; c++) { if (*c == '\n') { num_lines++; if (num_lines == execp->backend->continuous) end = c; } } if (num_lines >= execp->backend->continuous) { if (end) *end = '\0'; free_and_null(execp->backend->text); free_and_null(execp->backend->icon_path); if (!execp->backend->has_icon) { execp->backend->text = strdup(execp->backend->buf_stdout); } else { char *text = strchr(execp->backend->buf_stdout, '\n'); if (text) { *text = '\0'; text++; execp->backend->text = strdup(text); } else { execp->backend->text = strdup(""); } execp->backend->icon_path = strdup(execp->backend->buf_stdout); } size_t len = strlen(execp->backend->text); if (len > 0 && execp->backend->text[len - 1] == '\n') execp->backend->text[len - 1] = '\0'; if (end) { char *next = end + 1; ssize_t copied = next - execp->backend->buf_stdout; ssize_t remaining = execp->backend->buf_stdout_length - copied; if (remaining > 0) { memmove(execp->backend->buf_stdout, next, (size_t)remaining); execp->backend->buf_stdout_length = remaining; execp->backend->buf_stdout[execp->backend->buf_stdout_length] = '\0'; } else { execp->backend->buf_stdout_length = 0; execp->backend->buf_stdout[execp->backend->buf_stdout_length] = '\0'; } } execp->backend->last_update_finish_time = time(NULL); execp->backend->last_update_duration = execp->backend->last_update_finish_time - execp->backend->last_update_start_time; return TRUE; } } return FALSE; } const char *time_to_string(int seconds, char *buffer, size_t buffer_size) { if (seconds < 60) { snprintf(buffer, buffer_size, "%ds", seconds); } else if (seconds < 60 * 60) { int m = seconds / 60; seconds = seconds % 60; int s = seconds; snprintf(buffer, buffer_size, "%d:%ds", m, s); } else { int h = seconds / (60 * 60); seconds = seconds % (60 * 60); int m = seconds / 60; seconds = seconds % 60; int s = seconds; snprintf(buffer, buffer_size, "%d:%d:%ds", h, m, s); } return buffer; } char *execp_get_tooltip(void *obj) { Execp *execp = (Execp *)obj; if (execp->backend->tooltip) { if (strlen(execp->backend->tooltip) > 0) return strdup(execp->backend->tooltip); else return NULL; } time_t now = time(NULL); char tmp_buf1[256]; char tmp_buf2[256]; char tmp_buf3[256]; if (execp->backend->child_pipe_stdout < 0) { // Not executing command if (execp->backend->last_update_finish_time) { // We updated at least once if (execp->backend->interval > 0) { snprintf(execp->backend->tooltip_text, sizeof(execp->backend->tooltip_text), "Last update finished %s ago (took %s). Next update starting in %s.", time_to_string((int)(now - execp->backend->last_update_finish_time), tmp_buf1, sizeof(tmp_buf1)), time_to_string((int)execp->backend->last_update_duration, tmp_buf2, sizeof(tmp_buf2)), time_to_string((int)(execp->backend->interval - (now - execp->backend->last_update_finish_time)), tmp_buf3, sizeof(tmp_buf3))); } else { snprintf(execp->backend->tooltip_text, sizeof(execp->backend->tooltip_text), "Last update finished %s ago (took %s).", time_to_string((int)(now - execp->backend->last_update_finish_time), tmp_buf1, sizeof(tmp_buf1)), time_to_string((int)execp->backend->last_update_duration, tmp_buf2, sizeof(tmp_buf2))); } } else { // we never requested an update snprintf(execp->backend->tooltip_text, sizeof(execp->backend->tooltip_text), "Never updated. No update scheduled."); } } else { // Currently executing command if (execp->backend->last_update_finish_time) { // we finished updating at least once snprintf(execp->backend->tooltip_text, sizeof(execp->backend->tooltip_text), "Last update finished %s ago. Update in progress (started %s ago).", time_to_string((int)(now - execp->backend->last_update_finish_time), tmp_buf1, sizeof(tmp_buf1)), time_to_string((int)(now - execp->backend->last_update_start_time), tmp_buf3, sizeof(tmp_buf3))); } else { // we never finished an update snprintf(execp->backend->tooltip_text, sizeof(execp->backend->tooltip_text), "First update in progress (started %s seconds ago).", time_to_string((int)(now - execp->backend->last_update_start_time), tmp_buf1, sizeof(tmp_buf1))); } } return strdup(execp->backend->tooltip_text); } void execp_update_post_read(Execp *execp) { int icon_h, icon_w; if (reload_icon(execp)) { if (execp->backend->icon) { imlib_context_set_image(execp->backend->icon); icon_w = imlib_image_get_width(); icon_h = imlib_image_get_height(); } else { icon_w = icon_h = 0; } } else { icon_w = icon_h = 0; } if ((icon_h == 0 || icon_w == 0) && execp->backend->text[0] == 0) { // Easy to test with bash -c 'R=$(( RANDOM % 2 )); [ $R -eq 0 ] && echo HELLO $R' if (execp->area.on_screen) hide(&execp->area); } else { if (!execp->area.on_screen) show(&execp->area); execp->area.resize_needed = TRUE; schedule_panel_redraw(); } } void handle_execp_events() { for (GList *l = panel_config.execp_list; l; l = l->next) { Execp *execp = (Execp *)l->data; if (read_execp(execp)) { GList *l_instance; for (l_instance = execp->backend->instances; l_instance; l_instance = l_instance->next) { Execp *instance = (Execp *)l_instance->data; execp_update_post_read(instance); } } } }