blob: a9704152caf6397781666654d289acbf02428bb2 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// 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_GSIGNAL_H_
#define UI_BASE_GLIB_SCOPED_GSIGNAL_H_
#include <glib-object.h>
#include <glib.h>
#include <memory>
#include <utility>
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/sequence_checker.h"
#include "ui/base/glib/scoped_gobject.h"
// ScopedGSignal manages the lifecycle of a GLib signal connection.
// It disconnects the signal when this object is destroyed or goes out of scope.
// This class should be used on a single sequence.
class ScopedGSignal {
public:
// Constructs and connects a GLib signal with specified attributes.
// Parameters:
// - instance: The GLib object instance emitting the signal.
// - detailed_signal: Signal name to connect.
// - handler: Callback function to invoke when the signal is emitted.
// - connect_flags: Optional flags to influence signal connection behavior.
// If signal connection fails, this object will be left unconnected.
// `Connected()` can be used to check for signal connection success.
template <typename Sender, typename Ret, typename... Args>
ScopedGSignal(Sender* instance,
const gchar* detailed_signal,
base::RepeatingCallback<Ret(Sender*, Args...)> handler,
GConnectFlags connect_flags = static_cast<GConnectFlags>(0))
: impl_(std::make_unique<SignalImpl<Sender, Ret, Args...>>(
instance,
detailed_signal,
std::move(handler),
connect_flags)) {
if (!Connected()) {
// No need to keep `impl_` around.
impl_.reset();
}
}
// Overload accepting a ScopedGObject.
template <typename Sender, typename Ret, typename... Args>
ScopedGSignal(ScopedGObject<Sender> instance,
const gchar* detailed_signal,
base::RepeatingCallback<Ret(Sender*, Args...)> handler,
GConnectFlags connect_flags = static_cast<GConnectFlags>(0))
: ScopedGSignal(instance.get(),
detailed_signal,
std::move(handler),
connect_flags) {}
// Overload accepting a raw_ptr.
template <typename Sender, typename Ret, typename... Args>
ScopedGSignal(raw_ptr<Sender> instance,
const gchar* detailed_signal,
base::RepeatingCallback<Ret(Sender*, Args...)> handler,
GConnectFlags connect_flags = static_cast<GConnectFlags>(0))
: ScopedGSignal(instance.get(),
detailed_signal,
std::move(handler),
connect_flags) {}
// Constructs an unconnected ScopedGSignal.
ScopedGSignal();
ScopedGSignal(ScopedGSignal&&) noexcept;
ScopedGSignal& operator=(ScopedGSignal&&) noexcept;
~ScopedGSignal();
[[nodiscard]] bool Connected() const;
void Reset();
private:
// The implementation uses the PIMPL idiom for the following reasons:
// 1. GLib binds a user data pointer that gets passed to the callback.
// This means the implementation class can't be movable. To support
// moves, keep a pointer to the implementation.
// 2. Type erasure: the derived class depends on the callback type and
// sender type, so a virtual destructor is required.
class SignalBase {
public:
SignalBase(SignalBase&&) = delete;
SignalBase& operator=(SignalBase&&) = delete;
virtual ~SignalBase();
[[nodiscard]] bool Connected() const { return signal_id_; }
protected:
SignalBase();
[[nodiscard]] gulong signal_id() const { return signal_id_; }
void set_signal_id(gulong signal_id) { signal_id_ = signal_id; }
private:
gulong signal_id_ = 0;
};
template <typename Sender, typename Ret, typename... Args>
class SignalImpl final : public SignalBase {
public:
using Handler = base::RepeatingCallback<Ret(Sender*, Args...)>;
SignalImpl(Sender* instance,
const gchar* detailed_signal,
Handler handler,
GConnectFlags connect_flags) {
CHECK(instance);
CHECK(detailed_signal);
CHECK(handler);
const bool swapped = connect_flags & G_CONNECT_SWAPPED;
const bool after = connect_flags & G_CONNECT_AFTER;
auto* new_closure = swapped ? g_cclosure_new_swap : g_cclosure_new;
if (!(gclosure_ = new_closure(G_CALLBACK(OnSignalEmittedThunk), this,
OnDisconnectedThunk))) {
LOG(ERROR) << "Failed to create GClosure";
return;
}
set_signal_id(g_signal_connect_closure(instance, detailed_signal,
gclosure_, after));
if (!signal_id()) {
LOG(ERROR) << "Failed to connect to " << detailed_signal;
// Prevent OnDisconnectedThunk from running.
g_closure_remove_finalize_notifier(gclosure_, this,
OnDisconnectedThunk);
// Remove the floating reference to free `gclosure_`. Note that this
// should not be called if the signal connected since it will take
// ownership of `gclosure_`.
g_closure_unref(gclosure_.ExtractAsDangling());
return;
}
sender_ = instance;
handler_ = std::move(handler);
}
~SignalImpl() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!Connected()) {
return;
}
// If the finalize notifier is not removed, it will get called some time
// after this object has been destroyed. Remove the finalize notifier to
// prevent this.
g_closure_remove_finalize_notifier(gclosure_.ExtractAsDangling(), this,
OnDisconnectedThunk);
g_signal_handler_disconnect(sender_, signal_id());
// `OnDisconnected()` must be explicitly called since the finalize
// notifier was removed.
OnDisconnected();
}
private:
static Ret OnSignalEmittedThunk(Sender* sender,
Args... args,
gpointer self) {
return reinterpret_cast<SignalImpl*>(self)->OnSignalEmitted(sender,
args...);
}
static void OnDisconnectedThunk(gpointer self, GClosure* closure) {
reinterpret_cast<SignalImpl*>(self)->OnDisconnected();
}
Ret OnSignalEmitted(Sender* sender, Args... args) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return handler_.Run(sender, args...);
}
void OnDisconnected() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(Connected());
set_signal_id(0);
sender_ = nullptr;
gclosure_ = nullptr;
handler_.Reset();
}
raw_ptr<Sender> sender_ = nullptr;
raw_ptr<GClosure> gclosure_ = nullptr;
Handler handler_;
SEQUENCE_CHECKER(sequence_checker_);
};
std::unique_ptr<SignalBase> impl_;
};
#endif // UI_BASE_GLIB_SCOPED_GSIGNAL_H_