/*
 * Copyright (C) 2000, 2001, 2002, 2003, 2004 Shawn Betts <sabetts@vcn.bc.ca>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA.
 */

#include <unistd.h>
#include <ctype.h>
#include <sys/wait.h>
#include <X11/keysym.h>
#include <string.h>
#include <strings.h>
#include <err.h>
#include <errno.h>
#include <signal.h>
#include <limits.h>
#include <X11/Xproto.h>
#include <X11/extensions/XTest.h>
#include <sys/ioctl.h>

#include "sdorfehs.h"

/* arg_REST and arg_SHELLCMD eat the rest of the input. */
enum argtype {
	arg_REST,
	arg_NUMBER,
	arg_STRING,
	arg_FRAME,
	arg_WINDOW,
	arg_COMMAND,
	arg_SHELLCMD,
	arg_KEYMAP,
	arg_KEY,
	arg_GRAVITY,
	arg_VSCREEN,
	arg_HOOK,
	arg_VARIABLE,
	arg_RAW,
};

union arg_union {
	rp_frame *frame;
	int number;
	float fnumber;
	rp_window *win;
	rp_keymap *keymap;
	rp_vscreen *vscreen;
	struct list_head *hook;
	struct set_var *variable;
	struct rp_key *key;
	int gravity;
};

struct cmdarg {
	int type;
	char *string;
	union arg_union arg;
	struct list_head node;
};
#define ARG_STRING(elt) args[elt]->string
#define ARG(elt, type)  args[elt]->arg.type

struct argspec {
	int type;
	char *prompt;
};

struct set_var {
	char *var;
	cmdret *(*set_fn)(struct cmdarg **);
	int nargs;
	struct argspec *args;
	struct list_head node;
};

struct user_command {
	char *name;
	cmdret *(*func)(int, struct cmdarg **);
	struct argspec *args;
	int num_args;
	/*
	 * The number of required arguments. Any arguments after that are
	 * optional and won't be filled in when called interactively.
	 * ni_required_args is used when called non-interactively,
	 * i_required_args when called interactively.
	 */
	int ni_required_args, i_required_args;

	struct list_head node;
};

typedef struct {
	char *name;
	char *alias;
} alias_t;

typedef struct rp_frame_undo {
	char *frames;
	rp_vscreen *vscreen;
	struct list_head node;
} rp_frame_undo;

static LIST_HEAD(user_commands);
static LIST_HEAD(rp_keymaps);
static LIST_HEAD(set_vars);
static LIST_HEAD(rp_frame_undos);
static LIST_HEAD(rp_frame_redos);

static alias_t *alias_list;
static int alias_list_size;
static int alias_list_last;

static const char invalid_negative_arg[] = "invalid negative argument";

/* setter function prototypes */
static cmdret *set_barborder(struct cmdarg **args);
static cmdret *set_barbordercolor(struct cmdarg **args);
static cmdret *set_bargravity(struct cmdarg **args);
static cmdret *set_barinpadding(struct cmdarg **args);
static cmdret *set_barpadding(struct cmdarg **args);
static cmdret *set_barsticky(struct cmdarg **args);
static cmdret *set_bgcolor(struct cmdarg **args);
static cmdret *set_border(struct cmdarg **args);
static cmdret *set_bwcolor(struct cmdarg **args);
static cmdret *set_fgcolor(struct cmdarg **args);
static cmdret *set_font(struct cmdarg **args);
static cmdret *set_framefmt(struct cmdarg **args);
static cmdret *set_framemsgwait(struct cmdarg **args);
static cmdret *set_framesels(struct cmdarg **args);
static cmdret *set_fwcolor(struct cmdarg **args);
static cmdret *set_gap(struct cmdarg **args);
static cmdret *set_historysize(struct cmdarg **args);
static cmdret *set_ignoreresizehints(struct cmdarg **args);
static cmdret *set_infofmt(struct cmdarg **args);
static cmdret *set_inputwidth(struct cmdarg **args);
static cmdret *set_maxsizegravity(struct cmdarg **args);
static cmdret *set_maxundos(struct cmdarg **args);
static cmdret *set_msgwait(struct cmdarg **args);
static cmdret *set_onlyborder(struct cmdarg **args);
static cmdret *set_padding(struct cmdarg **args);
static cmdret *set_resizefmt(struct cmdarg **args);
static cmdret *set_resizeunit(struct cmdarg **args);
static cmdret *set_rudeness(struct cmdarg **args);
static cmdret *set_startupmessage(struct cmdarg **args);
static cmdret *set_stickyfmt(struct cmdarg **args);
static cmdret *set_topkmap(struct cmdarg **args);
static cmdret *set_transgravity(struct cmdarg **args);
static cmdret *set_vscreens(struct cmdarg **args);
static cmdret *set_waitcursor(struct cmdarg **args);
static cmdret *set_warp(struct cmdarg **args);
static cmdret *set_winfmt(struct cmdarg **args);
static cmdret *set_wingravity(struct cmdarg **args);
static cmdret *set_winliststyle(struct cmdarg **args);
static cmdret *set_winname(struct cmdarg **args);
static cmdret *set_winaddcurvscreen(struct cmdarg **args);

/* command function prototypes. */
static cmdret *cmd_abort(int interactive, struct cmdarg **args);
static cmdret *cmd_addhook(int interactive, struct cmdarg **args);
static cmdret *cmd_alias(int interactive, struct cmdarg **args);
static cmdret *cmd_banish(int interactive, struct cmdarg **args);
static cmdret *cmd_banishrel(int interactive, struct cmdarg **args);
static cmdret *cmd_chdir(int interactive, struct cmdarg **args);
static cmdret *cmd_clrunmanaged(int interactive, struct cmdarg **args);
static cmdret *cmd_cnext(int interactive, struct cmdarg **args);
static cmdret *cmd_colon(int interactive, struct cmdarg **args);
static cmdret *cmd_commands(int interactive, struct cmdarg **args);
static cmdret *cmd_cother(int interactive, struct cmdarg **args);
static cmdret *cmd_cprev(int interactive, struct cmdarg **args);
static cmdret *cmd_curframe(int interactive, struct cmdarg **args);
static cmdret *cmd_dedicate(int interactive, struct cmdarg **args);
static cmdret *cmd_definekey(int interactive, struct cmdarg **args);
static cmdret *cmd_delete(int interactive, struct cmdarg **args);
static cmdret *cmd_delkmap(int interactive, struct cmdarg **args);
static cmdret *cmd_describekey(int interactive, struct cmdarg **args);
static cmdret *cmd_echo(int interactive, struct cmdarg **args);
static cmdret *cmd_escape(int interactive, struct cmdarg **args);
static cmdret *cmd_exchangedown(int interactive, struct cmdarg **args);
static cmdret *cmd_exchangeleft(int interactive, struct cmdarg **args);
static cmdret *cmd_exchangeright(int interactive, struct cmdarg **args);
static cmdret *cmd_exchangeup(int interactive, struct cmdarg **args);
static cmdret *cmd_exec(int interactive, struct cmdarg **args);
static cmdret *cmd_execa(int interactive, struct cmdarg **args);
static cmdret *cmd_execf(int interactive, struct cmdarg **args);
static cmdret *cmd_execv(int interactive, struct cmdarg **args);
static cmdret *cmd_execw(int interactive, struct cmdarg **args);
static cmdret *cmd_fdump(int interactive, struct cmdarg **args);
static cmdret *cmd_focusdown(int interactive, struct cmdarg **args);
static cmdret *cmd_focuslast(int interactive, struct cmdarg **args);
static cmdret *cmd_focusleft(int interactive, struct cmdarg **args);
static cmdret *cmd_focusright(int interactive, struct cmdarg **args);
static cmdret *cmd_focusup(int interactive, struct cmdarg **args);
static cmdret *cmd_frestore(int interactive, struct cmdarg **args);
static cmdret *cmd_fselect(int interactive, struct cmdarg **args);
static cmdret *cmd_getenv(int interactive, struct cmdarg **args);
static cmdret *cmd_getsel(int interactive, struct cmdarg **args);
static cmdret *cmd_gravity(int interactive, struct cmdarg **args);
static cmdret *cmd_hsplit(int interactive, struct cmdarg **args);
static cmdret *cmd_help(int interactive, struct cmdarg **args);
static cmdret *cmd_inext(int interactive, struct cmdarg **args);
static cmdret *cmd_info(int interactive, struct cmdarg **args);
static cmdret *cmd_iother(int interactive, struct cmdarg **args);
static cmdret *cmd_iprev(int interactive, struct cmdarg **args);
static cmdret *cmd_kill(int interactive, struct cmdarg **args);
static cmdret *cmd_lastmsg(int interactive, struct cmdarg **args);
static cmdret *cmd_link(int interactive, struct cmdarg **args);
static cmdret *cmd_listhook(int interactive, struct cmdarg **args);
static cmdret *cmd_meta(int interactive, struct cmdarg **args);
static cmdret *cmd_newkmap(int interactive, struct cmdarg **args);
static cmdret *cmd_next(int interactive, struct cmdarg **args);
static cmdret *cmd_nextframe(int interactive, struct cmdarg **args);
static cmdret *cmd_nextscreen(int interactive, struct cmdarg **args);
static cmdret *cmd_number(int interactive, struct cmdarg **args);
static cmdret *cmd_only(int interactive, struct cmdarg **args);
static cmdret *cmd_other(int interactive, struct cmdarg **args);
static cmdret *cmd_prev(int interactive, struct cmdarg **args);
static cmdret *cmd_prevframe(int interactive, struct cmdarg **args);
static cmdret *cmd_prevscreen(int interactive, struct cmdarg **args);
static cmdret *cmd_prompt(int interactive, struct cmdarg **args);
static cmdret *cmd_putsel(int interactive, struct cmdarg **args);
static cmdret *cmd_quit(int interactive, struct cmdarg **args);
static cmdret *cmd_ratclick(int interactive, struct cmdarg **args);
static cmdret *cmd_rathold(int interactive, struct cmdarg **args);
static cmdret *cmd_ratinfo(int interactive, struct cmdarg **args);
static cmdret *cmd_ratrelinfo(int interactive, struct cmdarg **args);
static cmdret *cmd_ratrelwarp(int interactive, struct cmdarg **args);
static cmdret *cmd_ratwarp(int interactive, struct cmdarg **args);
static cmdret *cmd_readkey(int interactive, struct cmdarg **args);
static cmdret *cmd_redisplay(int interactive, struct cmdarg **args);
static cmdret *cmd_redo(int interactive, struct cmdarg **args);
static cmdret *cmd_remhook(int interactive, struct cmdarg **args);
static cmdret *cmd_remove(int interactive, struct cmdarg **args);
static cmdret *cmd_rename(int interactive, struct cmdarg **args);
static cmdret *cmd_resize(int interactive, struct cmdarg **args);
static cmdret *cmd_restart(int interactive, struct cmdarg **args);
static cmdret *cmd_sdump(int interactive, struct cmdarg **args);
static cmdret *cmd_select(int interactive, struct cmdarg **args);
static cmdret *cmd_set(int interactive, struct cmdarg **args);
static cmdret *cmd_setenv(int interactive, struct cmdarg **args);
static cmdret *cmd_sfdump(int interactive, struct cmdarg **args);
static cmdret *cmd_sfrestore(int interactive, struct cmdarg **args);
static cmdret *cmd_shrink(int interactive, struct cmdarg **args);
static cmdret *cmd_smove(int interactive, struct cmdarg **args);
static cmdret *cmd_source(int interactive, struct cmdarg **args);
static cmdret *cmd_sselect(int interactive, struct cmdarg **args);
static cmdret *cmd_stick(int interactive, struct cmdarg **args);
static cmdret *cmd_swap(int interactive, struct cmdarg **args);
static cmdret *cmd_unalias(int interactive, struct cmdarg **args);
static cmdret *cmd_undefinekey(int interactive, struct cmdarg **args);
static cmdret *cmd_undo(int interactive, struct cmdarg **args);
static cmdret *cmd_unmanage(int interactive, struct cmdarg **args);
static cmdret *cmd_unsetenv(int interactive, struct cmdarg **args);
static cmdret *cmd_unstick(int interactive, struct cmdarg **args);
static cmdret *cmd_vsplit(int interactive, struct cmdarg **args);
static cmdret *cmd_verbexec(int interactive, struct cmdarg **args);
static cmdret *cmd_version(int interactive, struct cmdarg **args);
static cmdret *cmd_vmove(int interactive, struct cmdarg **args);
static cmdret *cmd_vnext(int interactive, struct cmdarg **args);
static cmdret *cmd_vother(int interactive, struct cmdarg **args);
static cmdret *cmd_vprev(int interactive, struct cmdarg **args);
static cmdret *cmd_vrename(int interactive, struct cmdarg **args);
static cmdret *cmd_vscreens(int interactive, struct cmdarg **args);
static cmdret *cmd_vselect(int interactive, struct cmdarg **args);
static cmdret *cmd_windows(int interactive, struct cmdarg **args);

static void
add_set_var(char *name, cmdret *(*fn)(struct cmdarg **), int nargs, ...)
{
	int i = 0;
	struct set_var *var;
	va_list va;

	var = xmalloc(sizeof(struct set_var));
	var->var = name;
	var->set_fn = fn;
	var->nargs = nargs;
	var->args = xmalloc(sizeof(struct argspec) * nargs);

	/* Fill var->args */
	va_start(va, nargs);
	for (i = 0; i < nargs; i++) {
		var->args[i].prompt = va_arg(va, char *);
		var->args[i].type = va_arg(va, int);
	}
	va_end(va);

	list_add_tail(&var->node, &set_vars);
}

static void
set_var_free(struct set_var *var)
{
	if (var == NULL)
		return;

	free(var->args);
	free(var);
}

static void
init_set_vars(void)
{
	/* Keep this sorted alphabetically. */
	add_set_var("barborder", set_barborder, 1, "", arg_NUMBER);
	add_set_var("barbordercolor", set_barbordercolor, 1, "", arg_STRING);
	add_set_var("bargravity", set_bargravity, 1, "", arg_GRAVITY);
	add_set_var("barinpadding", set_barinpadding, 1, "", arg_NUMBER);
	add_set_var("barpadding", set_barpadding, 2, "", arg_NUMBER, "",
	    arg_NUMBER);
	add_set_var("barsticky", set_barsticky, 1, "", arg_NUMBER);
	add_set_var("bgcolor", set_bgcolor, 1, "", arg_STRING);
	add_set_var("border", set_border, 1, "", arg_NUMBER);
	add_set_var("bwcolor", set_bwcolor, 1, "", arg_STRING);
	add_set_var("fgcolor", set_fgcolor, 1, "", arg_STRING);
	add_set_var("font", set_font, 1, "", arg_STRING);
	add_set_var("framefmt", set_framefmt, 1, "", arg_REST);
	add_set_var("framemsgwait", set_framemsgwait, 1, "", arg_NUMBER);
	add_set_var("framesels", set_framesels, 1, "", arg_STRING);
	add_set_var("fwcolor", set_fwcolor, 1, "", arg_STRING);
	add_set_var("gap", set_gap, 1, "", arg_NUMBER);
	add_set_var("historysize", set_historysize, 1, "", arg_NUMBER);
	add_set_var("ignoreresizehints", set_ignoreresizehints, 1, "",
	    arg_NUMBER);
	add_set_var("infofmt", set_infofmt, 1, "", arg_REST);
	add_set_var("inputwidth", set_inputwidth, 1, "", arg_NUMBER);
	add_set_var("maxsizegravity", set_maxsizegravity, 1, "", arg_GRAVITY);
	add_set_var("maxundos", set_maxundos, 1, "", arg_NUMBER);
	add_set_var("msgwait", set_msgwait, 1, "", arg_NUMBER);
	add_set_var("onlyborder", set_onlyborder, 1, "", arg_NUMBER);
	add_set_var("padding", set_padding, 4, "", arg_NUMBER, "", arg_NUMBER,
	    "", arg_NUMBER, "", arg_NUMBER);
	add_set_var("resizefmt", set_resizefmt, 1, "", arg_REST);
	add_set_var("resizeunit", set_resizeunit, 1, "", arg_NUMBER);
	add_set_var("rudeness", set_rudeness, 1, "", arg_NUMBER);
	add_set_var("startupmessage", set_startupmessage, 1, "", arg_NUMBER);
	add_set_var("stickyfmt", set_stickyfmt, 1, "", arg_REST);
	add_set_var("topkmap", set_topkmap, 1, "", arg_STRING);
	add_set_var("transgravity", set_transgravity, 1, "", arg_GRAVITY);
	add_set_var("vscreens", set_vscreens, 1, "", arg_NUMBER);
	add_set_var("waitcursor", set_waitcursor, 1, "", arg_NUMBER);
	add_set_var("warp", set_warp, 1, "", arg_NUMBER);
	add_set_var("winfmt", set_winfmt, 1, "", arg_REST);
	add_set_var("wingravity", set_wingravity, 1, "", arg_GRAVITY);
	add_set_var("winliststyle", set_winliststyle, 1, "", arg_STRING);
	add_set_var("winname", set_winname, 1, "", arg_STRING);
	add_set_var("winaddcurvscreen", set_winaddcurvscreen, 1, "", arg_NUMBER);
}

/*
 * i_nrequired is the number required when called interactively. ni_nrequired
 * is when called non-interactively.
 */
static void
add_command(char *name, cmdret *(*fn)(int, struct cmdarg **), int nargs,
    int i_nrequired, int ni_nrequired, ...)
{
	int i = 0;
	struct user_command *cmd;
	va_list va;

	cmd = xmalloc(sizeof(struct user_command));
	cmd->name = name;
	cmd->func = fn;
	cmd->num_args = nargs;
	cmd->ni_required_args = ni_nrequired;
	cmd->i_required_args = i_nrequired;
	cmd->args = nargs ? xmalloc(nargs * sizeof(struct argspec)) : NULL;

	/* Fill cmd->args */
	va_start(va, ni_nrequired);
	for (i = 0; i < nargs; i++) {
		cmd->args[i].prompt = va_arg(va, char *);
		cmd->args[i].type = va_arg(va, int);
	}
	va_end(va);

	list_add(&cmd->node, &user_commands);
}

static void
user_command_free(struct user_command *cmd)
{
	if (cmd == NULL)
		return;

	free(cmd->args);
	free(cmd);
}

void
init_user_commands(void)
{
	/* @begin (tag required for genrpbindings) */
	add_command("abort",		cmd_abort,	0, 0, 0);
	add_command("addhook",		cmd_addhook,	2, 2, 2,
	            "Hook: ", arg_HOOK,
	            "Command: ", arg_COMMAND);
	add_command("alias",		cmd_alias,	2, 2, 2,
	            "Alias: ", arg_STRING,
	            "Command: ", arg_COMMAND);
	add_command("banish",		cmd_banish,	0, 0, 0);
	add_command("chdir",		cmd_chdir,	1, 0, 0,
	            "Dir: ", arg_REST);
	add_command("clrunmanaged",	cmd_clrunmanaged, 0, 0, 0);
	add_command("cnext",		cmd_cnext,	0, 0, 0);
	add_command("colon",		cmd_colon,	1, 0, 0,
	            "", arg_REST);
	add_command("commands",		cmd_commands,	0, 0, 0);
	add_command("cother",		cmd_cother,	0, 0, 0);
	add_command("cprev",		cmd_cprev,	0, 0, 0);
	add_command("curframe",		cmd_curframe,	0, 0, 0);
	add_command("dedicate",		cmd_dedicate,	1, 0, 0,
                    "", arg_NUMBER);
	add_command("definekey",	cmd_definekey,	3, 3, 3,
	            "Keymap: ", arg_KEYMAP,
	            "Key: ", arg_KEY,
	            "Command: ", arg_COMMAND);
	add_command("delete",		cmd_delete,	0, 0, 0);
	add_command("delkmap",		cmd_delkmap,	1, 1, 1,
	            "Keymap: ", arg_KEYMAP);
	add_command("describekey",	cmd_describekey, 1, 1, 1,
                    "Keymap: ", arg_KEYMAP);
	add_command("echo",		cmd_echo,	1, 1, 1,
	            "Echo: ", arg_RAW);
	add_command("escape",		cmd_escape,	1, 1, 1,
	            "Key: ", arg_KEY);
	add_command("exchangedown",	cmd_exchangedown, 0, 0, 0);
	add_command("exchangeleft",	cmd_exchangeleft, 0, 0, 0);
	add_command("exchangeright",	cmd_exchangeright, 0, 0, 0);
	add_command("exchangeup",	cmd_exchangeup,	0, 0, 0);
	add_command("exec",		cmd_exec,	1, 1, 1,
	            "/bin/sh -c ", arg_SHELLCMD);
	add_command("execa",		cmd_execa,	1, 1, 1,
	            "/bin/sh -c ", arg_SHELLCMD);
	add_command("execf",		cmd_execf,	2, 2, 2,
	            "frame to execute in:", arg_FRAME,
	            "/bin/sh -c ", arg_SHELLCMD);
	add_command("execv",		cmd_execv,	2, 2, 2,
	            "vscreen to execute in:", arg_VSCREEN,
	            "/bin/sh -c ", arg_SHELLCMD);
	add_command("execw",		cmd_execw,	1, 1, 1,
	            "/bin/sh -c ", arg_SHELLCMD);
	add_command("fdump",		cmd_fdump,	1, 0, 0,
	            "", arg_NUMBER);
	add_command("focus",		cmd_nextframe,	0, 0, 0);
	add_command("focusdown",	cmd_focusdown,	0, 0, 0);
	add_command("focusprev",	cmd_prevframe,	0, 0, 0);
	add_command("focuslast",	cmd_focuslast,	0, 0, 0);
	add_command("focusleft",	cmd_focusleft,	0, 0, 0);
	add_command("focusright",	cmd_focusright,	0, 0, 0);
	add_command("focusup",		cmd_focusup,	0, 0, 0);
	add_command("frestore",		cmd_frestore,	1, 1, 1,
	            "Frames: ", arg_REST);
	add_command("fselect",		cmd_fselect,	1, 1, 1,
	            "", arg_FRAME);
	add_command("getenv",		cmd_getenv,	1, 1, 1,
	            "Variable: ", arg_STRING);
	add_command("getsel",		cmd_getsel,	0, 0, 0);
	add_command("gravity",		cmd_gravity,	1, 0, 0,
	            "Gravity: ", arg_GRAVITY);
	add_command("help",		cmd_help,	1, 0, 0,
	            "Keymap: ", arg_KEYMAP);
	add_command("hsplit",		cmd_hsplit,	1, 0, 0,
	            "Split: ", arg_STRING);
	add_command("inext",		cmd_inext,	0, 0, 0);
	add_command("info",		cmd_info,	1, 0, 0,
	            "Format: ", arg_REST);
	add_command("iother",		cmd_iother,	0, 0, 0);
	add_command("iprev",		cmd_iprev,	0, 0, 0);
	add_command("kill",		cmd_kill,	0, 0, 0);
	add_command("lastmsg",		cmd_lastmsg,	0, 0, 0);
	add_command("link",		cmd_link,	2, 1, 1,
	            "Key: ", arg_STRING,
	            "Keymap: ", arg_KEYMAP);
	add_command("listhook",		cmd_listhook,	1, 1, 1,
                    "Hook: ", arg_HOOK);
	add_command("meta",		cmd_meta,	1, 0, 0,
                    "key: ", arg_KEY);
	add_command("newkmap",		cmd_newkmap,	1, 1, 1,
                    "Keymap: ", arg_STRING);
	add_command("next",		cmd_next,	0, 0, 0);
	add_command("nextscreen",	cmd_nextscreen,	0, 0, 0);
	add_command("number",		cmd_number,	2, 1, 1,
                    "Number: ", arg_NUMBER,
                    "Number: ", arg_NUMBER);
	add_command("only",		cmd_only,	0, 0, 0);
	add_command("other",		cmd_other,	0, 0, 0);
	add_command("prev",		cmd_prev,	0, 0, 0);
	add_command("prevscreen",	cmd_prevscreen,	0, 0, 0);
	add_command("prompt",		cmd_prompt,	1, 0, 0,
                    "", arg_REST);
	add_command("putsel",		cmd_putsel,	1, 1, 1,
                    "Text: ", arg_RAW);
	add_command("quit",		cmd_quit,	0, 0, 0);
	add_command("ratinfo",		cmd_ratinfo,	0, 0, 0);
	add_command("ratrelinfo",	cmd_ratrelinfo,	0, 0, 0);
	add_command("banishrel",	cmd_banishrel,	0, 0, 0);
	add_command("ratwarp",		cmd_ratwarp,	2, 2, 2,
                    "X: ", arg_NUMBER,
                    "Y: ", arg_NUMBER);
	add_command("ratrelwarp",	cmd_ratrelwarp,	2, 2, 2,
                    "X: ", arg_NUMBER,
                    "Y: ", arg_NUMBER);
	add_command("ratclick",		cmd_ratclick,	1, 0, 0,
                    "Button: ", arg_NUMBER);
	add_command("rathold",		cmd_rathold,	2, 1, 1,
                    "State: ", arg_STRING,
                    "Button: ", arg_NUMBER);
	add_command("readkey",		cmd_readkey,	1, 1, 1,
                    "Keymap: ", arg_KEYMAP);
	add_command("redisplay",	cmd_redisplay,	0, 0, 0);
	add_command("redo",		cmd_redo,	0, 0, 0);
	add_command("remhook",		cmd_remhook,	2, 2, 2,
                    "Hook: ", arg_HOOK,
                    "Command: ", arg_COMMAND);
	add_command("remove",		cmd_remove,	0, 0, 0);
	add_command("resize",		cmd_resize,	2, 0, 2,
                    "", arg_NUMBER,
                    "", arg_NUMBER);
	add_command("restart",		cmd_restart,	0, 0, 0);
	add_command("sdump",		cmd_sdump,	0, 0, 0);
	add_command("select",		cmd_select,	1, 0, 1,
                    "Select window: ", arg_REST);
	add_command("set",		cmd_set,	2, 0, 0,
                    "", arg_VARIABLE,
                    "", arg_REST);
	add_command("setenv",		cmd_setenv,	2, 2, 2,
                    "Variable: ", arg_STRING,
                    "Value: ", arg_REST);
	add_command("sfdump",		cmd_sfdump,	0, 0, 0);
	add_command("sfrestore",	cmd_sfrestore,	1, 1, 1,
                    "Frames: ", arg_REST);
	add_command("shrink",		cmd_shrink,	0, 0, 0);
	add_command("source",		cmd_source,	1, 1, 1,
                    "File: ", arg_REST);
	add_command("smove",		cmd_smove,	1, 1, 1,
                    "Screen: ", arg_NUMBER);
	add_command("sselect",		cmd_sselect,	1, 1, 1,
                    "Screen: ", arg_NUMBER);
	add_command("stick",		cmd_stick,	0, 0, 0);
	add_command("swap",		cmd_swap,	2, 1, 1,
	            "destination frame: ", arg_FRAME,
	            "source frame: ", arg_FRAME);
	add_command("title",		cmd_rename,	1, 1, 1,
                    "Set window's title to: ", arg_REST);
	add_command("unalias",		cmd_unalias,	1, 1, 1,
                    "Alias: ", arg_STRING);
	add_command("undefinekey",	cmd_undefinekey, 2, 2, 2,
	            "Keymap: ", arg_KEYMAP,
	            "Key: ", arg_KEY);
	add_command("undo",		cmd_undo,	0, 0, 0);
	add_command("unmanage",		cmd_unmanage,	1, 1, 0,
                    "Unmanage: ", arg_REST);
	add_command("unsetenv",		cmd_unsetenv,	1, 1, 1,
                    "Variable: ", arg_STRING);
	add_command("unstick",		cmd_unstick,	0, 0, 0);
	add_command("verbexec",		cmd_verbexec,	1, 1, 1,
                    "/bin/sh -c ", arg_SHELLCMD);
	add_command("version",		cmd_version,	0, 0, 0);
	add_command("vmove",		cmd_vmove,	1, 1, 1,
	            "Virtual Screen: ", arg_VSCREEN);
	add_command("vnext",		cmd_vnext,	0, 0, 0);
	add_command("vother",		cmd_vother,	0, 0, 0);
	add_command("vprev",		cmd_vprev,	0, 0, 0);
	add_command("vrename",		cmd_vrename,	1, 1, 1,
	            "Change virtual screen name to: ", arg_REST);
	add_command("vscreens",		cmd_vscreens,	0, 0, 0);
	add_command("vselect",		cmd_vselect,	1, 1, 1,
	            "Virtual Screen: ", arg_VSCREEN);
	add_command("vsplit",		cmd_vsplit,	1, 0, 0,
                    "Split: ", arg_STRING);
	add_command("windows",		cmd_windows,	1, 0, 0,
                    "", arg_REST);
	/* @end (tag required for genrpbindings) */

	init_set_vars();
}

/* Delete all entries in the redo list. */
static void
clear_frame_redos(void)
{
	rp_frame_undo *cur;
	struct list_head *tmp, *iter;

	list_for_each_safe_entry(cur, iter, tmp, &rp_frame_redos, node) {
		free(cur->frames);
		list_del(&(cur->node));
	}
}

static void
del_frame_undo(rp_frame_undo *u)
{
	if (!u)
		return;

	free(u->frames);
	list_del(&(u->node));
	free(u);
}

void
clear_frame_undos(void)
{
	while (list_size(&rp_frame_undos) > 0) {
		/* Delete the oldest node */
		rp_frame_undo *cur;
		list_last(cur, &rp_frame_undos, node);
		del_frame_undo(cur);
	}
}

static void
push_frame_undo(rp_vscreen *vscreen)
{
	rp_frame_undo *cur;

	if (list_size(&rp_frame_undos) > defaults.maxundos) {
		/* Delete the oldest node */
		list_last(cur, &rp_frame_undos, node);
		del_frame_undo(cur);
	}
	cur = xmalloc(sizeof(rp_frame_undo));
	cur->frames = fdump(vscreen);
	cur->vscreen = vscreen;
	list_add(&cur->node, &rp_frame_undos);
	/*
	 * Since we're creating new frames the redo list is now invalid, so
	 * clear it.
	 */
	clear_frame_redos();
}

static rp_frame_undo *
pop_frame_list(struct list_head *undo_list, struct list_head *redo_list)
{
	rp_vscreen *vscreen = rp_current_vscreen;
	rp_frame_undo *first, *new;

	/* Is there something to restore? */
	list_first(first, undo_list, node);
	if (!first)
		return NULL;

	/* First save the current layout into undo */
	new = xmalloc(sizeof(rp_frame_undo));
	new->frames = fdump(vscreen);
	new->vscreen = vscreen;
	list_add(&new->node, redo_list);

	list_del(&(first->node));
	return first;
}

/* Pop the head of the frame undo list off and put it in the redo list. */
static rp_frame_undo *
pop_frame_undo(void)
{
	return pop_frame_list(&rp_frame_undos, &rp_frame_redos);
}

/* Pop the head of the frame redo list off and put it in the undo list. */
static rp_frame_undo *
pop_frame_redo(void)
{
	return pop_frame_list(&rp_frame_redos, &rp_frame_undos);
}

rp_action *
find_keybinding_by_action(char *action, rp_keymap *map)
{
	int i;

	for (i = 0; i < map->actions_last; i++) {
		if (!strcmp(map->actions[i].data, action)) {
			return &map->actions[i];
		}
	}

	return NULL;
}

rp_action *
find_keybinding(KeySym keysym, unsigned int state, rp_keymap *map)
{
	int i;
	for (i = 0; i < map->actions_last; i++) {
		if (map->actions[i].key == keysym &&
		    map->actions[i].state == state)
			return &map->actions[i];
	}
	return NULL;
}

static char *
find_command_by_keydesc(char *desc, rp_keymap *map)
{
	int i = 0;
	char *keysym_name;

	while (i < map->actions_last) {
		keysym_name = keysym_to_string(map->actions[i].key,
		    map->actions[i].state);
		if (!strcmp(keysym_name, desc)) {
			free(keysym_name);
			return map->actions[i].data;
		}
		free(keysym_name);
		i++;
	}

	return NULL;
}

static char *
resolve_command_from_keydesc(char *desc, int depth, rp_keymap *map)
{
	char *cmd, *c;

	c = find_command_by_keydesc(desc, map);
	if (!c)
		return NULL;

	/* is it a link? */
	if (strncmp(c, "link", 4) || depth > MAX_LINK_DEPTH)
		/* it is not */
		return c;

	cmd = resolve_command_from_keydesc(&c[5], depth + 1, map);
	return (cmd != NULL) ? cmd : c;
}

static void
add_keybinding(KeySym keysym, int state, char *cmd, rp_keymap *map)
{
	if (map->actions_last >= map->actions_size) {
		/* double the key table size */
		map->actions_size *= 2;
		map->actions = xrealloc(map->actions,
		    sizeof(rp_action) * map->actions_size);
		PRINT_DEBUG(("realloc()ed key_table %d\n", map->actions_size));
	}
	map->actions[map->actions_last].key = keysym;
	map->actions[map->actions_last].state = state;
	/* free this on shutdown, or re/unbinding */
	map->actions[map->actions_last].data = xstrdup(cmd);

	map->actions_last++;
}

static void
replace_keybinding(rp_action * key_action, char *newcmd)
{
	free(key_action->data);
	key_action->data = xstrdup(newcmd);
}

static int
remove_keybinding(KeySym keysym, unsigned int state, rp_keymap *map)
{
	int i;
	int found = -1;

	for (i = 0; i < map->actions_last; i++) {
		if (map->actions[i].key == keysym &&
		    map->actions[i].state == state) {
			found = i;
			break;
		}
	}

	if (found >= 0) {
		free(map->actions[found].data);

		memmove(&map->actions[found], &map->actions[found + 1],
		    sizeof(rp_action) * (map->actions_last - found - 1));
		map->actions_last--;

		return 1;
	}

	return 0;
}

static rp_keymap *
keymap_new(char *name)
{
	rp_keymap *map;

	/* All keymaps must have a name. */
	if (name == NULL)
		return NULL;

	map = xmalloc(sizeof(rp_keymap));
	map->name = xstrdup(name);
	map->actions_size = 1;
	map->actions = xmalloc(sizeof(rp_action) * map->actions_size);
	map->actions_last = 0;

	return map;
}

rp_keymap *
find_keymap(char *name)
{
	rp_keymap *cur;

	list_for_each_entry(cur, &rp_keymaps, node) {
		if (!strcmp(name, cur->name)) {
			return cur;
		}
	}

	return NULL;
}

/*
 * Search the alias table for a match. If a match is found, return its index
 * into the table. Otherwise return -1.
 */
static int
find_alias_index(char *name)
{
	int i;

	for (i = 0; i < alias_list_last; i++)
		if (!strcmp(name, alias_list[i].name))
			return i;

	return -1;
}

static void
add_alias(char *name, char *alias)
{
	int i;

	/* Are we updating an existing alias, or creating a new one? */
	i = find_alias_index(name);
	if (i >= 0) {
		free(alias_list[i].alias);
		alias_list[i].alias = xstrdup(alias);
	} else {
		if (alias_list_last >= alias_list_size) {
			alias_list_size *= 2;
			alias_list = xrealloc(alias_list,
			    sizeof(alias_t) * alias_list_size);
		}
		alias_list[alias_list_last].name = xstrdup(name);
		alias_list[alias_list_last].alias = xstrdup(alias);
		alias_list_last++;
	}
}

void
initialize_default_keybindings(void)
{
	rp_keymap *map, *top;

	map = keymap_new(ROOT_KEYMAP);
	list_add(&map->node, &rp_keymaps);

	top = keymap_new(defaults.top_kmap);
	list_add(&top->node, &rp_keymaps);

	/* Initialize the alias list. */
	alias_list_size = 5;
	alias_list_last = 0;
	alias_list = xmalloc(sizeof(alias_t) * alias_list_size);

	prefix_key.sym = KEY_PREFIX;
	prefix_key.state = MODIFIER_PREFIX;

	/* Add the prefix key to the top-level map. */
	add_keybinding(prefix_key.sym, prefix_key.state, "readkey " ROOT_KEYMAP,
	    top);

	add_keybinding(prefix_key.sym, prefix_key.state, "other", map);
	add_keybinding(prefix_key.sym, 0, "meta", map);
	add_keybinding(XK_g, RP_CONTROL_MASK, "abort", map);
	add_keybinding(XK_0, 0, "select 0", map);
	add_keybinding(XK_1, 0, "select 1", map);
	add_keybinding(XK_2, 0, "select 2", map);
	add_keybinding(XK_3, 0, "select 3", map);
	add_keybinding(XK_4, 0, "select 4", map);
	add_keybinding(XK_5, 0, "select 5", map);
	add_keybinding(XK_6, 0, "select 6", map);
	add_keybinding(XK_7, 0, "select 7", map);
	add_keybinding(XK_8, 0, "select 8", map);
	add_keybinding(XK_9, 0, "select 9", map);
	add_keybinding(XK_minus, 0, "select -", map);
	add_keybinding(XK_A, 0, "title", map);
	add_keybinding(XK_A, RP_CONTROL_MASK, "title", map);
	add_keybinding(XK_K, 0, "kill", map);
	add_keybinding(XK_K, RP_CONTROL_MASK, "kill", map);
	add_keybinding(XK_Return, 0, "next", map);
	add_keybinding(XK_Return, RP_CONTROL_MASK, "next", map);
	add_keybinding(XK_b, 0, "banish", map);
	add_keybinding(XK_b, RP_CONTROL_MASK, "banish", map);
	add_keybinding(XK_c, 0, "exec " TERM_PROG, map);
	add_keybinding(XK_c, RP_CONTROL_MASK, "exec " TERM_PROG, map);
	add_keybinding(XK_colon, 0, "colon", map);
	add_keybinding(XK_exclam, 0, "exec", map);
	add_keybinding(XK_exclam, RP_CONTROL_MASK,
	    "colon exec " TERM_PROG " -e ", map);
	add_keybinding(XK_i, 0, "info", map);
	add_keybinding(XK_i, RP_CONTROL_MASK, "info", map);
	add_keybinding(XK_k, 0, "delete", map);
	add_keybinding(XK_k, RP_CONTROL_MASK, "delete", map);
	add_keybinding(XK_l, 0, "redisplay", map);
	add_keybinding(XK_l, RP_CONTROL_MASK, "redisplay", map);
	add_keybinding(XK_m, 0, "lastmsg", map);
	add_keybinding(XK_m, RP_CONTROL_MASK, "lastmsg", map);
	add_keybinding(XK_n, 0, "next", map);
	add_keybinding(XK_n, RP_CONTROL_MASK, "next", map);
	add_keybinding(XK_p, 0, "prev", map);
	add_keybinding(XK_p, RP_CONTROL_MASK, "prev", map);
	add_keybinding(XK_quoteright, 0, "select", map);
	add_keybinding(XK_quoteright, RP_CONTROL_MASK, "select", map);
	add_keybinding(XK_space, 0, "next", map);
	add_keybinding(XK_space, RP_CONTROL_MASK, "next", map);
	add_keybinding(XK_v, 0, "version", map);
	add_keybinding(XK_v, RP_CONTROL_MASK, "version", map);
	add_keybinding(XK_w, 0, "windows", map);
	add_keybinding(XK_w, RP_CONTROL_MASK, "windows", map);
	add_keybinding(XK_s, 0, "split", map);
	add_keybinding(XK_s, RP_CONTROL_MASK, "split", map);
	add_keybinding(XK_S, 0, "hsplit", map);
	add_keybinding(XK_S, RP_CONTROL_MASK, "hsplit", map);
	add_keybinding(XK_Tab, 0, "focus", map);
	add_keybinding(XK_Tab, RP_META_MASK, "focuslast", map);
	add_keybinding(XK_Left, RP_CONTROL_MASK, "exchangeleft", map);
	add_keybinding(XK_Right, RP_CONTROL_MASK, "exchangeright", map);
	add_keybinding(XK_Up, RP_CONTROL_MASK, "exchangeup", map);
	add_keybinding(XK_Down, RP_CONTROL_MASK, "exchangedown", map);
	add_keybinding(XK_Left, 0, "focusleft", map);
	add_keybinding(XK_Right, 0, "focusright", map);
	add_keybinding(XK_Up, 0, "focusup", map);
	add_keybinding(XK_Down, 0, "focusdown", map);
	add_keybinding(XK_Q, 0, "only", map);
	add_keybinding(XK_R, 0, "remove", map);
	add_keybinding(XK_f, 0, "fselect", map);
	add_keybinding(XK_f, RP_CONTROL_MASK, "fselect", map);
	add_keybinding(XK_F, 0, "curframe", map);
	add_keybinding(XK_r, 0, "resize", map);
	add_keybinding(XK_r, RP_CONTROL_MASK, "resize", map);
	add_keybinding(XK_question, 0, "help " ROOT_KEYMAP, map);
	add_keybinding(XK_underscore, RP_CONTROL_MASK, "undo", map);
	add_keybinding(XK_u, 0, "undo", map);
	add_keybinding(XK_u, RP_CONTROL_MASK, "undo", map);
	add_keybinding(XK_U, 0, "redo", map);
	add_keybinding(XK_x, 0, "swap", map);
	add_keybinding(XK_x, RP_CONTROL_MASK, "swap", map);
	add_keybinding(XK_N, 0, "nextscreen", map);
	add_keybinding(XK_P, 0, "prevscreen", map);
	add_keybinding(XK_F1, 0, "vselect 0", map);
	add_keybinding(XK_F2, 0, "vselect 1", map);
	add_keybinding(XK_F3, 0, "vselect 2", map);
	add_keybinding(XK_F4, 0, "vselect 3", map);
	add_keybinding(XK_F5, 0, "vselect 4", map);
	add_keybinding(XK_F6, 0, "vselect 5", map);
	add_keybinding(XK_F7, 0, "vselect 6", map);
	add_keybinding(XK_F8, 0, "vselect 7", map);
	add_keybinding(XK_F9, 0, "vselect 8", map);
	add_keybinding(XK_F10, 0, "vselect 9", map);
	add_keybinding(XK_F11, 0, "vselect 10", map);
	add_keybinding(XK_F12, 0, "vselect 11", map);

	add_alias("unbind", "undefinekey " ROOT_KEYMAP);
	add_alias("bind", "definekey " ROOT_KEYMAP);
	add_alias("split", "vsplit");
}

cmdret *
cmdret_new(int success, char *fmt,...)
{
	cmdret *ret;
	va_list ap;

	ret = xmalloc(sizeof(cmdret));
	ret->success = success;

	if (fmt) {
		va_start(ap, fmt);
		ret->output = xvsprintf(fmt, ap);
		va_end(ap);
	} else
		ret->output = NULL;

	return ret;
}

void
cmdret_free(cmdret *ret)
{
	free(ret->output);
	free(ret);
}

static void
keymap_free(rp_keymap *map)
{
	int i;

	/* Free the data in the actions. */
	for (i = 0; i < map->actions_last; i++) {
		free(map->actions[i].data);
	}

	/* Free the map data. */
	free(map->actions);
	free(map->name);

	/* ...and the map itself. */
	free(map);
}

void
free_keymaps(void)
{
	rp_keymap *cur;
	struct list_head *tmp, *iter;

	list_for_each_safe_entry(cur, iter, tmp, &rp_keymaps, node) {
		list_del(&cur->node);
		keymap_free(cur);
	}
}

void
free_aliases(void)
{
	int i;

	/* Free the alias data. */
	for (i = 0; i < alias_list_last; i++) {
		free(alias_list[i].name);
		free(alias_list[i].alias);
	}

	/* Free the alias list. */
	free(alias_list);
}

void
free_user_commands(void)
{
	struct user_command *cur;
	struct set_var *var;
	struct list_head *tmp, *iter;

	list_for_each_safe_entry(cur, iter, tmp, &user_commands, node) {
		list_del(&cur->node);
		user_command_free(cur);
	}
	list_for_each_safe_entry(var, iter, tmp, &set_vars, node) {
		list_del(&var->node);
		set_var_free(var);
	}
}

/*
 * return a KeySym from a string that contains either a hex value or an X
 * keysym description
 */
static int
string_to_keysym(char *str)
{
	int retval;
	int keysym;

	retval = sscanf(str, "0x%x", &keysym);

	if (!retval || retval == EOF)
		keysym = XStringToKeysym(str);

	return keysym;
}

static cmdret *
parse_keydesc(char *keydesc, struct rp_key *key)
{
	char *token, *next_token;

	if (keydesc == NULL)
		return NULL;

	key->state = 0;
	key->sym = 0;

	if (!strchr(keydesc, '-')) {
		/* Its got no hyphens in it, so just grab the keysym */
		key->sym = string_to_keysym(keydesc);

		if (key->sym == NoSymbol)
			return cmdret_new(RET_FAILURE,
			    "parse_keydesc: Unknown key '%s'", keydesc);
	} else if (keydesc[strlen(keydesc) - 1] == '-') {
		/* A key description can't end in a -. */
		return cmdret_new(RET_FAILURE,
		    "parse_keydesc: Can't parse key '%s'", keydesc);
	} else {
		/* Its got hyphens, so parse out the modifiers and keysym */
		char *copy;

		copy = xstrdup(keydesc);

		token = strtok(copy, "-");
		if (token == NULL) {
			/* It was nothing but hyphens */
			free(copy);
			return cmdret_new(RET_FAILURE,
			    "parse_keydesc: Can't parse key '%s'", keydesc);
		}
		do {
			next_token = strtok(NULL, "-");

			if (next_token == NULL) {
				/*
				 * There is nothing more to parse and token
				 * contains the keysym name.
				 */
				key->sym = string_to_keysym(token);

				if (key->sym == NoSymbol) {
					cmdret *ret = cmdret_new(RET_FAILURE,
					    "parse_keydesc: Unknown key '%s'",
					    token);
					free(copy);
					return ret;
				}
			} else {
				/*
				 * Which modifier is it? Only accept modifiers
				 * that are present. ie don't accept a hyper
				 * modifier if the keymap has no hyper key.
				 */
				if (!strcmp(token, "C")) {
					key->state |= RP_CONTROL_MASK;
				} else if (!strcmp(token, "M")) {
					key->state |= RP_META_MASK;
				} else if (!strcmp(token, "A")) {
					key->state |= RP_ALT_MASK;
				} else if (!strcmp(token, "S")) {
					key->state |= RP_SHIFT_MASK;
				} else if (!strcmp(token, "s")) {
					key->state |= RP_SUPER_MASK;
				} else if (!strcmp(token, "H")) {
					key->state |= RP_HYPER_MASK;
				} else {
					free(copy);
					return cmdret_new(RET_FAILURE,
					    "parse_keydesc: Unknown modifier '%s'",
					    token);
				}
			}

			token = next_token;
		} while (next_token != NULL);

		free(copy);
	}

	/* Successfully parsed the key. */
	return NULL;
}

static void
grab_rat(void)
{
	XGrabPointer(dpy, rp_current_screen->root, True, 0, GrabModeAsync,
	    GrabModeAsync, None, rp_current_screen->rat, CurrentTime);
}

static void
ungrab_rat(void)
{
	XUngrabPointer(dpy, CurrentTime);
}

/* Unmanage window */
cmdret *
cmd_unmanage(int interactive, struct cmdarg **args)
{
	if (args[0] == NULL && !interactive) {
		cmdret *ret;
		char *s = list_unmanaged_windows();

		if (s)
			ret = cmdret_new(RET_SUCCESS, "%s", s);
		else
			ret = cmdret_new(RET_SUCCESS, NULL);

		free(s);
		return ret;
	}

	if (!args[0])
		return cmdret_new(RET_FAILURE,
		    "unmanage: at least one argument required");

	add_unmanaged_window(ARG_STRING(0));

	return cmdret_new(RET_SUCCESS, NULL);
}

/* Clear the unmanaged window list */
cmdret *
cmd_clrunmanaged(int interactive, struct cmdarg **args)
{
	clear_unmanaged_list();
	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_undefinekey(int interactive, struct cmdarg **args)
{
	cmdret *ret = NULL;
	rp_keymap *map;
	struct rp_key *key;

	map = ARG(0, keymap);
	key = ARG(1, key);

	/*
	 * If we're updating the top level map, we'll need to update the keys
	 * grabbed.
	 */
	if (map == find_keymap(defaults.top_kmap))
		ungrab_keys_all_wins();

	/* If no comand is specified, then unbind the key. */
	if (!remove_keybinding(key->sym, key->state, map))
		ret = cmdret_new(RET_FAILURE,
		    "undefinekey: key '%s' is not bound", ARG_STRING(1));

	/* Update the grabbed keys. */
	if (map == find_keymap(defaults.top_kmap))
		grab_keys_all_wins();
	XSync(dpy, False);

	if (ret)
		return ret;

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_definekey(int interactive, struct cmdarg **args)
{
	cmdret *ret = NULL;
	rp_keymap *map;
	struct rp_key *key;
	char *cmd;
	rp_action *key_action;

	map = ARG(0, keymap);
	key = ARG(1, key);
	cmd = ARG_STRING(2);

	/*
	 * If we're updating the top level map, we'll need to update the keys
	 * grabbed.
	 */
	if (map == find_keymap(defaults.top_kmap))
		ungrab_keys_all_wins();

	if ((key_action = find_keybinding(key->sym, key->state, map)))
		replace_keybinding(key_action, cmd);
	else
		add_keybinding(key->sym, key->state, cmd, map);

	/* Update the grabbed keys. */
	if (map == find_keymap(defaults.top_kmap))
		grab_keys_all_wins();
	XSync(dpy, False);

	if (ret)
		return ret;

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_source(int interactive, struct cmdarg **args)
{
	FILE *fileptr;

	if ((fileptr = fopen(ARG_STRING(0), "r")) == NULL)
		return cmdret_new(RET_FAILURE, "source: %s: %s", ARG_STRING(0),
		    strerror(errno));

	set_close_on_exec(fileno(fileptr));
	read_rc_file(fileptr);
	fclose(fileptr);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_meta(int interactive, struct cmdarg **args)
{
	XEvent ev;

	memset(&ev, 0, sizeof(ev));
	/*
	 * Redundant with the line above, but points out that passing some
	 * garbage time value trips up some clients
	 */
	ev.xkey.time = CurrentTime;

	if (current_window() == NULL)
		return cmdret_new(RET_FAILURE, NULL);

	ev.xkey.type = KeyPress;
	ev.xkey.display = dpy;
	ev.xkey.window = current_window()->w;

	if (args[0]) {
		struct rp_key key;
		cmdret *ret;

		memset(&key, 0, sizeof(key));
		ret = parse_keydesc(ARG_STRING(0), &key);
		if (ret != NULL)
			return ret;

		ev.xkey.state = rp_mask_to_x11_mask(key.state);
		ev.xkey.keycode = XKeysymToKeycode(dpy, key.sym);
		if (ev.xkey.keycode == NoSymbol)
			return cmdret_new(RET_FAILURE,
			    "meta: Couldn't convert keysym to keycode");
	} else {
		ev.xkey.state = rp_mask_to_x11_mask(prefix_key.state);
		ev.xkey.keycode = XKeysymToKeycode(dpy, prefix_key.sym);
	}

	XSendEvent(dpy, current_window()->w, False, KeyPressMask, &ev);
	XSync(dpy, False);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_prev(int interactive, struct cmdarg **args)
{
	rp_window *cur, *win;
	cur = current_window();
	win = vscreen_prev_window(rp_current_vscreen, cur);

	if (win)
		set_active_window(win);
	else if (cur)
		return cmdret_new(RET_FAILURE, "%s", MESSAGE_NO_OTHER_WINDOW);
	else
		return cmdret_new(RET_FAILURE, "%s",
		    MESSAGE_NO_MANAGED_WINDOWS);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_prevframe(int interactive, struct cmdarg **args)
{
	rp_frame *frame;

	frame = find_frame_prev(current_frame(rp_current_vscreen));
	if (!frame)
		return cmdret_new(RET_FAILURE, "%s", MESSAGE_NO_OTHER_FRAME);

	set_active_frame(frame, 0);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_next(int interactive, struct cmdarg **args)
{
	rp_window *cur, *win;
	cur = current_window();
	win = vscreen_next_window(rp_current_vscreen, cur);

	if (win)
		set_active_window(win);
	else if (cur)
		return cmdret_new(RET_FAILURE, "%s", MESSAGE_NO_OTHER_WINDOW);
	else
		return cmdret_new(RET_FAILURE, "%s",
		    MESSAGE_NO_MANAGED_WINDOWS);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_nextframe(int interactive, struct cmdarg **args)
{
	rp_frame *frame;

	frame = find_frame_next(current_frame(rp_current_vscreen));
	if (!frame)
		return cmdret_new(RET_FAILURE, "%s", MESSAGE_NO_OTHER_FRAME);

	set_active_frame(frame, 0);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_other(int interactive, struct cmdarg **args)
{
	rp_window *w;

	/* w = find_window_other (); */
	w = vscreen_last_window(rp_current_vscreen);
	if (!w)
		return cmdret_new(RET_FAILURE, "%s", MESSAGE_NO_OTHER_WINDOW);

	set_active_window_force(w);

	return cmdret_new(RET_SUCCESS, NULL);
}

/* Parse a positive or null number, returns -1 on failure. */
static int
string_to_positive_int(char *str)
{
	char *ep;
	long lval;

	errno = 0;
	lval = strtol(str, &ep, 10);
	if (str[0] == '\0' || *ep != '\0')
		return -1;
	if ((errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN)) ||
	    (lval > INT_MAX || lval < 0))
		return -1;

	return (int) lval;
}

static struct list_head *
trivial_completions(char *str)
{
	struct list_head *list;

	/* Initialize our list. */
	list = xmalloc(sizeof(struct list_head));
	INIT_LIST_HEAD(list);

	return list;
}

static struct list_head *
keymap_completions(char *str)
{
	rp_keymap *cur;
	struct list_head *list;

	/* Initialize our list. */
	list = xmalloc(sizeof(struct list_head));
	INIT_LIST_HEAD(list);

	list_for_each_entry(cur, &rp_keymaps, node) {
		struct sbuf *name;

		name = sbuf_new(0);
		sbuf_copy(name, cur->name);
		list_add_tail(&name->node, list);
	}

	return list;
}

static struct list_head *
window_completions(char *str)
{
	rp_window_elem *cur;
	struct list_head *list;

	/* Initialize our list. */
	list = xmalloc(sizeof(struct list_head));
	INIT_LIST_HEAD(list);

	/* Gather the names of all the windows. */
	list_for_each_entry(cur, &rp_current_vscreen->mapped_windows, node) {
		struct sbuf *name;

		name = sbuf_new(0);
		sbuf_copy(name, window_name(cur->win));
		list_add_tail(&name->node, list);
	}

	return list;
}

/* switch to window number or name */
cmdret *
cmd_select(int interactive, struct cmdarg **args)
{
	cmdret *ret = NULL;
	char *str;
	int n;

	/*
	 * FIXME: This is manually done because of the kinds of things select
	 * accepts.
	 */
	if (args[0] == NULL)
		str = get_more_input(MESSAGE_PROMPT_SWITCH_TO_WINDOW, "",
		    hist_SELECT, SUBSTRING, window_completions);
	else
		str = xstrdup(ARG_STRING(0));

	/* User aborted. */
	if (str == NULL)
		return cmdret_new(RET_FAILURE, NULL);

	/* Only search if the string contains something to search for. */
	if (strlen(str) > 0) {
		if (strlen(str) == 1 && str[0] == '-') {
			blank_frame(current_frame(rp_current_vscreen));
			ret = cmdret_new(RET_SUCCESS, NULL);
		} else if ((n = string_to_positive_int(str)) >= 0) {
			/* try by number */
			rp_window_elem *elem = vscreen_find_window_by_number(
			    rp_current_vscreen, n);

			if (elem) {
				goto_window(elem->win);
				ret = cmdret_new(RET_SUCCESS, NULL);
			} else {
				if (interactive) {
					/* show the window list as feedback */
					show_bar(rp_current_screen,
					    defaults.window_fmt);
					ret = cmdret_new(RET_SUCCESS, NULL);
				} else {
					ret = cmdret_new(RET_FAILURE,
					    "select: unknown window number '%d'",
					    n);
				}
			}
		} else {
			/* try by name */
			rp_window *win = find_window_name(str, 1);

			if (!win)
				win = find_window_name(str, 0);

			if (win) {
				goto_window(win);
				ret = cmdret_new(RET_SUCCESS, NULL);
			} else
				ret = cmdret_new(RET_FAILURE,
				    "select: unknown window '%s'", str);
		}
	} else
		/* Silently fail, since the user didn't provide a window spec */
		ret = cmdret_new(RET_SUCCESS, NULL);

	free(str);

	return ret;
}

cmdret *
cmd_rename(int interactive, struct cmdarg **args)
{
	if (current_window() == NULL)
		return cmdret_new(RET_FAILURE, NULL);

	free(current_window()->user_name);
	current_window()->user_name = xstrdup(ARG_STRING(0));
	current_window()->named = 1;
	hook_run(&rp_title_changed_hook);

	/* Update the program bar. */
	update_window_names(rp_current_screen, defaults.window_fmt);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_delete(int interactive, struct cmdarg **args)
{
	XEvent ev;
	int status;

	if (current_window() == NULL)
		return cmdret_new(RET_FAILURE, NULL);

	ev.xclient.type = ClientMessage;
	ev.xclient.window = current_window()->w;
	ev.xclient.message_type = wm_protocols;
	ev.xclient.format = 32;
	ev.xclient.data.l[0] = wm_delete;
	ev.xclient.data.l[1] = CurrentTime;

	status = XSendEvent(dpy, current_window()->w, False, 0, &ev);
	if (status == 0)
		PRINT_DEBUG(("Delete window failed\n"));

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_kill(int interactive, struct cmdarg **args)
{
	if (current_window() == NULL)
		return cmdret_new(RET_FAILURE, NULL);

	if (XKillClient(dpy, current_window()->w) == BadValue)
		return cmdret_new(RET_FAILURE,
		    "kill failed (got BadValue, this may be a bug)");

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_version(int interactive, struct cmdarg **args)
{
	return cmdret_new(RET_SUCCESS, "%s %s", PROGNAME, VERSION);
}

static char *
frame_selector(unsigned int n)
{
	if (n < strlen(defaults.frame_selectors)) {
		return xsprintf(" %c ", defaults.frame_selectors[n]);
	} else {
		return xsprintf(" %d ", n);
	}
}

/* Return true if ch is nth frame selector. */
static int
frame_selector_match(char ch)
{
	size_t i;

	/* Is it in the frame selector string? */
	for (i = 0; i < strlen(defaults.frame_selectors); i++) {
		if (ch == defaults.frame_selectors[i])
			return i;
	}

	/*
	 * Maybe it's a number less than 9 and the frame selector doesn't
	 * define that many selectors.
	 */
	if (ch >= '0' && ch <= '9'
	    && (size_t) (ch - '0') >= strlen(defaults.frame_selectors)) {
		return ch - '0';
	}
	return -1;
}

static cmdret *
read_string(struct argspec *spec, struct sbuf *s, int history_id,
    completion_fn fn, struct cmdarg ** arg)
{
	char *input;

	if (s)
		input = xstrdup(sbuf_get(s));
	else
		input = get_input(spec->prompt, history_id, fn);

	if (input) {
		*arg = xmalloc(sizeof(struct cmdarg));
		(*arg)->type = spec->type;
		(*arg)->string = input;
		return NULL;
	}

	*arg = NULL;
	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
read_keymap(struct argspec *spec, struct sbuf *s, struct cmdarg **arg)
{
	char *input;

	if (s)
		input = xstrdup(sbuf_get(s));
	else
		input = get_input(spec->prompt, hist_KEYMAP, keymap_completions);

	if (input) {
		rp_keymap *map;

		map = find_keymap(input);
		if (map == NULL) {
			cmdret *ret = cmdret_new(RET_FAILURE,
			    "unknown keymap '%s'", input);
			free(input);
			return ret;
		}

		*arg = xmalloc(sizeof(struct cmdarg));
		(*arg)->type = spec->type;
		(*arg)->arg.keymap = map;
		(*arg)->string = input;

		return NULL;
	}

	*arg = NULL;
	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
read_keydesc(struct argspec *spec, struct sbuf *s, struct cmdarg **arg)
{
	char *input;

	if (s)
		input = xstrdup(sbuf_get(s));
	else
		input = get_input(spec->prompt, hist_KEY, trivial_completions);

	if (input) {
		cmdret *ret;
		struct rp_key *key = xmalloc(sizeof(struct rp_key));

		ret = parse_keydesc(input, key);
		if (ret) {
			free(key);
			return ret;
		}

		*arg = xmalloc(sizeof(struct cmdarg));
		(*arg)->type = spec->type;
		(*arg)->arg.key = key;
		(*arg)->string = input;

		return NULL;
	}

	*arg = NULL;
	return cmdret_new(RET_SUCCESS, NULL);
}

static struct list_head *
vscreen_completions(char *str)
{
       struct list_head *list;
       rp_vscreen *cur;

       /* Initialize our list. */
       list = xmalloc(sizeof(struct list_head));
       INIT_LIST_HEAD(list);

       /* Grab all the vscreen names. */
       list_for_each_entry(cur, &(rp_current_screen)->vscreens, node) {
	       struct sbuf *s;

	       s = sbuf_new(0);
	       if (cur->name) {
		       sbuf_copy(s, cur->name);
	       } else {
		       sbuf_printf(s, "%d", cur->number);
	       }

	       list_add_tail(&s->node, list);
       }

       return list;
}

static struct list_head *
colon_completions(char *str)
{
	int i;
	struct user_command *uc;
	struct sbuf *s;
	struct list_head *list;

	/* Initialize our list. */
	list = xmalloc(sizeof(struct list_head));
	INIT_LIST_HEAD(list);

	/* Put all the aliases in our list. */
	for (i = 0; i < alias_list_last; ++i) {
		s = sbuf_new(0);
		sbuf_copy(s, alias_list[i].name);
		/*
		 * The space is so when the user completes a space is
		 * conveniently inserted after the command.
		 */
		sbuf_concat(s, " ");
		list_add_tail(&s->node, list);
	}

	/* Put all the commands in our list. */
	list_for_each_entry(uc, &user_commands, node) {
		s = sbuf_new(0);
		sbuf_copy(s, uc->name);
		/*
		 * The space is so when the user completes a space is
		 * conveniently inserted after the command.
		 */
		sbuf_concat(s, " ");
		list_add_tail(&s->node, list);
	}

	return list;
}

static cmdret *
read_command(struct argspec *spec, struct sbuf *s, struct cmdarg **arg)
{
	return read_string(spec, s, hist_COMMAND, colon_completions, arg);
}

static struct list_head *
exec_completions(char *str)
{
	size_t n = 256;
	char *partial;
	struct sbuf *line;
	FILE *file;
	struct list_head *head;
	char *completion_string;

	/* Initialize our list. */
	head = xmalloc(sizeof(struct list_head));
	INIT_LIST_HEAD(head);

	/* FIXME: A Bash dependancy?? */
	completion_string = xsprintf("bash -c \"compgen -ac %s|sort\"", str);
	file = popen(completion_string, "r");
	free(completion_string);
	if (!file) {
		warn("popen failed");
		return head;
	}
	partial = xmalloc(n);

	/*
	 * Read data from the file, split it into lines and store it in a list.
	 */
	line = sbuf_new(0);
	while (fgets(partial, n, file) != NULL) {
		/* Read a chunk from the file into our line accumulator. */
		sbuf_concat(line, partial);

		if (feof(file) ||
		    (*(sbuf_get(line) + strlen(sbuf_get(line)) - 1) == '\n')) {
			char *s;
			struct sbuf *elem;

			s = sbuf_get(line);

			/* Frob the newline into */
			if (*(s + strlen(s) - 1) == '\n')
				*(s + strlen(s) - 1) = '\0';

			/* Add our line to the list. */
			elem = sbuf_new(0);
			sbuf_copy(elem, s);
			list_add_tail(&elem->node, head);

			sbuf_clear(line);
		}
	}

	sbuf_free(line);

	free(partial);
	pclose(file);

	return head;
}

static cmdret *
read_shellcmd(struct argspec *spec, struct sbuf *s, struct cmdarg **arg,
    const char *command_name)
{
	cmdret *ret;

	ret = read_string(spec, s, hist_SHELLCMD, exec_completions, arg);
	if (command_name && !s && !ret && (*arg)->string) {
		/* store for command history */
		struct sbuf *buf;

		buf = sbuf_new(0);
		sbuf_printf(buf, "%s %s", command_name, (*arg)->string);
		history_add(hist_COMMAND, sbuf_get(buf));
		sbuf_free(buf);
	}

	return ret;
}

static cmdret *
read_frame(struct sbuf *s, struct cmdarg **arg)
{
	rp_frame *frame;
	XSetWindowAttributes attr;
	int fnum = -1;
	KeySym c;
	char keysym_buf[513];
	int keysym_bufsize = sizeof(keysym_buf);
	unsigned int mod;
	Window *wins;
	int i = 0;
	rp_frame *cur_frame;
	rp_screen *cur_screen = rp_current_screen;
	int frames;

	if (s == NULL) {
		frames = num_frames(rp_current_vscreen);
		wins = xmalloc(sizeof(Window) * frames);

		/*
		 * Loop through each frame and display its number in its top
		 * left corner.
		 */
		attr.border_pixel = rp_glob_screen.fgcolor;
		attr.background_pixel = rp_glob_screen.bgcolor;
		attr.override_redirect = True;

		list_for_each_entry(cur_frame, &rp_current_vscreen->frames,
		    node) {
			int width, height;
			char *num;

			/*
			 * Create the string to be displayed in the
			 * window and determine the height and width of
			 * the window.
			 */
			num = frame_selector(cur_frame->number);
			width = defaults.bar_x_padding * 2 +
			    rp_text_width(cur_screen, num, -1, NULL);
			height = (FONT_HEIGHT(cur_screen) +
			    defaults.bar_y_padding * 2);

			/* Create and map the window. */
			wins[i] = XCreateWindow(dpy, cur_screen->root,
			    cur_frame->x,
			    cur_frame->y, width,
			    height, defaults.bar_border_width,
			    CopyFromParent, CopyFromParent,
			    CopyFromParent,
			    CWOverrideRedirect|CWBorderPixel|CWBackPixel,
			    &attr);
			XMapWindow(dpy, wins[i]);
			XClearWindow(dpy, wins[i]);

			/* Display the frame's number inside the window. */
			rp_draw_string(cur_screen, wins[i], STYLE_NORMAL,
			    defaults.bar_x_padding,
			    defaults.bar_y_padding +
			    FONT_ASCENT(cur_screen), num, -1,
			    NULL, NULL);

			free(num);
			i++;
		}
		XSync(dpy, False);

		/* Read a key. */
		read_single_key(&c, &mod, keysym_buf, keysym_bufsize);

		/* Destroy our number windows and free the array. */
		for (i = 0; i < frames; i++)
			XDestroyWindow(dpy, wins[i]);

		free(wins);

		/* FIXME: We only handle one character long keysym names. */
		if (strlen(keysym_buf) == 1) {
			fnum = frame_selector_match(keysym_buf[0]);
			if (fnum == -1)
				return cmdret_new(RET_FAILURE,
				    "unknown frame selector `%s'", keysym_buf);
		} else {
			return cmdret_new(RET_FAILURE,
			    "frame selector too long `%s'", keysym_buf);
		}
	} else {
		fnum = string_to_positive_int(sbuf_get(s));
		if (fnum == -1)
			return cmdret_new(RET_FAILURE,
			    "invalid frame selector `%s', negative or too big",
			    sbuf_get(s));
	}

	/*
	 * Now that we have a frame number to go to, let's try to jump to it.
	 */
	frame = find_frame_number(rp_current_vscreen, fnum);
	if (frame) {
		/*
		 * We have to return a string, because commands get lists of
		 * strings.  Sucky, yes. The command is simply going to parse
		 * it back into an rp_frame.
		 */
		*arg = xmalloc(sizeof(struct cmdarg));
		(*arg)->type = arg_FRAME;
		(*arg)->string = NULL;
		(*arg)->arg.frame = frame;
		return NULL;
	}

	return cmdret_new(RET_FAILURE, "frame not found");
}

static cmdret *
read_window(struct argspec *spec, struct sbuf *s, struct cmdarg **arg)
{
	rp_window *win = NULL;
	char *name;
	int n;

	if (s)
		name = xstrdup(sbuf_get(s));
	else
		name = get_input(spec->prompt, hist_WINDOW, window_completions);

	if (name) {
		/* try by number */
		if ((n = string_to_positive_int(name)) >= 0) {
			rp_window_elem *elem = vscreen_find_window_by_number(
			    rp_current_vscreen, n);
			if (elem)
				win = elem->win;
		} else {
			/* try by name */
			win = find_window_name(name, 1);
			if (win == NULL)
				win = find_window_name(name, 0);
		}

		if (win) {
			*arg = xmalloc(sizeof(struct cmdarg));
			(*arg)->type = arg_WINDOW;
			(*arg)->arg.win = win;
			(*arg)->string = name;
			return NULL;
		}

		free(name);
		*arg = NULL;
		return cmdret_new(RET_SUCCESS, NULL);
	}

	/* user abort. */
	*arg = NULL;
	return cmdret_new(RET_SUCCESS, NULL);
}

static int
parse_wingravity(char *data)
{
	int ret = -1;

	if (!strcasecmp(data, "northwest") || !strcasecmp(data, "nw") || !strcmp(data, "7"))
		ret = NorthWestGravity;
	if (!strcasecmp(data, "north") || !strcasecmp(data, "n") || !strcmp(data, "8"))
		ret = NorthGravity;
	if (!strcasecmp(data, "northeast") || !strcasecmp(data, "ne") || !strcmp(data, "9"))
		ret = NorthEastGravity;
	if (!strcasecmp(data, "west") || !strcasecmp(data, "w") || !strcmp(data, "4"))
		ret = WestGravity;
	if (!strcasecmp(data, "center") || !strcasecmp(data, "c") || !strcmp(data, "5"))
		ret = CenterGravity;
	if (!strcasecmp(data, "east") || !strcasecmp(data, "e") || !strcmp(data, "6"))
		ret = EastGravity;
	if (!strcasecmp(data, "southwest") || !strcasecmp(data, "sw") || !strcmp(data, "1"))
		ret = SouthWestGravity;
	if (!strcasecmp(data, "south") || !strcasecmp(data, "s") || !strcmp(data, "2"))
		ret = SouthGravity;
	if (!strcasecmp(data, "southeast") || !strcasecmp(data, "se") || !strcmp(data, "3"))
		ret = SouthEastGravity;

	return ret;
}

static cmdret *
read_gravity(struct argspec *spec, struct sbuf *s, struct cmdarg **arg)
{
	char *input;

	if (s)
		input = xstrdup(sbuf_get(s));
	else
		input = get_input(spec->prompt, hist_GRAVITY,
		    trivial_completions);

	if (input) {
		int g = parse_wingravity(input);

		if (g == -1) {
			cmdret *ret = cmdret_new(RET_FAILURE,
			    "bad gravity '%s'", input);
			free(input);
			return ret;
		}

		*arg = xmalloc(sizeof(struct cmdarg));
		(*arg)->type = arg_GRAVITY;
		(*arg)->arg.gravity = g;
		(*arg)->string = input;

		return NULL;
	}

	*arg = NULL;
	return cmdret_new(RET_SUCCESS, NULL);
}

/*
 * Given a string, find a matching vscreen. First check if the string exactly
 * matches a vscreen name, then check if it is a number & lastly check if it
 * partially matches the name of a vscreen.
 */
static rp_vscreen *
find_vscreen(char *str)
{
	rp_vscreen *vscreen;
	int n;

	/* Exact matches are special cases. */
	if ((vscreen = screen_find_vscreen_by_name(rp_current_screen, str, 1)))
		return vscreen;

	/* Check if the user typed a vsceen number. */
	n = string_to_positive_int(str);
	if (n >= 0) {
		vscreen = screen_find_vscreen_by_number(rp_current_screen, n);
		if (vscreen)
			return vscreen;
	}

	vscreen = screen_find_vscreen_by_name(rp_current_screen, str, 0);
	return vscreen;
}

static cmdret *
read_vscreen(struct argspec *spec, struct sbuf *s, struct cmdarg **arg)
{
	char *input;

	if (s)
		input = xstrdup(sbuf_get(s));
	else
		input = get_input(spec->prompt, hist_VSCREEN,
		    vscreen_completions);

	if (input) {
		rp_vscreen *v = find_vscreen(input);
		if (v) {
			*arg = xmalloc(sizeof(struct cmdarg));
			(*arg)->type = arg_VSCREEN;
			(*arg)->arg.vscreen = v;
			(*arg)->string = input;
			return NULL;
		}

		cmdret *ret = cmdret_new(RET_FAILURE, "unknown vscreen '%s'",
		    input);
		free(input);
		return ret;
	}

	*arg = NULL;
	return cmdret_new(RET_SUCCESS, NULL);
}

static struct list_head *
hook_completions(char *str)
{
	struct list_head *list;
	struct rp_hook_db_entry *entry;

	/* Initialize our list. */
	list = xmalloc(sizeof(struct list_head));
	INIT_LIST_HEAD(list);

	for (entry = rp_hook_db; entry->name; entry++) {
		struct sbuf *hookname;

		hookname = sbuf_new(0);
		sbuf_copy(hookname, entry->name);
		list_add_tail(&hookname->node, list);
	}

	return list;
}

static cmdret *
read_hook(struct argspec *spec, struct sbuf *s, struct cmdarg **arg)
{
	char *input;

	if (s)
		input = xstrdup(sbuf_get(s));
	else
		input = get_input(spec->prompt, hist_HOOK, hook_completions);

	if (input) {
		struct list_head *hook = hook_lookup(input);

		if (hook) {
			*arg = xmalloc(sizeof(struct cmdarg));
			(*arg)->type = arg_HOOK;
			(*arg)->arg.hook = hook;
			(*arg)->string = input;
			return NULL;
		}

		cmdret *ret = cmdret_new(RET_FAILURE, "unknown hook '%s'",
		    input);
		free(input);
		return ret;
	}

	*arg = NULL;
	return cmdret_new(RET_SUCCESS, NULL);
}

static struct set_var *
find_variable(char *str)
{
	struct set_var *cur;

	list_for_each_entry(cur, &set_vars, node) {
		if (!strcmp(str, cur->var))
			return cur;
	}

	return NULL;
}

static struct list_head *
var_completions(char *str)
{
	struct list_head *list;
	struct set_var *cur;

	/* Initialize our list. */
	list = xmalloc(sizeof(struct list_head));
	INIT_LIST_HEAD(list);

	/* Grab all the vscreen names. */
	list_for_each_entry(cur, &set_vars, node) {
		struct sbuf *s;

		s = sbuf_new(0);
		sbuf_copy(s, cur->var);
		list_add_tail(&s->node, list);
	}

	return list;
}

static cmdret *
read_variable(struct argspec *spec, struct sbuf *s, struct cmdarg **arg)
{
	char *input;

	if (s)
		input = xstrdup(sbuf_get(s));
	else
		input = get_input(spec->prompt, hist_VARIABLE, var_completions);

	if (input) {
		struct set_var *var = find_variable(input);

		if (var == NULL) {
			cmdret *ret = cmdret_new(RET_FAILURE,
			    "unknown variable '%s'", input);
			free(input);
			return ret;
		}

		*arg = xmalloc(sizeof(struct cmdarg));
		(*arg)->type = arg_VARIABLE;
		(*arg)->arg.variable = var;
		(*arg)->string = input;

		return NULL;
	}

	*arg = NULL;
	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
read_number(struct argspec *spec, struct sbuf *s, struct cmdarg **arg)
{
	char *input;

	if (s)
		input = xstrdup(sbuf_get(s));
	else
		/* numbers should perhaps be more fine grained, or hist_NONE */
		input = get_input(spec->prompt, hist_OTHER,
		    trivial_completions);

	if (input) {
		char *ep;
		long lval;

		errno = 0;
		lval = strtol(input, &ep, 10);
		if (input[0] == '\0' || *ep != '\0')
			return cmdret_new(RET_FAILURE, "malformed number `%s'",
			    input);
		if ((errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN)) ||
		    (lval > INT_MAX || lval < INT_MIN))
			return cmdret_new(RET_FAILURE, "out of range number `%s'",
			    input);

		*arg = xmalloc(sizeof(struct cmdarg));
		(*arg)->type = arg_NUMBER;
		(*arg)->arg.number = lval;
		(*arg)->string = input;

		return NULL;
	}

	*arg = NULL;
	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
read_arg(struct argspec *spec, struct sbuf *s, struct cmdarg **arg,
    const char *command_name)
{
	cmdret *ret = NULL;

	switch (spec->type) {
	case arg_STRING:
	case arg_REST:
	case arg_RAW:
		ret = read_string(spec, s, hist_OTHER, trivial_completions, arg);
		break;
	case arg_KEYMAP:
		ret = read_keymap(spec, s, arg);
		break;
	case arg_KEY:
		ret = read_keydesc(spec, s, arg);
		break;
	case arg_NUMBER:
		ret = read_number(spec, s, arg);
		break;
	case arg_GRAVITY:
		ret = read_gravity(spec, s, arg);
		break;
	case arg_COMMAND:
		ret = read_command(spec, s, arg);
		break;
	case arg_SHELLCMD:
		ret = read_shellcmd(spec, s, arg, command_name);
		break;
	case arg_WINDOW:
		ret = read_window(spec, s, arg);
		break;
	case arg_FRAME:
		ret = read_frame(s, arg);
		break;
	case arg_VSCREEN:
		ret = read_vscreen(spec, s, arg);
		break;
	case arg_HOOK:
		ret = read_hook(spec, s, arg);
		break;
	case arg_VARIABLE:
		ret = read_variable(spec, s, arg);
		break;
	}

	return ret;
}

/* Return -1 on failure. Return the number of args on success. */
static cmdret *
parsed_input_to_args(int num_args, struct argspec *argspec,
    struct list_head *list, struct list_head *args, int *parsed_args,
    const char *command_name)
{
	struct sbuf *s;
	struct cmdarg *arg;
	cmdret *ret;

	PRINT_DEBUG(("list len: %d\n", list_size(list)));

	*parsed_args = 0;

	/* Convert the existing entries to cmdarg's. */
	list_for_each_entry(s, list, node) {
		if (*parsed_args >= num_args)
			break;

		ret = read_arg(&argspec[*parsed_args], s, &arg, command_name);
		/* If there was an error, then abort. */
		if (ret)
			return ret;

		list_add_tail(&arg->node, args);
		(*parsed_args)++;
	}

	return NULL;
}

/*
 * Prompt the user for missing arguments. Returns non-zero on failure. 0 on
 * success.
 */
static cmdret *
fill_in_missing_args(struct user_command *cmd, struct list_head *list,
    struct list_head *args, const char *command_name)
{
	cmdret *ret;
	struct cmdarg *arg;
	int i = 0;

	ret = parsed_input_to_args(cmd->num_args, cmd->args, list, args, &i,
	    command_name);
	if (ret)
		return ret;

	/* Fill in the rest of the required arguments. */
	for (; i < cmd->i_required_args; i++) {
		ret = read_arg(&cmd->args[i], NULL, &arg, command_name);
		if (ret)
			return ret;
		list_add_tail(&arg->node, args);
	}

	return NULL;
}

/*
 * Stick a list of sbuf's in list. if nargs >= 0 then only parse nargs
 * arguments and and the rest of the string to the list. Return 0 on success.
 * non-zero on failure. When raw is true, then when we hit nargs, we should
 * keep any whitespace at the beginning.  When false, gobble the whitespace.
 */
static cmdret *
parse_args(char *str, struct list_head * list, int nargs, int raw)
{
	cmdret *ret = NULL;
	char *i;
	char *tmp;
	int len = 0;
	int str_escape = 0;
	int in_str = 0;
	int gobble = 1;
	int parsed_args = 0;

	if (str == NULL)
		return NULL;

	tmp = xmalloc(strlen(str) + 1);

	for (i = str; *i; i++) {
		/* Have we hit the arg limit? */
		if (raw && parsed_args >= nargs) {
			struct sbuf *s = sbuf_new(0);
			if (!raw) {
				while (*i && isspace((unsigned char) *i))
					i++;
			}
			if (*i) {
				sbuf_concat(s, i);
				list_add_tail(&s->node, list);
			}
			len = 0;
			break;
		}

		/* Should we eat the whitespace? */
		if (gobble) {
			while (*i && isspace((unsigned char) *i))
				i++;
			gobble = 0;
		}

		/* Escaped characters always get added. */
		if (str_escape) {
			tmp[len] = *i;
			len++;
			str_escape = 0;
		} else if (*i == '\\') {
			str_escape = 1;
		} else if (*i == '"') {
			if (in_str) {
				/* End the arg. */
				struct sbuf *s = sbuf_new(0);

				sbuf_nconcat(s, tmp, len);
				list_add_tail(&s->node, list);
				len = 0;
				gobble = 1;
				in_str = 0;
				parsed_args++;
			} else if (len == 0) {
				/*
				 * A string open can only start at the
				 * beginning of an argument.
				 */
				in_str = 1;
			} else {
				ret = cmdret_new(RET_FAILURE,
				    "parse error in '%s'", str);
				break;
			}
		} else if (isspace((unsigned char) *i) && !in_str) {
			/* End the current arg, and start a new one. */
			struct sbuf *s = sbuf_new(0);
			sbuf_nconcat(s, tmp, len);
			list_add_tail(&s->node, list);
			len = 0;
			gobble = 1;
			parsed_args++;
		} else {
			/* Add the character to the argument. */
			tmp[len] = *i;
			len++;
		}
	}

	/* Add the remaining text in tmp. */
	if (ret == NULL && len) {
		struct sbuf *s = sbuf_new(0);
		sbuf_nconcat(s, tmp, len);
		list_add_tail(&s->node, list);
	}

	free(tmp);
	return ret;
}

/* Convert the list to an array, for easier access in commands. */
static struct cmdarg **
arg_array(struct list_head *head)
{
	int i = 0;
	struct cmdarg **args, *cur;

	args = xmalloc(sizeof(struct cmdarg *) * (list_size(head) + 1));
	list_for_each_entry(cur, head, node) {
		args[i] = cur;
		i++;
	}

	/* NULL terminate the array. */
	args[list_size(head)] = NULL;
	return args;
}

static void
arg_free(struct cmdarg *arg)
{
	if (!arg)
		return;

	/* read_frame doesn't fill in string. */
	free(arg->string);

	switch (arg->type) {
	case arg_KEY:
		free(arg->arg.key);
		break;
	case arg_REST:
	case arg_STRING:
	case arg_NUMBER:
	case arg_WINDOW:
	case arg_FRAME:
	case arg_COMMAND:
	case arg_SHELLCMD:
	case arg_KEYMAP:
	case arg_GRAVITY:
	case arg_VSCREEN:
	case arg_HOOK:
	case arg_VARIABLE:
	case arg_RAW:
		/* Do nothing */
		break;
	default:
		warnx("unknown arg type %d\n", arg->type);
		break;
	}

	free(arg);
}

cmdret *
command(int interactive, char *data)
{
	/* This static counter is used to exit from recursive alias calls. */
	static int alias_recursive_depth = 0;
	cmdret *result = NULL;
	char *cmd, *rest;
	char *input;
	struct user_command *uc;
	int i;

	if (data == NULL)
		return cmdret_new(RET_FAILURE, NULL);

	/* get a writable copy for strtok() */
	input = xstrdup(data);

	cmd = input;
	/* skip beginning whitespace. */
	while (*cmd && isspace((unsigned char) *cmd))
		cmd++;
	rest = cmd;
	/* skip til we get to whitespace */
	while (*rest && !isspace((unsigned char) *rest))
		rest++;
	/*
	 * mark that spot as the end of the command and make rest point to the
	 * rest of the string.
	 */
	if (*rest) {
		*rest = 0;
		rest++;
	}
	PRINT_DEBUG(("cmd==%s rest==%s\n", cmd, rest ? rest : "NULL"));

	/* Look for it in the aliases, first. */
	for (i = 0; i < alias_list_last; i++) {
		if (strcmp(cmd, alias_list[i].name) != 0)
			continue;

		struct sbuf *s;

		/*
		 * Append any arguments onto the end of the alias'
		 * command.
		 */
		s = sbuf_new(0);
		sbuf_concat(s, alias_list[i].alias);
		if (rest != NULL && *rest)
			sbuf_printf_concat(s, " %s", rest);

		alias_recursive_depth++;
		if (alias_recursive_depth >= MAX_ALIAS_RECURSIVE_DEPTH)
			result = cmdret_new(RET_FAILURE,
			    "command: alias recursion has exceeded "
			    "maximum depth");
		else
			result = command(interactive, sbuf_get(s));
		alias_recursive_depth--;

		sbuf_free(s);
		goto done;
	}

	/* If it wasn't an alias, maybe its a command. */
	list_for_each_entry(uc, &user_commands, node) {
		if (strcmp(cmd, uc->name) != 0)
			continue;

		struct sbuf *scur;
		struct cmdarg *acur;
		struct list_head *iter, *tmp;
		struct list_head head, args;
		int nargs = 0, raw = 0;

		INIT_LIST_HEAD(&args);
		INIT_LIST_HEAD(&head);

		/*
		 * We need to tell parse_args about arg_REST and
		 * arg_SHELLCMD.
		 */
		for (i = 0; i < uc->num_args; i++)
			if (uc->args[i].type == arg_REST ||
			    uc->args[i].type == arg_COMMAND ||
			    uc->args[i].type == arg_SHELLCMD ||
			    uc->args[i].type == arg_RAW) {
				raw = 1;
				nargs = i;
				break;
			}

		/* Parse the arguments and call the function. */
		result = parse_args(rest, &head, nargs, raw);
		if (result)
			goto free_lists;

		/* Interactive commands prompt the user for missing args. */
		if (interactive)
			result = fill_in_missing_args(uc, &head, &args,
			    uc->name);
		else {
			int parsed_args;
			result = parsed_input_to_args(uc->num_args,
			    uc->args, &head, &args, &parsed_args,
			    uc->name);
		}

		if (result == NULL) {
			if ((interactive && list_size(&args) < uc->i_required_args) ||
			    (!interactive && list_size(&args) < uc->ni_required_args)) {
				result = cmdret_new(RET_FAILURE,
				    "not enough arguments.");
				goto free_lists;
			} else if (list_size(&head) > uc->num_args) {
				result = cmdret_new(RET_FAILURE,
				    "command: too many arguments.");
				goto free_lists;
			} else {
				struct cmdarg **cmdargs = arg_array(&args);
				result = uc->func(interactive, cmdargs);
				free(cmdargs);
			}
		}
free_lists:
		/* Free the parsed strings */
		list_for_each_safe_entry(scur, iter, tmp, &head, node)
		    sbuf_free(scur);

		/* Free the args */
		list_for_each_safe_entry(acur, iter, tmp, &args, node)
		    arg_free(acur);

		goto done;
	}

	result = cmdret_new(RET_FAILURE, MESSAGE_UNKNOWN_COMMAND, cmd);

done:
	free(input);
	return result;
}

cmdret *
cmd_colon(int interactive, struct cmdarg **args)
{
	cmdret *result;
	char *input;

	if (args[0] == NULL)
		input = get_input(MESSAGE_PROMPT_COMMAND, hist_COMMAND,
		    colon_completions);
	else
		input = get_more_input(MESSAGE_PROMPT_COMMAND, ARG_STRING(0),
		    hist_COMMAND, BASIC, colon_completions);

	/* User aborted. */
	if (input == NULL)
		return cmdret_new(RET_FAILURE, NULL);

	result = command(1, input);
	free(input);
	return result;
}

cmdret *
cmd_exec(int interactive, struct cmdarg **args)
{
	spawn(ARG_STRING(0), current_frame(rp_current_vscreen));
	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_execw(int interactive, struct cmdarg **args)
{
	int status = -1;
	pid_t pid = spawn(ARG_STRING(0), current_frame(rp_current_vscreen));
	if (waitpid(pid, &status, 0) == -1)
		perror("cmd_execw");
	else
		status = WEXITSTATUS(status);
	return cmdret_new(status == 0? RET_SUCCESS: RET_FAILURE, NULL);
}

cmdret *
cmd_execa(int interactive, struct cmdarg **args)
{
	spawn(ARG_STRING(0), NULL);
	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_execf(int interactive, struct cmdarg **args)
{
	/*
	 * Tell spawn's shell to exec the command it finds, reusing the pid
	 * that fork() returned so matching to its NET_WM_PID later works
	 * properly.
	 */
	char *cmd = xsprintf("exec %s", ARG_STRING(1));
	spawn(cmd, ARG(0, frame));
	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_execv(int interactive, struct cmdarg **args)
{
	rp_vscreen *vscreen;
	if (!(vscreen = find_vscreen(ARG_STRING(0))))
		return cmdret_new(RET_FAILURE, NULL);
	vspawn(ARG_STRING(1), current_frame(vscreen), vscreen);
	return cmdret_new(RET_SUCCESS, NULL);
}

int
spawn(char *cmd, rp_frame *frame) { return vspawn(cmd, frame, NULL); }
int
vspawn(char *cmd, rp_frame *frame, rp_vscreen *vscreen)
{
	rp_child_info *child;
	int pid;

	pid = fork();
	if (pid == 0) {
		/*
		 * Some process setup to make sure the spawned process runs in
		 * its own session.
		 */
		putenv(rp_current_screen->display_string);
		if (setsid() == -1)
		{
			int ctty;

			ctty = open("/dev/tty", O_RDONLY);
			if (ctty != -1) {
				ioctl(ctty, TIOCNOTTY);
				close(ctty);
			}

			setpgid(0, 0);
		}

		execl("/bin/sh", "sh", "-c", cmd, (char *) NULL);
		_exit(1);
	}

	/* wait((int *) 0); */
	PRINT_DEBUG(("spawned %s, pid %d, frame %d, vscreen %d, screen %d\n",
		     cmd, pid, frame ? frame->number : -1,
		     (vscreen? vscreen: rp_current_vscreen)->number,
		     rp_current_screen->number));

	/* Add this child process to our list. */
	child = xmalloc(sizeof(rp_child_info));
	child->cmd = xstrdup(cmd);
	child->pid = pid;
	child->terminated = 0;
	child->frame = frame;
	child->vscreen = vscreen? vscreen: rp_current_vscreen;
	child->screen = rp_current_screen;
	child->window_mapped = 0;

	list_add(&child->node, &rp_children);

	return pid;
}

cmdret *
cmd_quit(int interactive, struct cmdarg **args)
{
	kill_signalled = 1;
	return cmdret_new(RET_SUCCESS, NULL);
}

/* Assign a new number to a window ala screen's number command. */
cmdret *
cmd_number(int interactive, struct cmdarg **args)
{
	int old_number, new_number;
	rp_window_elem *other_win, *win;

	/* Gather the args. */
	new_number = ARG(0, number);
	if (args[1])
		win = vscreen_find_window_by_number(rp_current_vscreen,
		    ARG(1, number));
	else
		win = vscreen_find_window(&rp_current_vscreen->mapped_windows,
		    current_window());

	/* Make the switch. */
	if (new_number >= 0 && win) {
		/* Find other window with same number and give it old number. */
		other_win = vscreen_find_window_by_number(rp_current_vscreen,
		    new_number);
		if (other_win != NULL) {
			old_number = win->number;
			other_win->number = old_number;

			/* Resort the window in the list */
			vscreen_resort_window(rp_current_vscreen, other_win);
		} else {
			numset_release(rp_current_vscreen->numset, win->number);
		}

		win->number = new_number;
		numset_add_num(rp_current_vscreen->numset, new_number);

		/* resort the the window in the list */
		vscreen_resort_window(rp_current_vscreen, win);

		/* Update the window list. */
		update_window_names(win->win->vscreen->screen,
		    defaults.window_fmt);
	}

	return cmdret_new(RET_SUCCESS, NULL);
}

/* Toggle the display of the program bar */
cmdret *
cmd_windows(int interactive, struct cmdarg **args)
{
	struct sbuf *window_list = NULL;
	int dummy;
	char *fmt;
	cmdret *ret;

	if (args[0] == NULL)
		fmt = defaults.window_fmt;
	else
		fmt = ARG_STRING(0);

	if (interactive) {
		rp_screen *s;
		s = rp_current_screen;
		ret = cmdret_new(RET_SUCCESS, NULL);
		if (defaults.bar_timeout == 0 && s->bar_is_raised) {
			hide_bar(s, 0);
			return ret;
		}
		show_bar(s, fmt);
	} else {
		window_list = sbuf_new(0);
		get_window_list(fmt, "\n", window_list, &dummy, &dummy);
		ret = cmdret_new(RET_SUCCESS, "%s", sbuf_get(window_list));
		sbuf_free(window_list);
	}
	return ret;
}

cmdret *
cmd_abort(int interactive, struct cmdarg **args)
{
	return cmdret_new(RET_SUCCESS, NULL);
}

/* Redisplay the current window by sending 2 resize events. */
cmdret *
cmd_redisplay(int interactive, struct cmdarg **args)
{
	force_maximize(current_window());
	return cmdret_new(RET_SUCCESS, NULL);
}

/* Reassign the prefix key. */
cmdret *
cmd_escape(int interactive, struct cmdarg **args)
{
	struct rp_key *key;
	rp_action *action;
	rp_keymap *map, *top;

	top = find_keymap(defaults.top_kmap);
	map = find_keymap(ROOT_KEYMAP);
	key = ARG(0, key);

	/* Update the "other" keybinding */
	action = find_keybinding(prefix_key.sym, prefix_key.state, map);
	if (action != NULL && !strcmp(action->data, "other")) {
		action->key = key->sym;
		action->state = key->state;
	}

	/* Update the "meta" keybinding */
	action = find_keybinding(prefix_key.sym, 0, map);
	if (action != NULL && !strcmp(action->data, "meta")) {
		action->key = key->sym;
		if (key->state != 0)
			action->state = 0;
		else
			action->state = RP_CONTROL_MASK;
	}

	/* Remove the grab on the current prefix key */
	ungrab_keys_all_wins();

	action = find_keybinding(prefix_key.sym, prefix_key.state, top);
	if (action != NULL && !strcmp(action->data, "readkey " ROOT_KEYMAP)) {
		action->key = key->sym;
		action->state = key->state;
	}

	/* Add the grab for the new prefix key */
	grab_keys_all_wins();

	/* Finally, keep track of the current prefix. */
	prefix_key.sym = key->sym;
	prefix_key.state = key->state;

	return cmdret_new(RET_SUCCESS, NULL);
}

/* User accessible call to display the passed in string. */
cmdret *
cmd_echo(int interactive, struct cmdarg **args)
{
	marked_message_printf(0, 0, "%s", ARG_STRING(0));

	return cmdret_new(RET_SUCCESS, NULL);
}


static cmdret *
read_split(char *str, int max, int *p)
{
	int a, b;

	if (sscanf(str, "%d/%d", &a, &b) == 2) {
		*p = (int) (max * (float) (a) / (float) (b));
	} else if (sscanf(str, "%d", p) == 1) {
		if (*p < 0)
			*p = max + *p;
	} else {
		/* Failed to read input. */
		return cmdret_new(RET_FAILURE, "bad split '%s'", str);
	}

	return NULL;
}

cmdret *
cmd_vsplit(int interactive, struct cmdarg **args)
{
	cmdret *ret;
	rp_frame *frame;
	int pixels;

	push_frame_undo(rp_current_vscreen);	/* fdump to stack */
	frame = current_frame(rp_current_vscreen);

	/* Default to dividing the frame in half. */
	if (args[0] == NULL)
		pixels = frame->height / 2;
	else {
		ret = read_split(ARG_STRING(0), frame->height, &pixels);
		if (ret)
			return ret;
	}

	if (pixels > 0)
		h_split_frame(frame, pixels);
	else
		return cmdret_new(RET_FAILURE, "vsplit: %s",
		    invalid_negative_arg);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_hsplit(int interactive, struct cmdarg **args)
{
	cmdret *ret;
	rp_frame *frame;
	int pixels;

	push_frame_undo(rp_current_vscreen);	/* fdump to stack */
	frame = current_frame(rp_current_vscreen);

	/* Default to dividing the frame in half. */
	if (args[0] == NULL)
		pixels = frame->width / 2;
	else {
		ret = read_split(ARG_STRING(0), frame->width, &pixels);
		if (ret)
			return ret;
	}

	if (pixels > 0)
		v_split_frame(frame, pixels);
	else
		return cmdret_new(RET_FAILURE, "hsplit: %s",
		    invalid_negative_arg);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_only(int interactive, struct cmdarg **args)
{
	push_frame_undo(rp_current_vscreen);	/* fdump to stack */
	remove_all_splits();
	maximize(current_window());

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_remove(int interactive, struct cmdarg **args)
{
	rp_frame *frame;

	push_frame_undo(rp_current_vscreen);	/* fdump to stack */

	if (num_frames(rp_current_vscreen) <= 1)
		return cmdret_new(RET_FAILURE,
		    "remove: cannot remove only frame");

	frame = find_frame_next(current_frame(rp_current_vscreen));
	if (frame) {
		remove_frame(current_frame(rp_current_vscreen));
		set_active_frame(frame, 0);
		show_frame_indicator(0);
	}

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_shrink(int interactive, struct cmdarg **args)
{
	push_frame_undo(rp_current_vscreen);	/* fdump to stack */
	resize_shrink_to_window(current_frame(rp_current_vscreen));
	return cmdret_new(RET_SUCCESS, NULL);
}

typedef struct resize_binding resize_binding;

struct resize_binding {
	struct rp_key key;
	enum resize_action {
		RESIZE_UNKNOWN = 0,
		RESIZE_VGROW,
		RESIZE_VSHRINK,
		RESIZE_HGROW,
		RESIZE_HSHRINK,
		RESIZE_TO_WINDOW,
		RESIZE_ABORT,
		RESIZE_END
	} action;
};

static resize_binding resize_bindings[] = {
	{ { INPUT_ABORT_KEY, INPUT_ABORT_MODIFIER }, RESIZE_ABORT},
	{ { RESIZE_VGROW_KEY, RESIZE_VGROW_MODIFIER }, RESIZE_VGROW},
	{ { RESIZE_VSHRINK_KEY, RESIZE_VSHRINK_MODIFIER }, RESIZE_VSHRINK},
	{ { RESIZE_HGROW_KEY, RESIZE_HGROW_MODIFIER }, RESIZE_HGROW},
	{ { RESIZE_HSHRINK_KEY, RESIZE_HSHRINK_MODIFIER }, RESIZE_HSHRINK},
	{ { RESIZE_SHRINK_TO_WINDOW_KEY, RESIZE_SHRINK_TO_WINDOW_MODIFIER }, RESIZE_TO_WINDOW},
	{ { RESIZE_END_KEY, RESIZE_END_MODIFIER }, RESIZE_END},

	/*
	 * Some more default keys (after the values from conf.h, so that they have lower
	 * priority): first the arrow keys:
	 */
	{ {XK_Escape, 0 }, RESIZE_ABORT},
	{ {XK_Down, 0 }, RESIZE_VGROW},
	{ {XK_Up, 0 }, RESIZE_VSHRINK},
	{ {XK_Right, 0 }, RESIZE_HGROW},
	{ {XK_Left, 0 }, RESIZE_HSHRINK},

	/* some vi-like bindings: */
	{ {XK_j, 0 }, RESIZE_VGROW},
	{ {XK_k, 0 }, RESIZE_VSHRINK},
	{ {XK_l, 0 }, RESIZE_HGROW},
	{ {XK_h, 0 }, RESIZE_HSHRINK},

	{ {0, 0 }, RESIZE_UNKNOWN},
};

cmdret *
cmd_resize(int interactive, struct cmdarg **args)
{
	rp_screen *s = rp_current_screen;

	/*
	 * If the user calls resize with arguments, treat it like the
	 * non-interactive version.
	 */
	if (interactive && args[0] == NULL) {
		char buffer[513];
		unsigned int mod;
		KeySym c;
		struct list_head *bk;

		/*
		 * If we haven't got at least 2 frames, there isn't anything to
		 * scale.
		 */
		if (num_frames(rp_current_vscreen) < 2)
			return cmdret_new(RET_FAILURE, NULL);

		/* Save the frameset in case the user aborts. */
		bk = vscreen_copy_frameset(rp_current_vscreen);

		/* Get ready to read keys. */
		grab_rat();
		XGrabKeyboard(dpy, s->key_window, False, GrabModeAsync,
		    GrabModeAsync, CurrentTime);

		while (1) {
			struct resize_binding *binding;

			show_frame_message(defaults.resize_fmt);
			read_key(&c, &mod, buffer, sizeof(buffer));

			/* Convert the mask to be compatible with us. */
			mod = x11_mask_to_rp_mask(mod);

			for (binding = resize_bindings; binding->action; binding++) {
				if (c == binding->key.sym &&
				    mod == binding->key.state)
					break;
			}

			if (binding->action == RESIZE_VGROW)
				resize_frame_vertically(
				    current_frame(rp_current_vscreen),
				    defaults.frame_resize_unit);
			else if (binding->action == RESIZE_VSHRINK)
				resize_frame_vertically(
				    current_frame(rp_current_vscreen),
				    -defaults.frame_resize_unit);
			else if (binding->action == RESIZE_HGROW)
				resize_frame_horizontally(
				    current_frame(rp_current_vscreen),
				    defaults.frame_resize_unit);
			else if (binding->action == RESIZE_HSHRINK)
				resize_frame_horizontally(
				    current_frame(rp_current_vscreen),
				    -defaults.frame_resize_unit);
			else if (binding->action == RESIZE_TO_WINDOW)
				resize_shrink_to_window(
				    current_frame(rp_current_vscreen));
			else if (binding->action == RESIZE_ABORT) {
				rp_frame *cur;

				vscreen_restore_frameset(rp_current_vscreen, bk);
				list_for_each_entry(cur,
				    &(rp_current_vscreen->frames), node) {
					maximize_all_windows_in_frame(cur);
				}
				break;
			} else if (binding->action == RESIZE_END) {
				frameset_free(bk);
				break;
			}
		}

		/* It is our responsibility to free this. */
		free(bk);

		hide_frame_indicator();
		ungrab_rat();
		XUngrabKeyboard(dpy, CurrentTime);
	} else {
		if (args[0] && args[1]) {
			resize_frame_horizontally(
			    current_frame(rp_current_vscreen),
			    ARG(0, number));
			resize_frame_vertically(
			    current_frame(rp_current_vscreen),
			    ARG(1, number));
		} else
			return cmdret_new(RET_FAILURE,
			    "resize: two numeric arguments required");
	}

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_resizeunit(struct cmdarg **args)
{
	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d",
		    defaults.frame_resize_unit);

	if (ARG(0, number) >= 0)
		defaults.frame_resize_unit = ARG(0, number);
	else
		return cmdret_new(RET_FAILURE, "set resizeunit: %s",
		    invalid_negative_arg);

	return cmdret_new(RET_SUCCESS, NULL);
}

/* banish the rat pointer */
cmdret *
cmd_banish(int interactive, struct cmdarg **args)
{
	rp_screen *s;

	s = rp_current_screen;

	XWarpPointer(dpy, None, s->root, 0, 0, 0, 0, s->left + s->width - 2,
	    s->top + s->height - 2);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_banishrel(int interactive, struct cmdarg **args)
{
	rp_screen *s = rp_current_screen;
	rp_window *w = current_window();
	rp_frame *f = current_frame(rp_current_vscreen);

	if (w)
		XWarpPointer(dpy, None, w->w, 0, 0, 0, 0, w->x + w->width - 2,
		   w->y + w->height - 2);
	else
		XWarpPointer(dpy, None, s->root, 0, 0, 0, 0, f->x + f->width,
		   f->y + f->height);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_ratinfo(int interactive, struct cmdarg **args)
{
	rp_screen *s;
	Window root_win, child_win;
	int mouse_x, mouse_y, root_x, root_y;
	unsigned int mask;

	s = rp_current_screen;
	XQueryPointer(dpy, s->root, &root_win, &child_win, &mouse_x, &mouse_y,
	    &root_x, &root_y, &mask);

	return cmdret_new(RET_SUCCESS, "%d %d", mouse_x, mouse_y);
}

cmdret *
cmd_ratrelinfo(int interactive, struct cmdarg **args)
{
	rp_screen *s;
	rp_window *rpw;
	rp_frame *f;
	Window root_win, child_win;
	int mouse_x, mouse_y, root_x, root_y;
	unsigned int mask;

	s = rp_current_screen;
	rpw = current_window();
	f = current_frame(rp_current_vscreen);

	if (rpw)
		XQueryPointer(dpy, rpw->w, &root_win, &child_win, &mouse_x,
		    &mouse_y, &root_x, &root_y, &mask);
	else {
		XQueryPointer(dpy, s->root, &root_win, &child_win, &mouse_x,
		    &mouse_y, &root_x, &root_y, &mask);
		root_x -= f->x;
		root_y -= f->y;
	}

	return cmdret_new(RET_SUCCESS, "%d %d", root_x, root_y);
}

cmdret *
cmd_ratwarp(int interactive, struct cmdarg **args)
{
	rp_screen *s;

	s = rp_current_screen;
	XWarpPointer(dpy, None, s->root, 0, 0, 0, 0, ARG(0, number),
	    ARG(1, number));
	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_ratrelwarp(int interactive, struct cmdarg **args)
{
	XWarpPointer(dpy, None, None, 0, 0, 0, 0, ARG(0, number),
	    ARG(1, number));
	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_ratclick(int interactive, struct cmdarg **args)
{
	int button = 1;

	if (args[0]) {
		button = ARG(0, number);
		if (button < 1 || button > 3)
			return cmdret_new(RET_SUCCESS,
			    "ratclick: invalid argument");
	}

	XTestFakeButtonEvent(dpy, button, True, CurrentTime);
	XTestFakeButtonEvent(dpy, button, False, CurrentTime);
	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_rathold(int interactive, struct cmdarg **args)
{
	int button = 1;

	if (args[1]) {
		button = ARG(1, number);
		if (button < 1 || button > 3)
			return cmdret_new(RET_SUCCESS,
			    "ratclick: invalid argument");
	}

	if (!strcmp(ARG_STRING(0), "down"))
		XTestFakeButtonEvent(dpy, button, True, CurrentTime);
	else if (!strcmp(ARG_STRING(0), "up"))
		XTestFakeButtonEvent(dpy, button, False, CurrentTime);
	else
		return cmdret_new(RET_FAILURE,
		    "rathold: '%s' invalid argument", ARG_STRING(0));

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_curframe(int interactive, struct cmdarg **args)
{
	if (interactive) {
		show_frame_indicator(1);
		return cmdret_new(RET_SUCCESS, NULL);
	}

	return cmdret_new(RET_SUCCESS, "%d",
	    current_frame(rp_current_vscreen)->number);
}

cmdret *
cmd_help(int interactive, struct cmdarg **args)
{
	rp_keymap *map;

	if (args[0])
		map = ARG(0, keymap);
	else
		map = find_keymap(ROOT_KEYMAP);

	if (interactive) {
		rp_screen *s = rp_current_screen;
		int i, old_i;
		int x = 20;
		int y = 20;
		int header_offset;
		int width, max_width = 0;
		/* 1 if we are drawing keys, 0 if we are drawing commands */
		int drawing_keys = 1;
		char *keysym_name;

		/* Switch to the default colormap. */
		if (current_window())
			XUninstallColormap(dpy, current_window()->colormap);
		XInstallColormap(dpy, s->def_cmap);

		XMapRaised(dpy, s->help_window);

		rp_draw_string(s, s->help_window, STYLE_NORMAL,
		    x, y + FONT_ASCENT(s), PROGNAME " key bindings", -1, NULL,
		    NULL);

		y += FONT_HEIGHT(s) * 2;

		/* Only print the "Command key" for the root keymap */
		if (map == find_keymap(ROOT_KEYMAP)) {
			rp_draw_string(s, s->help_window, STYLE_NORMAL,
			    x, y + FONT_ASCENT(s),
			    "Command key: ", -1, NULL, NULL);

			keysym_name = keysym_to_string(prefix_key.sym,
			    prefix_key.state);
			rp_draw_string(s, s->help_window, STYLE_NORMAL,
			    x + rp_text_width(s, "Command key: ", -1, NULL),
			    y + FONT_ASCENT(s),
			    keysym_name, -1, NULL, NULL);
			free(keysym_name);

			y += FONT_HEIGHT(s) * 2;
		}
		header_offset = y;

		i = old_i = 0;
		while (i < map->actions_last && old_i < map->actions_last) {
			if (drawing_keys) {
				keysym_name = keysym_to_string(map->actions[i].key,
				    map->actions[i].state);

				rp_draw_string(s, s->help_window, STYLE_NORMAL,
				    x, y + FONT_ASCENT(s),
				    keysym_name, -1, NULL, NULL);

				width = rp_text_width(s, keysym_name, -1, NULL);
				if (width > max_width)
					max_width = width;

				free(keysym_name);
			} else {
				rp_draw_string(s, s->help_window, STYLE_NORMAL,
				    x, y + FONT_ASCENT(s),
				    map->actions[i].data, -1, NULL, NULL);

				width = rp_text_width(s, map->actions[i].data,
				    -1, NULL);
				if (width > max_width)
					max_width = width;
			}

			y += FONT_HEIGHT(s);
			/* Make sure the next line fits entirely within the window. */
			if (y + FONT_HEIGHT(s) >= (s->top + s->height)) {
				if (drawing_keys) {
					x += max_width + 10;
					drawing_keys = 0;
					i = old_i;
				} else {
					x += max_width + 20;
					drawing_keys = 1;
					i++;
					old_i = i;
				}

				max_width = 0;
				y = header_offset;
			} else {
				i++;
				if (i == map->actions_last && drawing_keys) {
					x += max_width + 10;
					drawing_keys = 0;
					y = header_offset;
					i = old_i;
					max_width = 0;
				}
			}
		}

		read_any_key();
		XUnmapWindow(dpy, s->help_window);

		/* Possibly restore colormap. */
		if (current_window()) {
			XUninstallColormap(dpy, s->def_cmap);
			XInstallColormap(dpy, current_window()->colormap);
		}
		/* The help window overlaps the bar, so redraw it. */
		if (rp_current_screen->bar_is_raised)
			redraw_last_message();

		return cmdret_new(RET_SUCCESS, NULL);
	} else {
		struct sbuf *help_list;
		char *keysym_name;
		int i;
		cmdret *ret;

		help_list = sbuf_new(0);

		for (i = 0; i < map->actions_last; i++) {
			keysym_name = keysym_to_string(map->actions[i].key,
			    map->actions[i].state);
			sbuf_concat(help_list, keysym_name);
			free(keysym_name);
			sbuf_concat(help_list, " ");
			sbuf_concat(help_list, map->actions[i].data);
			if (i < map->actions_last - 1)
				sbuf_concat(help_list, "\n");
		}

		ret = cmdret_new(RET_SUCCESS, "%s", sbuf_get(help_list));
		sbuf_free(help_list);
		return ret;
	}

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
set_rudeness(struct cmdarg **args)
{
	int num;

	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d",
		    rp_honour_transient_raise |
		    (rp_honour_normal_raise << 1) |
		    (rp_honour_transient_map << 2) |
		    (rp_honour_normal_map << 3) |
		    (rp_honour_vscreen_switch << 4));

	num = ARG(0, number);
	if (num < 0 || num > 31)
		return cmdret_new(RET_FAILURE, "rudeness: invalid level '%s'",
		    ARG_STRING(0));

	rp_honour_transient_raise = num & 1 ? 1 : 0;
	rp_honour_normal_raise = num & 2 ? 1 : 0;
	rp_honour_transient_map = num & 4 ? 1 : 0;
	rp_honour_normal_map = num & 8 ? 1 : 0;
	rp_honour_vscreen_switch = num & 16 ? 1 : 0;

	return cmdret_new(RET_SUCCESS, NULL);
}

char *
wingravity_to_string(int g)
{
	switch (g) {
	case NorthWestGravity:
		return "nw";
	case WestGravity:
		return "w";
	case SouthWestGravity:
		return "sw";
	case NorthGravity:
		return "n";
	case CenterGravity:
		return "c";
	case SouthGravity:
		return "s";
	case NorthEastGravity:
		return "ne";
	case EastGravity:
		return "e";
	case SouthEastGravity:
		return "se";
	}

	PRINT_DEBUG(("Unknown gravity!\n"));
	return "Unknown";
}

cmdret *
cmd_gravity(int interactive, struct cmdarg **args)
{
	int gravity;
	rp_window *win;

	win = current_window();
	if (!win)
		return cmdret_new(RET_FAILURE, NULL);

	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%s",
		    wingravity_to_string(win->gravity));

	if ((gravity = parse_wingravity(ARG_STRING(0))) < 0)
		return cmdret_new(RET_FAILURE, "gravity: unknown gravity");
	else {
		win->gravity = gravity;
		maximize(win);
	}

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_wingravity(struct cmdarg **args)
{
	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%s",
		    wingravity_to_string(defaults.win_gravity));

	defaults.win_gravity = ARG(0, gravity);

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_transgravity(struct cmdarg **args)
{
	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%s",
		    wingravity_to_string(defaults.trans_gravity));

	defaults.trans_gravity = ARG(0, gravity);

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_maxsizegravity(struct cmdarg **args)
{
	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%s",
		    wingravity_to_string(defaults.maxsize_gravity));

	defaults.maxsize_gravity = ARG(0, gravity);

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_msgwait(struct cmdarg **args)
{
	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d", defaults.bar_timeout);

	if (ARG(0, number) < 0)
		return cmdret_new(RET_FAILURE, "msgwait: %s",
		    invalid_negative_arg);
	else
		defaults.bar_timeout = ARG(0, number);

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_framemsgwait(struct cmdarg **args)
{
	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d",
		    defaults.frame_indicator_timeout);

	if (ARG(0, number) < -1)
		return cmdret_new(RET_FAILURE, "framemsgwait: %s",
		    invalid_negative_arg);
	else
		defaults.frame_indicator_timeout = ARG(0, number);

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_bargravity(struct cmdarg **args)
{
	rp_screen *s;

	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%s",
		    wingravity_to_string(defaults.bar_location));

	mark_edge_frames();

	defaults.bar_location = ARG(0, gravity);

	list_for_each_entry(s, &rp_screens, node) {
		screen_update_workarea(s);
		screen_update_frames(s);
	}

	return cmdret_new(RET_SUCCESS, NULL);
}

static void
update_gc(rp_screen * s)
{
	XGCValues gcv;

	gcv.foreground = rp_glob_screen.fgcolor;
	gcv.background = rp_glob_screen.bgcolor;
	gcv.function = GXcopy;
	gcv.line_width = 1;
	gcv.subwindow_mode = IncludeInferiors;
	XFreeGC(dpy, s->normal_gc);
	s->normal_gc = XCreateGC(dpy, s->root,
	    GCForeground | GCBackground | GCFunction | GCLineWidth |
	    GCSubwindowMode, &gcv);
	gcv.foreground = rp_glob_screen.bgcolor;
	gcv.background = rp_glob_screen.fgcolor;
	XFreeGC(dpy, s->inverse_gc);
	s->inverse_gc = XCreateGC(dpy, s->root,
	    GCForeground | GCBackground | GCFunction | GCLineWidth |
	    GCSubwindowMode, &gcv);
}

static cmdret *
set_historysize(struct cmdarg **args)
{
	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d", defaults.history_size);

	if (ARG(0, number) < 0)
		return cmdret_new(RET_FAILURE, "set historysize: %s",
		    invalid_negative_arg);

	defaults.history_size = ARG(0, number);
	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_font(struct cmdarg **args)
{
	XftFont *font;
	rp_screen *s;

	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%s", defaults.font_string);

	mark_edge_frames();

	list_for_each_entry(s, &rp_screens, node) {
		font = XftFontOpenName(dpy, s->screen_num, ARG_STRING(0));
		if (font == NULL)
			return cmdret_new(RET_FAILURE, "set font: unknown font");

		XftFontClose(dpy, s->xft_font);
		s->xft_font = font;
	}

	free(defaults.font_string);

	defaults.font_string = xstrdup(ARG_STRING(0));

	list_for_each_entry(s, &rp_screens, node) {
		screen_update_frames(s);
	}

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_padding(struct cmdarg **args)
{
	rp_screen *s;
	int l, t, r, b;

	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d %d %d %d",
		    defaults.padding_left,
		    defaults.padding_top,
		    defaults.padding_right,
		    defaults.padding_bottom);

	l = ARG(0, number);
	t = ARG(1, number);
	r = ARG(2, number);
	b = ARG(3, number);

	if (l < 0 || t < 0 || r < 0 || b < 0)
		return cmdret_new(RET_FAILURE, "set padding: %s",
		    invalid_negative_arg);

	mark_edge_frames();

	defaults.padding_left = l;
	defaults.padding_right = r;
	defaults.padding_top = t;
	defaults.padding_bottom = b;

	list_for_each_entry(s, &rp_screens, node) {
		screen_update_workarea(s);
		screen_update_frames(s);
	}

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_border(struct cmdarg **args)
{
	rp_screen *s;

	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d",
		    defaults.window_border_width);

	if (ARG(0, number) < 0)
		return cmdret_new(RET_FAILURE, "set border: %s",
		    invalid_negative_arg);

	mark_edge_frames();

	defaults.window_border_width = ARG(0, number);

	list_for_each_entry(s, &rp_screens, node) {
		screen_update_frames(s);
	}

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_onlyborder(struct cmdarg **args)
{
	rp_screen *s;

	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d", defaults.only_border);

	if (ARG(0, number) != 0 && ARG(0, number) != 1)
		return cmdret_new(RET_FAILURE, "set onlyborder: invalid argument");

	mark_edge_frames();

	defaults.only_border = ARG(0, number);

	list_for_each_entry(s, &rp_screens, node) {
		screen_update_frames(s);
	}

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_barborder(struct cmdarg **args)
{
	rp_screen *s;

	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d", defaults.bar_border_width);

	if (ARG(0, number) < 0)
		return cmdret_new(RET_FAILURE, "set barborder: %s",
		    invalid_negative_arg);

	mark_edge_frames();

	defaults.bar_border_width = ARG(0, number);

	/* Update the frame and bar windows. */
	list_for_each_entry(s, &rp_screens, node) {
		XSetWindowBorderWidth(dpy, s->bar_window,
		    defaults.bar_border_width);
		XSetWindowBorderWidth(dpy, s->frame_window,
		    defaults.bar_border_width);
		XSetWindowBorderWidth(dpy, s->input_window,
		    defaults.bar_border_width);

		screen_update_workarea(s);
		screen_update_frames(s);
	}

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_barinpadding(struct cmdarg **args)
{
	rp_screen *s;
	int new_value;

	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d", defaults.bar_in_padding);

	new_value = ARG(0, number);
	if (new_value != 0 && new_value != 1)
		return cmdret_new(RET_FAILURE,
		    "set barinpadding: invalid argument");

	mark_edge_frames();

	defaults.bar_in_padding = new_value;

	list_for_each_entry(s, &rp_screens, node) {
		screen_update_workarea(s);
		screen_update_frames(s);
	}

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_inputwidth(struct cmdarg **args)
{
	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d", defaults.input_window_size);

	if (ARG(0, number) < 0)
		return cmdret_new(RET_FAILURE, "set inputwidth: %s",
		    invalid_negative_arg);

	defaults.input_window_size = ARG(0, number);
	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_waitcursor(struct cmdarg **args)
{
	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d",
		    defaults.wait_for_key_cursor);

	if (ARG(0, number) != 0 && ARG(0, number) != 1)
		return cmdret_new(RET_FAILURE,
		    "set waitcursor: invalid argument");

	defaults.wait_for_key_cursor = ARG(0, number);
	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_infofmt(struct cmdarg **args)
{
	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%s", defaults.info_fmt);

	free(defaults.info_fmt);
	defaults.info_fmt = xstrdup(ARG_STRING(0));

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_topkmap(struct cmdarg **args)
{
	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%s", defaults.top_kmap);

	if (!find_keymap(ARG_STRING(0)))
		return cmdret_new(RET_FAILURE, "Unknown keymap %s",
		    ARG_STRING(0));

	ungrab_keys_all_wins();

	free(defaults.top_kmap);
	defaults.top_kmap = xstrdup(ARG_STRING(0));

	grab_keys_all_wins();
	XSync(dpy, False);

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_winfmt(struct cmdarg **args)
{
	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%s", defaults.window_fmt);

	free(defaults.window_fmt);
	defaults.window_fmt = xstrdup(ARG_STRING(0));

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_winname(struct cmdarg **args)
{
	char *name;

	if (args[0] == NULL)
		switch (defaults.win_name) {
		case WIN_NAME_TITLE:
			return cmdret_new(RET_SUCCESS, "title");
		case WIN_NAME_RES_NAME:
			return cmdret_new(RET_SUCCESS, "name");
		case WIN_NAME_RES_CLASS:
			return cmdret_new(RET_SUCCESS, "class");
		default:
			PRINT_DEBUG(("Unknown win_name\n"));
			return cmdret_new(RET_FAILURE, "unknown");
		}

	name = ARG_STRING(0);

	if (!strncmp(name, "title", sizeof("title")))
		defaults.win_name = WIN_NAME_TITLE;
	else if (!strncmp(name, "name", sizeof("name")))
		defaults.win_name = WIN_NAME_RES_NAME;
	else if (!strncmp(name, "class", sizeof("class")))
		defaults.win_name = WIN_NAME_RES_CLASS;
	else
		return cmdret_new(RET_FAILURE,
		    "set winname: invalid argument `%s'", name);

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_framefmt(struct cmdarg **args)
{
	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%s", defaults.frame_fmt);

	free(defaults.frame_fmt);
	defaults.frame_fmt = xstrdup(ARG_STRING(0));

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_fgcolor(struct cmdarg **args)
{
	XColor color, junk;
	rp_screen *s;

	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%s", defaults.fgcolor_string);

	list_for_each_entry(s, &rp_screens, node) {
		if (!XAllocNamedColor(dpy, s->def_cmap, ARG_STRING(0), &color,
		    &junk))
			return cmdret_new(RET_FAILURE,
			    "set fgcolor: unknown color");

		rp_glob_screen.fgcolor = color.pixel | (0xff << 24);
		update_gc(s);

		if (!XftColorAllocName(dpy, DefaultVisual(dpy, s->screen_num),
		    DefaultColormap(dpy, s->screen_num), ARG_STRING(0),
		    &s->xft_fgcolor))
			return cmdret_new(RET_FAILURE,
			    "set fgcolor: unknown color");

		free(defaults.fgcolor_string);
		defaults.fgcolor_string = xstrdup(ARG_STRING(0));
	}

	redraw_sticky_bar_text(1);

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_bgcolor(struct cmdarg **args)
{
	XColor color, junk;
	rp_screen *s;

	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%s", defaults.bgcolor_string);

	list_for_each_entry(s, &rp_screens, node) {
		if (!XAllocNamedColor(dpy, s->def_cmap, ARG_STRING(0), &color,
		    &junk))
			return cmdret_new(RET_FAILURE,
			    "set bgcolor: unknown color");

		color.pixel |= (0xff << 24);
		rp_glob_screen.bgcolor = color.pixel;
		update_gc(s);
		XSetWindowBackground(dpy, s->bar_window, color.pixel);
		XSetWindowBackground(dpy, s->input_window, color.pixel);
		XSetWindowBackground(dpy, s->frame_window, color.pixel);
		XSetWindowBackground(dpy, s->help_window, color.pixel);

		if (!XftColorAllocName(dpy, DefaultVisual(dpy, s->screen_num),
		    DefaultColormap(dpy, s->screen_num), ARG_STRING(0),
		    &s->xft_bgcolor))
			return cmdret_new(RET_FAILURE,
			    "set fgcolor: unknown color");

		free(defaults.bgcolor_string);
		defaults.bgcolor_string = xstrdup(ARG_STRING(0));
	}

	redraw_sticky_bar_text(1);

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_fwcolor(struct cmdarg **args)
{
	XColor color, junk;
	rp_window *win = current_window();
	rp_screen *s;

	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%s", defaults.fwcolor_string);

	list_for_each_entry(s, &rp_screens, node) {
		if (!XAllocNamedColor(dpy, s->def_cmap, ARG_STRING(0), &color,
		    &junk))
			return cmdret_new(RET_FAILURE,
			    "set fwcolor: unknown color");

		rp_glob_screen.fwcolor = color.pixel | (0xff << 24);
		update_gc(s);

		free(defaults.fwcolor_string);
		defaults.fwcolor_string = xstrdup(ARG_STRING(0));
	}

	/* Update current window. */
	if (win != NULL)
		XSetWindowBorder(dpy, win->w, rp_glob_screen.fwcolor);

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_bwcolor(struct cmdarg **args)
{
	XColor color, junk;
	rp_window *win, *cur_win = current_window();
	rp_screen *s;

	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%s", defaults.bwcolor_string);

	list_for_each_entry(s, &rp_screens, node) {
		if (!XAllocNamedColor(dpy, s->def_cmap, ARG_STRING(0), &color,
		    &junk))
			return cmdret_new(RET_FAILURE,
			    "set bwcolor: unknown color");

		rp_glob_screen.bwcolor = color.pixel | (0xff << 24);
		update_gc(s);

		free(defaults.bwcolor_string);
		defaults.bwcolor_string = xstrdup(ARG_STRING(0));
	}

	/* Update all the visible windows. */
	list_for_each_entry(win, &rp_mapped_window, node) {
		if (win != cur_win)
			XSetWindowBorder(dpy, win->w, rp_glob_screen.bwcolor);
	}

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_barbordercolor(struct cmdarg **args)
{
	XColor color, junk;
	rp_screen *s;

	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%s",
		    defaults.barbordercolor_string);

	list_for_each_entry(s, &rp_screens, node) {
		if (!XAllocNamedColor(dpy, s->def_cmap, ARG_STRING(0), &color,
		    &junk))
			return cmdret_new(RET_FAILURE,
			    "set barbordercolor: unknown color");

		color.pixel |= (0xff << 24);
		rp_glob_screen.bar_bordercolor = color.pixel;
		update_gc(s);
		XSetWindowBorder(dpy, s->bar_window, color.pixel);
		XSetWindowBorder(dpy, s->input_window, color.pixel);
		XSetWindowBorder(dpy, s->frame_window, color.pixel);
		XSetWindowBorder(dpy, s->help_window, color.pixel);

		free(defaults.barbordercolor_string);
		defaults.barbordercolor_string = xstrdup(ARG_STRING(0));
	}

	redraw_sticky_bar_text(1);

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_vscreens(struct cmdarg **args)
{
	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d", defaults.vscreens);

	if (ARG(0, number) < 1)
		return cmdret_new(RET_FAILURE, "vscreens: invalid argument");

	if (vscreens_resize(ARG(0, number)) != 0)
		return cmdret_new(RET_FAILURE, "vscreens: failed resizing");

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_gap(struct cmdarg **args)
{
	rp_screen *s;

	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d", defaults.gap);

	if (ARG(0, number) < 0)
		return cmdret_new(RET_FAILURE, "gap: invalid argument");

	mark_edge_frames();

	defaults.gap = ARG(0, number);

	list_for_each_entry(s, &rp_screens, node) {
		screen_update_workarea(s);
		screen_update_frames(s);
	}

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_ignoreresizehints(struct cmdarg **args)
{
	rp_screen *s;

	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d",
		    defaults.ignore_resize_hints);

	if (ARG(0, number) < 0 || ARG(0, number) > 1)
		return cmdret_new(RET_FAILURE,
		    "ignoreresizehints: invalid argument");

	mark_edge_frames();

	defaults.ignore_resize_hints = ARG(0, number);

	list_for_each_entry(s, &rp_screens, node) {
		screen_update_frames(s);
	}

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_resizefmt(struct cmdarg **args)
{
	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%s", defaults.resize_fmt);

	free(defaults.resize_fmt);
	defaults.resize_fmt = xstrdup(ARG_STRING(0));

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_winaddcurvscreen(struct cmdarg **args)
{
	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d",
		    defaults.win_add_cur_vscreen);

	if (ARG(0, number) < 0 || ARG(0, number) > 1)
		return cmdret_new(RET_FAILURE,
		    "winaddcurvscreen: invalid argument");

	defaults.win_add_cur_vscreen = ARG(0, number);

	return cmdret_new(RET_SUCCESS, NULL);
}
cmdret *
cmd_setenv(int interactive, struct cmdarg **args)
{
	const char *var = ARG_STRING(0), *val = ARG_STRING(1);

	PRINT_DEBUG(("setenv (\"%s\", \"%s\", 1)\n", var, val));
	if (setenv(var, val, 1) == -1)
		return cmdret_new(RET_FAILURE, "cmd_setenv failed: %s",
		    strerror(errno));

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_getenv(int interactive, struct cmdarg **args)
{
	char *value;

	value = getenv(ARG_STRING(0));
	if (value)
		return cmdret_new(RET_SUCCESS, "%s", value);

	return cmdret_new(RET_FAILURE, NULL);
}

/*
 * Thanks to Gergely Nagy <algernon@debian.org> for the original patch.
 */
cmdret *
cmd_chdir(int interactive, struct cmdarg **args)
{
	const char *dir;

	if (args[0] == NULL) {
		dir = get_homedir();
		if (dir == NULL) {
			return cmdret_new(RET_FAILURE,
			    "chdir: unable to find your HOME directory");
		}
	} else
		dir = ARG_STRING(0);

	if (chdir(dir) == -1)
		return cmdret_new(RET_FAILURE, "chdir: %s: %s", dir,
		    strerror(errno));

	return cmdret_new(RET_SUCCESS, NULL);
}

/*
 * Thanks to Gergely Nagy <algernon@debian.org> for the original patch.
 */
cmdret *
cmd_unsetenv(int interactive, struct cmdarg **args)
{
	const char *var = ARG_STRING(0);

	/*
	 * Use unsetenv() where possible since putenv("FOO") is not legit
	 * everywhere
	 */
	if (unsetenv(var) == -1)
		return cmdret_new(RET_FAILURE, "cmd_unsetenv failed: %s",
		    strerror(errno));

	return cmdret_new(RET_SUCCESS, NULL);
}

/*
 * Thanks to Gergely Nagy <algernon@debian.org> for the original patch.
 */
cmdret *
cmd_info(int interactive, struct cmdarg **args)
{
	struct sbuf *buf;
	if (current_window() != NULL) {
		rp_window *win = current_window();
		rp_window_elem *win_elem;
		win_elem = vscreen_find_window(&rp_current_vscreen->mapped_windows,
		    win);
		if (!win_elem)
			win_elem = vscreen_find_window(
			    &rp_current_vscreen->unmapped_windows, win);

		if (win_elem) {
			char *s;
			cmdret *ret;

			if (args[0] == NULL)
				s = defaults.info_fmt;
			else
				s = ARG_STRING(0);
			buf = sbuf_new(0);
			format_string(s, win_elem, buf);
			ret = cmdret_new(RET_SUCCESS, "%s", sbuf_get(buf));
			sbuf_free(buf);
			return ret;
		}
	}

	return cmdret_new(RET_SUCCESS, "No window.");
}

/*
 * Thanks to Gergely Nagy <algernon@debian.org> for the original patch.
 */
cmdret *
cmd_lastmsg(int interactive, struct cmdarg **args)
{
	show_last_message();
	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_focusup(int interactive, struct cmdarg **args)
{
	rp_frame *frame;

	if ((frame = find_frame_up(current_frame(rp_current_vscreen))))
		set_active_frame(frame, 0);
	else
		show_frame_indicator(0);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_focusdown(int interactive, struct cmdarg **args)
{
	rp_frame *frame;

	if ((frame = find_frame_down(current_frame(rp_current_vscreen))))
		set_active_frame(frame, 0);
	else
		show_frame_indicator(0);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_focusleft(int interactive, struct cmdarg **args)
{
	rp_frame *frame;

	if ((frame = find_frame_left(current_frame(rp_current_vscreen))))
		set_active_frame(frame, 0);
	else
		show_frame_indicator(0);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_focusright(int interactive, struct cmdarg **args)
{
	rp_frame *frame;

	if ((frame = find_frame_right(current_frame(rp_current_vscreen))))
		set_active_frame(frame, 0);
	else
		show_frame_indicator(0);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_exchangeup(int interactive, struct cmdarg **args)
{
	rp_frame *frame;

	if ((frame = find_frame_up(current_frame(rp_current_vscreen))))
		exchange_with_frame(current_frame(rp_current_vscreen), frame);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_exchangedown(int interactive, struct cmdarg **args)
{
	rp_frame *frame;

	if ((frame = find_frame_down(current_frame(rp_current_vscreen))))
		exchange_with_frame(current_frame(rp_current_vscreen), frame);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_exchangeleft(int interactive, struct cmdarg **args)
{
	rp_frame *frame;

	if ((frame = find_frame_left(current_frame(rp_current_vscreen))))
		exchange_with_frame(current_frame(rp_current_vscreen), frame);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_exchangeright(int interactive, struct cmdarg **args)
{
	rp_frame *frame;

	if ((frame = find_frame_right(current_frame(rp_current_vscreen))))
		exchange_with_frame(current_frame(rp_current_vscreen), frame);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_swap(int interactive, struct cmdarg **args)
{
	rp_frame *dest_frame;
	rp_frame *src_frame;

	dest_frame = ARG(0, frame);
	src_frame = args[1] ? ARG(1, frame) : current_frame(rp_current_vscreen);

	if (!rp_have_xrandr) {
		if (vscreen_find_frame_by_frame(src_frame->vscreen,
		    dest_frame) == NULL)
			return cmdret_new(RET_FAILURE,
			    "swap: frames on different screens");
	}
	exchange_with_frame(src_frame, dest_frame);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_restart(int interactive, struct cmdarg **args)
{
	hup_signalled = 1;
	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_startupmessage(struct cmdarg **args)
{
	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d", defaults.startup_message);

	if (ARG(0, number) != 0 && ARG(0, number) != 1)
		return cmdret_new(RET_FAILURE,
		    "set startupmessage: invalid argument");

	defaults.startup_message = ARG(0, number);
	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_focuslast(int interactive, struct cmdarg **args)
{
	rp_frame *frame = find_last_frame(rp_current_vscreen);

	if (frame)
		set_active_frame(frame, 0);
	else
		return cmdret_new(RET_FAILURE, "focuslast: no other frame");

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_link(int interactive, struct cmdarg **args)
{
	char *cmd = NULL;
	rp_keymap *map;

	if (args[1])
		map = ARG(1, keymap);
	else
		map = find_keymap(ROOT_KEYMAP);

	cmd = resolve_command_from_keydesc(args[0]->string, 0, map);
	if (cmd)
		return command(interactive, cmd);

	return cmdret_new(RET_SUCCESS, NULL);
}

/*
 * Thanks to Doug Kearns <djkea2@mugc.its.monash.edu.au> for the original patch.
 */
static cmdret *
set_barpadding(struct cmdarg **args)
{
	rp_screen *s;
	int x, y;

	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d %d", defaults.bar_x_padding,
		    defaults.bar_y_padding);

	x = ARG(0, number);
	y = ARG(1, number);

	if (x < 0 || y < 0)
		return cmdret_new(RET_FAILURE, "set barpadding: %s",
		    invalid_negative_arg);

	mark_edge_frames();

	defaults.bar_x_padding = x;
	defaults.bar_y_padding = y;

	list_for_each_entry(s, &rp_screens, node) {
		screen_update_workarea(s);
		screen_update_frames(s);
	}

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_barsticky(struct cmdarg **args)
{
	rp_screen *s;

	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d", defaults.bar_sticky);

	if (ARG(0, number) != 0 && ARG(0, number) != 1)
		return cmdret_new(RET_FAILURE,
		    "set barsticky: invalid argument");

	mark_edge_frames();

	defaults.bar_sticky = ARG(0, number);

	list_for_each_entry(s, &rp_screens, node) {
		hide_bar(s, 0);
		screen_update_workarea(s);
		screen_update_frames(s);
	}

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_stickyfmt(struct cmdarg **args)
{
	rp_screen *s;

	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%s", defaults.sticky_fmt);

	free(defaults.sticky_fmt);
	defaults.sticky_fmt = xstrdup(ARG_STRING(0));

	list_for_each_entry(s, &rp_screens, node) {
		hide_bar(s, 0);
	}

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_alias(int interactive, struct cmdarg **args)
{
	/* Add or update the alias. */
	add_alias(ARG_STRING(0), ARG_STRING(1));
	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_unalias(int interactive, struct cmdarg **args)
{
	char *tmp;
	int i;

	/* Are we updating an existing alias, or creating a new one? */
	i = find_alias_index(ARG_STRING(0));
	if (i < 0)
		return cmdret_new(RET_SUCCESS, "unalias: alias not found");

	alias_list_last--;

	/*
	 * Free the alias and put the last alias in the the space to
	 * keep alias_list from becoming sparse. This code must jump
	 * through some hoops to correctly handle the case when
	 * alias_list_last == i.
	 */
	tmp = alias_list[i].alias;
	alias_list[i].alias = xstrdup(alias_list[alias_list_last].alias);
	free(tmp);
	free(alias_list[alias_list_last].alias);

	/* Do the same for the name element. */
	tmp = alias_list[i].name;
	alias_list[i].name = xstrdup(alias_list[alias_list_last].name);
	free(tmp);
	free(alias_list[alias_list_last].name);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_nextscreen(int interactive, struct cmdarg **args)
{
	rp_screen *new_screen;
	rp_frame *new_frame;

	new_screen = screen_next();

	/* No need to go through the motions when we don't have to. */
	if (screen_count() <= 1 || new_screen == rp_current_screen)
		return cmdret_new(RET_FAILURE, "nextscreen: no other screen");

	new_frame = vscreen_get_frame(new_screen->current_vscreen,
	    new_screen->current_vscreen->current_frame);

	set_active_frame(new_frame, 1);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_prevscreen(int interactive, struct cmdarg **args)
{
	rp_screen *new_screen;
	rp_frame *new_frame;

	new_screen = screen_prev();

	/* No need to go through the motions when we don't have to. */
	if (screen_count() <= 1 || new_screen == rp_current_screen)
		return cmdret_new(RET_SUCCESS, "prevscreen: no other screen");

	new_frame = vscreen_get_frame(new_screen->current_vscreen,
	    new_screen->current_vscreen->current_frame);

	set_active_frame(new_frame, 1);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_smove(int interactive, struct cmdarg **args)
{
	rp_window *w;
	rp_screen *screen;
	int new_screen;

	if ((w = current_window()) == NULL)
		return cmdret_new(RET_FAILURE, "smove: no focused window");

	new_screen = ARG(0, number);
	if (new_screen < 0)
		return cmdret_new(RET_FAILURE, "smove: out of range");

	screen = screen_number(new_screen);
	if (!screen)
		return cmdret_new(RET_FAILURE, "smove: screen %d not found",
		    new_screen);

	if (screen == rp_current_screen)
		return cmdret_new(RET_SUCCESS, NULL);

	vscreen_move_window(screen->current_vscreen, w);
	set_active_frame(current_frame(screen->current_vscreen), 1);
	set_active_window(w);
	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_sselect(int interactive, struct cmdarg **args)
{
	int new_screen;
	rp_frame *new_frame;
	rp_screen *screen;

	new_screen = ARG(0, number);
	if (new_screen < 0)
		return cmdret_new(RET_FAILURE, "sselect: out of range");

	screen = screen_number(new_screen);
	if (!screen)
		return cmdret_new(RET_FAILURE, "sselect: screen %d not found",
		    new_screen);

	if (screen == rp_current_screen)
		return cmdret_new(RET_SUCCESS, NULL);

	new_frame = vscreen_get_frame(screen->current_vscreen,
	    screen->current_vscreen->current_frame);
	set_active_frame(new_frame, 1);

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_warp(struct cmdarg **args)
{
	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d", defaults.warp);

	if (ARG(0, number) != 0 && ARG(0, number) != 1)
		return cmdret_new(RET_FAILURE, "set warp: invalid argument");

	defaults.warp = ARG(0, number);
	return cmdret_new(RET_SUCCESS, NULL);
}

/*
 * Return a new string with the frame selector or it as a string if no selector
 * exists for the number.
 */

/* Select a frame by number. */
cmdret *
cmd_fselect(int interactive, struct cmdarg **args)
{
	set_active_frame(ARG(0, frame), 1);
	return cmdret_new(RET_SUCCESS, NULL);
}

char *
fdump(rp_vscreen *vscreen)
{
	struct sbuf *dump;
	rp_frame *cur;

	dump = sbuf_new(0);

	list_for_each_entry(cur, &(vscreen->frames), node) {
		char *frameset;

		frameset = frame_dump(cur, vscreen);
		sbuf_concat(dump, frameset);
		sbuf_concat(dump, ",");
		free(frameset);
	}
	sbuf_chop(dump);

	return sbuf_free_struct(dump);
}

cmdret *
cmd_fdump(int interactively, struct cmdarg **args)
{
	rp_screen *screen;
	cmdret *ret;
	char *dump;

	if (args[0] == NULL)
		screen = rp_current_screen;
	else {
		int snum;
		snum = ARG(0, number);

		if (snum < 0)
			return cmdret_new(RET_FAILURE,
			    "fdump: invalid negative screen number");
		else {
			screen = screen_number(snum);
			if (!screen)
				return cmdret_new(RET_FAILURE,
				    "fdump: screen %d not found", snum);
		}
	}

	dump = fdump(screen->current_vscreen);
	ret = cmdret_new(RET_SUCCESS, "%s", dump);
	free(dump);

	return ret;
}

cmdret *
frestore(char *data, rp_vscreen *v)
{
	char *token;
	char *d;
	rp_frame *new, *cur;
	rp_window *win;
	struct list_head fset;
	int max = -1;
	char *nexttok = NULL;

	INIT_LIST_HEAD(&fset);

	d = xstrdup(data);
	token = strtok_r(d, ",", &nexttok);
	if (token == NULL) {
		free(d);
		return cmdret_new(RET_FAILURE,
		    "frestore: invalid frame format");
	}

	/* Build the new frame set. */
	while (token != NULL) {
		new = frame_read(token, v);
		if (new == NULL) {
			free(d);
			return cmdret_new(RET_FAILURE,
			    "frestore: invalid frame format");
		}
		list_add_tail(&new->node, &fset);
		token = strtok_r(NULL, ",", &nexttok);
	}

	free(d);

	/* Clear all the frames. */
	list_for_each_entry(cur, &v->frames, node) {
		PRINT_DEBUG(("blank %d\n", cur->number));
		blank_frame(cur);
	}

	/* Get rid of the frames' numbers */
	vscreen_free_nums(v);

	/* Splice in our new frameset. */
	vscreen_restore_frameset(v, &fset);

	/* Process the frames a bit to make sure everything lines up. */
	list_for_each_entry(cur, &v->frames, node) {
		PRINT_DEBUG(("restore %d %d\n", cur->number, cur->win_number));

		/*
		 * Grab the frame's number, but if it already exists request a
		 * new one.
		 */
		if (!numset_add_num(v->frames_numset, cur->number)) {
			cur->number = numset_request(v->frames_numset);
		}
		/* Find the current frame based on last_access. */
		if (cur->last_access > max) {
			v->current_frame = cur->number;
			max = cur->last_access;
		}
		/* Update the window the frame points to. */
		if (cur->win_number != EMPTY) {
			set_frames_window(cur,
			    find_window_number(cur->win_number));
		}
	}

	/* Show the windows in the frames. */
	list_for_each_entry(win, &rp_mapped_window, node) {
		if (win->frame_number != EMPTY) {
			maximize(win);
			unhide_window(win);
		}
	}

	set_active_frame(current_frame(v), 0);
	update_bar(v->screen);
	show_frame_indicator(0);

	PRINT_DEBUG(("Done.\n"));
	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_frestore(int interactively, struct cmdarg **args)
{
	push_frame_undo(rp_current_vscreen);	/* fdump to stack */
	return frestore(ARG_STRING(0), rp_current_vscreen);
}

cmdret *
cmd_verbexec(int interactive, struct cmdarg **args)
{
	marked_message_printf(0, 0, "Running %s", ARG_STRING(0));
	spawn(ARG_STRING(0), current_frame(rp_current_vscreen));
	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_winliststyle(struct cmdarg **args)
{
	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%s",
		    defaults.window_list_style ? "column" : "row");

	if (!strcmp("column", ARG_STRING(0)))
		defaults.window_list_style = STYLE_COLUMN;
	else if (!strcmp("row", ARG_STRING(0)))
		defaults.window_list_style = STYLE_ROW;
	else
		return cmdret_new(RET_FAILURE,
		    "set winliststyle: invalid argument");

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_vrename(int interactive, struct cmdarg **args)
{
	if (screen_find_vscreen_by_name(rp_current_screen, ARG_STRING(0), 1))
		return cmdret_new(RET_FAILURE, "vrename: duplicate vscreen name");
	vscreen_rename(rp_current_vscreen, ARG_STRING(0));

	/* Update the vscreen list. */
	update_vscreen_names(rp_current_screen);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_vselect(int interactive, struct cmdarg **args)
{
	rp_vscreen *v;

	v = find_vscreen(ARG_STRING(0));
	if (v)
		set_current_vscreen(v);
	else
		return cmd_vscreens(interactive, NULL);

	return cmdret_new(RET_SUCCESS, NULL);
}

/* Show all the vscreens, with the current one highlighted. */
cmdret *
cmd_vscreens(int interactive, struct cmdarg **args)
{
	struct sbuf *vscreen_list = NULL;
	int dummy;
	cmdret *ret;

	if (interactive) {
		rp_screen *s;
		s = rp_current_screen;
		ret = cmdret_new(RET_SUCCESS, NULL);
		if (defaults.bar_timeout == 0 && s->bar_is_raised) {
			hide_bar(s, 0);
			return ret;
		}
		show_vscreen_bar(s);
	} else {
		vscreen_list = sbuf_new(0);
		get_vscreen_list(rp_current_screen, "\n", vscreen_list, &dummy,
		    &dummy);
		ret = cmdret_new(RET_SUCCESS, "%s", sbuf_get(vscreen_list));
		sbuf_free(vscreen_list);
	}
	return ret;
}

cmdret *
cmd_vnext(int interactive, struct cmdarg **args)
{
	rp_vscreen *v;
	v = vscreen_next_vscreen(rp_current_vscreen);
	if (!v)
		return cmdret_new(RET_FAILURE, "%s", "next vscreen failed");

	set_current_vscreen(v);
	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_vprev(int interactive, struct cmdarg **args)
{
	rp_vscreen *v;
	v = vscreen_prev_vscreen(rp_current_vscreen);
	if (!v)
		return cmdret_new(RET_FAILURE, "%s", "prev vscreen failed");

	set_current_vscreen(v);
	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_vother(int interactive, struct cmdarg **args)
{
	rp_vscreen *v;
	v = screen_last_vscreen(rp_current_screen);
	if (!v)
		/* todo: should we just return success here so it's a no-op? */
		return cmdret_new(RET_FAILURE, "%s", "last vscreen failed");

	set_current_vscreen(v);
	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_addhook(int interactive, struct cmdarg **args)
{
	struct list_head *hook;
	struct sbuf *cmd;

	hook = hook_lookup(ARG_STRING(0));
	if (hook == NULL)
		return cmdret_new(RET_FAILURE, "addhook: unknown hook '%s'",
		    ARG_STRING(0));

	/* Add the command to the hook */
	cmd = sbuf_new(0);
	sbuf_copy(cmd, ARG_STRING(1));
	hook_add(hook, cmd);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_remhook(int interactive, struct cmdarg **args)
{
	struct sbuf *cmd;

	/* Remove the command from the hook */
	cmd = sbuf_new(0);
	sbuf_copy(cmd, ARG_STRING(1));
	hook_remove(ARG(0, hook), cmd);
	sbuf_free(cmd);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_listhook(int interactive, struct cmdarg **args)
{
	cmdret *ret;
	struct sbuf *buffer;
	struct list_head *hook;
	struct sbuf *cur;

	hook = hook_lookup(ARG_STRING(0));
	if (hook == NULL)
		return cmdret_new(RET_FAILURE, "listhook: unknown hook '%s'",
		    ARG_STRING(0));

	if (list_empty(hook))
		return cmdret_new(RET_FAILURE, " Nothing defined for %s ",
		    ARG_STRING(0));

	buffer = sbuf_new(0);

	list_for_each_entry(cur, hook, node) {
		sbuf_printf_concat(buffer, "%s", sbuf_get(cur));
		if (cur->node.next != hook)
			sbuf_printf_concat(buffer, "\n");
	}

	ret = cmdret_new(RET_SUCCESS, "%s", sbuf_get(buffer));
	sbuf_free(buffer);
	return ret;
}

cmdret *
cmd_readkey(int interactive, struct cmdarg **args)
{
	char *keysym_name;
	rp_action *key_action;
	KeySym keysym;	/* Key pressed */
	unsigned int mod;	/* Modifiers */
	int rat_grabbed = 0;
	rp_keymap *map;
	cmdret *ret;

	map = ARG(0, keymap);

	/*
	 * Change the mouse icon to indicate to the user we are waiting for
	 * more keystrokes
	 */
	if (defaults.wait_for_key_cursor) {
		grab_rat();
		rat_grabbed = 1;
	}
	read_single_key(&keysym, &mod, NULL, 0);

	if (rat_grabbed)
		ungrab_rat();

	if ((key_action = find_keybinding(keysym, x11_mask_to_rp_mask(mod), map))) {
		return command(1, key_action->data);
	}

	/* No key match, notify user. */
	keysym_name = keysym_to_string(keysym, x11_mask_to_rp_mask(mod));
	ret = cmdret_new(RET_FAILURE, "readkey: unbound key '%s'", keysym_name);
	free(keysym_name);
	return ret;
}

cmdret *
cmd_newkmap(int interactive, struct cmdarg **args)
{
	rp_keymap *map;

	map = find_keymap(ARG_STRING(0));
	if (map)
		return cmdret_new(RET_FAILURE,
		    "newkmap: keymap '%s' already exists", ARG_STRING(0));

	map = keymap_new(ARG_STRING(0));
	list_add_tail(&map->node, &rp_keymaps);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_delkmap(int interactive, struct cmdarg **args)
{
	rp_keymap *map, *top, *root;

	top = find_keymap(defaults.top_kmap);
	root = find_keymap(ROOT_KEYMAP);

	map = ARG(0, keymap);
	if (map == root || map == top)
		return cmdret_new(RET_FAILURE,
		    "delkmap: cannot delete keymap '%s'", ARG_STRING(0));

	list_del(&map->node);

	return cmdret_new(RET_SUCCESS, NULL);
}

static cmdret *
set_framesels(struct cmdarg **args)
{
	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%s", defaults.frame_selectors);

	free(defaults.frame_selectors);
	defaults.frame_selectors = xstrdup(ARG_STRING(0));
	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_set(int interactive, struct cmdarg **args)
{
	struct sbuf *scur;
	struct cmdarg *acur;
	struct list_head *iter, *tmp;
	struct list_head head, arglist;
	int i, nargs = 0, raw = 0;
	int parsed_args;
	cmdret *result = NULL;
	struct cmdarg **cmdargs;
	char *input;

	if (args[0] == NULL) {
		/* List all the settings. */
		cmdret *ret;
		struct sbuf *s = sbuf_new(0);
		struct set_var *cur, *last;

		list_last(last, &set_vars, node);
		list_for_each_entry(cur, &set_vars, node) {
			cmdret *r;
			r = cur->set_fn(args);
			sbuf_printf_concat(s, "%s: %s", cur->var, r->output);
			/* Skip a newline on the last line. */
			if (cur != last)
				sbuf_concat(s, "\n");
			cmdret_free(r);
		}

		/* Return the accumulated string. */
		ret = cmdret_new(RET_SUCCESS, "%s", sbuf_get(s));
		sbuf_free(s);
		return ret;
	}

	INIT_LIST_HEAD(&arglist);
	INIT_LIST_HEAD(&head);

	/* We need to tell parse_args about arg_REST and arg_SHELLCMD. */
	for (i = 0; i < ARG(0, variable)->nargs; i++)
		if (ARG(0, variable)->args[i].type == arg_REST
		    || ARG(0, variable)->args[i].type == arg_COMMAND
		    || ARG(0, variable)->args[i].type == arg_SHELLCMD
		    || ARG(0, variable)->args[i].type == arg_RAW) {
			raw = 1;
			nargs = i;
			break;
		}

	/* Parse the arguments and call the function. */
	if (args[1])
		input = xstrdup(args[1]->string);
	else
		input = xstrdup("");

	result = parse_args(input, &head, nargs, raw);
	free(input);
	if (result)
		goto failed;

	result = parsed_input_to_args(ARG(0, variable)->nargs,
	    ARG(0, variable)->args, &head, &arglist, &parsed_args, NULL);
	if (result)
		goto failed;

	/* 0 or nargs is acceptable */
	if (list_size(&arglist) > 0 &&
	    list_size(&arglist) < ARG(0, variable)->nargs) {
		result = cmdret_new(RET_FAILURE, "not enough arguments.");
		goto failed;
	} else if (list_size(&head) > ARG(0, variable)->nargs) {
		result = cmdret_new(RET_FAILURE, "cmd_set: too many args.");
		goto failed;
	}

	cmdargs = arg_array(&arglist);
	result = ARG(0, variable)->set_fn(cmdargs);
	free(cmdargs);

	/* Free the lists. */
failed:
	/* Free the parsed strings */
	list_for_each_safe_entry(scur, iter, tmp, &head, node) {
		sbuf_free(scur);
	}

	/* Free the args */
	list_for_each_safe_entry(acur, iter, tmp, &arglist, node) {
		arg_free(acur);
	}

	return result;
}

cmdret *
cmd_sfdump(int interactively, struct cmdarg **args)
{
	char screen_suffix[16];
	cmdret *ret;
	struct sbuf *dump;
	rp_frame *cur_frame;
	rp_screen *cur_screen;

	dump = sbuf_new(0);

	list_for_each_entry(cur_screen, &rp_screens, node) {
		snprintf(screen_suffix, sizeof(screen_suffix), " %d,",
		    cur_screen->number);

		list_for_each_entry(cur_frame,
		    &(cur_screen->current_vscreen->frames), node) {
			char *frameset;

			frameset = frame_dump(cur_frame,
			    cur_screen->current_vscreen);
			sbuf_concat(dump, frameset);
			sbuf_concat(dump, screen_suffix);
			free(frameset);
		}
	}

	sbuf_chop(dump);
	ret = cmdret_new(RET_SUCCESS, "%s", sbuf_get(dump));
	sbuf_free(dump);
	return ret;
}

cmdret *
cmd_sfrestore(int interactively, struct cmdarg **args)
{
	char *copy, *ptr, *token;
	rp_screen *screen;
	int out_of_screen = 0, restored = 0;

	list_for_each_entry(screen, &rp_screens, node) {
		sbuf_free(screen->scratch_buffer);
		screen->scratch_buffer = sbuf_new(0);
	}

	copy = xstrdup(ARG_STRING(0));

	token = strtok(copy, ",");
	if (token == NULL) {
		free(copy);
		return cmdret_new(RET_FAILURE,
		    "sfrestore: invalid frame format");
	}

	while (token != NULL) {
		int snum;

		/* search for end of frameset */
		ptr = token;
		while (*ptr != ')')
			ptr++;
		ptr++;

		snum = string_to_positive_int(ptr);
		screen = screen_number(snum);
		if (screen) {
			/*
			 * clobber screen number here, frestore() doesn't need
			 * it
			 */
			*ptr = '\0';
			sbuf_concat(screen->scratch_buffer, token);
			sbuf_concat(screen->scratch_buffer, ",");
		} else
			out_of_screen++;

		/* continue with next frameset */
		token = strtok(NULL, ",");
	}

	free(copy);

	/* now restore the frames for each screen */
	list_for_each_entry(screen, &rp_screens, node) {
		cmdret *ret;

		if (strlen(sbuf_get(screen->scratch_buffer)) == 0)
			continue;

		push_frame_undo(screen->current_vscreen); /* fdump to stack */

		/*
		 * XXX save the failure of each frestore and display it in case
		 * of error
		 */
		ret = frestore(sbuf_get(screen->scratch_buffer),
		    screen->current_vscreen);
		if (ret->success)
			restored++;
		cmdret_free(ret);

		sbuf_free(screen->scratch_buffer);
		screen->scratch_buffer = NULL;
	}

	if (!out_of_screen)
		return cmdret_new(RET_SUCCESS, "screens restored: %d", restored);

	return cmdret_new(RET_SUCCESS,
	    "screens restored: %d, frames out of screen: %d",
	    restored, out_of_screen);
}

cmdret *
cmd_sdump(int interactive, struct cmdarg **args)
{
	cmdret *ret;
	struct sbuf *s;
	char *tmp;
	rp_screen *cur_screen;

	s = sbuf_new(0);
	list_for_each_entry(cur_screen, &rp_screens, node) {
		tmp = screen_dump(cur_screen);
		sbuf_concat(s, tmp);
		sbuf_concat(s, ",");
		free(tmp);
	}
	sbuf_chop(s);

	ret = cmdret_new(RET_SUCCESS, "%s", sbuf_get(s));
	sbuf_free(s);
	return ret;
}

static cmdret *
set_maxundos(struct cmdarg **args)
{
	rp_frame_undo *cur;

	if (args[0] == NULL)
		return cmdret_new(RET_SUCCESS, "%d", defaults.maxundos);

	if (ARG(0, number) < 0)
		return cmdret_new(RET_FAILURE, "set maxundos: %s",
		    invalid_negative_arg);

	defaults.maxundos = ARG(0, number);

	/* Delete any superfluous undos */
	while (list_size(&rp_frame_undos) > defaults.maxundos) {
		/* Delete the oldest node */
		list_last(cur, &rp_frame_undos, node);
		del_frame_undo(cur);
	}

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_cnext(int interactive, struct cmdarg **args)
{
	rp_window *cur, *last, *win;

	cur = current_window();
	if (!cur || !cur->res_class)	/* Can't be done. */
		return cmd_next(interactive, args);

	/* CUR !in cycle list, so LAST marks last node. */
	last = vscreen_prev_window(rp_current_vscreen, cur);

	if (last)
		for (win = vscreen_next_window(rp_current_vscreen, cur);
		    win;
		    win = vscreen_next_window(rp_current_vscreen, win)) {
			if (win->res_class
			    && strcmp(cur->res_class, win->res_class)) {
				set_active_window_force(win);
				return cmdret_new(RET_SUCCESS, NULL);
			}
			if (win == last)
				break;
		}

	return cmdret_new(RET_FAILURE, "%s", MESSAGE_NO_OTHER_WINDOW);
}

cmdret *
cmd_cprev(int interactive, struct cmdarg **args)
{
	rp_window *cur, *last, *win;

	cur = current_window();
	if (!cur || !cur->res_class)	/* Can't be done. */
		return cmd_next(interactive, args);

	/* CUR !in cycle list, so LAST marks last node. */
	last = vscreen_next_window(rp_current_vscreen, cur);

	if (last)
		for (win = vscreen_prev_window(rp_current_vscreen, cur);
		    win;
		    win = vscreen_prev_window(rp_current_vscreen, win)) {
			if (win->res_class
			    && strcmp(cur->res_class, win->res_class)) {
				set_active_window_force(win);
				return cmdret_new(RET_SUCCESS, NULL);
			}
			if (win == last)
				break;
		}

	return cmdret_new(RET_FAILURE, "%s", MESSAGE_NO_OTHER_WINDOW);
}

cmdret *
cmd_inext(int interactive, struct cmdarg **args)
{
	rp_window *cur, *last, *win;

	cur = current_window();
	if (!cur || !cur->res_class)	/* Can't be done. */
		return cmd_next(interactive, args);

	/* CUR !in cycle list, so LAST marks last node. */
	last = vscreen_prev_window(rp_current_vscreen, cur);

	if (last)
		for (win = vscreen_next_window(rp_current_vscreen, cur);
		    win;
		    win = vscreen_next_window(rp_current_vscreen, win)) {
			if (win->res_class
			    && !strcmp(cur->res_class, win->res_class)) {
				set_active_window_force(win);
				return cmdret_new(RET_SUCCESS, NULL);
			}
			if (win == last)
				break;
		}

	return cmdret_new(RET_FAILURE, "%s", MESSAGE_NO_OTHER_WINDOW);
}

cmdret *
cmd_iprev(int interactive, struct cmdarg **args)
{
	rp_window *cur, *last, *win;

	cur = current_window();
	if (!cur || !cur->res_class)	/* Can't be done. */
		return cmd_next(interactive, args);

	/* CUR !in cycle list, so LAST marks last node. */
	last = vscreen_next_window(rp_current_vscreen, cur);

	if (last)
		for (win = vscreen_prev_window(rp_current_vscreen, cur);
		    win;
		    win = vscreen_prev_window(rp_current_vscreen, win)) {
			if (win->res_class
			    && !strcmp(cur->res_class, win->res_class)) {
				set_active_window_force(win);
				return cmdret_new(RET_SUCCESS, NULL);
			}
			if (win == last)
				break;
		}

	return cmdret_new(RET_FAILURE, "%s", MESSAGE_NO_OTHER_WINDOW);
}

cmdret *
cmd_cother(int interactive, struct cmdarg **args)
{
	rp_window *cur, *w = NULL;

	cur = current_window();
	if (cur)
		w = vscreen_last_window_by_class(rp_current_vscreen,
		    cur->res_class);

	if (!w)
		return cmdret_new(RET_FAILURE, "%s", MESSAGE_NO_OTHER_WINDOW);
	else
		set_active_window_force(w);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_iother(int interactive, struct cmdarg **args)
{
	rp_window *cur, *w = NULL;

	cur = current_window();
	if (cur)
		w = vscreen_last_window_by_class_complement(rp_current_vscreen,
		    cur->res_class);

	if (!w)
		return cmdret_new(RET_FAILURE, "%s", MESSAGE_NO_OTHER_WINDOW);
	else
		set_active_window_force(w);

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_undo(int interactive, struct cmdarg **args)
{
	rp_frame_undo *cur;

	cur = pop_frame_undo();
	if (!cur)
		return cmdret_new(RET_FAILURE,
		    "No more undo information available");
	else {
		cmdret *ret;

		ret = frestore(cur->frames, cur->vscreen);
		return ret;
	}
}

cmdret *
cmd_redo(int interactive, struct cmdarg **args)
{
	rp_frame_undo *cur;
	cmdret *ret;

	/* The current layout goes on the undo. */
	cur = pop_frame_redo();
	if (!cur)
		return cmdret_new(RET_FAILURE,
		    "No more redo information available");

	ret = frestore(cur->frames, cur->vscreen);
	return ret;
}

cmdret *
cmd_prompt(int interactive, struct cmdarg **args)
{
	cmdret *ret;
	char *output;

	if (args[0] == NULL)
		output = get_input(MESSAGE_PROMPT_COMMAND, hist_PROMPT,
		    trivial_completions);
	else {
		char *arg_str, *prefix;

		arg_str = ARG_STRING(0);
		prefix = strchr(arg_str, ':');

		if (prefix) {
			struct sbuf *query;

			prefix++;	/* Don't return the colon. */
			query = sbuf_new(prefix - arg_str);
			sbuf_nconcat(query, arg_str, prefix - arg_str);
			output = get_more_input(sbuf_get(query), prefix,
			    hist_PROMPT, BASIC, trivial_completions);
			sbuf_free(query);
		} else {
			output = get_input(arg_str, hist_PROMPT,
			    trivial_completions);
		}
	}

	if (output == NULL)
		return cmdret_new(RET_FAILURE, NULL);	/* User aborted */

	ret = cmdret_new(RET_SUCCESS, "%s", output);
	free(output);

	return ret;
}

cmdret *
cmd_describekey(int interactive, struct cmdarg **args)
{
	char *keysym_name;
	rp_action *key_action;
	KeySym keysym;	/* Key pressed */
	unsigned int mod;	/* Modifiers */
	int rat_grabbed = 0;
	rp_keymap *map;

	map = ARG(0, keymap);

	/*
	 * Change the mouse icon to indicate to the user we are waiting for
	 * more keystrokes
	 */
	if (defaults.wait_for_key_cursor) {
		grab_rat();
		rat_grabbed = 1;
	}
	read_single_key(&keysym, &mod, NULL, 0);

	if (rat_grabbed)
		ungrab_rat();

	if ((key_action = find_keybinding(keysym, x11_mask_to_rp_mask(mod),
	    map))) {
		cmdret *ret;
		keysym_name = keysym_to_string(keysym,
		    x11_mask_to_rp_mask(mod));
		ret = cmdret_new(RET_SUCCESS, "%s bound to '%s'", keysym_name,
		    key_action->data);
		free(keysym_name);
		return ret;
	} else {
		cmdret *ret;
		/* No key match, notify user. */
		keysym_name = keysym_to_string(keysym,
		    x11_mask_to_rp_mask(mod));
		ret = cmdret_new(RET_SUCCESS, "describekey: unbound key '%s'",
		    keysym_name);
		free(keysym_name);
		return ret;
	}
}

cmdret *
cmd_dedicate(int interactive, struct cmdarg **args)
{
	rp_frame *f;

	f = current_frame(rp_current_vscreen);
	if (f == NULL)
		return cmdret_new(RET_SUCCESS, NULL);

	if (args[0] != NULL) {
		int dedicated;

		dedicated = ARG(0, number);
		if (dedicated != 0 && dedicated != 1)
			return cmdret_new(RET_FAILURE,
			    "Invalid \"dedicate\" value, use 0 or 1.");
		f->dedicated = dedicated;
	} else
		/* Just toggle it, rather than on or off. */
		f->dedicated = !(f->dedicated);

	return cmdret_new(RET_SUCCESS, "Consider this frame %s.",
	    f->dedicated ? "chaste" : "promiscuous");
}

cmdret *
cmd_putsel(int interactive, struct cmdarg **args)
{
	set_selection(ARG_STRING(0));
	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_getsel(int interactive, struct cmdarg **args)
{
	char *sel;
	cmdret *ret;
	sel = get_selection();
	if (sel != NULL) {
		ret = cmdret_new(RET_SUCCESS, "%s", sel);
		free(sel);
		return ret;
	}

	return cmdret_new(RET_FAILURE, "getsel: no X11 selection");
}

cmdret *
cmd_vmove(int interactive, struct cmdarg **args)
{
	rp_vscreen *v;
	rp_window *w;

	if ((w = current_window()) == NULL)
		return cmdret_new(RET_FAILURE, "vmove: no focused window");

	if (!(v = find_vscreen(ARG_STRING(0))))
		return cmdret_new(RET_FAILURE, "vmove: invalid vscreen");

	vscreen_move_window(v, w);
	set_current_vscreen(v);
	set_active_window(w);
	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_stick(int interactive, struct cmdarg **args)
{
	rp_window *cur = current_window();

	if (cur == NULL)
		return cmdret_new(RET_FAILURE, NULL);

	cur->sticky_frame = cur->frame_number;

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_unstick(int interactive, struct cmdarg **args)
{
	rp_window *cur = current_window();

	if (cur == NULL)
		return cmdret_new(RET_FAILURE, NULL);

	cur->sticky_frame = EMPTY;

	return cmdret_new(RET_SUCCESS, NULL);
}

cmdret *
cmd_commands(int interactive, struct cmdarg **args)
{
	struct sbuf *sb;
	struct user_command *cur;
	cmdret *ret;

	sb = sbuf_new(0);
	list_for_each_entry(cur, &user_commands, node) {
		sbuf_printf_concat(sb, "%s\n", cur->name);
	}
	sbuf_chop(sb);

	ret = cmdret_new(RET_SUCCESS, "%s", sbuf_get(sb));
	sbuf_free(sb);
	return ret;
}
