7 #include "../../dbus/bus.h" 17 #include <unordered_map> 20 #include <dbus/dbus-protocol.h> 21 #include <dbus/dbus-shared.h> 22 #include <dbus/dbus.h> 23 #include "fcitx-utils/environ.h" 24 #include "../../charutils.h" 25 #include "../../dbus/matchrule.h" 26 #include "../../dbus/message.h" 27 #include "../../dbus/objectvtable.h" 28 #include "../../event.h" 29 #include "../../eventloopinterface.h" 30 #include "../../flags.h" 31 #include "../../log.h" 32 #include "../../macros.h" 33 #include "../../misc_p.h" 34 #include "../../stringutils.h" 35 #include "../../trackableobject.h" 38 #include "message_p.h" 39 #include "objectvtable_p_libdbus.h" 43 FCITX_DEFINE_LOG_CATEGORY(libdbus_logcategory,
"libdbus");
45 class BusWatches :
public std::enable_shared_from_this<BusWatches> {
51 void addWatch(DBusWatch *watch) {
52 watches_[watch] = std::make_shared<DBusWatch *>(watch);
55 bool removeWatch(DBusWatch *watch) {
56 watches_.erase(watch);
58 return watches_.empty();
61 if (watches_.empty() || !bus_.isValid()) {
66 int fd = dbus_watch_get_unix_fd(watches_.begin()->first);
68 for (
const auto &[watch, _] : watches_) {
69 if (!dbus_watch_get_enabled(watch)) {
72 int dflags = dbus_watch_get_flags(watch);
73 if (dflags & DBUS_WATCH_READABLE) {
74 flags |= IOEventFlag::In;
76 if (dflags & DBUS_WATCH_WRITABLE) {
77 flags |= IOEventFlag::Out;
82 <<
"IOWatch for dbus fd: " << fd <<
" flags: " << flags;
89 ioEvent_ = bus_.get()->loop_->addIOEvent(
94 auto lock = shared_from_this();
97 std::vector<std::weak_ptr<DBusWatch *>> watchesView;
98 watchesView.reserve(watches_.size());
99 for (
const auto &[_, watchRef] : watches_) {
100 watchesView.push_back(watchRef);
103 for (
const auto &watchRef : watchesView) {
104 auto watchStrongRef = watchRef.lock();
105 if (!watchStrongRef) {
108 auto *watch = *watchStrongRef;
109 if (!dbus_watch_get_enabled(watch)) {
115 if ((dbus_watch_get_flags(watch) &
116 DBUS_WATCH_READABLE) &&
117 (flags & IOEventFlag::In)) {
118 dflags |= DBUS_WATCH_READABLE;
120 if ((dbus_watch_get_flags(watch) &
121 DBUS_WATCH_WRITABLE) &&
122 (flags & IOEventFlag::Out)) {
123 dflags |= DBUS_WATCH_WRITABLE;
125 if (flags & IOEventFlag::Err) {
126 dflags |= DBUS_WATCH_ERROR;
128 if (flags & IOEventFlag::Hup) {
129 dflags |= DBUS_WATCH_HANGUP;
134 dbus_watch_handle(watch, dflags);
135 if (
auto *bus = bus_.get()) {
142 ioEvent_->setEvents(flags);
146 static std::shared_ptr<BusWatches> create(
BusPrivate &bus) {
147 return std::make_shared<BusWatches>(bus, Private());
154 std::unordered_map<DBusWatch *, std::shared_ptr<DBusWatch *>> watches_;
155 std::unique_ptr<EventSourceIO> ioEvent_;
158 DBusHandlerResult DBusMessageCallback(DBusConnection * ,
159 DBusMessage *message,
void *userdata) {
160 auto *bus =
static_cast<BusPrivate *
>(userdata);
162 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
165 auto ref = bus->watch();
166 auto msg = MessagePrivate::fromDBusMessage(ref, message,
false,
true);
167 for (
const auto &filter : bus->filterHandlers_.view()) {
168 if (filter && filter(msg)) {
169 return DBUS_HANDLER_RESULT_HANDLED;
174 if (msg.type() == MessageType::Signal) {
175 if (
auto *bus = ref.get()) {
176 for (
auto &pair : bus->matchHandlers_.view()) {
177 auto *bus = ref.get();
178 std::string alterName;
179 if (bus && bus->nameCache_ &&
180 !pair.first.service().empty()) {
182 bus->nameCache_->owner(pair.first.service());
184 if (pair.first.check(msg, alterName)) {
185 if (pair.second && pair.second(msg)) {
186 return DBUS_HANDLER_RESULT_HANDLED;
193 }
catch (
const std::exception &e) {
195 FCITX_ERROR() << e.what();
198 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
203 constexpr
const char xmlHeader[] =
204 "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection " 206 "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">" 208 "<interface name=\"" DBUS_INTERFACE_INTROSPECTABLE
"\">" 209 "<method name=\"Introspect\">" 210 "<arg name=\"data\" direction=\"out\" type=\"s\"/>" 213 constexpr
const char xmlProperties[] =
214 "<interface name=\"" DBUS_INTERFACE_PROPERTIES
"\">" 215 "<method name=\"Get\">" 216 "<arg name=\"interface_name\" direction=\"in\" type=\"s\"/>" 217 "<arg name=\"property_name\" direction=\"in\" type=\"s\"/>" 218 "<arg name=\"value\" direction=\"out\" type=\"v\"/>" 220 "<method name=\"Set\">" 221 "<arg name=\"interface_name\" direction=\"in\" type=\"s\"/>" 222 "<arg name=\"property_name\" direction=\"in\" type=\"s\"/>" 223 "<arg name=\"value\" direction=\"in\" type=\"v\"/>" 225 "<method name=\"GetAll\">" 226 "<arg name=\"interface_name\" direction=\"in\" type=\"s\"/>" 227 "<arg name=\"values\" direction=\"out\" type=\"a{sv}\"/>" 229 "<signal name=\"PropertiesChanged\">" 230 "<arg name=\"interface_name\" type=\"s\"/>" 231 "<arg name=\"changed_properties\" type=\"a{sv}\"/>" 232 "<arg name=\"invalidated_properties\" type=\"as\"/>" 236 constexpr
const char xmlInterfaceFooter[] =
"</interface>";
238 constexpr
const char xmlFooter[] =
"</node>";
240 std::string DBusObjectVTableSlot::getXml()
const {
242 xml += stringutils::concat(
"<interface name=\"", interface_,
"\">");
243 xml += objPriv_->getXml(obj_);
244 xml += xmlInterfaceFooter;
248 BusPrivate::BusPrivate(
Bus *bus)
255 ScopedDBusError error;
256 if (needWatchService(rule)) {
257 nameCache()->addWatch(rule.service());
259 FCITX_LIBDBUS_DEBUG() <<
"Add dbus match: " << rule.rule();
260 dbus_bus_add_match(conn_.get(), rule.rule().c_str(),
262 bool isError = dbus_error_is_set(&error.error());
269 if (needWatchService(rule)) {
270 nameCache()->removeWatch(rule.service());
272 FCITX_LIBDBUS_DEBUG() <<
"Remove dbus match: " << rule.rule();
273 dbus_bus_remove_match(conn_.get(), rule.rule().c_str(),
nullptr);
276 [
this](
const std::string &path) {
280 DBusObjectPathVTable vtable;
281 memset(&vtable, 0,
sizeof(vtable));
283 vtable.message_function = DBusObjectPathVTableMessageCallback;
284 return dbus_connection_register_object_path(
285 conn_.get(), path.c_str(), &vtable,
this) != 0;
287 [
this](
const std::string &path) {
292 dbus_connection_unregister_object_path(conn_.get(), path.c_str());
295 BusPrivate::~BusPrivate() {
297 dbus_connection_flush(conn_.get());
301 DBusObjectVTableSlot *BusPrivate::findSlot(
const std::string &path,
302 const std::string &interface) {
304 for (
auto &item : objectRegistration_.view(path)) {
305 if (
auto *slot = item.get()) {
306 if (slot->interface_ == interface) {
314 bool BusPrivate::objectVTableCallback(
Message &message) {
315 if (!objectRegistration_.hasKey(message.
path())) {
318 if (message.
interface() ==
"org.freedesktop.DBus.Introspectable") {
319 if (message.
member() !=
"Introspect" || !message.
signature().empty()) {
322 std::string xml = xmlHeader;
323 bool hasProperties =
false;
324 for (
auto &item : objectRegistration_.view(message.
path())) {
325 if (
auto *slot = item.get()) {
327 hasProperties || !slot->objPriv_->properties_.empty();
332 xml += xmlProperties;
340 if (message.
interface() ==
"org.freedesktop.DBus.Properties") {
342 std::string interfaceName;
343 std::string propertyName;
344 message >> interfaceName >> propertyName;
345 if (
auto *slot = findSlot(message.
path(), interfaceName)) {
346 auto *
property = slot->obj_->findProperty(propertyName);
349 reply <<
Container(Container::Type::Variant,
350 property->signature());
351 property->getMethod()(reply);
356 DBUS_ERROR_UNKNOWN_PROPERTY,
"No such property");
361 }
else if (message.
member() ==
"Set" && message.
signature() ==
"ssv") {
362 std::string interfaceName;
363 std::string propertyName;
364 message >> interfaceName >> propertyName;
365 if (
auto *slot = findSlot(message.
path(), interfaceName)) {
366 auto *
property = slot->obj_->findProperty(propertyName);
368 if (property->writable()) {
369 message >>
Container(Container::Type::Variant,
370 property->signature());
375 ->setMethod()(message);
382 "Read-only property");
387 DBUS_ERROR_UNKNOWN_PROPERTY,
"No such property");
392 }
else if (message.
member() ==
"GetAll" && message.
signature() ==
"s") {
393 std::string interfaceName;
394 message >> interfaceName;
395 if (
auto *slot = findSlot(message.
path(), interfaceName)) {
398 for (
auto &pair : slot->objPriv_->properties_) {
399 if (pair.second->options().test(PropertyOption::Hidden)) {
402 reply << Container(Container::Type::DictEntry,
405 auto *
property = pair.second;
406 reply << Container(Container::Type::Variant,
407 property->signature());
408 property->getMethod()(reply);
410 reply << ContainerEnd();
417 }
else if (
auto *slot = findSlot(message.
path(), message.
interface())) {
418 if (
auto *method = slot->obj_->findMethod(message.
member())) {
419 if (method->signature() != message.
signature()) {
422 return method->handler()(std::move(message));
429 std::string escapePath(
const std::string &path) {
431 newPath.reserve(path.size() * 3);
432 for (
auto c : path) {
433 if (charutils::islower(c) || charutils::isupper(c) ||
434 charutils::isdigit(c) || c ==
'_' || c ==
'-' || c ==
'/' ||
436 newPath.push_back(c);
438 newPath.push_back(
'%');
439 newPath.push_back(charutils::toHex(c >> 4));
440 newPath.push_back(charutils::toHex(c & 0xf));
447 std::string sessionBusAddress() {
448 auto e = getEnvironment(
"DBUS_SESSION_BUS_ADDRESS");
452 auto xdg = getEnvironment(
"XDG_RUNTIME_DIR");
456 auto escapedXdg = escapePath(*xdg);
457 return stringutils::concat(
"unix:path=", escapedXdg,
"/bus");
460 std::string addressByType(BusType type) {
462 case BusType::Session:
463 return sessionBusAddress();
464 case BusType::System:
465 if (
auto env = getEnvironment(
"DBUS_SYSTEM_BUS_ADDRESS")) {
468 return DBUS_SYSTEM_BUS_DEFAULT_ADDRESS;
470 case BusType::Default:
471 if (
auto starter = getEnvironment(
"DBUS_STARTER_BUS_TYPE")) {
473 return addressByType(BusType::System);
477 return addressByType(BusType::Session);
480 if (
auto address = getEnvironment(
"DBUS_STARTER_ADDRESS")) {
485 uid_t uid = getuid();
486 uid_t euid = geteuid();
487 if (uid != euid || euid != 0) {
488 return addressByType(BusType::Session);
490 return addressByType(BusType::System);
501 if (address.empty()) {
505 d->conn_.reset(dbus_connection_open_private(address.c_str(),
nullptr));
510 dbus_connection_set_exit_on_disconnect(d->conn_.get(),
false);
512 if (!dbus_bus_register(d->conn_.get(),
nullptr)) {
515 if (!dbus_connection_add_filter(d->conn_.get(), DBusMessageCallback, d,
522 throw std::runtime_error(
"Failed to create dbus connection");
532 Bus::Bus(
Bus &&other) noexcept : d_ptr(std::move(other.d_ptr)) {}
536 return d->conn_ && dbus_connection_get_is_connected(d->conn_.get());
540 const char *interface,
const char *member) {
543 dbus_message_new_method_call(destination, path, interface, member);
547 return MessagePrivate::fromDBusMessage(d->watch(), dmsg,
true,
false);
551 const char *member) {
553 auto *dmsg = dbus_message_new_signal(path, interface, member);
557 return MessagePrivate::fromDBusMessage(d->watch(), dmsg,
true,
false);
560 void DBusToggleWatch(DBusWatch *watch,
void *data) {
563 findValue(bus->ioWatchers_, dbus_watch_get_unix_fd(watch))) {
564 watchers->get()->refreshWatch();
568 dbus_bool_t DBusAddWatch(DBusWatch *watch,
void *data) {
570 int fd = dbus_watch_get_unix_fd(watch);
571 FCITX_LIBDBUS_DEBUG() <<
"DBusAddWatch fd: " << fd
572 <<
" flags: " << dbus_watch_get_flags(watch);
573 auto &watchers = bus->ioWatchers_[fd];
575 watchers = BusWatches::create(*bus);
577 watchers->addWatch(watch);
581 void DBusRemoveWatch(DBusWatch *watch,
void *data) {
582 FCITX_LIBDBUS_DEBUG() <<
"DBusRemoveWatch fd: " 583 << dbus_watch_get_unix_fd(watch);
585 auto iter = bus->ioWatchers_.find(dbus_watch_get_unix_fd(watch));
586 if (iter == bus->ioWatchers_.end()) {
590 if (iter->second->removeWatch(watch)) {
591 bus->ioWatchers_.erase(iter);
595 dbus_bool_t DBusAddTimeout(DBusTimeout *timeout,
void *data) {
597 if (!dbus_timeout_get_enabled(timeout)) {
600 int interval = dbus_timeout_get_interval(timeout);
601 FCITX_LIBDBUS_DEBUG() <<
"DBusAddTimeout: " << interval;
602 auto ref = bus->watch();
604 bus->timeWatchers_.emplace(
606 bus->loop_->addTimeEvent(
607 CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + interval * 1000ULL, 0,
611 const auto refPivot = ref;
613 if (dbus_timeout_get_enabled(timeout)) {
614 event->setNextInterval(
615 dbus_timeout_get_interval(timeout) * 1000ULL);
618 dbus_timeout_handle(timeout);
620 if (
auto *bus = refPivot.get()) {
630 void DBusRemoveTimeout(DBusTimeout *timeout,
void *data) {
632 bus->timeWatchers_.erase(timeout);
635 void DBusToggleTimeout(DBusTimeout *timeout,
void *data) {
636 DBusRemoveTimeout(timeout, data);
637 DBusAddTimeout(timeout, data);
640 void DBusDispatchStatusCallback(DBusConnection * ,
641 DBusDispatchStatus status,
void *userdata) {
642 auto *bus =
static_cast<BusPrivate *
>(userdata);
643 if (status == DBUS_DISPATCH_DATA_REMAINS) {
644 bus->deferEvent_->setOneShot();
655 if (!dbus_connection_set_watch_functions(d->conn_.get(), DBusAddWatch,
657 DBusToggleWatch, d,
nullptr)) {
660 if (!dbus_connection_set_timeout_functions(
661 d->conn_.get(), DBusAddTimeout, DBusRemoveTimeout,
662 DBusToggleTimeout, d,
nullptr)) {
665 if (!d->deferEvent_) {
666 d->deferEvent_ = d->loop_->addDeferEvent([d](
EventSource *) {
670 d->deferEvent_->setOneShot();
672 dbus_connection_set_dispatch_status_function(
673 d->conn_.get(), DBusDispatchStatusCallback, d,
nullptr);
683 dbus_connection_set_watch_functions(d->conn_.get(),
nullptr,
nullptr,
684 nullptr,
nullptr,
nullptr);
685 dbus_connection_set_timeout_functions(d->conn_.get(),
nullptr,
nullptr,
686 nullptr,
nullptr,
nullptr);
687 dbus_connection_set_dispatch_status_function(d->conn_.get(),
nullptr,
689 d->deferEvent_.reset();
698 std::unique_ptr<Slot> Bus::addMatch(
const MatchRule &rule,
699 MessageCallback callback) {
701 auto slot = std::make_unique<DBusMatchSlot>();
703 FCITX_LIBDBUS_DEBUG() <<
"Add match for rule " << rule.rule()
704 <<
" in rule set " << d->matchRuleSet_.hasKey(rule);
706 slot->ruleRef_ = d->matchRuleSet_.add(rule, 1);
708 if (!slot->ruleRef_) {
711 slot->handler_ = d->matchHandlers_.add(rule, std::move(callback));
716 std::unique_ptr<Slot> Bus::addFilter(MessageCallback callback) {
719 auto slot = std::make_unique<DBusFilterSlot>();
720 slot->handler_ = d->filterHandlers_.add(std::move(callback));
725 DBusHandlerResult DBusObjectPathMessageCallback(DBusConnection * ,
726 DBusMessage *message,
728 auto *slot =
static_cast<DBusObjectSlot *
>(userdata);
730 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
733 MessagePrivate::fromDBusMessage(slot->bus_, message,
false,
true);
734 if (slot->callback_(msg)) {
735 return DBUS_HANDLER_RESULT_HANDLED;
737 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
740 std::unique_ptr<Slot> Bus::addObject(
const std::string &path,
741 MessageCallback callback) {
743 auto slot = std::make_unique<DBusObjectSlot>(path, std::move(callback));
744 DBusObjectPathVTable vtable;
745 memset(&vtable, 0,
sizeof(vtable));
746 vtable.message_function = DBusObjectPathMessageCallback;
747 if (dbus_connection_register_object_path(d->conn_.get(), path.c_str(),
748 &vtable, slot.get())) {
752 slot->bus_ = d->watch();
757 DBusObjectPathVTableMessageCallback(DBusConnection * ,
758 DBusMessage *message,
void *userdata) {
759 auto *bus =
static_cast<BusPrivate *
>(userdata);
761 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
764 MessagePrivate::fromDBusMessage(bus->watch(), message,
false,
true);
765 if (bus->objectVTableCallback(msg)) {
766 return DBUS_HANDLER_RESULT_HANDLED;
768 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
775 for (
auto &item : d->objectRegistration_.view(path)) {
776 if (
auto *slot = item.get()) {
777 if (slot->interface_ == interface) {
783 auto slot = std::make_unique<DBusObjectVTableSlot>(path, interface, &vtable,
786 auto handler = d->objectRegistration_.add(path, slot->watch());
791 slot->handler_ = std::move(handler);
792 slot->bus_ = d->watch();
794 vtable.setSlot(slot.release());
802 return d->conn_.get();
808 ((flags & RequestNameFlag::ReplaceExisting)
809 ? DBUS_NAME_FLAG_REPLACE_EXISTING
811 ((flags & RequestNameFlag::AllowReplacement)
812 ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT
814 ((flags & RequestNameFlag::Queue) ? 0 : DBUS_NAME_FLAG_DO_NOT_QUEUE);
816 dbus_bus_request_name(d->conn_.get(), name.c_str(), d_flags,
nullptr);
817 return ret == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER ||
818 ret == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER ||
819 ((ret == DBUS_REQUEST_NAME_REPLY_IN_QUEUE ||
820 ret == DBUS_REQUEST_NAME_REPLY_EXISTS) &&
821 (flags & RequestNameFlag::Queue));
826 return dbus_bus_release_name(d->conn_.get(), name.c_str(),
nullptr) >= 0;
830 auto msg =
createMethodCall(
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
831 "org.freedesktop.DBus",
"GetNameOwner");
833 auto reply = msg.call(usec);
835 if (reply.type() == dbus::MessageType::Reply) {
836 std::string ownerName;
843 std::unique_ptr<Slot> Bus::serviceOwnerAsync(
const std::string &name,
845 MessageCallback callback) {
846 auto msg =
createMethodCall(
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
847 "org.freedesktop.DBus",
"GetNameOwner");
849 return msg.callAsync(usec, std::move(callback));
854 const char *name = dbus_bus_get_unique_name(d->conn_.get());
868 dbus_connection_flush(d->conn_.get());
bool send()
Send this message.
Helper type for serialization, should not be used directly.
bool addObjectVTable(const std::string &path, const std::string &interface, ObjectVTableBase &vtable)
Register a new object on the dbus.
Utility class provides a weak reference to the object.
Basic DBus type of a DBus message.
FCITX_NODISCARD EventLoop * eventLoop() const
Return the attached event loop.
bool requestName(const std::string &name, Flags< RequestNameFlag > flags)
Request the dbus name on the bus.
std::string path() const
Return the path of the message.
bool releaseName(const std::string &name)
Release the dbus name.
Bus(const std::string &address)
Connect to given address.
String like type object signature 'g'.
A dbus matching rule to be used with add match.
Message createError(const char *name, const char *message) const
Create a error reply to this message.
std::string address()
Return the dbus address being connected to.
A class that represents a connection to the Bus.
Message createMethodCall(const char *destination, const char *path, const char *interface, const char *member)
Create a new method message.
std::string uniqueName()
Return the unique name of current connection.
Message createReply() const
Create a reply to this message.
Helper type for serialization, should not be used directly.
std::string serviceOwner(const std::string &name, uint64_t usec)
Helper function to query the service owner.
Register a DBus property to current DBus VTable.
FCITX_NODISCARD void * nativeHandle() const
Return the internal pointer of the implemenation.
void flush()
Flush the bus immediately.
std::string member() const
Return the member of the message.
std::string interface() const
Return the interface of the message.
void attachEventLoop(EventLoop *loop)
Attach this bus to an event loop.
Message createSignal(const char *path, const char *interface, const char *member)
Create a new signal message.
bool startsWith(std::string_view str, std::string_view prefix)
Check if a string starts with a prefix.
void detachEventLoop()
Remove this bus from an event loop.
static const char * impl()
Return the name of the compiled implentation of fcitx dbus.
FCITX_NODISCARD bool isOpen() const
Check if the connection is open.
std::string signature() const
Return the signature of the message.