all repos — tint2 @ 3f84d5d14c6adf7e4df1174bea3014b34bff252d

fork of the tint2 desktop panel for my custom setup - only minimized windows across all desktops for the taskbar

Merged execplugin from tint2-mods2 (no config GUI yet)
o9000 mrovi9000@gmail.com
commit

3f84d5d14c6adf7e4df1174bea3014b34bff252d

parent

33645f9b5aaa97f7bbc84fd5242da1ddb730744b

M CMakeLists.txtCMakeLists.txt

@@ -47,6 +47,7 @@ src/taskbar

src/launcher src/tooltip src/util + src/execplugin src/freespace ${X11_INCLUDE_DIRS} ${PANGOCAIRO_INCLUDE_DIRS}

@@ -73,6 +74,7 @@ src/taskbar/task.c

src/taskbar/taskbar.c src/taskbar/taskbarname.c src/tooltip/tooltip.c + src/execplugin/execplugin.c src/freespace/freespace.c src/util/area.c src/util/common.c
M ChangeLogChangeLog

@@ -1,6 +1,7 @@

-2015-11-21 master +2015-12-05 master - Enhancements: - Support for NETWM viewports (as in Compiz) (issue #94) + - New plugin: executor 2015-11-12 0.12.3 - Enhancements: - Battery: Multiple batteries are now supported under Linux (issue #139;
M src/config.csrc/config.c

@@ -52,6 +52,7 @@ #include "clock.h"

#include "window.h" #include "tooltip.h" #include "timer.h" +#include "execplugin.h" #ifdef ENABLE_BATTERY #include "battery.h"

@@ -197,6 +198,15 @@ panel_config.launcher.list_apps = g_slist_append(panel_config.launcher.list_apps, strdup(file));

g_free(file); } g_list_free(files); +} + +Execp *get_or_create_last_execp() +{ + if (!panel_config.execp_list) { + fprintf(stderr, "Warning: execp items should start with 'execp = new'\n"); + panel_config.execp_list = g_list_append(panel_config.execp_list, create_execp()); + } + return (Execp *)g_list_last(panel_config.execp_list)->data; } void add_entry(char *key, char *value)

@@ -496,6 +506,109 @@ } else if (strcmp(key, "battery_tooltip") == 0) {

#ifdef ENABLE_BATTERY battery_tooltip_enabled = atoi(value); #endif + } + + /* Execp */ + else if (strcmp(key, "execp") == 0) { + panel_config.execp_list = g_list_append(panel_config.execp_list, create_execp()); + } else if (strcmp(key, "execp_command") == 0) { + Execp *execp = get_or_create_last_execp(); + free_and_null(execp->backend->command); + if (strlen(value) > 0) + execp->backend->command = strdup(value); + } else if (strcmp(key, "execp_interval") == 0) { + Execp *execp = get_or_create_last_execp(); + execp->backend->interval = 0; + int v = atoi(value); + if (v < 1) { + fprintf(stderr, "execp_interval must be an integer >= 1\n"); + } else { + execp->backend->interval = v; + } + } else if (strcmp(key, "execp_has_icon") == 0) { + Execp *execp = get_or_create_last_execp(); + execp->backend->has_icon = atoi(value); + } else if (strcmp(key, "execp_continuous") == 0) { + Execp *execp = get_or_create_last_execp(); + execp->backend->continuous = atoi(value); + } else if (strcmp(key, "execp_cache_icon") == 0) { + Execp *execp = get_or_create_last_execp(); + execp->backend->cache_icon = atoi(value); + } else if (strcmp(key, "execp_tooltip") == 0) { + Execp *execp = get_or_create_last_execp(); + free_and_null(execp->backend->tooltip); + execp->backend->tooltip = strdup(value); + } else if (strcmp(key, "execp_font") == 0) { + Execp *execp = get_or_create_last_execp(); + pango_font_description_free(execp->backend->font_desc); + execp->backend->font_desc = pango_font_description_from_string(value); + } else if (strcmp(key, "execp_font_color") == 0) { + Execp *execp = get_or_create_last_execp(); + extract_values(value, &value1, &value2, &value3); + get_color(value1, execp->backend->font_color.rgb); + if (value2) + execp->backend->font_color.alpha = atoi(value2) / 100.0; + else + execp->backend->font_color.alpha = 0.5; + } else if (strcmp(key, "execp_padding") == 0) { + Execp *execp = get_or_create_last_execp(); + extract_values(value, &value1, &value2, &value3); + execp->backend->paddingxlr = execp->backend->paddingx = atoi(value1); + if (value2) + execp->backend->paddingy = atoi(value2); + else + execp->backend->paddingy = 0; + if (value3) + execp->backend->paddingx = atoi(value3); + } else if (strcmp(key, "execp_background_id") == 0) { + Execp *execp = get_or_create_last_execp(); + int id = atoi(value); + id = (id < backgrounds->len && id >= 0) ? id : 0; + execp->backend->bg = &g_array_index(backgrounds, Background, id); + } else if (strcmp(key, "execp_centered") == 0) { + Execp *execp = get_or_create_last_execp(); + execp->backend->centered = atoi(value); + } else if (strcmp(key, "execp_icon_w") == 0) { + Execp *execp = get_or_create_last_execp(); + int v = atoi(value); + if (v < 0) { + fprintf(stderr, "execp_icon_w must be an integer >= 0\n"); + } else { + execp->backend->icon_w = v; + } + } else if (strcmp(key, "execp_icon_h") == 0) { + Execp *execp = get_or_create_last_execp(); + int v = atoi(value); + if (v < 0) { + fprintf(stderr, "execp_icon_h must be an integer >= 0\n"); + } else { + execp->backend->icon_h = v; + } + } else if (strcmp(key, "execp_lclick_command") == 0) { + Execp *execp = get_or_create_last_execp(); + free_and_null(execp->backend->lclick_command); + if (strlen(value) > 0) + execp->backend->lclick_command = strdup(value); + } else if (strcmp(key, "execp_mclick_command") == 0) { + Execp *execp = get_or_create_last_execp(); + free_and_null(execp->backend->mclick_command); + if (strlen(value) > 0) + execp->backend->mclick_command = strdup(value); + } else if (strcmp(key, "execp_rclick_command") == 0) { + Execp *execp = get_or_create_last_execp(); + free_and_null(execp->backend->rclick_command); + if (strlen(value) > 0) + execp->backend->rclick_command = strdup(value); + } else if (strcmp(key, "execp_uwheel_command") == 0) { + Execp *execp = get_or_create_last_execp(); + free_and_null(execp->backend->uwheel_command); + if (strlen(value) > 0) + execp->backend->uwheel_command = strdup(value); + } else if (strcmp(key, "execp_dwheel_command") == 0) { + Execp *execp = get_or_create_last_execp(); + free_and_null(execp->backend->dwheel_command); + if (strlen(value) > 0) + execp->backend->dwheel_command = strdup(value); } /* Clock */
A src/execplugin/execplugin.c

@@ -0,0 +1,676 @@

+#include "execplugin.h" + +#include <string.h> +#include <stdio.h> +#include <cairo.h> +#include <cairo-xlib.h> +#include <math.h> +#include <pango/pangocairo.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> + +#include "window.h" +#include "server.h" +#include "panel.h" +#include "timer.h" +#include "common.h" + +void execp_timer_callback(void *arg); +char *execp_get_tooltip(void *obj); + +void default_execp() +{ +} + +Execp *create_execp() +{ + Execp *execp = calloc(1, sizeof(Execp)); + execp->backend = calloc(1, sizeof(ExecpBackend)); + execp->backend->child_pipe = -1; + + execp->backend->interval = 30; + execp->backend->cache_icon = TRUE; + execp->backend->centered = TRUE; + execp->backend->font_color.alpha = 0.5; + + return execp; +} + +gpointer create_execp_frontend(gconstpointer arg, gpointer data) +{ + Execp *execp_backend = (Execp *)arg; + + Execp *execp_frontend = 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 = 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); + } else { + // This is a backend element + stop_timeout(execp->backend->timer); + execp->backend->timer = NULL; + + if (execp->backend->icon) { + imlib_context_set_image(execp->backend->icon); + imlib_free_image(); + execp->backend->icon = NULL; + } + free_and_null(execp->backend->buf_output); + 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 >= 0) { + close(execp->backend->child_pipe); + execp->backend->child_pipe = -1; + } + + 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, "Error: Attempt to destroy backend while there are still frontend instances!\n"); + exit(-1); + } + 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) { + // 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); + } + + for (GList *l = panel_config.execp_list; l; l = l->next) { + Execp *execp = l->data; + + // Set missing config options + if (!execp->backend->font_desc) + execp->backend->font_desc = pango_font_description_from_string(DEFAULT_FONT); + if (!execp->backend->bg) + execp->backend->bg = &g_array_index(backgrounds, Background, 0); + execp->backend->buf_capacity = 1024; + execp->backend->buf_output = calloc(execp->backend->buf_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._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.has_mouse_press_effect = 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; + + if (!execp->backend->timer) + execp->backend->timer = add_timeout(10, 0, execp_timer_callback, execp, &execp->backend->timer); + } +} + +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 draw_execp(void *obj, cairo_t *c) +{ + Execp *execp = obj; + PangoLayout *layout = pango_cairo_create_layout(c); + + 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 * 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); + pango_layout_set_text(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); +} + +gboolean resize_execp(void *obj) +{ + Execp *execp = obj; + Panel *panel = execp->area.panel; + int horiz_padding = (panel_horizontal ? execp->area.paddingxlr : execp->area.paddingy); + int vert_padding = (panel_horizontal ? execp->area.paddingy : execp->area.paddingxlr); + int interior_padding = execp->area.paddingx; + + schedule_redraw(&execp->area); + + int icon_w, icon_h; + 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; + } + + int text_next_line = !panel_horizontal && icon_w > execp->area.width / 2; + + int txt_height_ink, txt_height, txt_width; + if (panel_horizontal) { + get_text_size2(execp->backend->font_desc, + &txt_height_ink, + &txt_height, + &txt_width, + panel->area.height, + panel->area.width, + execp->backend->text, + strlen(execp->backend->text), + PANGO_WRAP_WORD_CHAR, + PANGO_ELLIPSIZE_NONE); + } else { + get_text_size2(execp->backend->font_desc, + &txt_height_ink, + &txt_height, + &txt_width, + panel->area.height, + !text_next_line + ? execp->area.width - icon_w - (icon_w ? interior_padding : 0) - + 2 * (horiz_padding + execp->area.bg->border.width) + : execp->area.width - 2 * (horiz_padding + execp->area.bg->border.width), + execp->backend->text, + strlen(execp->backend->text), + PANGO_WRAP_WORD_CHAR, + PANGO_ELLIPSIZE_NONE); + } + + gboolean result = FALSE; + if (panel_horizontal) { + int new_size = txt_width; + if (icon_w) + new_size += interior_padding + icon_w; + new_size += 2 * (horiz_padding + execp->area.bg->border.width); + if (new_size > execp->area.width || new_size < (execp->area.width - 6)) { + // we try to limit the number of resize + execp->area.width = new_size + 1; + result = TRUE; + } + } else { + int new_size; + if (!text_next_line) { + new_size = txt_height + (2 * (vert_padding + execp->area.bg->border.width)); + if (new_size < icon_h + (2 * (vert_padding + execp->area.bg->border.width))) { + new_size = icon_h + (2 * (vert_padding + execp->area.bg->border.width)); + } + } else { + new_size = icon_h + interior_padding + txt_height + (2 * (vert_padding + execp->area.bg->border.width)); + } + if (new_size != execp->area.height) { + execp->area.height = new_size; + result = TRUE; + } + } + 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 = execp->area.bg->border.width + 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 = execp->area.bg->border.width + 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 = execp->area.bg->border.width + horiz_padding; + } + } + + return result; +} + +void execp_action(void *obj, int button) +{ + 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) { + tint_exec(command); + } else { + if (execp->backend->child_pipe > 0) { + // Command currently running, nothing to do + } else { + if (execp->backend->timer) + stop_timeout(execp->backend->timer); + // Run command right away + execp->backend->timer = add_timeout(10, 0, execp_timer_callback, execp, &execp->backend->timer); + } + } +} + +void execp_timer_callback(void *arg) +{ + Execp *execp = arg; + + if (!execp->backend->command) + return; + + // Still running! + if (execp->backend->child_pipe > 0) + return; + + int pipe_fd[2]; + if (pipe(pipe_fd)) { + // TODO maybe write this in tooltip, but if this happens we're screwed anyways + fprintf(stderr, "Execp: Creating pipe failed!\n"); + return; + } + + fcntl(pipe_fd[0], F_SETFL, O_NONBLOCK | fcntl(pipe_fd[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, "Fork failed.\n"); + close(pipe_fd[1]); + close(pipe_fd[0]); + return; + } else if (child == 0) { + // We are in the child + close(pipe_fd[0]); + dup2(pipe_fd[1], 1); // 1 is stdout + close(pipe_fd[1]); + setpgid(0, 0); + execl("/bin/sh", "/bin/sh", "-c", execp->backend->command, NULL); + // This should never happen! + fprintf(stdout, "execl() failed\nexecl() failed\n"); + fflush(stdout); + exit(0); + } + close(pipe_fd[1]); + execp->backend->child = child; + execp->backend->child_pipe = pipe_fd[0]; + execp->backend->buf_length = 0; + execp->backend->buf_output[execp->backend->buf_length] = '\0'; + execp->backend->last_update_start_time = time(NULL); +} + +gboolean read_execp(void *obj) +{ + Execp *execp = (Execp *)obj; + + if (execp->backend->child_pipe < 0) + return FALSE; + + gboolean command_finished = FALSE; + while (1) { + // Make sure there is free space in the buffer + if (execp->backend->buf_capacity - execp->backend->buf_length < 1024) { + execp->backend->buf_capacity *= 2; + execp->backend->buf_output = realloc(execp->backend->buf_output, execp->backend->buf_capacity); + } + ssize_t count = read(execp->backend->child_pipe, + execp->backend->buf_output + execp->backend->buf_length, + execp->backend->buf_capacity - execp->backend->buf_length - 1); + if (count > 0) { + // Successful read + execp->backend->buf_length += count; + execp->backend->buf_output[execp->backend->buf_length] = '\0'; + continue; + } else if (count == 0) { + // End of file + command_finished = 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 + command_finished = TRUE; + break; + } + break; + } + + if (command_finished) { + execp->backend->child = 0; + close(execp->backend->child_pipe); + execp->backend->child_pipe = -1; + if (execp->backend->interval) + execp->backend->timer = + add_timeout(execp->backend->interval * 1000, 0, execp_timer_callback, execp, &execp->backend->timer); + } + + if (!execp->backend->continuous && command_finished) { + 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_output); + } else { + char *text = strchr(execp->backend->buf_output, '\n'); + if (text) { + *text = '\0'; + text++; + execp->backend->text = strdup(text); + } else { + execp->backend->text = strdup(""); + } + execp->backend->icon_path = strdup(execp->backend->buf_output); + } + int len = strlen(execp->backend->text); + if (len > 0 && execp->backend->text[len - 1] == '\n') + execp->backend->text[len - 1] = '\0'; + execp->backend->buf_length = 0; + execp->backend->buf_output[execp->backend->buf_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) { + // Count lines in buffer + int num_lines = 0; + char *last = execp->backend->buf_output; + char *end = NULL; + for (char *c = execp->backend->buf_output; *c; c++) { + if (*c == '\n') { + num_lines++; + if (num_lines == execp->backend->continuous) + end = c; + } + last = c; + } + if (*last && *last != '\n') + num_lines++; + 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_output); + } else { + char *text = strchr(execp->backend->buf_output, '\n'); + if (text) { + *text = '\0'; + text++; + execp->backend->text = strdup(text); + } else { + execp->backend->text = strdup(""); + } + execp->backend->icon_path = strdup(execp->backend->buf_output); + } + int 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; + int copied = next - execp->backend->buf_output; + int remaining = execp->backend->buf_length - copied; + if (remaining > 0) { + memmove(execp->backend->buf_output, next, remaining); + execp->backend->buf_length = remaining; + execp->backend->buf_output[execp->backend->buf_length] = '\0'; + } else { + execp->backend->buf_length = 0; + execp->backend->buf_output[execp->backend->buf_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) +{ + if (seconds < 60) { + sprintf(buffer, "%ds", seconds); + } else if (seconds < 60 * 60) { + int m = seconds / 60; + seconds = seconds % 60; + int s = seconds; + sprintf(buffer, "%d:%ds", m, s); + } else { + int h = seconds / (60 * 60); + seconds = seconds % (60 * 60); + int m = seconds / 60; + seconds = seconds % 60; + int s = seconds; + sprintf(buffer, + "%d:%d:%ds", + h, + m, + s); + } + return buffer; +} + +char *execp_get_tooltip(void *obj) +{ + 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 < 0) { + // Not executing command + if (execp->backend->last_update_finish_time) { + // We updated at least once + if (execp->backend->interval > 0) { + sprintf(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), + time_to_string((int)execp->backend->last_update_duration, tmp_buf2), + time_to_string((int)(execp->backend->interval - (now - execp->backend->last_update_finish_time)), + tmp_buf3)); + } else { + sprintf(execp->backend->tooltip_text, + "Last update finished %s ago (took %s).", + time_to_string((int)(now - execp->backend->last_update_finish_time), tmp_buf1), + time_to_string((int)execp->backend->last_update_duration, tmp_buf2)); + } + } else { + // we never requested an update + sprintf(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 + sprintf(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), + time_to_string((int)(now - execp->backend->last_update_start_time), tmp_buf3)); + } else { + // we never finished an update + sprintf(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)); + } + } + return strdup(execp->backend->tooltip_text); +}
A src/execplugin/execplugin.h

@@ -0,0 +1,138 @@

+#ifndef EXECPLUGIN_H +#define EXECPLUGIN_H + +#include <sys/time.h> +#include <pango/pangocairo.h> + +#include "area.h" +#include "common.h" +#include "timer.h" + +// Architecture: +// Panel panel_config contains an array of Execp, each storing all config options and all the state variables. +// Only these run commands. +// +// Tint2 maintains an array of Panels, one for each monitor. Each stores an array of Execp which was initially copied +// from panel_config. Each works as a frontend to the corresponding Execp in panel_config as backend, using the +// backend's config and state variables. + +typedef struct ExecpBackend { + // Config: + // Command to execute at a specified interval + char *command; + // Interval in seconds + int interval; + // 1 if first line of output is an icon path + gboolean has_icon; + gboolean cache_icon; + int icon_w; + int icon_h; + char *tooltip; + gboolean centered; + PangoFontDescription *font_desc; + Color font_color; + int continuous; + char *lclick_command; + char *mclick_command; + char *rclick_command; + char *uwheel_command; + char *dwheel_command; + // paddingxlr = horizontal padding left/right + // paddingx = horizontal padding between childs + int paddingxlr, paddingx, paddingy; + Background *bg; + + // Backend state: + timeout *timer; + int child_pipe; + pid_t child; + + // Command output buffer + char *buf_output; + int buf_length; + int buf_capacity; + + // Text extracted from the output buffer + char *text; + // Icon path extracted from the output buffer + char *icon_path; + Imlib_Image icon; + char tooltip_text[512]; + + // The time the last command was started + time_t last_update_start_time; + // The time the last output was obtained + time_t last_update_finish_time; + // The time it took to execute last command + time_t last_update_duration; + + // List of Execp which are frontends for this backend, one for each panel + GList *instances; +} ExecpBackend; + +typedef struct ExecpFrontend { + // Frontend state: + int iconx; + int icony; + int textx; + int texty; + int textw; + int texth; +} ExecpFrontend; + +typedef struct Execp { + Area area; + // All elements have the backend pointer set. However only backend elements have ownership. + ExecpBackend *backend; + // Set only for frontend Execp items. + ExecpFrontend *frontend; +} Execp; + + +// Called before the config is read and panel_config/panels are created. +// Afterwards, the config parsing code creates the array of Execp in panel_config and populates the configuration fields +// in the backend. +// Probably does nothing. +void default_execp(); + +// Creates a new Execp item with only the backend field set. The state is NOT initialized. The config is initialized to +// the default values. +// This will be used by the config code to populate its backedn config fields. +Execp *create_execp(); + +void destroy_execp(void *obj); + +// Called after the config is read and panel_config is populated, but before panels are created. +// Initializes the state of the backend items. +// panel_config.panel_items is used to determine which backend items are enabled. The others should be destroyed and +// removed from panel_config.execp_list. +void init_execp(); + +// Called after each on-screen panel is created, with a pointer to the panel. +// Initializes the state of the frontend items. Also adds a pointer to it in backend->instances. +// At this point the Area has not been added yet to the GUI tree, but it will be added right away. +void init_execp_panel(void *panel); + +// Called just before the panels are destroyed. Afterwards, tint2 exits or restarts and reads the config again. +// Releases all frontends and then all the backends. +// The frontend items are not freed by this function, only their members. The items are Areas which are freed in the +// GUI element tree cleanup function (remove_area). +void cleanup_execp(); + + +// Called on draw, obj = pointer to the front-end Execp item. +void draw_execp(void *obj, cairo_t *c); + +// Called on resize, obj = pointer to the front-end Execp item. +// Returns 1 if the new size is different than the previous size. +gboolean resize_execp(void *obj); + +// Called on mouse click event. +void execp_action(void *obj, int button); + +// Called to check if new output from the command can be read. +// No command might be running. +// Returns 1 if the output has been updated and a redraw is needed. +gboolean read_execp(void *obj); + +#endif // EXECPLUGIN_H
M src/panel.csrc/panel.c

@@ -163,6 +163,7 @@ #ifdef ENABLE_BATTERY

init_battery(); #endif init_taskbar(); + init_execp(); // number of panels (one monitor or 'all' monitors) if (panel_config.monitor >= 0)

@@ -212,6 +213,8 @@ if (panel_items_order[k] == 'C')

init_clock_panel(p); if (panel_items_order[k] == 'F' && !strstr(panel_items_order, "T")) init_freespace_panel(p); + if (panel_items_order[k] == 'E') + init_execp_panel(p); } set_panel_items_order(p);

@@ -512,6 +515,7 @@ g_list_free(p->area.children);

p->area.children = 0; } + int i_execp = 0; for (int k = 0; k < strlen(panel_items_order); k++) { if (panel_items_order[k] == 'L') { p->area.children = g_list_append(p->area.children, &p->launcher);

@@ -533,6 +537,12 @@ if (panel_items_order[k] == 'C')

p->area.children = g_list_append(p->area.children, &p->clock); if (panel_items_order[k] == 'F') p->area.children = g_list_append(p->area.children, &p->freespace); + if (panel_items_order[k] == 'E') { + GList *item = g_list_nth(p->execp_list, i_execp); + i_execp++; + if (item) + p->area.children = g_list_append(p->area.children, (Area*)item->data); + } } initialize_positions(&p->area, 0); }

@@ -865,6 +875,22 @@ }

return FALSE; } #endif + +Execp *click_execp(Panel *panel, int x, int y) +{ + GList *l; + for (l = panel->execp_list; l; l = l->next) { + Execp *execp = (Execp *)l->data; + if (panel_horizontal) { + if (execp->area.on_screen && x >= execp->area.posx && x <= (execp->area.posx + execp->area.width)) + return execp; + } else { + if (execp->area.on_screen && y >= execp->area.posy && y <= (execp->area.posy + execp->area.height)) + return execp; + } + } + return NULL; +} Area *click_area(Panel *panel, int x, int y) {
M src/panel.hsrc/panel.h

@@ -21,6 +21,7 @@ #include "taskbar.h"

#include "systraybar.h" #include "launcher.h" #include "freespace.h" +#include "execplugin.h" #ifdef ENABLE_BATTERY #include "battery.h"

@@ -123,6 +124,7 @@ #endif

Launcher launcher; FreeSpace freespace; + GList *execp_list; // Autohide gboolean is_hidden;

@@ -170,6 +172,7 @@ gboolean click_battery(Panel *panel, int x, int y);

#endif Area *click_area(Panel *panel, int x, int y); +Execp *click_execp(Panel *panel, int x, int y); void autohide_show(void *p); void autohide_hide(void *p);
M src/tint.csrc/tint.c

@@ -121,6 +121,7 @@ default_clock();

default_launcher(); default_taskbar(); default_tooltip(); + default_execp(); default_panel(); // read options

@@ -314,6 +315,7 @@ }

void cleanup() { + cleanup_execp(); cleanup_systray(); cleanup_tooltip(); cleanup_clock();

@@ -477,6 +479,8 @@ else

return 0; } #endif + if (click_execp(panel, e->x, e->y)) + return 1; return 0; }

@@ -630,6 +634,15 @@ task_drag = 0;

return; } #endif + + Execp *execp = click_execp(panel, e->xbutton.x, e->xbutton.y); + if (execp) { + execp_action(execp, e->xbutton.button); + if (panel_layer == BOTTOM_LAYER) + XLowerWindow(server.dsp, panel->main_win); + task_drag = 0; + return; + } if (e->xbutton.button == 1 && click_launcher(panel, e->xbutton.x, e->xbutton.y)) { LauncherIcon *icon = click_launcher_icon(panel, e->xbutton.x, e->xbutton.y);

@@ -1346,6 +1359,14 @@ if (sn_pipe_valid) {

FD_SET(sn_pipe[0], &fdset); maxfd = maxfd < sn_pipe[0] ? sn_pipe[0] : maxfd; } + for (GList *l = panel_config.execp_list; l; l = l->next) { + Execp *execp = (Execp *)l->data; + int fd = execp->backend->child_pipe; + if (fd > 0) { + FD_SET(fd, &fdset); + maxfd = maxfd < fd ? fd : maxfd; + } + } if (ufd > 0) { FD_SET(ufd, &fdset); maxfd = maxfd < ufd ? ufd : maxfd;

@@ -1361,6 +1382,17 @@ if (sn_pipe_valid) {

char buffer[1]; while (read(sn_pipe[0], buffer, sizeof(buffer)) > 0) { sigchld_handler_async(); + } + } + 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 = l_instance->data; + instance->area.resize_needed = TRUE; + panel_refresh = TRUE; + } } } if (XPending(server.dsp) > 0) {
M src/util/common.hsrc/util/common.h

@@ -97,4 +97,6 @@

// Clears the pixmap (with transparent color) void clear_pixmap(Pixmap p, int x, int y, int w, int h); +#define free_and_null(p) { free(p); p = NULL; } + #endif
M tint2.filestint2.files

@@ -161,3 +161,5 @@ src/battery/openbsd.c

src/util/uevent.c src/util/uevent.h .clang-format +src/execplugin/execplugin.c +src/execplugin/execplugin.h
M tint2.includestint2.includes

@@ -20,3 +20,4 @@ /usr/include/startup-notification-1.0

po src/tint2conf/po src/freespace +src/execplugin