#include <algorithm>
#include <iostream>
#include <memory>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
#include <gio/gio.h>
#include "tuple_g_variant_helpers.hpp"
+constexpr static std::string_view bus_name = "org.tizen.sessiond";
+constexpr static std::string_view bus_object = "/org/tizen/sessiond";
+constexpr static std::string_view bus_iface = "org.tizen.sessiond.subsession.Manager";
+
+constexpr static int timeout = 10;
+
[[noreturn]] void g_error_throw(GError *err, std::string message) {
message += err->message;
g_error_free(err);
GMainLoop *inner;
};
+template<typename V>
+struct wait_manager {
+ wait_manager(int session_uid, GDBusConnection *connection, std::string_view completion_signal)
+ : session_uid(session_uid), connection(connection), completion_signal(completion_signal) {}
+ ~wait_manager()
+ {
+ for (auto ®istration : registered)
+ g_bus_unwatch_name(registration.second);
+
+ // We ought to swallow exceptions in the destructor.
+ // (std::uncaught_exception exists, but there's no need for it IMHO.)
+ try {
+ for (auto &waiting_for : waiting) {
+ waiting_for.second.clear();
+ finalize_if_empty(waiting_for.first);
+ }
+ } catch (const std::exception &ex) {
+ std::cerr << "Exception " << ex.what() << "\n" <<
+ "while finalizing the wait manager\n";
+ }
+
+ for (auto &timeout : timeouts)
+ g_source_remove(timeout->timeout_id);
+ }
+
+ // Because of the rule of three, we also have to delete these.
+ wait_manager(const wait_manager &) = delete;
+ wait_manager &operator=(const wait_manager &) = delete;
+
+ void on_client_register(const std::string &name)
+ {
+ if (registered.contains(name))
+ return; // TODO: any error here?
+ auto match = g_bus_watch_name(G_BUS_TYPE_SYSTEM, name.data(), G_BUS_NAME_WATCHER_FLAGS_NONE,
+ nullptr, glib_client_disappeared, this, nullptr);
+ registered.emplace(name, std::move(match));
+ }
+
+ void on_start(V v)
+ {
+ // See the comment in on_timeout.
+ timeouts.erase(std::remove_if(timeouts.begin(), timeouts.end(),
+ [](const auto &td) { return !td->active; }),
+ timeouts.end());
+
+ // See the comment in finalize.
+ std::erase_if(waiting, [](const auto &waits) { return waits.second.empty(); });
+
+ if (waiting.contains(v))
+ return; // TODO: any error here?
+
+ std::unordered_set<std::string> waits = {};
+ for (const auto &going_to_wait : registered)
+ waits.emplace(going_to_wait.first);
+ if (waits.empty())
+ signal(v);
+ else {
+ auto td = std::make_unique<timeout_data>();
+ td->self = this;
+ td->v = v;
+ td->active = true;
+ td->timeout_id = g_timeout_add_seconds(timeout, glib_timeout, td.get());
+
+ waiting.emplace(v, std::move(waits));
+ timeouts.emplace_back(std::move(td));
+ }
+ }
+
+ void on_client_done(V v, const std::string &name)
+ {
+ if (!waiting.contains(v))
+ return; // TODO: any error here?
+ drop_name(v, waiting[v], name);
+ }
+
+ void on_client_disappeared(const std::string &name)
+ {
+ registered.erase(name);
+ for (auto &waiting_for : waiting) {
+ drop_name(waiting_for.first, waiting_for.second, name);
+ }
+ }
+
+ void drop_name(V v, std::unordered_set<std::string> &waits, const std::string &name)
+ {
+ waits.erase(name);
+ finalize_if_empty(v);
+ }
+
+ void on_timeout(V v)
+ {
+ if (!waiting.contains(v))
+ return;
+ waiting[v].clear();
+
+ finalize_if_empty(v);
+
+ // One thing that we'd like to do here is removing the timeout from the array.
+ // However doing so is a bit sus, since glib_timeout still has pointer.
+ // What we do instead, is clearing the array up a bit when adding a new timeout in on_start.
+ }
+
+ void finalize_if_empty(V v)
+ {
+ // TODO: I don't like this, somehow.
+ // Refactor suggestions are more than welcome.
+ if (!waiting.contains(v) && !waiting[v].empty())
+ return;
+
+ signal(v);
+
+ // Again, we'd like to remove the now empty vector.
+ // However, doing so is again sus, since other functions (for example on_client_disappeared)
+ // might actually iterate on waiting, and this invalidates the vector.
+ // Again, let's clean up in on_start instead.
+ }
+
+ void signal(V v)
+ {
+ GError *err = nullptr;
+ if (!g_dbus_connection_emit_signal(connection, nullptr, bus_object.data(), bus_iface.data(), completion_signal.data(),
+ vals_to_g_variant(session_uid, v), &err))
+ g_error_throw(err, "Failed to emit a signal: ");
+ }
+
+ static void glib_client_disappeared(GDBusConnection *conn, const gchar *name, gpointer user_data)
+ // As this is called directly by GLib, we need to swallow exceptions here.
+ try {
+ auto self = static_cast<wait_manager *>(user_data);
+ self->on_client_disappeared(std::string(name));
+ } catch (const std::exception &ex) {
+ std::cerr << "Exception " << ex.what() << "\n" <<
+ "while handling " << name << " disappearing\n";
+ }
+
+ struct timeout_data {
+ guint timeout_id;
+ wait_manager *self;
+ V v;
+ bool active;
+ };
+ static gboolean glib_timeout(gpointer user_data)
+ // As this is called directly by GLib, we need to swallow exceptions here.
+ try {
+ auto data = static_cast<timeout_data *>(user_data);
+ data->self->on_timeout(data->v);
+ data->active = false;
+ return G_SOURCE_REMOVE;
+ } catch (const std::exception &ex) {
+ std::cerr << "Exception " << ex.what() << "\n" <<
+ "while timeout\n";
+ return G_SOURCE_REMOVE;
+ }
+
+ int session_uid;
+ GDBusConnection *connection;
+ std::string_view completion_signal;
+
+ std::unordered_map<std::string, guint> registered = {};
+ std::unordered_map<V, std::unordered_set<std::string>> waiting = {};
+
+ // We hold all the timeout_data, because I couldn't get memory management to work otherwise.
+ std::vector<std::unique_ptr<timeout_data>> timeouts = {};
+};
+
struct sessiond_context {
sessiond_context() : data(xml), id(bus_name, glib_bus_connected, glib_name_lost, this), loop()
{
vals_to_g_variant(session_uid, subsession_id), &err))
g_error_throw(err, "Failed to emit a signal: ");
+ wait_add.try_emplace(session_uid, session_uid, connection, "AddUserCompleted");
+ wait_add.at(session_uid).on_start(subsession_id);
+
g_dbus_method_invocation_return_value(invocation, nullptr);
}
vals_to_g_variant(session_uid, subsession_id), &err))
g_error_throw(err, "Failed to emit a signal: ");
+ wait_remove.try_emplace(session_uid, session_uid, connection, "RemoveUserCompleted");
+ wait_remove.at(session_uid).on_start(subsession_id);
+
g_dbus_method_invocation_return_value(invocation, nullptr);
}
vals_to_g_variant(session_uid, switch_id, prev_subsession_id, next_subsession_id), &err))
g_error_throw(err, "Failed to emit a signal: ");
+ wait_switch.try_emplace(session_uid, session_uid, connection, "SwitchUserCompleted");
+ wait_switch.at(session_uid).on_start(switch_id);
+
+ g_dbus_method_invocation_return_value(invocation, nullptr);
+ }
+
+ void on_add_user_wait(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+ {
+ auto [ session_uid ] = tuple_from_g_variant<int>(parameters);
+
+ if (session_uid <= 0) {
+ g_dbus_method_invocation_return_dbus_error(invocation, "org.freedesktop.DBus.Error.InvalidArgs", "Negative UID passed");
+ return;
+ }
+
+ wait_add.try_emplace(session_uid, session_uid, connection, "AddUserCompleted");
+ wait_add.at(session_uid).on_client_register(std::string(sender));
+
+ g_dbus_method_invocation_return_value(invocation, nullptr);
+ }
+
+ void on_remove_user_wait(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+ {
+ auto [ session_uid ] = tuple_from_g_variant<int>(parameters);
+
+ if (session_uid <= 0) {
+ g_dbus_method_invocation_return_dbus_error(invocation, "org.freedesktop.DBus.Error.InvalidArgs", "Negative UID passed");
+ return;
+ }
+
+ wait_remove.try_emplace(session_uid, session_uid, connection, "RemoveUserCompleted");
+ wait_remove.at(session_uid).on_client_register(std::string(sender));
+
+ g_dbus_method_invocation_return_value(invocation, nullptr);
+ }
+
+ void on_switch_user_wait(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+ {
+ auto [ session_uid ] = tuple_from_g_variant<int>(parameters);
+
+ if (session_uid <= 0) {
+ g_dbus_method_invocation_return_dbus_error(invocation, "org.freedesktop.DBus.Error.InvalidArgs", "Negative UID passed");
+ return;
+ }
+
+ wait_switch.try_emplace(session_uid, session_uid, connection, "SwitchUserCompleted");
+ wait_switch.at(session_uid).on_client_register(std::string(sender));
+
+ g_dbus_method_invocation_return_value(invocation, nullptr);
+ }
+
+ void on_add_user_done(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+ {
+ auto [ session_uid, subsession_id ] = tuple_from_g_variant<int, int>(parameters);
+
+ if (session_uid <= 0) {
+ g_dbus_method_invocation_return_dbus_error(invocation, "org.freedesktop.DBus.Error.InvalidArgs", "Negative UID passed");
+ return;
+ }
+
+ wait_add.try_emplace(session_uid, session_uid, connection, "AddUserCompleted");
+ wait_add.at(session_uid).on_client_done(subsession_id, std::string(sender));
+
+ g_dbus_method_invocation_return_value(invocation, nullptr);
+ }
+
+ void on_remove_user_done(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+ {
+ auto [ session_uid, subsession_id ] = tuple_from_g_variant<int, int>(parameters);
+
+ if (session_uid <= 0) {
+ g_dbus_method_invocation_return_dbus_error(invocation, "org.freedesktop.DBus.Error.InvalidArgs", "Negative UID passed");
+ return;
+ }
+
+ wait_remove.try_emplace(session_uid, session_uid, connection, "RemoveUserCompleted");
+ wait_remove.at(session_uid).on_client_done(subsession_id, std::string(sender));
+
+ g_dbus_method_invocation_return_value(invocation, nullptr);
+ }
+
+ void on_switch_user_done(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+ {
+ auto [ session_uid, switch_id ] = tuple_from_g_variant<int, uint64_t>(parameters);
+
+ if (session_uid <= 0) {
+ g_dbus_method_invocation_return_dbus_error(invocation, "org.freedesktop.DBus.Error.InvalidArgs", "Negative UID passed");
+ return;
+ }
+
+ wait_switch.try_emplace(session_uid, session_uid, connection, "SwitchUserCompleted");
+ wait_switch.at(session_uid).on_client_done(switch_id, std::string(sender));
+
g_dbus_method_invocation_return_value(invocation, nullptr);
}
"<arg name=\"prev_subsession_id\" type=\"i\" direction=\"in\"/>"
"<arg name=\"next_subsession_id\" type=\"i\" direction=\"in\"/>"
"</method>"
+ "<method name=\"AddUserWait\">"
+ "<arg name=\"session_uid\" type=\"i\" direction=\"in\"/>"
+ "</method>"
+ "<method name=\"RemoveUserWait\">"
+ "<arg name=\"session_uid\" type=\"i\" direction=\"in\"/>"
+ "</method>"
+ "<method name=\"SwitchUserWait\">"
+ "<arg name=\"session_uid\" type=\"i\" direction=\"in\"/>"
+ "</method>"
+ "<method name=\"AddUserDone\">"
+ "<arg name=\"session_uid\" type=\"i\" direction=\"in\"/>"
+ "<arg name=\"subsession_id\" type=\"i\" direction=\"in\"/>"
+ "</method>"
+ "<method name=\"RemoveUserDone\">"
+ "<arg name=\"session_uid\" type=\"i\" direction=\"in\"/>"
+ "<arg name=\"subsession_id\" type=\"i\" direction=\"in\"/>"
+ "</method>"
+ "<method name=\"SwitchUserDone\">"
+ "<arg name=\"session_uid\" type=\"i\" direction=\"in\"/>"
+ "<arg name=\"switch_id\" type=\"x\" direction=\"in\"/>"
+ "</method>"
"<signal name=\"AddUserStarted\">"
"<arg name=\"session_uid\" type=\"i\" direction=\"out\"/>"
"<arg name=\"subsession_id\" type=\"i\" direction=\"out\"/>"
"<arg name=\"prev_subsession_id\" type=\"i\" direction=\"out\"/>"
"<arg name=\"next_subsession_id\" type=\"i\" direction=\"out\"/>"
"</signal>"
+ "<signal name=\"AddUserCompleted\">"
+ "<arg name=\"session_uid\" type=\"i\" direction=\"out\"/>"
+ "<arg name=\"subsession_id\" type=\"i\" direction=\"out\"/>"
+ "</signal>"
+ "<signal name=\"RemoveUserCompleted\">"
+ "<arg name=\"session_uid\" type=\"i\" direction=\"out\"/>"
+ "<arg name=\"subsession_id\" type=\"i\" direction=\"out\"/>"
+ "</signal>"
+ "<signal name=\"SwitchUserCompleted\">"
+ "<arg name=\"session_uid\" type=\"i\" direction=\"out\"/>"
+ "<arg name=\"switch_id\" type=\"x\" direction=\"out\"/>"
+ "<arg name=\"prev_subsession_id\" type=\"i\" direction=\"out\"/>"
+ "<arg name=\"next_subsession_id\" type=\"i\" direction=\"out\"/>"
+ "</signal>"
"</interface>"
"</node>";
- constexpr static std::string_view bus_name = "org.tizen.sessiond";
- constexpr static std::string_view bus_object = "/org/tizen/sessiond";
- constexpr static std::string_view bus_iface = "org.tizen.sessiond.subsession.Manager";
constexpr static std::array<
std::pair<
std::string_view,
void (sessiond_context::*)(GDBusMethodInvocation *, std::string_view, GVariant *)
- >, 3> methods = {
- std::make_pair("AddUser", &sessiond_context::on_add_user),
- std::make_pair("RemoveUser", &sessiond_context::on_remove_user),
- std::make_pair("SwitchUser", &sessiond_context::on_switch_user),
+ >, 9> methods = {
+ std::make_pair("AddUser", &sessiond_context::on_add_user),
+ std::make_pair("RemoveUser", &sessiond_context::on_remove_user),
+ std::make_pair("SwitchUser", &sessiond_context::on_switch_user),
+ std::make_pair("AddUserWait", &sessiond_context::on_add_user_wait),
+ std::make_pair("RemoveUserWait", &sessiond_context::on_remove_user_wait),
+ std::make_pair("SwitchUserWait", &sessiond_context::on_switch_user_wait),
+ std::make_pair("AddUserDone", &sessiond_context::on_add_user_done),
+ std::make_pair("RemoveUserDone", &sessiond_context::on_remove_user_done),
+ std::make_pair("SwitchUserDone", &sessiond_context::on_switch_user_done),
// TODO: Add all the methods
};
+ std::unordered_map<int, wait_manager<int>> wait_add;
+ std::unordered_map<int, wait_manager<int>> wait_remove;
+ std::unordered_map<int, wait_manager<uint64_t>> wait_switch;
+
uint64_t switch_id = 0;
introspection_data data;