Support GNotification

It's used if there's a gtk notification daemon or application is running sandboxed without access to the freedesktop protocol.

GNotification API is poor, but should feel native on environments using GNOME technologies.
This commit is contained in:
Ilya Fedin
2022-11-11 15:49:50 +04:00
committed by John Preston
parent afaad155a0
commit f9dd2b4a0a
10 changed files with 439 additions and 16 deletions

View File

@@ -9,10 +9,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/linux/notifications_manager_linux.h"
#include "window/notifications_utilities.h"
#include "base/options.h"
#include "base/platform/base_platform_info.h"
#include "base/platform/linux/base_linux_glibmm_helper.h"
#include "base/platform/linux/base_linux_dbus_utilities.h"
#include "platform/platform_specific.h"
#include "core/application.h"
#include "core/sandbox.h"
#include "core/core_settings.h"
#include "data/data_forum_topic.h"
#include "history/history.h"
@@ -27,6 +30,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <glibmm.h>
#include <giomm.h>
#include <dlfcn.h>
namespace Platform {
namespace Notifications {
namespace {
@@ -341,6 +346,9 @@ private:
const not_null<Manager*> _manager;
NotificationId _id;
Glib::RefPtr<Gio::Application> _application;
Glib::RefPtr<Gio::Notification> _notification;
Glib::RefPtr<Gio::DBus::Connection> _dbusConnection;
Glib::ustring _title;
Glib::ustring _body;
@@ -367,7 +375,8 @@ NotificationData::NotificationData(
not_null<Manager*> manager,
NotificationId id)
: _manager(manager)
, _id(id) {
, _id(id)
, _application(Gio::Application::get_default()) {
}
bool NotificationData::init(
@@ -375,6 +384,64 @@ bool NotificationData::init(
const QString &subtitle,
const QString &msg,
Window::Notifications::Manager::DisplayOptions options) {
if (_application) {
_notification = Gio::Notification::create(title.toStdString());
_notification->set_body(
subtitle.isEmpty()
? msg.toStdString()
: qsl("%1\n%2").arg(subtitle, msg).toStdString());
_notification->set_icon(
Gio::ThemedIcon::create(base::IconName().toStdString()));
// glib 2.42+, we keep glib 2.40+ compatibility
static const auto set_priority = [] {
// reset dlerror after dlsym call
const auto guard = gsl::finally([] { dlerror(); });
return reinterpret_cast<decltype(&g_notification_set_priority)>(
dlsym(RTLD_DEFAULT, "g_notification_set_priority"));
}();
if (set_priority) {
// for chat messages, according to
// https://docs.gtk.org/gio/enum.NotificationPriority.html
set_priority(_notification->gobj(), G_NOTIFICATION_PRIORITY_HIGH);
}
// glib 2.70+, we keep glib 2.40+ compatibility
static const auto set_category = [] {
// reset dlerror after dlsym call
const auto guard = gsl::finally([] { dlerror(); });
return reinterpret_cast<decltype(&g_notification_set_category)>(
dlsym(RTLD_DEFAULT, "g_notification_set_category"));
}();
if (set_category) {
set_category(_notification->gobj(), "im.received");
}
const auto idTuple = _id.toTuple();
_notification->set_default_action(
"app.notification-reply",
idTuple);
if (!options.hideMarkAsRead) {
_notification->add_button(
tr::lng_context_mark_read(tr::now).toStdString(),
"app.notification-mark-as-read",
idTuple);
}
_notification->add_button(
tr::lng_notification_reply(tr::now).toStdString(),
"app.notification-reply",
idTuple);
return true;
}
Noexcept([&] {
_dbusConnection = Gio::DBus::Connection::get_sync(
Gio::DBus::BusType::SESSION);
@@ -545,6 +612,17 @@ NotificationData::~NotificationData() {
}
void NotificationData::show() {
if (_application && _notification) {
_application->send_notification(
std::to_string(_id.contextId.sessionId)
+ '-'
+ std::to_string(_id.contextId.peerId.value)
+ '-'
+ std::to_string(_id.msgId.bare),
_notification);
return;
}
// a hack for snap's activation restriction
const auto weak = base::make_weak(this);
StartServiceAsync(crl::guard(weak, [=] {
@@ -587,6 +665,17 @@ void NotificationData::show() {
}
void NotificationData::close() {
if (_application) {
_application->withdraw_notification(
std::to_string(_id.contextId.sessionId)
+ '-'
+ std::to_string(_id.contextId.peerId.value)
+ '-'
+ std::to_string(_id.msgId.bare));
_manager->clearNotification(_id);
return;
}
_dbusConnection->call(
std::string(kObjectPath),
std::string(kInterface),
@@ -602,7 +691,16 @@ void NotificationData::close() {
}
void NotificationData::setImage(const QString &imagePath) {
if (imagePath.isEmpty() || _imageKey.empty()) {
if (imagePath.isEmpty()) {
return;
}
if (_notification) {
_notification->set_icon(Gio::Icon::create(imagePath.toStdString()));
return;
}
if (_imageKey.empty()) {
return;
}
@@ -696,13 +794,13 @@ bool SkipFlashBounceForCustom() {
}
bool Supported() {
return ServiceRegistered;
return ServiceRegistered || Gio::Application::get_default();
}
bool Enforced() {
// Wayland doesn't support positioning
// and custom notifications don't work here
return IsWayland();
return IsWayland() || OptionGApplication.value();
}
bool ByDefault() {
@@ -728,25 +826,30 @@ bool ByDefault() {
}
void Create(Window::Notifications::System *system) {
static const auto ServiceWatcher = CreateServiceWatcher();
const auto managerSetter = [=] {
using ManagerType = Window::Notifications::ManagerType;
if ((Core::App().settings().nativeNotifications() || Enforced())
&& Supported()) {
if (system->managerType() != ManagerType::Native) {
if (system->manager().type() != ManagerType::Native) {
system->setManager(std::make_unique<Manager>(system));
}
} else if (Enforced()) {
if (system->managerType() != ManagerType::Dummy) {
if (system->manager().type() != ManagerType::Dummy) {
using DummyManager = Window::Notifications::DummyManager;
system->setManager(std::make_unique<DummyManager>(system));
}
} else if (system->managerType() != ManagerType::Default) {
} else if (system->manager().type() != ManagerType::Default) {
system->setManager(nullptr);
}
};
if (Gio::Application::get_default()) {
managerSetter();
return;
}
static const auto ServiceWatcher = CreateServiceWatcher();
const auto counter = std::make_shared<int>(2);
const auto oneReady = [=] {
if (!--*counter) {
@@ -1098,7 +1201,7 @@ void Manager::doClearFromSession(not_null<Main::Session*> session) {
}
bool Manager::doSkipAudio() const {
return _private->inhibited();
return _private->inhibited() || Gio::Application::get_default();
}
bool Manager::doSkipToast() const {
@@ -1106,7 +1209,7 @@ bool Manager::doSkipToast() const {
}
bool Manager::doSkipFlashBounce() const {
return _private->inhibited();
return _private->inhibited() || Gio::Application::get_default();
}
} // namespace Notifications