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/config.def.h b/config.def.h index 643a281..51e4003 100644 --- a/config.def.h +++ b/config.def.h @@ -188,7 +188,14 @@ 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 window whose `DVTM_WINDOW_ID` is `win_id` */ + { "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-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 diff --git a/dvtm.c b/dvtm.c index 43946ad..ac4e062 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[]); @@ -195,6 +196,8 @@ 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 untagid(const char *args[]); static void togglebar(const char *args[]); static void togglebarpos(const char *args[]); static void toggleminimize(const char *args[]); @@ -768,7 +771,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; } @@ -796,6 +799,41 @@ tag(const char *args[]) { tagschanged(); } +static void +tagid(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; + c->tags = 0; + for (i = 1; i < MAX_ARGS && args[i]; i++) + c->tags |= bitoftag(args[i]) & TAGMASK; + tagschanged(); + return; + } + } +} + +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) @@ -1125,6 +1163,26 @@ focusn(const char *args[]) { } } +static void +focusid(const char *args[]) { + if (!args[0]) + return; + + const int win_id = atoi(args[0]); + 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; + } + } +} + static void focusnext(const char *args[]) { Client *c; @@ -1443,90 +1501,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; } } }