xnux.eu - site map - news

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;
}