[input] Save/load config to/from plist configs

While the console command line is quite good for setting everything up,
the devices being bound do need to be present when the commands are
executed (due to needing extra data provided by the devices). Thus
property lists that store the extra data (button and axis counts, device
names/ids, connection names, etc) seems to be the best solution.
This commit is contained in:
Bill Currie 2021-11-14 10:17:05 +09:00
parent 7c9f3a3b09
commit fede9125e1
6 changed files with 420 additions and 26 deletions

View file

@ -143,6 +143,9 @@ int IN_RegisterDriver (in_driver_t *driver, void *data);
void IN_DriverData (int handlle, void *data); void IN_DriverData (int handlle, void *data);
void IN_Init (struct cbuf_s *cbuf); void IN_Init (struct cbuf_s *cbuf);
void IN_Init_Cvars (void); void IN_Init_Cvars (void);
struct plitem_s;
void IN_SaveConfig (struct plitem_s *config);
void IN_LoadConfig (struct plitem_s *config);
int IN_AddDevice (int driver, void *device, const char *name, const char *id); int IN_AddDevice (int driver, void *device, const char *name, const char *id);
void IN_RemoveDevice (int devid); void IN_RemoveDevice (int devid);

View file

@ -236,6 +236,9 @@ in_axis_t *IN_FindAxis (const char *name);
void IN_Binding_Activate (void); void IN_Binding_Activate (void);
void IN_Binding_Init (void); void IN_Binding_Init (void);
struct plitem_s;
void IN_Binding_SaveConfig (struct plitem_s *config);
void IN_Binding_LoadConfig (struct plitem_s *config);
#endif #endif

View file

@ -71,6 +71,12 @@ void IMT_BindButton (imt_t *imt, int button, const char *binding);
qboolean IMT_ProcessAxis (int axis, int value); qboolean IMT_ProcessAxis (int axis, int value);
qboolean IMT_ProcessButton (int button, int state); qboolean IMT_ProcessButton (int button, int state);
void IMT_Init (void); void IMT_Init (void);
struct plitem_s;
void IMT_SaveConfig (struct plitem_s *config);
void IMT_SaveAxisConfig (struct plitem_s *axes, int axis_ind, int dev_axis);
void IMT_SaveButtonConfig (struct plitem_s *buttons, int button_ind,
int dev_button);
void IMT_LoadConfig (struct plitem_s *config);
#endif #endif

View file

@ -47,6 +47,7 @@
#include "QF/plist.h" #include "QF/plist.h"
#include "QF/progs.h" // for PR_RESMAP #include "QF/progs.h" // for PR_RESMAP
#include "QF/sys.h" #include "QF/sys.h"
#include "QF/va.h"
#include "QF/input/imt.h" #include "QF/input/imt.h"
@ -95,23 +96,34 @@ in_binding_find_connection (const char *devname, const char *id)
return 0; return 0;
} }
static void
alloc_input_info (in_devbindings_t *db)
{
db->axis_info = malloc (db->num_axes * sizeof (in_axisinfo_t)
+ db->num_buttons * sizeof (in_buttoninfo_t));
db->button_info = (in_buttoninfo_t *) &db->axis_info[db->num_axes];
}
static void static void
in_binding_add_device (const IE_event_t *ie_event) in_binding_add_device (const IE_event_t *ie_event)
{ {
size_t devid = ie_event->device.devid; size_t devid = ie_event->device.devid;
if (in_find_devid (devid)) {
// the device is already known. this is likely the result of a
// broadcast of connected devices
return;
}
DARRAY_APPEND (&known_devices, devid);
// keep the known devices sorted by id
heapsort (known_devices.a, known_devices.size, sizeof (int), devid_cmp);
const char *devname = IN_GetDeviceName (devid); const char *devname = IN_GetDeviceName (devid);
const char *id = IN_GetDeviceId (devid); const char *id = IN_GetDeviceId (devid);
in_devbindings_t *db = in_binding_find_connection (devname, id);
if (!in_find_devid (devid)) {
DARRAY_APPEND (&known_devices, devid);
// keep the known devices sorted by id
heapsort (known_devices.a, known_devices.size, sizeof (int), devid_cmp);
Sys_Printf ("Added device %s %s\n", devname, id);
}
in_devbindings_t *db = IN_GetDeviceEventData (devid);
if (db) {
return;
}
db = in_binding_find_connection (devname, id);
if (db) { if (db) {
if (db->match_id) { if (db->match_id) {
@ -121,8 +133,11 @@ in_binding_add_device (const IE_event_t *ie_event)
} }
db->devid = devid; db->devid = devid;
IN_SetDeviceEventData (devid, db); IN_SetDeviceEventData (devid, db);
} else { if (!db->axis_info) {
Sys_Printf ("Added device %s %s\n", devname, id); alloc_input_info (db);
}
IN_AxisInfo (devid, db->axis_info, &db->num_axes);
IN_ButtonInfo (devid, db->button_info, &db->num_buttons);
} }
} }
@ -270,6 +285,18 @@ in_binding_find_device (const char *name)
return db; return db;
} }
static void
clear_connection (in_devbindings_t *db)
{
if (db->devid >= 0) {
IN_SetDeviceEventData (db->devid, 0);
}
free (db->name);
free (db->devname);
free (db->id);
free (db->axis_info);
}
static void static void
in_bind_f (void) in_bind_f (void)
{ {
@ -506,11 +533,9 @@ in_connect_f (void)
const char *device_id = Cmd_Argv (2); const char *device_id = Cmd_Argv (2);
int devid = -1; int devid = -1;
for (in_devbindings_t *db = devbindings_list; db; db = db->next) { if (in_binding_find_device (bindname)) {
if (strcmp (bindname, db->name) == 0) { Sys_Printf ("%s already exists\n", bindname);
Sys_Printf ("%s already exists\n", bindname); return;
return;
}
} }
if (device_id[0] == '#') { if (device_id[0] == '#') {
@ -560,9 +585,7 @@ in_connect_f (void)
IN_AxisInfo (devid, 0, &db->num_axes); IN_AxisInfo (devid, 0, &db->num_axes);
IN_ButtonInfo (devid, 0, &db->num_buttons); IN_ButtonInfo (devid, 0, &db->num_buttons);
db->axis_info = malloc (db->num_axes * sizeof (in_axisinfo_t) alloc_input_info (db);
+ db->num_buttons * sizeof (in_buttoninfo_t));
db->button_info = (in_buttoninfo_t *) &db->axis_info[db->num_axes];
IN_AxisInfo (devid, db->axis_info, &db->num_axes); IN_AxisInfo (devid, db->axis_info, &db->num_axes);
IN_ButtonInfo (devid, db->button_info, &db->num_buttons); IN_ButtonInfo (devid, db->button_info, &db->num_buttons);
@ -709,3 +732,192 @@ IN_Binding_Init (void)
Cmd_AddCommand (cmd->name, cmd->func, cmd->desc); Cmd_AddCommand (cmd->name, cmd->func, cmd->desc);
} }
} }
void
IN_Binding_SaveConfig (plitem_t *config)
{
plitem_t *devices = PL_NewArray ();
PL_D_AddObject (config, "devices", devices);
for (in_devbindings_t *db = devbindings_list; db; db = db->next) {
plitem_t *db_cfg = PL_NewDictionary (0); //FIXME hashlinks
PL_A_AddObject (devices, db_cfg);
PL_D_AddObject (db_cfg, "name", PL_NewString (db->name));
PL_D_AddObject (db_cfg, "devname", PL_NewString (db->devname));
if (db->match_id) {
PL_D_AddObject (db_cfg, "id", PL_NewString (db->id));
}
PL_D_AddObject (db_cfg, "num_axes",
PL_NewString (va (0, "%d", db->num_axes)));
PL_D_AddObject (db_cfg, "num_buttons",
PL_NewString (va (0, "%d", db->num_buttons)));
if (db->axis_imt_id >= 0) {
plitem_t *axes = PL_NewArray ();
PL_D_AddObject (db_cfg, "axes", axes);
for (int i = 0; i < db->num_axes; i++) {
IMT_SaveAxisConfig (axes, db->axis_imt_id + i, i);
}
}
if (db->button_imt_id >= 0) {
plitem_t *buttons = PL_NewArray ();
PL_D_AddObject (db_cfg, "buttons", buttons);
for (int i = 0; i < db->num_buttons; i++) {
IMT_SaveButtonConfig (buttons, db->button_imt_id + i, i);
}
}
}
}
static int
parse_num (plitem_t *item)
{
char *end;
const char *str = PL_String (item);
if (!str) {
return -1;
}
int num = strtol (str, &end, 0);
if (*end || num < 0) {
return -1;
}
return num;
}
static int
parse_int (plitem_t *item, int dflt)
{
char *end;
const char *str = PL_String (item);
if (!str) {
return dflt;
}
int num = strtol (str, &end, 0);
if (*end) {
return dflt;
}
return num;
}
static float
parse_float (plitem_t *item, float dflt)
{
char *end;
const char *str = PL_String (item);
if (!str) {
return dflt;
}
float num = strtof (str, &end);
if (*end) {
return dflt;
}
return num;
}
void
IN_Binding_LoadConfig (plitem_t *config)
{
for (in_devbindings_t *db = devbindings_list; db; db = db->next) {
clear_connection (db);
}
PR_RESRESET (devbindings);
devbindings_list = 0;
plitem_t *devices = PL_ObjectForKey (config, "devices");
if (PL_Type (devices) != QFArray) {
Sys_Printf ("IN_Binding_LoadConfig: devices not an array\n");
return;
}
for (int i = 0, count = PL_A_NumObjects (devices); i < count; i++) {
plitem_t *db_cfg = PL_ObjectAtIndex (devices, i);
const char *bindname = PL_String (PL_ObjectForKey (db_cfg, "name"));
const char *devname = PL_String (PL_ObjectForKey (db_cfg, "devname"));
const char *id = PL_String (PL_ObjectForKey (db_cfg, "id"));
int num_axes = parse_num (PL_ObjectForKey (db_cfg,
"num_axes"));
int num_buttons = parse_num (PL_ObjectForKey (db_cfg,
"num_buttons"));
if (in_binding_find_device (bindname)) {
Sys_Printf ("%s already exists\n", bindname);
continue;
}
if (num_axes < 0) {
continue;
}
if (num_buttons < 0) {
continue;
}
in_devbindings_t *db = PR_RESNEW (devbindings);
db->next = devbindings_list;
devbindings_list = db;
db->name = strdup (bindname);
db->devname = strdup (devname);
if (id) {
db->id = strdup (id);
db->match_id = 1;
} else {
db->id = 0;
db->match_id = 0;
}
db->devid = -1; // not connected yet
db->num_axes = num_axes;
db->num_buttons = num_buttons;
db->axis_imt_id = -1;
db->button_imt_id = -1;
plitem_t *axes = PL_ObjectForKey (db_cfg, "axes");
if (PL_A_NumObjects (axes)) {
db->axis_imt_id = IMT_GetAxisBlock (db->num_axes);
}
for (int i = 0, count = PL_A_NumObjects (axes); i < count; i++) {
plitem_t *a = PL_ObjectAtIndex (axes, i);
const char *imt_name = PL_String (PL_ObjectForKey (a, "imt"));
int num = parse_num (PL_ObjectForKey (a, "num"));
const char *axis_name = PL_String (PL_ObjectForKey (a, "axis"));
in_recipe_t recipe = {
.min = parse_int (PL_ObjectForKey (a, "min"), 0),
.max = parse_int (PL_ObjectForKey (a, "max"), 0),
.minzone = parse_int (PL_ObjectForKey (a, "minzone"), 0),
.maxzone = parse_int (PL_ObjectForKey (a, "maxzone"), 0),
.deadzone = parse_int (PL_ObjectForKey (a, "deadzone"), 0),
.curve = parse_float (PL_ObjectForKey (a, "curve"), 1),
.scale = parse_float (PL_ObjectForKey (a, "scale"), 1),
};
if (!imt_name || num < 0 || num >= db->num_axes) {
continue;
}
imt_t *imt = IMT_FindIMT (imt_name);
if (!imt) {
continue;
}
in_axis_t *axis = IN_FindAxis (axis_name);
if (!axis) {
continue;
}
IMT_BindAxis (imt, db->axis_imt_id + num, axis, &recipe);
}
plitem_t *buttons = PL_ObjectForKey (db_cfg, "buttons");
if (PL_A_NumObjects (buttons)) {
db->button_imt_id = IMT_GetButtonBlock (db->num_buttons);
}
for (int i = 0, count = PL_A_NumObjects (buttons); i < count; i++) {
plitem_t *b = PL_ObjectAtIndex (buttons, i);
const char *imt_name = PL_String (PL_ObjectForKey (b, "imt"));
int num = parse_num (PL_ObjectForKey (b, "num"));
const char *binding = PL_String (PL_ObjectForKey (b, "binding"));
if (!imt_name || num < 0 || num >= db->num_buttons) {
continue;
}
imt_t *imt = IMT_FindIMT (imt_name);
if (!imt) {
continue;
}
IMT_BindButton (imt, db->button_imt_id + num, binding);
}
}
// force device connection events so any connected devices get hoocked up
// with their bindings
IN_SendConnectedDevices ();
}

View file

@ -55,6 +55,7 @@
#include "QF/joystick.h" #include "QF/joystick.h"
#include "QF/keys.h" #include "QF/keys.h"
#include "QF/mathlib.h" #include "QF/mathlib.h"
#include "QF/plist.h"
#include "QF/sys.h" #include "QF/sys.h"
#include "QF/vid.h" #include "QF/vid.h"
@ -321,6 +322,27 @@ IN_ProcessEvents (void)
} }
} }
void
IN_SaveConfig (plitem_t *config)
{
plitem_t *input_config = PL_NewDictionary (0); //FIXME hashlinks
PL_D_AddObject (config, "input", input_config);
IMT_SaveConfig (input_config);
IN_Binding_SaveConfig (input_config);
}
void
IN_LoadConfig (plitem_t *config)
{
plitem_t *input_config = PL_ObjectForKey (config, "input");
if (input_config) {
IMT_LoadConfig (input_config);
IN_Binding_LoadConfig (input_config);
}
}
void void
IN_Move (void) IN_Move (void)
{ {

View file

@ -44,6 +44,7 @@
#include "QF/hash.h" #include "QF/hash.h"
#include "QF/input.h" #include "QF/input.h"
#include "QF/mathlib.h" #include "QF/mathlib.h"
#include "QF/plist.h"
#include "QF/sys.h" #include "QF/sys.h"
#include "QF/va.h" #include "QF/va.h"
@ -226,9 +227,11 @@ IMT_CreateContext (const char *name)
static in_context_t * __attribute__ ((pure)) static in_context_t * __attribute__ ((pure))
imt_find_context (const char *name) imt_find_context (const char *name)
{ {
for (size_t i = 0; i < in_contexts.size; i++) { if (name) {
if (strcmp (name, in_contexts.a[i].name) == 0) { for (size_t i = 0; i < in_contexts.size; i++) {
return &in_contexts.a[i]; if (strcmp (name, in_contexts.a[i].name) == 0) {
return &in_contexts.a[i];
}
} }
} }
return 0; return 0;
@ -312,6 +315,10 @@ IMT_CreateIMT (int context, const char *imt_name, const char *chain_imt_name)
} }
} }
imt = malloc (sizeof (imt_t)); imt = malloc (sizeof (imt_t));
if (!ctx->imts) {
ctx->default_imt = imt;
ctx->active_imt = imt;
}
*ctx->imt_tail = imt; *ctx->imt_tail = imt;
ctx->imt_tail = &imt->next; ctx->imt_tail = &imt->next;
@ -330,7 +337,7 @@ IMT_CreateIMT (int context, const char *imt_name, const char *chain_imt_name)
num_axes * sizeof (in_axisbinding_t *)); num_axes * sizeof (in_axisbinding_t *));
} }
if (num_buttons) { if (num_buttons) {
memset (imt->axis_bindings.a, 0, memset (imt->button_bindings.a, 0,
num_buttons * sizeof (in_buttonbinding_t *)); num_buttons * sizeof (in_buttonbinding_t *));
} }
return 1; return 1;
@ -481,7 +488,6 @@ IMT_ProcessButton (int button, int state)
{ {
imt_t *imt = in_contexts.a[imt_current_context].active_imt; imt_t *imt = in_contexts.a[imt_current_context].active_imt;
Sys_Printf ("IMT_ProcessButton: %d %d\n", button, state);
while (imt) { while (imt) {
in_buttonbinding_t *b = imt->button_bindings.a[button]; in_buttonbinding_t *b = imt->button_bindings.a[button];
if (b) { if (b) {
@ -598,6 +604,8 @@ imt_drop_all_f (void)
free (imt); free (imt);
} }
ctx->active_imt = 0; ctx->active_imt = 0;
ctx->default_imt = 0;
ctx->imt_tail = &ctx->imts;
} }
} }
@ -638,3 +646,143 @@ IMT_Init (void)
Cmd_AddCommand (cmd->name, cmd->func, cmd->desc); Cmd_AddCommand (cmd->name, cmd->func, cmd->desc);
} }
} }
void
IMT_SaveConfig (plitem_t *config)
{
plitem_t *ctx_list = PL_NewArray ();
PL_D_AddObject (config, "contexts", ctx_list);
for (size_t i = 0; i < in_contexts.size; i++) {
in_context_t *context = &in_contexts.a[i];
plitem_t *ctx = PL_NewDictionary (0); //FIXME hashlinks
PL_A_AddObject (ctx_list, ctx);
PL_D_AddObject (ctx, "name", PL_NewString (context->name));
if (context->imts) {
plitem_t *imt_list = PL_NewArray ();
PL_D_AddObject (ctx, "imts", imt_list);
for (imt_t *imt = context->imts; imt; imt = imt->next) {
plitem_t *imt_cfg = PL_NewDictionary (0); //FIXME hashlinks
PL_D_AddObject (imt_cfg, "name", PL_NewString (imt->name));
if (imt->chain) {
PL_D_AddObject (imt_cfg, "chain",
PL_NewString (imt->chain->name));
}
PL_A_AddObject (imt_list, imt_cfg);
// the bindings are not written here because they are managed
// by IN_Binding_SaveConfig: IMT does not really know the
// device-input structure (it cound via the blocks, but it
// doesn't know the device names (by design))
}
}
if (context->default_imt) {
PL_D_AddObject (ctx, "default_imt",
PL_NewString (context->default_imt->name));
}
}
}
void
IMT_SaveAxisConfig (plitem_t *axes, int axis_ind, int dev_axis)
{
for (size_t i = 0; i < in_contexts.size; i++) {
in_context_t *context = &in_contexts.a[i];
for (imt_t *imt = context->imts; imt; imt = imt->next) {
in_axisbinding_t *a = imt->axis_bindings.a[axis_ind];
if (a) {
in_recipe_t *recipe = a->recipe;
plitem_t *axis = PL_NewDictionary (0); //FIXME hashlinks
PL_A_AddObject (axes, axis);
PL_D_AddObject (axis, "imt", PL_NewString (imt->name));
PL_D_AddObject (axis, "num",
PL_NewString (va (0, "%d", dev_axis)));
PL_D_AddObject (axis, "axis", PL_NewString (a->axis->name));
PL_D_AddObject (axis, "min",
PL_NewString (va (0, "%d", recipe->min)));
PL_D_AddObject (axis, "max",
PL_NewString (va (0, "%d", recipe->max)));
PL_D_AddObject (axis, "minzone",
PL_NewString (va (0, "%d", recipe->minzone)));
PL_D_AddObject (axis, "maxzone",
PL_NewString (va (0, "%d", recipe->maxzone)));
PL_D_AddObject (axis, "deadzone",
PL_NewString (va (0, "%d", recipe->deadzone)));
PL_D_AddObject (axis, "curve",
PL_NewString (va (0, "%.9g", recipe->curve)));
PL_D_AddObject (axis, "scale",
PL_NewString (va (0, "%.9g", recipe->scale)));
}
}
}
}
void
IMT_SaveButtonConfig (plitem_t *buttons, int button_ind, int dev_button)
{
for (size_t i = 0; i < in_contexts.size; i++) {
in_context_t *context = &in_contexts.a[i];
for (imt_t *imt = context->imts; imt; imt = imt->next) {
in_buttonbinding_t *b = imt->button_bindings.a[button_ind];
if (b) {
plitem_t *button = PL_NewDictionary (0); //FIXME hashlinks
PL_A_AddObject (buttons, button);
PL_D_AddObject (button, "imt", PL_NewString (imt->name));
PL_D_AddObject (button, "num",
PL_NewString (va (0, "%d", dev_button)));
switch (b->type) {
case inb_button:
PL_D_AddObject (button, "binding",
PL_NewString (va (0, "+%s",
b->button->name)));
break;
case inb_command:
PL_D_AddObject (button, "binding",
PL_NewString (b->command));
break;
}
}
}
}
}
void
IMT_LoadConfig (plitem_t *config)
{
imt_drop_all_f ();
plitem_t *ctx_list = PL_ObjectForKey (config, "contexts");
if (PL_Type (ctx_list) != QFArray) {
Sys_Printf ("IMT_LoadConfig: contexts not an array\n");
return;
}
for (int i = 0, count = PL_A_NumObjects (ctx_list); i < count; i++) {
plitem_t *ctx = PL_ObjectAtIndex (ctx_list, i);
const char *name = PL_String (PL_ObjectForKey (ctx, "name"));
in_context_t *context = imt_find_context (name);
if (!context) {
continue;
}
plitem_t *imts = PL_ObjectForKey (ctx, "imts");
if (!imts || PL_Type (imts) != QFArray) {
continue;
}
for (int j = 0, num_imts = PL_A_NumObjects (imts); j < num_imts; j++) {
plitem_t *imt = PL_ObjectAtIndex (imts, j);
const char *imt_name = PL_String (PL_ObjectForKey (imt, "name"));
const char *imt_chain = PL_String (PL_ObjectForKey (imt, "chain"));
if (imt_name) {
IMT_CreateIMT (context - in_contexts.a, imt_name, imt_chain);
}
}
const char *default_imt = PL_String (PL_ObjectForKey (ctx,
"default_imt"));
if (default_imt) {
context->default_imt = IMT_FindIMT (default_imt);
}
if (!context->default_imt) {
context->default_imt = context->imts;
}
context->active_imt = context->default_imt;
}
}