Volume buttons
Volume buttons are connected via a resistor ladder to a low resolution ADC, which detects voltage changes and determines which key is pressed by measuring the voltage.
Reading volume keys status in Linux
In Linux volume keys are represented as a
/dev/input/event#
device. The #
number can be found by
checking sysfs via grep lradc /sys/class/input/event*/device/name
or by opening all /dev/input/event#
devices sequentially and
calling ioctl(fd, EVIOCGNAME(256), name)
to fetch the name of the
device into a char name[256]
buffer.
grep lradc /sys/class/input/event*/device/name
Outputs:
/sys/class/input/event1/device/name:1c21800.lradc
The number may change based on module probe order, and other factors, so you need an enumeration mechanism in your apps.
Key presses are recorded as regular
key press events. You can see the events with (replace #
with the
number you found above):
libinput debug-events --device=/dev/input/event#
Reading the events in C code can be done like this:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdarg.h> #include <stdbool.h> #include <errno.h> #include <fcntl.h> #include <poll.h> #include <unistd.h> #include <sys/ioctl.h> #include <linux/input.h> void syscall_error(int is_err, const char* fmt, ...) { va_list ap; if (!is_err) return; printf("ERROR: "); va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); printf(": %s\n", strerror(errno)); exit(1); } void error(const char* fmt, ...) { va_list ap; printf("ERROR: "); va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); printf("\n"); exit(1); } int open_event_dev(const char* name_needle, int flags) { char path[256]; char name[256]; int fd, ret; // find the right device and open it for (int i = 0; i < 10; i++) { snprintf(path, sizeof path, "/dev/input/event%d", i); fd = open(path, flags); if (fd < 0) continue; ret = ioctl(fd, EVIOCGNAME(256), name); if (ret < 0) continue; if (strstr(name, name_needle)) return fd; close(fd); } errno = ENOENT; return -1; } int read_key_code(int fd, int* code) { struct input_event ev; ssize_t read_bytes = read(fd, &ev, sizeof ev); if (read_bytes < 0) return -1; if (read_bytes != sizeof ev) { errno = EINVAL; return -1; } if (ev.type == EV_KEY && ev.value == 1) { if (code) *code = ev.code; return 0; } errno = EAGAIN; return -1; } int main(int ac, char* av[]) { char path[256]; int fd, ret; struct pollfd pfds[1]; int code; fd = open_event_dev("lradc", O_RDONLY | O_NONBLOCK | O_CLOEXEC); syscall_error(fd < 0, "Can't open lradc event device"); pfds[0].fd = fd; pfds[0].events = POLLIN; while (1) { ret = poll(pfds, sizeof pfds / sizeof pfds[0], -1); syscall_error(ret < 0, "Poll failed"); if (pfds[0].revents & POLLIN) { ret = read_key_code(fd, &code); syscall_error(ret < 0 && errno != EAGAIN && errno != EINTR, "Input read failed"); if (ret == 0) printf("key down %d\n", code); } } return 0; }
This is written using an event loop for a reason. There are many devices you may want to get events from when writing code for your mobile device, and this way you can easily read events from multiple devices at once (for example from the power button as in the following example):
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdarg.h> #include <stdbool.h> #include <errno.h> #include <fcntl.h> #include <poll.h> #include <unistd.h> #include <sys/ioctl.h> #include <linux/input.h> void syscall_error(int is_err, const char* fmt, ...) { va_list ap; if (!is_err) return; printf("ERROR: "); va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); printf(": %s\n", strerror(errno)); exit(1); } void error(const char* fmt, ...) { va_list ap; printf("ERROR: "); va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); printf("\n"); exit(1); } int open_event_dev(const char* name_needle, int flags) { char path[256]; char name[256]; int fd, ret; // find the right device and open it for (int i = 0; i < 10; i++) { snprintf(path, sizeof path, "/dev/input/event%d", i); fd = open(path, flags); if (fd < 0) continue; ret = ioctl(fd, EVIOCGNAME(256), name); if (ret < 0) continue; if (strstr(name, name_needle)) return fd; close(fd); } errno = ENOENT; return -1; } int read_key_code(int fd, int* code) { struct input_event ev; ssize_t read_bytes = read(fd, &ev, sizeof ev); if (read_bytes < 0) return -1; if (read_bytes != sizeof ev) { errno = EINVAL; return -1; } if (ev.type == EV_KEY && ev.value == 1) { if (code) *code = ev.code; return 0; } errno = EAGAIN; return -1; } int main(int ac, char* av[]) { char path[256]; int fd, ret; struct pollfd pfds[2]; int code; pfds[0].events = POLLIN; pfds[0].fd = open_event_dev("lradc", O_RDONLY | O_NONBLOCK | O_CLOEXEC); syscall_error(pfds[0].fd < 0, "Can't open lradc event device"); pfds[1].events = POLLIN; pfds[1].fd = open_event_dev("axp20x-pek", O_RDONLY | O_NONBLOCK | O_CLOEXEC); syscall_error(pfds[1].fd < 0, "Can't open axp20x-pek event device"); while (1) { ret = poll(pfds, sizeof pfds / sizeof pfds[0], -1); syscall_error(ret < 0, "Poll failed"); for (int i = 0; i < sizeof pfds / sizeof pfds[0]; i++) { if (pfds[i].revents & POLLIN) { ret = read_key_code(pfds[i].fd, &code); syscall_error(ret < 0 && errno != EAGAIN && errno != EINTR, "Input read failed"); if (ret == 0) printf("key down %d\n", code); } } } return 0; }