diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 18a3efea..2506e43c 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -437,9 +437,13 @@ extending outward from the snapped edge. ** The name of the Openbox theme to use. It is not set by default. +** + The name of the icon theme to use. It is not set by default. + ** Selection and order of buttons in a window's titlebar. The following identifiers can be used, each only once: + - 'icon': window icon - 'menu': window menu - 'iconify': iconify - 'max': maximize toggle @@ -624,6 +628,7 @@ extending outward from the snapped edge. buttons and the window title are shown. - Title: The area of the titlebar (including blank space) between the window buttons, where the window title is displayed. + - Icon: A window icon that, by default, displays a window menu. - WindowMenu: A button that, by default, displays a window menu. - Iconify: A button that, by default, iconifies a window. - Maximize: A button that, by default, toggles maximization of a window. diff --git a/docs/rc.xml.all b/docs/rc.xml.all index b14ab6b0..10e0bd80 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -28,8 +28,9 @@ + - menu:iconify,max,close + icon:iconify,max,close yes 8 @@ -457,6 +458,15 @@ + + + + + + + + + diff --git a/include/button/button-png.h b/include/button/button-png.h deleted file mode 100644 index 31d01ff5..00000000 --- a/include/button/button-png.h +++ /dev/null @@ -1,9 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -#ifndef LABWC_BUTTON_PNG_H -#define LABWC_BUTTON_PNG_H - -struct lab_data_buffer; - -void button_png_load(const char *button_name, struct lab_data_buffer **buffer); - -#endif /* LABWC_BUTTON_PNG_H */ diff --git a/include/button/button-svg.h b/include/button/button-svg.h deleted file mode 100644 index 884a8846..00000000 --- a/include/button/button-svg.h +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -#ifndef LABWC_BUTTON_SVG_H -#define LABWC_BUTTON_SVG_H - -struct lab_data_buffer; - -void button_svg_load(const char *button_name, struct lab_data_buffer **buffer, - int size); - -#endif /* LABWC_BUTTON_SVG_H */ diff --git a/include/button/button-xbm.h b/include/button/button-xbm.h deleted file mode 100644 index dd0e3169..00000000 --- a/include/button/button-xbm.h +++ /dev/null @@ -1,22 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -#ifndef LABWC_BUTTON_XBM_H -#define LABWC_BUTTON_XBM_H - -struct lab_data_buffer; - -/** - * button_xbm_from_bitmap() - create button from monochrome bitmap - * @bitmap: bitmap data array in hexadecimal xbm format - * @buffer: cairo-surface-buffer to create - * @rgba: color - * - * Example bitmap: char button[6] = { 0x3f, 0x3f, 0x21, 0x21, 0x21, 0x3f }; - */ -void button_xbm_from_bitmap(const char *bitmap, struct lab_data_buffer **buffer, - float *rgba); - -/* button_xbm_load - Convert xbm file to buffer with cairo surface */ -void button_xbm_load(const char *button_name, struct lab_data_buffer **buffer, - float *rgba); - -#endif /* LABWC_BUTTON_XBM_H */ diff --git a/include/button/common.h b/include/button/common.h deleted file mode 100644 index b3b5039c..00000000 --- a/include/button/common.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -#ifndef LABWC_BUTTON_COMMON_H -#define LABWC_BUTTON_COMMON_H - -#include - -/** - * button_filename() - Get full filename for button. - * @name: The name of the button (for example 'iconify.xbm'). - * @buf: Buffer to fill with the full filename - * @len: Length of buffer - * - * Example return value: /usr/share/themes/Numix/openbox-3/iconify.xbm - */ -void button_filename(const char *name, char *buf, size_t len); - -#endif /* LABWC_BUTTON_COMMON_H */ diff --git a/include/config/default-bindings.h b/include/config/default-bindings.h index 46a3819e..d901d9fa 100644 --- a/include/config/default-bindings.h +++ b/include/config/default-bindings.h @@ -376,6 +376,32 @@ static struct mouse_combos { .name = "atCursor", .value = "no", }, + }, { + .context = "Icon", + .button = "Left", + .event = "Click", + .action = "ShowMenu", + .attributes[0] = { + .name = "menu", + .value = "client-menu", + }, + .attributes[1] = { + .name = "atCursor", + .value = "no", + }, + }, { + .context = "Icon", + .button = "Right", + .event = "Click", + .action = "ShowMenu", + .attributes[0] = { + .name = "menu", + .value = "client-menu", + }, + .attributes[1] = { + .name = "atCursor", + .value = "no", + }, }, { .context = "Root", .button = "Left", diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 80ce4df7..dd237fa4 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -81,6 +81,7 @@ struct rcxml { /* theme */ char *theme_name; + char *icon_theme_name; struct wl_list title_buttons_left; struct wl_list title_buttons_right; int corner_radius; diff --git a/include/icon-loader.h b/include/icon-loader.h new file mode 100644 index 00000000..e6741ddb --- /dev/null +++ b/include/icon-loader.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef LABWC_ICON_LOADER_H +#define LABWC_ICON_LOADER_H + +struct server; + +void icon_loader_init(struct server *server); +void icon_loader_finish(struct server *server); +struct lab_data_buffer *icon_loader_lookup(struct server *server, + const char *app_id, int size, int scale); + +#endif /* LABWC_ICON_LOADER_H */ diff --git a/include/img/img-png.h b/include/img/img-png.h new file mode 100644 index 00000000..1347a53f --- /dev/null +++ b/include/img/img-png.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef LABWC_IMG_PNG_H +#define LABWC_IMG_PNG_H + +struct lab_data_buffer; + +void img_png_load(const char *filename, struct lab_data_buffer **buffer); + +#endif /* LABWC_IMG_PNG_H */ diff --git a/include/img/img-svg.h b/include/img/img-svg.h new file mode 100644 index 00000000..6843774d --- /dev/null +++ b/include/img/img-svg.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef LABWC_IMG_SVG_H +#define LABWC_IMG_SVG_H + +struct lab_data_buffer; + +void img_svg_load(const char *filename, struct lab_data_buffer **buffer, + int size); + +#endif /* LABWC_IMG_SVG_H */ diff --git a/include/img/img-xbm.h b/include/img/img-xbm.h new file mode 100644 index 00000000..24ad24d8 --- /dev/null +++ b/include/img/img-xbm.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef LABWC_IMG_XBM_H +#define LABWC_IMG_XBM_H + +struct lab_data_buffer; + +/** + * img_xbm_from_bitmap() - create button from monochrome bitmap + * @bitmap: bitmap data array in hexadecimal xbm format + * @buffer: cairo-surface-buffer to create + * @rgba: color + * + * Example bitmap: char button[6] = { 0x3f, 0x3f, 0x21, 0x21, 0x21, 0x3f }; + */ +void img_xbm_from_bitmap(const char *bitmap, struct lab_data_buffer **buffer, + float *rgba); + +/* img_xbm_load - Convert xbm file to buffer with cairo surface */ +void img_xbm_load(const char *filename, struct lab_data_buffer **buffer, + float *rgba); + +#endif /* LABWC_IMG_XBM_H */ diff --git a/include/labwc.h b/include/labwc.h index 93eaf0c2..af6ef399 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -365,6 +365,8 @@ struct server { struct menu *menu_current; struct wl_list menus; + struct icon_loader *icon_loader; + pid_t primary_client_pid; }; diff --git a/include/ssd-internal.h b/include/ssd-internal.h index 3c0fe38b..14bd3265 100644 --- a/include/ssd-internal.h +++ b/include/ssd-internal.h @@ -73,6 +73,8 @@ struct ssd { struct ssd_state_title_width active; struct ssd_state_title_width inactive; } title; + + char *app_id; } state; /* An invisible area around the view which allows resizing */ @@ -145,6 +147,8 @@ struct ssd_part *add_scene_button( void add_toggled_icon(struct ssd_button *button, struct wl_list *part_list, enum ssd_part_type type, struct wlr_buffer *icon_buffer, struct wlr_buffer *hover_buffer); +void update_window_icon_buffer(struct wlr_scene_node *button_node, + struct wlr_buffer *buffer); /* SSD internal helpers */ struct ssd_part *ssd_get_part( diff --git a/include/ssd.h b/include/ssd.h index 4a62d06a..daad81a5 100644 --- a/include/ssd.h +++ b/include/ssd.h @@ -24,6 +24,7 @@ enum ssd_part_type { LAB_SSD_BUTTON_CLOSE, LAB_SSD_BUTTON_MAXIMIZE, LAB_SSD_BUTTON_ICONIFY, + LAB_SSD_BUTTON_WINDOW_ICON, LAB_SSD_BUTTON_WINDOW_MENU, LAB_SSD_BUTTON_SHADE, LAB_SSD_BUTTON_OMNIPRESENT, @@ -87,6 +88,7 @@ void ssd_update_title(struct ssd *ssd); void ssd_update_geometry(struct ssd *ssd); void ssd_destroy(struct ssd *ssd); void ssd_set_titlebar(struct ssd *ssd, bool enabled); +void ssd_update_window_icon(struct ssd *ssd); void ssd_enable_keybind_inhibit_indicator(struct ssd *ssd, bool enable); void ssd_enable_shade(struct ssd *ssd, bool enable); diff --git a/meson.build b/meson.build index 3c63bda1..e5ccae58 100644 --- a/meson.build +++ b/meson.build @@ -73,6 +73,24 @@ pixman = dependency('pixman-1') math = cc.find_library('m') png = dependency('libpng') svg = dependency('librsvg-2.0', version: '>=2.46', required: false) +sfdo_basedir = dependency( + 'libsfdo-basedir', + default_options: ['default_library=static', 'examples=false', 'tests=false'], + version: '>=0.1.0', + required: not get_option('icon').disabled(), +) +sfdo_desktop = dependency( + 'libsfdo-desktop', + default_options: ['default_library=static', 'examples=false', 'tests=false'], + version: '>=0.1.0', + required: not get_option('icon').disabled(), +) +sfdo_icon = dependency( + 'libsfdo-icon', + default_options: ['default_library=static', 'examples=false', 'tests=false'], + version: '>=0.1.0', + required: not get_option('icon').disabled(), +) if get_option('xwayland').enabled() and not wlroots_has_xwayland error('no wlroots Xwayland support') @@ -88,6 +106,9 @@ else endif conf_data.set10('HAVE_RSVG', have_rsvg) +have_libsfdo = sfdo_basedir.found() and sfdo_desktop.found() and sfdo_icon.found() +conf_data.set10('HAVE_LIBSFDO', have_libsfdo) + if get_option('static_analyzer').enabled() add_project_arguments(['-fanalyzer'], language: 'c') endif @@ -126,6 +147,13 @@ if have_rsvg svg, ] endif +if have_libsfdo + labwc_deps += [ + sfdo_basedir, + sfdo_desktop, + sfdo_icon, + ] +endif subdir('include') subdir('src') diff --git a/meson_options.txt b/meson_options.txt index 4d6e8cd5..ec3fe85d 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,6 +1,7 @@ option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') option('xwayland', type: 'feature', value: 'auto', description: 'Enable support for X11 applications') option('svg', type: 'feature', value: 'enabled', description: 'Enable svg window buttons') +option('icon', type: 'feature', value: 'enabled', description: 'Enable window icons') option('nls', type: 'feature', value: 'auto', description: 'Enable native language support') option('static_analyzer', type: 'feature', value: 'disabled', description: 'Run gcc static analyzer') option('test', type: 'feature', value: 'disabled', description: 'Run tests') diff --git a/src/button/common.c b/src/button/common.c deleted file mode 100644 index 39400994..00000000 --- a/src/button/common.c +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -#include -#include -#include "button/common.h" -#include "common/dir.h" -#include "config/rcxml.h" -#include "labwc.h" - -void -button_filename(const char *name, char *buf, size_t len) -{ - struct wl_list paths; - paths_theme_create(&paths, rc.theme_name, name); - - /* - * You can't really merge buttons, so let's just iterate forwards - * and stop on the first hit - */ - struct path *path; - wl_list_for_each(path, &paths, link) { - if (access(path->string, R_OK) == 0) { - snprintf(buf, len, "%s", path->string); - break; - } - } - paths_destroy(&paths); -} diff --git a/src/config/mousebind.c b/src/config/mousebind.c index dc3dfb65..19169d55 100644 --- a/src/config/mousebind.c +++ b/src/config/mousebind.c @@ -114,6 +114,8 @@ context_from_str(const char *str) return LAB_SSD_BUTTON_ICONIFY; } else if (!strcasecmp(str, "WindowMenu")) { return LAB_SSD_BUTTON_WINDOW_MENU; + } else if (!strcasecmp(str, "Icon")) { + return LAB_SSD_BUTTON_WINDOW_ICON; } else if (!strcasecmp(str, "Shade")) { return LAB_SSD_BUTTON_SHADE; } else if (!strcasecmp(str, "AllDesktops")) { diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 88909f55..ab221c1b 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -151,7 +151,9 @@ fill_section(const char *content, struct wl_list *list) continue; } enum ssd_part_type type = LAB_SSD_NONE; - if (!strcmp(identifier, "menu")) { + if (!strcmp(identifier, "icon")) { + type = LAB_SSD_BUTTON_WINDOW_ICON; + } else if (!strcmp(identifier, "menu")) { type = LAB_SSD_BUTTON_WINDOW_MENU; } else if (!strcmp(identifier, "iconify")) { type = LAB_SSD_BUTTON_ICONIFY; @@ -1067,6 +1069,8 @@ entry(xmlNode *node, char *nodename, char *content) rc.placement_cascade_offset_y = atoi(content); } else if (!strcmp(nodename, "name.theme")) { rc.theme_name = xstrdup(content); + } else if (!strcmp(nodename, "icon.theme")) { + rc.icon_theme_name = xstrdup(content); } else if (!strcasecmp(nodename, "layout.titlebar.theme")) { fill_title_layout(content); } else if (!strcasecmp(nodename, "showTitle.titlebar.theme")) { @@ -1658,7 +1662,7 @@ post_processing(void) } if (!rc.title_layout_loaded) { - fill_title_layout("menu:iconify,max,close"); + fill_title_layout("icon:iconify,max,close"); } /* @@ -1898,6 +1902,7 @@ rcxml_finish(void) zfree(rc.font_menuitem.name); zfree(rc.font_osd.name); zfree(rc.theme_name); + zfree(rc.icon_theme_name); zfree(rc.workspace_config.prefix); struct title_button *p, *p_tmp; diff --git a/src/icon-loader.c b/src/icon-loader.c new file mode 100644 index 00000000..969c35af --- /dev/null +++ b/src/icon-loader.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include +#include "common/mem.h" +#include "config.h" +#include "icon-loader.h" +#include "img/img-png.h" + +#if HAVE_RSVG +#include "img/img-svg.h" +#endif + +#include "labwc.h" + +struct icon_loader { + struct sfdo_desktop_ctx *desktop_ctx; + struct sfdo_icon_ctx *icon_ctx; + struct sfdo_desktop_db *desktop_db; + struct sfdo_icon_theme *icon_theme; +}; + +void +icon_loader_init(struct server *server) +{ + struct icon_loader *loader = znew(*loader); + + struct sfdo_basedir_ctx *basedir_ctx = sfdo_basedir_ctx_create(); + if (!basedir_ctx) { + goto err_basedir_ctx; + } + loader->desktop_ctx = sfdo_desktop_ctx_create(basedir_ctx); + if (!loader->desktop_ctx) { + goto err_desktop_ctx; + } + loader->icon_ctx = sfdo_icon_ctx_create(basedir_ctx); + if (!loader->icon_ctx) { + goto err_icon_ctx; + } + loader->desktop_db = sfdo_desktop_db_load(loader->desktop_ctx, NULL); + if (!loader->desktop_db) { + goto err_desktop_db; + } + loader->icon_theme = sfdo_icon_theme_load(loader->icon_ctx, + rc.icon_theme_name, SFDO_ICON_THEME_LOAD_OPTIONS_DEFAULT); + if (!loader->icon_theme) { + goto err_icon_theme; + } + + /* basedir_ctx is not referenced by other objects */ + sfdo_basedir_ctx_destroy(basedir_ctx); + + server->icon_loader = loader; + return; + +err_icon_theme: + sfdo_desktop_db_destroy(loader->desktop_db); +err_desktop_db: + sfdo_icon_ctx_destroy(loader->icon_ctx); +err_icon_ctx: + sfdo_desktop_ctx_destroy(loader->desktop_ctx); +err_desktop_ctx: + sfdo_basedir_ctx_destroy(basedir_ctx); +err_basedir_ctx: + free(loader); + wlr_log(WLR_ERROR, "Failed to initialize icon loader"); +} + +void +icon_loader_finish(struct server *server) +{ + struct icon_loader *loader = server->icon_loader; + if (!loader) { + return; + } + + sfdo_desktop_db_destroy(loader->desktop_db); + sfdo_icon_ctx_destroy(loader->icon_ctx); + sfdo_desktop_ctx_destroy(loader->desktop_ctx); + free(loader); + server->icon_loader = NULL; +} + +struct lab_data_buffer * +icon_loader_lookup(struct server *server, const char *app_id, int size, int scale) +{ + struct icon_loader *loader = server->icon_loader; + if (!loader) { + return NULL; + } + + const char *icon_name = NULL; + struct sfdo_desktop_entry *entry = sfdo_desktop_db_get_entry_by_id( + loader->desktop_db, app_id, SFDO_NT); + if (entry) { + icon_name = sfdo_desktop_entry_get_icon(entry, NULL); + } + if (!icon_name) { + /* fall back to app id */ + icon_name = app_id; + } + + int lookup_options = SFDO_ICON_THEME_LOOKUP_OPTIONS_DEFAULT; +#if !HAVE_RSVG + lookup_options |= SFDO_ICON_THEME_LOOKUP_OPTION_NO_SVG; +#endif + struct sfdo_icon_file *icon_file = sfdo_icon_theme_lookup( + loader->icon_theme, icon_name, SFDO_NT, size, scale, + lookup_options); + if (!icon_file || icon_file == SFDO_ICON_FILE_INVALID) { + return NULL; + } + + struct lab_data_buffer *icon_buffer = NULL; + const char *icon_path = sfdo_icon_file_get_path(icon_file, NULL); + + wlr_log(WLR_DEBUG, "loading icon file %s", icon_path); + + switch (sfdo_icon_file_get_format(icon_file)) { + case SFDO_ICON_FILE_FORMAT_PNG: + img_png_load(icon_path, &icon_buffer); + break; + case SFDO_ICON_FILE_FORMAT_SVG: +#if HAVE_RSVG + img_svg_load(icon_path, &icon_buffer, size * scale); +#endif + break; + case SFDO_ICON_FILE_FORMAT_XPM: + /* XPM is not supported */ + break; + } + + sfdo_icon_file_destroy(icon_file); + + return icon_buffer; +} diff --git a/src/button/button-png.c b/src/img/img-png.c similarity index 78% rename from src/button/button-png.c rename to src/img/img-png.c index 2c700a06..07e6a8a7 100644 --- a/src/button/button-png.c +++ b/src/img/img-png.c @@ -10,8 +10,7 @@ #include #include #include "buffer.h" -#include "button/button-png.h" -#include "button/common.h" +#include "img/img-png.h" #include "common/string-helpers.h" #include "labwc.h" @@ -44,25 +43,22 @@ ispng(const char *filename) #undef PNG_BYTES_TO_CHECK void -button_png_load(const char *button_name, struct lab_data_buffer **buffer) +img_png_load(const char *filename, struct lab_data_buffer **buffer) { if (*buffer) { wlr_buffer_drop(&(*buffer)->base); *buffer = NULL; } - if (string_null_or_empty(button_name)) { + if (string_null_or_empty(filename)) { + return; + } + if (!ispng(filename)) { return; } - char path[4096] = { 0 }; - button_filename(button_name, path, sizeof(path)); - if (!ispng(path)) { - return; - } - - cairo_surface_t *image = cairo_image_surface_create_from_png(path); + cairo_surface_t *image = cairo_image_surface_create_from_png(filename); if (cairo_surface_status(image)) { - wlr_log(WLR_ERROR, "error reading png button '%s'", path); + wlr_log(WLR_ERROR, "error reading png button '%s'", filename); cairo_surface_destroy(image); return; } diff --git a/src/button/button-svg.c b/src/img/img-svg.c similarity index 85% rename from src/button/button-svg.c rename to src/img/img-svg.c index 2f27ea47..12c91102 100644 --- a/src/button/button-svg.c +++ b/src/img/img-svg.c @@ -10,25 +10,18 @@ #include #include #include "buffer.h" -#include "button/button-svg.h" -#include "button/common.h" +#include "img/img-svg.h" #include "common/string-helpers.h" #include "labwc.h" void -button_svg_load(const char *button_name, struct lab_data_buffer **buffer, +img_svg_load(const char *filename, struct lab_data_buffer **buffer, int size) { if (*buffer) { wlr_buffer_drop(&(*buffer)->base); *buffer = NULL; } - if (string_null_or_empty(button_name)) { - return; - } - - char filename[4096] = { 0 }; - button_filename(button_name, filename, sizeof(filename)); if (string_null_or_empty(filename)) { return; } diff --git a/src/button/button-xbm.c b/src/img/img-xbm.c similarity index 94% rename from src/button/button-xbm.c rename to src/img/img-xbm.c index 94ad238a..98d8f618 100644 --- a/src/button/button-xbm.c +++ b/src/img/img-xbm.c @@ -13,8 +13,7 @@ #include #include #include -#include "button/button-xbm.h" -#include "button/common.h" +#include "img/img-xbm.h" #include "common/grab-file.h" #include "common/mem.h" #include "common/string-helpers.h" @@ -257,7 +256,7 @@ parse_xbm_builtin(const char *button, int size) } void -button_xbm_from_bitmap(const char *bitmap, struct lab_data_buffer **buffer, +img_xbm_from_bitmap(const char *bitmap, struct lab_data_buffer **buffer, float *rgba) { struct pixmap pixmap = {0}; @@ -272,7 +271,7 @@ button_xbm_from_bitmap(const char *bitmap, struct lab_data_buffer **buffer, } void -button_xbm_load(const char *button_name, struct lab_data_buffer **buffer, +img_xbm_load(const char *filename, struct lab_data_buffer **buffer, float *rgba) { struct pixmap pixmap = {0}; @@ -280,14 +279,12 @@ button_xbm_load(const char *button_name, struct lab_data_buffer **buffer, wlr_buffer_drop(&(*buffer)->base); *buffer = NULL; } - if (string_null_or_empty(button_name)) { + if (string_null_or_empty(filename)) { return; } color = argb32(rgba); /* Read file into memory as it's easier to tokenize that way */ - char filename[4096] = { 0 }; - button_filename(button_name, filename, sizeof(filename)); struct buf token_buf = grab_file(filename); if (token_buf.len) { struct token *tokens = tokenize_xbm(token_buf.data); diff --git a/src/button/meson.build b/src/img/meson.build similarity index 52% rename from src/button/meson.build rename to src/img/meson.build index 2e587eaf..854898d0 100644 --- a/src/button/meson.build +++ b/src/img/meson.build @@ -1,12 +1,11 @@ labwc_sources += files( - 'button-png.c', - 'button-xbm.c', - 'common.c', + 'img-png.c', + 'img-xbm.c', ) if have_rsvg labwc_sources += files( - 'button-svg.c', + 'img-svg.c', ) endif diff --git a/src/meson.build b/src/meson.build index 9676bec1..37ed893a 100644 --- a/src/meson.build +++ b/src/meson.build @@ -44,8 +44,13 @@ if have_xwayland ) endif +if have_libsfdo + labwc_sources += files( + 'icon-loader.c', + ) +endif -subdir('button') +subdir('img') subdir('common') subdir('config') subdir('decorations') diff --git a/src/server.c b/src/server.c index 7f1caa30..bfe33c3c 100644 --- a/src/server.c +++ b/src/server.c @@ -25,6 +25,9 @@ #include "config/rcxml.h" #include "config/session.h" #include "decorations.h" +#if HAVE_LIBSFDO +#include "icon-loader.h" +#endif #include "idle.h" #include "labwc.h" #include "layers.h" @@ -56,6 +59,11 @@ reload_config_and_theme(struct server *server) theme_finish(server->theme); theme_init(server->theme, server, rc.theme_name); +#if HAVE_LIBSFDO + icon_loader_finish(server); + icon_loader_init(server); +#endif + struct view *view; wl_list_for_each(view, &server->views, link) { view_reload_ssd(view); @@ -547,6 +555,10 @@ server_init(struct server *server) layers_init(server); +#if HAVE_LIBSFDO + icon_loader_init(server); +#endif + #if HAVE_XWAYLAND xwayland_server_init(server, compositor); #endif @@ -597,4 +609,8 @@ server_finish(struct server *server) /* TODO: clean up various scene_tree nodes */ workspaces_destroy(server); + +#if HAVE_LIBSFDO + icon_loader_finish(server); +#endif } diff --git a/src/ssd/ssd-part.c b/src/ssd/ssd-part.c index 4a5eda72..d647a272 100644 --- a/src/ssd/ssd-part.c +++ b/src/ssd/ssd-part.c @@ -104,6 +104,23 @@ get_scale_box(struct wlr_buffer *buffer, double container_width, return icon_geo; } +void +update_window_icon_buffer(struct wlr_scene_node *button_node, + struct wlr_buffer *buffer) +{ + struct wlr_scene_buffer *scene_buffer = + wlr_scene_buffer_from_node(button_node); + + struct wlr_box icon_geo = get_scale_box(buffer, + rc.theme->window_button_width, + rc.theme->title_height); + + wlr_scene_buffer_set_buffer(scene_buffer, buffer); + wlr_scene_buffer_set_dest_size(scene_buffer, + icon_geo.width, icon_geo.height); + wlr_scene_node_set_position(button_node, icon_geo.x, icon_geo.y); +} + struct ssd_part * add_scene_button(struct wl_list *part_list, enum ssd_part_type type, struct wlr_scene_tree *parent, struct wlr_buffer *icon_buffer, diff --git a/src/ssd/ssd-titlebar.c b/src/ssd/ssd-titlebar.c index 1b2f56ac..923ae91e 100644 --- a/src/ssd/ssd-titlebar.c +++ b/src/ssd/ssd-titlebar.c @@ -4,10 +4,14 @@ #include #include #include "buffer.h" +#include "config.h" #include "common/mem.h" #include "common/scaled-font-buffer.h" #include "common/scene-helpers.h" #include "common/string-helpers.h" +#if HAVE_LIBSFDO +#include "icon-loader.h" +#endif #include "labwc.h" #include "node.h" #include "ssd-internal.h" @@ -34,6 +38,7 @@ add_button(struct ssd *ssd, struct ssd_sub_tree *subtree, enum ssd_part_type typ struct ssd_button *btn; switch (type) { + case LAB_SSD_BUTTON_WINDOW_ICON: /* fallthrough */ case LAB_SSD_BUTTON_WINDOW_MENU: add_scene_button(&subtree->parts, type, parent, active ? &theme->button_menu_active_unpressed->base @@ -171,6 +176,7 @@ ssd_titlebar_create(struct ssd *ssd) update_visible_buttons(ssd); ssd_update_title(ssd); + ssd_update_window_icon(ssd); bool maximized = view->maximized == VIEW_AXIS_BOTH; if (maximized) { @@ -368,7 +374,9 @@ ssd_titlebar_update(struct ssd *ssd) wlr_scene_node_set_position(part->node, x, 0); } } FOR_EACH_END + ssd_update_title(ssd); + ssd_update_window_icon(ssd); } void @@ -386,8 +394,10 @@ ssd_titlebar_destroy(struct ssd *ssd) } FOR_EACH_END if (ssd->state.title.text) { - free(ssd->state.title.text); - ssd->state.title.text = NULL; + zfree(ssd->state.title.text); + } + if (ssd->state.app_id) { + zfree(ssd->state.app_id); } wlr_scene_node_destroy(&ssd->titlebar.tree->node); @@ -624,4 +634,50 @@ ssd_should_be_squared(struct ssd *ssd) && view->maximized != VIEW_AXIS_BOTH; } +void +ssd_update_window_icon(struct ssd *ssd) +{ +#if HAVE_LIBSFDO + const char *app_id = view_get_string_prop(ssd->view, "app_id"); + if (string_null_or_empty(app_id)) { + return; + } + if (ssd->state.app_id && !strcmp(ssd->state.app_id, app_id)) { + return; + } + + free(ssd->state.app_id); + ssd->state.app_id = xstrdup(app_id); + + struct theme *theme = ssd->view->server->theme; + + int icon_size = MIN(theme->window_button_width, + theme->title_height - 2 * theme->padding_height); + /* TODO: take into account output scales */ + int icon_scale = 1; + + struct lab_data_buffer *icon_buffer = icon_loader_lookup( + ssd->view->server, app_id, icon_size, icon_scale); + if (!icon_buffer) { + wlr_log(WLR_DEBUG, "icon could not be loaded for %s", app_id); + return; + } + + struct ssd_sub_tree *subtree; + FOR_EACH_STATE(ssd, subtree) { + struct ssd_part *part = + ssd_get_part(&subtree->parts, LAB_SSD_BUTTON_WINDOW_ICON); + if (!part) { + break; + } + + struct ssd_button *button = node_ssd_button_from_node(part->node); + update_window_icon_buffer(button->normal, &icon_buffer->base); + update_window_icon_buffer(button->hover, &icon_buffer->base); + } FOR_EACH_END + + wlr_buffer_drop(&icon_buffer->base); +#endif +} + #undef FOR_EACH_STATE diff --git a/src/theme.c b/src/theme.c index 6965d39e..50980af1 100644 --- a/src/theme.c +++ b/src/theme.c @@ -29,14 +29,14 @@ #include "common/parse-double.h" #include "common/string-helpers.h" #include "config/rcxml.h" -#include "button/button-png.h" +#include "img/img-png.h" #include "labwc.h" #if HAVE_RSVG -#include "button/button-svg.h" +#include "img/img-svg.h" #endif -#include "button/button-xbm.h" +#include "img/img-xbm.h" #include "theme.h" #include "buffer.h" #include "ssd.h" @@ -222,6 +222,36 @@ create_hover_fallback(struct theme *theme, const char *icon_name, } } +/* + * Scan theme directories with button names (name + postfix) and write the full + * path of the found button file to @buf. An empty string is set if a button + * file is not found. + */ +static void +get_button_filename(char *buf, size_t len, const char *name, const char *postfix) +{ + buf[0] = '\0'; + + char filename[4096]; + snprintf(filename, sizeof(filename), "%s%s", name, postfix); + + struct wl_list paths; + paths_theme_create(&paths, rc.theme_name, filename); + + /* + * You can't really merge buttons, so let's just iterate forwards + * and stop on the first hit + */ + struct path *path; + wl_list_for_each(path, &paths, link) { + if (access(path->string, R_OK) == 0) { + snprintf(buf, len, "%s", path->string); + break; + } + } + paths_destroy(&paths); +} + /* * We use the following button filename schema: "BUTTON [TOGGLED] [STATE]" * with the words separated by underscore, and the following meaning: @@ -394,31 +424,31 @@ load_buttons(struct theme *theme) zdrop(b->inactive.buffer); /* PNG */ - snprintf(filename, sizeof(filename), "%s-active.png", b->name); - button_png_load(filename, b->active.buffer); - snprintf(filename, sizeof(filename), "%s-inactive.png", b->name); - button_png_load(filename, b->inactive.buffer); + get_button_filename(filename, sizeof(filename), b->name, "-active.png"); + img_png_load(filename, b->active.buffer); + get_button_filename(filename, sizeof(filename), b->name, "-inactive.png"); + img_png_load(filename, b->inactive.buffer); #if HAVE_RSVG /* SVG */ int size = theme->title_height - 2 * theme->padding_height; if (!*b->active.buffer) { - snprintf(filename, sizeof(filename), "%s-active.svg", b->name); - button_svg_load(filename, b->active.buffer, size); + get_button_filename(filename, sizeof(filename), b->name, "-active.svg"); + img_svg_load(filename, b->active.buffer, size); } if (!*b->inactive.buffer) { - snprintf(filename, sizeof(filename), "%s-inactive.svg", b->name); - button_svg_load(filename, b->inactive.buffer, size); + get_button_filename(filename, sizeof(filename), b->name, "-inactive.svg"); + img_svg_load(filename, b->inactive.buffer, size); } #endif /* XBM */ - snprintf(filename, sizeof(filename), "%s.xbm", b->name); + get_button_filename(filename, sizeof(filename), b->name, ".xbm"); if (!*b->active.buffer) { - button_xbm_load(filename, b->active.buffer, b->active.rgba); + img_xbm_load(filename, b->active.buffer, b->active.rgba); } if (!*b->inactive.buffer) { - button_xbm_load(filename, b->inactive.buffer, b->inactive.rgba); + img_xbm_load(filename, b->inactive.buffer, b->inactive.rgba); } /* @@ -426,15 +456,15 @@ load_buttons(struct theme *theme) * For example max_hover_toggled instead of max_toggled_hover */ if (b->alt_name) { - snprintf(filename, sizeof(filename), "%s.xbm", b->alt_name); + get_button_filename(filename, sizeof(filename), b->alt_name, ".xbm"); } else { filename[0] = '\0'; } if (!*b->active.buffer) { - button_xbm_load(filename, b->active.buffer, b->active.rgba); + img_xbm_load(filename, b->active.buffer, b->active.rgba); } if (!*b->inactive.buffer) { - button_xbm_load(filename, b->inactive.buffer, b->inactive.rgba); + img_xbm_load(filename, b->inactive.buffer, b->inactive.rgba); } /* @@ -447,11 +477,11 @@ load_buttons(struct theme *theme) continue; } if (!*b->active.buffer) { - button_xbm_from_bitmap(b->fallback_button, + img_xbm_from_bitmap(b->fallback_button, b->active.buffer, b->active.rgba); } if (!*b->inactive.buffer) { - button_xbm_from_bitmap(b->fallback_button, + img_xbm_from_bitmap(b->fallback_button, b->inactive.buffer, b->inactive.rgba); } } diff --git a/src/view.c b/src/view.c index 973b079f..00618a18 100644 --- a/src/view.c +++ b/src/view.c @@ -2266,6 +2266,10 @@ view_update_app_id(struct view *view) } wlr_foreign_toplevel_handle_v1_set_app_id( view->toplevel.handle, app_id); + + if (view->ssd_enabled) { + ssd_update_window_icon(view->ssd); + } } void diff --git a/subprojects/libsfdo.wrap b/subprojects/libsfdo.wrap new file mode 100644 index 00000000..22df1d6a --- /dev/null +++ b/subprojects/libsfdo.wrap @@ -0,0 +1,6 @@ +[wrap-git] +url = https://gitlab.freedesktop.org/vyivel/libsfdo.git +revision = v0.1.3 + +[provide] +dependency_names = libsfdo-basedir, libsfdo-desktop, libsfdo-icon