diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 80d9e473..caa5d020 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -67,7 +67,7 @@ jobs: pacman-key --init pacman -Syu --noconfirm pacman -S --noconfirm git meson clang wlroots libdrm libinput \ - wayland-protocols cairo pango libxml2 xorg-xwayland + wayland-protocols cairo pango libxml2 xorg-xwayland librsvg - name: Install Debian Testing dependencies if: matrix.name == 'Debian' @@ -77,7 +77,8 @@ jobs: apt-get upgrade -y apt-get install -y git clang \ hwdata \ - libxml2-dev libcairo2-dev libpango1.0-dev + libxml2-dev libcairo2-dev libpango1.0-dev \ + librsvg2-dev apt-get build-dep -y wlroots - name: Install FreeBSD dependencies @@ -108,6 +109,7 @@ jobs: xcb-util-cursor-devel xcb-util-devel xcb-util-image-devel \ xcb-util-keysyms-devel xcb-util-xrm-devel xorg-server-xwayland \ hwids \ + librsvg-devel \ libglib-devel cairo-devel pango-devel - name: Build with gcc diff --git a/README.md b/README.md index 90ab81c1..076221a0 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,7 @@ Run-time dependencies include: - wlroots, wayland, libinput, xkbcommon - libxml2, cairo, pango, glib-2.0 +- libpng, librsvg-2.0 - xwayland, xcb (optional) Build dependencies include: diff --git a/docs/labwc-theme.5.scd b/docs/labwc-theme.5.scd index 6de7f1cd..91264acd 100644 --- a/docs/labwc-theme.5.scd +++ b/docs/labwc-theme.5.scd @@ -182,6 +182,7 @@ The image formats listed below are supported. They are listed in order of precedence, where the first format in the list is searched for first. - png +- svg - xbm By default, buttons are 1-bit xbm (X Bitmaps). These are masks where 0=clear and diff --git a/include/button/button-svg.h b/include/button/button-svg.h new file mode 100644 index 00000000..884a8846 --- /dev/null +++ b/include/button/button-svg.h @@ -0,0 +1,10 @@ +/* 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/meson.build b/meson.build index b26e2b50..53cfc5d0 100644 --- a/meson.build +++ b/meson.build @@ -70,6 +70,7 @@ pangocairo = dependency('pangocairo') input = dependency('libinput', version: '>=1.14') math = cc.find_library('m') png = dependency('libpng') +svg = dependency('librsvg-2.0', version: '>=2.46', required: false) if get_option('xwayland').enabled() and not wlroots_has_xwayland error('no wlroots Xwayland support') @@ -78,6 +79,8 @@ have_xwayland = xcb.found() and wlroots_has_xwayland conf_data = configuration_data() conf_data.set10('HAVE_XWAYLAND', have_xwayland) +conf_data.set10('HAVE_RSVG', svg.found()) + msgfmt = find_program('msgfmt', required: get_option('nls')) if msgfmt.found() source_root = meson.current_source_dir() @@ -106,6 +109,11 @@ labwc_deps = [ math, png, ] +if svg.found() + labwc_deps += [ + svg, + ] +endif subdir('include') subdir('src') diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl index 98cba37f..72b1fba4 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -5653,7 +5653,7 @@ sub process { #Ignore Page variants $var !~ /^(?:Clear|Set|TestClear|TestSet|)Page[A-Z]/ && -#Ignore some pango and libxml2 CamelCase variants +#labwc-custom check to ignore some pango/libxml2/etc CamelCase variants $var !~ /^(?:PangoLayout|PangoFontDescription)/ && $var !~ /^(?:PangoTabArray|PangoRectangle)/ && $var !~ /^(?:PangoWeight|_PangoFontDescription)/ && @@ -5664,6 +5664,7 @@ sub process { $var !~ /^(?:xmlParseMemory)/ && $var !~ /^(?:xmlFree)/ && $var !~ /^(?:GString|GError)/ && + $var !~ /^(?:RsvgRectangle|RsvgHandle)/ && $var !~ /^(?:XKB_KEY_XF86Switch_VT_1)/ && #Ignore SI style variants like nS, mV and dB diff --git a/src/button/button-png.c b/src/button/button-png.c index 66cf5535..feceb7ec 100644 --- a/src/button/button-png.c +++ b/src/button/button-png.c @@ -54,7 +54,7 @@ png_load(const char *button_name, struct lab_data_buffer **buffer) char path[4096] = { 0 }; button_filename(button_name, path, sizeof(path)); - if (!file_exists(path) || !ispng(path)) { + if (!ispng(path)) { return; } diff --git a/src/button/button-svg.c b/src/button/button-svg.c new file mode 100644 index 00000000..3dab05d1 --- /dev/null +++ b/src/button/button-svg.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) Johan Malm 2023 + */ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include "buffer.h" +#include "button/button-svg.h" +#include "button/common.h" +#include "common/file-helpers.h" +#include "labwc.h" +#include "theme.h" + +void +button_svg_load(const char *button_name, struct lab_data_buffer **buffer, + int size) +{ + if (*buffer) { + wlr_buffer_drop(&(*buffer)->base); + *buffer = NULL; + } + + char filename[4096] = { 0 }; + button_filename(button_name, filename, sizeof(filename)); + + GError *err = NULL; + RsvgRectangle viewport = { .width = size, .height = size }; + RsvgHandle *svg = rsvg_handle_new_from_file(filename, &err); + if (err) { + wlr_log(WLR_DEBUG, "error reading svg %s-%s\n", filename, err->message); + g_error_free(err); + /* + * rsvg_handle_new_from_file() returns NULL if an error occurs, + * so there is no need to free svg here. + */ + return; + } + + cairo_surface_t *image = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size, size); + cairo_t *cr = cairo_create(image); + + rsvg_handle_render_document(svg, cr, &viewport, &err); + if (err) { + wlr_log(WLR_ERROR, "error rendering svg %s-%s\n", filename, err->message); + g_error_free(err); + goto error; + } + + if (cairo_surface_status(image)) { + wlr_log(WLR_ERROR, "error reading svg button '%s'", filename); + goto error; + } + cairo_surface_flush(image); + + double w = cairo_image_surface_get_width(image); + double h = cairo_image_surface_get_height(image); + *buffer = buffer_create_cairo((int)w, (int)h, 1.0, /* free_on_destroy */ true); + cairo_t *cairo = (*buffer)->cairo; + cairo_set_source_surface(cairo, image, 0, 0); + cairo_paint_with_alpha(cairo, 1.0); + +error: + cairo_destroy(cr); + cairo_surface_destroy(image); + g_object_unref(svg); +} diff --git a/src/button/meson.build b/src/button/meson.build index b5b10d2b..f0f24e96 100644 --- a/src/button/meson.build +++ b/src/button/meson.build @@ -3,3 +3,10 @@ labwc_sources += files( 'button-xbm.c', 'common.c', ) + +if svg.found() + labwc_sources += files( + 'button-svg.c', + ) +endif + diff --git a/src/theme.c b/src/theme.c index 30d50ebe..55d6a2b2 100644 --- a/src/theme.c +++ b/src/theme.c @@ -6,6 +6,7 @@ */ #define _POSIX_C_SOURCE 200809L +#include "config.h" #include #include #include @@ -25,6 +26,11 @@ #include "common/string-helpers.h" #include "config/rcxml.h" #include "button/button-png.h" + +#if HAVE_RSVG +#include "button/button-svg.h" +#endif + #include "button/button-xbm.h" #include "theme.h" #include "buffer.h" @@ -39,6 +45,15 @@ struct button { } active, inactive; }; +static void +drop(struct lab_data_buffer **buffer) +{ + if (*buffer) { + wlr_buffer_drop(&(*buffer)->base); + *buffer = NULL; + } +} + static void load_buttons(struct theme *theme) { @@ -97,13 +112,29 @@ load_buttons(struct theme *theme) for (size_t i = 0; i < sizeof(buttons) / sizeof(buttons[0]); ++i) { struct button *b = &buttons[i]; + drop(b->active.buffer); + drop(b->inactive.buffer); + /* Try png icon first */ snprintf(filename, sizeof(filename), "%s-active.png", b->name); png_load(filename, b->active.buffer); snprintf(filename, sizeof(filename), "%s-inactive.png", b->name); png_load(filename, b->inactive.buffer); - /* If there were no png buttons, use xbm */ +#if HAVE_RSVG + /* Then try svg icon */ + 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); + } + if (!*b->inactive.buffer) { + snprintf(filename, sizeof(filename), "%s-inactive.svg", b->name); + button_svg_load(filename, b->inactive.buffer, size); + } +#endif + + /* If there were no png/svg buttons, use xbm */ snprintf(filename, sizeof(filename), "%s.xbm", b->name); if (!*b->active.buffer) { button_xbm_load(filename, b->active.buffer,