Adding a new command to tmux
2023-01-04 00:00:00 +0000 UTCThis continues the work done in my last post on tmux’s objected-oriented commands in C.
To demonstrate the function of the command system, this post will walk you through adding a new “echo” command that prints its argument out to the screen (or stdout if invoked from the command line).
The diff for this work can be viewed on Github in this PR.
Step 1: Create File cmd-echo.c
This file will include the cmd_entry
instance and exec function.
static enum cmd_retval cmd_echo_exec(struct cmd *, struct cmdq_item *);
const struct cmd_entry cmd_echo_entry = {
.name = "echo",
.alias = NULL,
.args = { "", 1, 1, NULL },
.usage = "[message-to-echo]",
.flags = 0,
.exec = cmd_echo_exec
};
Note in the descriptor we set the alias to NULL
since we won’t have a shorter version of the word echo
. We can also elide the target
field sicne the command is untargeted. Finally, we set the lower and upper bounds in the .args
field each to 1 since we must have exactly one argument – this argument is the message-to-echo
mentioned in the usage string. Note finally we have an empty template in the args
member as well, since the command accepts no flags currently.
Next we need to write the exec
function. This function pulls context from the struct cmd *
and struct cmdq_item *
to achieve its objective.
static enum cmd_retval cmd_echo_exec(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
const char *s = args_string(args, 0);
if(s == NULL)
return (CMD_RETURN_ERROR);
cmdq_print(item, "%s", s);
return (CMD_RETURN_NORMAL);
}
First, we need to access the arguments of the command. For that use, we use the accessor function cmd_get_args
on our struct cmd *self
. This returns a value of the listed type struct args *
.
Next we can use the API for the struct args
type to extract the string value of an argument at a particular index: args_string
. This function returns a char*
. It returns NULL
when we have no argument present at that index, so we need to account for that case and return an error if no string is available to echo (even though this case should never occur due to the lower and upper bounds on argument count mentioned before).
Finally, this command uses the cmdq_print
API to display the message. This API extracts a struct client
from the struct cmdq_item
and writes the argument specified by the format string & variadic arguments to an appropriate location based on the command’s invocation.
Step 2: declare the cmd_echo_entry
as extern in cmd.c
`
To let the linker know to look for our cmd_echo_entry
struct definition outside of cmd.c
, we need to add an extern
d declaration:
/* ... omitted ... */
extern const struct cmd_entry cmd_down_pane_entry;
extern const struct cmd_entry cmd_echo_entry;
extern const struct cmd_entry cmd_find_window_entry;
/* ... omitted ... */
Step 3: add a reference to the cmd_echo_entry
in cmd_table[]
in cmd.c
Also in cmd.c
, we need to add a reference to our command descriptor cmd_echo_entry
to the cmd_table
:
const struct cmd_entry *cmd_table[] = {
/* ... omitted ... */
&cmd_display_panes_entry,
&cmd_echo_entry,
&cmd_find_window_entry,
/* ... omitted ... */
};
This allows the function cmd_find
to find our command by name or alias, if provided. cmd_find
is called by cmd_parse
, which constructs the struct cmd
that will eventually be passed back into our exec
function.
Step 4: add file cmd-echo.c
to Makefile.am
to register file with Automake
By adding cmd-echo.c
to the dis_tmux_SOURCES
variable in Makefile.am
, automake
will know to compile cmd-echo.c
so that the extern
d declaration in cmd.c
actually points to something!
# List of sources.
dist_tmux_SOURCES = \
# ... omitted ...
cmd-display-panes.c \
cmd-echo.c \
cmd-find-window.c \
# ... omitted ...
And that’s it! We can now compile with the usual tmux compilation workflow:
$ sh autogen.sh
$ ./configure && make
Tie it together: Using our command
Having started a new tmux server and client using our freshly built tmux, we can run the command from the shell:
$ ./tmux echo "Hey! This is the echo command"
Hey! This is the echo command
We can also run it from within a tmux session by entering the keystrokes Tmux-Prefix :echo "The string to echo is here!"
(the tmux prefix being C-b
by default):
The string to echo is here! [0/0]
[0] 0:[tmux]* 1:bash- "devenv" 07:51 04-Jan-23