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