Embedd assets icons
This commit is contained in:
191
src/daemonclient.cpp
Normal file
191
src/daemonclient.cpp
Normal file
@@ -0,0 +1,191 @@
|
||||
// gKeyMapper - talk to gkeymapper-daemon over the session bus.
|
||||
|
||||
#include "daemonclient.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "ipc_ids.h"
|
||||
|
||||
namespace gkeymapper {
|
||||
|
||||
namespace {
|
||||
|
||||
// The daemon answers within one loop iteration, but it may have to be started
|
||||
// first (D-Bus activation), so allow for a slow first reply.
|
||||
constexpr int kCallTimeoutMs = 5000;
|
||||
|
||||
constexpr const char* kDBusName = "org.freedesktop.DBus";
|
||||
constexpr const char* kDBusPath = "/org/freedesktop/DBus";
|
||||
|
||||
} // namespace
|
||||
|
||||
DaemonClient::~DaemonClient() {
|
||||
if (connection_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (signal_subscription_ != 0) {
|
||||
g_dbus_connection_signal_unsubscribe(connection_, signal_subscription_);
|
||||
}
|
||||
if (name_subscription_ != 0) {
|
||||
g_dbus_connection_signal_unsubscribe(connection_, name_subscription_);
|
||||
}
|
||||
g_object_unref(connection_);
|
||||
}
|
||||
|
||||
void DaemonClient::init(StatusFn onStatus, KeyFn onKey) {
|
||||
onStatus_ = std::move(onStatus);
|
||||
onKey_ = std::move(onKey);
|
||||
if (started_) {
|
||||
return; // a second activation reuses the existing connection
|
||||
}
|
||||
started_ = true;
|
||||
g_bus_get(G_BUS_TYPE_SESSION, nullptr, &DaemonClient::on_bus_ready, this);
|
||||
}
|
||||
|
||||
void DaemonClient::on_bus_ready(GObject* /*source*/, GAsyncResult* result, gpointer userData) {
|
||||
auto* self = static_cast<DaemonClient*>(userData);
|
||||
|
||||
GError* error = nullptr;
|
||||
GDBusConnection* connection = g_bus_get_finish(result, &error);
|
||||
if (connection == nullptr) {
|
||||
// No session bus: the GUI still edits and saves the file, it just
|
||||
// cannot show the daemon state.
|
||||
std::fprintf(stderr, "gkeymapper: no session bus: %s\n",
|
||||
error != nullptr ? error->message : "unknown error");
|
||||
g_clear_error(&error);
|
||||
self->report_unreachable();
|
||||
return;
|
||||
}
|
||||
|
||||
self->connection_ = connection;
|
||||
self->watch_signals();
|
||||
self->fetch_status();
|
||||
}
|
||||
|
||||
void DaemonClient::watch_signals() {
|
||||
signal_subscription_ = g_dbus_connection_signal_subscribe(
|
||||
connection_, ipc::kBusName, ipc::kInterface, nullptr, ipc::kObjectPath, nullptr,
|
||||
G_DBUS_SIGNAL_FLAGS_NONE, &DaemonClient::on_daemon_signal, this, nullptr);
|
||||
|
||||
// Lets the footer follow the daemon: it flips to "not reachable" when it
|
||||
// stops and back when it (or D-Bus activation) starts it again.
|
||||
name_subscription_ = g_dbus_connection_signal_subscribe(
|
||||
connection_, kDBusName, kDBusName, "NameOwnerChanged", kDBusPath, ipc::kBusName,
|
||||
G_DBUS_SIGNAL_FLAGS_NONE, &DaemonClient::on_name_owner_changed, this, nullptr);
|
||||
}
|
||||
|
||||
void DaemonClient::on_daemon_signal(GDBusConnection* /*connection*/, const gchar* /*sender*/,
|
||||
const gchar* /*objectPath*/, const gchar* /*interfaceName*/,
|
||||
const gchar* signalName, GVariant* parameters,
|
||||
gpointer userData) {
|
||||
auto* self = static_cast<DaemonClient*>(userData);
|
||||
|
||||
if (g_strcmp0(signalName, "KeyPressed") == 0) {
|
||||
guint32 index = 0;
|
||||
const gchar* action = nullptr;
|
||||
g_variant_get(parameters, "(u&s)", &index, &action);
|
||||
if (self->onKey_) {
|
||||
self->onKey_(static_cast<int>(index), action != nullptr ? action : "");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ConfigReloaded and DeviceChanged carry no state the footer does not
|
||||
// already get from GetStatus, so just refresh it.
|
||||
self->fetch_status();
|
||||
}
|
||||
|
||||
void DaemonClient::on_name_owner_changed(GDBusConnection* /*connection*/, const gchar* /*sender*/,
|
||||
const gchar* /*objectPath*/,
|
||||
const gchar* /*interfaceName*/,
|
||||
const gchar* /*signalName*/, GVariant* parameters,
|
||||
gpointer userData) {
|
||||
auto* self = static_cast<DaemonClient*>(userData);
|
||||
|
||||
const gchar* name = nullptr;
|
||||
const gchar* oldOwner = nullptr;
|
||||
const gchar* newOwner = nullptr;
|
||||
g_variant_get(parameters, "(&s&s&s)", &name, &oldOwner, &newOwner);
|
||||
|
||||
if (newOwner == nullptr || *newOwner == '\0') {
|
||||
self->report_unreachable();
|
||||
} else {
|
||||
self->fetch_status();
|
||||
}
|
||||
}
|
||||
|
||||
void DaemonClient::fetch_status() {
|
||||
if (connection_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
// A call to a name without an owner makes the bus activate the service, so
|
||||
// this also brings the daemon up on demand.
|
||||
g_dbus_connection_call(connection_, ipc::kBusName, ipc::kObjectPath, ipc::kInterface,
|
||||
"GetStatus", nullptr, G_VARIANT_TYPE("(sbss)"),
|
||||
G_DBUS_CALL_FLAGS_NONE, kCallTimeoutMs, nullptr,
|
||||
&DaemonClient::on_status_reply, this);
|
||||
}
|
||||
|
||||
void DaemonClient::on_status_reply(GObject* /*source*/, GAsyncResult* result, gpointer userData) {
|
||||
auto* self = static_cast<DaemonClient*>(userData);
|
||||
|
||||
GError* error = nullptr;
|
||||
GVariant* reply = g_dbus_connection_call_finish(self->connection_, result, &error);
|
||||
if (reply == nullptr) {
|
||||
// Normal when the daemon is not installed or not running.
|
||||
g_clear_error(&error);
|
||||
self->report_unreachable();
|
||||
return;
|
||||
}
|
||||
|
||||
const gchar* version = nullptr;
|
||||
const gchar* device = nullptr;
|
||||
const gchar* configPath = nullptr;
|
||||
gboolean running = FALSE;
|
||||
g_variant_get(reply, "(&sb&s&s)", &version, &running, &device, &configPath);
|
||||
g_variant_unref(reply);
|
||||
|
||||
DaemonStatus status;
|
||||
status.reachable = true;
|
||||
status.version = version != nullptr ? version : "";
|
||||
status.device = device != nullptr ? device : "";
|
||||
status.configPath = configPath != nullptr ? configPath : "";
|
||||
self->publish(status);
|
||||
}
|
||||
|
||||
void DaemonClient::reload() {
|
||||
if (connection_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
g_dbus_connection_call(connection_, ipc::kBusName, ipc::kObjectPath, ipc::kInterface,
|
||||
"Reload", nullptr, nullptr, G_DBUS_CALL_FLAGS_NONE, kCallTimeoutMs,
|
||||
nullptr, &DaemonClient::on_reload_reply, this);
|
||||
}
|
||||
|
||||
void DaemonClient::on_reload_reply(GObject* /*source*/, GAsyncResult* result, gpointer userData) {
|
||||
auto* self = static_cast<DaemonClient*>(userData);
|
||||
|
||||
GError* error = nullptr;
|
||||
GVariant* reply = g_dbus_connection_call_finish(self->connection_, result, &error);
|
||||
if (reply != nullptr) {
|
||||
g_variant_unref(reply);
|
||||
}
|
||||
if (error != nullptr) {
|
||||
// The file watch applies the save anyway, so a missing daemon is fine.
|
||||
g_clear_error(&error);
|
||||
}
|
||||
}
|
||||
|
||||
void DaemonClient::report_unreachable() {
|
||||
DaemonStatus status;
|
||||
status.reachable = false;
|
||||
publish(status);
|
||||
}
|
||||
|
||||
void DaemonClient::publish(const DaemonStatus& status) {
|
||||
if (onStatus_) {
|
||||
onStatus_(status);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gkeymapper
|
||||
Reference in New Issue
Block a user