diff --git a/daemon/gamemode-context.c b/daemon/gamemode-context.c index 07c2eb8..1e840ce 100644 --- a/daemon/gamemode-context.c +++ b/daemon/gamemode-context.c @@ -486,6 +486,7 @@ int game_mode_context_register(GameModeContext *self, pid_t client, pid_t reques /* Unlock now we're done applying optimisations */ pthread_rwlock_unlock(&self->rwlock); + game_mode_client_registered(client); game_mode_client_count_changed(); return 0; @@ -586,6 +587,7 @@ int game_mode_context_unregister(GameModeContext *self, pid_t client, pid_t requ pthread_rwlock_unlock(&self->rwlock); game_mode_client_count_changed(); + game_mode_client_unregistered(client); return 0; } diff --git a/daemon/gamemode-dbus.c b/daemon/gamemode-dbus.c index 847ad5d..8c5fb69 100644 --- a/daemon/gamemode-dbus.c +++ b/daemon/gamemode-dbus.c @@ -37,6 +37,16 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#include +#include +#include +#include + +#define GAME_PATH_PREFIX "/com/feralinteractive/GameMode/Games/" +/* maximum length of a valid game object path string: + * The path prefix including \0 (sizeof) and 10 digits for uint32_t ('%u')*/ +#define GAME_PATH_MAX (sizeof(GAME_PATH_PREFIX) + 10) + /* systemd dbus components */ static sd_bus *bus = NULL; static sd_bus_slot *slot = NULL; @@ -213,6 +223,90 @@ static int method_refresh_config(sd_bus_message *m, void *userdata, return sd_bus_reply_method_return(m, "i", status); } +static inline void game_object_bus_path(pid_t pid, char path[static GAME_PATH_MAX]) +{ + snprintf(path, GAME_PATH_MAX, GAME_PATH_PREFIX "%u", (uint32_t)pid); +} + +/** + * Handles the List Games + */ +static int method_list_games(sd_bus_message *m, void *userdata, + __attribute__((unused)) sd_bus_error *ret_error) +{ + GameModeContext *context = userdata; + sd_bus_message *reply = NULL; + unsigned int count; + pid_t *clients; + int r; + + r = sd_bus_message_new_method_return(m, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(io)"); + if (r < 0) + return r; + + clients = game_mode_context_list_clients(context, &count); + + for (unsigned int i = 0; i < count; i++) { + char path[GAME_PATH_MAX] = { + 0, + }; + pid_t pid = clients[i]; + + game_object_bus_path(pid, path); + r = sd_bus_message_append(reply, "(io)", (int32_t)pid, path); + + if (r < 0) + break; + } + + free(clients); + + if (r < 0) + return 0; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +/* Signal emission helper */ +static void game_mode_client_send_game_signal(pid_t pid, bool new_game) +{ + char path[GAME_PATH_MAX] = { + 0, + }; + int ret; + + game_object_bus_path(pid, path); + ret = sd_bus_emit_signal(bus, + "/com/feralinteractive/GameMode", + "com.feralinteractive.GameMode", + new_game ? "GameRegistered" : "GameUnregistered", + "io", + (int32_t)pid, + path); + if (ret < 0) + fprintf(stderr, "failed to emit signal: %s", strerror(-ret)); +} + +/* Emit GameRegistered signal */ +void game_mode_client_registered(pid_t pid) +{ + game_mode_client_send_game_signal(pid, true); +} + +/* Emit GameUnregistered signal */ +void game_mode_client_unregistered(pid_t pid) +{ + game_mode_client_send_game_signal(pid, false); +} + /** * D-BUS vtable to dispatch virtual methods */ @@ -232,6 +326,153 @@ static const sd_bus_vtable gamemode_vtable[] = { SD_BUS_METHOD("QueryStatusByPID", "ii", "i", method_query_status_by_pid, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("RefreshConfig", "", "i", method_refresh_config, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListGames", "", "a(io)", method_list_games, SD_BUS_VTABLE_UNPRIVILEGED), + + SD_BUS_SIGNAL("GameRegistered", "io", 0), + SD_BUS_SIGNAL("GameUnregistered", "io", 0), + + SD_BUS_VTABLE_END +}; + +/** + * Game Objects + */ + +static inline void pid_to_pointer(pid_t pid, void **pointer) +{ + _Static_assert(sizeof (void *) >= sizeof (pid_t), + "pointer type not large enough to store pid_t"); + + *pointer = (void *) (intptr_t) pid; +} + +static inline pid_t pid_from_pointer(const void *pointer) +{ + return (pid_t) (intptr_t) pointer; +} + +static int game_object_find(sd_bus *local_bus, const char *path, const char *interface, + void *userdata, void **found, sd_bus_error *ret_error) +{ + static const char prefix[] = "/com/feralinteractive/GameMode/Games/"; + const char *start; + unsigned long int n; + char *end; + + if (strncmp(path, prefix, strlen(prefix)) != 0) + return 0; + + start = path + strlen(prefix); + + errno = 0; + n = strtoul(start, &end, 10); + + if (start == end || errno != 0) + return 0; + + pid_to_pointer((pid_t) n, found); + + return 1; +} + +static int game_node_enumerator(sd_bus *local_bus, const char *path, void *userdata, + char ***nodes, + __attribute__((unused)) sd_bus_error *ret_error) +{ + GameModeContext *context = userdata; + unsigned int count; + pid_t *clients; + char **strv = NULL; + + clients = game_mode_context_list_clients(context, &count); + + strv = malloc (sizeof (char *) * (count + 1)); + + for (unsigned int i = 0; i < count; i++) { + char bus_path[GAME_PATH_MAX] = {0, }; + + game_object_bus_path(clients[i], bus_path); + strv[i] = strdup (bus_path); + } + + strv[count] = NULL; + *nodes = strv; + + free(clients); + + return 1; +} + +/** + * Handles the ProcessId property for Game objects + */ +static int game_object_get_process_id(sd_bus *local_bus, const char *path, const char *interface, + const char *property, sd_bus_message *reply, void *userdata, + sd_bus_error *ret_error) +{ + GameModeClient *client; + GameModeContext *context; + pid_t pid; + int pv; + int ret; + + pid = pid_from_pointer(userdata); + context = game_mode_context_instance(); + client = game_mode_context_lookup_client(context, pid); + + pv = (int) pid; + + if (client == NULL) { + return sd_bus_error_setf(ret_error, + SD_BUS_ERROR_UNKNOWN_OBJECT, + "No client registered with id '%d'", pv); + } + + ret = sd_bus_message_append_basic(reply, 'i', &pv); + game_mode_client_unref(client); + + return ret; +} + +/** + * Handles the Exectuable property for Game objects + */ +static int game_object_get_executable(sd_bus *local_bus, const char *path, const char *interface, + const char *property, sd_bus_message *reply, void *userdata, + sd_bus_error *ret_error) +{ + GameModeClient *client; + GameModeContext *context; + const char *exec; + pid_t pid; + int ret; + + pid = pid_from_pointer(userdata); + + context = game_mode_context_instance(); + client = game_mode_context_lookup_client(context, pid); + + if (client == NULL) { + return sd_bus_error_setf(ret_error, + SD_BUS_ERROR_UNKNOWN_OBJECT, + "No client registered with id '%d'", (int) pid); + } + + exec = game_mode_client_get_executable(client); + ret = sd_bus_message_append_basic(reply, 's', exec); + game_mode_client_unref(client); + + return ret; +} + +/* Same as above: this bit seems to be formatted differently by different clang-format versions */ +/* clang-format off */ +static const sd_bus_vtable game_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("ProcessId", "i", game_object_get_process_id, 0, + SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Exectuable", "s", game_object_get_executable, 0, + SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_VTABLE_END }; /* clang-format on */ @@ -264,6 +505,27 @@ void game_mode_context_loop(GameModeContext *context) FATAL_ERROR("Failed to install GameMode object: %s\n", strerror(-ret)); } + ret = sd_bus_add_fallback_vtable(bus, + &slot, + "/com/feralinteractive/GameMode/Games", + "com.feralinteractive.GameMode.Game", + game_vtable, + game_object_find, + context); + + if (ret < 0) { + FATAL_ERROR("Failed to install Game object: %s\n", strerror(-ret)); + } + + ret = sd_bus_add_node_enumerator(bus, + &slot, + "/com/feralinteractive/GameMode/Games", + game_node_enumerator, + context); + if (ret < 0) { + FATAL_ERROR("Failed to install Game object enumerator: %s\n", strerror(-ret)); + } + /* Request our name */ ret = sd_bus_request_name(bus, "com.feralinteractive.GameMode", 0); if (ret < 0) { diff --git a/daemon/gamemode.h b/daemon/gamemode.h index e81052c..4dffc93 100644 --- a/daemon/gamemode.h +++ b/daemon/gamemode.h @@ -197,4 +197,6 @@ int game_mode_get_gpu(GameModeGPUInfo *info); */ void game_mode_context_loop(GameModeContext *context) __attribute__((noreturn)); int game_mode_inhibit_screensaver(bool inhibit); -void game_mode_client_count_changed(void); \ No newline at end of file +void game_mode_client_count_changed(void); +void game_mode_client_registered(pid_t); +void game_mode_client_unregistered(pid_t);