From 1b6fd91611041b9abd182950636c7f45a437c03f Mon Sep 17 00:00:00 2001 From: o9000 Date: Wed, 15 Nov 2017 11:28:03 +0100 Subject: [PATCH] Taskbar: thumbnails (optimizations) --- CMakeLists.txt | 2 +- src/init.c | 6 ++ src/server.h | 1 + src/taskbar/task.c | 7 +- src/util/window.c | 221 +++++++++++++++++++++++++++++++++++++++++---- src/util/window.h | 2 +- 6 files changed, 214 insertions(+), 25 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4164353..9da6ed7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ endif() include( FindPkgConfig ) include( CheckLibraryExists ) include( CheckCSourceCompiles ) -pkg_check_modules( X11 REQUIRED x11 xcomposite xdamage xinerama xrender xrandr>=1.3 ) +pkg_check_modules( X11 REQUIRED x11 xcomposite xdamage xinerama xext xrender xrandr>=1.3 ) pkg_check_modules( PANGOCAIRO REQUIRED pangocairo ) pkg_check_modules( PANGO REQUIRED pango ) pkg_check_modules( CAIRO REQUIRED cairo ) diff --git a/src/init.c b/src/init.c index cb1b7fe..de61c53 100644 --- a/src/init.c +++ b/src/init.c @@ -6,6 +6,11 @@ #include #include +#include +#include +#include +#include + #include "config.h" #include "drag_and_drop.h" #include "fps_distribution.h" @@ -193,6 +198,7 @@ void init_X11_pre_config() server.screen = DefaultScreen(server.display); server.root_win = RootWindow(server.display, server.screen); server.desktop = get_current_desktop(); + server.has_shm = XShmQueryExtension(server.display); // Needed since the config file uses '.' as decimal separator setlocale(LC_ALL, ""); diff --git a/src/server.h b/src/server.h index f81a2d0..d2a3f0e 100644 --- a/src/server.h +++ b/src/server.h @@ -147,6 +147,7 @@ typedef struct Server { Global_atom atom; int xdamage_event_type; int xdamage_event_error_type; + gboolean has_shm; #ifdef HAVE_SN SnDisplay *sn_display; GTree *pids; diff --git a/src/taskbar/task.c b/src/taskbar/task.c index 8ecc1dc..3d11538 100644 --- a/src/taskbar/task.c +++ b/src/taskbar/task.c @@ -629,16 +629,15 @@ void task_refresh_thumbnail(Task *task) double now = get_time(); if (now - task->thumbnail_last_update < 0.1) return; - cairo_surface_t *thumbnail = get_window_thumbnail(task->win, panel_config.g_task.thumbnail_width); + fprintf(stderr, "tint2: thumbnail for window: %s" RESET "\n", task->title ? task->title : ""); + cairo_surface_t *thumbnail = get_window_thumbnail(task->win, panel_config.g_task.thumbnail_width, task->current_state == TASK_ACTIVE); if (!thumbnail) return; if (task->thumbnail) cairo_surface_destroy(task->thumbnail); task->thumbnail = thumbnail; task->thumbnail_last_update = get_time(); - if (task->thumbnail_last_update - now > 0.01) { - fprintf(stderr, YELLOW "tint2: %s took %f ms (window: %s)" RESET "\n", __func__, 1000 * (task->thumbnail_last_update - now), task->title ? task->title : ""); - } + fprintf(stderr, YELLOW "tint2: %s took %f ms (window: %s)" RESET "\n", __func__, 1000 * (task->thumbnail_last_update - now), task->title ? task->title : ""); if (g_tooltip.mapped && (g_tooltip.area == &task->area)) { tooltip_update_contents_for(&task->area); tooltip_update(); diff --git a/src/util/window.c b/src/util/window.c index 012850f..9a22d6d 100644 --- a/src/util/window.c +++ b/src/util/window.c @@ -29,6 +29,10 @@ #include #include +#include +#include +#include + #include "common.h" #include "window.h" #include "server.h" @@ -357,8 +361,146 @@ char *get_window_name(Window win) return result; } -cairo_surface_t *get_window_thumbnail(Window win, int size) +void smooth_thumbnail(cairo_surface_t *image_surface) { + u_int32_t *data = (u_int32_t *)cairo_image_surface_get_data(image_surface); + const size_t tw = cairo_image_surface_get_width(image_surface); + const size_t th = cairo_image_surface_get_height(image_surface); + const size_t rmask = 0xff0000; + const size_t gmask = 0xff00; + const size_t bmask = 0xff; + for (size_t i = 0; i < tw * (th - 1) - 1; i++) { + u_int32_t c1 = data[i]; + u_int32_t c2 = data[i+1]; + u_int32_t c3 = data[i+tw]; + u_int32_t b = (6 * (c1 & bmask) + (c2 & bmask) + (c3 & bmask)) / 8; + u_int32_t g = (6 * (c1 & gmask) + (c2 & gmask) + (c3 & gmask)) / 8; + u_int32_t r = (6 * (c1 & rmask) + (c2 & rmask) + (c3 & rmask)) / 8; + data[i] = (r & rmask) | (g & gmask) | (b & bmask); + } +} + +cairo_surface_t *screenshot(Window win, size_t size) +{ + cairo_surface_t *result = NULL; + XWindowAttributes wa; + if (!XGetWindowAttributes(server.display, win, &wa) || wa.width <= 0 || wa.height <= 0) + goto err0; + + size_t w, h; + w = (size_t)wa.width; + h = (size_t)wa.height; + size_t tw, th, fw; + size_t ox, oy; + tw = size; + th = h * tw / w; + if (th > tw * 0.618) { + th = (size_t)(tw * 0.618); + fw = w * th / h; + ox = (tw - fw) / 2; + oy = 0; + } else { + fw = tw; + ox = oy = 0; + } + + XShmSegmentInfo shminfo; + XImage *ximg = XShmCreateImage(server.display, + wa.visual, + (unsigned)wa.depth, + ZPixmap, + NULL, + &shminfo, + (unsigned)wa.width, + (unsigned)wa.height); + if (!ximg) { + fprintf(stderr, "tint2: !ximg\n"); + goto err0; + } + shminfo.shmid = shmget(IPC_PRIVATE, (size_t)(ximg->bytes_per_line * ximg->height), IPC_CREAT | 0777); + if (shminfo.shmid < 0) { + fprintf(stderr, "tint2: !shmget\n"); + goto err1; + } + shminfo.shmaddr = ximg->data = (char *)shmat(shminfo.shmid, 0, 0); + if (!shminfo.shmaddr) { + fprintf(stderr, "tint2: !shmat\n"); + goto err2; + } + shminfo.readOnly = False; + if (!XShmAttach(server.display, &shminfo)) { + fprintf(stderr, "tint2: !xshmattach\n"); + goto err3; + } + if (!XShmGetImage(server.display, win, ximg, 0, 0, AllPlanes)) { + fprintf(stderr, "tint2: !xshmgetimage\n"); + goto err4; + } + + result = cairo_image_surface_create(CAIRO_FORMAT_RGB24, (int)tw, (int)th); + u_int32_t *data = (u_int32_t *)cairo_image_surface_get_data(result); + memset(data, 0, tw * th); + + const size_t xstep = w / fw; + const size_t ystep = h / th; + const size_t offset_x1 = xstep / 4; + const size_t offset_y1 = ystep / 4; + const size_t offset_x2 = 3 * xstep / 4; + const size_t offset_y2 = ystep / 4; + const size_t offset_x3 = xstep / 4; + const size_t offset_y3 = 3 * ystep / 4; + const size_t offset_x4 = 3 * xstep / 4; + const size_t offset_y4 = ystep / 4; + const size_t offset_x5 = xstep / 2; + const size_t offset_y5 = ystep / 2; + const size_t offset_x6 = 5 * xstep / 8; + const size_t offset_y6 = 3 * ystep / 16; + const size_t offset_x7 = 3 * xstep / 16; + const size_t offset_y7 = 5 * ystep / 8; + const size_t rmask = 0xff0000; + const size_t gmask = 0xff00; + const size_t bmask = 0xff; + for (size_t yt = 0, y = 0; yt < th; yt++, y += ystep) { + for (size_t xt = 0, x = 0; xt < fw; xt++, x += xstep) { + size_t j = yt * tw + ox + xt; + u_int32_t c1 = (u_int32_t)XGetPixel(ximg, (int)(x + offset_x1), (int)(y + offset_y1)); + u_int32_t c2 = (u_int32_t)XGetPixel(ximg, (int)(x + offset_x2), (int)(y + offset_y2)); + u_int32_t c3 = (u_int32_t)XGetPixel(ximg, (int)(x + offset_x3), (int)(y + offset_y3)); + u_int32_t c4 = (u_int32_t)XGetPixel(ximg, (int)(x + offset_x4), (int)(y + offset_y4)); + u_int32_t c5 = (u_int32_t)XGetPixel(ximg, (int)(x + offset_x5), (int)(y + offset_y5)); + u_int32_t c6 = (u_int32_t)XGetPixel(ximg, (int)(x + offset_x6), (int)(y + offset_y6)); + u_int32_t c7 = (u_int32_t)XGetPixel(ximg, (int)(x + offset_x7), (int)(y + offset_y7)); + u_int32_t b = ((c1 & bmask) + (c2 & bmask) + (c3 & bmask) + (c4 & bmask) + (c5 & bmask) * 2 + (c6 & bmask) + + (c7 & bmask)) / + 8; + u_int32_t g = ((c1 & gmask) + (c2 & gmask) + (c3 & gmask) + (c4 & gmask) + (c5 & gmask) * 2 + (c6 & gmask) + + (c7 & gmask)) / + 8; + u_int32_t r = ((c1 & rmask) + (c2 & rmask) + (c3 & rmask) + (c4 & rmask) + (c5 & rmask) * 2 + (c6 & rmask) + + (c7 & rmask)) / + 8; + data[j] = (r & rmask) | (g & gmask) | (b & bmask); + } + } + + // 2nd pass + smooth_thumbnail(result); + +err4: + XShmDetach(server.display, &shminfo); +err3: + shmdt(shminfo.shmaddr); +err2: + shmctl(shminfo.shmid, IPC_RMID, NULL); +err1: + XDestroyImage(ximg); +err0: + return result; +} + +cairo_surface_t *get_window_thumbnail_cairo(Window win, int size) +{ + static cairo_filter_t filter = CAIRO_FILTER_BEST; XWindowAttributes wa; if (!XGetWindowAttributes(server.display, win, &wa)) return NULL; @@ -373,40 +515,81 @@ cairo_surface_t *get_window_thumbnail(Window win, int size) th = h * tw / w; if (th > tw * 0.618) { th = (int)(tw * 0.618); - sy = th/(double)h; + sy = th / (double)h; double fw = w * th / h; sx = fw / w; ox = (tw - fw) / 2; oy = 0; } else { - sx = tw/(double)w; - sy = th/(double)h; + sx = tw / (double)w; + sy = th / (double)h; ox = oy = 0; } - cairo_surface_t *x11_surface = - cairo_xlib_surface_create(server.display, win, wa.visual, w, h); - cairo_surface_t *image_surface = cairo_surface_create_similar_image(x11_surface, CAIRO_FORMAT_ARGB32, tw, th); + cairo_surface_t *x11_surface = cairo_xlib_surface_create(server.display, win, wa.visual, w, h); + cairo_surface_t *full_surface = cairo_surface_create_similar_image(x11_surface, CAIRO_FORMAT_ARGB32, w, h); + cairo_t *cr0 = cairo_create(full_surface); + cairo_set_source_surface(cr0, x11_surface, 0, 0); + cairo_paint(cr0); + cairo_destroy(cr0); + + cairo_surface_t *image_surface = cairo_surface_create_similar_image(full_surface, CAIRO_FORMAT_ARGB32, tw, th); + + double start_time = get_time(); cairo_t *cr = cairo_create(image_surface); cairo_translate(cr, ox, oy); cairo_scale(cr, sx, sy); - cairo_set_source_surface(cr, x11_surface, 0, 0); - cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_GAUSSIAN); + cairo_set_source_surface(cr, full_surface, 0, 0); + cairo_pattern_set_filter(cairo_get_source(cr), filter); cairo_paint(cr); cairo_destroy(cr); cairo_surface_destroy(x11_surface); + if (filter == CAIRO_FILTER_FAST) + smooth_thumbnail(image_surface); + double end_time = get_time(); - uint32_t *pixels = (uint32_t *)cairo_image_surface_get_data(image_surface); - gboolean empty = TRUE; - for (int i = 0; empty && i < tw * th; i += 4) { - if (pixels[i] & 0xffFFff) - empty = FALSE; - } - if (empty) { - cairo_surface_destroy(image_surface); - return NULL; - } + if (end_time - start_time > 0.030) + filter = CAIRO_FILTER_FAST; + else if (end_time - start_time < 0.010) + filter = CAIRO_FILTER_BEST; + + return image_surface; +} + +gboolean cairo_surface_is_blank(cairo_surface_t *image_surface) +{ + uint32_t *pixels = (uint32_t *)cairo_image_surface_get_data(image_surface); + gboolean empty = TRUE; + int size = cairo_image_surface_get_width(image_surface) * cairo_image_surface_get_height(image_surface); + for (int i = 0; empty && i < size; i++) { + if (pixels[i] & 0xffFFff) + empty = FALSE; + } + return empty; +} + +cairo_surface_t *get_window_thumbnail(Window win, int size, gboolean active) +{ + cairo_surface_t *image_surface = NULL; + if (server.has_shm && server.composite_manager) { + image_surface = screenshot(win, (size_t)size); + if (cairo_surface_is_blank(image_surface)) { + cairo_surface_destroy(image_surface); + image_surface = NULL; + } + } + + if (!image_surface) { + image_surface = get_window_thumbnail_cairo(win, size); + if (cairo_surface_is_blank(image_surface)) { + cairo_surface_destroy(image_surface); + image_surface = NULL; + } + } + + if (!image_surface) + return NULL; return image_surface; } diff --git a/src/util/window.h b/src/util/window.h index f88980d..2b6b144 100644 --- a/src/util/window.h +++ b/src/util/window.h @@ -34,6 +34,6 @@ int get_icon_count(gulong *data, int num); gulong *get_best_icon(gulong *data, int icon_count, int num, int *iw, int *ih, int best_icon_size); char *get_window_name(Window win); -cairo_surface_t *get_window_thumbnail(Window win, int size); +cairo_surface_t *get_window_thumbnail(Window win, int size, gboolean active); #endif