Explorar o código

Merge pull request #106 from mdiluz/supervisor-support

Add "supervisor" support
Alex Smith %!s(int64=6) %!d(string=hai) anos
pai
achega
326be7ebbd
Modificáronse 10 ficheiros con 505 adicións e 61 borrados
  1. 12 0
      README.md
  2. 78 12
      daemon/daemon_config.c
  3. 7 0
      daemon/daemon_config.h
  4. 74 5
      daemon/dbus_messaging.c
  5. 109 6
      daemon/gamemode-tests.c
  6. 80 11
      daemon/gamemode.c
  7. 12 5
      daemon/gamemode.h
  8. 10 0
      example/gamemode.ini
  9. 26 7
      lib/client_impl.c
  10. 97 15
      lib/gamemode_client.h

+ 12 - 0
README.md

@@ -148,6 +148,18 @@ Developers can integrate the request directly into an app. Note that none of the
 
 Or, distribute `libgamemodeauto.so` and either add `-lgamemodeauto` to your linker arguments, or add it to an LD\_PRELOAD in a launch script.
 
+### Supervisor support
+Developers can also create apps that manage GameMode on the system, for other processes:
+
+```C
+#include "gamemode_client.h"
+
+	gamemode_request_start_for(gamePID);
+	gamemode_request_end_for(gamePID);
+```
+
+This functionality can also be controlled in the config file in the `supervisor` section.
+
 ---
 ## Contributions
 

+ 78 - 12
daemon/daemon_config.c

@@ -94,6 +94,10 @@ struct GameModeConfig {
 		long nv_perf_level;
 		long amd_core_clock_percentage;
 		long amd_mem_clock_percentage;
+
+		long require_supervisor;
+		char supervisor_whitelist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
+		char supervisor_blacklist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
 	} values;
 };
 
@@ -171,6 +175,21 @@ static bool get_long_value_hex(const char *value_name, const char *value, long *
 	return true;
 }
 
+/**
+ * Simple strstr scheck
+ * Could be expanded for wildcard or regex
+ */
+static bool config_string_list_contains(const char *needle,
+                                        char haystack[CONFIG_LIST_MAX][CONFIG_VALUE_MAX])
+{
+	for (unsigned int i = 0; i < CONFIG_LIST_MAX && haystack[i][0]; i++) {
+		if (strstr(needle, haystack[i])) {
+			return true;
+		}
+	}
+	return false;
+}
+
 /*
  * Get a string value
  */
@@ -246,6 +265,15 @@ static int inih_handler(void *user, const char *section, const char *name, const
 		} else if (strcmp(name, "amd_mem_clock_percentage") == 0) {
 			valid = get_long_value(name, value, &self->values.amd_mem_clock_percentage);
 		}
+	} else if (strcmp(section, "supervisor") == 0) {
+		/* Supervisor subsection */
+		if (strcmp(name, "supervisor_whitelist") == 0) {
+			valid = append_value_to_list(name, value, self->values.supervisor_whitelist);
+		} else if (strcmp(name, "supervisor_blacklist") == 0) {
+			valid = append_value_to_list(name, value, self->values.supervisor_blacklist);
+		} else if (strcmp(name, "require_supervisor") == 0) {
+			valid = get_long_value(name, value, &self->values.require_supervisor);
+		}
 	} else if (strcmp(section, "custom") == 0) {
 		/* Custom subsection */
 		if (strcmp(name, "start") == 0) {
@@ -417,12 +445,7 @@ bool config_get_client_whitelisted(GameModeConfig *self, const char *client)
 		 * Check if the value is found in our whitelist
 		 * Currently is a simple strstr check, but could be modified for wildcards etc.
 		 */
-		found = false;
-		for (unsigned int i = 0; i < CONFIG_LIST_MAX && self->values.whitelist[i][0]; i++) {
-			if (strstr(client, self->values.whitelist[i])) {
-				found = true;
-			}
-		}
+		found = config_string_list_contains(client, self->values.whitelist);
 	}
 
 	/* release the lock */
@@ -442,12 +465,7 @@ bool config_get_client_blacklisted(GameModeConfig *self, const char *client)
 	 * Check if the value is found in our whitelist
 	 * Currently is a simple strstr check, but could be modified for wildcards etc.
 	 */
-	bool found = false;
-	for (unsigned int i = 0; i < CONFIG_LIST_MAX && self->values.blacklist[i][0]; i++) {
-		if (strstr(client, self->values.blacklist[i])) {
-			found = true;
-		}
-	}
+	bool found = config_string_list_contains(client, self->values.blacklist);
 
 	/* release the lock */
 	pthread_rwlock_unlock(&self->rwlock);
@@ -563,3 +581,51 @@ DEFINE_CONFIG_GET(nv_mem_clock_mhz_offset)
 DEFINE_CONFIG_GET(nv_perf_level)
 DEFINE_CONFIG_GET(amd_core_clock_percentage)
 DEFINE_CONFIG_GET(amd_mem_clock_percentage)
+
+/*
+        char supervisor_whitelist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
+        char supervisor_blacklist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
+*/
+DEFINE_CONFIG_GET(require_supervisor)
+
+/*
+ * Checks if the supervisor is whitelisted
+ */
+bool config_get_supervisor_whitelisted(GameModeConfig *self, const char *supervisor)
+{
+	/* Take the read lock for the internal data */
+	pthread_rwlock_rdlock(&self->rwlock);
+
+	/* If the whitelist is empty then everything passes */
+	bool found = true;
+	if (self->values.supervisor_whitelist[0][0]) {
+		/*
+		 * Check if the value is found in our whitelist
+		 * Currently is a simple strstr check, but could be modified for wildcards etc.
+		 */
+		found = config_string_list_contains(supervisor, self->values.supervisor_whitelist);
+	}
+
+	/* release the lock */
+	pthread_rwlock_unlock(&self->rwlock);
+	return found;
+}
+
+/*
+ * Checks if the supervisor is blacklisted
+ */
+bool config_get_supervisor_blacklisted(GameModeConfig *self, const char *supervisor)
+{
+	/* Take the read lock for the internal data */
+	pthread_rwlock_rdlock(&self->rwlock);
+
+	/*
+	 * Check if the value is found in our whitelist
+	 * Currently is a simple strstr check, but could be modified for wildcards etc.
+	 */
+	bool found = config_string_list_contains(supervisor, self->values.supervisor_blacklist);
+
+	/* release the lock */
+	pthread_rwlock_unlock(&self->rwlock);
+	return found;
+}

+ 7 - 0
daemon/daemon_config.h

@@ -141,3 +141,10 @@ long config_get_nv_mem_clock_mhz_offset(GameModeConfig *self);
 long config_get_nv_perf_level(GameModeConfig *self);
 long config_get_amd_core_clock_percentage(GameModeConfig *self);
 long config_get_amd_mem_clock_percentage(GameModeConfig *self);
+
+/**
+ * Functions to get supervisor config permissions
+ */
+long config_get_require_supervisor(GameModeConfig *self);
+bool config_get_supervisor_whitelisted(GameModeConfig *self, const char *supervisor);
+bool config_get_supervisor_blacklisted(GameModeConfig *self, const char *supervisor);

+ 74 - 5
daemon/dbus_messaging.c

@@ -75,9 +75,9 @@ static int method_register_game(sd_bus_message *m, void *userdata,
 		return ret;
 	}
 
-	game_mode_context_register(context, (pid_t)pid);
+	int status = game_mode_context_register(context, (pid_t)pid, (pid_t)pid);
 
-	return sd_bus_reply_method_return(m, "i", 0);
+	return sd_bus_reply_method_return(m, "i", status);
 }
 
 /**
@@ -95,9 +95,9 @@ static int method_unregister_game(sd_bus_message *m, void *userdata,
 		return ret;
 	}
 
-	game_mode_context_unregister(context, (pid_t)pid);
+	int status = game_mode_context_unregister(context, (pid_t)pid, (pid_t)pid);
 
-	return sd_bus_reply_method_return(m, "i", 0);
+	return sd_bus_reply_method_return(m, "i", status);
 }
 
 /**
@@ -115,7 +115,70 @@ static int method_query_status(sd_bus_message *m, void *userdata,
 		return ret;
 	}
 
-	int status = game_mode_context_query_status(context, (pid_t)pid);
+	int status = game_mode_context_query_status(context, (pid_t)pid, (pid_t)pid);
+
+	return sd_bus_reply_method_return(m, "i", status);
+}
+
+/**
+ * Handles the RegisterGameByPID D-BUS Method
+ */
+static int method_register_game_by_pid(sd_bus_message *m, void *userdata,
+                                       __attribute__((unused)) sd_bus_error *ret_error)
+{
+	int callerpid = 0;
+	int gamepid = 0;
+	GameModeContext *context = userdata;
+
+	int ret = sd_bus_message_read(m, "ii", &callerpid, &gamepid);
+	if (ret < 0) {
+		LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
+		return ret;
+	}
+
+	int reply = game_mode_context_register(context, (pid_t)gamepid, (pid_t)callerpid);
+
+	return sd_bus_reply_method_return(m, "i", reply);
+}
+
+/**
+ * Handles the UnregisterGameByPID D-BUS Method
+ */
+static int method_unregister_game_by_pid(sd_bus_message *m, void *userdata,
+                                         __attribute__((unused)) sd_bus_error *ret_error)
+{
+	int callerpid = 0;
+	int gamepid = 0;
+	GameModeContext *context = userdata;
+
+	int ret = sd_bus_message_read(m, "ii", &callerpid, &gamepid);
+	if (ret < 0) {
+		LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
+		return ret;
+	}
+
+	int reply = game_mode_context_unregister(context, (pid_t)gamepid, (pid_t)callerpid);
+
+	return sd_bus_reply_method_return(m, "i", reply);
+}
+
+/**
+ * Handles the QueryStatus D-BUS Method
+ */
+static int method_query_status_by_pid(sd_bus_message *m, void *userdata,
+                                      __attribute__((unused)) sd_bus_error *ret_error)
+{
+	int callerpid = 0;
+	int gamepid = 0;
+	GameModeContext *context = userdata;
+
+	int ret = sd_bus_message_read(m, "ii", &callerpid, &gamepid);
+	if (ret < 0) {
+		LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
+		return ret;
+	}
+
+	int status = game_mode_context_query_status(context, (pid_t)gamepid, (pid_t)callerpid);
 
 	return sd_bus_reply_method_return(m, "i", status);
 }
@@ -128,6 +191,12 @@ static const sd_bus_vtable gamemode_vtable[] =
 	  SD_BUS_METHOD("RegisterGame", "i", "i", method_register_game, SD_BUS_VTABLE_UNPRIVILEGED),
 	  SD_BUS_METHOD("UnregisterGame", "i", "i", method_unregister_game, SD_BUS_VTABLE_UNPRIVILEGED),
 	  SD_BUS_METHOD("QueryStatus", "i", "i", method_query_status, SD_BUS_VTABLE_UNPRIVILEGED),
+	  SD_BUS_METHOD("RegisterGameByPID", "ii", "i", method_register_game_by_pid,
+	                SD_BUS_VTABLE_UNPRIVILEGED),
+	  SD_BUS_METHOD("UnregisterGameByPID", "ii", "i", method_unregister_game_by_pid,
+	                SD_BUS_VTABLE_UNPRIVILEGED),
+	  SD_BUS_METHOD("QueryStatusByPID", "ii", "i", method_query_status_by_pid,
+	                SD_BUS_VTABLE_UNPRIVILEGED),
 	  SD_BUS_VTABLE_END };
 
 /**

+ 109 - 6
daemon/gamemode-tests.c

@@ -127,12 +127,6 @@ static int run_basic_client_tests(void)
 {
 	LOG_MSG(":: Basic client tests\n");
 
-	/* First verify that gamemode is not currently active on the system
-	 * As well as it being currently installed and queryable
-	 */
-	if (verify_gamemode_initial() != 0)
-		return -1;
-
 	/* Verify that gamemode_request_start correctly start gamemode */
 	if (gamemode_request_start() != 0) {
 		LOG_ERROR("gamemode_request_start failed: %s\n", gamemode_error_string());
@@ -576,6 +570,96 @@ static int game_mode_run_feature_tests(struct GameModeConfig *config)
 	return status;
 }
 
+/* Run a set of tests on the supervisor code */
+static int run_supervisor_tests(void)
+{
+	int supervisortests = 0;
+	int ret = 0;
+
+	LOG_MSG(":: Supervisor tests\n");
+
+	/* Launch an external dummy process we can leave running and request gamemode for it */
+	pid_t pid = fork();
+	if (pid == 0) {
+		/* Child simply pauses and exits */
+		pause();
+		exit(EXIT_SUCCESS);
+	}
+
+	/* Request gamemode for our dummy process */
+	ret = gamemode_request_start_for(pid);
+	if (ret != 0) {
+		LOG_ERROR("gamemode_request_start_for gave unexpected value %d, (expected 0)!\n", ret);
+		if (ret == -1)
+			LOG_ERROR("GameMode error string: %s!\n", gamemode_error_string());
+		supervisortests = -1;
+	}
+
+	/* Check it's active */
+	ret = gamemode_query_status();
+	if (ret != 1) {
+		LOG_ERROR(
+		    "gamemode_query_status after start request gave unexpected value %d, (expected 1)!\n",
+		    ret);
+		if (ret == -1)
+			LOG_ERROR("GameMode error string: %s!\n", gamemode_error_string());
+		supervisortests = -1;
+	}
+
+	/* Check it's active for the dummy */
+	ret = gamemode_query_status_for(pid);
+	if (ret != 2) {
+		LOG_ERROR(
+		    "gamemode_query_status_for after start request gave unexpected value %d, (expected "
+		    "2)!\n",
+		    ret);
+		if (ret == -1)
+			LOG_ERROR("GameMode error string: %s!\n", gamemode_error_string());
+		supervisortests = -1;
+	}
+
+	/* request gamemode end for the client */
+	ret = gamemode_request_end_for(pid);
+	if (ret != 0) {
+		LOG_ERROR("gamemode_request_end_for gave unexpected value %d, (expected 0)!\n", ret);
+		if (ret == -1)
+			LOG_ERROR("GameMode error string: %s!\n", gamemode_error_string());
+		supervisortests = -1;
+	}
+
+	/* Verify it's not active */
+	ret = gamemode_query_status();
+	if (ret != 0) {
+		LOG_ERROR(
+		    "gamemode_query_status after end request gave unexpected value %d, (expected 0)!\n",
+		    ret);
+		if (ret == -1)
+			LOG_ERROR("GameMode error string: %s!\n", gamemode_error_string());
+		supervisortests = -1;
+	}
+
+	/* Wake up the child process */
+	if (kill(pid, SIGUSR1) == -1) {
+		LOG_ERROR("failed to send continue signal to other child process: %s\n", strerror(errno));
+		supervisortests = -1;
+	}
+
+	// Wait for the child to finish up
+	int wstatus;
+	usleep(100000);
+	while (waitpid(pid, &wstatus, WNOHANG) == 0) {
+		LOG_MSG("...Waiting for child to quit...\n");
+		usleep(100000);
+	}
+
+	if (supervisortests == 0)
+		LOG_MSG(":: Passed\n\n");
+	else
+		LOG_ERROR(":: Failed!\n");
+
+	return supervisortests;
+}
+
 /**
  * game_mode_run_client_tests runs a set of tests of the client code
  * we simply verify that the client can request the status and recieves the correct results
@@ -595,6 +679,21 @@ int game_mode_run_client_tests()
 
 	LOG_MSG(": Running tests\n\n");
 
+	/* First verify that gamemode is not currently active on the system
+	 * As well as it being currently installed and queryable
+	 */
+	if (verify_gamemode_initial() != 0)
+		return -1;
+
+	/* Controls whether we require a supervisor to actually make requests */
+	/* TODO: This effects all tests below */
+	if (config_get_require_supervisor(config) != 0) {
+		LOG_ERROR("Tests currently unsupported when require_supervisor is set\n");
+		return -1;
+	}
+
+	/* TODO: Also check blacklist/whitelist values as these may mess up the tests below */
+
 	/* Run the basic tests */
 	if (run_basic_client_tests() != 0)
 		status = -1;
@@ -607,6 +706,10 @@ int game_mode_run_client_tests()
 	if (run_gamemoderun_and_reaper_tests(config) != 0)
 		status = -1;
 
+	/* Run the supervisor tests */
+	if (run_supervisor_tests() != 0)
+		status = -1;
+
 	if (status != 0) {
 		LOG_MSG(": Client tests failed, skipping feature tests\n");
 	} else {

+ 80 - 11
daemon/gamemode.c

@@ -291,7 +291,7 @@ static void game_mode_context_auto_expire(GameModeContext *self)
 			if (kill(client->pid, 0) != 0) {
 				LOG_MSG("Removing expired game [%i]...\n", client->pid);
 				pthread_rwlock_unlock(&self->rwlock);
-				game_mode_context_unregister(self, client->pid);
+				game_mode_context_unregister(self, client->pid, client->pid);
 				removing = true;
 				break;
 			}
@@ -335,20 +335,43 @@ static int game_mode_context_num_clients(GameModeContext *self)
 	return atomic_load(&self->refcount);
 }
 
-bool game_mode_context_register(GameModeContext *self, pid_t client)
+int game_mode_context_register(GameModeContext *self, pid_t client, pid_t requester)
 {
+	errno = 0;
+
 	/* Construct a new client if we can */
 	GameModeClient *cl = NULL;
 	char *executable = NULL;
 
+	/* Check our requester config first */
+	if (requester != client) {
+		/* Lookup the executable first */
+		executable = game_mode_context_find_exe(requester);
+		if (!executable) {
+			return -1;
+		}
+
+		/* Check our blacklist and whitelist */
+		if (!config_get_supervisor_whitelisted(self->config, executable)) {
+			LOG_MSG("Supervisor [%s] was rejected (not in whitelist)\n", executable);
+			free(executable);
+			return -2;
+		} else if (config_get_supervisor_blacklisted(self->config, executable)) {
+			LOG_MSG("Supervisor [%s] was rejected (in blacklist)\n", executable);
+			free(executable);
+			return -2;
+		}
+	} else if (config_get_require_supervisor(self->config)) {
+		LOG_ERROR("Direct request made but require_supervisor was set, rejecting request!\n");
+		return -2;
+	}
+
 	/* Cap the total number of active clients */
 	if (game_mode_context_num_clients(self) + 1 > MAX_GAMES) {
 		LOG_ERROR("Max games (%d) reached, not registering %d\n", MAX_GAMES, client);
-		return false;
+		goto error_cleanup;
 	}
 
-	errno = 0;
-
 	/* Check the PID first to spare a potentially expensive lookup for the exe */
 	pthread_rwlock_rdlock(&self->rwlock); // ensure our pointer is sane
 	const GameModeClient *existing = game_mode_context_has_client(self, client);
@@ -407,22 +430,47 @@ bool game_mode_context_register(GameModeContext *self, pid_t client)
 	/* Apply io priorities */
 	game_mode_apply_ioprio(self, client);
 
-	return true;
+	return 0;
 
 error_cleanup:
 	if (errno != 0)
 		LOG_ERROR("Failed to register client [%d]: %s\n", client, strerror(errno));
 	free(executable);
 	game_mode_client_free(cl);
-	return false;
+	return -1;
 }
 
-bool game_mode_context_unregister(GameModeContext *self, pid_t client)
+int game_mode_context_unregister(GameModeContext *self, pid_t client, pid_t requester)
 {
 	GameModeClient *cl = NULL;
 	GameModeClient *prev = NULL;
 	bool found = false;
 
+	/* Check our requester config first */
+	if (requester != client) {
+		/* Lookup the executable first */
+		char *executable = game_mode_context_find_exe(requester);
+		if (!executable) {
+			return -1;
+		}
+
+		/* Check our blacklist and whitelist */
+		if (!config_get_supervisor_whitelisted(self->config, executable)) {
+			LOG_MSG("Supervisor [%s] was rejected (not in whitelist)\n", executable);
+			free(executable);
+			return -2;
+		} else if (config_get_supervisor_blacklisted(self->config, executable)) {
+			LOG_MSG("Supervisor [%s] was rejected (in blacklist)\n", executable);
+			free(executable);
+			return -2;
+		}
+
+		free(executable);
+	} else if (config_get_require_supervisor(self->config)) {
+		LOG_ERROR("Direct request made but require_supervisor was set, rejecting request!\n");
+		return -2;
+	}
+
 	/* Requires locking. */
 	pthread_rwlock_wrlock(&self->rwlock);
 
@@ -457,7 +505,7 @@ bool game_mode_context_unregister(GameModeContext *self, pid_t client)
 		    "    -- with a nearby 'Removing expired game' which means we cleaned up properly\n"
 		    "    -- (we will log this event). This hint will be displayed only once.\n",
 		    client);
-		return false;
+		return -1;
 	}
 
 	/* When we hit bottom then end the game mode */
@@ -465,14 +513,35 @@ bool game_mode_context_unregister(GameModeContext *self, pid_t client)
 		game_mode_context_leave(self);
 	}
 
-	return true;
+	return 0;
 }
 
-int game_mode_context_query_status(GameModeContext *self, pid_t client)
+int game_mode_context_query_status(GameModeContext *self, pid_t client, pid_t requester)
 {
 	GameModeClient *cl = NULL;
 	int ret = 0;
 
+	/* First check the requester settings if appropriate */
+	if (client != requester) {
+		char *executable = game_mode_context_find_exe(requester);
+		if (!executable) {
+			return -1;
+		}
+
+		/* Check our blacklist and whitelist */
+		if (!config_get_supervisor_whitelisted(self->config, executable)) {
+			LOG_MSG("Supervisor [%s] was rejected (not in whitelist)\n", executable);
+			free(executable);
+			return -2;
+		} else if (config_get_supervisor_blacklisted(self->config, executable)) {
+			LOG_MSG("Supervisor [%s] was rejected (in blacklist)\n", executable);
+			free(executable);
+			return -2;
+		}
+
+		free(executable);
+	}
+
 	/*
 	 * Check the current refcount on gamemode, this equates to whether gamemode is active or not,
 	 * see game_mode_context_register and game_mode_context_unregister

+ 12 - 5
daemon/gamemode.h

@@ -67,17 +67,23 @@ void game_mode_context_destroy(GameModeContext *self);
  * Register a new game client with the context
  *
  * @param pid Process ID for the remote client
- * @returns True if the new client could be registered
+ * @param requester Process ID for the remote requestor
+ * @returns 0 if the request was accepted and the client could be registered
+ *          -1 if the request was accepted but the client could not be registered
+ *          -2 if the request was rejected
  */
-bool game_mode_context_register(GameModeContext *self, pid_t pid);
+int game_mode_context_register(GameModeContext *self, pid_t pid, pid_t requester);
 
 /**
  * Unregister an existing remote game client from the context
  *
  * @param pid Process ID for the remote client
- * @returns True if the client was removed, and existed.
+ * @param requester Process ID for the remote requestor
+ * @returns 0 if the request was accepted and the client existed
+ *          -1 if the request was accepted but the client did not exist
+ *          -2 if the request was rejected
  */
-bool game_mode_context_unregister(GameModeContext *self, pid_t pid);
+int game_mode_context_unregister(GameModeContext *self, pid_t pid, pid_t requester);
 
 /**
  * Query the current status of gamemode
@@ -86,8 +92,9 @@ bool game_mode_context_unregister(GameModeContext *self, pid_t pid);
  * @returns Positive if gamemode is active
  *          1 if gamemode is active but the client is not registered
  *          2 if gamemode is active and the client is registered
+ *          -2 if this requester was rejected
  */
-int game_mode_context_query_status(GameModeContext *self, pid_t pid);
+int game_mode_context_query_status(GameModeContext *self, pid_t pid, pid_t requester);
 
 /**
  * Query the config of a gamemode context

+ 10 - 0
example/gamemode.ini

@@ -61,6 +61,16 @@ inhibit_screensaver=1
 ;amd_core_clock_percentage=0
 ;amd_mem_clock_percentage=0
 
+[supervisor]
+; This section controls the new gamemode functions gamemode_request_start_for and gamemode_request_end_for
+; The whilelist and blacklist control which supervisor programs are allowed to make the above requests
+;supervisor_whitelist=
+;supervisor_blacklist=
+
+; In case you want to allow a supervisor to take full control of gamemode, this option can be set
+; This will only allow gamemode clients to be registered by using the above functions by a supervisor client
+;require_supervisor=0
+
 [custom]
 ; Custom scripts (executed using the shell) when gamemode starts and ends
 ;start=notify-send "GameMode started"

+ 26 - 7
lib/client_impl.c

@@ -43,7 +43,7 @@ POSSIBILITY OF SUCH DAMAGE.
 static char error_string[512] = { 0 };
 
 // Simple requestor function for a gamemode
-static int gamemode_request(const char *function)
+static int gamemode_request(const char *function, int arg)
 {
 	sd_bus_message *msg = NULL;
 	sd_bus *bus = NULL;
@@ -66,8 +66,9 @@ static int gamemode_request(const char *function)
 		                         function,
 		                         NULL,
 		                         &msg,
-		                         "i",
-		                         getpid());
+		                         arg ? "ii" : "i",
+		                         getpid(),
+		                         arg);
 		if (ret < 0) {
 			snprintf(error_string,
 			         sizeof(error_string),
@@ -97,17 +98,35 @@ extern const char *real_gamemode_error_string(void)
 // Wrapper to call RegisterGame
 extern int real_gamemode_request_start(void)
 {
-	return gamemode_request("RegisterGame");
+	return gamemode_request("RegisterGame", 0);
 }
 
 // Wrapper to call UnregisterGame
 extern int real_gamemode_request_end(void)
 {
-	return gamemode_request("UnregisterGame");
+	return gamemode_request("UnregisterGame", 0);
 }
 
-// Wrapper to call UnregisterGame
+// Wrapper to call QueryStatus
 extern int real_gamemode_query_status(void)
 {
-	return gamemode_request("QueryStatus");
+	return gamemode_request("QueryStatus", 0);
+}
+
+// Wrapper to call RegisterGameByPID
+extern int real_gamemode_request_start_for(pid_t pid)
+{
+	return gamemode_request("RegisterGameByPID", pid);
+}
+
+// Wrapper to call UnregisterGameByPID
+extern int real_gamemode_request_end_for(pid_t pid)
+{
+	return gamemode_request("UnregisterGameByPID", pid);
+}
+
+// Wrapper to call QueryStatusByPID
+extern int real_gamemode_query_status_for(pid_t pid)
+{
+	return gamemode_request("QueryStatusByPID", pid);
 }

+ 97 - 15
lib/gamemode_client.h

@@ -48,6 +48,20 @@ POSSIBILITY OF SUCH DAMAGE.
  *   2 if gamemode is active and this client is registered
  *   -1 if the query failed
  *
+ * int gamemode_request_start_for(pid_t pid) - Request gamemode starts for another process
+ *   0 if the request was sent successfully
+ *   -1 if the request failed
+ *   -2 if the request was rejected
+ *
+ * int gamemode_request_end_for(pid_t pid) - Request gamemode ends for another process
+ *   0 if the request was sent successfully
+ *   -1 if the request failed
+ *   -2 if the request was rejected
+ *
+ * int gamemode_query_status_for(pid_t pid) - Query the current status of gamemode for another
+ * process 0 if gamemode is inactive 1 if gamemode is active 2 if gamemode is active and this client
+ * is registered -1 if the query failed
+ *
  * const char* gamemode_error_string() - Get an error string
  *   returns a string describing any of the above errors
  */
@@ -61,6 +75,8 @@ POSSIBILITY OF SUCH DAMAGE.
 #include <errno.h>
 #include <string.h>
 
+#include <sys/types.h>
+
 static char internal_gamemode_client_error_string[512] = { 0 };
 
 /**
@@ -72,26 +88,26 @@ static char internal_gamemode_client_error_string[512] = { 0 };
 static volatile int internal_libgamemode_loaded = 1;
 
 /* Typedefs for the functions to load */
-typedef int (*internal_gamemode_request_start)(void);
-typedef int (*internal_gamemode_request_end)(void);
-typedef int (*internal_gamemode_query_status)(void);
-typedef const char *(*internal_gamemode_error_string)(void);
+typedef int (*api_call_return_int)(void);
+typedef const char *(*api_call_return_cstring)(void);
+typedef int (*api_call_pid_return_int)(pid_t);
 
 /* Storage for functors */
-static internal_gamemode_request_start REAL_internal_gamemode_request_start = NULL;
-static internal_gamemode_request_end REAL_internal_gamemode_request_end = NULL;
-static internal_gamemode_query_status REAL_internal_gamemode_query_status = NULL;
-static internal_gamemode_error_string REAL_internal_gamemode_error_string = NULL;
+static api_call_return_int REAL_internal_gamemode_request_start = NULL;
+static api_call_return_int REAL_internal_gamemode_request_end = NULL;
+static api_call_return_int REAL_internal_gamemode_query_status = NULL;
+static api_call_return_cstring REAL_internal_gamemode_error_string = NULL;
+static api_call_pid_return_int REAL_internal_gamemode_request_start_for = NULL;
+static api_call_pid_return_int REAL_internal_gamemode_request_end_for = NULL;
+static api_call_pid_return_int REAL_internal_gamemode_query_status_for = NULL;
 
 /**
  * Internal helper to perform the symbol binding safely.
  *
  * Returns 0 on success and -1 on failure
  */
-__attribute__((always_inline)) static inline int internal_bind_libgamemode_symbol(void *handle,
-                                                                                  const char *name,
-                                                                                  void **out_func,
-                                                                                  size_t func_size)
+__attribute__((always_inline)) static inline int internal_bind_libgamemode_symbol(
+    void *handle, const char *name, void **out_func, size_t func_size, bool required)
 {
 	void *symbol_lookup = NULL;
 	char *dl_error = NULL;
@@ -99,7 +115,7 @@ __attribute__((always_inline)) static inline int internal_bind_libgamemode_symbo
 	/* Safely look up the symbol */
 	symbol_lookup = dlsym(handle, name);
 	dl_error = dlerror();
-	if (dl_error || !symbol_lookup) {
+	if (required && (dl_error || !symbol_lookup)) {
 		snprintf(internal_gamemode_client_error_string,
 		         sizeof(internal_gamemode_client_error_string),
 		         "dlsym failed - %s",
@@ -147,6 +163,18 @@ __attribute__((always_inline)) static inline int internal_load_libgamemode(void)
 		  (void **)&REAL_internal_gamemode_error_string,
 		  sizeof(REAL_internal_gamemode_error_string),
 		  true },
+		{ "real_gamemode_request_start_for",
+		  (void **)&REAL_internal_gamemode_request_start_for,
+		  sizeof(REAL_internal_gamemode_request_start_for),
+		  false },
+		{ "real_gamemode_request_end_for",
+		  (void **)&REAL_internal_gamemode_request_end_for,
+		  sizeof(REAL_internal_gamemode_request_end_for),
+		  false },
+		{ "real_gamemode_query_status_for",
+		  (void **)&REAL_internal_gamemode_query_status_for,
+		  sizeof(REAL_internal_gamemode_query_status_for),
+		  false },
 	};
 
 	void *libgamemode = NULL;
@@ -175,8 +203,8 @@ __attribute__((always_inline)) static inline int internal_load_libgamemode(void)
 		if (internal_bind_libgamemode_symbol(libgamemode,
 		                                     binder->name,
 		                                     binder->functor,
-		                                     binder->func_size) != 0 &&
-		    binder->required) {
+		                                     binder->func_size,
+		                                     binder->required)) {
 			internal_libgamemode_loaded = -1;
 			return -1;
 		};
@@ -275,4 +303,58 @@ __attribute__((always_inline)) static inline int gamemode_query_status(void)
 	return REAL_internal_gamemode_query_status();
 }
 
+/* Redirect to the real libgamemode */
+__attribute__((always_inline)) static inline int gamemode_request_start_for(pid_t pid)
+{
+	/* Need to load gamemode */
+	if (internal_load_libgamemode() < 0) {
+		return -1;
+	}
+
+	if (REAL_internal_gamemode_request_start_for == NULL) {
+		snprintf(internal_gamemode_client_error_string,
+		         sizeof(internal_gamemode_client_error_string),
+		         "gamemode_request_start_for missing (older host?)");
+		return -1;
+	}
+
+	return REAL_internal_gamemode_request_start_for(pid);
+}
+
+/* Redirect to the real libgamemode */
+__attribute__((always_inline)) static inline int gamemode_request_end_for(pid_t pid)
+{
+	/* Need to load gamemode */
+	if (internal_load_libgamemode() < 0) {
+		return -1;
+	}
+
+	if (REAL_internal_gamemode_request_end_for == NULL) {
+		snprintf(internal_gamemode_client_error_string,
+		         sizeof(internal_gamemode_client_error_string),
+		         "gamemode_request_end_for missing (older host?)");
+		return -1;
+	}
+
+	return REAL_internal_gamemode_request_end_for(pid);
+}
+
+/* Redirect to the real libgamemode */
+__attribute__((always_inline)) static inline int gamemode_query_status_for(pid_t pid)
+{
+	/* Need to load gamemode */
+	if (internal_load_libgamemode() < 0) {
+		return -1;
+	}
+
+	if (REAL_internal_gamemode_query_status_for == NULL) {
+		snprintf(internal_gamemode_client_error_string,
+		         sizeof(internal_gamemode_client_error_string),
+		         "gamemode_query_status_for missing (older host?)");
+		return -1;
+	}
+
+	return REAL_internal_gamemode_query_status_for(pid);
+}
+
 #endif // CLIENT_GAMEMODE_H