mirror of
https://github.com/systemd/systemd.git
synced 2024-11-05 06:52:22 +03:00
442 lines
9.7 KiB
C
442 lines
9.7 KiB
C
/*
|
|
* keymap - dump keymap of an evdev device or set a new keymap from a file
|
|
*
|
|
* Based on keyfuzz by Lennart Poettering <mzqrovna@0pointer.net>
|
|
* Adapted for udev-extras by Martin Pitt <martin.pitt@ubuntu.com>
|
|
*
|
|
* Copyright (C) 2006, Lennart Poettering
|
|
* Copyright (C) 2009, Canonical Ltd.
|
|
*
|
|
* keymap 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.
|
|
*
|
|
* keymap 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 keymap; if not, write to the Free Software Foundation,
|
|
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <ctype.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <fcntl.h>
|
|
#include <getopt.h>
|
|
#include <sys/ioctl.h>
|
|
#include <linux/limits.h>
|
|
#include <linux/input.h>
|
|
|
|
const struct key* lookup_key (const char *str, unsigned int len);
|
|
|
|
#include "keys-from-name.h"
|
|
#include "keys-to-name.h"
|
|
|
|
#define MAX_SCANCODES 1024
|
|
|
|
static int evdev_open(const char *dev)
|
|
{
|
|
int fd;
|
|
char fn[PATH_MAX];
|
|
|
|
if (strncmp(dev, "/dev", 4) != 0) {
|
|
snprintf(fn, sizeof(fn), "/dev/%s", dev);
|
|
dev = fn;
|
|
}
|
|
|
|
if ((fd = open(dev, O_RDWR)) < 0) {
|
|
fprintf(stderr, "error open('%s'): %m\n", dev);
|
|
return -1;
|
|
}
|
|
return fd;
|
|
}
|
|
|
|
static int evdev_get_keycode(int fd, int scancode, int e)
|
|
{
|
|
int codes[2];
|
|
|
|
codes[0] = scancode;
|
|
if (ioctl(fd, EVIOCGKEYCODE, codes) < 0) {
|
|
if (e && errno == EINVAL) {
|
|
return -2;
|
|
} else {
|
|
fprintf(stderr, "EVIOCGKEYCODE: %m\n");
|
|
return -1;
|
|
}
|
|
}
|
|
return codes[1];
|
|
}
|
|
|
|
static int evdev_set_keycode(int fd, int scancode, int keycode)
|
|
{
|
|
int codes[2];
|
|
|
|
codes[0] = scancode;
|
|
codes[1] = keycode;
|
|
|
|
if (ioctl(fd, EVIOCSKEYCODE, codes) < 0) {
|
|
fprintf(stderr, "EVIOCSKEYCODE: %m\n");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int evdev_driver_version(int fd, char *v, size_t l)
|
|
{
|
|
int version;
|
|
|
|
if (ioctl(fd, EVIOCGVERSION, &version)) {
|
|
fprintf(stderr, "EVIOCGVERSION: %m\n");
|
|
return -1;
|
|
}
|
|
|
|
snprintf(v, l, "%i.%i.%i.", version >> 16, (version >> 8) & 0xff, version & 0xff);
|
|
return 0;
|
|
}
|
|
|
|
static int evdev_device_name(int fd, char *n, size_t l)
|
|
{
|
|
if (ioctl(fd, EVIOCGNAME(l), n) < 0) {
|
|
fprintf(stderr, "EVIOCGNAME: %m\n");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Return a lower-case string with KEY_ prefix removed */
|
|
static const char* format_keyname(const char* key) {
|
|
static char result[101];
|
|
const char* s;
|
|
int len;
|
|
|
|
for (s = key+4, len = 0; *s && len < 100; ++len, ++s)
|
|
result[len] = tolower(*s);
|
|
result[len] = '\0';
|
|
return result;
|
|
}
|
|
|
|
static int dump_table(int fd) {
|
|
char version[256], name[256];
|
|
int scancode, r = -1;
|
|
|
|
if (evdev_driver_version(fd, version, sizeof(version)) < 0)
|
|
goto fail;
|
|
|
|
if (evdev_device_name(fd, name, sizeof(name)) < 0)
|
|
goto fail;
|
|
|
|
printf("### evdev %s, driver '%s'\n", version, name);
|
|
|
|
r = 0;
|
|
for (scancode = 0; scancode < MAX_SCANCODES; scancode++) {
|
|
int keycode;
|
|
|
|
if ((keycode = evdev_get_keycode(fd, scancode, 1)) < 0) {
|
|
if (keycode == -2)
|
|
continue;
|
|
r = -1;
|
|
break;
|
|
}
|
|
|
|
if (keycode < KEY_MAX && key_names[keycode])
|
|
printf("0x%03x %s\n", scancode, format_keyname(key_names[keycode]));
|
|
else
|
|
printf("0x%03x 0x%03x\n", scancode, keycode);
|
|
}
|
|
fail:
|
|
return r;
|
|
}
|
|
|
|
static void set_key(int fd, const char* scancode_str, const char* keyname)
|
|
{
|
|
unsigned scancode;
|
|
char *endptr;
|
|
char t[105] = "KEY_UNKNOWN";
|
|
const struct key *k;
|
|
|
|
scancode = (unsigned) strtol(scancode_str, &endptr, 0);
|
|
if (*endptr != '\0') {
|
|
fprintf(stderr, "ERROR: Invalid scancode\n");
|
|
exit(1);
|
|
}
|
|
|
|
snprintf(t, sizeof(t), "KEY_%s", keyname);
|
|
|
|
if (!(k = lookup_key(t, strlen(t)))) {
|
|
fprintf(stderr, "ERROR: Unknown key name '%s'\n", keyname);
|
|
exit(1);
|
|
}
|
|
|
|
if (evdev_set_keycode(fd, scancode, k->id) < 0)
|
|
fprintf(stderr, "setting scancode 0x%2X to key code %i failed\n",
|
|
scancode, k->id);
|
|
else
|
|
printf("setting scancode 0x%2X to key code %i\n",
|
|
scancode, k->id);
|
|
}
|
|
|
|
static int merge_table(int fd, const char *filename) {
|
|
int r = 0;
|
|
int line = 0;
|
|
FILE* f;
|
|
|
|
f = fopen(filename, "r");
|
|
if (!f) {
|
|
perror(filename);
|
|
return -1;
|
|
}
|
|
|
|
while (!feof(f)) {
|
|
char s[256], *p;
|
|
int scancode, new_keycode, old_keycode;
|
|
|
|
if (!fgets(s, sizeof(s), f))
|
|
break;
|
|
|
|
line++;
|
|
p = s+strspn(s, "\t ");
|
|
if (*p == '#' || *p == '\n')
|
|
continue;
|
|
|
|
if (sscanf(p, "%i %i", &scancode, &new_keycode) != 2) {
|
|
char t[105] = "KEY_UNKNOWN";
|
|
const struct key *k;
|
|
|
|
if (sscanf(p, "%i %100s", &scancode, t+4) != 2) {
|
|
fprintf(stderr, "WARNING: Parse failure at line %i, ignoring.\n", line);
|
|
r = -1;
|
|
continue;
|
|
}
|
|
|
|
if (!(k = lookup_key(t, strlen(t)))) {
|
|
fprintf(stderr, "WARNING: Unknown key '%s' at line %i, ignoring.\n", t, line);
|
|
r = -1;
|
|
continue;
|
|
}
|
|
|
|
new_keycode = k->id;
|
|
}
|
|
|
|
|
|
if ((old_keycode = evdev_get_keycode(fd, scancode, 0)) < 0) {
|
|
r = -1;
|
|
goto fail;
|
|
}
|
|
|
|
if (evdev_set_keycode(fd, scancode, new_keycode) < 0) {
|
|
r = -1;
|
|
goto fail;
|
|
}
|
|
|
|
if (new_keycode != old_keycode)
|
|
fprintf(stderr, "Remapped scancode 0x%02x to 0x%02x (prior: 0x%02x)\n",
|
|
scancode, new_keycode, old_keycode);
|
|
}
|
|
fail:
|
|
fclose(f);
|
|
return r;
|
|
}
|
|
|
|
static const char* default_keymap_path(const char* path)
|
|
{
|
|
static char result[PATH_MAX];
|
|
|
|
/* If keymap file is given without a path, assume udev directory; must end with '/' * */
|
|
if (!strchr(path, '/')) {
|
|
snprintf(result, sizeof(result), "%s%s", LIBEXECDIR "/keymaps/", path);
|
|
return result;
|
|
}
|
|
return path;
|
|
}
|
|
|
|
/* read one event; return 1 if valid */
|
|
static int read_event(int fd, struct input_event* ev)
|
|
{
|
|
int ret;
|
|
ret = read(fd, ev, sizeof(struct input_event));
|
|
|
|
if (ret < 0) {
|
|
perror("read");
|
|
return 0;
|
|
}
|
|
if (ret != sizeof(struct input_event)) {
|
|
fprintf(stderr, "did not get enough data for event struct, aborting\n");
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void print_key(uint32_t scancode, uint16_t keycode, int has_scan, int has_key)
|
|
{
|
|
const char *keyname;
|
|
|
|
/* ignore key release events */
|
|
if (has_key == 1)
|
|
return;
|
|
|
|
if (has_key == 0 && has_scan != 0) {
|
|
fprintf(stderr, "got scan code event 0x%02X without a key code event\n",
|
|
scancode);
|
|
return;
|
|
}
|
|
|
|
if (has_scan != 0)
|
|
printf("scan code: 0x%02X ", scancode);
|
|
else
|
|
printf("(no scan code received) ");
|
|
|
|
keyname = key_names[keycode];
|
|
if (keyname != NULL)
|
|
printf("key code: %s\n", format_keyname(keyname));
|
|
else
|
|
printf("key code: %03X\n", keycode);
|
|
}
|
|
|
|
static void interactive(int fd)
|
|
{
|
|
struct input_event ev;
|
|
uint32_t last_scan = 0;
|
|
uint16_t last_key = 0;
|
|
int has_scan; /* boolean */
|
|
int has_key; /* 0: none, 1: release, 2: press */
|
|
|
|
/* grab input device */
|
|
ioctl(fd, EVIOCGRAB, 1);
|
|
puts("Press ESC to finish, or Control-C if this device is not your primary keyboard");
|
|
|
|
has_scan = has_key = 0;
|
|
while (read_event(fd, &ev)) {
|
|
/* Drivers usually send the scan code first, then the key code,
|
|
* then a SYN. Some drivers (like thinkpad_acpi) send the key
|
|
* code first, and some drivers might not send SYN events, so
|
|
* keep a robust state machine which can deal with any of those
|
|
*/
|
|
|
|
if (ev.type == EV_MSC && ev.code == MSC_SCAN) {
|
|
if (has_scan) {
|
|
fputs("driver did not send SYN event in between key events; previous event:\n",
|
|
stderr);
|
|
print_key(last_scan, last_key, has_scan, has_key);
|
|
has_key = 0;
|
|
}
|
|
|
|
last_scan = ev.value;
|
|
has_scan = 1;
|
|
/*printf("--- got scan %u; has scan %i key %i\n", last_scan, has_scan, has_key); */
|
|
}
|
|
else if (ev.type == EV_KEY) {
|
|
if (has_key) {
|
|
fputs("driver did not send SYN event in between key events; previous event:\n",
|
|
stderr);
|
|
print_key(last_scan, last_key, has_scan, has_key);
|
|
has_scan = 0;
|
|
}
|
|
|
|
last_key = ev.code;
|
|
has_key = 1 + ev.value;
|
|
/*printf("--- got key %hu; has scan %i key %i\n", last_key, has_scan, has_key);*/
|
|
|
|
/* Stop on ESC */
|
|
if (ev.code == KEY_ESC && ev.value == 0)
|
|
break;
|
|
}
|
|
else if (ev.type == EV_SYN) {
|
|
/*printf("--- got SYN; has scan %i key %i\n", has_scan, has_key);*/
|
|
print_key(last_scan, last_key, has_scan, has_key);
|
|
|
|
has_scan = has_key = 0;
|
|
}
|
|
|
|
}
|
|
|
|
/* release input device */
|
|
ioctl(fd, EVIOCGRAB, 0);
|
|
}
|
|
|
|
static void help(int error)
|
|
{
|
|
const char* h = "Usage: keymap <event device> [<map file>]\n"
|
|
" keymap <event device> scancode keyname [...]\n"
|
|
" keymap -i <event device>\n";
|
|
if (error) {
|
|
fputs(h, stderr);
|
|
exit(2);
|
|
} else {
|
|
fputs(h, stdout);
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
static const struct option options[] = {
|
|
{ "help", no_argument, NULL, 'h' },
|
|
{ "interactive", no_argument, NULL, 'i' },
|
|
{}
|
|
};
|
|
int fd = -1;
|
|
int opt_interactive = 0;
|
|
int i;
|
|
|
|
while (1) {
|
|
int option;
|
|
|
|
option = getopt_long(argc, argv, "hi", options, NULL);
|
|
if (option == -1)
|
|
break;
|
|
|
|
switch (option) {
|
|
case 'h':
|
|
help(0);
|
|
|
|
case 'i':
|
|
opt_interactive = 1;
|
|
break;
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (argc < optind+1)
|
|
help (1);
|
|
|
|
if ((fd = evdev_open(argv[optind])) < 0)
|
|
return 3;
|
|
|
|
/* one argument (device): dump or interactive */
|
|
if (argc == optind+1) {
|
|
if (opt_interactive)
|
|
interactive(fd);
|
|
else
|
|
dump_table(fd);
|
|
return 0;
|
|
}
|
|
|
|
/* two arguments (device, mapfile): set map file */
|
|
if (argc == optind+2) {
|
|
merge_table(fd, default_keymap_path(argv[optind+1]));
|
|
return 0;
|
|
}
|
|
|
|
/* more arguments (device, scancode/keyname pairs): set keys directly */
|
|
if ((argc - optind - 1) % 2 == 0) {
|
|
for (i = optind+1; i < argc; i += 2)
|
|
set_key(fd, argv[i], argv[i+1]);
|
|
return 0;
|
|
}
|
|
|
|
/* invalid number of arguments */
|
|
help(1);
|
|
return 1; /* not reached */
|
|
}
|