Commit 02fc962e authored by thomasanderson's avatar thomasanderson Committed by Commit bot

Linux native notifications: Support closing and updating notifications

BUG=676220
R=thestig@chromium.org,peter@chromium.org

Review-Url: https://codereview.chromium.org/2803873003
Cr-Commit-Position: refs/heads/master@{#462751}
parent 5987c4f6
...@@ -4,8 +4,12 @@ ...@@ -4,8 +4,12 @@
#include "chrome/browser/notifications/notification_platform_bridge_linux.h" #include "chrome/browser/notifications/notification_platform_bridge_linux.h"
#include <algorithm>
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/notifications/notification.h" #include "chrome/browser/notifications/notification.h"
namespace { namespace {
...@@ -13,6 +17,23 @@ namespace { ...@@ -13,6 +17,23 @@ namespace {
const char kFreedesktopNotificationsName[] = "org.freedesktop.Notifications"; const char kFreedesktopNotificationsName[] = "org.freedesktop.Notifications";
const char kFreedesktopNotificationsPath[] = "/org/freedesktop/Notifications"; const char kFreedesktopNotificationsPath[] = "/org/freedesktop/Notifications";
// Callback used by GLib when the "Notify" message completes for the
// first time.
void NotifyCompleteReceiver(GObject* source_object,
GAsyncResult* result,
gpointer user_data) {
GDBusProxy* proxy = G_DBUS_PROXY(source_object);
GVariant* value = g_dbus_proxy_call_finish(proxy, result, nullptr);
if (!value) {
// The message might have been cancelled, in which case
// |user_data| points to a destroyed NotificationData.
return;
}
auto* platform_bridge_linux = static_cast<NotificationPlatformBridgeLinux*>(
g_browser_process->notification_platform_bridge());
platform_bridge_linux->NotifyCompleteInternal(user_data, value);
}
} // namespace } // namespace
// static // static
...@@ -29,13 +50,48 @@ NotificationPlatformBridge* NotificationPlatformBridge::Create() { ...@@ -29,13 +50,48 @@ NotificationPlatformBridge* NotificationPlatformBridge::Create() {
return new NotificationPlatformBridgeLinux(notification_proxy); return new NotificationPlatformBridgeLinux(notification_proxy);
} }
struct NotificationPlatformBridgeLinux::NotificationData {
NotificationData(const std::string& notification_id,
const std::string& profile_id,
bool is_incognito)
: notification_id(notification_id),
profile_id(profile_id),
is_incognito(is_incognito) {}
~NotificationData() {
if (cancellable)
g_cancellable_cancel(cancellable);
}
// The ID used by the notification server. Will be 0 until the
// first "Notify" message completes.
uint32_t dbus_id = 0;
// Same parameters used by NotificationPlatformBridge::Display().
const std::string notification_id;
const std::string profile_id;
const bool is_incognito;
// Used to cancel the initial "Notify" message so we don't call
// NotificationPlatformBridgeLinux::NotifyCompleteInternal() with a
// destroyed Notification.
ScopedGObject<GCancellable> cancellable;
// If not null, the data to update the notification with once
// |dbus_id| becomes available.
std::unique_ptr<Notification> update_data;
NotificationCommon::Type update_type = NotificationCommon::TYPE_MAX;
// If true, indicates the notification should be closed once
// |dbus_id| becomes available.
bool should_close = false;
};
NotificationPlatformBridgeLinux::NotificationPlatformBridgeLinux( NotificationPlatformBridgeLinux::NotificationPlatformBridgeLinux(
GDBusProxy* notification_proxy) GDBusProxy* notification_proxy)
: notification_proxy_(notification_proxy) {} : notification_proxy_(notification_proxy) {}
NotificationPlatformBridgeLinux::~NotificationPlatformBridgeLinux() { NotificationPlatformBridgeLinux::~NotificationPlatformBridgeLinux() {}
g_object_unref(notification_proxy_);
}
void NotificationPlatformBridgeLinux::Display( void NotificationPlatformBridgeLinux::Display(
NotificationCommon::Type notification_type, NotificationCommon::Type notification_type,
...@@ -43,20 +99,38 @@ void NotificationPlatformBridgeLinux::Display( ...@@ -43,20 +99,38 @@ void NotificationPlatformBridgeLinux::Display(
const std::string& profile_id, const std::string& profile_id,
bool is_incognito, bool is_incognito,
const Notification& notification) { const Notification& notification) {
// TODO(thomasanderson): Add a complete implementation. NotificationData* data = FindNotificationData(notification_id, profile_id);
g_dbus_proxy_call( if (data) {
notification_proxy_, "Notify", // Update an existing notification.
g_variant_new("(susssasa{sv}i)", "", 0, "", if (data->dbus_id) {
base::UTF16ToUTF8(notification.title()).c_str(), NotifyNow(data->dbus_id, notification_type, notification, nullptr,
base::UTF16ToUTF8(notification.message()).c_str(), nullptr, nullptr, nullptr);
nullptr, -1), } else {
G_DBUS_CALL_FLAGS_NONE, -1, nullptr, nullptr, nullptr); data->update_type = notification_type;
data->update_data = base::MakeUnique<Notification>(notification);
}
} else {
// Send the notification for the first time.
data = new NotificationData(notification_id, profile_id, is_incognito);
data->cancellable.reset(g_cancellable_new());
notifications_.emplace(data, base::WrapUnique(data));
NotifyNow(0, notification_type, notification, data->cancellable,
NotifyCompleteReceiver, data);
}
} }
void NotificationPlatformBridgeLinux::Close( void NotificationPlatformBridgeLinux::Close(
const std::string& profile_id, const std::string& profile_id,
const std::string& notification_id) { const std::string& notification_id) {
NOTIMPLEMENTED(); NotificationData* data = FindNotificationData(notification_id, profile_id);
if (!data)
return;
if (data->dbus_id) {
CloseNow(data->dbus_id);
notifications_.erase(data);
} else {
data->should_close = true;
}
} }
void NotificationPlatformBridgeLinux::GetDisplayed( void NotificationPlatformBridgeLinux::GetDisplayed(
...@@ -66,3 +140,64 @@ void NotificationPlatformBridgeLinux::GetDisplayed( ...@@ -66,3 +140,64 @@ void NotificationPlatformBridgeLinux::GetDisplayed(
// TODO(thomasanderson): implement. // TODO(thomasanderson): implement.
callback.Run(base::MakeUnique<std::set<std::string>>(), false); callback.Run(base::MakeUnique<std::set<std::string>>(), false);
} }
void NotificationPlatformBridgeLinux::NotifyCompleteInternal(gpointer user_data,
GVariant* value) {
NotificationData* data = reinterpret_cast<NotificationData*>(user_data);
if (!base::ContainsKey(notifications_, data))
return;
data->cancellable.reset();
if (value && g_variant_is_of_type(value, G_VARIANT_TYPE("(u)")))
g_variant_get(value, "(u)", &data->dbus_id);
if (!data->dbus_id) {
// There was some sort of error with creating the notification.
notifications_.erase(data);
} else if (data->should_close) {
CloseNow(data->dbus_id);
notifications_.erase(data);
} else if (data->update_data) {
NotifyNow(data->dbus_id, data->update_type, *data->update_data, nullptr,
nullptr, nullptr);
data->update_data.reset();
}
}
void NotificationPlatformBridgeLinux::NotifyNow(
uint32_t dbus_id,
NotificationCommon::Type notification_type,
const Notification& notification,
GCancellable* cancellable,
GAsyncReadyCallback callback,
gpointer user_data) {
// TODO(thomasanderson): Add a complete implementation.
const std::string title = base::UTF16ToUTF8(notification.title());
const std::string message = base::UTF16ToUTF8(notification.message());
GVariant* parameters =
g_variant_new("(susssasa{sv}i)", "", dbus_id, "", title.c_str(),
message.c_str(), nullptr, nullptr, -1);
g_dbus_proxy_call(notification_proxy_, "Notify", parameters,
G_DBUS_CALL_FLAGS_NONE, -1, cancellable, callback,
user_data);
}
void NotificationPlatformBridgeLinux::CloseNow(uint32_t dbus_id) {
g_dbus_proxy_call(notification_proxy_, "CloseNotification",
g_variant_new("(u)", dbus_id), G_DBUS_CALL_FLAGS_NONE, -1,
nullptr, nullptr, nullptr);
}
NotificationPlatformBridgeLinux::NotificationData*
NotificationPlatformBridgeLinux::FindNotificationData(
const std::string& notification_id,
const std::string& profile_id) {
for (const auto& pair : notifications_) {
NotificationData* data = pair.first;
if (data->notification_id == notification_id &&
data->profile_id == profile_id) {
return data;
}
}
return nullptr;
}
...@@ -7,8 +7,11 @@ ...@@ -7,8 +7,11 @@
#include <gio/gio.h> #include <gio/gio.h>
#include <unordered_map>
#include "base/macros.h" #include "base/macros.h"
#include "chrome/browser/notifications/notification_platform_bridge.h" #include "chrome/browser/notifications/notification_platform_bridge.h"
#include "ui/base/glib/scoped_gobject.h"
class NotificationPlatformBridgeLinux : public NotificationPlatformBridge { class NotificationPlatformBridgeLinux : public NotificationPlatformBridge {
public: public:
...@@ -29,8 +32,35 @@ class NotificationPlatformBridgeLinux : public NotificationPlatformBridge { ...@@ -29,8 +32,35 @@ class NotificationPlatformBridgeLinux : public NotificationPlatformBridge {
bool incognito, bool incognito,
const GetDisplayedNotificationsCallback& callback) const override; const GetDisplayedNotificationsCallback& callback) const override;
// Called from NotifyCompleteReceiver().
void NotifyCompleteInternal(gpointer user_data, GVariant* value);
private: private:
GDBusProxy* const notification_proxy_; struct NotificationData;
ScopedGObject<GDBusProxy> notification_proxy_;
// A std::set<std::unique_ptr<T>> doesn't work well because
// eg. std::set::erase(T) would require a std::unique_ptr<T>
// argument, so the data would get double-destructed.
template <typename T>
using UnorderedUniqueSet = std::unordered_map<T*, std::unique_ptr<T>>;
UnorderedUniqueSet<NotificationData> notifications_;
// Makes the "Notify" call to D-Bus.
void NotifyNow(uint32_t dbus_id,
NotificationCommon::Type notification_type,
const Notification& notification,
GCancellable* cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
// Makes the "CloseNotification" call to D-Bus.
void CloseNow(uint32_t dbus_id);
NotificationData* FindNotificationData(const std::string& notification_id,
const std::string& profile_id);
DISALLOW_COPY_AND_ASSIGN(NotificationPlatformBridgeLinux); DISALLOW_COPY_AND_ASSIGN(NotificationPlatformBridgeLinux);
}; };
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include <string> #include <string>
#include "ui/base/glib/scoped_gobject.h"
#include "ui/native_theme/native_theme.h" #include "ui/native_theme/native_theme.h"
namespace aura { namespace aura {
...@@ -115,47 +116,14 @@ class CairoSurface { ...@@ -115,47 +116,14 @@ class CairoSurface {
// |major|.|minor|.|micro|. // |major|.|minor|.|micro|.
bool GtkVersionCheck(int major, int minor = 0, int micro = 0); bool GtkVersionCheck(int major, int minor = 0, int micro = 0);
// Similar in spirit to a std::unique_ptr. using ScopedStyleContext = ScopedGObject<GtkStyleContext>;
template <typename T> using ScopedCssProvider = ScopedGObject<GtkCssProvider>;
class ScopedGObject {
public:
explicit ScopedGObject(T* obj) : obj_(obj) {
// Remove the floating reference from |obj_| if it has one.
if (g_object_is_floating(obj_))
g_object_ref_sink(obj_);
DCHECK(G_OBJECT(obj_)->ref_count == 1);
}
ScopedGObject(const ScopedGObject<T>& other) = delete;
ScopedGObject(ScopedGObject<T>&& other) : obj_(other.obj_) {
other.obj_ = nullptr;
}
~ScopedGObject() {
if (obj_)
Unref();
}
ScopedGObject<T>& operator=(const ScopedGObject<T>& other) = delete;
ScopedGObject<T>& operator=(ScopedGObject<T>&& other) { } // namespace libgtkui
g_object_unref(obj_);
obj_ = other.obj_;
other.obj_ = nullptr;
return *this;
}
operator T*() { return obj_; }
private:
void Unref() { g_object_unref(obj_); }
T* obj_;
};
// Template override cannot be in the libgtkui namespace.
template <> template <>
inline void ScopedGObject<GtkStyleContext>::Unref() { inline void libgtkui::ScopedStyleContext::Unref() {
// Versions of GTK earlier than 3.15.4 had a bug where a g_assert // Versions of GTK earlier than 3.15.4 had a bug where a g_assert
// would be triggered when trying to free a GtkStyleContext that had // would be triggered when trying to free a GtkStyleContext that had
// a parent whose only reference was the child context in question. // a parent whose only reference was the child context in question.
...@@ -165,7 +133,7 @@ inline void ScopedGObject<GtkStyleContext>::Unref() { ...@@ -165,7 +133,7 @@ inline void ScopedGObject<GtkStyleContext>::Unref() {
while (context) { while (context) {
GtkStyleContext* parent = gtk_style_context_get_parent(context); GtkStyleContext* parent = gtk_style_context_get_parent(context);
if (parent && G_OBJECT(context)->ref_count == 1 && if (parent && G_OBJECT(context)->ref_count == 1 &&
!GtkVersionCheck(3, 15, 4)) { !libgtkui::GtkVersionCheck(3, 15, 4)) {
g_object_ref(parent); g_object_ref(parent);
gtk_style_context_set_parent(context, nullptr); gtk_style_context_set_parent(context, nullptr);
g_object_unref(context); g_object_unref(context);
...@@ -177,8 +145,7 @@ inline void ScopedGObject<GtkStyleContext>::Unref() { ...@@ -177,8 +145,7 @@ inline void ScopedGObject<GtkStyleContext>::Unref() {
} }
} }
typedef ScopedGObject<GtkStyleContext> ScopedStyleContext; namespace libgtkui {
typedef ScopedGObject<GtkCssProvider> ScopedCssProvider;
// Converts ui::NativeTheme::State to GtkStateFlags. // Converts ui::NativeTheme::State to GtkStateFlags.
GtkStateFlags StateToStateFlags(ui::NativeTheme::State state); GtkStateFlags StateToStateFlags(ui::NativeTheme::State state);
......
...@@ -481,6 +481,11 @@ component("base") { ...@@ -481,6 +481,11 @@ component("base") {
if (use_glib) { if (use_glib) {
configs += [ "//build/config/linux:glib" ] configs += [ "//build/config/linux:glib" ]
sources += [
"glib/glib_integers.h",
"glib/glib_signal.h",
"glib/scoped_gobject.h",
]
} }
if (is_linux) { if (is_linux) {
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_BASE_GLIB_SCOPED_GOBJECT_H_
#define UI_BASE_GLIB_SCOPED_GOBJECT_H_
// Similar in spirit to a std::unique_ptr.
template <typename T>
class ScopedGObject {
public:
ScopedGObject() : obj_(nullptr) {}
explicit ScopedGObject(T* obj) : obj_(obj) { Ref(); }
ScopedGObject(const ScopedGObject<T>& other) = delete;
ScopedGObject(ScopedGObject<T>&& other) : obj_(other.obj_) {
other.obj_ = nullptr;
}
~ScopedGObject() { reset(); }
ScopedGObject<T>& operator=(const ScopedGObject<T>& other) = delete;
ScopedGObject<T>& operator=(ScopedGObject<T>&& other) {
reset();
obj_ = other.obj_;
other.obj_ = nullptr;
return *this;
}
operator T*() { return obj_; }
void reset(T* obj = nullptr) {
Unref();
obj_ = obj;
Ref();
}
private:
void Ref() {
// Remove the floating reference from |obj_| if it has one.
if (obj_ && g_object_is_floating(obj_))
g_object_ref_sink(obj_);
}
// This function is necessary so that libgtkui can overload it in
// the case of T = GtkStyleContext.
void Unref() {
if (obj_)
g_object_unref(obj_);
}
T* obj_;
};
#endif // UI_BASE_GLIB_SCOPED_GOBJECT_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment