mirror of
https://github.com/labwc/labwc.git
synced 2026-04-09 08:21:18 -04:00
This commit fixes that client-side icons were not loaded when the rendered icon size is larger than icon sizes from the client. This bug has become more likely to happen due to the new thumnail-style window switcher. The cause was `abs(INT_MIN)` becomes `INT_MIN` due to integer overflow.
361 lines
10 KiB
C
361 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
#define _POSIX_C_SOURCE 200809L
|
|
#include "common/scaled-icon-buffer.h"
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <wlr/util/log.h>
|
|
#include "buffer.h"
|
|
#include "common/macros.h"
|
|
#include "common/mem.h"
|
|
#include "common/scaled-scene-buffer.h"
|
|
#include "common/string-helpers.h"
|
|
#include "config.h"
|
|
#include "config/rcxml.h"
|
|
#include "desktop-entry.h"
|
|
#include "img/img.h"
|
|
#include "node.h"
|
|
#include "view.h"
|
|
#include "window-rules.h"
|
|
|
|
#if HAVE_LIBSFDO
|
|
|
|
static struct lab_data_buffer *
|
|
choose_best_icon_buffer(struct scaled_icon_buffer *self, int icon_size, double scale)
|
|
{
|
|
int best_dist = -INT_MAX;
|
|
struct lab_data_buffer *best_buffer = NULL;
|
|
|
|
struct lab_data_buffer **buffer;
|
|
wl_array_for_each(buffer, &self->view_icon_buffers) {
|
|
int curr_dist = (*buffer)->base.width - (int)(icon_size * scale);
|
|
bool curr_is_better;
|
|
if ((curr_dist < 0 && best_dist > 0)
|
|
|| (curr_dist > 0 && best_dist < 0)) {
|
|
/* prefer too big icon over too small icon */
|
|
curr_is_better = curr_dist > 0;
|
|
} else {
|
|
curr_is_better = abs(curr_dist) < abs(best_dist);
|
|
}
|
|
if (curr_is_better) {
|
|
best_dist = curr_dist;
|
|
best_buffer = *buffer;
|
|
}
|
|
}
|
|
return best_buffer;
|
|
}
|
|
|
|
static struct lab_data_buffer *
|
|
img_to_buffer(struct lab_img *img, int width, int height, double scale)
|
|
{
|
|
struct lab_data_buffer *buffer = lab_img_render(img, width, height, scale);
|
|
lab_img_destroy(img);
|
|
return buffer;
|
|
}
|
|
|
|
/*
|
|
* Load an icon from application-supplied icon name or buffers.
|
|
* Wayland apps can provide icon names and buffers via xdg-toplevel-icon protocol.
|
|
* X11 apps can provide icon buffers via _NET_WM_ICON property.
|
|
*/
|
|
static struct lab_data_buffer *
|
|
load_client_icon(struct scaled_icon_buffer *self, int icon_size, double scale)
|
|
{
|
|
struct lab_img *img = desktop_entry_load_icon(self->server,
|
|
self->view_icon_name, icon_size, scale);
|
|
if (img) {
|
|
wlr_log(WLR_DEBUG, "loaded icon from client icon name");
|
|
return img_to_buffer(img, self->width, self->height, scale);
|
|
}
|
|
|
|
struct lab_data_buffer *buffer = choose_best_icon_buffer(self, icon_size, scale);
|
|
if (buffer) {
|
|
wlr_log(WLR_DEBUG, "loaded icon from client buffer");
|
|
return buffer_resize(buffer, self->width, self->height, scale);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Load an icon by a view's app_id. For example, if the app_id is 'firefox', then
|
|
* libsfdo will parse firefox.desktop to get the Icon name and then find that icon
|
|
* based on the icon theme specified in rc.xml.
|
|
*/
|
|
static struct lab_data_buffer *
|
|
load_server_icon(struct scaled_icon_buffer *self, int icon_size, double scale)
|
|
{
|
|
struct lab_img *img = desktop_entry_load_icon_from_app_id(self->server,
|
|
self->view_app_id, icon_size, scale);
|
|
if (img) {
|
|
wlr_log(WLR_DEBUG, "loaded icon by app_id");
|
|
return img_to_buffer(img, self->width, self->height, scale);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#endif /* HAVE_LIBSFDO */
|
|
|
|
static struct lab_data_buffer *
|
|
_create_buffer(struct scaled_scene_buffer *scaled_buffer, double scale)
|
|
{
|
|
#if HAVE_LIBSFDO
|
|
struct scaled_icon_buffer *self = scaled_buffer->data;
|
|
int icon_size = MIN(self->width, self->height);
|
|
struct lab_img *img = NULL;
|
|
struct lab_data_buffer *buffer = NULL;
|
|
|
|
if (self->icon_name) {
|
|
/* generic icon (e.g. menu icons) */
|
|
img = desktop_entry_load_icon(self->server, self->icon_name,
|
|
icon_size, scale);
|
|
if (img) {
|
|
wlr_log(WLR_DEBUG, "loaded icon by icon name");
|
|
return img_to_buffer(img, self->width, self->height, scale);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* window icon */
|
|
if (self->view_icon_prefer_client) {
|
|
buffer = load_client_icon(self, icon_size, scale);
|
|
if (buffer) {
|
|
return buffer;
|
|
}
|
|
buffer = load_server_icon(self, icon_size, scale);
|
|
if (buffer) {
|
|
return buffer;
|
|
}
|
|
} else {
|
|
buffer = load_server_icon(self, icon_size, scale);
|
|
if (buffer) {
|
|
return buffer;
|
|
}
|
|
buffer = load_client_icon(self, icon_size, scale);
|
|
if (buffer) {
|
|
return buffer;
|
|
}
|
|
}
|
|
/* If both client and server icons are unavailable, use the fallback icon */
|
|
img = desktop_entry_load_icon(self->server, rc.fallback_app_icon_name,
|
|
icon_size, scale);
|
|
if (img) {
|
|
wlr_log(WLR_DEBUG, "loaded fallback icon");
|
|
return img_to_buffer(img, self->width, self->height, scale);
|
|
}
|
|
#endif /* HAVE_LIBSFDO */
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
set_icon_buffers(struct scaled_icon_buffer *self, struct wl_array *buffers)
|
|
{
|
|
struct lab_data_buffer **icon_buffer;
|
|
wl_array_for_each(icon_buffer, &self->view_icon_buffers) {
|
|
wlr_buffer_unlock(&(*icon_buffer)->base);
|
|
}
|
|
wl_array_release(&self->view_icon_buffers);
|
|
wl_array_init(&self->view_icon_buffers);
|
|
|
|
if (!buffers) {
|
|
return;
|
|
}
|
|
|
|
wl_array_for_each(icon_buffer, buffers) {
|
|
wlr_buffer_lock(&(*icon_buffer)->base);
|
|
}
|
|
wl_array_copy(&self->view_icon_buffers, buffers);
|
|
}
|
|
|
|
static void
|
|
_destroy(struct scaled_scene_buffer *scaled_buffer)
|
|
{
|
|
struct scaled_icon_buffer *self = scaled_buffer->data;
|
|
if (self->view) {
|
|
wl_list_remove(&self->on_view.set_icon.link);
|
|
wl_list_remove(&self->on_view.new_title.link);
|
|
wl_list_remove(&self->on_view.new_app_id.link);
|
|
wl_list_remove(&self->on_view.destroy.link);
|
|
}
|
|
free(self->view_app_id);
|
|
free(self->view_icon_name);
|
|
set_icon_buffers(self, NULL);
|
|
free(self->icon_name);
|
|
free(self);
|
|
}
|
|
|
|
static bool
|
|
icon_buffers_equal(struct wl_array *a, struct wl_array *b)
|
|
{
|
|
if (a->size != b->size) {
|
|
return false;
|
|
}
|
|
return a->size == 0 || !memcmp(a->data, b->data, a->size);
|
|
}
|
|
|
|
static bool
|
|
_equal(struct scaled_scene_buffer *scaled_buffer_a,
|
|
struct scaled_scene_buffer *scaled_buffer_b)
|
|
{
|
|
struct scaled_icon_buffer *a = scaled_buffer_a->data;
|
|
struct scaled_icon_buffer *b = scaled_buffer_b->data;
|
|
|
|
return str_equal(a->view_app_id, b->view_app_id)
|
|
&& a->view_icon_prefer_client == b->view_icon_prefer_client
|
|
&& str_equal(a->view_icon_name, b->view_icon_name)
|
|
&& icon_buffers_equal(&a->view_icon_buffers, &b->view_icon_buffers)
|
|
&& str_equal(a->icon_name, b->icon_name)
|
|
&& a->width == b->width
|
|
&& a->height == b->height;
|
|
}
|
|
|
|
static struct scaled_scene_buffer_impl impl = {
|
|
.create_buffer = _create_buffer,
|
|
.destroy = _destroy,
|
|
.equal = _equal,
|
|
};
|
|
|
|
struct scaled_icon_buffer *
|
|
scaled_icon_buffer_create(struct wlr_scene_tree *parent, struct server *server,
|
|
int width, int height)
|
|
{
|
|
assert(parent);
|
|
assert(width >= 0 && height >= 0);
|
|
|
|
struct scaled_scene_buffer *scaled_buffer = scaled_scene_buffer_create(
|
|
parent, &impl, /* drop_buffer */ true);
|
|
struct scaled_icon_buffer *self = znew(*self);
|
|
self->scaled_buffer = scaled_buffer;
|
|
self->scene_buffer = scaled_buffer->scene_buffer;
|
|
self->server = server;
|
|
self->width = width;
|
|
self->height = height;
|
|
|
|
scaled_buffer->data = self;
|
|
|
|
return self;
|
|
}
|
|
|
|
static void
|
|
handle_view_set_icon(struct wl_listener *listener, void *data)
|
|
{
|
|
struct scaled_icon_buffer *self =
|
|
wl_container_of(listener, self, on_view.set_icon);
|
|
|
|
bool icon_name_equal = str_equal(self->view_icon_name, self->view->icon.name);
|
|
if (icon_name_equal && icon_buffers_equal(&self->view_icon_buffers,
|
|
&self->view->icon.buffers)) {
|
|
return;
|
|
}
|
|
|
|
if (!icon_name_equal) {
|
|
zfree(self->view_icon_name);
|
|
if (self->view->icon.name) {
|
|
xstrdup_replace(self->view_icon_name, self->view->icon.name);
|
|
}
|
|
}
|
|
|
|
set_icon_buffers(self, &self->view->icon.buffers);
|
|
scaled_scene_buffer_request_update(self->scaled_buffer,
|
|
self->width, self->height);
|
|
}
|
|
|
|
static void
|
|
handle_view_new_title(struct wl_listener *listener, void *data)
|
|
{
|
|
struct scaled_icon_buffer *self =
|
|
wl_container_of(listener, self, on_view.new_title);
|
|
|
|
bool prefer_client = window_rules_get_property(
|
|
self->view, "iconPreferClient") == LAB_PROP_TRUE;
|
|
if (prefer_client == self->view_icon_prefer_client) {
|
|
return;
|
|
}
|
|
self->view_icon_prefer_client = prefer_client;
|
|
scaled_scene_buffer_request_update(self->scaled_buffer,
|
|
self->width, self->height);
|
|
}
|
|
|
|
static void
|
|
handle_view_new_app_id(struct wl_listener *listener, void *data)
|
|
{
|
|
struct scaled_icon_buffer *self =
|
|
wl_container_of(listener, self, on_view.new_app_id);
|
|
|
|
const char *app_id = view_get_string_prop(self->view, "app_id");
|
|
if (str_equal(app_id, self->view_app_id)) {
|
|
return;
|
|
}
|
|
|
|
xstrdup_replace(self->view_app_id, app_id);
|
|
self->view_icon_prefer_client = window_rules_get_property(
|
|
self->view, "iconPreferClient") == LAB_PROP_TRUE;
|
|
scaled_scene_buffer_request_update(self->scaled_buffer,
|
|
self->width, self->height);
|
|
}
|
|
|
|
static void
|
|
handle_view_destroy(struct wl_listener *listener, void *data)
|
|
{
|
|
struct scaled_icon_buffer *self =
|
|
wl_container_of(listener, self, on_view.destroy);
|
|
wl_list_remove(&self->on_view.destroy.link);
|
|
wl_list_remove(&self->on_view.set_icon.link);
|
|
wl_list_remove(&self->on_view.new_title.link);
|
|
wl_list_remove(&self->on_view.new_app_id.link);
|
|
self->view = NULL;
|
|
}
|
|
|
|
void
|
|
scaled_icon_buffer_set_view(struct scaled_icon_buffer *self, struct view *view)
|
|
{
|
|
assert(view);
|
|
if (self->view == view) {
|
|
return;
|
|
}
|
|
|
|
if (self->view) {
|
|
wl_list_remove(&self->on_view.set_icon.link);
|
|
wl_list_remove(&self->on_view.new_title.link);
|
|
wl_list_remove(&self->on_view.new_app_id.link);
|
|
wl_list_remove(&self->on_view.destroy.link);
|
|
}
|
|
self->view = view;
|
|
|
|
self->on_view.set_icon.notify = handle_view_set_icon;
|
|
wl_signal_add(&view->events.set_icon, &self->on_view.set_icon);
|
|
|
|
self->on_view.new_title.notify = handle_view_new_title;
|
|
wl_signal_add(&view->events.new_title, &self->on_view.new_title);
|
|
|
|
self->on_view.new_app_id.notify = handle_view_new_app_id;
|
|
wl_signal_add(&view->events.new_app_id, &self->on_view.new_app_id);
|
|
|
|
self->on_view.destroy.notify = handle_view_destroy;
|
|
wl_signal_add(&view->events.destroy, &self->on_view.destroy);
|
|
|
|
handle_view_set_icon(&self->on_view.set_icon, NULL);
|
|
handle_view_new_app_id(&self->on_view.new_app_id, NULL);
|
|
handle_view_new_title(&self->on_view.new_title, NULL);
|
|
}
|
|
|
|
void
|
|
scaled_icon_buffer_set_icon_name(struct scaled_icon_buffer *self,
|
|
const char *icon_name)
|
|
{
|
|
assert(icon_name);
|
|
if (str_equal(self->icon_name, icon_name)) {
|
|
return;
|
|
}
|
|
xstrdup_replace(self->icon_name, icon_name);
|
|
scaled_scene_buffer_request_update(self->scaled_buffer, self->width, self->height);
|
|
}
|
|
|
|
struct scaled_icon_buffer *
|
|
scaled_icon_buffer_from_node(struct wlr_scene_node *node)
|
|
{
|
|
struct scaled_scene_buffer *scaled_buffer =
|
|
node_scaled_scene_buffer_from_node(node);
|
|
assert(scaled_buffer->impl == &impl);
|
|
return scaled_buffer->data;
|
|
}
|