| /* |
| * ir-ctl.c - Program to send and receive IR using lirc interface |
| * |
| * Copyright (C) 2016 Sean Young <sean@mess.org> |
| * |
| * This program 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, version 2 of the License. |
| * |
| * This program 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. |
| */ |
| |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/ioctl.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <argp.h> |
| #include <sysexits.h> |
| |
| #include <config.h> |
| |
| #include <linux/lirc.h> |
| |
| #include "ir-encode.h" |
| #include "keymap.h" |
| #include "bpf_encoder.h" |
| |
| #ifdef ENABLE_NLS |
| # define _(string) gettext(string) |
| # include "gettext.h" |
| # include <locale.h> |
| # include <langinfo.h> |
| # include <iconv.h> |
| #else |
| # include <string.h> |
| # define _(string) string |
| #endif |
| |
| # define N_(string) string |
| |
| /* taken from glibc unistd.h */ |
| #ifndef TEMP_FAILURE_RETRY |
| #define TEMP_FAILURE_RETRY(expression) \ |
| ({ long int __result; \ |
| do __result = (long int) (expression); \ |
| while (__result == -1L && errno == EINTR); \ |
| __result; }) |
| #endif |
| |
| /* See drivers/media/rc/lirc_dev.c line 22 */ |
| #define LIRCBUF_SIZE 1024 |
| #define IR_DEFAULT_TIMEOUT 125000 |
| #define UNSET UINT32_MAX |
| /* Maximum number of columns per line */ |
| #define LINE_SIZE 8192 |
| |
| const char *argp_program_version = "IR ctl version " V4L_UTILS_VERSION; |
| const char *argp_program_bug_address = "Sean Young <sean@mess.org>"; |
| |
| enum send_ty { |
| SEND_RAW, |
| SEND_SCANCODE, |
| SEND_KEYCODE, |
| SEND_GAP, |
| }; |
| |
| /* |
| * Since this program drives the lirc interface, use the same terminology |
| */ |
| struct send { |
| struct send *next; |
| const char *fname; |
| enum send_ty ty; |
| union { |
| struct { |
| unsigned carrier; |
| unsigned len; |
| unsigned buf[LIRCBUF_SIZE]; |
| }; |
| struct { |
| unsigned scancode; |
| unsigned protocol; |
| }; |
| char keycode[1]; |
| unsigned gap; |
| }; |
| }; |
| |
| struct arguments { |
| char *device; |
| bool features; |
| bool receive; |
| bool verbose; |
| bool mode2; |
| struct keymap *keymap; |
| struct send *send; |
| bool oneshot; |
| char *savetofile; |
| int wideband; |
| unsigned carrier_low, carrier_high; |
| unsigned timeout; |
| unsigned gap; |
| int carrier_reports; |
| unsigned carrier; |
| unsigned duty; |
| unsigned emitters; |
| bool work_to_do; |
| }; |
| |
| static const struct argp_option options[] = { |
| { "device", 'd', N_("DEV"), 0, N_("lirc device to use") }, |
| { "features", 'f', 0, 0, N_("list lirc device features") }, |
| { "receive", 'r', N_("FILE"), OPTION_ARG_OPTIONAL, N_("receive IR to stdout or file") }, |
| { "send", 's', N_("FILE"), 0, N_("send IR pulse and space file") }, |
| { "scancode", 'S', N_("SCANCODE"), 0, N_("send IR scancode in protocol specified") }, |
| { "keycode", 'K', N_("KEYCODE"), 0, N_("send IR keycode from keymap") }, |
| { "verbose", 'v', 0, 0, N_("verbose output") }, |
| { .doc = N_("Receiving options:") }, |
| { "one-shot", '1', 0, 0, N_("end receiving after first message") }, |
| { "mode2", 2, 0, 0, N_("output in mode2 format") }, |
| { "wideband", 'w', 0, 0, N_("use wideband receiver aka learning mode") }, |
| { "narrowband", 'n', 0, 0, N_("use narrowband receiver, disable learning mode") }, |
| { "carrier-range", 'R', N_("RANGE"), 0, N_("set receiver carrier range") }, |
| { "measure-carrier", 'm', 0, 0, N_("report carrier frequency") }, |
| { "no-measure-carrier", 'M', 0, 0, N_("disable reporting carrier frequency") }, |
| { "timeout", 't', N_("TIMEOUT"), 0, N_("set receiving timeout") }, |
| { .doc = N_("Sending options:") }, |
| { "keymap", 'k', N_("KEYMAP"), 0, N_("use keymap to send key from") }, |
| { "carrier", 'c', N_("CARRIER"), 0, N_("set send carrier") }, |
| { "duty-cycle", 'D', N_("DUTY"), 0, N_("set send duty cycle") }, |
| { "emitters", 'e', N_("EMITTERS"), 0, N_("set send emitters") }, |
| { "gap", 'g', N_("GAP"), 0, N_("set gap between files or scancodes") }, |
| { } |
| }; |
| |
| static const char args_doc[] = N_( |
| "--features\n" |
| "--receive [save to file]\n" |
| "--send [file to send]\n" |
| "--scancode [scancode to send]\n" |
| "--keycode [keycode to send]\n" |
| "[to set lirc option]"); |
| |
| static const char doc[] = N_( |
| "\nReceive IR, send IR and list features of lirc device\n" |
| "You will need permission on /dev/lirc for the program to work\n" |
| "\nOn the options below, the arguments are:\n" |
| " DEV - the /dev/lirc* device to use\n" |
| " FILE - a text file containing pulses and spaces\n" |
| " CARRIER - the carrier frequency to use for sending\n" |
| " DUTY - the duty cycle to use for sending\n" |
| " EMITTERS - comma separated list of emitters to use for sending, e.g. 1,2\n" |
| " GAP - gap between sending in microseconds\n" |
| " RANGE - set range of accepted carrier frequencies, e.g. 20000-40000\n" |
| " TIMEOUT - set length of space before receiving stops in microseconds\n" |
| " KEYCODE - key code in keymap\n" |
| " SCANCODE - protocol:scancode, e.g. nec:0xa814\n" |
| " KEYMAP - a rc keymap file from which to send keys\n\n" |
| "Note that most lirc setting have global state, i.e. the device will remain\n" |
| "in this state until set otherwise."); |
| |
| static bool strtoint(const char *p, const char *unit, unsigned *ret) |
| { |
| char *end; |
| long arg = strtol(p, &end, 10); |
| if (end == NULL || (end[0] != 0 && strcasecmp(end, unit) != 0)) |
| return false; |
| |
| if (arg < 0 || arg >= 0xffffff) |
| return false; |
| |
| *ret = arg; |
| return true; |
| } |
| |
| static bool strtoscancode(const char *p, unsigned *ret) |
| { |
| char *end; |
| long long arg = strtoll(p, &end, 0); |
| if (end == NULL || end[0] != 0) |
| return false; |
| |
| if (arg < 0 || arg > 0xffffffff) |
| return false; |
| |
| *ret = arg; |
| return true; |
| } |
| |
| static unsigned parse_emitters(char *p) |
| { |
| unsigned emit = 0; |
| static const char *sep = " ,;:"; |
| char *saveptr, *q; |
| |
| q = strtok_r(p, sep, &saveptr); |
| while (q) { |
| char *endptr; |
| long e = strtol(q, &endptr, 10); |
| if ((endptr && *endptr) || e <= 0 || e > 32) |
| return 0; |
| |
| emit |= 1 << (e - 1); |
| q = strtok_r(NULL, sep, &saveptr); |
| } |
| |
| return emit; |
| } |
| |
| static struct send *read_file_pulse_space(struct arguments *args, const char *fname, FILE *input) |
| { |
| bool expect_pulse = true; |
| int lineno = 0, lastspace = 0; |
| char line[LINE_SIZE]; |
| int len = 0; |
| static const char whitespace[] = " \n\r\t"; |
| struct send *f; |
| |
| f = malloc(sizeof(*f)); |
| if (f == NULL) { |
| fprintf(stderr, _("Failed to allocate memory\n")); |
| return NULL; |
| } |
| f->ty = SEND_RAW; |
| f->carrier = UNSET; |
| f->fname = fname; |
| |
| while (fgets(line, sizeof(line), input)) { |
| char *p, *saveptr; |
| lineno++; |
| char *keyword = strtok_r(line, whitespace, &saveptr); |
| |
| if (keyword == NULL || *keyword == 0 || *keyword == '#' || |
| (keyword[0] == '/' && keyword[1] == '/')) |
| continue; |
| |
| p = strtok_r(NULL, whitespace, &saveptr); |
| if (p == NULL) { |
| fprintf(stderr, _("warning: %s:%d: missing argument\n"), fname, lineno); |
| continue; |
| } |
| |
| if (strcmp(keyword, "scancode") == 0) { |
| enum rc_proto proto; |
| unsigned scancode, carrier; |
| char *scancodestr; |
| |
| if (!expect_pulse) { |
| f->buf[len++] = args->gap; |
| expect_pulse = true; |
| } |
| |
| scancodestr = strchr(p, ':'); |
| if (!scancodestr) { |
| fprintf(stderr, _("error: %s:%d: scancode argument '%s' should be in protocol:scancode format\n"), fname, lineno, p); |
| fclose(input); |
| free(f); |
| return NULL; |
| } |
| |
| *scancodestr++ = 0; |
| |
| if (!protocol_match(p, &proto)) { |
| fprintf(stderr, _("error: %s:%d: protocol '%s' not found\n"), fname, lineno, p); |
| fclose(input); |
| free(f); |
| return NULL; |
| } |
| |
| if (!strtoscancode(scancodestr, &scancode)) { |
| fprintf(stderr, _("error: %s:%d: invalid scancode '%s'\n"), fname, lineno, scancodestr); |
| fclose(input); |
| free(f); |
| return NULL; |
| } |
| |
| if (!protocol_encoder_available(proto)) { |
| fprintf(stderr, _("error: %s:%d: no encoder available for `%s'\n"), fname, lineno, protocol_name(proto)); |
| fclose(input); |
| free(f); |
| return NULL; |
| } |
| |
| protocol_scancode_valid(&proto, &scancode); |
| |
| if (len + protocol_max_size(proto) >= LIRCBUF_SIZE) { |
| fprintf(stderr, _("error: %s:%d: too much IR for one transmit\n"), fname, lineno); |
| fclose(input); |
| free(f); |
| return NULL; |
| } |
| |
| carrier = protocol_carrier(proto); |
| if (f->carrier && f->carrier != carrier) |
| fprintf(stderr, _("error: %s:%d: carrier already specified\n"), fname, lineno); |
| else |
| f->carrier = carrier; |
| |
| len += protocol_encode(proto, scancode, f->buf + len); |
| expect_pulse = false; |
| continue; |
| } |
| |
| unsigned int arg; |
| if (!strtoint(p, "", &arg)) { |
| fprintf(stderr, _("warning: %s:%d: invalid argument '%s'\n"), fname, lineno, p); |
| continue; |
| } |
| |
| p = strtok_r(NULL, whitespace, &saveptr); |
| if (p && p[0] != '#' && !(p[0] == '/' && p[1] == '/')) { |
| fprintf(stderr, _("warning: %s:%d: '%s' unexpected\n"), fname, lineno, p); |
| continue; |
| } |
| |
| if (!strcmp(keyword, "space") || !strcmp(keyword, "timeout")) { |
| if (arg == 0) { |
| fprintf(stderr, _("warning: %s:%d: invalid argument to space '%d'\n"), fname, lineno, arg); |
| continue; |
| } |
| if (expect_pulse) { |
| if (len == 0) { |
| fprintf(stderr, _("warning: %s:%d: leading space ignored\n"), |
| fname, lineno); |
| } else { |
| f->buf[len-1] += arg; |
| } |
| } else { |
| f->buf[len++] = arg; |
| } |
| lastspace = lineno; |
| expect_pulse = true; |
| } else if (!strcmp(keyword, "pulse")) { |
| if (arg == 0) { |
| fprintf(stderr, _("warning: %s:%d: invalid argument to pulse '%d'\n"), fname, lineno, arg); |
| continue; |
| } |
| if (!expect_pulse) |
| f->buf[len-1] += arg; |
| else |
| f->buf[len++] = arg; |
| expect_pulse = false; |
| } else if (!strcmp(keyword, "carrier")) { |
| if (f->carrier != UNSET && f->carrier != arg) { |
| fprintf(stderr, _("warning: %s:%d: carrier already specified\n"), fname, lineno); |
| } else { |
| f->carrier = arg; |
| } |
| } else { |
| fprintf(stderr, _("warning: %s:%d: unknown keyword '%s' ignored\n"), fname, lineno, keyword); |
| continue; |
| } |
| |
| if (len >= LIRCBUF_SIZE) { |
| fprintf(stderr, _("warning: %s:%d: IR cannot exceed %u edges\n"), fname, lineno, LIRCBUF_SIZE); |
| break; |
| } |
| } |
| |
| fclose(input); |
| |
| if (len == 0) { |
| fprintf(stderr, _("%s: no pulses or spaces found\n"), fname); |
| free(f); |
| return NULL; |
| } |
| |
| if ((len % 2) == 0) { |
| fprintf(stderr, _("warning: %s:%d: trailing space ignored\n"), |
| fname, lastspace); |
| len--; |
| } |
| |
| f->len = len; |
| |
| return f; |
| } |
| |
| static struct send *read_file_raw(struct arguments *args, const char *fname, FILE *input) |
| { |
| int lineno = 0, lastspace = 0; |
| char line[LINE_SIZE]; |
| int len = 0; |
| static const char whitespace[] = " \n\r\t,"; |
| struct send *f; |
| |
| f = malloc(sizeof(*f)); |
| if (f == NULL) { |
| fprintf(stderr, _("Failed to allocate memory\n")); |
| fclose(input); |
| return NULL; |
| } |
| f->ty = SEND_RAW; |
| f->carrier = UNSET; |
| f->fname = fname; |
| |
| while (fgets(line, sizeof(line), input)) { |
| long int value; |
| char *p, *saveptr; |
| lineno++; |
| char *keyword = strtok_r(line, whitespace, &saveptr); |
| |
| for (;;) { |
| if (keyword == NULL || *keyword == 0 || *keyword == '#' || |
| (keyword[0] == '/' && keyword[1] == '/')) |
| break; |
| |
| value = strtol(keyword, &p, 10); |
| if (errno || *p) { |
| fprintf(stderr, _("%s:%d: error: expected integer, got `%s'\n"), |
| fname, lineno, keyword); |
| fclose(input); |
| free(f); |
| return NULL; |
| } |
| |
| if (len % 2) { |
| if (keyword[0] == '+') { |
| fprintf(stderr, _("%s:%d: error: pulse found where space expected `%s'\n"), fname, lineno, keyword); |
| free(f); |
| return NULL; |
| } |
| if (keyword[0] == '-') |
| value = -value; |
| } else { |
| if (keyword[0] == '-') { |
| fprintf(stderr, _("%s:%d: error: space found where pulse expected `%s'\n"), fname, lineno, keyword); |
| fclose(input); |
| free(f); |
| return NULL; |
| } |
| lastspace = lineno; |
| } |
| |
| if (value <= 0 || value >= LIRC_VALUE_MASK) { |
| fprintf(stderr, _("%s:%d: error: value `%s' out of range\n"), fname, lineno, keyword); |
| fclose(input); |
| free(f); |
| return NULL; |
| } |
| |
| f->buf[len++] = value; |
| |
| if (len >= LIRCBUF_SIZE) { |
| fprintf(stderr, _("warning: %s:%d: IR cannot exceed %u edges\n"), fname, lineno, LIRCBUF_SIZE); |
| break; |
| } |
| |
| keyword = strtok_r(NULL, whitespace, &saveptr); |
| } |
| } |
| |
| fclose(input); |
| |
| if (len == 0) { |
| fprintf(stderr, _("%s: no pulses or spaces found\n"), fname); |
| free(f); |
| return NULL; |
| } |
| |
| if ((len % 2) == 0) { |
| fprintf(stderr, _("warning: %s:%d: trailing space ignored\n"), |
| fname, lastspace); |
| len--; |
| } |
| |
| f->len = len; |
| |
| return f; |
| } |
| |
| static struct send *read_file(struct arguments *args, const char *fname) |
| { |
| FILE *input = fopen(fname, "r"); |
| char line[LINE_SIZE]; |
| |
| if (!input) { |
| fprintf(stderr, _("%s: could not open: %m\n"), fname); |
| return NULL; |
| } |
| |
| while (fgets(line, sizeof(line), input)) { |
| int start = 0; |
| |
| while (isspace(line[start])) |
| start++; |
| |
| switch (line[start]) { |
| case '/': |
| if (line[start+1] != '/') |
| break; |
| case 0: |
| case '#': |
| continue; |
| case '+': |
| case '-': |
| case '0' ... '9': |
| rewind(input); |
| return read_file_raw(args, fname, input); |
| default: |
| rewind(input); |
| return read_file_pulse_space(args, fname, input); |
| } |
| } |
| |
| fclose(input); |
| |
| fprintf(stderr, _("%s: file is empty\n"), fname); |
| |
| return NULL; |
| } |
| |
| static struct send *read_scancode(const char *name) |
| { |
| enum rc_proto proto; |
| struct send *f; |
| unsigned scancode; |
| char *pstr; |
| char *p = strchr(name, ':'); |
| |
| if (!p) { |
| fprintf(stderr, _("error: scancode '%s' must be in protocol:scancode format\n"), name); |
| return NULL; |
| } |
| |
| pstr = strndup(name, p - name); |
| |
| if (!protocol_match(pstr, &proto)) { |
| fprintf(stderr, _("error: protocol '%s' not found\n"), pstr); |
| free(pstr); |
| return NULL; |
| } |
| free(pstr); |
| |
| if (!strtoscancode(p + 1, &scancode)) { |
| fprintf(stderr, _("error: invalid scancode '%s'\n"), p + 1); |
| return NULL; |
| } |
| |
| protocol_scancode_valid(&proto, &scancode); |
| |
| f = malloc(sizeof(*f)); |
| if (f == NULL) { |
| fprintf(stderr, _("Failed to allocate memory\n")); |
| return NULL; |
| } |
| |
| f->ty = SEND_SCANCODE; |
| f->scancode = scancode; |
| f->protocol = proto; |
| |
| return f; |
| } |
| |
| static void add_to_send_list(struct arguments *arguments, struct send *send) |
| { |
| send->next = NULL; |
| |
| if (arguments->send == NULL) |
| arguments->send = send; |
| else { |
| // introduce gap |
| struct send *gap = malloc(sizeof(*gap)); |
| gap->ty = SEND_GAP; |
| gap->fname= NULL; |
| gap->gap = arguments->gap; |
| gap->next = send; |
| |
| struct send *p = arguments->send; |
| while (p->next) p = p->next; |
| p->next = gap; |
| } |
| } |
| |
| static error_t parse_opt(int k, char *arg, struct argp_state *state) |
| { |
| struct arguments *arguments = state->input; |
| struct keymap *map; |
| struct send *s; |
| |
| switch (k) { |
| case 'f': |
| if (arguments->receive || arguments->send) |
| argp_error(state, _("features can not be combined with receive or send option")); |
| arguments->features = true; |
| break; |
| // receiving |
| case 'r': |
| if (arguments->features || arguments->send) |
| argp_error(state, _("receive can not be combined with features or send option")); |
| |
| arguments->receive = true; |
| if (arg) { |
| if (arguments->savetofile) |
| argp_error(state, _("receive filename already set")); |
| |
| arguments->savetofile = arg; |
| } |
| break; |
| case '1': |
| arguments->oneshot = true; |
| break; |
| case 2: |
| arguments->mode2 = true; |
| break; |
| case 'v': |
| arguments->verbose = true; |
| break; |
| case 'm': |
| if (arguments->carrier_reports == 2) |
| argp_error(state, _("cannot enable and disable carrier reports")); |
| |
| arguments->carrier_reports = 1; |
| break; |
| case 'M': |
| if (arguments->carrier_reports == 1) |
| argp_error(state, _("cannot enable and disable carrier reports")); |
| |
| arguments->carrier_reports = 2; |
| break; |
| case 'n': |
| if (arguments->wideband) |
| argp_error(state, _("cannot use narrowband and wideband receiver at once")); |
| |
| arguments->wideband = 2; |
| break; |
| case 'w': |
| if (arguments->wideband) |
| argp_error(state, _("cannot use narrowband and wideband receiver at once")); |
| |
| arguments->wideband = 1; |
| break; |
| case 'R': { |
| long low, high; |
| char *end; |
| |
| low = strtol(arg, &end, 10); |
| if (end == NULL || end[0] != '-') |
| argp_error(state, _("cannot parse carrier range `%s'"), arg); |
| high = strtol(end + 1, &end, 10); |
| if (end[0] != 0 || low <= 0 || low >= high || high > 1000000) |
| argp_error(state, _("cannot parse carrier range `%s'"), arg); |
| |
| arguments->carrier_low = low; |
| arguments->carrier_high = high; |
| break; |
| } |
| case 't': |
| if (!strtoint(arg, "µs", &arguments->timeout)) |
| argp_error(state, _("cannot parse timeout `%s'"), arg); |
| break; |
| |
| // sending |
| case 'd': |
| arguments->device = arg; |
| break; |
| case 'c': |
| if (!strtoint(arg, "Hz", &arguments->carrier)) |
| argp_error(state, _("cannot parse carrier `%s'"), arg); |
| break; |
| case 'e': |
| arguments->emitters = parse_emitters(arg); |
| if (arguments->emitters == 0) |
| argp_error(state, _("cannot parse emitters `%s'"), arg); |
| break; |
| case 'g': |
| if (!strtoint(arg, "", &arguments->gap) || arguments->gap == 0) |
| argp_error(state, _("cannot parse gap `%s'"), arg); |
| break; |
| case 'D': |
| if (!strtoint(arg, "%", &arguments->duty) || |
| arguments->duty == 0 || arguments->duty >= 100) |
| argp_error(state, _("invalid duty cycle `%s'"), arg); |
| break; |
| case 's': |
| if (arguments->receive || arguments->features) |
| argp_error(state, _("send can not be combined with receive or features option")); |
| s = read_file(arguments, arg); |
| if (s == NULL) |
| exit(EX_DATAERR); |
| |
| add_to_send_list(arguments, s); |
| break; |
| case 'S': |
| if (arguments->receive || arguments->features) |
| argp_error(state, _("send can not be combined with receive or features option")); |
| s = read_scancode(arg); |
| if (s == NULL) |
| exit(EX_DATAERR); |
| |
| add_to_send_list(arguments, s); |
| break; |
| |
| case 'K': |
| if (arguments->receive || arguments->features) |
| argp_error(state, _("key send can not be combined with receive or features option")); |
| s = malloc(sizeof(*s) + strlen(arg)); |
| if (s == NULL) |
| exit(EX_DATAERR); |
| strcpy(s->keycode, arg); |
| s->ty = SEND_KEYCODE; |
| |
| add_to_send_list(arguments, s); |
| break; |
| |
| case 'k': |
| if (parse_keymap(arg, &map, arguments->verbose)) |
| exit(EX_DATAERR); |
| if (arguments->keymap == NULL) |
| arguments->keymap = map; |
| else { |
| // add to end of list of keymaps |
| struct keymap *p = arguments->keymap; |
| while (p->next) p = p->next; |
| p->next = map; |
| } |
| break; |
| |
| case ARGP_KEY_END: |
| if (!arguments->work_to_do) |
| argp_usage(state); |
| |
| break; |
| default: |
| return ARGP_ERR_UNKNOWN; |
| } |
| |
| if (k != '1' && k != 'd' && k != 'v' && k != 'k' && k != 2) |
| arguments->work_to_do = true; |
| |
| return 0; |
| } |
| |
| static struct send* convert_keycode(struct keymap *map, const char *keycode) |
| { |
| struct send *s = NULL; |
| int count = 0; |
| |
| for (;map; map = map->next) { |
| struct scancode_entry *se; |
| struct raw_entry *re; |
| |
| for (re = map->raw; re; re = re->next) { |
| if (strcmp(re->keycode, keycode)) |
| continue; |
| |
| count++; |
| |
| if (!s) { |
| s = malloc(sizeof(*s) + re->raw_length * sizeof(int)); |
| s->len = re->raw_length; |
| memcpy(s->buf, re->raw, s->len * sizeof(int)); |
| s->ty = SEND_RAW; |
| s->carrier = keymap_param(map, "carrier", 0); |
| s->next = NULL; |
| } |
| } |
| |
| for (se = map->scancode; se; se = se->next) { |
| int buf[LIRCBUF_SIZE], length; |
| const char *proto_str; |
| enum rc_proto proto; |
| |
| if (strcmp(se->keycode, keycode)) |
| continue; |
| |
| count++; |
| |
| if (s) |
| continue; |
| |
| proto_str = map->variant ?: map->protocol; |
| |
| if (protocol_match(proto_str, &proto)) { |
| s = malloc(sizeof(*s)); |
| s->protocol = proto; |
| s->scancode = se->scancode; |
| s->ty = SEND_SCANCODE; |
| s->next = NULL; |
| } else if (encode_bpf_protocol(map, se->scancode, |
| buf, &length)) { |
| s = malloc(sizeof(*s) + sizeof(int) * length); |
| s->len = length; |
| memcpy(s->buf, buf, length * sizeof(int)); |
| s->ty = SEND_RAW; |
| s->carrier = keymap_param(map, "carrier", 0); |
| s->next = NULL; |
| } else { |
| fprintf(stderr, _("error: protocol '%s' not supported\n"), proto_str); |
| return NULL; |
| } |
| } |
| } |
| |
| if (!s) { |
| fprintf(stderr, _("error: keycode `%s' not found in keymap\n"), keycode); |
| return NULL; |
| } |
| |
| if (count > 1) |
| fprintf(stderr, _("warning: keycode `%s' has %d definitions in keymaps, using first\n"), keycode, count); |
| |
| return s; |
| } |
| |
| static const struct argp argp = { |
| .options = options, |
| .parser = parse_opt, |
| .args_doc = args_doc, |
| .doc = doc |
| }; |
| |
| static int open_lirc(const char *fname, unsigned *features) |
| { |
| int fd; |
| |
| fd = TEMP_FAILURE_RETRY(open(fname, O_RDWR | O_CLOEXEC)); |
| if (fd == -1) { |
| fprintf(stderr, _("%s: cannot open: %m\n"), fname); |
| return -1; |
| } |
| |
| struct stat st; |
| int rc = TEMP_FAILURE_RETRY(fstat(fd, &st)); |
| if (rc) { |
| fprintf(stderr, _("%s: cannot stat: %m\n"), fname); |
| close(fd); |
| return -1; |
| } |
| |
| if ((st.st_mode & S_IFMT) != S_IFCHR) { |
| fprintf(stderr, _("%s: not character device\n"), fname); |
| close(fd); |
| return -1; |
| } |
| |
| rc = ioctl(fd, LIRC_GET_FEATURES, features); |
| if (rc) { |
| fprintf(stderr, _("%s: failed to get lirc features: %m\n"), fname); |
| close(fd); |
| return -1; |
| } |
| |
| return fd; |
| } |
| |
| static void lirc_set_send_carrier(int fd, const char *devname, unsigned features, unsigned carrier) |
| { |
| if (features & LIRC_CAN_SET_SEND_CARRIER) { |
| int rc = ioctl(fd, LIRC_SET_SEND_CARRIER, &carrier); |
| if (rc < 0) |
| fprintf(stderr, _("warning: %s: failed to set carrier: %m\n"), devname); |
| if (rc != 0) |
| fprintf(stderr, _("warning: %s: set send carrier returned %d, should return 0\n"), devname, rc); |
| } else |
| fprintf(stderr, _("warning: %s: does not support setting send carrier\n"), devname); |
| } |
| |
| static int lirc_options(struct arguments *args, int fd, unsigned features) |
| { |
| const char *dev = args->device; |
| int rc; |
| |
| if (args->timeout != UNSET) { |
| if (features & LIRC_CAN_SET_REC_TIMEOUT) { |
| rc = ioctl(fd, LIRC_SET_REC_TIMEOUT, &args->timeout); |
| if (rc) |
| fprintf(stderr, _("%s: failed to set receiving timeout\n"), dev); |
| } else |
| fprintf(stderr, _("%s: device does not support setting timeout\n"), dev); |
| } |
| |
| if (args->wideband) { |
| unsigned on = args->wideband == 1; |
| if (features & LIRC_CAN_USE_WIDEBAND_RECEIVER) { |
| rc = ioctl(fd, LIRC_SET_WIDEBAND_RECEIVER, &on); |
| if (rc) |
| fprintf(stderr, _("%s: failed to set wideband receiver %s\n"), dev, on ? _("on") : _("off")); |
| } else |
| fprintf(stderr, _("%s: device does not have wideband receiver\n"), dev); |
| } |
| |
| if (args->carrier_reports) { |
| unsigned on = args->carrier_reports == 1; |
| if (features & LIRC_CAN_MEASURE_CARRIER) { |
| rc = ioctl(fd, LIRC_SET_MEASURE_CARRIER_MODE, &on); |
| if (rc) |
| fprintf(stderr, _("%s: failed to set carrier reports %s\n"), dev, on ? _("on") : _("off")); |
| } else |
| fprintf(stderr, _("%s: device cannot measure carrier\n"), dev); |
| } |
| |
| if (features & LIRC_CAN_REC_MODE2) { |
| unsigned on = 1; |
| rc = ioctl(fd, LIRC_SET_REC_TIMEOUT_REPORTS, &on); |
| if (rc) |
| fprintf(stderr, _("%s: failed to enable timeout reports: %m\n"), dev); |
| } |
| |
| if (args->carrier_low) { |
| if (features & LIRC_CAN_SET_REC_CARRIER_RANGE) { |
| rc = ioctl(fd, LIRC_SET_REC_CARRIER_RANGE, &args->carrier_low); |
| if (rc) |
| fprintf(stderr, _("%s: failed to set low carrier range: %m\n"), dev); |
| rc = ioctl(fd, LIRC_SET_REC_CARRIER, &args->carrier_high); |
| if (rc) |
| fprintf(stderr, _("%s: failed to set high carrier range: %m\n"), dev); |
| } else |
| fprintf(stderr, _("%s: device does not support setting receiver carrier range\n"), dev); |
| } |
| |
| if (args->carrier != UNSET) |
| lirc_set_send_carrier(fd, dev, features, args->carrier); |
| |
| if (args->duty) { |
| if (features & LIRC_CAN_SET_SEND_DUTY_CYCLE) { |
| rc = ioctl(fd, LIRC_SET_SEND_DUTY_CYCLE, &args->duty); |
| if (rc) |
| fprintf(stderr, _("warning: %s: failed to set duty cycle: %m\n"), dev); |
| } else |
| fprintf(stderr, _("warning: %s: does not support setting send duty cycle\n"), dev); |
| } |
| |
| if (args->emitters) { |
| if (features & LIRC_CAN_SET_TRANSMITTER_MASK) { |
| rc = ioctl(fd, LIRC_SET_TRANSMITTER_MASK, &args->emitters); |
| if (rc > 0) |
| fprintf(stderr, _("warning: %s: failed to set send transmitters: only %d available\n"), dev, rc); |
| else if (rc < 0) |
| fprintf(stderr, _("warning: %s: failed to set send transmitters: %m\n"), dev); |
| } else |
| fprintf(stderr, _("warning: %s: does not support setting send transmitters\n"), dev); |
| } |
| |
| |
| return 0; |
| } |
| |
| static void lirc_features(struct arguments *args, int fd, unsigned features) |
| { |
| const char *dev = args->device; |
| unsigned resolution = 0, mode = LIRC_MODE_SCANCODE; |
| int rc; |
| |
| if (features & LIRC_CAN_GET_REC_RESOLUTION) { |
| rc = ioctl(fd, LIRC_GET_REC_RESOLUTION, &resolution); |
| if (rc == 0 && resolution == 0) |
| fprintf(stderr, _("warning: %s: device returned resolution of 0\n"), dev); |
| else if (rc) |
| fprintf(stderr, _("warning: %s: unexpected error while retrieving resolution: %m\n"), dev); |
| } |
| |
| printf(_("Receive features %s:\n"), dev); |
| if (features & LIRC_CAN_REC_SCANCODE) { |
| printf(_(" - Device can receive scancodes\n")); |
| } else if (features & LIRC_CAN_REC_MODE2) { |
| printf(_(" - Device can receive raw IR\n")); |
| if (ioctl(fd, LIRC_SET_REC_MODE, &mode) == 0) |
| printf(_(" - Can report decoded scancodes and protocol\n")); |
| if (resolution) |
| printf(_(" - Resolution %u microseconds\n"), resolution); |
| if (features & LIRC_CAN_SET_REC_CARRIER) |
| printf(_(" - Set receive carrier\n")); |
| if (features & LIRC_CAN_USE_WIDEBAND_RECEIVER) |
| printf(_(" - Use wideband receiver\n")); |
| if (features & LIRC_CAN_MEASURE_CARRIER) |
| printf(_(" - Can measure carrier\n")); |
| |
| // This ioctl is only supported from kernel 4.18 onwards |
| unsigned timeout; |
| int rc = ioctl(fd, LIRC_GET_REC_TIMEOUT, &timeout); |
| if (rc == 0) { |
| if (timeout == 0) |
| printf(_(" - Receiving timeout not set\n")); |
| else |
| printf(_(" - Receiving timeout %u microseconds\n"), timeout); |
| } |
| |
| if (features & LIRC_CAN_SET_REC_TIMEOUT) { |
| unsigned min_timeout, max_timeout; |
| |
| rc = ioctl(fd, LIRC_GET_MIN_TIMEOUT, &min_timeout); |
| if (rc) { |
| fprintf(stderr, _("warning: %s: device supports setting receiving timeout but LIRC_GET_MIN_TIMEOUT returns: %m\n"), dev); |
| min_timeout = 0; |
| } else if (min_timeout == 0) |
| fprintf(stderr, _("warning: %s: device supports setting receiving timeout but min timeout is 0\n"), dev); |
| rc = ioctl(fd, LIRC_GET_MAX_TIMEOUT, &max_timeout); |
| if (rc) { |
| fprintf(stderr, _("warning: %s: device supports setting receiving timeout but LIRC_GET_MAX_TIMEOUT returns: %m\n"), dev); |
| max_timeout = 0; |
| } else if (max_timeout == 0) { |
| fprintf(stderr, _("warning: %s: device supports setting receiving timeout but max timeout is 0\n"), dev); |
| } |
| |
| if (min_timeout || max_timeout) |
| printf(_(" - Can set receiving timeout min %u microseconds, max %u microseconds\n"), min_timeout, max_timeout); |
| } |
| } else if (features & LIRC_CAN_REC_LIRCCODE) { |
| printf(_(" - Device can receive using device dependent LIRCCODE mode (not supported)\n")); |
| } else { |
| printf(_(" - Device cannot receive\n")); |
| } |
| |
| printf(_("Send features %s:\n"), dev); |
| if (features & LIRC_CAN_SEND_PULSE) { |
| printf(_(" - Device can send raw IR\n")); |
| if (ioctl(fd, LIRC_SET_SEND_MODE, &mode) == 0) |
| printf(_(" - IR scancode encoder\n")); |
| if (features & LIRC_CAN_SET_SEND_CARRIER) |
| printf(_(" - Set carrier\n")); |
| if (features & LIRC_CAN_SET_SEND_DUTY_CYCLE) |
| printf(_(" - Set duty cycle\n")); |
| if (features & LIRC_CAN_SET_TRANSMITTER_MASK) { |
| unsigned mask = ~0; |
| rc = ioctl(fd, LIRC_SET_TRANSMITTER_MASK, &mask); |
| if (rc == 0) |
| fprintf(stderr, _("warning: %s: device supports setting transmitter mask but returns 0 as number of transmitters\n"), dev); |
| else if (rc < 0) |
| fprintf(stderr, _("warning: %s: device supports setting transmitter mask but returns: %m\n"), dev); |
| else |
| printf(_(" - Set transmitter (%d available)\n"), rc); |
| } |
| } else if (features & LIRC_CAN_SEND_LIRCCODE) { |
| printf(_(" - Device can send using device dependent LIRCCODE mode (not supported)\n")); |
| } else { |
| printf(_(" - Device cannot send\n")); |
| } |
| } |
| |
| static int lirc_send(struct arguments *args, int fd, unsigned features, struct send *f) |
| { |
| const char *dev = args->device; |
| int rc, mode; |
| ssize_t ret; |
| |
| if (!(features & LIRC_CAN_SEND_PULSE)) { |
| fprintf(stderr, _("%s: device cannot send\n"), dev); |
| return EX_UNAVAILABLE; |
| } |
| |
| if (f->ty == SEND_SCANCODE) { |
| if (args->verbose) |
| printf("Sending to kernel encoder protocol:%s scancode:0x%x\n", |
| protocol_name(f->protocol), f->scancode); |
| |
| mode = LIRC_MODE_SCANCODE; |
| rc = ioctl(fd, LIRC_SET_SEND_MODE, &mode); |
| if (rc == 0) { |
| struct lirc_scancode sc = { |
| .scancode = f->scancode, |
| .rc_proto = f->protocol, |
| .flags = 0 |
| }; |
| ret = TEMP_FAILURE_RETRY(write(fd, &sc, sizeof sc)); |
| if (ret > 0) |
| return 0; |
| } |
| } |
| |
| mode = LIRC_MODE_PULSE; |
| rc = ioctl(fd, LIRC_SET_SEND_MODE, &mode); |
| if (rc) { |
| fprintf(stderr, _("%s: cannot set send mode\n"), dev); |
| return EX_UNAVAILABLE; |
| } |
| |
| if (f->ty == SEND_SCANCODE) { |
| // encode scancode |
| enum rc_proto proto = f->protocol; |
| if (!protocol_encoder_available(proto)) { |
| fprintf(stderr, _("%s: no encoder available for `%s'\n"), |
| dev, protocol_name(proto)); |
| return EX_UNAVAILABLE; |
| } |
| f->len = protocol_encode(f->protocol, f->scancode, f->buf); |
| f->carrier = protocol_carrier(proto); |
| } |
| |
| if (args->carrier != UNSET) { |
| if (f->carrier != UNSET) |
| fprintf(stderr, _("warning: carrier specified but overwritten on command line\n")); |
| lirc_set_send_carrier(fd, dev, features, args->carrier); |
| } else if (f->carrier != UNSET) |
| lirc_set_send_carrier(fd, dev, features, f->carrier); |
| |
| size_t size = f->len * sizeof(unsigned); |
| if (args->verbose) { |
| int i; |
| printf("Sending:"); |
| for (i=0; i<f->len; i++) |
| printf("%s%u", i & 1 ? " -" : " +", f->buf[i]); |
| putchar('\n'); |
| } |
| ret = TEMP_FAILURE_RETRY(write(fd, f->buf, size)); |
| if (ret < 0) { |
| fprintf(stderr, _("%s: failed to send: %m\n"), dev); |
| return EX_IOERR; |
| } |
| |
| if (size < ret) { |
| fprintf(stderr, _("warning: %s: sent %zd out %zd edges\n"), |
| dev, |
| ret / sizeof(unsigned), |
| size / sizeof(unsigned)); |
| return EX_IOERR; |
| } |
| if (args->verbose) |
| printf("Successfully sent\n"); |
| |
| return 0; |
| } |
| |
| int lirc_receive(struct arguments *args, int fd, unsigned features) |
| { |
| char *dev = args->device; |
| FILE *out = stdout; |
| int rc = EX_IOERR; |
| int mode = LIRC_MODE_MODE2; |
| |
| if (!(features & LIRC_CAN_REC_MODE2)) { |
| fprintf(stderr, _("%s: device cannot receive raw ir\n"), dev); |
| return EX_UNAVAILABLE; |
| } |
| |
| // kernel v4.8 and v4.9 return ENOTTY |
| if (ioctl(fd, LIRC_SET_REC_MODE, &mode) && errno != ENOTTY) { |
| fprintf(stderr, _("%s: failed to set receive mode: %m\n"), dev); |
| return EX_IOERR; |
| } |
| |
| if (args->savetofile) { |
| out = fopen(args->savetofile, "w"); |
| if (!out) { |
| fprintf(stderr, _("%s: failed to open for writing: %m\n"), args->savetofile); |
| return EX_CANTCREAT; |
| } |
| } |
| unsigned buf[LIRCBUF_SIZE]; |
| |
| bool keep_reading = true; |
| bool leading_space = true; |
| unsigned carrier = 0; |
| |
| while (keep_reading) { |
| ssize_t ret = TEMP_FAILURE_RETRY(read(fd, buf, sizeof(buf))); |
| if (ret < 0) { |
| fprintf(stderr, _("%s: failed read: %m\n"), dev); |
| goto err; |
| } |
| |
| if (ret == 0 || ret % sizeof(unsigned)) { |
| fprintf(stderr, _("%s: read returned %zd bytes\n"), |
| dev, ret); |
| goto err; |
| } |
| |
| for (int i=0; i<ret / sizeof(unsigned); i++) { |
| unsigned val = buf[i] & LIRC_VALUE_MASK; |
| unsigned msg = buf[i] & LIRC_MODE2_MASK; |
| |
| // FIXME: the kernel often send us a space after |
| // the IR receiver comes out of idle mode. This |
| // is meaningless, maybe fix the kernel? |
| if (leading_space && msg == LIRC_MODE2_SPACE) |
| continue; |
| |
| leading_space = false; |
| if (args->oneshot && |
| (msg == LIRC_MODE2_TIMEOUT || |
| (msg == LIRC_MODE2_SPACE && val > 19000))) { |
| keep_reading = false; |
| break; |
| } |
| |
| if (args->mode2) { |
| switch (msg) { |
| case LIRC_MODE2_TIMEOUT: |
| fprintf(out, "timeout %u\n", val); |
| leading_space = true; |
| break; |
| case LIRC_MODE2_PULSE: |
| fprintf(out, "pulse %u\n", val); |
| break; |
| case LIRC_MODE2_SPACE: |
| fprintf(out, "space %u\n", val); |
| break; |
| case LIRC_MODE2_FREQUENCY: |
| fprintf(out, "carrier %u\n", val); |
| break; |
| case LIRC_MODE2_OVERFLOW: |
| fprintf(out, "overflow\n"); |
| leading_space = true; |
| break; |
| } |
| } else { |
| switch (msg) { |
| case LIRC_MODE2_TIMEOUT: |
| if (carrier) |
| fprintf(out, "-%u # carrier %uHz\n", val, carrier); |
| else |
| fprintf(out, "-%u\n", val); |
| leading_space = true; |
| carrier = 0; |
| break; |
| case LIRC_MODE2_PULSE: |
| fprintf(out, "+%u ", val); |
| break; |
| case LIRC_MODE2_SPACE: |
| fprintf(out, "-%u ", val); |
| break; |
| case LIRC_MODE2_FREQUENCY: |
| carrier = val; |
| break; |
| case LIRC_MODE2_OVERFLOW: |
| if (carrier) |
| fprintf(out, "# carrier %uHz, overflow\n", carrier); |
| else |
| fprintf(out, "# overflow\n"); |
| leading_space = true; |
| carrier = 0; |
| break; |
| } |
| } |
| |
| fflush(out); |
| } |
| } |
| |
| rc = 0; |
| err: |
| if (args->savetofile) |
| fclose(out); |
| |
| return rc; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| struct arguments args = { |
| .gap = IR_DEFAULT_TIMEOUT, |
| .carrier = UNSET, |
| .timeout = UNSET, |
| }; |
| |
| #ifdef ENABLE_NLS |
| setlocale (LC_ALL, ""); |
| bindtextdomain (PACKAGE, LOCALEDIR); |
| textdomain (PACKAGE); |
| #endif |
| |
| argp_parse(&argp, argc, argv, 0, 0, &args); |
| |
| if (args.device == NULL) |
| args.device = "/dev/lirc0"; |
| |
| int rc, fd; |
| unsigned features; |
| |
| fd = open_lirc(args.device, &features); |
| if (fd < 0) |
| exit(EX_NOINPUT); |
| |
| rc = lirc_options(&args, fd, features); |
| if (rc) |
| exit(EX_IOERR); |
| |
| struct send *s = args.send; |
| while (s) { |
| struct send *next = s->next; |
| if (s->ty == SEND_GAP) { |
| usleep(s->gap); |
| } else { |
| if (s->ty == SEND_KEYCODE) { |
| struct send *k; |
| |
| if (!args.keymap) { |
| fprintf(stderr, _("error: no keymap specified\n")); |
| exit(EX_DATAERR); |
| } |
| |
| k = convert_keycode(args.keymap, s->keycode); |
| if (!k) |
| exit(EX_DATAERR); |
| |
| free(s); |
| s = k; |
| } |
| |
| rc = lirc_send(&args, fd, features, s); |
| if (rc) { |
| close(fd); |
| exit(rc); |
| } |
| } |
| |
| free(s); |
| s = next; |
| } |
| |
| if (args.receive) { |
| rc = lirc_receive(&args, fd, features); |
| if (rc) { |
| close(fd); |
| exit(rc); |
| } |
| } |
| |
| if (args.features) |
| lirc_features(&args, fd, features); |
| |
| free_keymap(args.keymap); |
| close(fd); |
| |
| return 0; |
| } |