xref: /openbsd-src/sbin/wsconsctl/mousecfg.c (revision c08965e9c177ac9b4ce34a134339d443b08de977)
1*c08965e9Schrisz /* $OpenBSD: mousecfg.c,v 1.12 2024/10/05 13:27:16 chrisz Exp $ */
2d22ea701Sbru 
3d22ea701Sbru /*
4d22ea701Sbru  * Copyright (c) 2017 Ulf Brosziewski
5d22ea701Sbru  *
6d22ea701Sbru  * Permission to use, copy, modify, and distribute this software for any
7d22ea701Sbru  * purpose with or without fee is hereby granted, provided that the above
8d22ea701Sbru  * copyright notice and this permission notice appear in all copies.
9d22ea701Sbru  *
10d22ea701Sbru  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11d22ea701Sbru  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12d22ea701Sbru  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13d22ea701Sbru  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14d22ea701Sbru  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15d22ea701Sbru  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16d22ea701Sbru  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17d22ea701Sbru  */
18d22ea701Sbru 
19d22ea701Sbru /*
20d22ea701Sbru  * Read/write wsmouse parameters for touchpad configuration.
21d22ea701Sbru  */
22d22ea701Sbru 
239b9064ccSderaadt #include <sys/types.h>
24d22ea701Sbru #include <sys/ioctl.h>
259b9064ccSderaadt #include <sys/time.h>
26d22ea701Sbru #include <dev/wscons/wsconsio.h>
27d22ea701Sbru #include <stdio.h>
28d22ea701Sbru #include <stdlib.h>
29d22ea701Sbru #include <string.h>
30d22ea701Sbru #include <err.h>
31d22ea701Sbru #include <errno.h>
32d22ea701Sbru #include "mousecfg.h"
33d22ea701Sbru 
349b9064ccSderaadt #ifndef nitems
359b9064ccSderaadt #define nitems(_a)       (sizeof((_a)) / sizeof((_a)[0]))
369b9064ccSderaadt #endif
379b9064ccSderaadt 
389310c18aSbru #define BASESIZE ((WSMOUSECFG__FILTERS - WSMOUSECFG_DX_SCALE) \
399310c18aSbru     + (WSMOUSECFG__DEBUG - WSMOUSECFG_LOG_INPUT))
40d22ea701Sbru 
41d22ea701Sbru static const int range[][2] = {
429310c18aSbru 	{ WSMOUSECFG_DX_SCALE, WSMOUSECFG__FILTERS - 1 },
439310c18aSbru 	{ WSMOUSECFG_LOG_INPUT, WSMOUSECFG__DEBUG - 1 },
449310c18aSbru 	{ WSMOUSECFG_DX_MAX, WSMOUSECFG__TPFILTERS - 1 },
459310c18aSbru 	{ WSMOUSECFG_SOFTBUTTONS, WSMOUSECFG__TPFEATURES - 1 },
469310c18aSbru 	{ WSMOUSECFG_LEFT_EDGE, WSMOUSECFG__TPSETUP - 1 },
47d22ea701Sbru };
48d22ea701Sbru 
49d22ea701Sbru static const int touchpad_types[] = {
50d22ea701Sbru 	WSMOUSE_TYPE_SYNAPTICS,		/* Synaptics touchpad */
51d22ea701Sbru 	WSMOUSE_TYPE_ALPS,		/* ALPS touchpad */
52d22ea701Sbru 	WSMOUSE_TYPE_ELANTECH,		/* Elantech touchpad */
53d22ea701Sbru 	WSMOUSE_TYPE_SYNAP_SBTN,	/* Synaptics soft buttons */
54c2ca70daSjcs 	WSMOUSE_TYPE_TOUCHPAD,		/* Generic touchpad */
55d22ea701Sbru };
56d22ea701Sbru 
57d22ea701Sbru struct wsmouse_parameters cfg_tapping = {
58d22ea701Sbru 	(struct wsmouse_param[]) {
59b1ba6288Sbru 	    { WSMOUSECFG_TAP_ONE_BTNMAP, 0 },
60b1ba6288Sbru 	    { WSMOUSECFG_TAP_TWO_BTNMAP, 0 },
61b1ba6288Sbru 	    { WSMOUSECFG_TAP_THREE_BTNMAP, 0 }, },
62b1ba6288Sbru 	3
63d22ea701Sbru };
64d22ea701Sbru 
659310c18aSbru struct wsmouse_parameters cfg_mtbuttons = {
669310c18aSbru 	(struct wsmouse_param[]) {
679310c18aSbru 	    { WSMOUSECFG_MTBUTTONS, 0 }, },
689310c18aSbru 	1
699310c18aSbru };
709310c18aSbru 
71d22ea701Sbru struct wsmouse_parameters cfg_scaling = {
72d22ea701Sbru 	(struct wsmouse_param[]) {
73d22ea701Sbru 	    { WSMOUSECFG_DX_SCALE, 0 },
74d22ea701Sbru 	    { WSMOUSECFG_DY_SCALE, 0 } },
75d22ea701Sbru 	2
76d22ea701Sbru };
77d22ea701Sbru 
780137e658Sbru struct wsmouse_parameters cfg_edges = {
790137e658Sbru 	(struct wsmouse_param[]) {
800137e658Sbru 	    { WSMOUSECFG_TOP_EDGE, 0 },
810137e658Sbru 	    { WSMOUSECFG_RIGHT_EDGE, 0 },
820137e658Sbru 	    { WSMOUSECFG_BOTTOM_EDGE, 0 },
830137e658Sbru 	    { WSMOUSECFG_LEFT_EDGE, 0 } },
840137e658Sbru 	4
850137e658Sbru };
860137e658Sbru 
87d22ea701Sbru struct wsmouse_parameters cfg_swapsides = {
88d22ea701Sbru 	(struct wsmouse_param[]) {
89d22ea701Sbru 	    { WSMOUSECFG_SWAPSIDES, 0 }, },
90d22ea701Sbru 	1
91d22ea701Sbru };
92d22ea701Sbru 
93d22ea701Sbru struct wsmouse_parameters cfg_disable = {
94d22ea701Sbru 	(struct wsmouse_param[]) {
95d22ea701Sbru 	    { WSMOUSECFG_DISABLE, 0 }, },
96d22ea701Sbru 	1
97d22ea701Sbru };
98d22ea701Sbru 
9977727bd5Sbru struct wsmouse_parameters cfg_revscroll = {
10077727bd5Sbru 	(struct wsmouse_param[]) {
10177727bd5Sbru 	    { WSMOUSECFG_REVERSE_SCROLLING, 0 }, },
10277727bd5Sbru 	1
10377727bd5Sbru };
10477727bd5Sbru 
105d22ea701Sbru struct wsmouse_parameters cfg_param = {
106d22ea701Sbru 	(struct wsmouse_param[]) {
107d22ea701Sbru 	    { -1, 0 },
108d22ea701Sbru 	    { -1, 0 },
109d22ea701Sbru 	    { -1, 0 },
110d22ea701Sbru 	    { -1, 0 } },
111d22ea701Sbru 	4
112d22ea701Sbru };
113d22ea701Sbru 
114a143f7beSbru int cfg_touchpad;
115a143f7beSbru 
116d22ea701Sbru static int cfg_horiz_res;
117d22ea701Sbru static int cfg_vert_res;
1189310c18aSbru static struct wsmouse_param cfg_buffer[WSMOUSECFG_MAX];
119d22ea701Sbru 
120d22ea701Sbru 
121d22ea701Sbru int
122d22ea701Sbru mousecfg_init(int dev_fd, const char **errstr)
123d22ea701Sbru {
124d22ea701Sbru 	struct wsmouse_calibcoords coords;
125d22ea701Sbru 	struct wsmouse_parameters parameters;
126d22ea701Sbru 	struct wsmouse_param *param;
127d22ea701Sbru 	enum wsmousecfg k;
128d22ea701Sbru 	int i, err, type;
129d22ea701Sbru 
130d22ea701Sbru 	*errstr = NULL;
131d22ea701Sbru 
132d22ea701Sbru 	if ((err = ioctl(dev_fd, WSMOUSEIO_GTYPE, &type))) {
133d22ea701Sbru 		*errstr = "WSMOUSEIO_GTYPE";
134d22ea701Sbru 		return err;
135d22ea701Sbru 	}
136a143f7beSbru 	cfg_touchpad = 0;
137a143f7beSbru 	for (i = 0; !cfg_touchpad && i < nitems(touchpad_types); i++)
138a143f7beSbru 		cfg_touchpad = (type == touchpad_types[i]);
139d22ea701Sbru 
140a143f7beSbru 	cfg_horiz_res = cfg_vert_res = 0;
141a143f7beSbru 	if (cfg_touchpad) {
142d22ea701Sbru 		if ((err = ioctl(dev_fd, WSMOUSEIO_GCALIBCOORDS, &coords))) {
143d22ea701Sbru 			*errstr = "WSMOUSEIO_GCALIBCOORDS";
144d22ea701Sbru 			return err;
145d22ea701Sbru 		}
146d22ea701Sbru 		cfg_horiz_res = coords.resx;
147d22ea701Sbru 		cfg_vert_res = coords.resy;
148a143f7beSbru 	}
149d22ea701Sbru 
150d22ea701Sbru 	param = cfg_buffer;
151d22ea701Sbru 	for (i = 0; i < nitems(range); i++)
152d22ea701Sbru 		for (k = range[i][0]; k <= range[i][1]; k++, param++) {
153d22ea701Sbru 			param->key = k;
154d22ea701Sbru 			param->value = 0;
155d22ea701Sbru 		}
156d22ea701Sbru 
157d22ea701Sbru 	parameters.params = cfg_buffer;
1588665dd6dSbru 	parameters.nparams = BASESIZE;
159d22ea701Sbru 	if ((err = ioctl(dev_fd, WSMOUSEIO_GETPARAMS, &parameters))) {
160d22ea701Sbru 		*errstr = "WSMOUSEIO_GETPARAMS";
161d22ea701Sbru 		return (err);
162d22ea701Sbru 	}
1638665dd6dSbru 	if (cfg_touchpad) {
1648665dd6dSbru 		parameters.params = cfg_buffer + BASESIZE;
1659310c18aSbru 		parameters.nparams = WSMOUSECFG_MAX - BASESIZE;
1668665dd6dSbru 		if (ioctl(dev_fd, WSMOUSEIO_GETPARAMS, &parameters))
1678665dd6dSbru 			cfg_touchpad = 0;
1688665dd6dSbru 	}
169d22ea701Sbru 
170d22ea701Sbru 	return (0);
171d22ea701Sbru }
172d22ea701Sbru 
173d22ea701Sbru /* Map a key to its buffer index. */
174d22ea701Sbru static int
175d22ea701Sbru index_of(enum wsmousecfg key)
176d22ea701Sbru {
177d22ea701Sbru 	int i, n;
178d22ea701Sbru 
179a143f7beSbru 	for (i = 0, n = 0; i < nitems(range); i++) {
180a143f7beSbru 		if (key <= range[i][1] && key >= range[i][0]) {
181d22ea701Sbru 			return (key - range[i][0] + n);
182a143f7beSbru 		}
183d22ea701Sbru 		n += range[i][1] - range[i][0] + 1;
184a143f7beSbru 		if (!cfg_touchpad && n >= BASESIZE)
185a143f7beSbru 			break;
186a143f7beSbru 	}
187d22ea701Sbru 
188d22ea701Sbru 	return (-1);
189d22ea701Sbru }
190d22ea701Sbru 
191d22ea701Sbru int
192d22ea701Sbru mousecfg_get_field(struct wsmouse_parameters *field)
193d22ea701Sbru {
194d22ea701Sbru 	int i, n;
195d22ea701Sbru 
196d22ea701Sbru 	for (i = 0; i < field->nparams; i++) {
197d22ea701Sbru 		if ((n = index_of(field->params[i].key)) >= 0)
198d22ea701Sbru 			field->params[i].value = cfg_buffer[n].value;
199d22ea701Sbru 		else
200d22ea701Sbru 			return (-1);
201d22ea701Sbru 	}
202d22ea701Sbru 	return (0);
203d22ea701Sbru }
204d22ea701Sbru 
205d22ea701Sbru int
206d22ea701Sbru mousecfg_put_field(int fd, struct wsmouse_parameters *field)
207d22ea701Sbru {
208d22ea701Sbru 	int i, n, d, err;
209d22ea701Sbru 
210d22ea701Sbru 	d = 0;
211d22ea701Sbru 	for (i = 0; i < field->nparams; i++)
212d22ea701Sbru 		if ((n = index_of(field->params[i].key)) < 0)
213d22ea701Sbru 			return (-1);
214d22ea701Sbru 		else
215d22ea701Sbru 			d |= (cfg_buffer[n].value != field->params[i].value);
216d22ea701Sbru 
217d22ea701Sbru 	if (!d)
218d22ea701Sbru 		return (0);
219d22ea701Sbru 
220d22ea701Sbru 	/* Write and read back immediately, wsmouse may normalize values. */
221d22ea701Sbru 	if ((err = ioctl(fd, WSMOUSEIO_SETPARAMS, field))
222d22ea701Sbru 	    || (err = ioctl(fd, WSMOUSEIO_GETPARAMS, field)))
223d22ea701Sbru 		return err;
224d22ea701Sbru 
22521cd3483Sbru 	for (i = 0; i < field->nparams; i++) {
22621cd3483Sbru 		n = index_of(field->params[i].key);
227d22ea701Sbru 		cfg_buffer[n].value = field->params[i].value;
22821cd3483Sbru 	}
229d22ea701Sbru 
230d22ea701Sbru 	return (0);
231d22ea701Sbru }
232d22ea701Sbru 
233d22ea701Sbru static int
234d22ea701Sbru get_value(struct wsmouse_parameters *field, enum wsmousecfg key)
235d22ea701Sbru {
236d22ea701Sbru 	int i;
237d22ea701Sbru 
238d22ea701Sbru 	for (i = 0; i < field->nparams && key != field->params[i].key; i++) {}
239d22ea701Sbru 
240d22ea701Sbru 	return (i < field->nparams ? field->params[i].value : 0);
241d22ea701Sbru }
242d22ea701Sbru 
243d22ea701Sbru static void
244d22ea701Sbru set_value(struct wsmouse_parameters *field, enum wsmousecfg key, int value)
245d22ea701Sbru {
246d22ea701Sbru 	int i;
247d22ea701Sbru 
248d22ea701Sbru 	for (i = 0; i < field->nparams && key != field->params[i].key; i++) {}
249d22ea701Sbru 
250d22ea701Sbru 	field->params[i].value = (i < field->nparams ? value : 0);
251d22ea701Sbru }
252d22ea701Sbru 
2530137e658Sbru static float
2540137e658Sbru get_percent(struct wsmouse_parameters *field, enum wsmousecfg key)
2550137e658Sbru {
2560137e658Sbru 	return ((float) get_value(field, key) * 100 / 4096);
2570137e658Sbru }
2580137e658Sbru 
2590137e658Sbru static void
2600137e658Sbru set_percent(struct wsmouse_parameters *field, enum wsmousecfg key, float f)
2610137e658Sbru {
2620137e658Sbru 	set_value(field, key, (int) ((f * 4096 + 50) / 100));
2630137e658Sbru }
2640137e658Sbru 
2650137e658Sbru static int
266b1ba6288Sbru set_tapping(struct wsmouse_parameters *field, char *tapping)
267b1ba6288Sbru {
268b1ba6288Sbru 	int i1, i2, i3;
269b1ba6288Sbru 
270b1ba6288Sbru 	switch (sscanf(tapping, "%d,%d,%d", &i1, &i2, &i3)) {
271b1ba6288Sbru 	case 1:
272b1ba6288Sbru 		if (i1 == 0) /* Disable */
273b1ba6288Sbru 			i2 = i3 = i1;
274b1ba6288Sbru 		else { /* Enable with defaults */
275b1ba6288Sbru 			i1 = 1; /* Left click */
276b1ba6288Sbru 			i2 = 3; /* Right click */
277b1ba6288Sbru 			i3 = 2; /* Middle click */
278b1ba6288Sbru 		}
279b1ba6288Sbru 		/* FALLTHROUGH */
280b1ba6288Sbru 	case 3:
281b1ba6288Sbru 		set_value(field, WSMOUSECFG_TAP_ONE_BTNMAP, i1);
282b1ba6288Sbru 		set_value(field, WSMOUSECFG_TAP_TWO_BTNMAP, i2);
283b1ba6288Sbru 		set_value(field, WSMOUSECFG_TAP_THREE_BTNMAP, i3);
284b1ba6288Sbru 		return (0);
285b1ba6288Sbru 	}
286b1ba6288Sbru 	return (-1);
287b1ba6288Sbru }
288b1ba6288Sbru 
289b1ba6288Sbru static int
2900137e658Sbru set_edges(struct wsmouse_parameters *field, char *edges)
2910137e658Sbru {
2920137e658Sbru 	float f1, f2, f3, f4;
2930137e658Sbru 
2940137e658Sbru 	if (sscanf(edges, "%f,%f,%f,%f", &f1, &f2, &f3, &f4) == 4) {
2950137e658Sbru 		set_percent(field, WSMOUSECFG_TOP_EDGE, f1);
2960137e658Sbru 		set_percent(field, WSMOUSECFG_RIGHT_EDGE,f2);
2970137e658Sbru 		set_percent(field, WSMOUSECFG_BOTTOM_EDGE, f3);
2980137e658Sbru 		set_percent(field, WSMOUSECFG_LEFT_EDGE, f4);
2990137e658Sbru 		return (0);
3000137e658Sbru 	}
3010137e658Sbru 	return (-1);
3020137e658Sbru }
3030137e658Sbru 
304d22ea701Sbru /*
305d22ea701Sbru  * Read or write up to four raw parameter values.  In this case
306d22ea701Sbru  * reading is a 'put' operation that writes back a value from the
307d22ea701Sbru  * buffer.
308d22ea701Sbru  */
309d22ea701Sbru static int
310d22ea701Sbru read_param(struct wsmouse_parameters *field, char *val)
311d22ea701Sbru {
312d22ea701Sbru 	int i, j, n;
313d22ea701Sbru 
314d22ea701Sbru 	n = sscanf(val, "%d:%d,%d:%d,%d:%d,%d:%d",
315d22ea701Sbru 		&field->params[0].key, &field->params[0].value,
316d22ea701Sbru 		&field->params[1].key, &field->params[1].value,
317d22ea701Sbru 		&field->params[2].key, &field->params[2].value,
318d22ea701Sbru 		&field->params[3].key, &field->params[3].value);
319d22ea701Sbru 	if (n > 0 && (n & 1) == 0) {
320d22ea701Sbru 		n /= 2;
321d22ea701Sbru 		for (i = 0; i < n; i++) {
322d22ea701Sbru 			if (index_of(field->params[i].key) < 0)
323d22ea701Sbru 				return (-1);
324d22ea701Sbru 		}
325d22ea701Sbru 		field->nparams = n;
326d22ea701Sbru 		return (0);
327d22ea701Sbru 	}
328d22ea701Sbru 	n = sscanf(val, "%d,%d,%d,%d",
329d22ea701Sbru 		&field->params[0].key, &field->params[1].key,
330d22ea701Sbru 		&field->params[2].key, &field->params[3].key);
331d22ea701Sbru 	if (n > 0) {
332d22ea701Sbru 		for (i = 0; i < n; i++) {
333d22ea701Sbru 			if ((j = index_of(field->params[i].key)) < 0)
334d22ea701Sbru 				return (-1);
335d22ea701Sbru 			field->params[i].value = cfg_buffer[j].value;
336d22ea701Sbru 		}
337d22ea701Sbru 		field->nparams = n;
338d22ea701Sbru 		return (0);
339d22ea701Sbru 	}
340d22ea701Sbru 	return (-1);
341d22ea701Sbru }
342d22ea701Sbru 
343d22ea701Sbru void
344d22ea701Sbru mousecfg_pr_field(struct wsmouse_parameters *field)
345d22ea701Sbru {
346d22ea701Sbru 	int i, value;
347d22ea701Sbru 	float f;
348d22ea701Sbru 
349d22ea701Sbru 	if (field == &cfg_param) {
350d22ea701Sbru 		for (i = 0; i < field->nparams; i++)
351d22ea701Sbru 			printf(i > 0 ? ",%d:%d" : "%d:%d",
352d22ea701Sbru 			    field->params[i].key,
353d22ea701Sbru 			    field->params[i].value);
354d22ea701Sbru 		return;
355d22ea701Sbru 	}
356d22ea701Sbru 
357d22ea701Sbru 	if (field == &cfg_scaling) {
358d22ea701Sbru 		value = get_value(field, WSMOUSECFG_DX_SCALE);
359*c08965e9Schrisz 		value = value == 0 ? 4096 : value;
360d22ea701Sbru 		f = (float) value / 4096;
361d22ea701Sbru 		printf("%.3f", f);
362d22ea701Sbru 		return;
363d22ea701Sbru 	}
364d22ea701Sbru 
3650137e658Sbru 	if (field == &cfg_edges) {
3660137e658Sbru 		printf("%.1f,%.1f,%.1f,%.1f",
3670137e658Sbru 		    get_percent(field, WSMOUSECFG_TOP_EDGE),
3680137e658Sbru 		    get_percent(field, WSMOUSECFG_RIGHT_EDGE),
3690137e658Sbru 		    get_percent(field, WSMOUSECFG_BOTTOM_EDGE),
3700137e658Sbru 		    get_percent(field, WSMOUSECFG_LEFT_EDGE));
3710137e658Sbru 		return;
3720137e658Sbru 	}
3730137e658Sbru 
374d22ea701Sbru 	for (i = 0; i < field->nparams; i++)
375d22ea701Sbru 		printf(i > 0 ? ",%d" : "%d", field->params[i].value);
376d22ea701Sbru }
377d22ea701Sbru 
378d22ea701Sbru void
379d22ea701Sbru mousecfg_rd_field(struct wsmouse_parameters *field, char *val)
380d22ea701Sbru {
381d22ea701Sbru 	int i, n;
382d22ea701Sbru 	const char *s;
383d22ea701Sbru 	float f;
384d22ea701Sbru 
385d22ea701Sbru 	if (field == &cfg_param) {
386d22ea701Sbru 		if (read_param(field, val))
387d22ea701Sbru 			errx(1, "invalid input (param)");
388d22ea701Sbru 		return;
389d22ea701Sbru 	}
390d22ea701Sbru 
391b1ba6288Sbru 	if (field == &cfg_tapping) {
392b1ba6288Sbru 		if (set_tapping(field, val))
393b1ba6288Sbru 			errx(1, "invalid input (tapping)");
394b1ba6288Sbru 		return;
395b1ba6288Sbru 	}
396b1ba6288Sbru 
397d22ea701Sbru 	if (field == &cfg_scaling) {
398d22ea701Sbru 		if (sscanf(val, "%f", &f) == 1) {
399d22ea701Sbru 			n = (int) (f * 4096);
400d22ea701Sbru 			set_value(field, WSMOUSECFG_DX_SCALE, n);
401d22ea701Sbru 			if (cfg_horiz_res && cfg_vert_res)
402d22ea701Sbru 				n = n * cfg_horiz_res / cfg_vert_res;
403d22ea701Sbru 			set_value(field, WSMOUSECFG_DY_SCALE, n);
404d22ea701Sbru 		} else {
405d22ea701Sbru 			errx(1, "invalid input (scaling)");
406d22ea701Sbru 		}
407d22ea701Sbru 		return;
408d22ea701Sbru 	}
409d22ea701Sbru 
4100137e658Sbru 	if (field == &cfg_edges) {
4110137e658Sbru 		if (set_edges(field, val))
4120137e658Sbru 			errx(1, "invalid input (edges)");
4130137e658Sbru 		return;
4140137e658Sbru 	}
4150137e658Sbru 
416d22ea701Sbru 	s = val;
417d22ea701Sbru 	for (i = 0; i < field->nparams; i++) {
418d22ea701Sbru 		if (sscanf(s, (i > 0 ? ",%d" : "%d"), &n) != 1)
419d22ea701Sbru 			break;
420d22ea701Sbru 		field->params[i].value = abs(n);
421d22ea701Sbru 		for (s++; *s != '\0' && *s != ','; s++) {}
422d22ea701Sbru 	}
423d22ea701Sbru 	if (i < field->nparams || *s != '\0')
424d22ea701Sbru 		errx(1, "invalid input '%s'", val);
425d22ea701Sbru }
426