mirror of
https://github.com/labwc/labwc.git
synced 2025-11-09 13:30:01 -05:00
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:
parent
b9414d8b8d
commit
a745f91169
32 changed files with 452 additions and 142 deletions
73
src/img/img-png.c
Normal file
73
src/img/img-png.c
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
// 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 <wlr/util/log.h>
|
||||
#include "buffer.h"
|
||||
#include "img/img-png.h"
|
||||
#include "common/string-helpers.h"
|
||||
#include "labwc.h"
|
||||
|
||||
/*
|
||||
* 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
|
||||
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(filename)) {
|
||||
return;
|
||||
}
|
||||
if (!ispng(filename)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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'", filename);
|
||||
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);
|
||||
}
|
||||
69
src/img/img-svg.c
Normal file
69
src/img/img-svg.c
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) Johan Malm 2023
|
||||
*/
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <cairo.h>
|
||||
#include <librsvg/rsvg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <wlr/util/log.h>
|
||||
#include "buffer.h"
|
||||
#include "img/img-svg.h"
|
||||
#include "common/string-helpers.h"
|
||||
#include "labwc.h"
|
||||
|
||||
void
|
||||
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(filename)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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", 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);
|
||||
}
|
||||
306
src/img/img-xbm.c
Normal file
306
src/img/img-xbm.c
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Convert xbm file to buffer with cairo surface
|
||||
*
|
||||
* Copyright Johan Malm 2020-2023
|
||||
*/
|
||||
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <drm_fourcc.h>
|
||||
#include "img/img-xbm.h"
|
||||
#include "common/grab-file.h"
|
||||
#include "common/mem.h"
|
||||
#include "common/string-helpers.h"
|
||||
#include "buffer.h"
|
||||
|
||||
enum token_type {
|
||||
TOKEN_NONE = 0,
|
||||
TOKEN_IDENT,
|
||||
TOKEN_INT,
|
||||
TOKEN_SPECIAL,
|
||||
TOKEN_OTHER,
|
||||
};
|
||||
|
||||
#define MAX_TOKEN_SIZE (256)
|
||||
struct token {
|
||||
char name[MAX_TOKEN_SIZE];
|
||||
int value;
|
||||
size_t pos;
|
||||
enum token_type type;
|
||||
};
|
||||
|
||||
struct pixmap {
|
||||
uint32_t *data;
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
|
||||
static uint32_t color;
|
||||
static char *current_buffer_position;
|
||||
static struct token *tokens;
|
||||
static int nr_tokens, alloc_tokens;
|
||||
|
||||
static void
|
||||
add_token(enum token_type token_type)
|
||||
{
|
||||
if (nr_tokens == alloc_tokens) {
|
||||
alloc_tokens = (alloc_tokens + 16) * 2;
|
||||
tokens = xrealloc(tokens, alloc_tokens * sizeof(struct token));
|
||||
}
|
||||
struct token *token = tokens + nr_tokens;
|
||||
memset(token, 0, sizeof(*token));
|
||||
nr_tokens++;
|
||||
token->type = token_type;
|
||||
}
|
||||
|
||||
static void
|
||||
get_identifier_token(void)
|
||||
{
|
||||
struct token *token = tokens + nr_tokens - 1;
|
||||
token->name[token->pos] = current_buffer_position[0];
|
||||
token->pos++;
|
||||
if (token->pos == MAX_TOKEN_SIZE - 1) {
|
||||
return;
|
||||
}
|
||||
current_buffer_position++;
|
||||
switch (current_buffer_position[0]) {
|
||||
case '\0':
|
||||
return;
|
||||
case 'a' ... 'z':
|
||||
case 'A' ... 'Z':
|
||||
case '0' ... '9':
|
||||
case '_':
|
||||
case '#':
|
||||
get_identifier_token();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
get_number_token(void)
|
||||
{
|
||||
struct token *token = tokens + nr_tokens - 1;
|
||||
token->name[token->pos] = current_buffer_position[0];
|
||||
token->pos++;
|
||||
if (token->pos == MAX_TOKEN_SIZE - 1) {
|
||||
return;
|
||||
}
|
||||
current_buffer_position++;
|
||||
switch (current_buffer_position[0]) {
|
||||
case '\0':
|
||||
return;
|
||||
case '0' ... '9':
|
||||
case 'a' ... 'f':
|
||||
case 'A' ... 'F':
|
||||
case 'x':
|
||||
get_number_token();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
get_special_char_token(void)
|
||||
{
|
||||
struct token *token = tokens + nr_tokens - 1;
|
||||
token->name[0] = current_buffer_position[0];
|
||||
current_buffer_position++;
|
||||
}
|
||||
|
||||
/**
|
||||
* tokenize_xbm - tokenize xbm file
|
||||
* @buffer: buffer containing xbm file
|
||||
* return token vector
|
||||
*/
|
||||
static struct token *
|
||||
tokenize_xbm(char *buffer)
|
||||
{
|
||||
tokens = NULL;
|
||||
nr_tokens = 0;
|
||||
alloc_tokens = 0;
|
||||
|
||||
current_buffer_position = buffer;
|
||||
|
||||
for (;;) {
|
||||
switch (current_buffer_position[0]) {
|
||||
case '\0':
|
||||
goto out;
|
||||
case 'a' ... 'z':
|
||||
case 'A' ... 'Z':
|
||||
case '_':
|
||||
case '#':
|
||||
add_token(TOKEN_IDENT);
|
||||
get_identifier_token();
|
||||
continue;
|
||||
case '0' ... '9':
|
||||
add_token(TOKEN_INT);
|
||||
get_number_token();
|
||||
struct token *token = tokens + nr_tokens - 1;
|
||||
token->value = (int)strtol(token->name, NULL, 0);
|
||||
continue;
|
||||
case '{':
|
||||
add_token(TOKEN_SPECIAL);
|
||||
get_special_char_token();
|
||||
continue;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
++current_buffer_position;
|
||||
}
|
||||
out:
|
||||
add_token(TOKEN_NONE); /* vector end marker */
|
||||
return tokens;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
argb32(float *rgba)
|
||||
{
|
||||
uint32_t r[4] = { 0 };
|
||||
for (int i = 0; i < 4; i++) {
|
||||
r[i] = rgba[i] * 255;
|
||||
}
|
||||
return ((r[3] & 0xff) << 24) | ((r[0] & 0xff) << 16) |
|
||||
((r[1] & 0xff) << 8) | (r[2] & 0xff);
|
||||
}
|
||||
|
||||
static void
|
||||
process_bytes(struct pixmap *pixmap, struct token *tokens)
|
||||
{
|
||||
pixmap->data = znew_n(uint32_t, pixmap->width * pixmap->height);
|
||||
struct token *t = tokens;
|
||||
for (int row = 0; row < pixmap->height; row++) {
|
||||
int byte = 1;
|
||||
for (int col = 0; col < pixmap->width; col++) {
|
||||
if (col == byte * 8) {
|
||||
++byte;
|
||||
++t;
|
||||
}
|
||||
if (!t->type) {
|
||||
return;
|
||||
}
|
||||
if (t->type != TOKEN_INT) {
|
||||
return;
|
||||
}
|
||||
int bit = 1 << (col % 8);
|
||||
if (t->value & bit) {
|
||||
pixmap->data[row * pixmap->width + col] = color;
|
||||
}
|
||||
}
|
||||
++t;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* parse_xbm_tokens - parse xbm tokens and create pixmap
|
||||
* @tokens: token vector
|
||||
*/
|
||||
static struct pixmap
|
||||
parse_xbm_tokens(struct token *tokens)
|
||||
{
|
||||
struct pixmap pixmap = { 0 };
|
||||
|
||||
for (struct token *t = tokens; t->type; t++) {
|
||||
if (pixmap.width && pixmap.height) {
|
||||
if (t->type != TOKEN_INT) {
|
||||
continue;
|
||||
}
|
||||
process_bytes(&pixmap, t);
|
||||
goto out;
|
||||
}
|
||||
if (strstr(t->name, "width")) {
|
||||
pixmap.width = atoi((++t)->name);
|
||||
} else if (strstr(t->name, "height")) {
|
||||
pixmap.height = atoi((++t)->name);
|
||||
}
|
||||
}
|
||||
out:
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
/*
|
||||
* Openbox built-in icons are not bigger than 8x8, so have only written this
|
||||
* function to cope wit that max size
|
||||
*/
|
||||
#define LABWC_BUILTIN_ICON_MAX_SIZE (8)
|
||||
|
||||
/**
|
||||
* parse_xbm_builtin - parse builtin xbm button and create pixmap
|
||||
* @button: button byte array (xbm format)
|
||||
*/
|
||||
static struct pixmap
|
||||
parse_xbm_builtin(const char *button, int size)
|
||||
{
|
||||
struct pixmap pixmap = { 0 };
|
||||
|
||||
assert(size <= LABWC_BUILTIN_ICON_MAX_SIZE);
|
||||
pixmap.width = size;
|
||||
pixmap.height = size;
|
||||
|
||||
struct token t[LABWC_BUILTIN_ICON_MAX_SIZE + 1];
|
||||
for (int i = 0; i < size; i++) {
|
||||
t[i].value = button[i];
|
||||
t[i].type = TOKEN_INT;
|
||||
}
|
||||
t[size].type = 0;
|
||||
process_bytes(&pixmap, t);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
void
|
||||
img_xbm_from_bitmap(const char *bitmap, struct lab_data_buffer **buffer,
|
||||
float *rgba)
|
||||
{
|
||||
struct pixmap pixmap = {0};
|
||||
if (*buffer) {
|
||||
wlr_buffer_drop(&(*buffer)->base);
|
||||
*buffer = NULL;
|
||||
}
|
||||
color = argb32(rgba);
|
||||
pixmap = parse_xbm_builtin(bitmap, 6);
|
||||
*buffer = buffer_create_wrap(pixmap.data, pixmap.width, pixmap.height,
|
||||
pixmap.width * 4, /* free_on_destroy */ true);
|
||||
}
|
||||
|
||||
void
|
||||
img_xbm_load(const char *filename, struct lab_data_buffer **buffer,
|
||||
float *rgba)
|
||||
{
|
||||
struct pixmap pixmap = {0};
|
||||
if (*buffer) {
|
||||
wlr_buffer_drop(&(*buffer)->base);
|
||||
*buffer = NULL;
|
||||
}
|
||||
if (string_null_or_empty(filename)) {
|
||||
return;
|
||||
}
|
||||
color = argb32(rgba);
|
||||
|
||||
/* Read file into memory as it's easier to tokenize that way */
|
||||
struct buf token_buf = grab_file(filename);
|
||||
if (token_buf.len) {
|
||||
struct token *tokens = tokenize_xbm(token_buf.data);
|
||||
pixmap = parse_xbm_tokens(tokens);
|
||||
if (tokens) {
|
||||
free(tokens);
|
||||
}
|
||||
}
|
||||
buf_reset(&token_buf);
|
||||
if (!pixmap.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create buffer with free_on_destroy being true */
|
||||
if (pixmap.data) {
|
||||
*buffer = buffer_create_wrap(pixmap.data, pixmap.width,
|
||||
pixmap.height, pixmap.width * 4, true);
|
||||
}
|
||||
}
|
||||
11
src/img/meson.build
Normal file
11
src/img/meson.build
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
labwc_sources += files(
|
||||
'img-png.c',
|
||||
'img-xbm.c',
|
||||
)
|
||||
|
||||
if have_rsvg
|
||||
labwc_sources += files(
|
||||
'img-svg.c',
|
||||
)
|
||||
endif
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue