1 /*
2  * SPDX-FileCopyrightText: 2016-2016 CSSlayer <wengxt@gmail.com>
3  *
4  * SPDX-License-Identifier: LGPL-2.1-or-later
5  *
6  */
7 #ifndef _FCITX_UTILS_DBUS_OBJECTVTABLE_H_
8 #define _FCITX_UTILS_DBUS_OBJECTVTABLE_H_
9 
10 #include <functional>
11 #include <memory>
12 #include <mutex>
13 #include <stdexcept>
14 #include <string>
15 #include <type_traits>
16 #include <fcitx-utils/flags.h>
17 #include <fcitx-utils/macros.h>
18 #include <fcitx-utils/trackableobject.h>
19 
20 /// \addtogroup FcitxUtils
21 /// \{
22 /// \file
23 /// \brief High level API for dbus objects.
24 
25 namespace fcitx {
26 namespace dbus {
27 class Message;
28 class ObjectVTableBase;
29 class Slot;
30 class Bus;
31 class ObjectVTablePrivate;
32 
33 typedef std::function<bool(Message)> ObjectMethod;
34 typedef std::function<bool(Message, const ObjectMethod &)> ObjectMethodClosure;
35 typedef std::function<void(Message &)> PropertyGetMethod;
36 typedef std::function<bool(Message &)> PropertySetMethod;
37 
38 /**
39  * An exception if you want message to return a DBus error.
40  *
41  * In the registered property or method, you may throw this exception if a DBus
42  * error happens.
43  *
44  * E.g.
45  * @code
46  * throw dbus::MethodCallError("org.freedesktop.DBus.Error.InvalidArgs", ...);
47  * @endcode
48  */
49 class FCITXUTILS_EXPORT MethodCallError : public std::exception {
50 public:
MethodCallError(const char * name,const char * error)51     MethodCallError(const char *name, const char *error)
52         : name_(name), error_(error) {}
53 
what()54     const char *what() const noexcept override { return error_.c_str(); }
55 
name()56     const char *name() const { return name_.c_str(); }
57 
58 private:
59     std::string name_;
60     std::string error_;
61 };
62 
63 class ObjectVTableMethodPrivate;
64 
65 /**
66  * Register a DBus method to current DBus VTable.
67  *
68  * Usually this class should not be used directory in the code.
69  *
70  * @see FCITX_OBJECT_VTABLE_METHOD
71  */
72 class FCITXUTILS_EXPORT ObjectVTableMethod {
73 public:
74     ObjectVTableMethod(ObjectVTableBase *vtable, const std::string &name,
75                        const std::string &signature, const std::string &ret,
76                        ObjectMethod handler);
77 
78     virtual ~ObjectVTableMethod();
79 
80     const std::string &name() const;
81     const std::string &signature() const;
82     const std::string &ret() const;
83     const ObjectMethod &handler() const;
84     ObjectVTableBase *vtable() const;
85 
86     /**
87      * Set a closure function to call the handler with in it.
88      *
89      * This is useful when you want to do something before and after the dbus
90      * message delivery.
91      *
92      * @param wrapper wrapper function.
93      */
94     void setClosureFunction(ObjectMethodClosure closure);
95 
96 private:
97     std::unique_ptr<ObjectVTableMethodPrivate> d_ptr;
98     FCITX_DECLARE_PRIVATE(ObjectVTableMethod);
99 };
100 
101 template <typename T>
102 struct ReturnValueHelper {
103     typedef T type;
104     type ret;
105 
106     template <typename U>
callReturnValueHelper107     void call(U u) {
108         ret = u();
109     }
110 };
111 
112 template <>
113 struct ReturnValueHelper<void> {
114     typedef std::tuple<> type;
115     type ret;
116     template <typename U>
117     void call(U u) {
118         u();
119     }
120 };
121 
122 /**
123  * Register a class member function as a DBus method.
124  *
125  * It will also check if the dbus signature matches the function type.
126  *
127  * @param FUNCTION a member function of the class
128  * @param FUNCTION_NAME a string of DBus method name
129  * @param SIGNATURE The dbus signature of arguments.
130  * @param RET The dbus signature of the return value.
131  *
132  * @see https://dbus.freedesktop.org/doc/dbus-specification.html#type-system
133  */
134 #define FCITX_OBJECT_VTABLE_METHOD(FUNCTION, FUNCTION_NAME, SIGNATURE, RET)    \
135     ::fcitx::dbus::ObjectVTableMethod FUNCTION##Method {                       \
136         this, FUNCTION_NAME, SIGNATURE, RET,                                   \
137             [this](::fcitx::dbus::Message msg) {                               \
138                 this->setCurrentMessage(&msg);                                 \
139                 auto watcher = static_cast<ObjectVTableBase *>(this)->watch(); \
140                 FCITX_STRING_TO_DBUS_TUPLE(SIGNATURE) args;                    \
141                 msg >> args;                                                   \
142                 auto func = [](auto that, auto &&...args) {                    \
143                     return that->FUNCTION(                                     \
144                         std::forward<decltype(args)>(args)...);                \
145                 };                                                             \
146                 auto argsWithThis =                                            \
147                     std::tuple_cat(std::make_tuple(this), std::move(args));    \
148                 typedef decltype(callWithTuple(func,                           \
149                                                argsWithThis)) ReturnType;      \
150                 ::fcitx::dbus::ReturnValueHelper<ReturnType> helper;           \
151                 auto functor = [&argsWithThis, func]() {                       \
152                     return callWithTuple(func, argsWithThis);                  \
153                 };                                                             \
154                 try {                                                          \
155                     helper.call(functor);                                      \
156                     auto reply = msg.createReply();                            \
157                     static_assert(std::is_same<FCITX_STRING_TO_DBUS_TYPE(RET), \
158                                                ReturnType>::value,             \
159                                   "Return type does not match: " RET);         \
160                     reply << helper.ret;                                       \
161                     reply.send();                                              \
162                 } catch (const ::fcitx::dbus::MethodCallError &error) {        \
163                     auto reply = msg.createError(error.name(), error.what());  \
164                     reply.send();                                              \
165                 }                                                              \
166                 if (watcher.isValid()) {                                       \
167                     watcher.get()->setCurrentMessage(nullptr);                 \
168                 }                                                              \
169                 return true;                                                   \
170             }                                                                  \
171     }
172 
173 /**
174  * Register a new DBus signal.
175  *
176  * This macro will define two new function, SIGNAL and SIGNALTo.
177  *
178  * The latter one will only be send to one DBus destination.
179  *
180  * @param SIGNAL will be used to define two member functions.
181  * @param SIGNAL_NAME a string of DBus signal name
182  * @param SIGNATURE The dbus signature of the signal.
183  *
184  * @see https://dbus.freedesktop.org/doc/dbus-specification.html#type-system
185  */
186 #define FCITX_OBJECT_VTABLE_SIGNAL(SIGNAL, SIGNAL_NAME, SIGNATURE)             \
187     ::fcitx::dbus::ObjectVTableSignal SIGNAL##Signal{this, SIGNAL_NAME,        \
188                                                      SIGNATURE};               \
189     typedef FCITX_STRING_TO_DBUS_TUPLE(SIGNATURE) SIGNAL##ArgType;             \
190     template <typename... Args>                                                \
191     void SIGNAL(Args &&...args) {                                              \
192         auto msg = SIGNAL##Signal.createSignal();                              \
193         SIGNAL##ArgType tupleArg{std::forward<Args>(args)...};                 \
194         msg << tupleArg;                                                       \
195         msg.send();                                                            \
196     }                                                                          \
197     template <typename... Args>                                                \
198     void SIGNAL##To(const std::string &dest, Args &&...args) {                 \
199         auto msg = SIGNAL##Signal.createSignal();                              \
200         msg.setDestination(dest);                                              \
201         SIGNAL##ArgType tupleArg{std::forward<Args>(args)...};                 \
202         msg << tupleArg;                                                       \
203         msg.send();                                                            \
204     }
205 
206 /**
207  * Register a new DBus read-only property.
208  *
209  * @param PROPERTY will be used to define class member.
210  * @param NAME a string of DBus property name
211  * @param SIGNATURE The dbus signature of the property.
212  * @param GETMETHOD The method used to return the value of the property
213  *
214  * @see https://dbus.freedesktop.org/doc/dbus-specification.html#type-system
215  */
216 #define FCITX_OBJECT_VTABLE_PROPERTY(PROPERTY, NAME, SIGNATURE, GETMETHOD,     \
217                                      ...)                                      \
218     ::fcitx::dbus::ObjectVTableProperty PROPERTY##Property{                    \
219         this, NAME, SIGNATURE,                                                 \
220         [method = GETMETHOD](::fcitx::dbus::Message &msg) {                    \
221             typedef FCITX_STRING_TO_DBUS_TUPLE(SIGNATURE) property_type;       \
222             property_type property = method();                                 \
223             msg << property;                                                   \
224         },                                                                     \
225         ::fcitx::dbus::PropertyOptions{__VA_ARGS__}};
226 
227 /**
228  * Register a new DBus read-only property.
229  *
230  * @param PROPERTY will be used to define class member.
231  * @param NAME a string of DBus property name
232  * @param SIGNATURE The dbus signature of the property.
233  * @param GETMETHOD The method used to return the value of the property
234  * @param SETMETHOD The method used to update the value of the property
235  *
236  * @see https://dbus.freedesktop.org/doc/dbus-specification.html#type-system
237  */
238 #define FCITX_OBJECT_VTABLE_WRITABLE_PROPERTY(PROPERTY, NAME, SIGNATURE,       \
239                                               GETMETHOD, SETMETHOD, ...)       \
240     ::fcitx::dbus::ObjectVTableWritableProperty PROPERTY##Property{            \
241         this,                                                                  \
242         NAME,                                                                  \
243         SIGNATURE,                                                             \
244         [method = GETMETHOD](::fcitx::dbus::Message &msg) {                    \
245             typedef FCITX_STRING_TO_DBUS_TUPLE(SIGNATURE) property_type;       \
246             property_type property = method();                                 \
247             msg << property;                                                   \
248         },                                                                     \
249         [this, method = SETMETHOD](::fcitx::dbus::Message &msg) {              \
250             this->setCurrentMessage(&msg);                                     \
251             auto watcher = static_cast<ObjectVTableBase *>(this)->watch();     \
252             FCITX_STRING_TO_DBUS_TUPLE(SIGNATURE) args;                        \
253             msg >> args;                                                       \
254             callWithTuple(method, args);                                       \
255             auto reply = msg.createReply();                                    \
256             reply.send();                                                      \
257             if (watcher.isValid()) {                                           \
258                 watcher.get()->setCurrentMessage(nullptr);                     \
259             }                                                                  \
260             return true;                                                       \
261         },                                                                     \
262         ::fcitx::dbus::PropertyOptions{__VA_ARGS__}};
263 
264 class ObjectVTableSignalPrivate;
265 
266 /**
267  * Register a DBus signal to current DBus VTable.
268  *
269  * Usually this class should not be used directory in the code.
270  *
271  * @see FCITX_OBJECT_VTABLE_SIGNAL
272  */
273 class FCITXUTILS_EXPORT ObjectVTableSignal {
274 public:
275     ObjectVTableSignal(ObjectVTableBase *vtable, std::string name,
276                        std::string signature);
277     virtual ~ObjectVTableSignal();
278 
279     Message createSignal();
280     const std::string &name() const;
281     const std::string &signature() const;
282 
283 private:
284     std::unique_ptr<ObjectVTableSignalPrivate> d_ptr;
285     FCITX_DECLARE_PRIVATE(ObjectVTableSignal);
286 };
287 
288 enum class PropertyOption : uint32_t { Hidden = (1 << 0) };
289 
290 using PropertyOptions = Flags<PropertyOption>;
291 
292 class ObjectVTablePropertyPrivate;
293 
294 /**
295  * Register a DBus read-only property to current DBus VTable.
296  *
297  * Usually this class should not be used directory in the code.
298  *
299  * @see FCITX_OBJECT_VTABLE_PROPERTY
300  */
301 class FCITXUTILS_EXPORT ObjectVTableProperty {
302 public:
303     ObjectVTableProperty(ObjectVTableBase *vtable, std::string name,
304                          std::string signature, PropertyGetMethod getMethod,
305                          PropertyOptions options);
306     virtual ~ObjectVTableProperty();
307 
308     const std::string &name() const;
309     const std::string &signature() const;
310     bool writable() const;
311     const PropertyGetMethod &getMethod() const;
312     const PropertyOptions &options() const;
313 
314 protected:
315     ObjectVTableProperty(std::unique_ptr<ObjectVTablePropertyPrivate> d);
316 
317     std::unique_ptr<ObjectVTablePropertyPrivate> d_ptr;
318     FCITX_DECLARE_PRIVATE(ObjectVTableProperty);
319 };
320 
321 /**
322  * Register a DBus property to current DBus VTable.
323  *
324  * Usually this class should not be used directory in the code.
325  *
326  * @see FCITX_OBJECT_VTABLE_WRITABLE_PROPERTY
327  */
328 class FCITXUTILS_EXPORT ObjectVTableWritableProperty
329     : public ObjectVTableProperty {
330 public:
331     ObjectVTableWritableProperty(ObjectVTableBase *vtable, std::string name,
332                                  std::string signature,
333                                  PropertyGetMethod getMethod,
334                                  PropertySetMethod setMethod,
335                                  PropertyOptions options);
336 
337     const PropertySetMethod &setMethod() const;
338 };
339 
340 class ObjectVTableBasePrivate;
341 class MessageSetter;
342 
343 class FCITXUTILS_EXPORT ObjectVTableBase
344     : public TrackableObject<ObjectVTableBase> {
345     friend class Bus;
346     friend class MessageSetter;
347 
348 public:
349     ObjectVTableBase();
350     virtual ~ObjectVTableBase();
351 
352     void addMethod(ObjectVTableMethod *method);
353     void addSignal(ObjectVTableSignal *sig);
354     void addProperty(ObjectVTableProperty *property);
355 
356     /**
357      * Unregister the dbus object from the bus.
358      *
359      * The object will automatically unregister itself upon destruction. So this
360      * method should only be used if you want to temporarily remove a object
361      * from dbus.
362      */
363     void releaseSlot();
364 
365     /// Return the bus that the object is registered to.
366     Bus *bus();
367     /// Return whether this object is registered to a bus.
368     bool isRegistered() const;
369     /// Return the registered dbus object path of the object.
370     const std::string &path() const;
371     /// Return the registered dbus interface of the object.
372     const std::string &interface() const;
373 
374     /**
375      * Return the current dbus message for current method.
376      *
377      * This should only be used with in a registered callback.
378      *
379      * @return DBus message
380      */
381     Message *currentMessage() const;
382 
383     /**
384      * Set the current dbus message.
385      *
386      * This is only used by internal dbus class and not supposed to be used
387      * anywhere else.
388      *
389      * @param message current message.
390      */
391     void setCurrentMessage(Message *message);
392 
393     ObjectVTableMethod *findMethod(const std::string &name);
394     ObjectVTableProperty *findProperty(const std::string &name);
395 
396 protected:
397     virtual std::mutex &privateDataMutexForType() = 0;
398     virtual ObjectVTablePrivate *privateDataForType() = 0;
399     static std::shared_ptr<ObjectVTablePrivate> newSharedPrivateData();
400 
401 private:
402     void setSlot(Slot *slot);
403 
404     std::unique_ptr<ObjectVTableBasePrivate> d_ptr;
405     FCITX_DECLARE_PRIVATE(ObjectVTableBase);
406 };
407 
408 /**
409  * Base class of any DBus object.
410  *
411  * This should be used with curiously recurring template pattern. Like:
412  *
413  * @code
414  * class Object : public ObjectVTable<OBject> {};
415  * @endcode
416  *
417  * It will instantiate the related shared data for this type.
418  *
419  */
420 template <typename T>
421 class ObjectVTable : public ObjectVTableBase {
422 public:
423     std::mutex &privateDataMutexForType() override {
424         return privateDataMutex();
425     }
426     ObjectVTablePrivate *privateDataForType() override { return privateData(); }
427     static std::mutex &privateDataMutex() {
428         static std::mutex mutex;
429         return mutex;
430     }
431     static ObjectVTablePrivate *privateData() {
432         static std::shared_ptr<ObjectVTablePrivate> d(newSharedPrivateData());
433         return d.get();
434     }
435 };
436 } // namespace dbus
437 } // namespace fcitx
438 
439 #endif // _FCITX_UTILS_DBUS_OBJECTVTABLE_H_
440