// gKeyMapper daemon - D-Bus service for the GUI (control plane only). #include "ipc_dbus.h" #include #include "ipc_ids.h" #include "mapping.h" namespace gkeymapper::daemon { namespace { // Index convention: 0 = G1 ... 8 = G9, the same as the internal mapping vector // and the KeyPressed signal, so GUI and CLI agree on one numbering. const char kIntrospectionXml[] = "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " ""; } // namespace DaemonService::~DaemonService() { if (owner_ != 0) { g_bus_unown_name(owner_); } if (registration_ != 0 && connection_ != nullptr) { g_dbus_connection_unregister_object(connection_, registration_); } if (connection_ != nullptr) { g_object_unref(connection_); } } StartResult DaemonService::start(std::string configPath) { configPath_ = std::move(configPath); GError* error = nullptr; connection_ = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error); if (connection_ == nullptr) { std::fprintf(stderr, "gkeymapper-daemon: no session bus (%s); running without " "the GUI channel\n", error != nullptr ? error->message : "unknown"); g_clear_error(&error); return StartResult::NoBus; } GDBusNodeInfo* info = g_dbus_node_info_new_for_xml(kIntrospectionXml, &error); if (info == nullptr) { std::fprintf(stderr, "gkeymapper-daemon: bad D-Bus introspection: %s\n", error->message); g_clear_error(&error); return StartResult::NoBus; } GDBusInterfaceVTable vtable{}; vtable.method_call = &DaemonService::handle_method_call; registration_ = g_dbus_connection_register_object( connection_, gkeymapper::ipc::kObjectPath, info->interfaces[0], &vtable, this, nullptr, &error); g_dbus_node_info_unref(info); if (registration_ == 0) { std::fprintf(stderr, "gkeymapper-daemon: cannot register object: %s\n", error->message); g_clear_error(&error); return StartResult::NoBus; } // DO_NOT_QUEUE: if the name is taken, fail instead of waiting. Two daemons // would fight over the keyboard and create two virtual keyboards. owner_ = g_bus_own_name_on_connection( connection_, gkeymapper::ipc::kBusName, G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE, [](GDBusConnection*, const gchar*, gpointer userData) { static_cast(userData)->name_owned_ = true; }, [](GDBusConnection*, const gchar* name, gpointer userData) { auto* self = static_cast(userData); self->name_lost_ = true; std::fprintf(stderr, "gkeymapper-daemon: bus name %s is taken\n", name); }, this, nullptr); // Let the name request complete before deciding. for (int i = 0; i < 100 && !name_owned_ && !name_lost_; ++i) { g_main_context_iteration(nullptr, FALSE); g_usleep(10 * 1000); } if (!name_owned_ && name_lost_) { return StartResult::AlreadyRunning; } std::fprintf(stderr, "gkeymapper-daemon: listening on %s (%s)\n", gkeymapper::ipc::kBusName, gkeymapper::ipc::kObjectPath); return StartResult::Ok; } void DaemonService::pump() { if (connection_ == nullptr) { return; } while (g_main_context_iteration(nullptr, FALSE)) { // Drain everything that is ready; nothing else uses this context. } } void DaemonService::handle_method_call(GDBusConnection* /*connection*/, const gchar* /*sender*/, const gchar* /*objectPath*/, const gchar* /*interfaceName*/, const gchar* methodName, GVariant* parameters, GDBusMethodInvocation* invocation, gpointer userData) { auto* self = static_cast(userData); if (g_strcmp0(methodName, "Reload") == 0) { if (self->reload_) { self->reload_(); } g_dbus_method_invocation_return_value(invocation, nullptr); return; } if (g_strcmp0(methodName, "GetStatus") == 0) { const std::string device = self->device_ ? self->device_() : std::string(); g_dbus_method_invocation_return_value( invocation, g_variant_new("(sbss)", gkeymapper::ipc::kVersion, TRUE, device.c_str(), self->configPath_.c_str())); return; } if (g_strcmp0(methodName, "TestMapping") == 0) { guint32 index = 0; g_variant_get(parameters, "(u)", &index); if (index >= static_cast(gkeymapper::kNumKeys)) { g_dbus_method_invocation_return_error( invocation, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "no G%u (index 0..%d)", index + 1, gkeymapper::kNumKeys - 1); return; } std::string action; if (!self->test_ || !self->test_(static_cast(index), action)) { g_dbus_method_invocation_return_error(invocation, G_IO_ERROR, G_IO_ERROR_FAILED, "G%u has no usable mapping", index + 1); return; } g_dbus_method_invocation_return_value(invocation, nullptr); self->notify_key_pressed(static_cast(index), action); return; } g_dbus_method_invocation_return_error(invocation, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "unknown method %s", methodName); } void DaemonService::notify_key_pressed(int index, const std::string& action) { if (connection_ == nullptr) { return; } GError* error = nullptr; g_dbus_connection_emit_signal( connection_, nullptr, gkeymapper::ipc::kObjectPath, gkeymapper::ipc::kInterface, "KeyPressed", g_variant_new("(us)", static_cast(index), action.c_str()), &error); if (error != nullptr) { g_clear_error(&error); } } void DaemonService::notify_config_reloaded(bool ok) { if (connection_ == nullptr) { return; } GError* error = nullptr; g_dbus_connection_emit_signal(connection_, nullptr, gkeymapper::ipc::kObjectPath, gkeymapper::ipc::kInterface, "ConfigReloaded", g_variant_new("(b)", ok ? TRUE : FALSE), &error); if (error != nullptr) { g_clear_error(&error); } } void DaemonService::notify_device_changed(bool present, const std::string& device) { if (connection_ == nullptr) { return; } GError* error = nullptr; g_dbus_connection_emit_signal( connection_, nullptr, gkeymapper::ipc::kObjectPath, gkeymapper::ipc::kInterface, "DeviceChanged", g_variant_new("(bs)", present ? TRUE : FALSE, device.c_str()), &error); if (error != nullptr) { g_clear_error(&error); } } } // namespace gkeymapper::daemon