Choosing a theme is done by editing the <name> key in the <theme> section of
the rc.xml configuration file (labwc-config(5)).
-A theme consists of a themerc file and optionally some xbm icons.
+A theme consists of a themerc file and optionally some titlebar icons (referred
+to as buttons).
Theme settings specified in themerc can be overridden by creating a
'themerc-override' file in the configuration directory, which is normally
# BUTTONS
-The images used for the titlebar buttons are 1-bit xbm (X Bitmaps). These are
-masks where 0=clear and 1=colored. The xbm image files are placed in the same
-directory within your theme as the themerc file. Here are all the possible xbm
-files looked for:
+The images used for the titlebar icons are referred to as buttons.
+
+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
+- xbm
+
+By default, buttons are 1-bit xbm (X Bitmaps). These are masks where 0=clear and
+1=colored. The xbm image files are placed in the same directory as the themerc
+file within a particular theme. The following xbm buttons are supported:
- max.xbm
- iconify.xbm
- close.xbm
- menu.xbm
-More will be supported later.
-
-Note: menu.xbm is not part of openbox-3.6 spec
+One advantage of xbm buttons over other formats is that they change color based
+on the theme. Other formats use the suffices "-active" and "-inactive" to align
+with the respective titlebar colors. For example: "close-active.png"
# DEFINITIONS
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef LABWC_BUTTON_PNG_H
+#define LABWC_BUTTON_PNG_H
+
+struct lab_data_buffer;
+
+void png_load(const char *filename, struct lab_data_buffer **buffer);
+
+#endif /* LABWC_BUTTON_PNG_H */
pangocairo = dependency('pangocairo')
input = dependency('libinput', version: '>=1.14')
math = cc.find_library('m')
+png = dependency('libpng')
if get_option('xwayland').enabled() and not wlroots_has_xwayland
error('no wlroots Xwayland support')
pangocairo,
input,
math,
+ png,
]
subdir('include')
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) Johan Malm 2023
+ */
+#define _POSIX_C_SOURCE 200809L
+#include <cairo.h>
+#include <png.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <wlr/util/log.h>
+#include "buffer.h"
+#include "button-png.h"
+#include "common/dir.h"
+#include "labwc.h"
+#include "theme.h"
+
+/* Share with session.c:isfile() */
+static bool
+file_exists(const char *path)
+{
+ struct stat st;
+ return (!stat(path, &st));
+}
+
+/* Share with xbm.c:xbm_path() */
+static char *
+button_path(const char *filename)
+{
+ static char buffer[4096] = { 0 };
+ snprintf(buffer, sizeof(buffer), "%s/%s", theme_dir(rc.theme_name), filename);
+ return buffer;
+}
+
+/*
+ * cairo_image_surface_create_from_png() does not gracefully handle non-png
+ * files, so we verify the header before trying to read the rest of the file.
+ */
+#define PNG_BYTES_TO_CHECK (4)
+static bool
+ispng(const char *filename)
+{
+ unsigned char header[PNG_BYTES_TO_CHECK];
+ FILE *fp = fopen(filename, "rb");
+ if (!fp) {
+ return false;
+ }
+ if (fread(header, 1, PNG_BYTES_TO_CHECK, fp) != PNG_BYTES_TO_CHECK) {
+ fclose(fp);
+ return false;
+ }
+ if (png_sig_cmp(header, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
+ wlr_log(WLR_ERROR, "file '%s' is not a recognised png file", filename);
+ fclose(fp);
+ return false;
+ }
+ fclose(fp);
+ return true;
+}
+
+#undef PNG_BYTES_TO_CHECK
+
+void
+png_load(const char *filename, struct lab_data_buffer **buffer)
+{
+ if (*buffer) {
+ wlr_buffer_drop(&(*buffer)->base);
+ *buffer = NULL;
+ }
+
+ char *path = button_path(filename);
+ if (!file_exists(path) || !ispng(path)) {
+ return;
+ }
+
+ cairo_surface_t *image = cairo_image_surface_create_from_png(path);
+ if (cairo_surface_status(image)) {
+ wlr_log(WLR_ERROR, "error reading png button '%s'", path);
+ cairo_surface_destroy(image);
+ return;
+ }
+ 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, true);
+ cairo_t *cairo = (*buffer)->cairo;
+ cairo_set_source_surface(cairo, image, 0, 0);
+ cairo_paint_with_alpha(cairo, 1.0);
+}
'node.c',
'osd.c',
'output.c',
+ 'button-png.c',
'regions.c',
'resistance.c',
'seat.c',
corner_top_right = &theme->corner_top_right_inactive_normal->base;
menu_button_unpressed = &theme->button_menu_inactive_unpressed->base;
iconify_button_unpressed = &theme->button_iconify_inactive_unpressed->base;
- maximize_button_unpressed = &theme->button_maximize_inactive_unpressed->base;
+ maximize_button_unpressed =
+ &theme->button_maximize_inactive_unpressed->base;
close_button_unpressed = &theme->button_close_inactive_unpressed->base;
wlr_scene_node_set_enabled(&parent->node, false);
}
#include "common/match.h"
#include "common/string-helpers.h"
#include "config/rcxml.h"
+#include "button-png.h"
#include "theme.h"
#include "xbm/xbm.h"
#include "buffer.h"
char filename[4096] = {0};
for (size_t i = 0; i < sizeof(buttons) / sizeof(buttons[0]); ++i) {
struct button *b = &buttons[i];
+
+ /* 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 */
snprintf(filename, sizeof(filename), "%s.xbm", b->name);
- xbm_load_button(filename, b->active.buffer, b->fallback_button, b->active.rgba);
- xbm_load_button(filename, b->inactive.buffer, b->fallback_button, b->inactive.rgba);
+ if (!*b->active.buffer) {
+ xbm_load_button(filename, b->active.buffer,
+ b->fallback_button, b->active.rgba);
+ }
+ if (!*b->inactive.buffer) {
+ xbm_load_button(filename, b->inactive.buffer,
+ b->fallback_button, b->inactive.rgba);
+ }
}
}