]> skyeroc.xyz Git - dgamelaunch/commitdiff
Enhanced watchmode
authorTangles <andyrthomson@gmail.com>
Wed, 3 Jan 2018 01:28:44 +0000 (12:28 +1100)
committerTangles <andyrthomson@gmail.com>
Wed, 3 Jan 2018 01:28:44 +0000 (12:28 +1100)
Allow an external program to be specified to watch particular games
to make use of NH4's wachmode. Add game config options watch_path,
watch_args and watchcommands to support this.  Also add %w/%W for
format tokens the watched player, and %l/%L for the logged-in user
because %n/%N fall back to the watched player if nobody is logged
in, which is not always what we want.  Watch menu now displays N/A
for the terminal size of games which use this, because NH4 watch
mode does not care about player's terminal dimensions.  Other external
watching tools may need this in the future, but for now there is
nothing that requires this.

.gitignore
config.l
config.y
dgamelaunch.c
dgamelaunch.h
dgl-common.c
examples/dgamelaunch.conf

index 182dfd4282194fc54a4b98eec46122845d5a9345..efb6f050a84fb3d6de7e1b5cc6eef6c4beea1f55 100644 (file)
@@ -16,3 +16,4 @@
 /*.o
 /virus
 /ee
+/*.swp
index b3b78d49efe7fc7466a9d6d958414616ad4accbf..db3ff1cef575a1d29906b80540189d0f0570f65a 100644 (file)
--- a/config.l
+++ b/config.l
@@ -76,6 +76,7 @@ cursor                { return TYPE_CURSOR; }
 "short_name"   { return TYPE_GAME_SHORT_NAME; }
 "game_id"      { return TYPE_GAME_ID; }
 "game_path"    { return TYPE_PATH_GAME; }
+"watch_path"   { return TYPE_PATH_WATCH; }
 "dglroot"      { return TYPE_PATH_DGLDIR; }
 "spooldir"     { return TYPE_PATH_SPOOL; }
 "banner"       { return TYPE_PATH_BANNER; }
@@ -84,6 +85,7 @@ cursor                { return TYPE_CURSOR; }
 "lockfile"     { return TYPE_PATH_LOCKFILE; }
 "inprogressdir" { return TYPE_PATH_INPROGRESS; }
 "game_args"    { return TYPE_GAME_ARGS; }
+"watch_args"   { return TYPE_WATCH_ARGS; }
 extra_info_file { return TYPE_EXTRA_INFO_FILE; }
 "max_idle_time"        { return TYPE_MAX_IDLE_TIME; }
 "rc_fmt"       { return TYPE_RC_FMT; }
@@ -94,6 +96,7 @@ sortmode      { return TYPE_WATCH_SORTMODE; }
 watch_columns  { return TYPE_WATCH_COLUMNS; }
 commands       { return TYPE_CMDQUEUE; }
 postcommands   { return TYPE_POSTCMDQUEUE; }
+watchcommands  { return TYPE_WATCHCMDQUEUE; }
 encoding       { return TYPE_ENCODING; }
 locale         { return TYPE_LOCALE; }
 default_term   { return TYPE_DEFTERM; }
index fd90224b0dd85b2bd0ef00315e3791bc6160a66d..3749178b68538960967105a10d2839318f9a93ce 100644 (file)
--- a/config.y
+++ b/config.y
@@ -67,6 +67,7 @@ static int sortmode_number(const char *sortmode_name) {
 %token <i> TYPE_DGLCMD0 TYPE_DGLCMD1 TYPE_DGLCMD2
 %token TYPE_DEFINE_GAME
 %token <i> TYPE_BOOL
+%token TYPE_PATH_WATCH TYPE_WATCH_ARGS TYPE_WATCHCMDQUEUE
 
 %%
 
@@ -450,11 +451,28 @@ game_definition : TYPE_CMDQUEUE
            myconfig[ncnf]->postcmdqueue = curr_cmdqueue;
            curr_cmdqueue = NULL;
        }
+       | TYPE_WATCHCMDQUEUE
+       {
+           if (myconfig[ncnf]->watchcmdqueue) {
+               fprintf(stderr, "%s:%d: watchcommand queue defined twice, bailing out\n",
+                       config, line);
+               exit(1);
+           }
+       }
+       '=' cmdlist
+       {
+           myconfig[ncnf]->watchcmdqueue = curr_cmdqueue;
+           curr_cmdqueue = NULL;
+       }
 
        | TYPE_GAME_ARGS '=' game_args_list
        {
            /* nothing */
        }
+       | TYPE_WATCH_ARGS '=' watch_args_list
+       {
+           /* nothing */
+       }
        | TYPE_EXTRA_INFO_FILE '=' TYPE_VALUE
        {
             myconfig[ncnf]->extra_info_file = strdup($3);
@@ -486,6 +504,11 @@ game_definition : TYPE_CMDQUEUE
                myconfig[ncnf]->game_path = strdup ($3);
                break;
 
+           case TYPE_PATH_WATCH:
+               if (myconfig[ncnf]->watch_path) free(myconfig[ncnf]->watch_path);
+               myconfig[ncnf]->watch_path = strdup ($3);
+               break;
+
            case TYPE_NAME_GAME:
                if (myconfig[ncnf]->game_name) free (myconfig[ncnf]->game_name);
                myconfig[ncnf]->game_name = strdup($3);
@@ -548,10 +571,32 @@ game_arg : TYPE_VALUE
        }
        ;
 
+watch_arg : TYPE_VALUE
+       {
+           char **tmpargs;
+           if (myconfig[ncnf]->watch_args) {
+               myconfig[ncnf]->num_wargs++;
+               tmpargs = calloc((myconfig[ncnf]->num_wargs+1), sizeof(char *));
+               memcpy(tmpargs, myconfig[ncnf]->watch_args, (myconfig[ncnf]->num_wargs * sizeof(char *)));
+               free(myconfig[ncnf]->watch_args);
+               myconfig[ncnf]->watch_args = tmpargs;
+           } else {
+               myconfig[ncnf]->num_wargs = 1;
+               myconfig[ncnf]->watch_args = calloc(2, sizeof(char *));
+           }
+           myconfig[ncnf]->watch_args[(myconfig[ncnf]->num_wargs)-1] = strdup($1);
+           myconfig[ncnf]->watch_args[(myconfig[ncnf]->num_wargs)] = 0;
+       }
+       ;
+
 game_args_list : game_arg
        | game_arg ',' game_args_list
        ;
 
+watch_args_list : watch_arg
+       | watch_arg ',' watch_args_list
+       ;
+
 game_definitions : game_definition
        | game_definition game_definitions
        ;
@@ -649,6 +694,7 @@ KeyType : TYPE_SUSER        { $$ = TYPE_SUSER; }
        | TYPE_PATH_CHROOT      { $$ = TYPE_PATH_CHROOT; }
        | TYPE_ALLOW_REGISTRATION       { $$ = TYPE_ALLOW_REGISTRATION; }
        | TYPE_PATH_GAME        { $$ = TYPE_PATH_GAME; }
+       | TYPE_PATH_WATCH       { $$ = TYPE_PATH_WATCH; }
         | TYPE_NAME_GAME        { $$ = TYPE_NAME_GAME; }
        | TYPE_GAME_SHORT_NAME  { $$ = TYPE_GAME_SHORT_NAME; }
        | TYPE_GAME_ID  { $$ = TYPE_GAME_ID; }
@@ -685,6 +731,7 @@ const char* lookup_token (int t)
     case TYPE_MENU_MAX_IDLE_TIME: return "menu_max_idle_time";
     case TYPE_PATH_CHROOT: return "chroot_path";
     case TYPE_PATH_GAME: return "game_path";
+    case TYPE_PATH_WATCH: return "watch_path";
     case TYPE_NAME_GAME: return "game_name";
     case TYPE_ALLOW_REGISTRATION: return "allow_new_nicks";
     case TYPE_GAME_SHORT_NAME: return "short_name";
@@ -696,6 +743,7 @@ const char* lookup_token (int t)
     case TYPE_PATH_TTYREC: return "ttyrecdir";
     case TYPE_PATH_INPROGRESS: return "inprogressdir";
     case TYPE_GAME_ARGS: return "game_args";
+    case TYPE_WATCH_ARGS: return "watch_args";
     case TYPE_MAX_IDLE_TIME: return "max_idle_time";
     case TYPE_RC_FMT: return "rc_fmt";
     case TYPE_WATCH_SORTMODE: return "sortmode";
index d361f36039f811dd49b9a446962e7273ae3ceccf..2ab37aea36140e12b30ad1daf5661483b0fe05ae 100644 (file)
@@ -949,6 +949,11 @@ game_get_column_data(struct dg_game *game,
         break;
 
     case SORTMODE_WINDOWSIZE:
+        if (myconfig[game->gamenum]->watch_path) {
+            snprintf(data, bufsz, "N/A");
+            *hilite = CLR_GREEN;
+            break;
+        }
         snprintf(data, bufsz, "%3dx%3d", game->ws_col, game->ws_row);
        if (showplayers)
                snprintf(data, bufsz, "%dx%d", game->ws_col, game->ws_row);
@@ -1039,7 +1044,7 @@ inprogressmenu (int gameid)
 
   shm_init(&shm_dg_data, &shm_dg_game);
 
-  games = populate_games (gameid, &len, NULL); /* FIXME: should be 'me' instead of 'NULL' */
+  games = populate_games (gameid, &len, me);
   shm_update(shm_dg_data, games, len);
   games = sort_games (games, len, sortmode);
 
@@ -1274,7 +1279,49 @@ watchgame:
                  setproctitle("%s [watching %s]", me->username, chosen_name);
              else
                  setproctitle("<Anonymous> [watching %s]", chosen_name);
-              ttyplay_main (ttyrecname, 1, resizex, resizey);
+              if (myconfig[games[idx]->gamenum]->watch_path) {
+                  pid_t child;
+                  int gnum = games[idx]->gamenum;
+                  char **wargs = (char **)malloc(sizeof(char *) * (myconfig[gnum]->num_wargs+1));
+                  dgl_exec_cmdqueue_w(myconfig[gnum]->watchcmdqueue, gnum, me, chosen_name);
+                  /* fix the variables in the arguments */
+                  for (i = 0; i < myconfig[gnum]->num_wargs; i++) {
+                      wargs[i] = strdup(dgl_format_str(gnum, me, myconfig[gnum]->watch_args[i], chosen_name));
+                  }
+                  wargs[myconfig[gnum]->num_wargs] = NULL;
+                  /* tidy up signals before launching external process */
+                  signal(SIGWINCH, SIG_DFL);
+                  signal(SIGINT, SIG_DFL);
+                  signal(SIGQUIT, SIG_DFL);
+                  signal(SIGTERM, SIG_DFL);
+                  idle_alarm_set_enabled(0);
+                  /* launch program */
+                  child = fork();
+                  if (child < 0) {
+                      perror ("fork");
+                      fail ();
+                  }
+                  if (child == 0) {
+                      execvp (myconfig[gnum]->watch_path, wargs);
+                  } else {
+                      int status;
+                      (void) wait(&status);
+                  }
+                  /* reset signals on return */
+                  idle_alarm_set_enabled(1);
+                  signal(SIGHUP, catch_sighup);
+                  signal(SIGINT, catch_sighup);
+                  signal(SIGQUIT, catch_sighup);
+                  signal(SIGTERM, catch_sighup);
+                  signal(SIGWINCH, sigwinch_func);
+                  /* free temporary watch args */
+                  for (i = 0; i < myconfig[gnum]->num_wargs; i++) {
+                      free(wargs[i]);
+                  }
+                  free(wargs);
+              } else {
+                  ttyplay_main (ttyrecname, 1, resizex, resizey);
+              }
              if (loggedin)
                  setproctitle("%s", me->username);
              else
@@ -1302,7 +1349,7 @@ watchgame:
 
       if (selected >= 0 && selected < len)
          selectedgame = strdup(games[selected]->name);
-      games = populate_games (gameid, &len, NULL); /* FIXME: should be 'me' instead of 'NULL' */
+      games = populate_games (gameid, &len, me);
       shm_update(shm_dg_data, games, len);
       games = sort_games (games, len, sortmode);
       if (selectedgame) {
@@ -1345,7 +1392,7 @@ inprogressdisplay (int gameid)
 
   shm_init(&shm_dg_data, &shm_dg_game);
 
-  games = populate_games (gameid, &len, NULL); /* FIXME: should be 'me' instead of 'NULL' */
+  games = populate_games (gameid, &len, me);
   shm_update(shm_dg_data, games, len);
   games = sort_games (games, len, sortmode);
 
index e298adfee42d59c678fde3e94e8f1933e8070e77..c7ba13452c29bef51f7780e9160a802fb8c42a6d 100644 (file)
 #ifdef USE_NCURSES_COLOR
 # define CLR_NORMAL  COLOR_PAIR(11)   | A_NORMAL
 # define CLR_RED     COLOR_PAIR(COLOR_RED)   | A_NORMAL
+# define CLR_GREEN   COLOR_PAIR(COLOR_GREEN) | A_NORMAL
 #else
 # define CLR_NORMAL  0
 # define CLR_RED     0
+# define CLR_GREEN   0
 #endif
 extern int color_remap[];
 
@@ -198,6 +200,7 @@ struct dg_game
 struct dg_config
 {
   char* game_path;
+  char* watch_path;
   char* game_name;
   char* game_id;
   char* shortname;
@@ -206,10 +209,13 @@ struct dg_config
   char* spool;
   char* inprogressdir;
     int num_args; /* # of bin_args */
+    int num_wargs; /* # of watch_args */
     char **bin_args; /* args for game binary */
+    char **watch_args; /* args for watch binary */
     char *rc_fmt;
     struct dg_cmdpart *cmdqueue;
     struct dg_cmdpart *postcmdqueue;
+    struct dg_cmdpart *watchcmdqueue;
     int max_idle_time;
     char *extra_info_file;
     int encoding; // -1 = run --print-charset
@@ -301,6 +307,7 @@ extern void sigwinch_func(int sig);
 extern struct dg_menu *dgl_find_menu(char *menuname);
 
 extern int dgl_exec_cmdqueue(struct dg_cmdpart *queue, int game, struct dg_user *me);
+extern int dgl_exec_cmdqueue_w(struct dg_cmdpart *queue, int game, struct dg_user *me, char *playername);
 
 extern void free_populated_games(struct dg_game **games, int len);
 extern struct dg_game **populate_games(int game, int *l, struct dg_user *me);
index 80560b54eb4d4ba4dcf5bd813fcb08b56ff73faa..e02c00b3e4d67eda4d95ef9ed6563c361306d621 100644 (file)
@@ -24,6 +24,7 @@ extern void (*g_chain_winch)(int);
 struct dg_config **myconfig = NULL;
 struct dg_config defconfig = {
   /* game_path = */ "/bin/nethack",
+  /* watch_path = */ NULL,
   /* game_name = */ "NetHack",
   /* game_id = */ NULL,
   /* shortname = */ "NH",
@@ -32,10 +33,13 @@ struct dg_config defconfig = {
   /* spool = */ "/var/mail/",
   /* inprogressdir = */ "%rinprogress/",
   /* num_args = */ 0,
+  /* num_wargs = */ 0,
   /* bin_args = */ NULL,
+  /* watch_args = */ NULL,
   /* rc_fmt = */ "%rrcfiles/%n.nethackrc", /* [dglroot]rcfiles/[username].nethackrc */
   /* cmdqueue = */ NULL,
   /* postcmdqueue = */ NULL,
+  /* watchcmdqueue = */ NULL,
   /* max_idle_time = */ 0,
   /* extra_info_file = */ NULL,
   /* encoding */ 0
@@ -106,11 +110,14 @@ dgl_find_menu(char *menuname)
 /*
  * replace following codes with variables:
  * %u == shed_uid (number)
+ * %l == logged-in user (string; from 'me'. Empty string if 'me' is null)
  * %n == user name (string; gotten from 'me', or from 'plrname' if 'me' is null)
  * %r == chroot (string)  (aka "dglroot" config var)
  * %g == game name
  * %s == short game name
  * %t == ttyrec file (full path&name) of the last game played.
+ * %w == 'watched' player (string; from 'plrname', or 'me' if 'plrname' is null)
+ * %N, %W, %L (char; first character of their lowercase counterparts)
  */
 char *
 dgl_format_str(int game, struct dg_user *me, char *str, char *plrname)
@@ -124,16 +131,29 @@ dgl_format_str(int game, struct dg_user *me, char *str, char *plrname)
 
     f = str;
     p = buf;
+    *p = '\0';
     end = buf + sizeof(buf) - 10;
 
     while (*f) {
        if (ispercent) {
            switch (*f) {
-           case 'u':
+           case 'u':
                snprintf (p, end + 1 - p, "%d", globalconfig.shed_uid);
                while (*p != '\0')
                    p++;
                break;
+           case 'L':
+               if (me) {
+                    *p = me->username[0];
+                   p++;
+                }
+               *p = '\0';
+               break;
+           case 'l':
+               if (me) snprintf (p, end + 1 - p, "%s", me->username);
+               while (*p != '\0')
+                   p++;
+               break;
            case 'N':
                if (me) *p = me->username[0];
                else if (plrname) *p = plrname[0];
@@ -141,14 +161,14 @@ dgl_format_str(int game, struct dg_user *me, char *str, char *plrname)
                p++;
                *p = '\0';
                break;
-           case 'n':
+           case 'n':
                if (me) snprintf (p, end + 1 - p, "%s", me->username);
                else if (plrname) snprintf(p, end + 1 - p, "%s", plrname);
                else return NULL;
                while (*p != '\0')
                    p++;
                break;
-           case 'g':
+           case 'g':
                if (game >= 0 && game < num_games && myconfig[game]) snprintf (p, end + 1 - p, "%s", myconfig[game]->game_name);
                else return NULL;
                while (*p != '\0')
@@ -170,6 +190,20 @@ dgl_format_str(int game, struct dg_user *me, char *str, char *plrname)
                while (*p != '\0')
                    p++;
                break;
+           case 'W':
+               if (plrname) *p = plrname[0];
+               else if (me) *p = me->username[0];
+               else return NULL;
+               p++;
+               *p = '\0';
+               break;
+           case 'w':
+               if (plrname) snprintf(p, end + 1 - p, "%s", plrname);
+               else if (me) snprintf (p, end + 1 - p, "%s", me->username);
+               else return NULL;
+               while (*p != '\0')
+                   p++;
+               break;
            default:
                *p = *f;
                if (p < end)
@@ -210,7 +244,7 @@ dgl_format_str(int game, struct dg_user *me, char *str, char *plrname)
 }
 
 int
-dgl_exec_cmdqueue(struct dg_cmdpart *queue, int game, struct dg_user *me)
+dgl_exec_cmdqueue_w(struct dg_cmdpart *queue, int game, struct dg_user *me, char *playername)
 {
     int i;
     struct dg_cmdpart *tmp = queue;
@@ -228,8 +262,8 @@ dgl_exec_cmdqueue(struct dg_cmdpart *queue, int game, struct dg_user *me)
     return_from_submenu = 0;
 
     while (tmp && !return_from_submenu) {
-       if (tmp->param1) strcpy(p1, dgl_format_str(game, me, tmp->param1, NULL));
-       if (tmp->param2) strcpy(p2, dgl_format_str(game, me, tmp->param2, NULL));
+       if (tmp->param1) strcpy(p1, dgl_format_str(game, me, tmp->param1, playername));
+       if (tmp->param2) strcpy(p2, dgl_format_str(game, me, tmp->param2, playername));
 
        switch (tmp->cmd) {
        default: break;
@@ -422,6 +456,11 @@ dgl_exec_cmdqueue(struct dg_cmdpart *queue, int game, struct dg_user *me)
 }
 
 
+int
+dgl_exec_cmdqueue(struct dg_cmdpart *queue, int game, struct dg_user *me)
+{
+    return dgl_exec_cmdqueue_w(queue, game, me, NULL);
+}
 
 static int
 sort_game_username(const void *g1, const void *g2)
@@ -667,7 +706,7 @@ populate_games (int xgame, int *l, struct dg_user *me)
              }
               replacestr++;
 
-             ttrecdir = dgl_format_str(game, me, myconfig[game]->ttyrecdir, playername);
+             ttrecdir = dgl_format_str(game, NULL, myconfig[game]->ttyrecdir, playername);
              if (!ttrecdir) continue;
               snprintf (ttyrecname, 130, "%s%s", ttrecdir, replacestr);
 
index 2a7917544842d0c070dc5578014d9a5b50bd64e4..b4276e303884984562071b415f6acc7c2cf691f0 100644 (file)
@@ -153,8 +153,10 @@ default_term = "xterm"
 # defined above.
 # Parameters to the commands are subject to variable substitution:
 #   %r = dglroot, as defined above
-#   %n = user nick, if user is logged in
-#   %N = first character of user name, if user is logged in
+#   %n = user nick, if user is logged in, or user being watched.
+#   %l = user nick, if logged in, otherwise empty string.
+#   %w = user selected from the watch menu (or logged in user if no user selected)
+#   %N, %L, %W = first character of the above
 #   %u = shed_uid, as defined above, but numeric
 #   %g = game name, if user has selected a game.
 #   %s = short game name, if user has selected a game.
@@ -310,6 +312,59 @@ menu["watchmenu_help"] {
 #  encoding = "unicode"
 #}
 
+#
+# fiqhack example - showing watch mode options
+#
+#
+
+#DEFINE {
+#  game_path = "/fiqhackdir/fiqhack"
+#  game_name = "FIQHack 4.3.0"
+#  short_name = "fiqhack"
+#
+#  game_args = "/fiqhackdir/fiqhack",
+#              "-H", "/fiqhackdir/data",
+#              "-U", "%ruserdata/%N/%n/fiqhack"
+#
+#  spooldir = "/mail/"
+#  #rc_template = "/dgl-default-rcfile.gh"
+#
+#  #rc_fmt = "%ruserdata/%N/%n/%n.ghrc"
+#
+#  inprogressdir = "%rinprogress-fh/"
+#  extra_info_file = "%rextrainfo-fh/%n.extrainfo"
+#
+#  # The place where ttyrecs are stored for this game.
+#  # If this is not defined, ttyrecs are not saved for this game.
+#  # ttyrecs are not necessary for spectating if watch_path is configured.
+#  ttyrecdir = "%ruserdata/%N/%n/ttyrec/"
+#
+#
+#  commands = setenv "NH4SERVERUSER" "%n",
+#             setenv "NHMAILBOX" "/mail/%n",
+#             setenv "SIMPLEMAIL" "1",
+#             unlink "/mail/%n"
+#
+#  # The executable to use for watching instead of the built-in ttyrec player
+#  watch_path = "/fiqhackdir/fiqhack"
+#
+#  # The args to call it with. As with game_args, argv[0] needs to be included
+#  watch_args = "/fiqhackdir/fiqhack",
+#               "-H", "/fiqhackdir/data",
+#               # we use %n here so anonymous viewers can inherit the config
+#               # of the player. The watch program needs to ensure the watcher
+#               # can't modify anything in here that would affect the player.
+#               "-U", "%ruserdata/%N/%n/fiqhack",
+#               # %w is the player to watch
+#               "-W", "%ruserdata/%W/%w/fiqhack"
+#
+#  # these are run before watching commences.
+#  # Use %l so the watching program knows if the watcher is logged in.
+#  # The watching program should disable mail for anonymous watchers.
+#  watchcommands = setenv "NH4SERVERUSER" "%w",
+#                  setenv "NH4WATCHER" "%l",
+#                  setenv "NHMAILBOX" "/mail/%w"
+#}
 
 #
 # the second game