LINUX.ORG.RU

Telegram Desktop: пропадают аватарки уведомлений при включённом GNotification [Решение]

 , , , ,


0

1

У 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

А так я не програмист, я обычный сварщик, так что сильно не бейте)

★
Ответ на: комментарий от Ja-Ja-Hey-Ho

с этим воркараундом можно пользоваться прямо сейчас, не дожидаясь пока разберутся кто виноват и починят

BACR ★
() автор топика
  • Markdown
Пустая строка (два раза Enter) начинает новый абзац. Знак '>' в начале абзаца выделяет абзац курсивом цитирования.
Внимание: прочитайте описание разметки Markdown.
Используйте Ctrl-Enter для размещения комментария