Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .builds/alpine.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ packages:
- wayland-protocols
- cairo-dev
- pango-dev
- gdk-pixbuf-dev
- scdoc
sources:
- https://github.com/emersion/mako
- https://gitlab.freedesktop.org/emersion/keulim.git
tasks:
- keulim: |
cd keulim
meson setup build/
ninja -C build/
sudo ninja -C build/ install
- setup: |
cd mako
meson setup build/ --fatal-meson-warnings -Dauto_features=enabled -Dsd-bus-provider=libelogind
Expand Down
2 changes: 1 addition & 1 deletion .builds/archlinux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ packages:
- wayland-protocols
- cairo
- pango
- gdk-pixbuf2
- keulim-git
- scdoc
- systemd
sources:
Expand Down
7 changes: 6 additions & 1 deletion .builds/freebsd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ image: freebsd/latest
packages:
- basu
- evdev-proto
- gdk-pixbuf2
- libepoll-shim
- meson
- pango
Expand All @@ -12,7 +11,13 @@ packages:
- wayland-protocols
sources:
- https://github.com/emersion/mako
- https://gitlab.freedesktop.org/emersion/keulim.git
tasks:
- keulim: |
cd keulim
meson setup build/
ninja -C build/
sudo ninja -C build/ install
- setup: |
cd mako
meson setup build/ --fatal-meson-warnings -Dauto_features=enabled -Dsd-bus-provider=basu
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/subprojects/
/subprojects/*
!/subprojects/*.wrap
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Install dependencies:
* pango
* cairo
* systemd, elogind or [basu] (for the sd-bus library)
* gdk-pixbuf (optional, for icons support)
* [keulim] (optional, for icons support)
* dbus (runtime dependency, user-session support is required)
* scdoc (optional, for man pages)

Expand All @@ -69,3 +69,4 @@ MIT
[irc]: https://web.libera.chat/gamja/#emersion
[spec]: https://specifications.freedesktop.org/notification-spec/latest/
[basu]: https://github.com/emersion/basu
[keulim]: https://gitlab.freedesktop.org/emersion/keulim
91 changes: 0 additions & 91 deletions cairo-pixbuf.c

This file was deleted.

195 changes: 161 additions & 34 deletions icon.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@

#ifdef HAVE_ICONS

#include <gdk-pixbuf/gdk-pixbuf.h>
#include "cairo-pixbuf.h"
#include <keulim.h>

static bool validate_icon_name(const char* icon_name) {
int icon_len = strlen(icon_name);
Expand All @@ -40,29 +39,165 @@ static bool validate_icon_name(const char* icon_name) {
return true;
}

static GdkPixbuf *load_image(const char *path) {
if (strlen(path) == 0) {
static uint8_t to_premult(uint8_t x, uint8_t a) {
return (uint8_t)roundf((float)x * (float)a / 0xFF);
}

/**
* Create a cairo surface from 8-bit unpacked RGB(A) pixel data, with straight
* alpha.
*/
static cairo_surface_t *create_surface_from_data(const uint8_t *src_pixels,
size_t width, size_t height, size_t src_stride, bool has_alpha) {
size_t src_bytes_per_pixel = has_alpha ? 4 : 3;
cairo_format_t format = has_alpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24;

cairo_surface_t *surface = cairo_image_surface_create(format, width, height);
if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
fprintf(stderr, "Failed to create cairo surface\n");
cairo_surface_destroy(surface);
return NULL;
}
GError *err = NULL;
GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &err);
if (!pixbuf) {
fprintf(stderr, "Failed to load icon (%s)\n", err->message);
g_error_free(err);

cairo_surface_flush(surface);

uint8_t *dst_pixels = cairo_image_surface_get_data(surface);
int dst_stride = cairo_image_surface_get_stride(surface);
for (size_t y = 0; y < height; y++) {
for (size_t x = 0; x < width; x++) {
const uint8_t *src = &src_pixels[y * src_stride + x * src_bytes_per_pixel];
uint8_t *dst = &dst_pixels[y * dst_stride + x * sizeof(uint32_t)];

uint8_t r = src[0];
uint8_t g = src[1];
uint8_t b = src[2];
uint8_t a = has_alpha ? src[3] : 0xFF;

// Convert from straight alpha to pre-multiplied alpha
r = to_premult(r, a);
g = to_premult(g, a);
b = to_premult(b, a);

// Convert from unpacked RGBA to native-endian packed ARGB
uint32_t packed = 0;
packed |= (uint32_t)r << 16;
packed |= (uint32_t)g << 8;
packed |= b;
packed |= (uint32_t)a << 24;

memcpy(dst, &packed, sizeof(packed));
}
}

cairo_surface_mark_dirty(surface);

return surface;
}

static cairo_surface_t *load_image(const char *path) {
if (strlen(path) == 0) {
return NULL;
}
return pixbuf;

FILE *f = fopen(path, "r");
if (f == NULL) {
fprintf(stderr, "Failed to open icon\n");
goto error;
}

struct klm_decoder *dec = klm_decoder_create_with_file(f);
if (dec == NULL) {
fprintf(stderr, "Failed to create icon decoder\n");
goto error_file;
}

const struct klm_decoder_info *info = klm_decoder_read_info(dec);
if (info == NULL) {
fprintf(stderr, "Failed to decode icon info\n");
goto error_dec;
}

enum klm_format format;
size_t bytes_per_pixel = 0;
bool has_alpha = false;
for (size_t i = 0; i < info->formats_len; i++) {
format = info->formats[i];
if (format == KLM_FORMAT_R8G8B8) {
bytes_per_pixel = 3;
break;
} else if (format == KLM_FORMAT_R8G8B8A8) {
bytes_per_pixel = 4;
has_alpha = true;
break;
}
}
if (bytes_per_pixel == 0) {
fprintf(stderr, "Unsupported icon pixel format\n");
goto error_dec;
}

size_t stride = bytes_per_pixel * info->width;
size_t size = stride * info->height;
uint8_t *buffer = malloc(size);
if (buffer == NULL) {
perror("Failed to allocate buffer");
goto error_buffer;
}

bool to_srgb_gamma22 = info->color_primaries != 0 && info->color_transfer_function != 0;
struct klm_decoder_read_frame_options options = {
.format = format,
.buffer = buffer,
.size = size,
.stride = stride,
.color_primaries = to_srgb_gamma22 ? KLM_COLOR_PRIMARIES_SRGB : 0,
.color_transfer_function = to_srgb_gamma22 ? KLM_COLOR_TRANSFER_FUNCTION_GAMMA22 : 0,
};
if (!klm_decoder_read_frame(dec, &options)) {
fprintf(stderr, "Failed to decode icon frame\n");
goto error_buffer;
}

cairo_surface_t *surface = create_surface_from_data(buffer,
info->width, info->height, stride, has_alpha);
if (surface == NULL) {
goto error_buffer;
}

free(buffer);
klm_decoder_destroy(dec);
fclose(f);
return surface;

error_buffer:
free(buffer);
error_dec:
klm_decoder_destroy(dec);
error_file:
fclose(f);
error:
fprintf(stderr, "Failed to load icon from %s\n", path);
return NULL;
}

static GdkPixbuf *load_image_data(struct mako_image_data *image_data) {
GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(image_data->data, GDK_COLORSPACE_RGB,
image_data->has_alpha, image_data->bits_per_sample, image_data->width,
image_data->height, image_data->rowstride, NULL, NULL);
if (!pixbuf) {
fprintf(stderr, "Failed to load icon\n");
static cairo_surface_t *load_image_data(struct mako_image_data *image_data) {
if (image_data->bits_per_sample != 8) {
fprintf(stderr, "Unsupported number of bits per sample\n");
return NULL;
}
return pixbuf;
if ((image_data->has_alpha && image_data->channels != 4) ||
(!image_data->has_alpha && image_data->channels != 3)) {
fprintf(stderr, "Unsupported number of channels\n");
return NULL;
}

cairo_surface_t *surface = create_surface_from_data(image_data->data,
image_data->width, image_data->height, image_data->rowstride, image_data->has_alpha);
if (surface == NULL) {
return NULL;
}

return surface;
}

static double fit_to_square(int width, int height, int square_size) {
Expand Down Expand Up @@ -251,40 +386,32 @@ static char *resolve_icon(struct mako_notification *notif) {
}

struct mako_icon *create_icon(struct mako_notification *notif) {
GdkPixbuf *image = NULL;
cairo_surface_t *surface = NULL;
if (notif->image_data != NULL) {
image = load_image_data(notif->image_data);
surface = load_image_data(notif->image_data);
}

if (image == NULL) {
if (surface == NULL) {
char *path = resolve_icon(notif);
if (path == NULL) {
return NULL;
}

image = load_image(path);
surface = load_image(path);
free(path);
if (image == NULL) {
if (surface == NULL) {
return NULL;
}
}

int image_width = gdk_pixbuf_get_width(image);
int image_height = gdk_pixbuf_get_height(image);
int image_width = cairo_image_surface_get_width(surface);
int image_height = cairo_image_surface_get_height(surface);

struct mako_icon *icon = calloc(1, sizeof(struct mako_icon));
icon->scale = fit_to_square(
image_width, image_height, notif->style.max_icon_size);
icon->scale = fit_to_square(image_width, image_height, notif->style.max_icon_size);
icon->width = image_width * icon->scale;
icon->height = image_height * icon->scale;

icon->image = create_cairo_surface_from_gdk_pixbuf(image);
g_object_unref(image);
if (icon->image == NULL) {
free(icon);
return NULL;
}

icon->image = surface;
return icon;
}
#else
Expand Down
Loading