ssd: support window icons

The default `titleLayout` is updated to `icon:iconify,max,close` which
replaces the window menu button with the window icon.

When the icon file is not found or could not be loaded, the window menu
icon as before is shown.

The icon theme can be selected with `<theme><icon>`.

This commit adds libsfdo as an optional dependency. `-Dicon=disabled` can
be passsed to `meson setup` command in order to disable window icon, in
which case the window icon is always replaced with a window menu button.
This commit is contained in:
tokyo4j 2024-09-06 17:00:40 +09:00 committed by Hiroaki Yamamoto
parent b9414d8b8d
commit a745f91169
32 changed files with 452 additions and 142 deletions

View file

@ -437,9 +437,13 @@ extending outward from the snapped edge.
*<theme><name>* *<theme><name>*
The name of the Openbox theme to use. It is not set by default. The name of the Openbox theme to use. It is not set by default.
*<theme><icon>*
The name of the icon theme to use. It is not set by default.
*<theme><titlebar><layout>* *<theme><titlebar><layout>*
Selection and order of buttons in a window's titlebar. Selection and order of buttons in a window's titlebar.
The following identifiers can be used, each only once: The following identifiers can be used, each only once:
- 'icon': window icon
- 'menu': window menu - 'menu': window menu
- 'iconify': iconify - 'iconify': iconify
- 'max': maximize toggle - 'max': maximize toggle
@ -624,6 +628,7 @@ extending outward from the snapped edge.
buttons and the window title are shown. buttons and the window title are shown.
- Title: The area of the titlebar (including blank space) between - Title: The area of the titlebar (including blank space) between
the window buttons, where the window title is displayed. 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. - WindowMenu: A button that, by default, displays a window menu.
- Iconify: A button that, by default, iconifies a window. - Iconify: A button that, by default, iconifies a window.
- Maximize: A button that, by default, toggles maximization of a window. - Maximize: A button that, by default, toggles maximization of a window.

View file

@ -28,8 +28,9 @@
<!-- <font><theme> can be defined without an attribute to set all places --> <!-- <font><theme> can be defined without an attribute to set all places -->
<theme> <theme>
<name></name> <name></name>
<icon></icon>
<titlebar> <titlebar>
<layout>menu:iconify,max,close</layout> <layout>icon:iconify,max,close</layout>
<showTitle>yes</showTitle> <showTitle>yes</showTitle>
</titlebar> </titlebar>
<cornerRadius>8</cornerRadius> <cornerRadius>8</cornerRadius>
@ -457,6 +458,15 @@
</mousebind> </mousebind>
</context> </context>
<context name="Icon">
<mousebind button="Left" action="Click">
<action name="ShowMenu" menu="client-menu" atCursor="no" />
</mousebind>
<mousebind button="Right" action="Click">
<action name="ShowMenu" menu="client-menu" atCursor="no" />
</mousebind>
</context>
<context name="Shade"> <context name="Shade">
<mousebind button="Left" action="Click"> <mousebind button="Left" action="Click">
<action name="ToggleShade" /> <action name="ToggleShade" />

View file

@ -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 */

View file

@ -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 */

View file

@ -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 */

View file

@ -1,17 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef LABWC_BUTTON_COMMON_H
#define LABWC_BUTTON_COMMON_H
#include <stddef.h>
/**
* 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 */

View file

@ -376,6 +376,32 @@ static struct mouse_combos {
.name = "atCursor", .name = "atCursor",
.value = "no", .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", .context = "Root",
.button = "Left", .button = "Left",

View file

@ -81,6 +81,7 @@ struct rcxml {
/* theme */ /* theme */
char *theme_name; char *theme_name;
char *icon_theme_name;
struct wl_list title_buttons_left; struct wl_list title_buttons_left;
struct wl_list title_buttons_right; struct wl_list title_buttons_right;
int corner_radius; int corner_radius;

12
include/icon-loader.h Normal file
View file

@ -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 */

9
include/img/img-png.h Normal file
View file

@ -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 */

10
include/img/img-svg.h Normal file
View file

@ -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 */

22
include/img/img-xbm.h Normal file
View file

@ -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 */

View file

@ -365,6 +365,8 @@ struct server {
struct menu *menu_current; struct menu *menu_current;
struct wl_list menus; struct wl_list menus;
struct icon_loader *icon_loader;
pid_t primary_client_pid; pid_t primary_client_pid;
}; };

View file

@ -73,6 +73,8 @@ struct ssd {
struct ssd_state_title_width active; struct ssd_state_title_width active;
struct ssd_state_title_width inactive; struct ssd_state_title_width inactive;
} title; } title;
char *app_id;
} state; } state;
/* An invisible area around the view which allows resizing */ /* 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, void add_toggled_icon(struct ssd_button *button, struct wl_list *part_list,
enum ssd_part_type type, struct wlr_buffer *icon_buffer, enum ssd_part_type type, struct wlr_buffer *icon_buffer,
struct wlr_buffer *hover_buffer); struct wlr_buffer *hover_buffer);
void update_window_icon_buffer(struct wlr_scene_node *button_node,
struct wlr_buffer *buffer);
/* SSD internal helpers */ /* SSD internal helpers */
struct ssd_part *ssd_get_part( struct ssd_part *ssd_get_part(

View file

@ -24,6 +24,7 @@ enum ssd_part_type {
LAB_SSD_BUTTON_CLOSE, LAB_SSD_BUTTON_CLOSE,
LAB_SSD_BUTTON_MAXIMIZE, LAB_SSD_BUTTON_MAXIMIZE,
LAB_SSD_BUTTON_ICONIFY, LAB_SSD_BUTTON_ICONIFY,
LAB_SSD_BUTTON_WINDOW_ICON,
LAB_SSD_BUTTON_WINDOW_MENU, LAB_SSD_BUTTON_WINDOW_MENU,
LAB_SSD_BUTTON_SHADE, LAB_SSD_BUTTON_SHADE,
LAB_SSD_BUTTON_OMNIPRESENT, LAB_SSD_BUTTON_OMNIPRESENT,
@ -87,6 +88,7 @@ void ssd_update_title(struct ssd *ssd);
void ssd_update_geometry(struct ssd *ssd); void ssd_update_geometry(struct ssd *ssd);
void ssd_destroy(struct ssd *ssd); void ssd_destroy(struct ssd *ssd);
void ssd_set_titlebar(struct ssd *ssd, bool enabled); 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_keybind_inhibit_indicator(struct ssd *ssd, bool enable);
void ssd_enable_shade(struct ssd *ssd, bool enable); void ssd_enable_shade(struct ssd *ssd, bool enable);

View file

@ -73,6 +73,24 @@ pixman = dependency('pixman-1')
math = cc.find_library('m') math = cc.find_library('m')
png = dependency('libpng') png = dependency('libpng')
svg = dependency('librsvg-2.0', version: '>=2.46', required: false) 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 if get_option('xwayland').enabled() and not wlroots_has_xwayland
error('no wlroots Xwayland support') error('no wlroots Xwayland support')
@ -88,6 +106,9 @@ else
endif endif
conf_data.set10('HAVE_RSVG', have_rsvg) 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() if get_option('static_analyzer').enabled()
add_project_arguments(['-fanalyzer'], language: 'c') add_project_arguments(['-fanalyzer'], language: 'c')
endif endif
@ -126,6 +147,13 @@ if have_rsvg
svg, svg,
] ]
endif endif
if have_libsfdo
labwc_deps += [
sfdo_basedir,
sfdo_desktop,
sfdo_icon,
]
endif
subdir('include') subdir('include')
subdir('src') subdir('src')

View file

@ -1,6 +1,7 @@
option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') 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('xwayland', type: 'feature', value: 'auto', description: 'Enable support for X11 applications')
option('svg', type: 'feature', value: 'enabled', description: 'Enable svg window buttons') 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('nls', type: 'feature', value: 'auto', description: 'Enable native language support')
option('static_analyzer', type: 'feature', value: 'disabled', description: 'Run gcc static analyzer') option('static_analyzer', type: 'feature', value: 'disabled', description: 'Run gcc static analyzer')
option('test', type: 'feature', value: 'disabled', description: 'Run tests') option('test', type: 'feature', value: 'disabled', description: 'Run tests')

View file

@ -1,27 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <stdio.h>
#include <unistd.h>
#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);
}

View file

@ -114,6 +114,8 @@ context_from_str(const char *str)
return LAB_SSD_BUTTON_ICONIFY; return LAB_SSD_BUTTON_ICONIFY;
} else if (!strcasecmp(str, "WindowMenu")) { } else if (!strcasecmp(str, "WindowMenu")) {
return LAB_SSD_BUTTON_WINDOW_MENU; return LAB_SSD_BUTTON_WINDOW_MENU;
} else if (!strcasecmp(str, "Icon")) {
return LAB_SSD_BUTTON_WINDOW_ICON;
} else if (!strcasecmp(str, "Shade")) { } else if (!strcasecmp(str, "Shade")) {
return LAB_SSD_BUTTON_SHADE; return LAB_SSD_BUTTON_SHADE;
} else if (!strcasecmp(str, "AllDesktops")) { } else if (!strcasecmp(str, "AllDesktops")) {

View file

@ -151,7 +151,9 @@ fill_section(const char *content, struct wl_list *list)
continue; continue;
} }
enum ssd_part_type type = LAB_SSD_NONE; 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; type = LAB_SSD_BUTTON_WINDOW_MENU;
} else if (!strcmp(identifier, "iconify")) { } else if (!strcmp(identifier, "iconify")) {
type = LAB_SSD_BUTTON_ICONIFY; type = LAB_SSD_BUTTON_ICONIFY;
@ -1067,6 +1069,8 @@ entry(xmlNode *node, char *nodename, char *content)
rc.placement_cascade_offset_y = atoi(content); rc.placement_cascade_offset_y = atoi(content);
} else if (!strcmp(nodename, "name.theme")) { } else if (!strcmp(nodename, "name.theme")) {
rc.theme_name = xstrdup(content); rc.theme_name = xstrdup(content);
} else if (!strcmp(nodename, "icon.theme")) {
rc.icon_theme_name = xstrdup(content);
} else if (!strcasecmp(nodename, "layout.titlebar.theme")) { } else if (!strcasecmp(nodename, "layout.titlebar.theme")) {
fill_title_layout(content); fill_title_layout(content);
} else if (!strcasecmp(nodename, "showTitle.titlebar.theme")) { } else if (!strcasecmp(nodename, "showTitle.titlebar.theme")) {
@ -1658,7 +1662,7 @@ post_processing(void)
} }
if (!rc.title_layout_loaded) { 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_menuitem.name);
zfree(rc.font_osd.name); zfree(rc.font_osd.name);
zfree(rc.theme_name); zfree(rc.theme_name);
zfree(rc.icon_theme_name);
zfree(rc.workspace_config.prefix); zfree(rc.workspace_config.prefix);
struct title_button *p, *p_tmp; struct title_button *p, *p_tmp;

137
src/icon-loader.c Normal file
View file

@ -0,0 +1,137 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <sfdo-desktop.h>
#include <sfdo-icon.h>
#include <sfdo-basedir.h>
#include <wlr/util/log.h>
#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;
}

View file

@ -10,8 +10,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <wlr/util/log.h> #include <wlr/util/log.h>
#include "buffer.h" #include "buffer.h"
#include "button/button-png.h" #include "img/img-png.h"
#include "button/common.h"
#include "common/string-helpers.h" #include "common/string-helpers.h"
#include "labwc.h" #include "labwc.h"
@ -44,25 +43,22 @@ ispng(const char *filename)
#undef PNG_BYTES_TO_CHECK #undef PNG_BYTES_TO_CHECK
void 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) { if (*buffer) {
wlr_buffer_drop(&(*buffer)->base); wlr_buffer_drop(&(*buffer)->base);
*buffer = NULL; *buffer = NULL;
} }
if (string_null_or_empty(button_name)) { if (string_null_or_empty(filename)) {
return;
}
if (!ispng(filename)) {
return; return;
} }
char path[4096] = { 0 }; cairo_surface_t *image = cairo_image_surface_create_from_png(filename);
button_filename(button_name, path, sizeof(path));
if (!ispng(path)) {
return;
}
cairo_surface_t *image = cairo_image_surface_create_from_png(path);
if (cairo_surface_status(image)) { 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); cairo_surface_destroy(image);
return; return;
} }

View file

@ -10,25 +10,18 @@
#include <stdlib.h> #include <stdlib.h>
#include <wlr/util/log.h> #include <wlr/util/log.h>
#include "buffer.h" #include "buffer.h"
#include "button/button-svg.h" #include "img/img-svg.h"
#include "button/common.h"
#include "common/string-helpers.h" #include "common/string-helpers.h"
#include "labwc.h" #include "labwc.h"
void 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) int size)
{ {
if (*buffer) { if (*buffer) {
wlr_buffer_drop(&(*buffer)->base); wlr_buffer_drop(&(*buffer)->base);
*buffer = NULL; *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)) { if (string_null_or_empty(filename)) {
return; return;
} }

View file

@ -13,8 +13,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <drm_fourcc.h> #include <drm_fourcc.h>
#include "button/button-xbm.h" #include "img/img-xbm.h"
#include "button/common.h"
#include "common/grab-file.h" #include "common/grab-file.h"
#include "common/mem.h" #include "common/mem.h"
#include "common/string-helpers.h" #include "common/string-helpers.h"
@ -257,7 +256,7 @@ parse_xbm_builtin(const char *button, int size)
} }
void 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) float *rgba)
{ {
struct pixmap pixmap = {0}; struct pixmap pixmap = {0};
@ -272,7 +271,7 @@ button_xbm_from_bitmap(const char *bitmap, struct lab_data_buffer **buffer,
} }
void 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) float *rgba)
{ {
struct pixmap pixmap = {0}; 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); wlr_buffer_drop(&(*buffer)->base);
*buffer = NULL; *buffer = NULL;
} }
if (string_null_or_empty(button_name)) { if (string_null_or_empty(filename)) {
return; return;
} }
color = argb32(rgba); color = argb32(rgba);
/* Read file into memory as it's easier to tokenize that way */ /* 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); struct buf token_buf = grab_file(filename);
if (token_buf.len) { if (token_buf.len) {
struct token *tokens = tokenize_xbm(token_buf.data); struct token *tokens = tokenize_xbm(token_buf.data);

View file

@ -1,12 +1,11 @@
labwc_sources += files( labwc_sources += files(
'button-png.c', 'img-png.c',
'button-xbm.c', 'img-xbm.c',
'common.c',
) )
if have_rsvg if have_rsvg
labwc_sources += files( labwc_sources += files(
'button-svg.c', 'img-svg.c',
) )
endif endif

View file

@ -44,8 +44,13 @@ if have_xwayland
) )
endif endif
if have_libsfdo
labwc_sources += files(
'icon-loader.c',
)
endif
subdir('button') subdir('img')
subdir('common') subdir('common')
subdir('config') subdir('config')
subdir('decorations') subdir('decorations')

View file

@ -25,6 +25,9 @@
#include "config/rcxml.h" #include "config/rcxml.h"
#include "config/session.h" #include "config/session.h"
#include "decorations.h" #include "decorations.h"
#if HAVE_LIBSFDO
#include "icon-loader.h"
#endif
#include "idle.h" #include "idle.h"
#include "labwc.h" #include "labwc.h"
#include "layers.h" #include "layers.h"
@ -56,6 +59,11 @@ reload_config_and_theme(struct server *server)
theme_finish(server->theme); theme_finish(server->theme);
theme_init(server->theme, server, rc.theme_name); theme_init(server->theme, server, rc.theme_name);
#if HAVE_LIBSFDO
icon_loader_finish(server);
icon_loader_init(server);
#endif
struct view *view; struct view *view;
wl_list_for_each(view, &server->views, link) { wl_list_for_each(view, &server->views, link) {
view_reload_ssd(view); view_reload_ssd(view);
@ -547,6 +555,10 @@ server_init(struct server *server)
layers_init(server); layers_init(server);
#if HAVE_LIBSFDO
icon_loader_init(server);
#endif
#if HAVE_XWAYLAND #if HAVE_XWAYLAND
xwayland_server_init(server, compositor); xwayland_server_init(server, compositor);
#endif #endif
@ -597,4 +609,8 @@ server_finish(struct server *server)
/* TODO: clean up various scene_tree nodes */ /* TODO: clean up various scene_tree nodes */
workspaces_destroy(server); workspaces_destroy(server);
#if HAVE_LIBSFDO
icon_loader_finish(server);
#endif
} }

View file

@ -104,6 +104,23 @@ get_scale_box(struct wlr_buffer *buffer, double container_width,
return icon_geo; 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 * struct ssd_part *
add_scene_button(struct wl_list *part_list, enum ssd_part_type type, add_scene_button(struct wl_list *part_list, enum ssd_part_type type,
struct wlr_scene_tree *parent, struct wlr_buffer *icon_buffer, struct wlr_scene_tree *parent, struct wlr_buffer *icon_buffer,

View file

@ -4,10 +4,14 @@
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include "buffer.h" #include "buffer.h"
#include "config.h"
#include "common/mem.h" #include "common/mem.h"
#include "common/scaled-font-buffer.h" #include "common/scaled-font-buffer.h"
#include "common/scene-helpers.h" #include "common/scene-helpers.h"
#include "common/string-helpers.h" #include "common/string-helpers.h"
#if HAVE_LIBSFDO
#include "icon-loader.h"
#endif
#include "labwc.h" #include "labwc.h"
#include "node.h" #include "node.h"
#include "ssd-internal.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; struct ssd_button *btn;
switch (type) { switch (type) {
case LAB_SSD_BUTTON_WINDOW_ICON: /* fallthrough */
case LAB_SSD_BUTTON_WINDOW_MENU: case LAB_SSD_BUTTON_WINDOW_MENU:
add_scene_button(&subtree->parts, type, parent, add_scene_button(&subtree->parts, type, parent,
active ? &theme->button_menu_active_unpressed->base active ? &theme->button_menu_active_unpressed->base
@ -171,6 +176,7 @@ ssd_titlebar_create(struct ssd *ssd)
update_visible_buttons(ssd); update_visible_buttons(ssd);
ssd_update_title(ssd); ssd_update_title(ssd);
ssd_update_window_icon(ssd);
bool maximized = view->maximized == VIEW_AXIS_BOTH; bool maximized = view->maximized == VIEW_AXIS_BOTH;
if (maximized) { if (maximized) {
@ -368,7 +374,9 @@ ssd_titlebar_update(struct ssd *ssd)
wlr_scene_node_set_position(part->node, x, 0); wlr_scene_node_set_position(part->node, x, 0);
} }
} FOR_EACH_END } FOR_EACH_END
ssd_update_title(ssd); ssd_update_title(ssd);
ssd_update_window_icon(ssd);
} }
void void
@ -386,8 +394,10 @@ ssd_titlebar_destroy(struct ssd *ssd)
} FOR_EACH_END } FOR_EACH_END
if (ssd->state.title.text) { if (ssd->state.title.text) {
free(ssd->state.title.text); zfree(ssd->state.title.text);
ssd->state.title.text = NULL; }
if (ssd->state.app_id) {
zfree(ssd->state.app_id);
} }
wlr_scene_node_destroy(&ssd->titlebar.tree->node); wlr_scene_node_destroy(&ssd->titlebar.tree->node);
@ -624,4 +634,50 @@ ssd_should_be_squared(struct ssd *ssd)
&& view->maximized != VIEW_AXIS_BOTH; && 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 #undef FOR_EACH_STATE

View file

@ -29,14 +29,14 @@
#include "common/parse-double.h" #include "common/parse-double.h"
#include "common/string-helpers.h" #include "common/string-helpers.h"
#include "config/rcxml.h" #include "config/rcxml.h"
#include "button/button-png.h" #include "img/img-png.h"
#include "labwc.h" #include "labwc.h"
#if HAVE_RSVG #if HAVE_RSVG
#include "button/button-svg.h" #include "img/img-svg.h"
#endif #endif
#include "button/button-xbm.h" #include "img/img-xbm.h"
#include "theme.h" #include "theme.h"
#include "buffer.h" #include "buffer.h"
#include "ssd.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]" * We use the following button filename schema: "BUTTON [TOGGLED] [STATE]"
* with the words separated by underscore, and the following meaning: * with the words separated by underscore, and the following meaning:
@ -394,31 +424,31 @@ load_buttons(struct theme *theme)
zdrop(b->inactive.buffer); zdrop(b->inactive.buffer);
/* PNG */ /* PNG */
snprintf(filename, sizeof(filename), "%s-active.png", b->name); get_button_filename(filename, sizeof(filename), b->name, "-active.png");
button_png_load(filename, b->active.buffer); img_png_load(filename, b->active.buffer);
snprintf(filename, sizeof(filename), "%s-inactive.png", b->name); get_button_filename(filename, sizeof(filename), b->name, "-inactive.png");
button_png_load(filename, b->inactive.buffer); img_png_load(filename, b->inactive.buffer);
#if HAVE_RSVG #if HAVE_RSVG
/* SVG */ /* SVG */
int size = theme->title_height - 2 * theme->padding_height; int size = theme->title_height - 2 * theme->padding_height;
if (!*b->active.buffer) { if (!*b->active.buffer) {
snprintf(filename, sizeof(filename), "%s-active.svg", b->name); get_button_filename(filename, sizeof(filename), b->name, "-active.svg");
button_svg_load(filename, b->active.buffer, size); img_svg_load(filename, b->active.buffer, size);
} }
if (!*b->inactive.buffer) { if (!*b->inactive.buffer) {
snprintf(filename, sizeof(filename), "%s-inactive.svg", b->name); get_button_filename(filename, sizeof(filename), b->name, "-inactive.svg");
button_svg_load(filename, b->inactive.buffer, size); img_svg_load(filename, b->inactive.buffer, size);
} }
#endif #endif
/* XBM */ /* XBM */
snprintf(filename, sizeof(filename), "%s.xbm", b->name); get_button_filename(filename, sizeof(filename), b->name, ".xbm");
if (!*b->active.buffer) { 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) { 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 * For example max_hover_toggled instead of max_toggled_hover
*/ */
if (b->alt_name) { if (b->alt_name) {
snprintf(filename, sizeof(filename), "%s.xbm", b->alt_name); get_button_filename(filename, sizeof(filename), b->alt_name, ".xbm");
} else { } else {
filename[0] = '\0'; filename[0] = '\0';
} }
if (!*b->active.buffer) { 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) { 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; continue;
} }
if (!*b->active.buffer) { if (!*b->active.buffer) {
button_xbm_from_bitmap(b->fallback_button, img_xbm_from_bitmap(b->fallback_button,
b->active.buffer, b->active.rgba); b->active.buffer, b->active.rgba);
} }
if (!*b->inactive.buffer) { if (!*b->inactive.buffer) {
button_xbm_from_bitmap(b->fallback_button, img_xbm_from_bitmap(b->fallback_button,
b->inactive.buffer, b->inactive.rgba); b->inactive.buffer, b->inactive.rgba);
} }
} }

View file

@ -2266,6 +2266,10 @@ view_update_app_id(struct view *view)
} }
wlr_foreign_toplevel_handle_v1_set_app_id( wlr_foreign_toplevel_handle_v1_set_app_id(
view->toplevel.handle, app_id); view->toplevel.handle, app_id);
if (view->ssd_enabled) {
ssd_update_window_icon(view->ssd);
}
} }
void void

6
subprojects/libsfdo.wrap Normal file
View file

@ -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