File: scoped_gsignal.h

package info (click to toggle)
chromium 138.0.7204.183-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,908 kB
  • sloc: cpp: 34,937,088; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (202 lines) | stat: -rw-r--r-- 7,160 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
// 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_