У Telegram Desktop на Linux при включённом GNotification в системных уведомлениях пропадают аватарки контактов/чатов.
При этом если отключить GNotification и использовать родной backend уведомлений Telegram, аватарки появляются. Также проблема не зависит от notification daemon: проверено как с dunst, так и с xfce4-notifyd.
Система:
- Arch Linux
- X11
- Telegram Desktop 7.0.9
- GLib 2.88.3
- dunst 1.13.2
Похоже, проблема находится в связке GNotification → GLib → freedesktop notifications.
Telegram передаёт аватарку в GNotification как GBytesIcon. Но GLib при преобразовании GNotification в org.freedesktop.Notifications.Notify не преобразует GBytesIcon в image-data/image-path.
В результате в D-Bus вызове Notify есть desktop-entry, urgency, category и т. п., но нет ни image-data, ни image-path:
string "Telegram Desktop"
uint32 0
string ""
string "..."
string "..."
...
array [
dict entry(
string "desktop-entry"
variant string "telegram-desktop"
)
dict entry(
string "urgency"
variant byte 1
)
dict entry(
string "category"
variant string "im.received"
)
]
То есть notification daemon просто не получает изображение.
Интересно, что это как оказалось можно исправить.
Я сделал небольшой LD_PRELOAD shim, который перехватывает:
g_notification_set_icon(GNotification *, GIcon *)
Если передан GBytesIcon, shim извлекает из него PNG, сохраняет его в /run/user/$UID/gnotification-icons/, создаёт GFileIcon и передаёт уже его настоящей функции GLib.
После этого GLib нормально отправляет:
image-path = /run/user/$UID/gnotification-icons/<hash>.png
и аватарка появляется.
Таким образом, получается:
Telegram
→ GNotification
→ GBytesIcon
→ [shim: GBytesIcon → PNG → GFileIcon]
→ GLib
→ org.freedesktop.Notifications
→ dunst/xfce4-notifyd
Возможно, это можно исправить непосредственно в Telegram Desktop или в GLib. Если кто-нибудь знает более правильный способ — буду рад.
Для тех, кому нужен рабочий workaround, ниже полный вариант.
Исходник shim.c:
#define _GNU_SOURCE
#include <gio/gio.h>
#include <glib.h>
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
typedef void (*g_notification_set_icon_fn)(GNotification *, GIcon *);
static g_notification_set_icon_fn real_g_notification_set_icon = NULL;
static char *icon_dir = NULL;
static GMutex init_mutex;
static gboolean initialized = FALSE;
static void init_shim(void)
{
g_mutex_lock(&init_mutex);
if (initialized) {
g_mutex_unlock(&init_mutex);
return;
}
initialized = TRUE;
uid_t uid = getuid();
icon_dir = g_strdup_printf(
"/run/user/%u/gnotification-icons",
(unsigned)uid);
if (g_mkdir_with_parents(icon_dir, 0700) != 0)
g_clear_pointer(&icon_dir, g_free);
g_mutex_unlock(&init_mutex);
}
static void resolve_real_function(void)
{
if (real_g_notification_set_icon != NULL)
return;
real_g_notification_set_icon =
(g_notification_set_icon_fn)dlsym(
RTLD_NEXT,
"g_notification_set_icon");
}
static char *write_icon_if_needed(GBytes *bytes)
{
if (bytes == NULL || icon_dir == NULL)
return NULL;
gsize size = 0;
const guint8 *data = g_bytes_get_data(bytes, &size);
if (data == NULL || size == 0)
return NULL;
gchar *hash = g_compute_checksum_for_data(
G_CHECKSUM_SHA256,
data,
size);
gchar *path = g_build_filename(
icon_dir,
hash,
NULL);
gchar *png_path = g_strconcat(path, ".png", NULL);
if (g_file_test(png_path, G_FILE_TEST_IS_REGULAR)) {
g_free(hash);
g_free(path);
return png_path;
}
gchar *tmp_template = g_strconcat(
path,
".tmp-XXXXXX",
NULL);
int fd = g_mkstemp(tmp_template);
if (fd == -1) {
g_free(hash);
g_free(path);
g_free(png_path);
g_free(tmp_template);
return NULL;
}
fchmod(fd, 0600);
gboolean success = TRUE;
gsize written = 0;
while (written < size) {
ssize_t n = write(
fd,
data + written,
size - written);
if (n < 0) {
if (errno == EINTR)
continue;
success = FALSE;
break;
}
if (n == 0) {
success = FALSE;
break;
}
written += (gsize)n;
}
if (success && fsync(fd) != 0)
success = FALSE;
close(fd);
if (!success) {
unlink(tmp_template);
g_free(hash);
g_free(path);
g_free(png_path);
g_free(tmp_template);
return NULL;
}
if (rename(tmp_template, png_path) != 0) {
if (!g_file_test(png_path, G_FILE_TEST_IS_REGULAR)) {
unlink(tmp_template);
g_free(hash);
g_free(path);
g_free(png_path);
g_free(tmp_template);
return NULL;
}
unlink(tmp_template);
}
g_free(hash);
g_free(path);
g_free(tmp_template);
return png_path;
}
void g_notification_set_icon(
GNotification *notification,
GIcon *icon)
{
resolve_real_function();
if (real_g_notification_set_icon == NULL)
return;
init_shim();
if (icon != NULL && G_IS_BYTES_ICON(icon)) {
GBytes *bytes = g_bytes_icon_get_bytes(
G_BYTES_ICON(icon));
char *png_path = write_icon_if_needed(bytes);
if (png_path != NULL) {
GFile *file = g_file_new_for_path(png_path);
GIcon *file_icon = g_file_icon_new(file);
real_g_notification_set_icon(
notification,
file_icon);
g_object_unref(file_icon);
g_object_unref(file);
g_free(png_path);
return;
}
}
real_g_notification_set_icon(notification, icon);
}
Сборка:
gcc -shared -fPIC \
-o ~/.local/lib/libtelegram-gicon-shim.so \
shim.c \
$(pkg-config --cflags --libs gio-2.0) \
-ldl
Запуск Telegram:
LD_PRELOAD="$HOME/.local/lib/libtelegram-gicon-shim.so" \
telegram-desktop
Если Telegram запускается через собственный wrapper, то:
#!/usr/bin/env bash
export LD_PRELOAD="$HOME/.local/lib/libtelegram-gicon-shim.so"
exec /usr/bin/Telegram "$@"
После первого уведомления изображения появляются здесь:
/run/user/$UID/gnotification-icons/
Они находятся в пользовательском runtime-каталоге, поэтому после завершения пользовательской сессии исчезают сами.
Файлы именуются по SHA-256 содержимого PNG, поэтому одинаковые аватарки не дублируются.
Если кому-нибудь интересно, можно попробовать сделать из этого небольшой пакет для Arch или нормальный патч для Telegram/GLib.
Разбирался почему не работает и делал с помощью ChatGPT
А так я не програмист, я обычный сварщик, так что сильно не бейте)
