From df939d4fe83003d44bc682ee52675484e00ce4c0 Mon Sep 17 00:00:00 2001 From: LordMZTE Date: Fri, 19 Sep 2025 18:13:18 +0200 Subject: [PATCH] osdep/linux-als: support ambient-light on Linux This implements the ambient-light property on Linux using the sysfs interface. --- .pre-commit-config.yaml | 2 +- DOCS/man/input.rst | 2 +- meson.build | 6 ++ osdep/als-linux.c | 189 ++++++++++++++++++++++++++++++++++++++++ osdep/als-voctrl.c | 43 +++++++++ osdep/als.h | 49 +++++++++++ player/command.c | 19 ++-- player/core.h | 3 + 8 files changed, 306 insertions(+), 7 deletions(-) create mode 100644 osdep/als-linux.c create mode 100644 osdep/als-voctrl.c create mode 100644 osdep/als.h diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 28031e2c94..5276e8c1b9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,5 +19,5 @@ repos: rev: v2.3.0 hooks: - id: codespell - args: ["--ignore-words-list", "datas,DNE,enew,HDA,numer,ontop,optionA,Paeth,pathc,inout"] + args: ["--ignore-words-list", "ALS,als,datas,DNE,enew,HDA,numer,ontop,optionA,Paeth,pathc,inout"] exclude: ^misc/language.c$ diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index 288df2e8ef..186c07b62c 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -2967,7 +2967,7 @@ Property list Whether the window has focus. Might not be supported by all VOs. ``ambient-light`` - Ambient lighting condition in lux. (macOS only) + Ambient lighting condition in lux. Only observable on macOS (macOS and Linux only) ``display-names`` Names of the displays that the mpv window covers. On X11, these diff --git a/meson.build b/meson.build index 8d43b4c84b..c492cccc25 100644 --- a/meson.build +++ b/meson.build @@ -443,6 +443,12 @@ if posix and not darwin timer_source = files('osdep/timer-linux.c') endif +if host_machine.system() == 'linux' + sources += files('osdep/als-linux.c') +else + sources += files('osdep/als-voctrl.c') +endif + features += {'ppoll': cc.has_function('ppoll', args: '-D_GNU_SOURCE', prefix: '#include ')} features += {'memrchr': cc.has_function('memrchr', args: '-D_GNU_SOURCE', diff --git a/osdep/als-linux.c b/osdep/als-linux.c new file mode 100644 index 0000000000..29eab5c95d --- /dev/null +++ b/osdep/als-linux.c @@ -0,0 +1,189 @@ +/* + * Linux ambient light sensor utilities + * + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see . + */ + +#include +#include +#include +#include +#include + +#include "common/common.h" +#include "common/global.h" +#include "common/msg.h" +#include "misc/bstr.h" +#include "misc/mp_assert.h" +#include "misc/path_utils.h" +#include "osdep/als.h" +#include "osdep/io.h" +#include "player/core.h" +#include "ta/ta_talloc.h" + +// For the sysfs API documentation, refer to +// Documentation/ABI/testing/sysfs-bus-iio +// In Kernel tree. +#define SYSFS_BASEDIR "/sys/bus/iio/devices" +#define INP_RAW "in_illuminance_raw" +#define INP_OFF "in_illuminance_offset" +#define INP_SCL "in_illuminance_scale" + +struct mp_als { + DIR *device_dir; + struct mp_log *log; + + // Flag we set to true if we've looked for IIO devices and found none. This + // way, we know that we shouldn't look again next time. + bool found_no_devices; +}; + +static void mp_als_destroy(void *p) +{ + struct mp_als *state = p; + + if (state->device_dir) { + closedir(state->device_dir); + } +} + +struct mp_als *mp_als_create(void *parent, MPContext *mpctx) +{ + struct mp_als *state = + talloc_zero(parent, struct mp_als); + + state->log = mp_log_new(state, mpctx->global->log, "linux-als"); + + talloc_set_destructor(state, mp_als_destroy); + + return state; +} + +// Assumes that state->device_dir is NULL +static int acquire_sensor(struct mp_als *state) +{ + mp_assert(!state->device_dir); + DIR *devices_dir = opendir(SYSFS_BASEDIR); + if (!devices_dir) { + MP_WARN(state, + "No IIO device directory in sysfs: %s\n", + mp_strerror(errno)); + return -1; + } + + void *tmp = talloc_new(NULL); + + struct dirent *ent; + while ((ent = readdir(devices_dir))) { + // The directory also contains files named `trigger*` which we don't + // care about; only include ones for devices. + if (!bstr_startswith0(bstr0(ent->d_name), "iio:device")) + continue; + + // Check if in_illuminance_raw exists so we know we have an ambient + // light sensor. + char *inp_raw_path = mp_path_join(tmp, ent->d_name, INP_RAW); + + if (faccessat(dirfd(devices_dir), inp_raw_path, R_OK, 0) != 0) { + // Couldn't stat, continue + continue; + } + + // We found an ALS + + char *dev_path = mp_path_join(tmp, SYSFS_BASEDIR, ent->d_name); + + state->device_dir = opendir(dev_path); + if (state->device_dir) { + MP_VERBOSE(state, "Chose ALS device %s\n", dev_path); + break; + } + + MP_WARN(state, "Failed to open ambient light sensor %s: %s\n", + ent->d_name, + mp_strerror(errno)); + } + + talloc_free(tmp); + closedir(devices_dir); + + return state->device_dir ? 0 : -1; +} + +static int scan_from_subfile(struct mp_als *state, + const char *subpath, + const char *format, + void *out) +{ + mp_assert(state->device_dir); + int fd = openat(dirfd(state->device_dir), subpath, O_RDONLY); + if (fd < 0) + return -1; + + char readbuf[256]; + ssize_t nread = read(fd, readbuf, sizeof(readbuf) - 1); + close(fd); + + if (nread < 0) + return -1; + + readbuf[nread] = 0; + return sscanf(readbuf, format, out) == 1 ? 0 : -1; +} + +static int read_als_assume_have_sensor(struct mp_als *state, + double *lux) +{ + int illu_raw; + if (scan_from_subfile(state, INP_RAW, "%d", &illu_raw) < 0) { + MP_ERR(state, + "Failed to read " INP_RAW ": %s\n", + mp_strerror(errno)); + return MP_ALS_STATUS_READFAILED; + } + + double illu_off; + if (scan_from_subfile(state, INP_OFF, "%lf", &illu_off) < 0) { + MP_ERR(state, + "Failed to read " INP_OFF ": %s\n", + mp_strerror(errno)); + return MP_ALS_STATUS_READFAILED; + } + + double illu_scl; + if (scan_from_subfile(state, INP_SCL, "%lf", &illu_scl) < 0) { + MP_ERR(state, + "Failed to read " INP_SCL ": %s\n", + mp_strerror(errno)); + return MP_ALS_STATUS_READFAILED; + } + + *lux = (illu_raw + illu_off) * illu_scl; + return MP_ALS_STATUS_OK; +} + +enum mp_als_status mp_als_get_lux(struct mp_als *state, double *lux) +{ + if (state->found_no_devices) + return MP_ALS_STATUS_NODEVICE; + + if (!state->device_dir && acquire_sensor(state) < 0) { + state->found_no_devices = true; + return MP_ALS_STATUS_NODEVICE; + } + + return read_als_assume_have_sensor(state, lux); +} diff --git a/osdep/als-voctrl.c b/osdep/als-voctrl.c new file mode 100644 index 0000000000..b4c6457d5f --- /dev/null +++ b/osdep/als-voctrl.c @@ -0,0 +1,43 @@ +/* + * Ambient light sensor implementation that delegates to VOCTRL_GET_AMBIENT_LUX + * + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see . + */ + +// TODO: refactor macOS implementation to not use voctrl and replace this file +// with one for macOS. + +#include "osdep/als.h" +#include "player/core.h" +#include "ta/ta_talloc.h" +#include "video/out/vo.h" + +struct mp_als { + MPContext *mpctx; +}; + +struct mp_als *mp_als_create(void *parent, MPContext *mpctx) { + struct mp_als *als = talloc(parent, struct mp_als); + *als = (struct mp_als){ .mpctx = mpctx }; + return als; +} + +enum mp_als_status mp_als_get_lux(struct mp_als *state, double *lux) { + struct vo *vo = state->mpctx->video_out; + if (!vo || vo_control(vo, VOCTRL_GET_AMBIENT_LUX, lux) < 1) + return MP_ALS_STATUS_NODEVICE; + return MP_ALS_STATUS_OK; +} diff --git a/osdep/als.h b/osdep/als.h new file mode 100644 index 0000000000..e1916b515e --- /dev/null +++ b/osdep/als.h @@ -0,0 +1,49 @@ +/* + * Generic interface for ambient light sensors (ALS) + * + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see . + */ + + +#pragma once + +typedef struct MPContext MPContext; + +struct mp_als; + +enum mp_als_status { + MP_ALS_STATUS_OK = 0, + + // The system has no ALS + MP_ALS_STATUS_NODEVICE, + + // The system has an ALS, but we couldn't read it. + MP_ALS_STATUS_READFAILED, +}; + +/** + * @param parent talloc parent + * @return a newly allocated mp_linux_als_state + */ +// TODO: replace the mpctx parameter with mpv_global when we no longer need the +// vo for the als-voctrl implementation. +struct mp_als *mp_als_create(void *parent, MPContext *mpctx); + +/** + * Get the ambient brightness. + * @param lux output value + */ +enum mp_als_status mp_als_get_lux(struct mp_als *state, double *lux); diff --git a/player/command.c b/player/command.c index 5039aa8a8b..a680c17071 100644 --- a/player/command.c +++ b/player/command.c @@ -41,6 +41,7 @@ #include "common/stats.h" #include "filters/f_decoder_wrapper.h" #include "command.h" +#include "osdep/als.h" #include "osdep/threads.h" #include "osdep/timer.h" #include "common/common.h" @@ -2837,12 +2838,20 @@ static int mp_property_ambient_light(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; - struct vo *vo = mpctx->video_out; - double lux; - if (!vo || vo_control(vo, VOCTRL_GET_AMBIENT_LUX, &lux) < 1) - return M_PROPERTY_UNAVAILABLE; + if (!mpctx->als_state) { + mpctx->als_state = mp_als_create(mpctx, mpctx); + } - return m_property_double_ro(action, arg, lux); + double lux; + switch (mp_als_get_lux(mpctx->als_state, &lux)) { + case MP_ALS_STATUS_OK: + return m_property_double_ro(action, arg, lux); + case MP_ALS_STATUS_NODEVICE: + return M_PROPERTY_UNAVAILABLE; + case MP_ALS_STATUS_READFAILED: + default: + return M_PROPERTY_ERROR; + } } static int mp_property_focused(void *ctx, struct m_property *prop, diff --git a/player/core.h b/player/core.h index 8d935c0181..eb7f508cab 100644 --- a/player/core.h +++ b/player/core.h @@ -31,6 +31,7 @@ #include "sub/osd.h" #include "video/mp_image.h" #include "video/out/vo.h" +#include "osdep/als.h" // definitions used internally by the core player code @@ -469,6 +470,8 @@ typedef struct MPContext { // to true. struct demuxer *open_res_demuxer; int open_res_error; + + struct mp_als *als_state; // lazily initialized on first use } MPContext; // Contains information about an asynchronous work item, how it can be aborted,