Browse Source

Add gamemode_query_status and teach gamemoded '-s'

	This allows the client to query the daemon about the status of gamemode.

	Returns the following:
		 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

	Passing -s to gamemoded will simply query and print the current status.

	Allows for more comprehensive testing when using 'gamemoded -r' as well as more reactionary program behaviour
Marc Di Luzio 6 years ago
parent
commit
4f3bc2c9a2
8 changed files with 140 additions and 4 deletions
  1. 1 2
      README.md
  2. 21 0
      daemon/dbus_messaging.c
  3. 34 0
      daemon/gamemode.c
  4. 10 0
      daemon/gamemode.h
  5. 27 2
      daemon/main.c
  6. 3 0
      data/gamemoded.1
  7. 6 0
      lib/client_impl.c
  8. 38 0
      lib/gamemode_client.h

+ 1 - 2
README.md

@@ -68,7 +68,7 @@ Or, distribute `libgamemodeauto.so` and either add `-lgamemodeauto` to your link
 ---
 ## Components
 
-**gamemoded** runs in the background, activates game mode on request, refcounts and also checks caller PID lifetime. Accepts `-d` (daemonize) and `-l` (log to syslog).
+**gamemoded** runs in the background, activates game mode on request, refcounts and also checks caller PID lifetime. Run `man gamemoded` for command line options.
 
 **libgamemode** is an internal library used to dispatch requests to the daemon. Note: `libgamemode` should never be linked with directly.
 
@@ -95,7 +95,6 @@ clang-format -i $(find . -name '*.[ch]' -not -path "*subprojects/*")
 ### Planned Features
 * Additional mode-switch plugins
 * Improved client state tracking (PID is unreliable)
-* API to query if game mode is active
 
 ### Maintained by
 Feral Interactive

+ 21 - 0
daemon/dbus_messaging.c

@@ -101,6 +101,26 @@ static int method_unregister_game(sd_bus_message *m, void *userdata,
 	return sd_bus_reply_method_return(m, "i", 0);
 }
 
+/**
+ * Handles the QueryStatus D-BUS Method
+ */
+static int method_query_status(sd_bus_message *m, void *userdata,
+                               __attribute__((unused)) sd_bus_error *ret_error)
+{
+	int pid = 0;
+	GameModeContext *context = userdata;
+
+	int ret = sd_bus_message_read(m, "i", &pid);
+	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)pid);
+
+	return sd_bus_reply_method_return(m, "i", status);
+}
+
 /**
  * D-BUS vtable to dispatch virtual methods
  */
@@ -108,6 +128,7 @@ static const sd_bus_vtable gamemode_vtable[] =
     { SD_BUS_VTABLE_START(0),
 	  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_VTABLE_END };
 
 /**

+ 34 - 0
daemon/gamemode.c

@@ -366,6 +366,40 @@ bool game_mode_context_unregister(GameModeContext *self, pid_t client)
 	return true;
 }
 
+int game_mode_context_query_status(GameModeContext *self, pid_t client)
+{
+	GameModeClient *cl = NULL;
+	int ret = 0;
+
+	/*
+	 * 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
+	 */
+	if (atomic_load_explicit(&self->refcount, memory_order_seq_cst)) {
+		ret++;
+
+		/* Check if the current client is registered */
+
+		/* Requires locking. */
+		pthread_rwlock_rdlock(&self->rwlock);
+
+		for (cl = self->client; cl; cl = cl->next) {
+			if (cl->pid != client) {
+				continue;
+			}
+
+			/* Found it */
+			ret++;
+			break;
+		}
+
+		/* Unlock here, potentially yielding */
+		pthread_rwlock_unlock(&self->rwlock);
+	}
+
+	return ret;
+}
+
 /**
  * Construct a new GameModeClient for the given process ID
  *

+ 10 - 0
daemon/gamemode.h

@@ -73,3 +73,13 @@ bool game_mode_context_register(GameModeContext *self, pid_t pid);
  * @returns True if the client was removed, and existed.
  */
 bool game_mode_context_unregister(GameModeContext *self, pid_t pid);
+
+/**
+ * Query the current status of gamemode
+ *
+ * @param pid Process ID for the remote client
+ * @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
+ */
+int game_mode_context_query_status(GameModeContext *self, pid_t pid);

+ 27 - 2
daemon/main.c

@@ -96,7 +96,7 @@ int main(int argc, char *argv[])
 	bool daemon = false;
 	bool use_syslog = false;
 	int opt = 0;
-	while ((opt = getopt(argc, argv, "dlrvh")) != -1) {
+	while ((opt = getopt(argc, argv, "dlsrvh")) != -1) {
 		switch (opt) {
 		case 'd':
 			daemon = true;
@@ -104,13 +104,38 @@ int main(int argc, char *argv[])
 		case 'l':
 			use_syslog = true;
 			break;
+		case 's': {
+			int status;
+
+			if ((status = gamemode_query_status()) < 0) {
+				fprintf(stderr, "gamemode status request failed: %s\n", gamemode_error_string());
+				exit(EXIT_FAILURE);
+			} else if (status > 0) {
+				fprintf(stdout, "gamemode is active\n");
+			} else {
+				fprintf(stdout, "gamemode is inactive\n");
+			}
+
+			exit(EXIT_SUCCESS);
+			break;
+		}
 		case 'r':
 			if (gamemode_request_start() < 0) {
 				fprintf(stderr, "gamemode request failed: %s\n", gamemode_error_string());
 				exit(EXIT_FAILURE);
 			}
 
-			fprintf(stdout, "gamemode request succeeded...\n");
+			int status = gamemode_query_status();
+			if (status == 2) {
+				fprintf(stdout, "gamemode request succeeded and is active\n");
+			} else if (status == 1) {
+				fprintf(stderr,
+				        "gamemode request succeeded and is active but registration failed\n");
+				exit(EXIT_FAILURE);
+			} else {
+				fprintf(stderr, "gamemode request succeeded but is not active\n");
+				exit(EXIT_FAILURE);
+			}
 
 			// Simply pause and wait for any signal
 			pause();

+ 3 - 0
data/gamemoded.1

@@ -22,6 +22,9 @@ Log to syslog
 .B \-r
 Request gamemode and wait for any signal
 .TP 8
+.B \-s
+Query the current status of gamemode
+.TP 8
 .B \-h
 Print help text
 .TP 8

+ 6 - 0
lib/client_impl.c

@@ -105,3 +105,9 @@ extern int real_gamemode_request_end(void)
 {
 	return gamemode_request("UnregisterGame");
 }
+
+// Wrapper to call UnregisterGame
+extern int real_gamemode_query_status(void)
+{
+	return gamemode_request("QueryStatus");
+}

+ 38 - 0
lib/gamemode_client.h

@@ -30,6 +30,28 @@ POSSIBILITY OF SUCH DAMAGE.
  */
 #ifndef CLIENT_GAMEMODE_H
 #define CLIENT_GAMEMODE_H
+/*
+ * GameMode supports the following client functions
+ * Requests are refcounted in the daemon
+ *
+ * int gamemode_request_start() - Request gamemode starts
+ *   0 if the request was sent successfully
+ *   -1 if the request failed
+ *
+ * int gamemode_request_end() - Request gamemode ends
+ *   0 if the request was sent successfully
+ *   -1 if the request failed
+ *
+ * int gamemode_query_status() - Query the current status of gamemode
+ *   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
+ */
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -51,11 +73,13 @@ 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);
 
 /* 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;
 
 /**
@@ -111,6 +135,9 @@ __attribute__((always_inline)) static inline int internal_load_libgamemode(void)
 		{ "real_gamemode_request_end",
 		  (void **)&REAL_internal_gamemode_request_end,
 		  sizeof(REAL_internal_gamemode_request_end) },
+		{ "real_gamemode_query_status",
+		  (void **)&REAL_internal_gamemode_query_status,
+		  sizeof(REAL_internal_gamemode_query_status) },
 		{ "real_gamemode_error_string",
 		  (void **)&REAL_internal_gamemode_error_string,
 		  sizeof(REAL_internal_gamemode_error_string) },
@@ -216,4 +243,15 @@ int gamemode_request_end(void)
 	return 0;
 }
 
+/* Redirect to the real libgamemode */
+__attribute__((always_inline)) static inline int gamemode_query_status(void)
+{
+	/* Need to load gamemode */
+	if (internal_load_libgamemode() < 0) {
+		return -1;
+	}
+
+	return REAL_internal_gamemode_query_status();
+}
+
 #endif // CLIENT_GAMEMODE_H