From 9c3192a0201098ddbe08e278d61f5d97d165d2d6 Mon Sep 17 00:00:00 2001 From: Frank LENORMAND Date: Fri, 30 Sep 2016 19:45:06 +0300 Subject: [PATCH 1/8] Make the `handle_cmdfifo` function more readable --- dvtm.c | 158 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 79 insertions(+), 79 deletions(-) diff --git a/dvtm.c b/dvtm.c index 43946ad..540595f 100644 --- a/dvtm.c +++ b/dvtm.c @@ -1443,90 +1443,90 @@ handle_cmdfifo(void) { int r; char *p, *s, cmdbuf[512], c; Cmd *cmd; - switch (r = read(cmdfifo.fd, cmdbuf, sizeof cmdbuf - 1)) { - case -1: - case 0: + + r = read(cmdfifo.fd, cmdbuf, sizeof cmdbuf - 1); + if (r <= 0) { cmdfifo.fd = -1; - break; - default: - cmdbuf[r] = '\0'; - p = cmdbuf; - while (*p) { - /* find the command name */ - for (; *p == ' ' || *p == '\n'; p++); - for (s = p; *p && *p != ' ' && *p != '\n'; p++); - if ((c = *p)) - *p++ = '\0'; - if (*s && (cmd = get_cmd_by_name(s)) != NULL) { - bool quote = false; - int argc = 0; - const char *args[MAX_ARGS], *arg; - memset(args, 0, sizeof(args)); - /* if arguments were specified in config.h ignore the one given via - * the named pipe and thus skip everything until we find a new line - */ - if (cmd->action.args[0] || c == '\n') { - debug("execute %s", s); - cmd->action.cmd(cmd->action.args); - while (*p && *p != '\n') - p++; - continue; - } - /* no arguments were given in config.h so we parse the command line */ - while (*p == ' ') + return; + } + + cmdbuf[r] = '\0'; + p = cmdbuf; + while (*p) { + /* find the command name */ + for (; *p == ' ' || *p == '\n'; p++); + for (s = p; *p && *p != ' ' && *p != '\n'; p++); + if ((c = *p)) + *p++ = '\0'; + if (*s && (cmd = get_cmd_by_name(s)) != NULL) { + bool quote = false; + int argc = 0; + const char *args[MAX_ARGS], *arg; + memset(args, 0, sizeof(args)); + /* if arguments were specified in config.h ignore the one given via + * the named pipe and thus skip everything until we find a new line + */ + if (cmd->action.args[0] || c == '\n') { + debug("execute %s", s); + cmd->action.cmd(cmd->action.args); + while (*p && *p != '\n') p++; - arg = p; - for (; (c = *p); p++) { - switch (*p) { - case '\\': - /* remove the escape character '\\' move every - * following character to the left by one position - */ - switch (p[1]) { - case '\\': - case '\'': - case '\"': { - char *t = p+1; - do { - t[-1] = *t; - } while (*t++); - } - } - break; - case '\'': - case '\"': - quote = !quote; - break; - case ' ': - if (!quote) { - case '\n': - /* remove trailing quote if there is one */ - if (*(p - 1) == '\'' || *(p - 1) == '\"') - *(p - 1) = '\0'; - *p++ = '\0'; - /* remove leading quote if there is one */ - if (*arg == '\'' || *arg == '\"') - arg++; - if (argc < MAX_ARGS) - args[argc++] = arg; - - while (*p == ' ') - ++p; - arg = p--; + continue; + } + /* no arguments were given in config.h so we parse the command line */ + while (*p == ' ') + p++; + arg = p; + for (; (c = *p); p++) { + switch (*p) { + case '\\': + /* remove the escape character '\\' move every + * following character to the left by one position + */ + switch (p[1]) { + case '\\': + case '\'': + case '\"': { + char *t = p+1; + do { + t[-1] = *t; + } while (*t++); } - break; } - - if (c == '\n' || *p == '\n') { - if (!*p) - p++; - debug("execute %s", s); - for(int i = 0; i < argc; i++) - debug(" %s", args[i]); - debug("\n"); - cmd->action.cmd(args); - break; + break; + case '\'': + case '\"': + quote = !quote; + break; + case ' ': + if (!quote) { + case '\n': + /* remove trailing quote if there is one */ + if (*(p - 1) == '\'' || *(p - 1) == '\"') + *(p - 1) = '\0'; + *p++ = '\0'; + /* remove leading quote if there is one */ + if (*arg == '\'' || *arg == '\"') + arg++; + if (argc < MAX_ARGS) + args[argc++] = arg; + + while (*p == ' ') + ++p; + arg = p--; } + break; + } + + if (c == '\n' || *p == '\n') { + if (!*p) + p++; + debug("execute %s", s); + for(int i = 0; i < argc; i++) + debug(" %s", args[i]); + debug("\n"); + cmd->action.cmd(args); + break; } } } From 5870e0bdc31952267519a8e09be59fb94fa39d82 Mon Sep 17 00:00:00 2001 From: Frank LENORMAND Date: Sat, 1 Oct 2016 16:28:56 +0300 Subject: [PATCH 2/8] Allow focusing windows through the command FIFO This commit adds a `focus` command to the list of supported commands available through the external FIFO, which switches focus to the window whose id is equal to the one passed as parameter. --- config.def.h | 1 + dvtm.c | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/config.def.h b/config.def.h index 643a281..b860d79 100644 --- a/config.def.h +++ b/config.def.h @@ -189,6 +189,7 @@ static Button buttons[] = { static Cmd commands[] = { { "create", { create, { NULL } } }, + { "focus", { focusid, { NULL } } }, }; /* gets executed when dvtm is started */ diff --git a/dvtm.c b/dvtm.c index 540595f..9f0c795 100644 --- a/dvtm.c +++ b/dvtm.c @@ -179,6 +179,7 @@ typedef struct { static void create(const char *args[]); static void copymode(const char *args[]); static void focusn(const char *args[]); +static void focusid(const char *args[]); static void focusnext(const char *args[]); static void focusnextnm(const char *args[]); static void focusprev(const char *args[]); @@ -1125,6 +1126,22 @@ focusn(const char *args[]) { } } +static void +focusid(const char *args[]) { + if (!args[0]) + return; + + const int win_id = atoi(args[0]); + for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) { + if (c->id == win_id) { + focus(c); + if (c->minimized) + toggleminimize(NULL); + return; + } + } +} + static void focusnext(const char *args[]) { Client *c; From 10e4db210f33661a429a7ce6d0f1f721ffc244f1 Mon Sep 17 00:00:00 2001 From: Frank LENORMAND Date: Sat, 1 Oct 2016 22:19:33 +0300 Subject: [PATCH 3/8] Allow window tagging through the commands FIFO Introduce a `tag` command that allows assigning a tag to a visible window through the commands FIFO. This commit also fixes the `bitoftag` function which compared pointers instead of the actual characters of the strings containing the tags. --- config.def.h | 3 ++- dvtm.c | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/config.def.h b/config.def.h index b860d79..aa68ffe 100644 --- a/config.def.h +++ b/config.def.h @@ -189,7 +189,8 @@ static Button buttons[] = { static Cmd commands[] = { { "create", { create, { NULL } } }, - { "focus", { focusid, { NULL } } }, + { "focus", { focusid, { NULL } } }, + { "tag", { tagid, { NULL } } }, }; /* gets executed when dvtm is started */ diff --git a/dvtm.c b/dvtm.c index 9f0c795..6dd6287 100644 --- a/dvtm.c +++ b/dvtm.c @@ -196,6 +196,7 @@ static void incnmaster(const char *args[]); static void setmfact(const char *args[]); static void startup(const char *args[]); static void tag(const char *args[]); +static void tagid(const char *args[]); static void togglebar(const char *args[]); static void togglebarpos(const char *args[]); static void toggleminimize(const char *args[]); @@ -769,7 +770,7 @@ keybinding(KeyCombo keys, unsigned int keycount) { static unsigned int bitoftag(const char *tag) { unsigned int i; - for (i = 0; (i < LENGTH(tags)) && (tags[i] != tag); i++); + for (i = 0; (i < LENGTH(tags)) && (*tags[i] != *tag); i++); return (i < LENGTH(tags)) ? (1 << i) : ~0; } @@ -797,6 +798,21 @@ tag(const char *args[]) { tagschanged(); } +static void +tagid(const char *args[]) { + if (!args[0] || !args[1] || !atoi(args[1])) + return; + + const int win_id = atoi(args[0]); + for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) { + if (c->id == win_id) { + c->tags = bitoftag(args[1]) & TAGMASK; + tagschanged(); + return; + } + } +} + static void toggletag(const char *args[]) { if (!sel) From d6fc319cc2bc6da832fa0330420b84b08bfa589c Mon Sep 17 00:00:00 2001 From: Frank LENORMAND Date: Sun, 2 Oct 2016 16:17:10 +0300 Subject: [PATCH 4/8] Compare full tag names, and assign them to not only visible windows Since tags can be set to strings longer than the default one byte long digits, we need to compare the whole strings and not only the first character. Assigning a tag should not depend on the visibility of a window, so we loop over all the clients (as opposed to only the visible ones). --- dvtm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dvtm.c b/dvtm.c index 6dd6287..0fef193 100644 --- a/dvtm.c +++ b/dvtm.c @@ -770,7 +770,7 @@ keybinding(KeyCombo keys, unsigned int keycount) { static unsigned int bitoftag(const char *tag) { unsigned int i; - for (i = 0; (i < LENGTH(tags)) && (*tags[i] != *tag); i++); + for (i = 0; (i < LENGTH(tags)) && strcmp(tags[i], tag); i++); return (i < LENGTH(tags)) ? (1 << i) : ~0; } @@ -804,7 +804,7 @@ tagid(const char *args[]) { return; const int win_id = atoi(args[0]); - for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) { + for (Client *c = clients; c; c = c->next) { if (c->id == win_id) { c->tags = bitoftag(args[1]) & TAGMASK; tagschanged(); From 785017b1256a123aefa242441cfc4c858a3f406d Mon Sep 17 00:00:00 2001 From: Frank LENORMAND Date: Sun, 2 Oct 2016 16:23:24 +0300 Subject: [PATCH 5/8] Add a short documentation of the FIFO commands --- config.def.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config.def.h b/config.def.h index aa68ffe..93aa766 100644 --- a/config.def.h +++ b/config.def.h @@ -188,8 +188,11 @@ static Button buttons[] = { #endif /* CONFIG_MOUSE */ static Cmd commands[] = { + /* create [ cmd ]: create a new window, run `cmd` in the shell if specified */ { "create", { create, { NULL } } }, + /* focus : focus the visible window whose `DVTM_WINDOW_ID` is `win_id` */ { "focus", { focusid, { NULL } } }, + /* tag : assign the given `tag` to the window whose `DVTM_WINDOW_ID` is `win_id` */ { "tag", { tagid, { NULL } } }, }; From ca2df77446856a5c3da1d923ac52783f3faece5c Mon Sep 17 00:00:00 2001 From: Frank LENORMAND Date: Wed, 5 Oct 2016 10:34:01 +0300 Subject: [PATCH 6/8] Fix several issues with remote commands and their documentation This commit updates the documentation of the `create`, `focus` and `tag` commands in the default configuration file; the `tagid` function no longer expects tags to be integers and only appends tags instead of replacing them; the `focusid` function allows focusing non-visible windows. --- config.def.h | 6 +++--- dvtm.c | 13 ++++++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/config.def.h b/config.def.h index 93aa766..718aaf1 100644 --- a/config.def.h +++ b/config.def.h @@ -188,11 +188,11 @@ static Button buttons[] = { #endif /* CONFIG_MOUSE */ static Cmd commands[] = { - /* create [ cmd ]: create a new window, run `cmd` in the shell if specified */ + /* create [cmd]: create a new window, run `cmd` in the shell if specified */ { "create", { create, { NULL } } }, - /* focus : focus the visible window whose `DVTM_WINDOW_ID` is `win_id` */ + /* focus : focus the window whose `DVTM_WINDOW_ID` is `win_id` */ { "focus", { focusid, { NULL } } }, - /* tag : assign the given `tag` to the window whose `DVTM_WINDOW_ID` is `win_id` */ + /* tag [tag ...]: replace the tags of the window with the given identifier */ { "tag", { tagid, { NULL } } }, }; diff --git a/dvtm.c b/dvtm.c index 0fef193..4c59544 100644 --- a/dvtm.c +++ b/dvtm.c @@ -800,13 +800,16 @@ tag(const char *args[]) { static void tagid(const char *args[]) { - if (!args[0] || !args[1] || !atoi(args[1])) + if (!args[0] || !args[1]) return; const int win_id = atoi(args[0]); for (Client *c = clients; c; c = c->next) { if (c->id == win_id) { - c->tags = bitoftag(args[1]) & TAGMASK; + unsigned int i; + c->tags = 0; + for (i = 1; i < MAX_ARGS && args[i]; i++) + c->tags |= bitoftag(args[i]) & TAGMASK; tagschanged(); return; } @@ -1148,11 +1151,15 @@ focusid(const char *args[]) { return; const int win_id = atoi(args[0]); - for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) { + for (Client *c = clients; c; c = c->next) { if (c->id == win_id) { focus(c); if (c->minimized) toggleminimize(NULL); + if (!isvisible(c)) { + c->tags |= tagset[seltags]; + tagschanged(); + } return; } } From 6a748d8999448af160490999c7bc371645ac7a82 Mon Sep 17 00:00:00 2001 From: Frank LENORMAND Date: Wed, 5 Oct 2016 10:36:54 +0300 Subject: [PATCH 7/8] Add an `untag` command available through the command FIFO THe `untagid` function the implements the `untag` command removes the given tags from the ones assigned to a window. --- config.def.h | 2 ++ dvtm.c | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/config.def.h b/config.def.h index 718aaf1..51e4003 100644 --- a/config.def.h +++ b/config.def.h @@ -194,6 +194,8 @@ static Cmd commands[] = { { "focus", { focusid, { NULL } } }, /* tag [tag ...]: replace the tags of the window with the given identifier */ { "tag", { tagid, { NULL } } }, + /* untag [tag ...]: remove the tags of the window with the given identifier */ + { "untag", { untagid, { NULL } } }, }; /* gets executed when dvtm is started */ diff --git a/dvtm.c b/dvtm.c index 4c59544..ac4e062 100644 --- a/dvtm.c +++ b/dvtm.c @@ -197,6 +197,7 @@ static void setmfact(const char *args[]); static void startup(const char *args[]); static void tag(const char *args[]); static void tagid(const char *args[]); +static void untagid(const char *args[]); static void togglebar(const char *args[]); static void togglebarpos(const char *args[]); static void toggleminimize(const char *args[]); @@ -816,6 +817,23 @@ tagid(const char *args[]) { } } +static void +untagid(const char *args[]) { + if (!args[0] || !args[1]) + return; + + const int win_id = atoi(args[0]); + for (Client *c = clients; c; c = c->next) { + if (c->id == win_id) { + unsigned int i; + for (i = 1; i < MAX_ARGS && args[i]; i++) + c->tags &= ~(bitoftag(args[i]) & TAGMASK); + tagschanged(); + return; + } + } +} + static void toggletag(const char *args[]) { if (!sel) From 131529f191f5ec028e98db0c63c5e72d0b568474 Mon Sep 17 00:00:00 2001 From: Frank LENORMAND Date: Wed, 5 Oct 2016 10:39:02 +0300 Subject: [PATCH 8/8] Add a remote interaction utility script and its man page Introduce the `dvtm-cmd` wrapper that allows interacting with `dvtm` through its command FIFO easily. A man page that documents its features was also added, and they are both installed by default. --- Makefile | 12 ++++++-- dvtm-cmd | 74 ++++++++++++++++++++++++++++++++++++++++++++++ dvtm-cmd.1 | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+), 3 deletions(-) create mode 100755 dvtm-cmd create mode 100644 dvtm-cmd.1 diff --git a/Makefile b/Makefile index 09d1080..41f1ed2 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,7 @@ dist: clean @cp -R LICENSE Makefile README.md testsuite.sh config.def.h config.mk \ ${SRC} vt.h forkpty-aix.c forkpty-sunos.c tile.c bstack.c \ tstack.c vstack.c grid.c fullscreen.c fibonacci.c \ - dvtm-status dvtm.info dvtm.1 dvtm-${VERSION} + dvtm-status dvtm-cmd dvtm.info dvtm-cmd.1 dvtm.1 dvtm-${VERSION} @tar -cf dvtm-${VERSION}.tar dvtm-${VERSION} @gzip dvtm-${VERSION}.tar @rm -rf dvtm-${VERSION} @@ -51,10 +51,14 @@ install: dvtm @chmod 755 ${DESTDIR}${PREFIX}/bin/dvtm @cp -f dvtm-status ${DESTDIR}${PREFIX}/bin @chmod 755 ${DESTDIR}${PREFIX}/bin/dvtm-status - @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1 + @cp -f dvtm-cmd ${DESTDIR}${PREFIX}/bin + @chmod 755 ${DESTDIR}${PREFIX}/bin/dvtm-cmd + @echo installing manual pages to ${DESTDIR}${MANPREFIX}/man1 @mkdir -p ${DESTDIR}${MANPREFIX}/man1 @sed "s/VERSION/${VERSION}/g" < dvtm.1 > ${DESTDIR}${MANPREFIX}/man1/dvtm.1 @chmod 644 ${DESTDIR}${MANPREFIX}/man1/dvtm.1 + @cp -f dvtm-cmd.1 ${DESTDIR}${MANPREFIX}/man1/dvtm-cmd.1 + @chmod 644 ${DESTDIR}${MANPREFIX}/man1/dvtm-cmd.1 @echo installing terminfo description @TERMINFO=${TERMINFO} tic -s dvtm.info @@ -62,7 +66,9 @@ uninstall: @echo removing executable file from ${DESTDIR}${PREFIX}/bin @rm -f ${DESTDIR}${PREFIX}/bin/dvtm @rm -f ${DESTDIR}${PREFIX}/bin/dvtm-status - @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 + @rm -f ${DESTDIR}${PREFIX}/bin/dvtm-cmd + @echo removing manual pages from ${DESTDIR}${MANPREFIX}/man1 @rm -f ${DESTDIR}${MANPREFIX}/man1/dvtm.1 + @rm -f ${DESTDIR}${MANPREFIX}/man1/dvtm-cmd.1 .PHONY: all options clean dist install uninstall debug diff --git a/dvtm-cmd b/dvtm-cmd new file mode 100755 index 0000000..917394e --- /dev/null +++ b/dvtm-cmd @@ -0,0 +1,74 @@ +#!/bin/sh + +DVTM_CMD_FIFO='/tmp/dvtm.fifo' + +# Print a message on the standard error and quit the script +fatal() { + printf %s\\n "$@" 1>&2 + exit 1 +} + +# Print a usage string and exit the script +usage() { + printf %s\\n "Usage: $0 [-h] [ -c ] command [ args ... ]" + echo + echo "The following commands are supported, refer to the dvtm-cmd(1) manpage for more information:" + printf ' - %s %s\n' "create [cmd] " "Create a new client and optionally run a command in it" + printf ' - %s %s\n' "focus " "Focus the given window" + printf ' - %s %s\n' "tag ... " "Assign tags to the given window" + printf ' - %s %s\n' "untag ..." "Remove tags from the given window" + exit +} + +# Send a command to the command FIFO +send_command() { + printf %s\\n "$*" > "${DVTM_CMD_FIFO}" +} + +main() { + ## Get CLI option values + while getopts hc: o; do + case $o in + c) DVTM_CMD_FIFO="${OPTARG}";; + ?) usage;; + esac + done + shift $((OPTIND - 1)) + + ## Check the arguments passed + if [ $# -lt 1 ]; then + fatal "No command passed" + elif [ ! -p "${DVTM_CMD_FIFO}" ]; then + fatal "No such named pipe: ${DVTM_CMD_FIFO}" + fi + + ## Execute the command + command="$1"; shift + case "${command}" in + create) + if [ $# -ge 1 ]; then + send_command "create" "'$*'" + else + send_command "create" + fi + ;; + focus) + if [ $# -eq 1 ]; then + send_command "${command}" "$1" + else + fatal "Not enough arguments (expected 1)" + fi + ;; + untag|tag) + if [ $# -ge 2 ]; then + win_id="$1"; shift + send_command "${command}" "${win_id}" "$@" + else + fatal "Not enough arguments (expected at least 2)" + fi + ;; + *) fatal "Invalid command: ${command}";; + esac +} + +main "$@" diff --git a/dvtm-cmd.1 b/dvtm-cmd.1 new file mode 100644 index 0000000..96818df --- /dev/null +++ b/dvtm-cmd.1 @@ -0,0 +1,86 @@ +'\" t +.\" Title: dvtm-cmd +.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author] +.\" Generator: DocBook XSL Stylesheets v1.79.1 +.\" Date: 10/04/2016 +.\" Manual: \ \& +.\" Source: \ \& +.\" Language: English +.\" +.TH "DVTM\-CMD" "1" "10/04/2016" "\ \&" "\ \&" +.\" ----------------------------------------------------------------- +.\" * Define some portability stuff +.\" ----------------------------------------------------------------- +.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.\" http://bugs.debian.org/507673 +.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html +.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.\" ----------------------------------------------------------------- +.\" * set default formatting +.\" ----------------------------------------------------------------- +.\" disable hyphenation +.nh +.\" disable justification (adjust text to left margin only) +.ad l +.\" ----------------------------------------------------------------- +.\" * MAIN CONTENT STARTS HERE * +.\" ----------------------------------------------------------------- +.SH "NAME" +dvtm-cmd \- an external wrapper script to interact with dvtm +.SH "SYNOPSIS" +.sp +dvtm\-cmd [\-h] [\-c cmd_fifo] command [args \&...] +.SH "DESCRIPTION" +.sp +The \fBdvtm\-cmd\fR utility is a shell script that allows interacting with and controlling the \fBdvtm\fR terminal multiplexer\&. The commands are formatted in a format understand by \fBdvtm\fR and sent over the \fBcmd_fifo\fR file\&. +.SH "ENVIRONMENT" +.sp +New windows created by \fBdvtm\fR automatically spawn a shell (unless a command has been passed, c\&.f\&. the \fBCOMMANDS\fR section of this document), which will contain the following variables: +.PP +\fBDVTM\fR (version) +.RS 4 +version of the currently running +\fBdvtm\fR +session +.RE +.PP +\fBDVTM_WINDOW_ID\fR (integer) +.RS 4 +identifier of the current window +.RE +.PP +\fBDVTM_CMD_FIFO\fR (file path) +.RS 4 +path to the command FIFO that was passed to the currently running +\fBdvtm\fR +session\&. If no such path was passed when +\fBdvtm\fR +was started, this variable is undefined +.RE +.SH "COMMANDS" +.sp +Due to technical limitations, the maximum amount of arguments a command can take is limited to \fI4\fR\&. +.PP +\fBcreate\fR [command] +.RS 4 +create a new window in the current session, and pass +\fIcommand\fR +to the shell\(cqs +\fI\-c\fR +flag if specified +.RE +.PP +\fBfocus\fR +.RS 4 +focus the window whose identifier is +\fIwindow_id\fR +.RE +.PP +\fBtag\fR \&... +.RS 4 +assign the given tags to the window whose identifier is +\fIwindow_id\fR +(the tags previously assigned will be overwritten) +.RE