]> git.mdlowis.com Git - proto/labwc.git/commitdiff
osd: support window switcher with thumbnails
authortokyo4j <hrak1529@gmail.com>
Fri, 8 Aug 2025 05:53:34 +0000 (14:53 +0900)
committerJohan Malm <johanmalm@users.noreply.github.com>
Fri, 29 Aug 2025 19:42:01 +0000 (20:42 +0100)
The new-style window switcher can be enabled with
<windowSwitcher style="thumbnail">.

New theme entries:

osd.window-switcher.style-thumbnail.width.max: 80%
osd.window-switcher.style-thumbnail.padding: 4
osd.window-switcher.style-thumbnail.item.width: 300
osd.window-switcher.style-thumbnail.item.height: 250
osd.window-switcher.style-thumbnail.item.padding: 10
osd.window-switcher.style-thumbnail.item.active.border.width: 2
osd.window-switcher.style-thumbnail.item.active.border.color: #589bda
osd.window-switcher.style-thumbnail.item.active.bg.color: #c7e2fc
osd.window-switcher.style-thumbnail.item.icon.size: 60

13 files changed:
docs/labwc-config.5.scd
docs/labwc-theme.5.scd
docs/rc.xml.all
docs/themerc
include/config/rcxml.h
include/config/types.h
include/osd.h
include/theme.h
src/config/rcxml.c
src/osd/meson.build
src/osd/osd-thumbnail.c [new file with mode: 0644]
src/osd/osd.c
src/theme.c

index e86fb75f1b296644904676f6f5aa6572c1afc708..1dc59a8c4e1663c78c5c85208a01155666da10c2 100644 (file)
@@ -278,7 +278,7 @@ this is for compatibility with Openbox.
 ## WINDOW SWITCHER
 
 ```
-<windowSwitcher show="yes" preview="yes" outlines="yes" allWorkspaces="no">
+<windowSwitcher show="yes" style="classic" preview="yes" outlines="yes" allWorkspaces="no">
   <fields>
     <field content="icon" width="5%" />
     <field content="desktop_entry_name" width="30%" />
@@ -287,10 +287,14 @@ this is for compatibility with Openbox.
 </windowSwitcher>
 ```
 
-*<windowSwitcher show="" preview="" outlines="" allWorkspaces="">*
+*<windowSwitcher show="" style="" preview="" outlines="" allWorkspaces="">*
        *show* [yes|no] Draw the OnScreenDisplay when switching between
        windows. Default is yes.
 
+       *style* [classic|thumbnail] Configures the style of the OnScreenDisplay.
+       "classic" displays window information like icons and titles in a vertical list.
+       "thumbnail" shows window thumbnail, icon and title in grids.
+
        *preview* [yes|no] Preview the contents of the selected window when
        switching between windows. Default is yes.
 
@@ -302,7 +306,7 @@ this is for compatibility with Openbox.
        are shown).
 
 *<windowSwitcher><fields><field content="" width="%">*
-       Define window switcher fields.
+       Define window switcher fields when using *<windowSwitcher style="classic" />*.
 
        *content* defines what the field shows and can be any of:
 
index dc24961186a1cdc9d13f907383eaac857cce7c7d..efc3a1a507b328aa5f614bca61ae329d201337e5 100644 (file)
@@ -303,6 +303,10 @@ all are supported.
        Text color of on-screen-display. Inherits
        *window.active.label.text.color* if not set.
 
+*osd.window-switcher.style-classic*
+       Theme for window switcher when using <windowSwitcher style="classic" />.
+       See below for details.
+
 *osd.window-switcher.style-classic.width*
        Width of window switcher in pixels. Width can also be a percentage of the
        monitor width by adding '%' as suffix (e.g. 70%). Default is 600.
@@ -328,6 +332,40 @@ all are supported.
        If not set, the font size derived from <theme><font place="OnScreenDisplay">
        is used.
 
+*osd.window-switcher.style-thumbnail*
+       Theme for window switcher when using <windowSwitcher style="thumbnail" />.
+       See below for details.
+
+*osd.window-switcher.style-thumbnail.width.max*
+       Maximum width of window switcher in pixels. Width can also be a percentage of
+       the monitor width by adding '%' as suffix (e.g. 70%). Default is 80%.
+
+*osd.window-switcher.style-thumbnail.padding*
+       Padding of window switcher in pixels. This is the space between the
+       window-switcher border and its items. Default is 4.
+
+*osd.window-switcher.style-thumbnail.item.width*
+       Width of window switcher items in pixels. Default is 300.
+
+*osd.window-switcher.style-thumbnail.item.height*
+       Height of window switcher items in pixels. Default is 250.
+
+*osd.window-switcher.style-thumbnail.item.padding*
+       Padding of window switcher items in pixels. This is the space between the
+       border around selected items and window thumbnail. Default is 2.
+
+*osd.window-switcher.style-thumbnail.item.active.border.width*
+       Border width of selected window switcher items in pixels. Default is 2.
+
+*osd.window-switcher.style-thumbnail.item.active.border.color*
+       Color of border around selected window switcher items. Default is #589bda.
+
+*osd.window-switcher.style-thumbnail.item.active.bg.color*
+       Color of selected window switcher items. Default is #c7e2fc.
+
+*osd.window-switcher.style-thumbnail.item.icon.size*
+       Size of window icons in window switcher items in pixels. Default is 60.
+
 *osd.window-switcher.preview.border.width*
        Border width of the outlines shown as the preview of the window selected
        by window switcher. Inherits *osd.border.width* if not set.
index 91dc77913e05546e85767def1c706c3078264902..940b2f96698800a920148202f303f18e02d35ba6 100644 (file)
@@ -75,7 +75,7 @@
     </font>
   </theme>
 
-  <windowSwitcher show="yes" preview="yes" outlines="yes" allWorkspaces="no">
+  <windowSwitcher show="yes" style="classic" preview="yes" outlines="yes" allWorkspaces="no">
     <fields>
       <field content="icon" width="5%" />
       <field content="desktop_entry_name" width="30%" />
index a4a707991c399b3fe6cb2cd8c37f09c3eac7cbd6..7b251a3c7eca4c54451d64ccd4261a0dfc887263 100644 (file)
@@ -99,6 +99,17 @@ osd.window-switcher.style-classic.item.padding.y: 1
 osd.window-switcher.style-classic.item.active.border.width: 2
 # The icon size the same as the font size by default
 # osd.window-switcher.style-classic.item.icon.size: 50
+
+osd.window-switcher.style-thumbnail.width.max: 80%
+osd.window-switcher.style-thumbnail.padding: 4
+osd.window-switcher.style-thumbnail.item.width: 300
+osd.window-switcher.style-thumbnail.item.height: 250
+osd.window-switcher.style-thumbnail.item.padding: 10
+osd.window-switcher.style-thumbnail.item.active.border.width: 2
+osd.window-switcher.style-thumbnail.item.active.border.color: #589bda
+osd.window-switcher.style-thumbnail.item.active.bg.color: #c7e2fc
+osd.window-switcher.style-thumbnail.item.icon.size: 60
+
 osd.window-switcher.preview.border.width: 1
 osd.window-switcher.preview.border.color: #dddda6,#000000,#dddda6
 
index 86daf4e4bcae23081cda495ec68a1e6a8233a50d..0da3811406e5c0e59ad52e7037ad40c61172d12c 100644 (file)
@@ -175,6 +175,7 @@ struct rcxml {
                bool outlines;
                enum lab_view_criteria criteria;
                struct wl_list fields;  /* struct window_switcher_field.link */
+               enum window_switcher_style style;
        } window_switcher;
 
        struct wl_list window_rules; /* struct window_rule.link */
index 75f207ac12a27a206cf4ee297491648aaf060934..e832a65896a4c9a7cd373d4f2f95ec3aea27631b 100644 (file)
@@ -107,4 +107,9 @@ enum lab_window_type {
        LAB_WINDOW_TYPE_LEN
 };
 
+enum window_switcher_style {
+       WINDOW_SWITCHER_CLASSIC,
+       WINDOW_SWITCHER_THUMBNAIL,
+};
+
 #endif /* LABWC_CONFIG_TYPES_H */
index ad0b3bce1c9f4b29ec2e236be4f7d7b2158ef297..a5c2a4cc3deb00d413fe484b1a1bcb30939e1ba6 100644 (file)
@@ -83,5 +83,6 @@ struct osd_impl {
 };
 
 extern struct osd_impl osd_classic_impl;
+extern struct osd_impl osd_thumbnail_impl;
 
 #endif // LABWC_OSD_H
index 6f4a81c62967a810fda19797bfb00d1c4b8aabe3..75f3c8c0bca980776668a260214dbc1ec8e60013 100644 (file)
@@ -180,6 +180,21 @@ struct theme {
                int item_height;
        } osd_window_switcher_classic;
 
+       struct window_switcher_thumbnail_theme {
+               int max_width;
+               int padding;
+               int item_width;
+               int item_height;
+               int item_padding;
+               int item_active_border_width;
+               float item_active_border_color[4];
+               float item_active_bg_color[4];
+               int item_icon_size;
+               bool max_width_is_percent;
+
+               int title_height;
+       } osd_window_switcher_thumbnail;
+
        int osd_window_switcher_preview_border_width;
        float osd_window_switcher_preview_border_color[3][4];
 
index 280c0256a57ea87dfe833f2fb2efa79493e1cc36..26b56ec6b41c59a4a07704b10f35f38eb9e3f9b8 100644 (file)
@@ -1210,6 +1210,12 @@ entry(xmlNode *node, char *nodename, char *content)
        /* <windowSwitcher show="" preview="" outlines="" /> */
        } else if (!strcasecmp(nodename, "show.windowSwitcher")) {
                set_bool(content, &rc.window_switcher.show);
+       } else if (!strcasecmp(nodename, "style.windowSwitcher")) {
+               if (!strcasecmp(content, "classic")) {
+                       rc.window_switcher.style = WINDOW_SWITCHER_CLASSIC;
+               } else if (!strcasecmp(content, "thumbnail")) {
+                       rc.window_switcher.style = WINDOW_SWITCHER_THUMBNAIL;
+               }
        } else if (!strcasecmp(nodename, "preview.windowSwitcher")) {
                set_bool(content, &rc.window_switcher.preview);
        } else if (!strcasecmp(nodename, "outlines.windowSwitcher")) {
@@ -1431,6 +1437,7 @@ rcxml_init(void)
        rc.snap_tiling_events_mode = LAB_TILING_EVENTS_ALWAYS;
 
        rc.window_switcher.show = true;
+       rc.window_switcher.style = WINDOW_SWITCHER_CLASSIC;
        rc.window_switcher.preview = true;
        rc.window_switcher.outlines = true;
        rc.window_switcher.criteria = LAB_VIEW_CRITERIA_CURRENT_WORKSPACE
index 4f4da2cba6b282d357b24d4589744cd0716a362a..17b4bf513cd1db0a974bc349e1f976c274e3011d 100644 (file)
@@ -2,4 +2,5 @@ labwc_sources += files(
   'osd.c',
   'osd-classic.c',
   'osd-field.c',
+  'osd-thumbnail.c',
 )
diff --git a/src/osd/osd-thumbnail.c b/src/osd/osd-thumbnail.c
new file mode 100644 (file)
index 0000000..2f37d50
--- /dev/null
@@ -0,0 +1,280 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <assert.h>
+#include <wlr/render/swapchain.h>
+#include <wlr/types/wlr_scene.h>
+#include <wlr/types/wlr_compositor.h>
+#include <wlr/render/allocator.h>
+#include "config/rcxml.h"
+#include "config/types.h"
+#include "common/array.h"
+#include "common/box.h"
+#include "common/lab-scene-rect.h"
+#include "common/scaled-font-buffer.h"
+#include "common/scaled-icon-buffer.h"
+#include "labwc.h"
+#include "osd.h"
+#include "output.h"
+#include "theme.h"
+
+struct osd_thumbnail_scene_item {
+       struct view *view;
+       struct wlr_scene_tree *tree;
+       struct scaled_font_buffer *normal_title;
+       struct scaled_font_buffer *active_title;
+       struct lab_scene_rect *active_bg;
+};
+
+static void
+render_node(struct server *server, struct wlr_render_pass *pass,
+               struct wlr_scene_node *node, int x, int y)
+{
+       switch (node->type) {
+       case WLR_SCENE_NODE_TREE: {
+               struct wlr_scene_tree *tree = wlr_scene_tree_from_node(node);
+               struct wlr_scene_node *child;
+               wl_list_for_each(child, &tree->children, link) {
+                       render_node(server, pass, child, x + node->x, y + node->y);
+               }
+               break;
+       }
+       case WLR_SCENE_NODE_BUFFER: {
+               struct wlr_scene_buffer *scene_buffer =
+                       wlr_scene_buffer_from_node(node);
+               if (!scene_buffer->buffer) {
+                       break;
+               }
+               struct wlr_texture *texture = wlr_texture_from_buffer(
+                       server->renderer, scene_buffer->buffer);
+               if (!texture) {
+                       break;
+               }
+               wlr_render_pass_add_texture(pass, &(struct wlr_render_texture_options){
+                       .texture = texture,
+                       .src_box = scene_buffer->src_box,
+                       .dst_box = {
+                               .x = x,
+                               .y = y,
+                               .width = scene_buffer->dst_width,
+                               .height = scene_buffer->dst_height,
+                       },
+                       .transform = scene_buffer->transform,
+               });
+               wlr_texture_destroy(texture);
+               break;
+       }
+       case WLR_SCENE_NODE_RECT:
+               /* should be unreached */
+               wlr_log(WLR_ERROR, "ignoring rect");
+               break;
+       }
+}
+
+static struct wlr_buffer *
+render_thumb(struct output *output, struct view *view)
+{
+       struct server *server = output->server;
+       struct wlr_buffer *buffer = wlr_allocator_create_buffer(server->allocator,
+               view->current.width, view->current.height,
+               &output->wlr_output->swapchain->format);
+       struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(
+               server->renderer, buffer, NULL);
+       render_node(server, pass, &view->content_tree->node, 0, 0);
+       if (!wlr_render_pass_submit(pass)) {
+               wlr_log(WLR_ERROR, "failed to submit render pass");
+               wlr_buffer_drop(buffer);
+               return NULL;
+       }
+       return buffer;
+}
+
+static struct scaled_font_buffer *
+create_title(struct wlr_scene_tree *parent,
+               struct window_switcher_thumbnail_theme *switcher_theme,
+               const char *title, const float *title_color,
+               const float *bg_color, int y)
+{
+       struct scaled_font_buffer *buffer =
+               scaled_font_buffer_create(parent);
+       scaled_font_buffer_update(buffer, title,
+               switcher_theme->item_width - 2 * switcher_theme->item_padding,
+               &rc.font_osd, title_color, bg_color);
+       wlr_scene_node_set_position(&buffer->scene_buffer->node,
+               (switcher_theme->item_width - buffer->width) / 2, y);
+       return buffer;
+}
+
+static struct osd_thumbnail_scene_item *
+create_item_scene(struct wlr_scene_tree *parent, struct view *view,
+               struct output *output)
+{
+       struct server *server = output->server;
+       struct theme *theme = server->theme;
+       struct window_switcher_thumbnail_theme *switcher_theme =
+               &theme->osd_window_switcher_thumbnail;
+       int padding = theme->border_width + switcher_theme->item_padding;
+       int title_y = switcher_theme->item_height - padding - switcher_theme->title_height;
+       struct wlr_box thumb_bounds = {
+               .x = padding,
+               .y = padding,
+               .width = switcher_theme->item_width - 2 * padding,
+               .height = title_y - 2 * padding,
+       };
+       if (thumb_bounds.width <= 0 || thumb_bounds.height <= 0) {
+               wlr_log(WLR_ERROR, "too small thumbnail area");
+               return NULL;
+       }
+
+       struct osd_thumbnail_scene_item *item =
+               wl_array_add(&output->osd_scene.items, sizeof(*item));
+       item->tree = wlr_scene_tree_create(parent);
+       item->view = view;
+
+       /* background for selected item */
+       struct lab_scene_rect_options opts = {
+               .width = switcher_theme->item_width,
+               .height = switcher_theme->item_height,
+               .bg_color = switcher_theme->item_active_bg_color,
+               .nr_borders = 1,
+               .border_colors = (float *[1]) { switcher_theme->item_active_border_color },
+               .border_width = switcher_theme->item_active_border_width,
+       };
+       item->active_bg = lab_scene_rect_create(item->tree, &opts);
+
+       /* thumbnail */
+       struct wlr_buffer *thumb_buffer = render_thumb(output, view);
+       if (thumb_buffer) {
+               struct wlr_scene_buffer *thumb_scene_buffer =
+                       wlr_scene_buffer_create(item->tree, thumb_buffer);
+               wlr_buffer_drop(thumb_buffer);
+               struct wlr_box thumb_box = box_fit_within(
+                       thumb_buffer->width, thumb_buffer->height,
+                       &thumb_bounds);
+               wlr_scene_buffer_set_dest_size(thumb_scene_buffer,
+                       thumb_box.width, thumb_box.height);
+               wlr_scene_node_set_position(&thumb_scene_buffer->node,
+                       thumb_box.x, thumb_box.y);
+       }
+
+       /* title */
+       const char *title = view_get_string_prop(view, "title");
+       if (title) {
+               item->normal_title = create_title(item->tree, switcher_theme,
+                       title, theme->osd_label_text_color,
+                       theme->osd_bg_color, title_y);
+               item->active_title = create_title(item->tree, switcher_theme,
+                       title, theme->osd_label_text_color,
+                       switcher_theme->item_active_bg_color, title_y);
+       }
+
+       /* icon */
+       int icon_size = switcher_theme->item_icon_size;
+       struct scaled_icon_buffer *icon_buffer = scaled_icon_buffer_create(
+               item->tree, server, icon_size, icon_size);
+       scaled_icon_buffer_set_view(icon_buffer, view);
+       int x = (switcher_theme->item_width - icon_size) / 2;
+       int y = title_y - padding - icon_size + 10; /* slide by 10px */
+       wlr_scene_node_set_position(&icon_buffer->scene_buffer->node, x, y);
+
+       return item;
+}
+
+static void
+get_items_geometry(struct output *output, struct theme *theme,
+               int nr_thumbs, int *nr_rows, int *nr_cols)
+{
+       struct window_switcher_thumbnail_theme *thumb_theme =
+               &theme->osd_window_switcher_thumbnail;
+       int output_width = output->wlr_output->width / output->wlr_output->scale;
+
+       int max_bg_width = thumb_theme->max_width;
+       if (thumb_theme->max_width_is_percent) {
+               max_bg_width = output_width * thumb_theme->max_width / 100;
+       }
+
+       *nr_rows = 1;
+       *nr_cols = nr_thumbs;
+       while (1) {
+               assert(*nr_rows <= nr_thumbs);
+               int bg_width = *nr_cols * thumb_theme->item_width
+                       + theme->osd_border_width + thumb_theme->padding;
+               if (bg_width < max_bg_width) {
+                       break;
+               }
+               if (*nr_rows >= nr_thumbs) {
+                       break;
+               }
+               (*nr_rows)++;
+               *nr_cols = ceilf((float)nr_thumbs / *nr_rows);
+       }
+}
+
+static void
+osd_thumbnail_create(struct output *output, struct wl_array *views)
+{
+       assert(!output->osd_scene.tree);
+
+       struct theme *theme = output->server->theme;
+       struct window_switcher_thumbnail_theme *switcher_theme =
+               &theme->osd_window_switcher_thumbnail;
+       int padding = theme->osd_border_width + switcher_theme->padding;
+
+       output->osd_scene.tree = wlr_scene_tree_create(output->osd_tree);
+
+       int nr_views = wl_array_len(views);
+       assert(nr_views > 0);
+       int nr_rows, nr_cols;
+       get_items_geometry(output, theme, nr_views, &nr_rows, &nr_cols);
+
+       /* items */
+       struct view **view;
+       int index = 0;
+       wl_array_for_each(view, views) {
+               struct osd_thumbnail_scene_item *item = create_item_scene(
+                       output->osd_scene.tree, *view, output);
+               if (!item) {
+                       break;
+               }
+               int x = (index % nr_cols) * switcher_theme->item_width + padding;
+               int y = (index / nr_cols) * switcher_theme->item_height + padding;
+               wlr_scene_node_set_position(&item->tree->node, x, y);
+               index++;
+       }
+
+       /* background */
+       struct lab_scene_rect_options bg_opts = {
+               .width = nr_cols * switcher_theme->item_width + 2 * padding,
+               .height = nr_rows * switcher_theme->item_height + 2 * padding,
+               .bg_color = theme->osd_bg_color,
+               .nr_borders = 1,
+               .border_width = theme->osd_border_width,
+               .border_colors = (float *[1]) { theme->osd_border_color },
+       };
+       struct lab_scene_rect *bg =
+               lab_scene_rect_create(output->osd_scene.tree, &bg_opts);
+       wlr_scene_node_lower_to_bottom(&bg->tree->node);
+
+       /* center */
+       struct wlr_box usable = output_usable_area_in_layout_coords(output);
+       int lx = usable.x + (usable.width - bg_opts.width) / 2;
+       int ly = usable.y + (usable.height - bg_opts.height) / 2;
+       wlr_scene_node_set_position(&output->osd_scene.tree->node, lx, ly);
+}
+
+static void
+osd_thumbnail_update(struct output *output)
+{
+       struct osd_thumbnail_scene_item *item;
+       wl_array_for_each(item, &output->osd_scene.items) {
+               bool active = (item->view == output->server->osd_state.cycle_view);
+               wlr_scene_node_set_enabled(&item->active_bg->tree->node, active);
+               wlr_scene_node_set_enabled(
+                       &item->active_title->scene_buffer->node, active);
+               wlr_scene_node_set_enabled(
+                       &item->normal_title->scene_buffer->node, !active);
+       }
+}
+
+struct osd_impl osd_thumbnail_impl = {
+       .create = osd_thumbnail_create,
+       .update = osd_thumbnail_update,
+};
index 9bef5ce3129180c170002a9fef0d1bea8914405b..28714d7265bf8074acbef1d710a31442776b671a 100644 (file)
@@ -265,7 +265,16 @@ update_osd(struct server *server)
        struct wl_array views;
        wl_array_init(&views);
        view_array_append(server, &views, rc.window_switcher.criteria);
-       struct osd_impl *osd_impl = &osd_classic_impl;
+
+       struct osd_impl *osd_impl = NULL;
+       switch (rc.window_switcher.style) {
+       case WINDOW_SWITCHER_CLASSIC:
+               osd_impl = &osd_classic_impl;
+               break;
+       case WINDOW_SWITCHER_THUMBNAIL:
+               osd_impl = &osd_thumbnail_impl;
+               break;
+       }
 
        if (!wl_array_len(&views) || !server->osd_state.cycle_view) {
                osd_finish(server);
index f874e58580971b168a560dc369240d5b08f1f608..45e42c15f432c67debb41c2dad28a97a83b508f4 100644 (file)
@@ -616,6 +616,17 @@ theme_builtin(struct theme *theme, struct server *server)
        theme->osd_window_switcher_classic.item_active_border_width = 2;
        theme->osd_window_switcher_classic.item_icon_size = -1;
 
+       theme->osd_window_switcher_thumbnail.max_width = 80;
+       theme->osd_window_switcher_thumbnail.max_width_is_percent = true;
+       theme->osd_window_switcher_thumbnail.padding = 4;
+       theme->osd_window_switcher_thumbnail.item_width = 300;
+       theme->osd_window_switcher_thumbnail.item_height = 250;
+       theme->osd_window_switcher_thumbnail.item_padding = 10;
+       theme->osd_window_switcher_thumbnail.item_active_border_width = 2;
+       parse_color("#589bda", theme->osd_window_switcher_thumbnail.item_active_border_color);
+       parse_color("#c7e2fc", theme->osd_window_switcher_thumbnail.item_active_bg_color);
+       theme->osd_window_switcher_thumbnail.item_icon_size = 60;
+
        /* inherit settings in post_processing() if not set elsewhere */
        theme->osd_window_switcher_preview_border_width = INT_MIN;
        zero_array(theme->osd_window_switcher_preview_border_color);
@@ -681,6 +692,8 @@ entry(struct theme *theme, const char *key, const char *value)
 
        struct window_switcher_classic_theme *switcher_classic_theme =
                &theme->osd_window_switcher_classic;
+       struct window_switcher_thumbnail_theme *switcher_thumb_theme =
+               &theme->osd_window_switcher_thumbnail;
 
        /*
         * Note that in order for the pattern match to apply to more than just
@@ -992,6 +1005,47 @@ entry(struct theme *theme, const char *key, const char *value)
                        get_int_if_positive(value,
                                "osd.window-switcher.style-classic.item.icon.size");
        }
+       /* thumbnail window switcher */
+       if (match_glob(key, "osd.window-switcher.style-thumbnail.width.max")) {
+               if (strrchr(value, '%')) {
+                       switcher_thumb_theme->max_width_is_percent = true;
+               } else {
+                       switcher_thumb_theme->max_width_is_percent = false;
+               }
+               switcher_thumb_theme->max_width = get_int_if_positive(
+                       value, "osd.window-switcher.style-thumbnail.width.max");
+       }
+       if (match_glob(key, "osd.window-switcher.style-thumbnail.padding")) {
+               switcher_thumb_theme->padding = get_int_if_positive(
+                       value, "osd.window-switcher.style-thumbnail.padding");
+       }
+       if (match_glob(key, "osd.window-switcher.style-thumbnail.item.width")) {
+               switcher_thumb_theme->item_width = get_int_if_positive(
+                       value, "osd.window-switcher.style-thumbnail.item.width");
+       }
+       if (match_glob(key, "osd.window-switcher.style-thumbnail.item.height")) {
+               switcher_thumb_theme->item_height = get_int_if_positive(
+                       value, "osd.window-switcher.style-thumbnail.item.height");
+       }
+       if (match_glob(key, "osd.window-switcher.style-thumbnail.item.padding")) {
+               switcher_thumb_theme->item_padding = get_int_if_positive(
+                       value, "osd.window-switcher.style-thumbnail.item.padding");
+       }
+       if (match_glob(key, "osd.window-switcher.style-thumbnail.item.active.border.width")) {
+               switcher_thumb_theme->item_active_border_width = get_int_if_positive(
+                       value, "osd.window-switcher.style-thumbnail.item.active.border.width");
+       }
+       if (match_glob(key, "osd.window-switcher.style-thumbnail.item.active.border.color")) {
+               parse_color(value, switcher_thumb_theme->item_active_border_color);
+       }
+       if (match_glob(key, "osd.window-switcher.style-thumbnail.item.active.bg.color")) {
+               parse_color(value, switcher_thumb_theme->item_active_bg_color);
+       }
+       if (match_glob(key, "osd.window-switcher.style-thumbnail.item.icon.size")) {
+               switcher_thumb_theme->item_icon_size = get_int_if_positive(
+                       value, "osd.window-switcher.style-thumbnail.item.icon.size");
+       }
+
        if (match_glob(key, "osd.window-switcher.preview.border.width")) {
                theme->osd_window_switcher_preview_border_width =
                        get_int_if_positive(
@@ -1594,6 +1648,8 @@ post_processing(struct theme *theme)
 {
        struct window_switcher_classic_theme *switcher_classic_theme =
                &theme->osd_window_switcher_classic;
+       struct window_switcher_thumbnail_theme *switcher_thumb_theme =
+               &theme->osd_window_switcher_thumbnail;
 
        theme->titlebar_height = get_titlebar_height(theme);
 
@@ -1607,6 +1663,7 @@ post_processing(struct theme *theme)
                + 2 * theme->menu_items_padding_y;
 
        int osd_font_height = font_height(&rc.font_osd);
+       switcher_thumb_theme->title_height = osd_font_height;
        if (switcher_classic_theme->item_icon_size <= 0) {
                switcher_classic_theme->item_icon_size = osd_font_height;
        }