From 2c92d7b3d17321200d5d9abfe3646767c72390a5 Mon Sep 17 00:00:00 2001 From: Tangles Date: Wed, 3 Jan 2018 12:28:44 +1100 Subject: [PATCH 1/2] Enhanced watchmode 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 | 1 + config.l | 3 ++ config.y | 48 +++++++++++++++++++++++++++++++ dgamelaunch.c | 55 +++++++++++++++++++++++++++++++++--- dgamelaunch.h | 7 +++++ dgl-common.c | 53 ++++++++++++++++++++++++++++++----- examples/dgamelaunch.conf | 59 +++++++++++++++++++++++++++++++++++++-- 7 files changed, 213 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 182dfd4..efb6f05 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ /*.o /virus /ee +/*.swp diff --git a/config.l b/config.l index b3b78d4..db3ff1c 100644 --- 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; } diff --git a/config.y b/config.y index fd90224..3749178 100644 --- a/config.y +++ b/config.y @@ -67,6 +67,7 @@ static int sortmode_number(const char *sortmode_name) { %token TYPE_DGLCMD0 TYPE_DGLCMD1 TYPE_DGLCMD2 %token TYPE_DEFINE_GAME %token 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"; diff --git a/dgamelaunch.c b/dgamelaunch.c index d361f36..2ab37ae 100644 --- a/dgamelaunch.c +++ b/dgamelaunch.c @@ -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 @@ inprogressmenu (int gameid) setproctitle("%s [watching %s]", me->username, chosen_name); else setproctitle(" [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 @@ inprogressmenu (int gameid) 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); diff --git a/dgamelaunch.h b/dgamelaunch.h index e298adf..c7ba134 100644 --- a/dgamelaunch.h +++ b/dgamelaunch.h @@ -30,9 +30,11 @@ #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); diff --git a/dgl-common.c b/dgl-common.c index 80560b5..e02c00b 100644 --- a/dgl-common.c +++ b/dgl-common.c @@ -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); diff --git a/examples/dgamelaunch.conf b/examples/dgamelaunch.conf index 2a79175..b4276e3 100644 --- a/examples/dgamelaunch.conf +++ b/examples/dgamelaunch.conf @@ -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 From 7ce95151d22d816c2ea7aad18921318da132e94f Mon Sep 17 00:00:00 2001 From: Tangles Date: Wed, 3 Jan 2018 14:58:49 +1100 Subject: [PATCH 2/2] Alignment of 'N/A' terminal size for enhanced watchmode games. --- dgamelaunch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dgamelaunch.c b/dgamelaunch.c index 2ab37ae..0595840 100644 --- a/dgamelaunch.c +++ b/dgamelaunch.c @@ -950,7 +950,7 @@ game_get_column_data(struct dg_game *game, case SORTMODE_WINDOWSIZE: if (myconfig[game->gamenum]->watch_path) { - snprintf(data, bufsz, "N/A"); + snprintf(data, bufsz, " N/A"); *hilite = CLR_GREEN; break; }