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..0595840 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