Allow binding mouse buttons

Instead of hardcoding mouse button actions, allow the user to
bind mouse buttons to extended commands.  For example the new
defaults are:

BIND=mouse1:therecmdmenu
BIND=mouse2:clicklook

Currently a bit rudimentary; the defaults should be OK, but
documentation is bit lacking, and in-game binding and option
saving are missing.

Allowed commands to bind are "nothing", "therecmdmenu", "clicklook",
and "mouseaction". Clicklook replaces the "clicklook" boolean option,
and mouseaction does what mouse 1 button used to do - a context sensitive
action.
This commit is contained in:
Pasi Kallinen
2022-08-23 23:06:26 +03:00
parent 3a255e86c4
commit c42e73fd9c
11 changed files with 135 additions and 78 deletions

View File

@@ -3483,8 +3483,8 @@ Set exceptions to the
option.
See the \(lqConfiguring Autopickup Exceptions\(rq section.
.lp BINDINGS
Change the key bindings of some special keys, menu accelerators, or
extended commands.
Change the key bindings of some special keys, menu accelerators,
extended commands, or mouse buttons.
You can specify multiple bindings.
Format is key followed by the command, separated by a colon.
See the \(lqChanging Key Bindings\(rq section for more information.
@@ -3753,9 +3753,6 @@ for more details.
.lp checkpoint
Save game state after each level change, for possible recovery after
program crash (default on). Persistent.
.lp clicklook
Allows looking at things on the screen by navigating the mouse
over them and clicking the right mouse button (default off).
.lp cmdassist
Have the game provide some additional command assistance for
new players if it detects some anticipated mistakes (default on).
@@ -4873,7 +4870,8 @@ menu accelerator keys, and extended commands, by using BIND stanzas in the
configuration file.
Format is key, followed by the command to bind to, separated by a colon.
The key can be a single character (\(lqx\(rq), a control key (\(lq\(haX\(rq,
\(lqC-x\(rq), a meta key (\(lqM-x\(rq), or a three-digit decimal ASCII code.
\(lqC-x\(rq), a meta key (\(lqM-x\(rq), a mouse button,
or a three-digit decimal ASCII code.
.pg
For example:
.sd
@@ -4893,6 +4891,9 @@ The menu control or accelerator keys can also be rebound via OPTIONS lines
in the configuration file.
You cannot bind object symbols or selection letters into menu accelerators.
Some interfaces only support some of the menu accelerators.
.lp "Mouse buttons"
You can bind \(lqmouse1\(rq or \(lqmouse2\(rq to \(lqnothing\(rq,
\(lqtherecmdmenu\(rq, \(lqclicklook\(rq, or \(lqmouseaction\(rq.
.lp "Special command keys"
Below are the special commands you can rebind.
Some of them can be bound to

View File

@@ -3779,9 +3779,9 @@ Set exceptions to the {{\it pickup\_types\/}}
option. See the ``Configuring Autopickup Exceptions'' section.
%.lp
\item[\bb{BINDINGS}]
Change the key bindings of some special keys, menu accelerators, or
extended commands. You can specify multiple bindings. Format is key
followed by the command, separated by a colon.
Change the key bindings of some special keys, menu accelerators,
extended commands, or mouse buttons. You can specify multiple bindings.
Format is key followed by the command, separated by a colon.
See the ``Changing Key Bindings`` section for more information.
%.lp ""
@@ -4087,10 +4087,6 @@ Synonym for ``{\tt role}'' to pick the type of your character
Save game state after each level change, for possible recovery after
program crash (default on). Persistent.
%.lp
\item[\ib{clicklook}]
Allows looking at things on the screen by navigating the mouse
over them and clicking the right mouse button (default off).
%.lp
\item[\ib{cmdassist}]
Have the game provide some additional command assistance for new
players if it detects some anticipated mistakes (default on).
@@ -5379,11 +5375,11 @@ autopickup.
%.pg
It is possible to change the default key bindings of some special commands,
menu accelerator keys, and extended commands, by using BIND stanzas in the
menu accelerator keys, extended commands, by using BIND stanzas in the
configuration file. Format is key, followed by the command to bind to,
separated by a colon. The key can be a single character (``{\tt x}''),
a control key (``{\tt \^{}X}'', ``{\tt C-x}''), a meta key (``{\tt M-x}''),
or a three-digit decimal ASCII code.
a mouse button, or a three-digit decimal ASCII code.
%.pg
For example:
@@ -5408,6 +5404,11 @@ in the configuration file.
You cannot bind object symbols or selection letters into menu accelerators.
Some interfaces only support some of the menu accelerators.
%.lp "Mouse buttons"
\item[\tb{Mouse buttons}]
You can bind ``mouse1'' or ``mouse2'' to ``{\tt nothing}'',
``{\tt therecmdmenu}'', ``{\tt clicklook}'', or ``{\tt mouseaction}''.
%.lp "Special command keys"
\item[\tb{Special command keys}]
Below are the special commands you can rebind. Some of them can be bound to

View File

@@ -1803,6 +1803,7 @@ eliminate scimitar skill and have scimitars use saber skill
simplified configuration options menu
rudimentary key rebinding in game options
experimental #saveoptions command to allow saving configuration settings
mouse buttons can be bound to extended commands
Platform- and/or Interface-Specific New Features

View File

@@ -506,6 +506,7 @@ struct cmd {
const char *dirchars; /* current movement/direction characters */
const char *alphadirchars; /* same as dirchars if !numpad */
const struct ext_func_tab *commands[256]; /* indexed by input character */
const struct ext_func_tab *mousebtn[NUM_MOUSE_BUTTONS];
char spkeys[NUM_NHKF];
char extcmd_char; /* key that starts an extended command ('#') */
};

View File

@@ -279,6 +279,7 @@ extern void rhack(char *);
extern int doextlist(void);
extern int extcmd_via_menu(void);
extern int enter_explore_mode(void);
extern boolean bind_mousebtn(int, const char *);
extern boolean bind_key(uchar, const char *);
extern void dokeylist(void);
extern coordxy xytod(coordxy, coordxy);
@@ -291,7 +292,7 @@ extern const char *directionname(int);
extern int isok(coordxy, coordxy);
extern int get_adjacent_loc(const char *, const char *, coordxy, coordxy,
coord *);
extern const char *click_to_cmd(coordxy, coordxy, int);
extern void click_to_cmd(coordxy, coordxy, int);
extern char get_count(const char *, char, long, cmdcount_nht *, unsigned);
#ifdef HANGUPHANDLING
extern void hangup(int);

View File

@@ -318,7 +318,6 @@ struct instance_flags {
#ifdef TTY_SOUND_ESCCODES
boolean vt_sounddata; /* output console codes for sound support in TTY*/
#endif
boolean clicklook; /* allow right-clicking for look */
boolean cmdassist; /* provide detailed assistance for some comnds */
boolean fireassist; /* autowield launcher when using fire-command */
boolean time_botl; /* context.botl for 'time' (moves) only */

View File

@@ -19,6 +19,7 @@
#define CMD_MOVE_PREFIXES (CMD_M_PREFIX | CMD_gGF_PREFIX)
#define PREFIXCMD 0x0200 /* prefix command, requires another one after it */
#define MOVEMENTCMD 0x0400 /* used to move hero/cursor */
#define MOUSECMD 0x0800 /* cmd allowed to be bound to mouse button */
/* flags for extcmds_match() */
#define ECM_NOFLAGS 0

View File

@@ -162,8 +162,6 @@ static int optfn_##a(int, int, boolean, char *, char *);
NHOPTB(checkpoint, Advanced, 0, opt_out, set_in_config,
Off, No, No, No, NoAlias, (boolean *) 0)
#endif
NHOPTB(clicklook, Advanced, 0, opt_in, set_in_game,
Off, Yes, No, No, NoAlias, &iflags.clicklook)
NHOPTB(cmdassist, Behavior, 0, opt_out, set_in_game,
On, Yes, No, No, NoAlias, &iflags.cmdassist)
NHOPTB(color, Map, 0, opt_in, set_in_game,

View File

@@ -135,6 +135,7 @@ typedef struct gi {
/* nh_poskey() modifier types */
#define CLICK_1 1
#define CLICK_2 2
#define NUM_MOUSE_BUTTONS 2
/* invalid winid */
#define WIN_ERR ((winid) -1)

155
src/cmd.c
View File

@@ -105,6 +105,7 @@ static boolean can_do_extcmd(const struct ext_func_tab *);
static int dotravel(void);
static int dotravel_target(void);
static int doclicklook(void);
static int domouseaction(void);
static int doterrain(void);
static int wiz_wish(void);
static int wiz_identify(void);
@@ -2692,7 +2693,7 @@ struct ext_func_tab extcmdlist[] = {
doterrain, IFBURIED | AUTOCOMPLETE, NULL },
{ '\0', "therecmdmenu",
"menu of commands you can do from here to adjacent spot",
dotherecmdmenu, AUTOCOMPLETE | GENERALCMD, NULL },
dotherecmdmenu, AUTOCOMPLETE | GENERALCMD | MOUSECMD, NULL },
{ 't', "throw", "throw something",
dothrow, 0, NULL },
{ '\0', "timeout", "look at timeout queue and hero's timed intrinsics",
@@ -2831,7 +2832,8 @@ struct ext_func_tab extcmdlist[] = {
do_run_southwest, MOVEMENTCMD | CMD_M_PREFIX, NULL },
/* internal commands: only used by game core, not available for user */
{ '\0', "clicklook", NULL, doclicklook, INTERNALCMD, NULL },
{ '\0', "clicklook", NULL, doclicklook, INTERNALCMD | MOUSECMD, NULL },
{ '\0', "mouseaction", NULL, domouseaction, INTERNALCMD | MOUSECMD, NULL },
{ '\0', "altdip", NULL, dip_into, INTERNALCMD, NULL },
{ '\0', "altadjust", NULL, adjust_split, INTERNALCMD, NULL },
{ '\0', "altunwield", NULL, remarm_swapwep, INTERNALCMD, NULL },
@@ -3169,6 +3171,43 @@ key2extcmddesc(uchar key)
return (char *) 0;
}
boolean
bind_mousebtn(int btn, const char *command)
{
struct ext_func_tab *extcmd;
if (btn < 1 || btn > NUM_MOUSE_BUTTONS) {
config_error_add("Wrong mouse button, valid are 1-%i", NUM_MOUSE_BUTTONS);
return FALSE;
}
btn--;
/* special case: "nothing" is reserved for unbinding */
if (!strcmpi(command, "nothing")) {
g.Cmd.mousebtn[btn] = (struct ext_func_tab *) 0;
return TRUE;
}
for (extcmd = extcmdlist; extcmd->ef_txt; extcmd++) {
if (strcmpi(command, extcmd->ef_txt))
continue;
if (!(extcmd->flags & MOUSECMD))
continue;
g.Cmd.mousebtn[btn] = extcmd;
#if 0 /* silently accept key binding for unavailable command (!SHELL,&c) */
if ((extcmd->flags & CMD_NOT_AVAILABLE) != 0) {
char buf[BUFSZ];
Sprintf(buf, cmdnotavail, extcmd->ef_txt);
config_error_add("%s", buf);
}
#endif
return TRUE;
}
return FALSE;
}
boolean
bind_key(uchar key, const char *command)
{
@@ -3228,6 +3267,9 @@ commands_init(void)
if (extcmd->key)
g.Cmd.commands[extcmd->key] = extcmd;
(void) bind_mousebtn(1, "therecmdmenu");
(void) bind_mousebtn(2, "clicklook");
/* number_pad */
(void) bind_key(C('l'), "redraw");
(void) bind_key('h', "help");
@@ -5203,8 +5245,20 @@ dotherecmdmenu(void)
{
char ch;
int dir, click;
coordxy x = g.clicklook_cc.x;
coordxy y = g.clicklook_cc.y;
iflags.getdir_click = CLICK_1 | CLICK_2; /* allow 'far' click */
if (isok(x, y)) {
if (x == u.ux && y == u.uy)
ch = here_cmd_menu();
else
ch = there_cmd_menu(x, y, iflags.getdir_click);
g.clicklook_cc.x = g.clicklook_cc.y = -1;
return (ch && ch != '\033') ? ECMD_TIME : ECMD_OK;
}
dir = getdir((const char *) 0);
click = iflags.getdir_click;
iflags.getdir_click = 0;
@@ -5731,30 +5785,25 @@ here_cmd_menu(void)
return '\0';
}
/*
* convert a MAP window position into a movement key usable with movecmd()
*/
const char *
void
click_to_cmd(coordxy x, coordxy y, int mod)
{
static char cmd[4];
g.clicklook_cc.x = x;
g.clicklook_cc.y = y;
if (g.Cmd.mousebtn[mod-1])
cmdq_add_ec(CQ_CANNED, g.Cmd.mousebtn[mod-1]->ef_funct);
}
static int
domouseaction(void)
{
coordxy x, y;
struct obj *o;
int dir;
cmd[0] = cmd[1] = '\0';
if (!iflags.herecmd_menu && iflags.clicklook && mod == CLICK_2) {
g.clicklook_cc.x = x;
g.clicklook_cc.y = y;
cmdq_add_ec(CQ_CANNED, doclicklook);
return cmd;
}
if (iflags.herecmd_menu && isok(x, y)) {
(void) there_cmd_menu(x, y, mod);
return cmd;
}
x -= u.ux;
y -= u.uy;
x = g.clicklook_cc.x - u.ux;
y = g.clicklook_cc.y - u.uy;
if (flags.travelcmd) {
if (abs(x) <= 1 && abs(y) <= 1) {
@@ -5763,30 +5812,30 @@ click_to_cmd(coordxy x, coordxy y, int mod)
iflags.travelcc.x = u.tx = u.ux + x;
iflags.travelcc.y = u.ty = u.uy + y;
cmdq_add_ec(CQ_CANNED, dotravel_target);
return cmd;
return ECMD_OK;
}
if (x == 0 && y == 0) {
/* here */
if (IS_FOUNTAIN(levl[u.ux][u.uy].typ)
|| IS_SINK(levl[u.ux][u.uy].typ)) {
cmd[0] = cmd_from_func(mod == CLICK_1 ? dodrink : dodip);
return cmd;
cmdq_add_ec(CQ_CANNED, dodrink);
return ECMD_OK;
} else if (IS_THRONE(levl[u.ux][u.uy].typ)) {
cmd[0] = cmd_from_func(dosit);
return cmd;
cmdq_add_ec(CQ_CANNED, dosit);
return ECMD_OK;
} else if (On_stairs_up(u.ux, u.uy)) {
cmd[0] = cmd_from_func(doup);
return cmd;
cmdq_add_ec(CQ_CANNED, doup);
return ECMD_OK;
} else if (On_stairs_dn(u.ux, u.uy)) {
cmd[0] = cmd_from_func(dodown);
return cmd;
cmdq_add_ec(CQ_CANNED, dodown);
return ECMD_OK;
} else if ((o = vobj_at(u.ux, u.uy)) != 0) {
cmd[0] = cmd_from_func(Is_container(o) ? doloot : dopickup);
return cmd;
cmdq_add_ec(CQ_CANNED, Is_container(o) ? doloot : dopickup);
return ECMD_OK;
} else {
cmd[0] = cmd_from_func(donull); /* just rest */
return cmd;
cmdq_add_ec(CQ_CANNED, donull); /* just rest */
return ECMD_OK;
}
}
@@ -5795,25 +5844,23 @@ click_to_cmd(coordxy x, coordxy y, int mod)
dir = xytod(x, y);
if (!m_at(u.ux + x, u.uy + y)
&& !test_move(u.ux, u.uy, x, y, TEST_MOVE)) {
cmd[1] = cmd_from_func(move_funcs[dir][MV_WALK]);
cmd[2] = '\0';
if (IS_DOOR(levl[u.ux + x][u.uy + y].typ)) {
/* slight assistance to player: choose kick/open for them */
if (levl[u.ux + x][u.uy + y].doormask & D_LOCKED) {
cmd[0] = cmd_from_func(dokick);
return cmd;
cmdq_add_ec(CQ_CANNED, dokick);
return ECMD_OK;
}
if (levl[u.ux + x][u.uy + y].doormask & D_CLOSED) {
cmd[0] = cmd_from_func(doopen);
return cmd;
cmdq_add_ec(CQ_CANNED, doopen);
return ECMD_OK;
}
}
if (levl[u.ux + x][u.uy + y].typ <= SCORR) {
cmd[0] = cmd_from_func(dosearch);
cmd[1] = 0;
return cmd;
cmdq_add_ec(CQ_CANNED, dosearch);
return ECMD_OK;
}
cmdq_add_ec(CQ_CANNED, move_funcs[dir][MV_WALK]);
return ECMD_OK;
}
} else {
/* convert without using floating point, allowing sloppy clicking */
@@ -5830,21 +5877,15 @@ click_to_cmd(coordxy x, coordxy y, int mod)
if (x == 0 && y == 0) {
/* map click on player to "rest" command */
cmd[0] = cmd_from_func(donull);
return cmd;
cmdq_add_ec(CQ_CANNED, donull);
return ECMD_OK;
}
dir = xytod(x, y);
}
/* move, attack, etc. */
cmd[1] = 0;
if (mod == CLICK_1) {
cmd[0] = cmd_from_func(move_funcs[dir][MV_WALK]);
} else {
cmd[0] = cmd_from_func(move_funcs[dir][MV_RUN]);
}
return cmd;
cmdq_add_ec(CQ_CANNED, move_funcs[dir][MV_WALK]);
return ECMD_OK;
}
/* gather typed digits into a number in *count; return the next non-digit */
@@ -6077,8 +6118,8 @@ readchar_core(coordxy *x, coordxy *y, int *mod)
#endif /*ALTMETA*/
} else if (sym == 0) {
/* click event */
readchar_queue = click_to_cmd(*x, *y, *mod);
sym = *readchar_queue++;
g.clicklook_cc.x = g.clicklook_cc.y = -1;
click_to_cmd(*x, *y, *mod);
}
g.program_state.getting_a_command = 0; /* next readchar() will be for an
* ordinary char unless parse()
@@ -6183,7 +6224,7 @@ static int
doclicklook(void)
{
if (!isok(g.clicklook_cc.x, g.clicklook_cc.y))
return 0;
return ECMD_OK;
g.context.move = FALSE;
(void) do_look(2, &g.clicklook_cc);

View File

@@ -6632,6 +6632,9 @@ parsebindings(char *bindings)
uchar key;
int i;
boolean ret = TRUE; /* assume success */
static const char *const mousebtn_names[NUM_MOUSE_BUTTONS] = {
"mouse1", "mouse2"
};
/* look for first comma, then decide whether it is the key being bound
or a list element separator; if it's a key, find separator beyond it */
@@ -6660,6 +6663,17 @@ parsebindings(char *bindings)
return FALSE; /* it's not a binding */
*bind++ = 0;
bind = trimspaces(bind);
for (i = 0; i < SIZE(mousebtn_names); i++)
if (!strcmp(bindings, mousebtn_names[i])) {
if (!bind_mousebtn(i + 1, bind)) {
config_error_add("Error binding mouse button %i", i + 1);
} else {
return ret;
}
}
/* read the key to be bound */
key = txt2key(bindings);
if (!key) {
@@ -6667,8 +6681,6 @@ parsebindings(char *bindings)
return FALSE;
}
bind = trimspaces(bind);
/* is it a special key? */
if (bind_specialkey(key, bind))
return ret;