199ca880aSopenharmony_ci/***
299ca880aSopenharmony_ci  This file is part of systemd.
399ca880aSopenharmony_ci
499ca880aSopenharmony_ci  Copyright 2013 Kay Sievers <kay@vrfy.org>
599ca880aSopenharmony_ci
699ca880aSopenharmony_ci  systemd is free software; you can redistribute it and/or modify it
799ca880aSopenharmony_ci  under the terms of the GNU Lesser General Public License as published by
899ca880aSopenharmony_ci  the Free Software Foundation; either version 2.1 of the License, or
999ca880aSopenharmony_ci  (at your option) any later version.
1099ca880aSopenharmony_ci
1199ca880aSopenharmony_ci  systemd is distributed in the hope that it will be useful, but
1299ca880aSopenharmony_ci  WITHOUT ANY WARRANTY; without even the implied warranty of
1399ca880aSopenharmony_ci  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1499ca880aSopenharmony_ci  Lesser General Public License for more details.
1599ca880aSopenharmony_ci
1699ca880aSopenharmony_ci  You should have received a copy of the GNU Lesser General Public License
1799ca880aSopenharmony_ci  along with systemd; If not, see <http://www.gnu.org/licenses/>.
1899ca880aSopenharmony_ci***/
1999ca880aSopenharmony_ci
2099ca880aSopenharmony_ci#include <stdio.h>
2199ca880aSopenharmony_ci#include <errno.h>
2299ca880aSopenharmony_ci#include <string.h>
2399ca880aSopenharmony_ci#include <stdlib.h>
2499ca880aSopenharmony_ci#include <fcntl.h>
2599ca880aSopenharmony_ci#include <sys/ioctl.h>
2699ca880aSopenharmony_ci#include <linux/limits.h>
2799ca880aSopenharmony_ci#include <linux/input.h>
2899ca880aSopenharmony_ci
2999ca880aSopenharmony_ci#include "udev.h"
3099ca880aSopenharmony_ci
3199ca880aSopenharmony_ci#include "keyboard-keys-from-name.h"
3299ca880aSopenharmony_ci#include "keyboard-keys-to-name.h"
3399ca880aSopenharmony_ci
3499ca880aSopenharmony_cistatic int install_force_release(struct udev_device *dev, const unsigned *release, unsigned release_count) {
3599ca880aSopenharmony_ci        struct udev_device *atkbd;
3699ca880aSopenharmony_ci        const char *cur;
3799ca880aSopenharmony_ci        char codes[4096];
3899ca880aSopenharmony_ci        char *s;
3999ca880aSopenharmony_ci        size_t l;
4099ca880aSopenharmony_ci        unsigned i;
4199ca880aSopenharmony_ci        int ret;
4299ca880aSopenharmony_ci
4399ca880aSopenharmony_ci        atkbd = udev_device_get_parent_with_subsystem_devtype(dev, "serio", NULL);
4499ca880aSopenharmony_ci        if (!atkbd)
4599ca880aSopenharmony_ci                return -ENODEV;
4699ca880aSopenharmony_ci
4799ca880aSopenharmony_ci        cur = udev_device_get_sysattr_value(atkbd, "force_release");
4899ca880aSopenharmony_ci        if (!cur)
4999ca880aSopenharmony_ci                return -ENODEV;
5099ca880aSopenharmony_ci
5199ca880aSopenharmony_ci        s = codes;
5299ca880aSopenharmony_ci        l = sizeof(codes);
5399ca880aSopenharmony_ci
5499ca880aSopenharmony_ci        /* copy current content */
5599ca880aSopenharmony_ci        l = strpcpy(&s, l, cur);
5699ca880aSopenharmony_ci
5799ca880aSopenharmony_ci        /* append new codes */
5899ca880aSopenharmony_ci        for (i = 0; i < release_count; i++)
5999ca880aSopenharmony_ci                l = strpcpyf(&s, l, ",%u", release[i]);
6099ca880aSopenharmony_ci
6199ca880aSopenharmony_ci        log_debug("keyboard: updating force-release list with '%s'", codes);
6299ca880aSopenharmony_ci        ret = udev_device_set_sysattr_value(atkbd, "force_release", codes);
6399ca880aSopenharmony_ci        if (ret < 0)
6499ca880aSopenharmony_ci                log_error_errno(ret, "Error writing force-release attribute: %m");
6599ca880aSopenharmony_ci        return ret;
6699ca880aSopenharmony_ci}
6799ca880aSopenharmony_ci
6899ca880aSopenharmony_cistatic void map_keycode(int fd, const char *devnode, int scancode, const char *keycode)
6999ca880aSopenharmony_ci{
7099ca880aSopenharmony_ci        struct {
7199ca880aSopenharmony_ci                unsigned scan;
7299ca880aSopenharmony_ci                unsigned key;
7399ca880aSopenharmony_ci        } map;
7499ca880aSopenharmony_ci        char *endptr;
7599ca880aSopenharmony_ci        const struct key *k;
7699ca880aSopenharmony_ci        unsigned keycode_num;
7799ca880aSopenharmony_ci
7899ca880aSopenharmony_ci        /* translate identifier to key code */
7999ca880aSopenharmony_ci        k = keyboard_lookup_key(keycode, strlen(keycode));
8099ca880aSopenharmony_ci        if (k) {
8199ca880aSopenharmony_ci                keycode_num = k->id;
8299ca880aSopenharmony_ci        } else {
8399ca880aSopenharmony_ci                /* check if it's a numeric code already */
8499ca880aSopenharmony_ci                keycode_num = strtoul(keycode, &endptr, 0);
8599ca880aSopenharmony_ci                if (endptr[0] !='\0') {
8699ca880aSopenharmony_ci                        log_error("Unknown key identifier '%s'", keycode);
8799ca880aSopenharmony_ci                        return;
8899ca880aSopenharmony_ci                }
8999ca880aSopenharmony_ci        }
9099ca880aSopenharmony_ci
9199ca880aSopenharmony_ci        map.scan = scancode;
9299ca880aSopenharmony_ci        map.key = keycode_num;
9399ca880aSopenharmony_ci
9499ca880aSopenharmony_ci        log_debug("keyboard: mapping scan code %d (0x%x) to key code %d (0x%x)",
9599ca880aSopenharmony_ci                  map.scan, map.scan, map.key, map.key);
9699ca880aSopenharmony_ci
9799ca880aSopenharmony_ci        if (ioctl(fd, EVIOCSKEYCODE, &map) < 0)
9899ca880aSopenharmony_ci                log_error_errno(errno, "Error calling EVIOCSKEYCODE on device node '%s' (scan code 0x%x, key code %d): %m", devnode, map.scan, map.key);
9999ca880aSopenharmony_ci}
10099ca880aSopenharmony_ci
10199ca880aSopenharmony_cistatic inline char* parse_token(const char *current, int32_t *val_out) {
10299ca880aSopenharmony_ci        char *next;
10399ca880aSopenharmony_ci        int32_t val;
10499ca880aSopenharmony_ci
10599ca880aSopenharmony_ci        if (!current)
10699ca880aSopenharmony_ci                return NULL;
10799ca880aSopenharmony_ci
10899ca880aSopenharmony_ci        val = strtol(current, &next, 0);
10999ca880aSopenharmony_ci        if (*next && *next != ':')
11099ca880aSopenharmony_ci                return NULL;
11199ca880aSopenharmony_ci
11299ca880aSopenharmony_ci        if (next != current)
11399ca880aSopenharmony_ci                *val_out = val;
11499ca880aSopenharmony_ci
11599ca880aSopenharmony_ci        if (*next)
11699ca880aSopenharmony_ci                next++;
11799ca880aSopenharmony_ci
11899ca880aSopenharmony_ci        return next;
11999ca880aSopenharmony_ci}
12099ca880aSopenharmony_ci
12199ca880aSopenharmony_cistatic void override_abs(int fd, const char *devnode,
12299ca880aSopenharmony_ci                         unsigned evcode, const char *value) {
12399ca880aSopenharmony_ci        struct input_absinfo absinfo;
12499ca880aSopenharmony_ci        int rc;
12599ca880aSopenharmony_ci        char *next;
12699ca880aSopenharmony_ci
12799ca880aSopenharmony_ci        rc = ioctl(fd, EVIOCGABS(evcode), &absinfo);
12899ca880aSopenharmony_ci        if (rc < 0) {
12999ca880aSopenharmony_ci                log_error_errno(errno, "Unable to EVIOCGABS device \"%s\"", devnode);
13099ca880aSopenharmony_ci                return;
13199ca880aSopenharmony_ci        }
13299ca880aSopenharmony_ci
13399ca880aSopenharmony_ci        next = parse_token(value, &absinfo.minimum);
13499ca880aSopenharmony_ci        next = parse_token(next, &absinfo.maximum);
13599ca880aSopenharmony_ci        next = parse_token(next, &absinfo.resolution);
13699ca880aSopenharmony_ci        next = parse_token(next, &absinfo.fuzz);
13799ca880aSopenharmony_ci        next = parse_token(next, &absinfo.flat);
13899ca880aSopenharmony_ci        if (!next) {
13999ca880aSopenharmony_ci                log_error("Unable to parse EV_ABS override '%s' for '%s'", value, devnode);
14099ca880aSopenharmony_ci                return;
14199ca880aSopenharmony_ci        }
14299ca880aSopenharmony_ci
14399ca880aSopenharmony_ci        log_debug("keyboard: %x overridden with %"PRIi32"/%"PRIi32"/%"PRIi32"/%"PRIi32"/%"PRIi32" for \"%s\"",
14499ca880aSopenharmony_ci                  evcode,
14599ca880aSopenharmony_ci                  absinfo.minimum, absinfo.maximum, absinfo.resolution, absinfo.fuzz, absinfo.flat,
14699ca880aSopenharmony_ci                  devnode);
14799ca880aSopenharmony_ci        rc = ioctl(fd, EVIOCSABS(evcode), &absinfo);
14899ca880aSopenharmony_ci        if (rc < 0)
14999ca880aSopenharmony_ci                log_error_errno(errno, "Unable to EVIOCSABS device \"%s\"", devnode);
15099ca880aSopenharmony_ci}
15199ca880aSopenharmony_ci
15299ca880aSopenharmony_cistatic void set_trackpoint_sensitivity(struct udev_device *dev, const char *value)
15399ca880aSopenharmony_ci{
15499ca880aSopenharmony_ci        struct udev_device *pdev;
15599ca880aSopenharmony_ci        char val_s[DECIMAL_STR_MAX(int)];
15699ca880aSopenharmony_ci        int r, val_i;
15799ca880aSopenharmony_ci
15899ca880aSopenharmony_ci        /* The sensitivity sysfs attr belongs to the serio parent device */
15999ca880aSopenharmony_ci        pdev = udev_device_get_parent_with_subsystem_devtype(dev, "serio", NULL);
16099ca880aSopenharmony_ci        if (!pdev) {
16199ca880aSopenharmony_ci                log_warning("Failed to get serio parent for '%s'", udev_device_get_devnode(dev));
16299ca880aSopenharmony_ci                return;
16399ca880aSopenharmony_ci        }
16499ca880aSopenharmony_ci
16599ca880aSopenharmony_ci        r = safe_atoi(value, &val_i);
16699ca880aSopenharmony_ci        if (r < 0) {
16799ca880aSopenharmony_ci                log_error("Unable to parse POINTINGSTICK_SENSITIVITY '%s' for '%s'", value, udev_device_get_devnode(dev));
16899ca880aSopenharmony_ci                return;
16999ca880aSopenharmony_ci        }
17099ca880aSopenharmony_ci
17199ca880aSopenharmony_ci        xsprintf(val_s, "%d", val_i);
17299ca880aSopenharmony_ci
17399ca880aSopenharmony_ci        r = udev_device_set_sysattr_value(pdev, "sensitivity", val_s);
17499ca880aSopenharmony_ci        if (r < 0)
17599ca880aSopenharmony_ci                log_error_errno(r, "Failed to write 'sensitivity' attribute for '%s': %m", udev_device_get_devnode(pdev));
17699ca880aSopenharmony_ci}
17799ca880aSopenharmony_ci
17899ca880aSopenharmony_cistatic int open_device(const char *devnode) {
17999ca880aSopenharmony_ci        int fd;
18099ca880aSopenharmony_ci
18199ca880aSopenharmony_ci        fd = open(devnode, O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
18299ca880aSopenharmony_ci        if (fd < 0)
18399ca880aSopenharmony_ci                return log_error_errno(errno, "Error opening device \"%s\": %m", devnode);
18499ca880aSopenharmony_ci
18599ca880aSopenharmony_ci        return fd;
18699ca880aSopenharmony_ci}
18799ca880aSopenharmony_ci
18899ca880aSopenharmony_cistatic int builtin_keyboard(struct udev_device *dev, int argc, char *argv[], bool test) {
18999ca880aSopenharmony_ci        struct udev_list_entry *entry;
19099ca880aSopenharmony_ci        unsigned release[1024];
19199ca880aSopenharmony_ci        unsigned release_count = 0;
19299ca880aSopenharmony_ci        _cleanup_close_ int fd = -1;
19399ca880aSopenharmony_ci        const char *node;
19499ca880aSopenharmony_ci
19599ca880aSopenharmony_ci        node = udev_device_get_devnode(dev);
19699ca880aSopenharmony_ci        if (!node) {
19799ca880aSopenharmony_ci                log_error("No device node for \"%s\"", udev_device_get_syspath(dev));
19899ca880aSopenharmony_ci                return EXIT_FAILURE;
19999ca880aSopenharmony_ci        }
20099ca880aSopenharmony_ci
20199ca880aSopenharmony_ci        udev_list_entry_foreach(entry, udev_device_get_properties_list_entry(dev)) {
20299ca880aSopenharmony_ci                const char *key;
20399ca880aSopenharmony_ci                char *endptr;
20499ca880aSopenharmony_ci
20599ca880aSopenharmony_ci                key = udev_list_entry_get_name(entry);
20699ca880aSopenharmony_ci                if (startswith(key, "KEYBOARD_KEY_")) {
20799ca880aSopenharmony_ci                        const char *keycode;
20899ca880aSopenharmony_ci                        unsigned scancode;
20999ca880aSopenharmony_ci
21099ca880aSopenharmony_ci                        /* KEYBOARD_KEY_<hex scan code>=<key identifier string> */
21199ca880aSopenharmony_ci                        scancode = strtoul(key + 13, &endptr, 16);
21299ca880aSopenharmony_ci                        if (endptr[0] != '\0') {
21399ca880aSopenharmony_ci                                log_warning("Unable to parse scan code from \"%s\"", key);
21499ca880aSopenharmony_ci                                continue;
21599ca880aSopenharmony_ci                        }
21699ca880aSopenharmony_ci
21799ca880aSopenharmony_ci                        keycode = udev_list_entry_get_value(entry);
21899ca880aSopenharmony_ci
21999ca880aSopenharmony_ci                        /* a leading '!' needs a force-release entry */
22099ca880aSopenharmony_ci                        if (keycode[0] == '!') {
22199ca880aSopenharmony_ci                                keycode++;
22299ca880aSopenharmony_ci
22399ca880aSopenharmony_ci                                release[release_count] = scancode;
22499ca880aSopenharmony_ci                                if (release_count <  ELEMENTSOF(release)-1)
22599ca880aSopenharmony_ci                                        release_count++;
22699ca880aSopenharmony_ci
22799ca880aSopenharmony_ci                                if (keycode[0] == '\0')
22899ca880aSopenharmony_ci                                        continue;
22999ca880aSopenharmony_ci                        }
23099ca880aSopenharmony_ci
23199ca880aSopenharmony_ci                        if (fd == -1) {
23299ca880aSopenharmony_ci                                fd = open_device(node);
23399ca880aSopenharmony_ci                                if (fd < 0)
23499ca880aSopenharmony_ci                                        return EXIT_FAILURE;
23599ca880aSopenharmony_ci                        }
23699ca880aSopenharmony_ci
23799ca880aSopenharmony_ci                        map_keycode(fd, node, scancode, keycode);
23899ca880aSopenharmony_ci                } else if (startswith(key, "EVDEV_ABS_")) {
23999ca880aSopenharmony_ci                        unsigned evcode;
24099ca880aSopenharmony_ci
24199ca880aSopenharmony_ci                        /* EVDEV_ABS_<EV_ABS code>=<min>:<max>:<res>:<fuzz>:<flat> */
24299ca880aSopenharmony_ci                        evcode = strtoul(key + 10, &endptr, 16);
24399ca880aSopenharmony_ci                        if (endptr[0] != '\0') {
24499ca880aSopenharmony_ci                                log_warning("Unable to parse EV_ABS code from \"%s\"", key);
24599ca880aSopenharmony_ci                                continue;
24699ca880aSopenharmony_ci                        }
24799ca880aSopenharmony_ci
24899ca880aSopenharmony_ci                        if (fd == -1) {
24999ca880aSopenharmony_ci                                fd = open_device(node);
25099ca880aSopenharmony_ci                                if (fd < 0)
25199ca880aSopenharmony_ci                                        return EXIT_FAILURE;
25299ca880aSopenharmony_ci                        }
25399ca880aSopenharmony_ci
25499ca880aSopenharmony_ci                        override_abs(fd, node, evcode, udev_list_entry_get_value(entry));
25599ca880aSopenharmony_ci                } else if (streq(key, "POINTINGSTICK_SENSITIVITY")) {
25699ca880aSopenharmony_ci                        set_trackpoint_sensitivity(dev, udev_list_entry_get_value(entry));
25799ca880aSopenharmony_ci                }
25899ca880aSopenharmony_ci        }
25999ca880aSopenharmony_ci
26099ca880aSopenharmony_ci        /* install list of force-release codes */
26199ca880aSopenharmony_ci        if (release_count > 0)
26299ca880aSopenharmony_ci                install_force_release(dev, release, release_count);
26399ca880aSopenharmony_ci
26499ca880aSopenharmony_ci        return EXIT_SUCCESS;
26599ca880aSopenharmony_ci}
26699ca880aSopenharmony_ci
26799ca880aSopenharmony_ciconst struct udev_builtin udev_builtin_keyboard = {
26899ca880aSopenharmony_ci        .name = "keyboard",
26999ca880aSopenharmony_ci        .cmd = builtin_keyboard,
27099ca880aSopenharmony_ci        .help = "Keyboard scan code to key mapping",
27199ca880aSopenharmony_ci};
272