From fee703567f1b5fce7d7ff2118024b39577bf8711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Thu, 9 Feb 2017 20:12:09 +0100 Subject: Add input-switch Moving common stuff for DDC/CI communicating programs to ddc-ci.c. --- CMakeLists.txt | 5 +- README.adoc | 1 + brightness.c | 218 ++------------------------------------------------ ddc-ci.c | 246 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ input-switch.c | 153 +++++++++++++++++++++++++++++++++++ 5 files changed, 411 insertions(+), 212 deletions(-) create mode 100644 ddc-ci.c create mode 100644 input-switch.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 31cbfa1..15e117e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,9 @@ add_threads (wmstatus) add_executable (brightness brightness.c) target_link_libraries (brightness ${project_libraries}) +add_executable (input-switch input-switch.c) +target_link_libraries (input-switch ${project_libraries}) + add_executable (fancontrol-ng fancontrol-ng.c) target_link_libraries (fancontrol-ng ${project_libraries}) @@ -76,7 +79,7 @@ if (WITH_GDM) install (TARGETS gdm-switch-user DESTINATION ${CMAKE_INSTALL_BINDIR}) endif (WITH_GDM) -install (TARGETS wmstatus brightness fancontrol-ng siprandom +install (TARGETS wmstatus brightness input-switch fancontrol-ng siprandom DESTINATION ${CMAKE_INSTALL_BINDIR}) install (PROGRAMS shellify DESTINATION ${CMAKE_INSTALL_BINDIR}) install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR}) diff --git a/README.adoc b/README.adoc index 0d3ab97..d1bdf0c 100644 --- a/README.adoc +++ b/README.adoc @@ -9,6 +9,7 @@ to other people as well: includes PulseAudio volume management and hand-written NUT and MPD clients, all in the name of liberation from GPL-licensed software of course. - 'brightness' allows me to change the brightness of w/e display device I have. + - 'input-switch' likewise switches the input source of external displays. - 'fancontrol-ng' is a clone of fancontrol that can handle errors on resume from suspend instead of setting fans to maximum speed and quitting; in general it doesn't handle everything the original does diff --git a/brightness.c b/brightness.c index 3b52fc5..4bf63a7 100644 --- a/brightness.c +++ b/brightness.c @@ -17,51 +17,20 @@ * */ -// Sources: ddcciv1r1.pdf, i2c-dev.c in Linux, ddccontrol source code, -// http://www.boichat.ch/nicolas/ddcci/specs.html was also helpful - // This makes openat() available even though I set _POSIX_C_SOURCE and // _XOPEN_SOURCE to a version of POSIX older than 2008 #define _GNU_SOURCE -// Undo some dwm Makefile damage and import my everything-library #include "config.h" #undef PROGRAM_NAME #define PROGRAM_NAME "brightness" #include "liberty/liberty.c" -#include +#include "ddc-ci.c" #include -#include -#ifndef I2C_FUNC_I2C -// Fuck you, openSUSE, for fucking up the previous file, see e.g. -// https://github.com/solettaproject/soletta/commit/427f47f -#include -#endif - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static void -log_message_custom (void *user_data, const char *quote, const char *fmt, - va_list ap) -{ - (void) user_data; - FILE *stream = stdout; - - fprintf (stream, PROGRAM_NAME ": "); - fputs (quote, stream); - vfprintf (stream, fmt, ap); - fputs ("\n", stream); -} - -static void -wait_ms (long ms) -{ - struct timespec ts = { 0, ms * 1000 * 1000 }; - nanosleep (&ts, NULL); -} - static bool xstrtol (const char *s, long *out) { @@ -73,189 +42,16 @@ xstrtol (const char *s, long *out) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#define DDC_LENGTH_XOR 0x80 - -enum -{ - DDC_ADDRESS_HOST = 0x50, ///< Bus master's base address - DDC_ADDRESS_DISPLAY = 0x6E ///< The display's base address -}; - -enum { I2C_WRITE, I2C_READ }; - -enum -{ - DDC_GET_VCP_FEATURE = 0x01, ///< Request info about a feature - DDC_GET_VCP_FEATURE_REPLY = 0x02, ///< Feature info response - DDC_SET_VCP_FEATURE = 0x03 ///< Set or activate a feature -}; - -enum -{ - VCP_BRIGHTNESS = 0x10, ///< Standard VCP opcode for brightness - VCP_CONTRAST = 0x12 ///< Standard VCP opcode for contrast -}; - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static bool -check_edid (int fd, struct error **e) -{ - uint8_t edid_req[] = { 0x00 }; - uint8_t buf[8] = ""; - struct i2c_msg bufs[] = - { - { .addr = 0x50, .flags = 0, - .len = 1, .buf = edid_req }, - { .addr = 0x50, .flags = I2C_M_RD, - .len = sizeof buf, .buf = buf }, - }; - - struct i2c_rdwr_ioctl_data data; - data.msgs = bufs; - data.nmsgs = 2; - - if (ioctl (fd, I2C_RDWR, &data) < 0) - return error_set (e, "%s: %s", "ioctl", strerror (errno)); - if (memcmp ("\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00", buf, sizeof buf)) - return error_set (e, "invalid EDID"); - return true; -} - -static bool -is_a_display (int fd, struct error **e) -{ - struct stat st; - if (fstat (fd, &st) < 0) - return error_set (e, "%s: %s", "fstat", strerror (errno)); - - unsigned long funcs; - if (!(st.st_mode & S_IFCHR) - || ioctl (fd, I2C_FUNCS, &funcs) < 0 - || !(funcs & I2C_FUNC_I2C)) - return error_set (e, "not an I2C device"); - - return check_edid (fd, e); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static bool -ddc_send (int fd, unsigned command, void *args, size_t args_len, - struct error **e) -{ - struct str buf; - str_init (&buf); - str_pack_u8 (&buf, DDC_ADDRESS_HOST | I2C_READ); - str_pack_u8 (&buf, DDC_LENGTH_XOR | (args_len + 1)); - str_pack_u8 (&buf, command); - str_append_data (&buf, args, args_len); - - unsigned xor = DDC_ADDRESS_DISPLAY; - for (size_t i = 0; i < buf.len; i++) - xor ^= buf.str[i]; - str_pack_u8 (&buf, xor); - - struct i2c_msg msg = - { - // The driver unshifts it back - .addr = DDC_ADDRESS_DISPLAY >> 1, .flags = 0, - .len = buf.len, .buf = (uint8_t *) buf.str, - }; - - struct i2c_rdwr_ioctl_data data; - data.msgs = &msg; - data.nmsgs = 1; - - bool failed = ioctl (fd, I2C_RDWR, &data) < 0; - str_free (&buf); - if (failed) - return error_set (e, "%s: %s", "ioctl", strerror (errno)); - return true; -} - -static bool -ddc_read (int fd, unsigned *command, void *out_buf, size_t *n_read, - struct error **e) -{ - uint8_t buf[128] = ""; - struct i2c_msg msg = - { - // The driver unshifts it back - .addr = DDC_ADDRESS_DISPLAY >> 1, .flags = I2C_M_RD, - .len = sizeof buf, .buf = buf, - }; - - struct i2c_rdwr_ioctl_data data; - data.msgs = &msg; - data.nmsgs = 1; - - if (ioctl (fd, I2C_RDWR, &data) < 0) - return error_set (e, "%s: %s", "ioctl", strerror (errno)); - - struct msg_unpacker unpacker; - msg_unpacker_init (&unpacker, buf, sizeof buf); - - uint8_t sender, length, cmd; - (void) msg_unpacker_u8 (&unpacker, &sender); - (void) msg_unpacker_u8 (&unpacker, &length); - (void) msg_unpacker_u8 (&unpacker, &cmd); - - if (sender != (DDC_ADDRESS_DISPLAY | I2C_WRITE) || !(length & 0x80)) - return error_set (e, "invalid response"); - if (!(length ^= 0x80)) - return error_set (e, "NULL response"); - - // TODO: also check the checksum - - *command = cmd; - memcpy (out_buf, unpacker.data + unpacker.offset, (*n_read = length - 1)); - return true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static bool set_brightness (int fd, long diff, struct error **e) { - uint8_t get_req[] = { VCP_BRIGHTNESS }; - if (!ddc_send (fd, DDC_GET_VCP_FEATURE, get_req, sizeof get_req, e)) + struct vcp_feature_readout readout; + if (!vcp_get_feature (fd, VCP_BRIGHTNESS, &readout, e)) return false; - wait_ms (40); - - unsigned command = 0; - uint8_t buf[128] = ""; - size_t len = 0; - if (!ddc_read (fd, &command, buf, &len, e)) - return false; - - if (command != DDC_GET_VCP_FEATURE_REPLY || len != 7) - return error_set (e, "invalid response"); - - struct msg_unpacker unpacker; - msg_unpacker_init (&unpacker, buf, len); - - uint8_t result; msg_unpacker_u8 (&unpacker, &result); - uint8_t vcp_opcode; msg_unpacker_u8 (&unpacker, &vcp_opcode); - uint8_t type; msg_unpacker_u8 (&unpacker, &type); - int16_t max; msg_unpacker_i16 (&unpacker, &max); - int16_t cur; msg_unpacker_i16 (&unpacker, &cur); - - if (result == 0x01) - return error_set (e, "error reported by monitor"); - - if (result != 0x00 - || vcp_opcode != VCP_BRIGHTNESS) - return error_set (e, "invalid response"); - - // These are unsigned but usually just one byte long - if (max < 0 || cur < 0) - return error_set (e, "capability range overflow"); - - int16_t req = (cur * 100 + diff * max + 50) / 100; - if (req > max) req = max; - if (req < 0) req = 0; + int16_t req = (readout.cur * 100 + diff * readout.max + 50) / 100; + req = MIN (req, readout.max); + req = MAX (req, 0); uint8_t set_req[] = { VCP_BRIGHTNESS, req >> 8, req }; if (!ddc_send (fd, DDC_SET_VCP_FEATURE, set_req, sizeof set_req, e)) @@ -263,7 +59,7 @@ set_brightness (int fd, long diff, struct error **e) wait_ms (50); - printf ("brightness set to %.2f%%\n", 100. * req / max); + printf ("brightness set to %.2f%%\n", 100. * req / readout.max); return true; } diff --git a/ddc-ci.c b/ddc-ci.c new file mode 100644 index 0000000..0edce8a --- /dev/null +++ b/ddc-ci.c @@ -0,0 +1,246 @@ +/* + * ddc-ci.c: DDC-CI utilities, Linux-only + * + * Copyright (c) 2015 - 2017, Přemysl Janouch + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +// Sources: ddcciv1r1.pdf, i2c-dev.c in Linux, ddccontrol source code, +// http://www.boichat.ch/nicolas/ddcci/specs.html was also helpful + +#include + +#include +#ifndef I2C_FUNC_I2C +// Fuck you, openSUSE, for fucking up the previous file, see e.g. +// https://github.com/solettaproject/soletta/commit/427f47f +#include +#endif + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static void +log_message_custom (void *user_data, const char *quote, const char *fmt, + va_list ap) +{ + (void) user_data; + FILE *stream = stdout; + + fprintf (stream, PROGRAM_NAME ": "); + fputs (quote, stream); + vfprintf (stream, fmt, ap); + fputs ("\n", stream); +} + +static void +wait_ms (long ms) +{ + struct timespec ts = { 0, ms * 1000 * 1000 }; + nanosleep (&ts, NULL); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +#define DDC_LENGTH_XOR 0x80 + +enum +{ + DDC_ADDRESS_HOST = 0x50, ///< Bus master's base address + DDC_ADDRESS_DISPLAY = 0x6E ///< The display's base address +}; + +enum { I2C_WRITE, I2C_READ }; + +enum +{ + DDC_GET_VCP_FEATURE = 0x01, ///< Request info about a feature + DDC_GET_VCP_FEATURE_REPLY = 0x02, ///< Feature info response + DDC_SET_VCP_FEATURE = 0x03 ///< Set or activate a feature +}; + +enum +{ + VCP_BRIGHTNESS = 0x10, ///< Standard VCP opcode for brightness + VCP_CONTRAST = 0x12, ///< Standard VCP opcode for contrast + VCP_INPUT_SOURCE = 0x60 ///< Standard VCP opcode for input +}; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static bool +check_edid (int fd, struct error **e) +{ + uint8_t edid_req[] = { 0x00 }; + uint8_t buf[8] = ""; + struct i2c_msg bufs[] = + { + { .addr = 0x50, .flags = 0, + .len = 1, .buf = edid_req }, + { .addr = 0x50, .flags = I2C_M_RD, + .len = sizeof buf, .buf = buf }, + }; + + struct i2c_rdwr_ioctl_data data; + data.msgs = bufs; + data.nmsgs = 2; + + if (ioctl (fd, I2C_RDWR, &data) < 0) + return error_set (e, "%s: %s", "ioctl", strerror (errno)); + if (memcmp ("\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00", buf, sizeof buf)) + return error_set (e, "invalid EDID"); + return true; +} + +static bool +is_a_display (int fd, struct error **e) +{ + struct stat st; + if (fstat (fd, &st) < 0) + return error_set (e, "%s: %s", "fstat", strerror (errno)); + + unsigned long funcs; + if (!(st.st_mode & S_IFCHR) + || ioctl (fd, I2C_FUNCS, &funcs) < 0 + || !(funcs & I2C_FUNC_I2C)) + return error_set (e, "not an I2C device"); + + return check_edid (fd, e); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static bool +ddc_send (int fd, unsigned command, void *args, size_t args_len, + struct error **e) +{ + struct str buf; + str_init (&buf); + str_pack_u8 (&buf, DDC_ADDRESS_HOST | I2C_READ); + str_pack_u8 (&buf, DDC_LENGTH_XOR | (args_len + 1)); + str_pack_u8 (&buf, command); + str_append_data (&buf, args, args_len); + + unsigned xor = DDC_ADDRESS_DISPLAY; + for (size_t i = 0; i < buf.len; i++) + xor ^= buf.str[i]; + str_pack_u8 (&buf, xor); + + struct i2c_msg msg = + { + // The driver unshifts it back + .addr = DDC_ADDRESS_DISPLAY >> 1, .flags = 0, + .len = buf.len, .buf = (uint8_t *) buf.str, + }; + + struct i2c_rdwr_ioctl_data data; + data.msgs = &msg; + data.nmsgs = 1; + + bool failed = ioctl (fd, I2C_RDWR, &data) < 0; + str_free (&buf); + if (failed) + return error_set (e, "%s: %s", "ioctl", strerror (errno)); + return true; +} + +static bool +ddc_read (int fd, unsigned *command, void *out_buf, size_t *n_read, + struct error **e) +{ + uint8_t buf[128] = ""; + struct i2c_msg msg = + { + // The driver unshifts it back + .addr = DDC_ADDRESS_DISPLAY >> 1, .flags = I2C_M_RD, + .len = sizeof buf, .buf = buf, + }; + + struct i2c_rdwr_ioctl_data data; + data.msgs = &msg; + data.nmsgs = 1; + + if (ioctl (fd, I2C_RDWR, &data) < 0) + return error_set (e, "%s: %s", "ioctl", strerror (errno)); + + struct msg_unpacker unpacker; + msg_unpacker_init (&unpacker, buf, sizeof buf); + + uint8_t sender, length, cmd; + (void) msg_unpacker_u8 (&unpacker, &sender); + (void) msg_unpacker_u8 (&unpacker, &length); + (void) msg_unpacker_u8 (&unpacker, &cmd); + + if (sender != (DDC_ADDRESS_DISPLAY | I2C_WRITE) || !(length & 0x80)) + return error_set (e, "invalid response"); + if (!(length ^= 0x80)) + return error_set (e, "NULL response"); + + // TODO: also check the checksum + + *command = cmd; + memcpy (out_buf, unpacker.data + unpacker.offset, (*n_read = length - 1)); + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +struct vcp_feature_readout +{ + uint8_t type; ///< Type of the value + int16_t max; ///< Maximum value + int16_t cur; ///< Current value +}; + +static bool +vcp_get_feature (int fd, uint8_t feature, struct vcp_feature_readout *out, + struct error **e) +{ + uint8_t get_req[] = { feature }; + if (!ddc_send (fd, DDC_GET_VCP_FEATURE, get_req, sizeof get_req, e)) + return false; + + wait_ms (40); + + unsigned command = 0; + uint8_t buf[128] = ""; + size_t len = 0; + if (!ddc_read (fd, &command, buf, &len, e)) + return false; + + if (command != DDC_GET_VCP_FEATURE_REPLY || len != 7) + return error_set (e, "invalid response"); + + struct msg_unpacker unpacker; + msg_unpacker_init (&unpacker, buf, len); + + uint8_t result; msg_unpacker_u8 (&unpacker, &result); + uint8_t vcp_opcode; msg_unpacker_u8 (&unpacker, &vcp_opcode); + msg_unpacker_u8 (&unpacker, &out->type); + msg_unpacker_i16 (&unpacker, &out->max); + msg_unpacker_i16 (&unpacker, &out->cur); + + if (result == 0x01) + return error_set (e, "error reported by monitor"); + + if (result != 0x00 + || vcp_opcode != feature) + return error_set (e, "invalid response"); + + // These are unsigned but usually just one byte long + if (out->max < 0 || out->cur < 0) + return error_set (e, "capability range overflow"); + return true; +} + diff --git a/input-switch.c b/input-switch.c new file mode 100644 index 0000000..352c71c --- /dev/null +++ b/input-switch.c @@ -0,0 +1,153 @@ +/* + * input-switch.c: switches display input via DDC/CI + * + * Copyright (c) 2017, Přemysl Janouch + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +// This makes openat() available even though I set _POSIX_C_SOURCE and +// _XOPEN_SOURCE to a version of POSIX older than 2008 +#define _GNU_SOURCE + +#include "config.h" +#undef PROGRAM_NAME +#define PROGRAM_NAME "input-switch" +#include "liberty/liberty.c" + +#include "ddc-ci.c" +#include + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static bool +set_input_source (int fd, int input, struct error **e) +{ + struct vcp_feature_readout readout; + if (!vcp_get_feature (fd, VCP_INPUT_SOURCE, &readout, e)) + return false; + if (input < 0 || input > readout.max) + return error_set (e, "input index out of range"); + + uint8_t set_req[] = { VCP_INPUT_SOURCE, input >> 8, input }; + if (!ddc_send (fd, DDC_SET_VCP_FEATURE, set_req, sizeof set_req, e)) + return false; + + wait_ms (50); + + printf ("input set from %d to %d of %d\n", readout.cur, input, readout.max); + return true; +} + +static void +i2c (int input) +{ + DIR *dev = opendir ("/dev"); + if (!dev) + { + print_error ("cannot access %s: %s: %s", + "/dev", "opendir", strerror (errno)); + return; + } + + struct dirent *entry; + while ((entry = readdir (dev))) + { + if (strncmp (entry->d_name, "i2c-", 4)) + continue; + + printf ("Trying %s... ", entry->d_name); + int fd = openat (dirfd (dev), entry->d_name, O_RDONLY); + if (fd < 0) + { + print_error ("%s: %s", "openat", strerror (errno)); + continue; + } + + struct error *e = NULL; + if (!is_a_display (fd, &e) + || !set_input_source (fd, input, &e)) + { + printf ("%s\n", e->message); + error_free (e); + } + + close (fd); + } + closedir (dev); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +// This list is from the MCCS 2.2a specification +struct +{ + int code; ///< Input code + const char *name; ///< Input name + int index; ///< Input index +} +g_inputs[] = +{ + { 0x01, "vga", 1, }, // Analog video (R/G/B) 1 + { 0x02, "vga", 2, }, // Analog video (R/G/B) 2 + { 0x03, "dvi", 1, }, // Digital video (TMDS) 1 DVI 1 + { 0x04, "dvi", 2, }, // Digital video (TMDS) 2 DVI 2 + { 0x05, "composite", 1, }, // Composite video 1 + { 0x06, "composite", 2, }, // Composite video 2 + { 0x07, "s-video", 1, }, // S-video 1 + { 0x08, "s-video", 2, }, // S-video 2 + { 0x09, "tuner", 1, }, // Tuner 1 + { 0x0A, "tuner", 2, }, // Tuner 2 + { 0x0B, "tuner", 3, }, // Tuner 3 + { 0x0C, "component", 1, }, // Component video (YPbPr/YCbCr) 1 + { 0x0D, "component", 2, }, // Component video (YPbPr/YCbCr) 2 + { 0x0E, "component", 3, }, // Component video (YPbPr/YCbCr) 3 + { 0x0F, "dp", 1, }, // DisplayPort 1 + { 0x10, "dp", 2, }, // DisplayPort 2 + { 0x11, "hdmi", 1, }, // Digital Video (TMDS) 3 HDMI 1 + { 0x12, "hdmi", 2, }, // Digital Video (TMDS) 4 HDMI 2 +}; + +int +main (int argc, char *argv[]) +{ + g_log_message_real = log_message_custom; + + if (argc <= 1) + { + printf ("Usage: %s []\n", argv[0]); + exit (EXIT_FAILURE); + } + + unsigned long input_source = 0; + if (xstrtoul (&input_source, argv[1], 10)) + { + i2c (input_source); + exit (EXIT_SUCCESS); + } + + unsigned long index = 1; + if (argc > 2 && !xstrtoul (&index, argv[2], 10)) + exit_fatal ("given index is not a number: %s", argv[2]); + for (size_t i = 0; i < N_ELEMENTS (g_inputs); i++) + if (!strcasecmp_ascii (g_inputs[i].name, argv[1]) + && g_inputs[i].index == (int) index) + input_source = g_inputs[i].code; + if (!input_source) + exit_fatal ("unknown input_source: %s %lu", argv[1], index); + + i2c (input_source); + return 0; +} + -- cgit v1.2.3