From 4fe5e4501f7d6a90b06b70897d5dc7a601689e75 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Thu, 29 Apr 2021 19:53:29 +0200 Subject: [PATCH 01/23] Initial pipewire support. --- CMakeLists.txt | 1 + TODO.md | 12 +- src/modules/dpms.c | 2 +- src/modules/sensor.c | 2 +- src/modules/sensor.h | 13 +- src/modules/sensors/camera.c | 55 +++-- src/modules/sensors/camera.h | 11 + src/modules/sensors/pipewire.c | 380 +++++++++++++++++++++++++++++++++ 8 files changed, 436 insertions(+), 40 deletions(-) create mode 100644 src/modules/sensors/camera.h create mode 100644 src/modules/sensors/pipewire.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 95f423a..48d7ac8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,7 @@ optional_dep(DPMS "x11;xext;libdrm;wayland-client" "DPMS" protocol/org_kde_kwin_ optional_dep(SCREEN "x11" "screen emitted brightness" protocol/wlr-screencopy-unstable-v1.xml src/modules/screen_plugins) optional_dep(DDC "ddcutil>=0.9.5" "external monitor backlight") optional_dep(YOCTOLIGHT "libusb-1.0" "Yoctolight usb als devices support") +optional_dep(PIPEWIRE "libpipewire-0.3" "Enable pipewire camera sensor support") # Convert ld flag list from list to space separated string. string(REPLACE ";" " " COMBINED_LDFLAGS "${COMBINED_LDFLAGS}") diff --git a/TODO.md b/TODO.md index e302f33..5c68f38 100644 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,14 @@ -## 5.X -- [ ] Keep it up to date with possible ddcutil/libmodule api changes +## 5.4 + +### Sensor - [x] ALS sensors (and yoctolight too) have a logarithmic curve for lux values (#71) -## 6.0 +### Pipewire +- [x] Support pipewire for Camera sensor? This would allow multiple application sharing camera +- [ ] Support monitor sensor api for pipewire +- [ ] Pipewire run as root needs XDG_RUNTIME_DIR env + +## 6.x ### Backlight - [ ] Create new object paths for each detected display diff --git a/src/modules/dpms.c b/src/modules/dpms.c index e58bb8b..467e0a7 100644 --- a/src/modules/dpms.c +++ b/src/modules/dpms.c @@ -133,7 +133,7 @@ static int method_setdpms(sd_bus_message *m, void *userdata, sd_bus_error *ret_e const char *display = NULL, *env = NULL; int level; - ASSERT_AUTH(); + ASSERT_AUTH(); /* Read the parameters */ int r = sd_bus_message_read(m, "ssi", &display, &env, &level); diff --git a/src/modules/sensor.c b/src/modules/sensor.c index 778057a..e2de721 100644 --- a/src/modules/sensor.c +++ b/src/modules/sensor.c @@ -87,7 +87,7 @@ static void destroy(void) { void sensor_register_new(sensor_t *sensor) { const char *sensor_names[] = { - #define X(name, val) #name, + #define X(name) #name, _SENSORS #undef X }; diff --git a/src/modules/sensor.h b/src/modules/sensor.h index 9ad2b14..0cb8066 100644 --- a/src/modules/sensor.h +++ b/src/modules/sensor.h @@ -6,7 +6,7 @@ * - a bunch of callbacks: * * -> validate_dev() called when retrieving a device from a monitor to validate retrieved device. - * -> fetch_dev() called by is_sensor_available(), to fetch a device (with requqested interface, if set) + * -> fetch_dev() called by is_sensor_available(), to fetch a device (with requested interface, if set) * -> fetch_props_dev() to retrieve a device properties, ie: its node and, if (action), an associated action * -> destroy_dev() to free dev resources * @@ -24,13 +24,14 @@ /* Sensor->name must match its enumeration stringified value */ #define _SENSORS \ - X(ALS, 0) \ - X(YOCTOLIGHT, 1) \ - X(CAMERA, 2) \ - X(CUSTOM, 3) + X(ALS) \ + X(YOCTOLIGHT) \ + X(CAMERA_PW) \ + X(CAMERA) \ + X(CUSTOM) enum sensors { -#define X(name, val) name = val, +#define X(name) name, _SENSORS #undef X SENSOR_NUM diff --git a/src/modules/sensors/camera.c b/src/modules/sensors/camera.c index 6d3e0c0..cc99600 100644 --- a/src/modules/sensors/camera.c +++ b/src/modules/sensors/camera.c @@ -1,12 +1,10 @@ #include -#include #include #include #include -#include +#include "camera.h" #include #include -#include #define CAMERA_NAME "Camera" #define CAMERA_ILL_MAX 255 @@ -15,11 +13,6 @@ #define SET_V4L2(id, val) set_v4l2_control(id, val, #id, true) #define SET_V4L2_DEF(id) set_v4l2_control_def(id, #id) -#ifndef NDEBUG - #define INFO(fmt, ...) printf(fmt, ##__VA_ARGS__); -#else - #define INFO(fmt, ...) -#endif static const __u32 supported_fmts[] = { V4L2_PIX_FMT_GREY, @@ -66,7 +59,6 @@ struct state { int device_fd; uint32_t pixelformat; struct buffer buf; - struct histogram hist[HISTOGRAM_STEPS]; char *settings; struct mjpeg_dec *decoder; map_t *stored_values; @@ -132,11 +124,9 @@ static int capture(void *dev, double *pct, const int num_captures, char *setting set_camera_settings(); create_decoder(); for (int i = 0; i < num_captures; i++) { - struct v4l2_buffer buf = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .memory = V4L2_MEMORY_MMAP}; - memset(state.hist, 0, HISTOGRAM_STEPS * sizeof(struct histogram)); - + struct v4l2_buffer buf = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .memory = V4L2_MEMORY_MMAP}; if (send_frame(&buf) == 0 && recv_frame(&buf) == 0) { - pct[ctr++] = compute_brightness(buf.bytesused) / CAMERA_ILL_MAX; + pct[ctr++] = compute_brightness(buf.bytesused); } } destroy_decoder(); @@ -434,8 +424,6 @@ static int recv_frame(struct v4l2_buffer *buf) { static double compute_brightness(unsigned int size) { double brightness = 0.0; - double min = CAMERA_ILL_MAX; - double max = 0.0; uint8_t *img_data = state.buf.start; if (state.decoder) { @@ -444,12 +432,25 @@ static double compute_brightness(unsigned int size) { return brightness; } } + + brightness = get_frame_brightness(img_data, size, (state.pixelformat == V4L2_PIX_FMT_YUYV)); + + if (img_data != state.buf.start) { + free(img_data); + } + return brightness; +} + +double get_frame_brightness(uint8_t *img_data, size_t size, bool is_yuv) { + double brightness = 0.0; + double min = CAMERA_ILL_MAX; + double max = 0.0; /* - * If greyscale -> increment by 1. + * If greyscale (rgb is converted to grey) -> increment by 1. * If YUYV -> increment by 2: we only want Y! */ - const int inc = 1 + (state.pixelformat == V4L2_PIX_FMT_YUYV); + const int inc = 1 + is_yuv; const double total = size / inc; /* Find minimum and maximum brightness */ @@ -464,16 +465,17 @@ static double compute_brightness(unsigned int size) { /* Ok, we should never get in here */ if (max == 0.0) { - goto end; + return brightness; } /* Calculate histogram */ + struct histogram hist[HISTOGRAM_STEPS] = {0}; const double step_size = (max - min) / HISTOGRAM_STEPS; for (int i = 0; i < size; i += inc) { int bucket = (img_data[i] - min) / step_size; if (bucket >= 0 && bucket < HISTOGRAM_STEPS) { - state.hist[bucket].sum += img_data[i]; - state.hist[bucket].count++; + hist[bucket].sum += img_data[i]; + hist[bucket].count++; } } @@ -482,7 +484,7 @@ static double compute_brightness(unsigned int size) { double quartiles[3] = {0.0, 0.0, 0.0}; int j = 0; for (int i = 0; i < HISTOGRAM_STEPS && j < 3; i++) { - quartiles[j] += state.hist[i].count; + quartiles[j] += hist[i].count; if (quartiles[j] >= quartile_size) { quartiles[j] = (quartile_size / quartiles[j]) + i; j++; @@ -514,15 +516,10 @@ static double compute_brightness(unsigned int size) { * and return the average brightness for it. */ for (int i = max_bucket; i >= min_bucket; i--) { - if (state.hist[i].count > step_size) { - brightness = state.hist[i].sum / state.hist[i].count; + if (hist[i].count > step_size) { + brightness = hist[i].sum / hist[i].count; break; } } - -end: - if (img_data != state.buf.start) { - free(img_data); - } - return brightness; + return (double)brightness / CAMERA_ILL_MAX; } diff --git a/src/modules/sensors/camera.h b/src/modules/sensors/camera.h new file mode 100644 index 0000000..cc0108c --- /dev/null +++ b/src/modules/sensors/camera.h @@ -0,0 +1,11 @@ +#include +#include +#include + +#ifndef NDEBUG + #define INFO(fmt, ...) printf(fmt, ##__VA_ARGS__); +#else + #define INFO(fmt, ...) +#endif + +double get_frame_brightness(uint8_t *img_data, size_t size, bool is_yuv); diff --git a/src/modules/sensors/pipewire.c b/src/modules/sensors/pipewire.c new file mode 100644 index 0000000..64ed85a --- /dev/null +++ b/src/modules/sensors/pipewire.c @@ -0,0 +1,380 @@ +#ifdef PIPEWIRE_PRESENT + +#include "camera.h" + +#include +#include +#include + +#include + +#define PW_NAME "Camera_PW" + +typedef struct { + struct pw_main_loop *loop; + struct pw_stream *stream; + struct spa_video_info format; + double *pct; + int num_captures; + int capture_idx; + char *settings; + map_t *stored_values; +} pw_data_t; + +typedef struct { + uint32_t id; + struct spa_hook proxy_listener; + struct pw_proxy *proxy; +} pw_node_t; + +typedef struct { + struct pw_loop *mon_loop; + struct pw_context *context; + struct pw_core *core; + struct pw_registry *registry; +} pw_mon_t; + +extern const struct pw_stream_control *pw_stream_get_control(struct pw_stream *stream, uint32_t id); +static void build_format(struct spa_pod_builder *b, const struct spa_pod **params); +static uint32_t control_to_prop_id(uint32_t control_id); +static void set_camera_setting(pw_data_t *pw, uint32_t op, float val, bool store); +static void set_camera_settings_def(pw_data_t *pw); +static void set_camera_settings(pw_data_t *pw); +static void restore_camera_settings(pw_data_t *pw); + +SENSOR(PW_NAME); + +static pw_mon_t pw_mon; + +static void _ctor_ init_libpipewire(void) { + pw_init(NULL, NULL); +} + +static void _dtor_ destroy_libpipewire(void) { + pw_deinit(); +} + +static void on_process(void *_data) { + pw_data_t *data = _data; + + struct pw_buffer *b; + if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { + fprintf(stderr, "out of buffers: %m"); + goto end; + } + + const bool is_yuv = data->format.info.raw.format == SPA_VIDEO_FORMAT_YUY2; + struct spa_buffer *buf = b->buffer; + for (int i = 0; i < buf->n_datas; i++) { + uint8_t *sdata; + if ((sdata = buf->datas[i].data) == NULL) { + goto end; + } + data->pct[data->capture_idx++] = get_frame_brightness(sdata, buf->datas[i].chunk->size, is_yuv); + + // Ok, we finished capturing frames + if (data->capture_idx == data->num_captures) { + goto end; + } + } + pw_stream_queue_buffer(data->stream, b); + return; + +end: + pw_main_loop_quit(data->loop); +} + +static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) { + pw_data_t *data = _data; + + if (param == NULL || id != SPA_PARAM_Format) { + return; + } + + if (spa_format_parse(param, + &data->format.media_type, + &data->format.media_subtype) < 0) { + pw_main_loop_quit(data->loop); + return; + } + + if (data->format.media_type != SPA_MEDIA_TYPE_video || + data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) { + pw_main_loop_quit(data->loop); + return; + } + + if (spa_format_video_raw_parse(param, &data->format.info.raw) < 0) { + pw_main_loop_quit(data->loop); + return; + } +} + +/* these are the stream events we listen for */ +static const struct pw_stream_events stream_events = { + PW_VERSION_STREAM_EVENTS, + .param_changed = on_stream_param_changed, + .process = on_process, +}; + +static void build_format(struct spa_pod_builder *b, const struct spa_pod **params) { + *params = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(2, + SPA_VIDEO_FORMAT_GRAY8, // V4L2_PIX_FMT_GREY + SPA_VIDEO_FORMAT_YUY2), // V4L2_PIX_FMT_YUYV + SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( + &SPA_RECTANGLE(160, 120), + &SPA_RECTANGLE(1, 1), + &SPA_RECTANGLE(640, 480)), + SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction( + &SPA_FRACTION(25, 1), + &SPA_FRACTION(0, 1), + &SPA_FRACTION(120, 1))); +} + +static bool validate_dev(void *dev) { + pw_data_t *pw = (pw_data_t *)dev; + return pw_stream_set_active(pw->stream, true) == 0; +} + +static void fetch_dev(const char *interface, void **dev) { + // TODO + setenv("XDG_RUNTIME_DIR", "/run/user/1000", 1); + pw_data_t *pw = calloc(1, sizeof(pw_data_t)); + if (pw) { + const struct spa_pod *params; + uint8_t buffer[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + + pw->loop = pw_main_loop_new(NULL); + pw->stream = pw_stream_new_simple( + pw_main_loop_get_loop(pw->loop), + "clightd-camera-pw", + pw_properties_new( + PW_KEY_MEDIA_TYPE, "Video", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Camera", + NULL), + &stream_events, + pw); + + build_format(&b, ¶ms); + + int res; + // FIXME: fix when correct device is passed + if ((res = pw_stream_connect(pw->stream, + PW_DIRECTION_INPUT, + interface ? (uint32_t)atoi(interface) : PW_ID_ANY, + PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ + PW_STREAM_FLAG_INACTIVE | + PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */ + ¶ms, 1)) /* extra parameters, see above */ < 0) { + + fprintf(stderr, "can't connect: %s\n", spa_strerror(res)); + free(pw); + return; + } + *dev = pw; + } +} + +static void fetch_props_dev(void *dev, const char **node, const char **action) { + static char str_id[32]; + pw_data_t *pw = (pw_data_t *)dev; + uint32_t id = pw_stream_get_node_id(pw->stream); + sprintf(str_id, "%d", id); + *node = str_id; + + if (action) { + // TODO + *action = ""; + } +} + +static void destroy_dev(void *dev) { + pw_data_t *pw = (pw_data_t *)dev; + pw_stream_destroy(pw->stream); + pw_main_loop_destroy(pw->loop); + map_free(pw->stored_values); + free(dev); +} +/* +static void removed_proxy (void *data) { + pw_node_t *node = (pw_node_t *)data; + INFO("Removed node %d\n", node->id); + // TODO: manage node removed + pw_proxy_destroy(node->proxy); + free(node); +} + +static const struct pw_proxy_events proxy_events = { + PW_VERSION_PROXY_EVENTS, + .removed = removed_proxy, +}; + +static void registry_event_global(void *data, uint32_t id, + uint32_t permissions, const char *type, uint32_t version, + const struct spa_dict *props) { + + if (strcmp(type, PW_TYPE_INTERFACE_Node) == 0) { + const char *mc = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); + const char *mr = spa_dict_lookup(props, PW_KEY_MEDIA_ROLE); + if (mc && mr && !strcmp(mc, "Video/Source") && !strcmp(mr, "Camera")) { + INFO("Added node %d\n", id); + pw_node_t *node = calloc(1, sizeof(pw_node_t)); + node->id = id; + node->proxy = pw_registry_bind(pw_mon.registry, id, type, PW_VERSION_NODE, sizeof(pw_node_t)); + pw_proxy_add_listener(node->proxy, &node->proxy_listener, &proxy_events, node); + } + } +} + +static const struct pw_registry_events registry_events = { + PW_VERSION_REGISTRY_EVENTS, + .global = registry_event_global, +};*/ + +static int init_monitor(void) { + // TODO ? +// pw_mon.mon_loop = pw_loop_new(NULL); +// pw_mon.context = pw_context_new(pw_mon.mon_loop, NULL, 0); +// pw_mon.core = pw_context_connect(pw_mon.context, NULL, 0); +// pw_mon.registry = pw_core_get_registry(pw_mon.core, PW_VERSION_REGISTRY, 0); +// +// struct spa_hook registry_listener; +// spa_zero(registry_listener); +// pw_registry_add_listener(pw_mon.registry, ®istry_listener, ®istry_events, NULL); +// +// int fd = pw_loop_get_fd(pw_mon.mon_loop); +// pw_loop_enter(pw_mon.mon_loop); +// return fd; +} + +static void recv_monitor(void **dev) { +// pw_loop_iterate(pw_mon.mon_loop, 0); +// *dev = NULL; // TODO +} + +static void destroy_monitor(void) { +// pw_loop_leave(pw_mon.mon_loop); +// pw_proxy_destroy((struct pw_proxy*)pw_mon.registry); +// pw_core_disconnect(pw_mon.core); +// pw_context_destroy(pw_mon.context); +// pw_loop_destroy(pw_mon.mon_loop); +} + +static int capture(void *dev, double *pct, const int num_captures, char *settings) { + pw_data_t *pw = (pw_data_t *)dev; + pw->num_captures = num_captures; + pw->pct = pct; + pw->settings = settings; + pw->stored_values = map_new(true, free); + + set_camera_settings(pw); + pw_main_loop_run(pw->loop); + restore_camera_settings(pw); + return pw->capture_idx; +} + +// Stolen from https://github.com/PipeWire/pipewire/blob/master/spa/plugins/v4l2/v4l2-utils.c#L1017 +static uint32_t control_to_prop_id(uint32_t control_id) { + switch (control_id) { + case V4L2_CID_BRIGHTNESS: + return SPA_PROP_brightness; + case V4L2_CID_CONTRAST: + return SPA_PROP_contrast; + case V4L2_CID_SATURATION: + return SPA_PROP_saturation; + case V4L2_CID_HUE: + return SPA_PROP_hue; + case V4L2_CID_GAMMA: + return SPA_PROP_gamma; + case V4L2_CID_EXPOSURE: + return SPA_PROP_exposure; + case V4L2_CID_GAIN: + return SPA_PROP_gain; + case V4L2_CID_SHARPNESS: + return SPA_PROP_sharpness; + default: + return SPA_PROP_START_CUSTOM + control_id; + } +} + +static void set_camera_setting(pw_data_t *pw, uint32_t op, float val, bool store) { + enum spa_prop pw_op = control_to_prop_id(op); + const struct pw_stream_control *ctrl = pw_stream_get_control(pw->stream, pw_op); + if (ctrl) { + INFO("%s (%u) default val: %.2lf\n", ctrl->name, op, ctrl->def); + if (val < 0) { + val = ctrl->def; + } + if (ctrl->values[0] != val) { + float old_val = ctrl->values[0]; + pw_stream_set_control(pw->stream, pw_op, 1, &val); + INFO("Set '%s' val: %.2lf\n", ctrl->name, val); + if (store) { + INFO("Storing initial setting for '%s': %.2lf\n", ctrl->name, old_val); + struct v4l2_control *v = malloc(sizeof(struct v4l2_control)); + v->value = old_val; + v->id = op; + map_put(pw->stored_values, ctrl->name, v); + } + } else { + INFO("Value %2.lf for '%s' already set.\n", val, ctrl->name); + } + } else { + INFO("%u unsupported\n", op); + } +} + +static void set_camera_settings_def(pw_data_t *pw) { + set_camera_setting(pw, V4L2_CID_SCENE_MODE, -1, true); + set_camera_setting(pw, V4L2_CID_AUTO_WHITE_BALANCE, -1, true); + set_camera_setting(pw, V4L2_CID_EXPOSURE_AUTO, -1, true); + set_camera_setting(pw, V4L2_CID_AUTOGAIN, -1, true); + set_camera_setting(pw, V4L2_CID_ISO_SENSITIVITY_AUTO, -1, true); + set_camera_setting(pw, V4L2_CID_BACKLIGHT_COMPENSATION, -1, true); + set_camera_setting(pw, V4L2_CID_AUTOBRIGHTNESS, -1, true); + + set_camera_setting(pw, V4L2_CID_WHITE_BALANCE_TEMPERATURE, -1, true); + set_camera_setting(pw, V4L2_CID_EXPOSURE_ABSOLUTE, -1, true); + set_camera_setting(pw, V4L2_CID_IRIS_ABSOLUTE, -1, true); + set_camera_setting(pw, V4L2_CID_GAIN, -1, true); + set_camera_setting(pw, V4L2_CID_ISO_SENSITIVITY, -1, true); + set_camera_setting(pw, V4L2_CID_BRIGHTNESS, -1, true); +} + +/* Parse settings string! */ +static void set_camera_settings(pw_data_t *pw) { + /* Set default values */ + set_camera_settings_def(pw); + if (pw->settings && strlen(pw->settings)) { + char *token; + char *rest = pw->settings; + + while ((token = strtok_r(rest, ",", &rest))) { + uint32_t v4l2_op; + int32_t v4l2_val; + if (sscanf(token, "%u=%d", &v4l2_op, &v4l2_val) == 2) { + float val = v4l2_val; + set_camera_setting(pw, v4l2_op, val, true); + } else { + fprintf(stderr, "Expected a=b format in '%s' token.\n", token); + } + } + } +} + +static void restore_camera_settings(pw_data_t *pw) { + for (map_itr_t *itr = map_itr_new(pw->stored_values); itr; itr = map_itr_next(itr)) { + struct v4l2_control *old_ctrl = map_itr_get_data(itr); + const char *ctrl_name = map_itr_get_key(itr); + INFO("Restoring setting for '%s'\n", ctrl_name) + set_camera_setting(pw, old_ctrl->id, old_ctrl->value, false); + } +} + +#endif From 44bd91a21941ee3f0a1b3dde7a3719fcdb7f6ebd Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Mon, 5 Jul 2021 18:07:34 +0200 Subject: [PATCH 02/23] Some more changes; pipewire support is quite in place but clightd needs to be run as user. --- TODO.md | 28 ++++- src/modules/sensors/camera.c | 1 - src/modules/sensors/camera.h | 1 + src/modules/sensors/pipewire.c | 205 ++++++++++++++++++++------------- 4 files changed, 154 insertions(+), 81 deletions(-) diff --git a/TODO.md b/TODO.md index 5c68f38..f391b41 100644 --- a/TODO.md +++ b/TODO.md @@ -5,8 +5,32 @@ ### Pipewire - [x] Support pipewire for Camera sensor? This would allow multiple application sharing camera -- [ ] Support monitor sensor api for pipewire -- [ ] Pipewire run as root needs XDG_RUNTIME_DIR env +- [x] Support monitor sensor api for pipewire +- [ ] Pipewire run as root needs XDG_RUNTIME_DIR env ... +- [ ] Unify camera settings between camera and pipewire sensors + +### Run as user? + +- [ ] API breaking change (6.0) +- [ ] Connect to user bus +- [ ] Check each module: +- - [x] Signal +- - [x] Bus +- - [x] Pipewire/Camera (require "video" group) +- - [ ] yoctolight +- - [ ] ALS +- - [x] dpms (double check drm) +- - [x] gamma (double check drm) +- - [x] idle +- - [ ] keyboard +- - [ ] backlight +- - [x] screen (double check drm) + +- [ ] Drop polkit and use sd_session_is_active() +- [ ] Add udev rules for yoctolight, als, keyboard and backlight modules for "clightd" group +- [ ] Drop useless API params (eg: DISPLAY, XAUTHORITY, XDG_RUNTIME_DIR etc etc) + +... would break https://github.com/FedeDP/Clight/issues/144 ... ## 6.x diff --git a/src/modules/sensors/camera.c b/src/modules/sensors/camera.c index cc99600..ca4909e 100644 --- a/src/modules/sensors/camera.c +++ b/src/modules/sensors/camera.c @@ -3,7 +3,6 @@ #include #include #include "camera.h" -#include #include #define CAMERA_NAME "Camera" diff --git a/src/modules/sensors/camera.h b/src/modules/sensors/camera.h index cc0108c..aa28e35 100644 --- a/src/modules/sensors/camera.h +++ b/src/modules/sensors/camera.h @@ -1,6 +1,7 @@ #include #include #include +#include #ifndef NDEBUG #define INFO(fmt, ...) printf(fmt, ##__VA_ARGS__); diff --git a/src/modules/sensors/pipewire.c b/src/modules/sensors/pipewire.c index 64ed85a..f118a35 100644 --- a/src/modules/sensors/pipewire.c +++ b/src/modules/sensors/pipewire.c @@ -11,27 +11,30 @@ #define PW_NAME "Camera_PW" typedef struct { - struct pw_main_loop *loop; + uint32_t id; + struct spa_hook proxy_listener; + struct pw_proxy *proxy; + const char *action; +} pw_node_t; + +typedef struct { + pw_node_t node; + struct pw_loop *loop; struct pw_stream *stream; struct spa_video_info format; double *pct; - int num_captures; int capture_idx; char *settings; map_t *stored_values; + bool with_err; } pw_data_t; -typedef struct { - uint32_t id; - struct spa_hook proxy_listener; - struct pw_proxy *proxy; -} pw_node_t; - typedef struct { struct pw_loop *mon_loop; struct pw_context *context; struct pw_core *core; struct pw_registry *registry; + struct spa_hook registry_listener; } pw_mon_t; extern const struct pw_stream_control *pw_stream_get_control(struct pw_stream *stream, uint32_t id); @@ -45,6 +48,9 @@ static void restore_camera_settings(pw_data_t *pw); SENSOR(PW_NAME); static pw_mon_t pw_mon; +static pw_data_t *last_recved; +static pw_data_t **nodes; +static int nodes_len; static void _ctor_ init_libpipewire(void) { pw_init(NULL, NULL); @@ -60,28 +66,21 @@ static void on_process(void *_data) { struct pw_buffer *b; if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { fprintf(stderr, "out of buffers: %m"); - goto end; + goto err; } const bool is_yuv = data->format.info.raw.format == SPA_VIDEO_FORMAT_YUY2; struct spa_buffer *buf = b->buffer; - for (int i = 0; i < buf->n_datas; i++) { - uint8_t *sdata; - if ((sdata = buf->datas[i].data) == NULL) { - goto end; - } - data->pct[data->capture_idx++] = get_frame_brightness(sdata, buf->datas[i].chunk->size, is_yuv); - - // Ok, we finished capturing frames - if (data->capture_idx == data->num_captures) { - goto end; - } + uint8_t *sdata; + if ((sdata = buf->datas[0].data) == NULL) { + goto err; } + data->pct[data->capture_idx++] = get_frame_brightness(sdata, buf->datas[0].chunk->size, is_yuv); pw_stream_queue_buffer(data->stream, b); return; -end: - pw_main_loop_quit(data->loop); +err: + data->with_err = true; } static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) { @@ -94,18 +93,18 @@ static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_p if (spa_format_parse(param, &data->format.media_type, &data->format.media_subtype) < 0) { - pw_main_loop_quit(data->loop); + data->with_err = true; return; } if (data->format.media_type != SPA_MEDIA_TYPE_video || data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) { - pw_main_loop_quit(data->loop); + data->with_err = true; return; } if (spa_format_video_raw_parse(param, &data->format.info.raw) < 0) { - pw_main_loop_quit(data->loop); + data->with_err = true; return; } } @@ -137,21 +136,34 @@ static void build_format(struct spa_pod_builder *b, const struct spa_pod **param static bool validate_dev(void *dev) { pw_data_t *pw = (pw_data_t *)dev; - return pw_stream_set_active(pw->stream, true) == 0; + if (pw->stream) { + return pw_stream_set_active(pw->stream, true) == 0; + } + return true; } static void fetch_dev(const char *interface, void **dev) { - // TODO - setenv("XDG_RUNTIME_DIR", "/run/user/1000", 1); - pw_data_t *pw = calloc(1, sizeof(pw_data_t)); + pw_data_t *pw = NULL; + if (interface) { + uint32_t id = atoi(interface); + for (int i = 0; i < nodes_len && !pw; i++) { + if (nodes[i]->node.id == id) { + pw = nodes[i]; + } + } + } else if (nodes_len > 0) { + pw = nodes[0]; + } + + // TODO error checking if (pw) { const struct spa_pod *params; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - pw->loop = pw_main_loop_new(NULL); + pw->loop = pw_loop_new(NULL); pw->stream = pw_stream_new_simple( - pw_main_loop_get_loop(pw->loop), + pw->loop, "clightd-camera-pw", pw_properties_new( PW_KEY_MEDIA_TYPE, "Video", @@ -164,10 +176,10 @@ static void fetch_dev(const char *interface, void **dev) { build_format(&b, ¶ms); int res; - // FIXME: fix when correct device is passed + // FIXME: fix when correct device is passed ?? if ((res = pw_stream_connect(pw->stream, PW_DIRECTION_INPUT, - interface ? (uint32_t)atoi(interface) : PW_ID_ANY, + /*pw->node.id*/PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ PW_STREAM_FLAG_INACTIVE | PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */ @@ -177,57 +189,84 @@ static void fetch_dev(const char *interface, void **dev) { free(pw); return; } - *dev = pw; } + *dev = pw; } static void fetch_props_dev(void *dev, const char **node, const char **action) { static char str_id[32]; pw_data_t *pw = (pw_data_t *)dev; - uint32_t id = pw_stream_get_node_id(pw->stream); - sprintf(str_id, "%d", id); + + sprintf(str_id, "%d", pw->node.id); *node = str_id; if (action) { - // TODO - *action = ""; + *action = pw->node.action; } } static void destroy_dev(void *dev) { pw_data_t *pw = (pw_data_t *)dev; - pw_stream_destroy(pw->stream); - pw_main_loop_destroy(pw->loop); + if (pw->stream) { + pw_stream_destroy(pw->stream); + } + if (pw->loop) { + pw_loop_destroy(pw->loop); + } map_free(pw->stored_values); - free(dev); + if (!strcmp(pw->node.action, UDEV_ACTION_RM)) { + int found_idx = -1; + for (int i = 0; i < nodes_len; i++) { + if (found_idx != -1) { + nodes[i - 1] = nodes[i]; + nodes[i] = NULL; + } + if (pw == nodes[i]) { + found_idx = i; + } + } + free(pw); + nodes = realloc(nodes, --nodes_len); + } } -/* -static void removed_proxy (void *data) { - pw_node_t *node = (pw_node_t *)data; - INFO("Removed node %d\n", node->id); - // TODO: manage node removed - pw_proxy_destroy(node->proxy); - free(node); + +static void removed_proxy(void *data) { + pw_data_t *pw = (pw_data_t *)data; + pw->node.action = UDEV_ACTION_RM; + INFO("Removed node %d\n", pw->node.id); + pw_proxy_destroy(pw->node.proxy); + last_recved = pw; } static const struct pw_proxy_events proxy_events = { - PW_VERSION_PROXY_EVENTS, - .removed = removed_proxy, + PW_VERSION_PROXY_EVENTS, + .removed = removed_proxy, }; static void registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { - if (strcmp(type, PW_TYPE_INTERFACE_Node) == 0) { const char *mc = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); const char *mr = spa_dict_lookup(props, PW_KEY_MEDIA_ROLE); if (mc && mr && !strcmp(mc, "Video/Source") && !strcmp(mr, "Camera")) { + void *tmp = realloc(nodes, ++nodes_len); + if (!tmp) { + return; + } + nodes = tmp; + + pw_data_t *pw = calloc(1, sizeof(pw_data_t)); + if (!pw) { + return; + } INFO("Added node %d\n", id); - pw_node_t *node = calloc(1, sizeof(pw_node_t)); - node->id = id; - node->proxy = pw_registry_bind(pw_mon.registry, id, type, PW_VERSION_NODE, sizeof(pw_node_t)); - pw_proxy_add_listener(node->proxy, &node->proxy_listener, &proxy_events, node); + pw->node.id = id; + pw->node.action = UDEV_ACTION_ADD; + pw->node.proxy = pw_registry_bind(pw_mon.registry, id, type, PW_VERSION_NODE, sizeof(pw_data_t)); + pw_proxy_add_listener(pw->node.proxy, &pw->node.proxy_listener, &proxy_events, pw); + last_recved = pw; // used by recv_monitor + nodes[nodes_len - 1] = pw; } } } @@ -235,46 +274,56 @@ static void registry_event_global(void *data, uint32_t id, static const struct pw_registry_events registry_events = { PW_VERSION_REGISTRY_EVENTS, .global = registry_event_global, -};*/ +}; static int init_monitor(void) { - // TODO ? -// pw_mon.mon_loop = pw_loop_new(NULL); -// pw_mon.context = pw_context_new(pw_mon.mon_loop, NULL, 0); -// pw_mon.core = pw_context_connect(pw_mon.context, NULL, 0); -// pw_mon.registry = pw_core_get_registry(pw_mon.core, PW_VERSION_REGISTRY, 0); -// -// struct spa_hook registry_listener; -// spa_zero(registry_listener); -// pw_registry_add_listener(pw_mon.registry, ®istry_listener, ®istry_events, NULL); -// -// int fd = pw_loop_get_fd(pw_mon.mon_loop); -// pw_loop_enter(pw_mon.mon_loop); -// return fd; + // TODO... setenv... ?? + setenv("XDG_RUNTIME_DIR", "/run/user/1000", 1); + pw_mon.mon_loop = pw_loop_new(NULL); + pw_mon.context = pw_context_new(pw_mon.mon_loop, NULL, 0); + pw_mon.core = pw_context_connect(pw_mon.context, NULL, 0); + pw_mon.registry = pw_core_get_registry(pw_mon.core, PW_VERSION_REGISTRY, 0); + + spa_zero(pw_mon.registry_listener); + pw_registry_add_listener(pw_mon.registry, &pw_mon.registry_listener, ®istry_events, NULL); + + int fd = pw_loop_get_fd(pw_mon.mon_loop); + pw_loop_enter(pw_mon.mon_loop); + return fd; } static void recv_monitor(void **dev) { -// pw_loop_iterate(pw_mon.mon_loop, 0); -// *dev = NULL; // TODO + last_recved = NULL; + while (pw_loop_iterate(pw_mon.mon_loop, -1) >= 0) { + if (last_recved) { + break; + } + } + *dev = last_recved; } static void destroy_monitor(void) { -// pw_loop_leave(pw_mon.mon_loop); -// pw_proxy_destroy((struct pw_proxy*)pw_mon.registry); -// pw_core_disconnect(pw_mon.core); -// pw_context_destroy(pw_mon.context); -// pw_loop_destroy(pw_mon.mon_loop); + pw_loop_leave(pw_mon.mon_loop); + pw_proxy_destroy((struct pw_proxy*)pw_mon.registry); + pw_core_disconnect(pw_mon.core); + pw_context_destroy(pw_mon.context); + pw_loop_destroy(pw_mon.mon_loop); } static int capture(void *dev, double *pct, const int num_captures, char *settings) { pw_data_t *pw = (pw_data_t *)dev; - pw->num_captures = num_captures; pw->pct = pct; pw->settings = settings; pw->stored_values = map_new(true, free); set_camera_settings(pw); - pw_main_loop_run(pw->loop); + pw_loop_enter(pw->loop); + while (pw->capture_idx < num_captures && !pw->with_err) { + if (pw_loop_iterate(pw->loop, -1) < 0) { + break; + } + } + pw_loop_leave(pw->loop); restore_camera_settings(pw); return pw->capture_idx; } From 40591e974405d52544e08c7d015f58ac833f3d90 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Fri, 26 Nov 2021 20:00:36 +0100 Subject: [PATCH 03/23] Rebased on master + fixes. Signed-off-by: Federico Di Pierro --- CMakeLists.txt | 19 +- TODO.md | 101 +++-- src/commons.h | 9 + src/main.c | 16 +- src/modules/backlight.c | 698 +++---------------------------- src/modules/backlight2.c | 601 ++++++++++++++++++++++++++ src/modules/dpms.c | 2 +- src/modules/gamma.c | 3 +- src/modules/gamma.h | 4 +- src/modules/gamma_plugins/drm.c | 5 +- src/modules/gamma_plugins/wl.c | 76 +++- src/modules/gamma_plugins/xorg.c | 5 +- src/modules/keyboard.c | 139 ++++-- src/modules/screen_plugins/wl.c | 2 - src/modules/sensor.c | 31 +- src/modules/sensor.h | 2 +- src/modules/sensors/als.c | 14 +- src/modules/sensors/camera.c | 317 ++++++++------ src/modules/sensors/camera.h | 112 ++++- src/modules/sensors/custom.c | 2 +- src/modules/sensors/pipewire.c | 260 +++++++----- src/utils/udev.h | 4 - src/utils/wl_utils.c | 1 - 23 files changed, 1435 insertions(+), 988 deletions(-) create mode 100644 src/modules/backlight2.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 48d7ac8..737f61b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,19 @@ cmake_minimum_required(VERSION 3.14) -project(clightd VERSION 5.4 LANGUAGES C) +project(clightd VERSION 5.5 LANGUAGES C) set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") include(GNUInstallDirs) find_package(PkgConfig) +execute_process( + COMMAND git log -1 --format=%h + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + OUTPUT_VARIABLE GIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE +) + # Create program target file(GLOB SOURCES src/*.c src/utils/*.c src/modules/*.c src/modules/sensors/*.c) @@ -19,7 +26,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE ) target_compile_definitions(${PROJECT_NAME} PRIVATE -D_GNU_SOURCE - -DVERSION="${PROJECT_VERSION}" + -DVERSION="${PROJECT_VERSION}-${GIT_HASH}" ) set_property(TARGET ${PROJECT_NAME} PROPERTY C_STANDARD 99) @@ -120,14 +127,18 @@ if(SYSTEMD_BASE_FOUND) else() set(POLKIT_NAME "polkit") endif() - + + if(WITH_DDC) + set(AFTER "After=systemd-modules-load.service") + endif() + # Properly configure clightd systemd service to use correct dep on polkit.service configure_file(${SCRIPT_DIR}/clightd.service clightd.service @ONLY) # This can be overridden by cmdline if(NOT SYSTEMD_SERVICE_DIR) # Fetch it from systemd - pkg_get_variable(SYSTEMD_SERVICE_DIR systemd systemdsystemconfdir) + pkg_get_variable(SYSTEMD_SERVICE_DIR systemd systemdsystemunitdir) endif() install(FILES ${CMAKE_CURRENT_BINARY_DIR}/clightd.service diff --git a/TODO.md b/TODO.md index 8d0710f..0d7614d 100644 --- a/TODO.md +++ b/TODO.md @@ -1,46 +1,81 @@ -## 5.4 +## 5.5 + +### Camera +- [x] Add support for a cropping setting parameter: "x=0.4-0.6,y=0.2-0.8" +- [x] Support crop through crop and selection v4l2 api if available +- [x] fallback at manually skipping pixels + +### Gamma +- [x] return 0 for Wl gamma Get (sway protocol) even if it is not implemented, to avoid breaking clight +- [x] gamma on sway fix: keep connection alive and call dispatch! +- [x] fix segfault +- [x] move free(priv) inside each plugin as sway implementation requires it to be kept alive! + +### Backlight +- [x] Never set current pct to -1 before emitting signals; fixes https://github.com/FedeDP/Clight/issues/225 +- [x] Route old Backlight module to new Backlight2 to avoid api break for now + +### Backlight2 +- [x] Create new object paths for each detected display +- [x] Drop "Set/Get" methods, and add a Set method on each object path +- [x] SetAll will call Set on each object path (and will be renamed to Set); same for GetAll +- [x] Follow Keyboard API +- [x] Create this new interface under org.clightd.clightd.Backlight2 (to avoid api break)? +- [x] Actually implement logic +- [x] Add support for monitor hotplug using new ddcutil 1.2.0 api ddca_redetect_displays() + +- [x] call sd_bus_emit_object_added() sd_bus_emit_object_removed() When object path are created/deleted + +- [x] Better error handling/code +- [x] Drop bl_store_vpcode() and only load vpcode from CLIGHTD_BL_VCP env? +- [x] Add CLIGHTD_BL_VCP Environment variable to systemd script with a comment thus it is simple to update it if needed +- [x] Expose Max and Internal properties +- [ ] Update dbus api wiki +- [x] add a page about monitor hotplugging (dep on ddcutil >= 1.2.0 and refresh time!) +- [x] Investigate memleaks (related to ddca_redetect_displays()?) -> see here: https://github.com/rockowitz/ddcutil/issues/202 +- [x] Instead of 30s sleep, use an udev_monitor on drm subsystem? + +### KbdBacklight +- [x] call sd_bus_emit_object_added() sd_bus_emit_object_removed() When object path are created/deleted +- [x] Fix: udev_reference is a snapshot of an udev device at a current time. Wrong! + +### Generic +- [x] When built with ddcutil, clightd.service should be started after systemd-modules-load.service +- [x] Show commit hash in version + +### ALS +- [x] Fix: avoid using cached udev_dev reference in loop (thus always returning same ambient brightness read during a Capture request) +- [x] Fixed EIO errors ### Sensor -- [x] ALS sensors (and yoctolight too) have a logarithmic curve for lux values (#71) -- [x] fixed memleak in camera sensor +- [x] Only emit Sensor.Changed signal for added/removed devices ### Pipewire - [x] Support pipewire for Camera sensor? This would allow multiple application sharing camera +- [x] Pipewire run as root needs XDG_RUNTIME_DIR env -> workaround: find the first /run/user folder and use that +- [ ] Unify camera settings between camera and pipewire sensors... ? - [x] Support monitor sensor api for pipewire -- [ ] Pipewire run as root needs XDG_RUNTIME_DIR env ... -- [ ] Unify camera settings between camera and pipewire sensors - -### Run as user? - -- [ ] API breaking change (6.0) -- [ ] Connect to user bus -- [ ] Check each module: -- - [x] Signal -- - [x] Bus -- - [x] Pipewire/Camera (require "video" group) -- - [ ] yoctolight -- - [ ] ALS -- - [x] dpms (double check drm) -- - [x] gamma (double check drm) -- - [x] idle -- - [ ] keyboard -- - [ ] backlight -- - [x] screen (double check drm) -- [ ] Drop polkit and use sd_session_is_active() -- [ ] Add udev rules for yoctolight, als, keyboard and backlight modules for "clightd" group -- [ ] Drop useless API params (eg: DISPLAY, XAUTHORITY, XDG_RUNTIME_DIR etc etc) +## 5.x +- [ ] Keep it up to date with possible ddcutil api changes -... would break https://github.com/FedeDP/Clight/issues/144 ... +## 6.x (api break release) -## 6.x +### Generic +- [ ] Drop is_smooth options for gamma +- [ ] avoid returning boolean where it does not make sense +- [ ] Drop old BACKLIGHT module -> in case, drop {Lower,Raise,Set}All from clightd polkit policy +- [ ] Rename Backlight2 to Backlight -### Backlight -- [ ] Create new object paths for each detected display -- [ ] Drop "Set/Get" methods, and add a Set method on each object path -- [ ] SetAll will call Set on each object path (and will be renamed to Set) -- [ ] Drop GetAll method: you can only call Get on each object -- [ ] Follow Keyboard API +### Move to user service (?) +- [ ] move clightd to user service +- [ ] Drop polkit and use sd_session_is_active() +- [ ] Add udev rules for yoctolight, als, keyboard and backlight modules for "clightd" +- [ ] Drop useless API params (eg: DISPLAY, XAUTHORITY, XDG_RUNTIME_DIR etc etc) +- [ ] # groupadd clightd +- [ ] # usermod -aG clightd myusername +- [ ] # echo 'KERNEL=="i2c-[0-9]*", GROUP="clightd"' >> /etc/udev/rules.d/10-local_i2c_group.rules +... would break https://github.com/FedeDP/Clight/issues/144 ... ## Ideas - [ ] follow ddcci kernel driver and in case, drop ddcutil and add the kernel driver as clightd opt-dep diff --git a/src/commons.h b/src/commons.h index 41ab2d8..4bf83b2 100644 --- a/src/commons.h +++ b/src/commons.h @@ -4,7 +4,9 @@ #include #include #include +#ifndef USE_STACK_T // TODO: drop when ugprading to libmodule6.0.0 #include +#endif #include #include #include @@ -13,6 +15,7 @@ #include #include #include +#include #include #include @@ -25,5 +28,11 @@ #define WRONG_PLUGIN INT_MIN + 1 #define COMPOSITOR_NO_PROTOCOL INT_MIN + 2 +#define UDEV_ACTION_ADD "add" +#define UDEV_ACTION_RM "remove" +#define UDEV_ACTION_CHANGE "change" + +#ifndef USE_STACK_T extern sd_bus *bus; +#endif extern struct udev *udev; diff --git a/src/main.c b/src/main.c index 7d29731..862de94 100644 --- a/src/main.c +++ b/src/main.c @@ -26,10 +26,6 @@ sd_bus *bus = NULL; struct udev *udev = NULL; -#ifdef DDC_PRESENT -extern void bl_store_vpcode(int code); -#endif - static const char bus_interface[] = "org.clightd.clightd"; /* Every module needs it; let's init it before any module */ @@ -43,16 +39,12 @@ static void check_opts(int argc, char *argv[]) { printf("Clightd: dbus API to easily set screen backlight, gamma temperature and get ambient brightness through webcam frames capture or ALS devices.\n"); printf("* Current version: %s\n", VERSION); printf("* https://github.com/FedeDP/Clightd\n"); - printf("* Copyright (C) 2019 Federico Di Pierro \n"); + printf("* Copyright (C) 2021 Federico Di Pierro \n"); exit(EXIT_SUCCESS); + } else { + fprintf(stderr, "Unrecognized option: %s.\n", argv[i]); + exit(EXIT_FAILURE); } -#ifdef DDC_PRESENT - else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--vpcode")) { - if (++i < argc) { - bl_store_vpcode(atoi(argv[i])); - } - } -#endif } } diff --git a/src/modules/backlight.c b/src/modules/backlight.c index 95fae8a..9e00ce0 100644 --- a/src/modules/backlight.c +++ b/src/modules/backlight.c @@ -3,170 +3,18 @@ #include #include -#ifdef DDC_PRESENT - -#include - -/* Default value */ -static DDCA_Vcp_Feature_Code br_code = 0x10; - -void bl_store_vpcode(int code) { - br_code = code; -} - -static void bl_load_vpcode(void) { - if (getenv("CLIGHTD_BL_VCP")) { - br_code = strtol(getenv("CLIGHTD_BL_VCP"), NULL, 16); - } -} - -#define DDCUTIL_LOOP(func) \ - DDCA_Display_Info_List *dlist = NULL; \ - ddca_get_display_info_list2(false, &dlist); \ - if (dlist) { \ - bool leave = false; \ - for (int ndx = 0; ndx < dlist->ct && !leave; ndx++) { \ - DDCA_Display_Info *dinfo = &dlist->info[ndx]; \ - DDCA_Display_Ref dref = dinfo->dref; \ - DDCA_Display_Handle dh = NULL; \ - if (ddca_open_display2(dref, false, &dh)) { \ - continue; \ - } \ - DDCA_Any_Vcp_Value *valrec; \ - if (!ddca_get_any_vcp_value_using_explicit_type(dh, br_code, DDCA_NON_TABLE_VCP_VALUE, &valrec)) { \ - char id[32]; \ - get_info_id(id, sizeof(id), dinfo); \ - func; \ - ddca_free_any_vcp_value(valrec); \ - } \ - ddca_close_display(dh); \ - } \ - ddca_free_display_info_list(dlist); \ - } - -#define DDCUTIL_FUNC(sn, func) \ - DDCA_Display_Identifier pdid = NULL; \ - DDCA_Display_Ref dref = NULL; \ - DDCA_Display_Handle dh = NULL; \ - DDCA_Any_Vcp_Value *valrec = NULL; \ - if (convert_sn_to_id(sn, &pdid)) { \ - goto end; \ - } \ - if (ddca_get_display_ref(pdid, &dref)) { \ - goto end; \ - } \ - if (ddca_open_display2(dref, false, &dh)) { \ - goto end; \ - } \ - if (!ddca_get_any_vcp_value_using_explicit_type(dh, br_code, DDCA_NON_TABLE_VCP_VALUE, &valrec)) { \ - func; \ - ddca_free_any_vcp_value(valrec); \ - } \ -end: \ - if (dh) { \ - ddca_close_display(dh); \ - } \ - if (pdid) { \ - ddca_free_display_identifier(pdid); \ - } - - static void get_info_id(char *id, const int size, const DDCA_Display_Info *dinfo) { - if (!strlen(dinfo->sn) || !strcasecmp(dinfo->sn, "Unspecified")) { - switch(dinfo->path.io_mode) { - case DDCA_IO_I2C: - snprintf(id, size, "/dev/i2c-%d", dinfo->path.path.i2c_busno); - break; - case DDCA_IO_USB: - snprintf(id, size, "/dev/usb/hiddev%d", dinfo->path.path.hiddev_devno); - break; - default: - snprintf(id, size, "%d", dinfo->dispno); - break; - } - } else { - strncpy(id, dinfo->sn, size); - } - } - - static DDCA_Status convert_sn_to_id(const char *sn, DDCA_Display_Identifier *pdid) { - int rc = ddca_create_mfg_model_sn_display_identifier(NULL, NULL, sn, pdid); - if (rc == 0) { - return rc; - } - int id; - if (sscanf(sn, "/dev/i2c-%d", &id) == 1) { - return ddca_create_busno_display_identifier(id, pdid); - } - if (sscanf(sn, "/dev/usb/hiddev%d", &id) == 1) { - return ddca_create_usb_hiddev_display_identifier(id, pdid); - } - if (sscanf(sn, "%d", &id) == 1) { - return ddca_create_dispno_display_identifier(id, pdid); - } - return -ENOENT; - } - -#else - -#define DDCUTIL_LOOP(func) do {} while(0) -#define DDCUTIL_FUNC(sn, func) do {} while(0) - -#endif - -#define BL_SUBSYSTEM "backlight" - -typedef struct { - bool is_internal; - void *dev; - char *sn; - int max; -} device_t; - -typedef struct { - double step; - unsigned int wait; - int fd; -} smooth_t; - -typedef struct { - double curr_pct; - double target_pct; - smooth_t smooth; - device_t d; - int verse; - bool reached_target; -} smooth_client; - -/* Helpers */ -static void dtor_client(void *client); -static void reset_backlight_struct(smooth_client *sc, double target_pct, double initial_pct, bool is_smooth, - double smooth_step, unsigned int smooth_wait, int verse); -static int add_backlight_sn(double target_pct, bool is_smooth, double smooth_step, - unsigned int smooth_wait, int verse, const char *sn, int internal); -static void sanitize_target_step(double *target_pct, double *smooth_step); -static int get_all_brightness(sd_bus_message *m, sd_bus_message **reply, sd_bus_error *ret_error); -static int next_backlight_level(smooth_client *sc); -static int set_internal_backlight(smooth_client *sc); -static int set_external_backlight(smooth_client *sc); -static int set_single_serial(double target_pct, bool is_smooth, double smooth_step, const unsigned int smooth_wait, - const char *serial, int verse); -static smooth_client *fetch_running_client(const char *sn); -static void append_backlight(sd_bus_message *reply, const char *name, const double pct); -static int append_internal_backlight(sd_bus_message *reply, const char *path); -static int append_external_backlight(sd_bus_message *reply, const char *sn, bool first_found); -static int append_running_backlight(sd_bus_message *reply, const char *sn); +static inline void *fetch_bl(sd_bus_message *m); /* Exposed */ static int method_setallbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); static int method_getallbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); static int method_raiseallbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); static int method_lowerallbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); -static int method_setbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); -static int method_getbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); -static int method_raisebrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); -static int method_lowerbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +static int method_setbrightness1(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +static int method_getbrightness1(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +static int method_raisebrightness1(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +static int method_lowerbrightness1(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); -static map_t *running_clients; static const char object_path[] = "/org/clightd/clightd/Backlight"; static const char bus_interface[] = "org.clightd.clightd.Backlight"; static const sd_bus_vtable vtable[] = { @@ -175,14 +23,20 @@ static const sd_bus_vtable vtable[] = { SD_BUS_METHOD("GetAll", "s", "a(sd)", method_getallbrightness, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("RaiseAll", "d(bdu)s", "b", method_raiseallbrightness, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("LowerAll", "d(bdu)s", "b", method_lowerallbrightness, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Set", "d(bdu)s", "b", method_setbrightness, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Get", "s", "(sd)", method_getbrightness, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Raise", "d(bdu)s", "b", method_raisebrightness, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Lower", "d(bdu)s", "b", method_lowerbrightness, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Set", "d(bdu)s", "b", method_setbrightness1, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Get", "s", "(sd)", method_getbrightness1, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Raise", "d(bdu)s", "b", method_raisebrightness1, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Lower", "d(bdu)s", "b", method_lowerbrightness1, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_SIGNAL("Changed", "sd", 0), SD_BUS_VTABLE_END }; -static struct udev_monitor *mon; + +extern int method_setbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +extern int method_getbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +extern int method_raisebrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +extern int method_lowerbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +extern map_ret_code get_backlight(void *userdata, const char *key, void *data); +extern map_t *bls; MODULE("BACKLIGHT"); @@ -199,10 +53,6 @@ static bool evaluate(void) { } static void init(void) { -#ifdef DDC_PRESENT - bl_load_vpcode(); -#endif - running_clients = map_new(false, dtor_client); int r = sd_bus_add_object_vtable(bus, NULL, object_path, @@ -212,509 +62,77 @@ static void init(void) { if (r < 0) { m_log("Failed to issue method call: %s\n", strerror(-r)); } - int fd = init_udev_monitor(BL_SUBSYSTEM, &mon); - m_register_fd(fd, false, NULL); } static void receive(const msg_t *msg, const void *userdata) { - uint64_t t; - if (!msg->is_pubsub) { - if (msg->fd_msg->userptr) { - /* From smooth client */ - smooth_client *sc = (smooth_client *)msg->fd_msg->userptr; - read(sc->smooth.fd, &t, sizeof(uint64_t)); - if (!sc->reached_target) { - int ret; - if (sc->d.is_internal) { - ret = set_internal_backlight(sc); - } else { - ret = set_external_backlight(sc); - } - if (ret != 0) { - m_log("failed to set backlight for %s\n", sc->d.sn); - /* - * failed to set backlight; stop right now to avoid endless loop: - * some external monitors report the capability to mange backlight - * but they fail instead, leaving us to an infinite loop. - */ - sc->reached_target = true; - } - } - - /* - * For external monitor: - * Emit signal now as we will never receive them from udev monitor. - */ - if (!sc->d.is_internal) { - sd_bus_emit_signal(bus, object_path, bus_interface, "Changed", "sd", sc->d.sn, sc->curr_pct); - } - - if (!sc->reached_target) { - struct itimerspec timerValue = {{0}}; - timerValue.it_value.tv_sec = sc->smooth.wait / 1000; - timerValue.it_value.tv_nsec = 1000 * 1000 * (sc->smooth.wait % 1000); // ms - timerfd_settime(sc->smooth.fd, 0, &timerValue, NULL); - } else { - m_log("%s reached target backlight: %s%.2lf.\n", sc->d.sn, sc->verse > 0 ? "+" : (sc->verse < 0 ? "-" : ""), sc->target_pct); - map_remove(running_clients, sc->d.sn); - } - } else { - /* From udev monitor, consume! */ - struct udev_device *dev = udev_monitor_receive_device(mon); - if (dev) { - const char *action = udev_device_get_action(dev); - if (action && !strcmp(action, UDEV_ACTION_CHANGE)) { - int val = atoi(udev_device_get_sysattr_value(dev, "brightness")); - int max = atoi(udev_device_get_sysattr_value(dev, "max_brightness")); - const double pct = (double)val / max; - sd_bus_emit_signal(bus, object_path, bus_interface, "Changed", "sd", udev_device_get_sysname(dev), pct); - } - udev_device_unref(dev); - } - } - } } static void destroy(void) { - map_free(running_clients); - udev_monitor_unref(mon); -} - -static void dtor_client(void *client) { - smooth_client *sc = (smooth_client *)client; - /* Free all resources */ - m_deregister_fd(sc->smooth.fd); // this will automatically close it! - free(sc->d.sn); - if (sc->d.is_internal) { - udev_device_unref(sc->d.dev); - } - free(sc); -} - -static void reset_backlight_struct(smooth_client *sc, double target_pct, double initial_pct, bool is_smooth, - double smooth_step, unsigned int smooth_wait, int verse) { - sc->smooth.step = is_smooth ? smooth_step : 0.0; - sc->smooth.wait = is_smooth ? smooth_wait : 0; - sc->target_pct = target_pct; - sc->reached_target = false; - sc->curr_pct = initial_pct; - sc->verse = verse; - - /* Only if not already there */ - if (sc->smooth.fd == 0) { - sc->smooth.fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); - m_register_fd(sc->smooth.fd, true, sc); - } - - struct itimerspec timerValue = {{0}}; - timerValue.it_value.tv_sec = 0; - timerValue.it_value.tv_nsec = 1; // immediately - timerfd_settime(sc->smooth.fd, 0, &timerValue, NULL); -} - -static int add_backlight_sn(double target_pct, bool is_smooth, double smooth_step, - unsigned int smooth_wait, int verse, const char *sn, int internal) { - bool ok = !internal; // for external monitor -> always ok - struct udev_device *dev = NULL; - void *device = NULL; - char *sn_id = (sn && strlen(sn)) ? strdup(sn) : NULL; - double initial_pct = 0.0; - int max = -1; - /* Properly check internal interface exists before adding it */ - if (internal) { - get_udev_device(sn_id, BL_SUBSYSTEM, NULL, NULL, &dev); - if (dev) { - ok = true; - if (!sn_id) { - sn_id = strdup(udev_device_get_sysname(dev)); - } - max = atoi(udev_device_get_sysattr_value(dev, "max_brightness")); - int curr = atoi(udev_device_get_sysattr_value(dev, "brightness")); - initial_pct = (double) curr / max; - internal = 1; - device = dev; - } - - if (internal == -1 && !ok) { - if (sn_id) { - // find display with given id - DDCUTIL_FUNC(sn_id, { - ok = true; - max = VALREC_MAX_VAL(valrec); - const uint16_t curr = VALREC_CUR_VAL(valrec); - initial_pct = (double)curr / max; - internal = 0; - device = dref; - }); - } else { - // Find first display available - DDCUTIL_LOOP({ - sn_id = strdup(id); - ok = true; - leave = true; // loop variable defined in DDCUTIL_LOOP - max = VALREC_MAX_VAL(valrec); - const uint16_t curr = VALREC_CUR_VAL(valrec); - initial_pct = (double)curr / max; - internal = 0; - device = dref; - }); - } - } - } - /* When internal argument was 0, initial_pct is set afterwards */ - - if (ok) { - smooth_client *sc = calloc(1, sizeof(smooth_client)); - reset_backlight_struct(sc, target_pct, initial_pct, is_smooth, smooth_step, smooth_wait, verse); - sc->d.is_internal = internal; - // device is internal or external, it does not matter because is inside an union - // thus sharing memory space. - sc->d.dev = device; - sc->d.sn = sn_id; - sc->d.max = max; - - map_put(running_clients, sc->d.sn, sc); - } else { - free(sn_id); - } - return ok ? 0 : -1; -} - -static void sanitize_target_step(double *target_pct, double *smooth_step) { - if (target_pct) { - if (*target_pct > 1.0) { - *target_pct = 1.0; - } else if (*target_pct < 0.0) { - *target_pct = 0.0; - } - } - - if (smooth_step) { - if (*smooth_step >= 1.0 || *smooth_step < 0.0) { - *smooth_step = 0.0; // disable smoothing - } - } -} - -static int get_all_brightness(sd_bus_message *m, sd_bus_message **reply, sd_bus_error *ret_error) { - const char *backlight_interface = NULL; - - int r = sd_bus_message_read(m, "s", &backlight_interface); - if (r >= 0) { - sd_bus_message_new_method_return(m, reply); - sd_bus_message_open_container(*reply, SD_BUS_TYPE_ARRAY, "(sd)"); - - int ret = append_internal_backlight(*reply, backlight_interface); - ret += append_external_backlight(*reply, NULL, false); - if (ret == -2) { // both returned -1 - r = -ENODEV; - sd_bus_error_set_errno(ret_error, -r); - } else { - r = 0; - } - sd_bus_message_close_container(*reply); - } - return r; -} - -static int next_backlight_level(smooth_client *sc) { - double target_pct = sc->target_pct; - if (sc->verse != 0) { - target_pct = sc->curr_pct + (sc->verse * sc->target_pct); - sanitize_target_step(&target_pct, NULL); - } - if (sc->smooth.step > 0) { - if (target_pct < sc->curr_pct) { - sc->curr_pct = (sc->curr_pct - sc->smooth.step < target_pct) ? - target_pct : sc->curr_pct - sc->smooth.step; - } else if (target_pct > sc->curr_pct) { - sc->curr_pct = (sc->curr_pct + sc->smooth.step) > target_pct ? - target_pct : sc->curr_pct + sc->smooth.step; - } else { - sc->curr_pct = -1.0f; // useless - } - } else { - sc->curr_pct = target_pct; - } - - if (sc->curr_pct == target_pct || sc->curr_pct == -1.0f) { - sc->reached_target = true; - } - return sc->d.max * sc->curr_pct; -} - -static int set_internal_backlight(smooth_client *sc) { - int r = -1; - - int value = next_backlight_level(sc); - /* Check if next_backlight_level returned -1 */ - if (value >= 0) { - char val[15] = {0}; - snprintf(val, sizeof(val) - 1, "%d", value); - r = udev_device_set_sysattr_value(sc->d.dev, "brightness", val); - } - return r; -} - -static int set_external_backlight(smooth_client *sc) { - int ret = -1; -#ifdef DDC_PRESENT - DDCA_Display_Handle dh = NULL; - if (!ddca_open_display2(sc->d.dev, false, &dh)) { - int new_value = next_backlight_level(sc); - if (new_value >= 0) { - int8_t new_sh = (new_value >> 8) & 0xff; - int8_t new_sl = new_value & 0xff; - ret = ddca_set_non_table_vcp_value(dh, br_code, new_sh, new_sl); - } - ddca_close_display(dh); - } -#endif - return ret; -} - -static int set_single_serial(double target_pct, bool is_smooth, double smooth_step, - const unsigned int smooth_wait, const char *serial, int verse) { - int r = -1; - sanitize_target_step(&target_pct, &smooth_step); - - smooth_client *sc = NULL; - if (serial && strlen(serial)) { - sc = fetch_running_client(serial); - } - if (!sc) { - // we do not know if this is an internal backlight, check both (-1) - r = add_backlight_sn(target_pct, is_smooth, smooth_step, smooth_wait, verse, serial, -1); - } else { - r = 0; - reset_backlight_struct(sc, target_pct, sc->curr_pct, is_smooth, smooth_step, smooth_wait, verse); - } - return r; -} - -static smooth_client *fetch_running_client(const char *sn) { - smooth_client *sc = map_get(running_clients, sn); - if (sc) { - return sc; - } -#ifdef DDC_PRESENT - /* - * For external monitors, check that user - * did not provide different display id in Get request - * (ie: the map key is different from sn) - */ - DDCA_Display_Identifier pdid = NULL; - if (convert_sn_to_id(sn, &pdid)) { - return NULL; - } - DDCA_Display_Ref ref = NULL; - if (ddca_get_display_ref(pdid, &ref) != 0) { - ddca_free_display_identifier(pdid); - return NULL; - } - - bool found = false; - map_itr_t *itr = NULL; - for (itr = map_itr_new(running_clients); itr && !found; itr = map_itr_next(itr)) { - sc = (smooth_client *)map_itr_get_data(itr); - if (!sc->d.is_internal) { - found = sc->d.dev == ref; - } - } - free(itr); - ddca_free_display_identifier(pdid); - if (found) { - return sc; - } -#endif - return NULL; -} - -static void append_backlight(sd_bus_message *reply, const char *name, const double pct) { - sd_bus_message_open_container(reply, SD_BUS_TYPE_STRUCT, "sd"); - sd_bus_message_append(reply, "sd", name, pct); - sd_bus_message_close_container(reply); -} - -static int append_internal_backlight(sd_bus_message *reply, const char *path) { - int ret = -1; - struct udev_device *dev = NULL; - get_udev_device(path, BL_SUBSYSTEM, NULL, NULL, &dev); - - if (dev) { - int val = atoi(udev_device_get_sysattr_value(dev, "brightness")); - int max = atoi(udev_device_get_sysattr_value(dev, "max_brightness")); - const double pct = (double)val / max; - - append_backlight(reply, udev_device_get_sysname(dev), pct); - udev_device_unref(dev); - ret = 0; - } - return ret; -} - -static int append_external_backlight(sd_bus_message *reply, const char *sn, bool first_found) { - int ret = -1; - if (sn && strlen(sn)) { - DDCUTIL_FUNC(sn, { - ret = 0; - append_backlight(reply, sn, (double)VALREC_CUR_VAL(valrec) / VALREC_MAX_VAL(valrec)); - }); - } else { - DDCUTIL_LOOP({ - ret = 0; - append_backlight(reply, id, (double)VALREC_CUR_VAL(valrec) / VALREC_MAX_VAL(valrec)); - leave = first_found; // loop variable defined in DDCUTIL_LOOP - }); - } - return ret; -} - -static int append_running_backlight(sd_bus_message *reply, const char *sn) { - if (!sn || !strlen(sn)) { - return -1; - } - - smooth_client *sc = fetch_running_client(sn); - if (sc) { - append_backlight(reply, sn, sc->curr_pct); - return 0; - } - return -1; } static int method_setallbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - ASSERT_AUTH(); - - const char *backlight_interface = NULL; - double target_pct, smooth_step; - const int is_smooth; - const unsigned int smooth_wait; - - int r = sd_bus_message_read(m, "d(bdu)s", &target_pct, &is_smooth, &smooth_step, - &smooth_wait, &backlight_interface); - if (r >= 0) { - sanitize_target_step(&target_pct, &smooth_step); - - int verse = 0; - if (userdata) { - verse = *((int *)userdata); - } - - /* Clear map */ - map_clear(running_clients); - add_backlight_sn(target_pct, is_smooth, smooth_step, smooth_wait, verse, backlight_interface, 1); - DDCUTIL_LOOP({ - /* - * In case same monitor happens multiple times - * (ie: it is connected to eg: hdmi and DisplayPort) - * skip it! - */ - if (map_has_key(running_clients, id)) { - continue; - } - add_backlight_sn(target_pct, is_smooth, smooth_step, smooth_wait, verse, id, 0); - smooth_client *sc = map_get(running_clients, id); - if (sc) { - sc->d.dev = dref; - sc->d.max = VALREC_MAX_VAL(valrec); - const uint16_t curr = VALREC_CUR_VAL(valrec); - sc->curr_pct = (double)curr / sc->d.max; - } - }); - m_log("Target pct: %s%.2lf\n", verse > 0 ? "+" : (verse < 0 ? "-" : ""), target_pct); - // Returns true if no errors happened; false if another client is already changing backlight - r = sd_bus_reply_method_return(m, "b", true); - } - return r; + return method_setbrightness(m, NULL, ret_error); } -/** - * Backlight pct getter method: for each screen (both internal and external) - * it founds, it will return a "(uid, current backlight pct)" struct. - * Note that for internal laptop screen, uid = syspath (eg: intel_backlight) - */ static int method_getallbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - sd_bus_message *reply = NULL; - int r = get_all_brightness(m, &reply, ret_error); - if (r == 0) { - r = sd_bus_send(NULL, reply, NULL); - } - sd_bus_message_unref(reply); - return r; + return method_getbrightness(m, NULL, ret_error); } static int method_raiseallbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - int verse = 1; - return method_setallbrightness(m, &verse, ret_error); + return method_raisebrightness(m, NULL, ret_error); } static int method_lowerallbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - int verse = -1; - return method_setallbrightness(m, &verse, ret_error); + return method_lowerbrightness(m, NULL, ret_error); } -static int method_setbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - ASSERT_AUTH(); - - const char *serial = NULL; - double target_pct, smooth_step; - const int is_smooth; - const unsigned int smooth_wait; - - int r = sd_bus_message_read(m, "d(bdu)s", &target_pct, &is_smooth, &smooth_step, &smooth_wait, &serial); +static inline void *fetch_bl(sd_bus_message *m) { + void *bl = NULL; + const char *sn = NULL; + // Do not check for errors: method_getbrightness1 has only a "s" signature! + sd_bus_message_skip(m, "d(bdu)"); + int r = sd_bus_message_read(m, "s", &sn); if (r >= 0) { - int verse = 0; - if (userdata) { - verse = *((int *)userdata); - } - r = set_single_serial(target_pct, is_smooth, smooth_step, smooth_wait, serial, verse); - if (r == -1) { - r = -EINVAL; - sd_bus_error_set_errno(ret_error, -r); - } else { - // Returns true if no errors happened; - r = sd_bus_reply_method_return(m, "b", true); - } - m_log("Target pct: %s%.2lf\n", verse > 0 ? "+" : (verse < 0 ? "-" : ""), target_pct); + bl = map_get(bls, sn); + sd_bus_message_rewind(m, true); } - return r; + return bl; } -static int method_getbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - const char *sn = NULL; - int r = sd_bus_message_read(m, "s", &sn); - if (r >= 0) { - sd_bus_message *reply = NULL; - sd_bus_message_new_method_return(m, &reply); - - r = append_running_backlight(reply, sn); - if (r != 0) { - r = 0; - if (append_internal_backlight(reply, sn) == -1) { - if (append_external_backlight(reply, sn, true) == -1) { - sd_bus_error_set_errno(ret_error, ENODEV); - r = -ENODEV; - } - } - } - - if (!r) { - r = sd_bus_send(NULL, reply, NULL); - } - sd_bus_message_unref(reply); +static int method_setbrightness1(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + void *bl = fetch_bl(m); + if (!bl) { + return -ENOENT; } - return r; + return method_setbrightness(m, bl, ret_error); } -static int method_raisebrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - int verse = 1; - return method_setbrightness(m, &verse, ret_error); +static int method_getbrightness1(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + void *bl = fetch_bl(m); + if (!bl) { + return -ENOENT; + } + const char *sn = NULL; + sd_bus_message_read(m, "s", &sn); + double pct = 0.0; + get_backlight(&pct, NULL, bl); + return sd_bus_reply_method_return(m, "(sd)", sn, pct); } -static int method_lowerbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - int verse = -1; - return method_setbrightness(m, &verse, ret_error); +static int method_raisebrightness1(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + void *bl = fetch_bl(m); + if (!bl) { + return -ENOENT; + } + return method_raisebrightness(m, bl, ret_error); +} + +static int method_lowerbrightness1(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + void *bl = fetch_bl(m); + if (!bl) { + return -ENOENT; + } + return method_lowerbrightness(m, bl, ret_error); } diff --git a/src/modules/backlight2.c b/src/modules/backlight2.c new file mode 100644 index 0000000..529076b --- /dev/null +++ b/src/modules/backlight2.c @@ -0,0 +1,601 @@ +#include +#include +#include +#include +#include + +#define BL_SUBSYSTEM "backlight" +#define DRM_SUBSYSTEM "drm" + +typedef struct { + double target_pct; + double step; + unsigned int wait; +} smooth_params_t; + +typedef struct { + smooth_params_t params; + int fd; +} smooth_t; + +typedef struct { + int is_internal; + void *dev; // differs between struct udev_device (internal devices) and DDCA_Display_Ref for external ones + int max; // cached device max backlight value + char obj_path[100]; + const char *sn; + sd_bus_slot *slot; + smooth_t *smooth; // when != NULL -> smoothing + uint64_t cookie; +} bl_t; + +/* Device manager */ +static void stop_smooth(bl_t *bl); +static void bl_dtor(void *data); +static int store_internal_device(struct udev_device *dev, void *userdata); +static void store_external_devices(void); + +/* Getters */ +static double get_internal_backlight(bl_t *bl); +static double get_external_backlight(bl_t *bl); +map_ret_code get_backlight(void *userdata, const char *key, void *data); + +/* Setters */ +static int set_internal_backlight(bl_t *bl, int value); +static int set_external_backlight(bl_t *bl, int value); +static int set_backlight_value(bl_t *bl, double *target_pct, double smooth_step); +static map_ret_code set_backlight(void *userdata, const char *key, void *data); + +/* Helper methods */ +static void emit_signals(bl_t *bl, double pct); +static void sanitize_target_step(double *target_pct, double *smooth_step); +static double next_backlight_pct(bl_t *bl, double *target_pct, double smooth_step); +static inline bool is_smooth(smooth_params_t *params); + +/* DBus API */ +// TODO: make these static once BACKLIGHT is killed +int method_setbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +int method_getbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +int method_raisebrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +int method_lowerbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); + +map_t *bls; +static struct udev_monitor *bl_mon, *drm_mon; +static uint64_t curr_cookie; +static int verse; + +static const char object_path[] = "/org/clightd/clightd/Backlight2"; +static const char bus_interface[] = "org.clightd.clightd.Backlight2.Server"; +static const char main_interface[] = "org.clightd.clightd.Backlight2"; + +/* To send signal on old interface. TODO: drop this once BACKLIGHT is killed */ +static const char old_object_path[] = "/org/clightd/clightd/Backlight"; +static const char old_bus_interface[] = "org.clightd.clightd.Backlight"; + +static const sd_bus_vtable main_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("Set", "d(du)", NULL, method_setbrightness, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Get", NULL, "a(sd)", method_getbrightness, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Raise", "d(du)", NULL, method_raisebrightness, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Lower", "d(du)", NULL, method_lowerbrightness, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_SIGNAL("Changed", "sd", 0), + SD_BUS_VTABLE_END +}; +static const sd_bus_vtable vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("Set", "d(du)", NULL, method_setbrightness, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Get", NULL, "d", method_getbrightness, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Raise", "d(du)", NULL, method_raisebrightness, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Lower", "d(du)", NULL, method_lowerbrightness, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_PROPERTY("Max", "i", NULL, offsetof(bl_t, max), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Internal", "b", NULL, offsetof(bl_t, is_internal), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_SIGNAL("Changed", "d", 0), + SD_BUS_VTABLE_END +}; + +MODULE("BACKLIGHT2"); + +#ifdef DDC_PRESENT + + #include + #include + + /* Default value */ + static DDCA_Vcp_Feature_Code br_code = 0x10; + + static void bl_load_vpcode(void) { + if (getenv("CLIGHTD_BL_VCP")) { + br_code = strtol(getenv("CLIGHTD_BL_VCP"), NULL, 16); + m_log("Set 0x%x vcp code.\n", br_code); + } + } + + static void get_info_id(char *id, const int size, const DDCA_Display_Info *dinfo) { + if (!strlen(dinfo->sn) || !strcasecmp(dinfo->sn, "Unspecified")) { + switch(dinfo->path.io_mode) { + case DDCA_IO_I2C: + snprintf(id, size, "/dev/i2c-%d", dinfo->path.path.i2c_busno); + break; + case DDCA_IO_USB: + snprintf(id, size, "/dev/usb/hiddev%d", dinfo->path.path.hiddev_devno); + break; + default: + snprintf(id, size, "%d", dinfo->dispno); + break; + } + } else { + strncpy(id, dinfo->sn, size); + } + } + + static void store_external_devices(void) { + DDCA_Display_Info_List *dlist = NULL; + ddca_get_display_info_list2(false, &dlist); + if (dlist) { + for (int ndx = 0; ndx < dlist->ct; ndx++) { + DDCA_Display_Info *dinfo = &dlist->info[ndx]; + DDCA_Display_Ref dref = dinfo->dref; + DDCA_Display_Handle dh = NULL; + if (ddca_open_display2(dref, false, &dh)) { + continue; + } + DDCA_Any_Vcp_Value *valrec; + if (!ddca_get_any_vcp_value_using_explicit_type(dh, br_code, DDCA_NON_TABLE_VCP_VALUE, &valrec)) { + char id[32]; + get_info_id(id, sizeof(id), dinfo); + bl_t *d = map_get(bls, id); + if (!d) { + d = calloc(1, sizeof(bl_t)); + if (d) { + d->is_internal = false; + d->dev = dref; + d->sn = strdup(id); + d->max = VALREC_MAX_VAL(valrec); + d->cookie = curr_cookie; + snprintf(d->obj_path, sizeof(d->obj_path) - 1, "%s/%s", object_path, d->sn); + int r = sd_bus_add_object_vtable(bus, &d->slot, d->obj_path, bus_interface, vtable, d); + if (r < 0) { + m_log("Failed to add object vtable on path '%s': %d\n", d->obj_path, r); + bl_dtor(d); + } else { + map_put(bls, d->sn, d); + /* Not on first load */ + if (d->cookie > 0) { + sd_bus_emit_object_added(bus, d->obj_path); + } + } + } + } else { + // Update cookie and dref + d->cookie = curr_cookie; + d->dev = dref; + } + ddca_free_any_vcp_value(valrec); + } + ddca_close_display(dh); + } + ddca_free_display_info_list(dlist); + } + } + +#else + + static void store_external_devices(void) { } + +#endif + +#if DDCUTIL_VMAJOR >= 1 && DDCUTIL_VMINOR >= 2 + + static void update_external_devices(void) { + /* + * Algo: increment current cookie, + * then rededect all displays. + * Then, store any new external monitor with new cookie, + * or update cookie and dref for still existent ones. + * Finally, remove any non-existent monitor + * (look for external monitors whose cookie is != of current cookie) + */ + curr_cookie++; + ddca_redetect_displays(); + store_external_devices(); + for (map_itr_t *itr = map_itr_new(bls); itr; itr = map_itr_next(itr)) { + bl_t *d = map_itr_get_data(itr); + if (!d->is_internal && d->cookie != curr_cookie) { + sd_bus_emit_object_removed(bus, d->obj_path); + map_itr_remove(itr); + } + } + } + +#else + + static void update_external_devices(void) { } + +#endif + +static void module_pre_start(void) { + +} + +static bool check(void) { + return true; +} + +static bool evaluate(void) { + return true; +} + +static void init(void) { +#ifdef DDC_PRESENT + bl_load_vpcode(); +#endif + bls = map_new(false, bl_dtor); + sd_bus_add_object_manager(bus, NULL, object_path); + int r = sd_bus_add_object_vtable(bus, + NULL, + object_path, + main_interface, + main_vtable, + NULL); + if (r < 0) { + m_log("Failed to issue method call: %s\n", strerror(-r)); + } + + // Store and create internal devices object paths + udev_devices_foreach(BL_SUBSYSTEM, NULL, store_internal_device, NULL); + + // Store and create external devices object paths + store_external_devices(); + + // Register udev monitor for new internal devices + int fd = init_udev_monitor(BL_SUBSYSTEM, &bl_mon); + m_register_fd(fd, false, NULL); + + // Register udev monitor for external devices + fd = init_udev_monitor(DRM_SUBSYSTEM, &drm_mon); + m_register_fd(fd, false, NULL); +} + +/* + * Note that ddcutil does not yet support monitor plug/unplug, + * thus we are not able to runtime detect newly added/removed monitors. + */ +static void receive(const msg_t *msg, const void *userdata) { + uint64_t t; + + if (!msg->is_pubsub) { + if (msg->fd_msg->userptr) { + /* From smooth client */ + bl_t *bl = (bl_t *)msg->fd_msg->userptr; + read(bl->smooth->fd, &t, sizeof(uint64_t)); + + int ret = set_backlight_value(bl, &bl->smooth->params.target_pct, bl->smooth->params.step); + if (ret != 0) { + m_log("failed to set backlight for %s\n", bl->sn); + /* + * failed to set backlight; stop right now to avoid endless loop: + * some external monitors report the capability to manage backlight + * but they fail instead, leaving us to an infinite loop. + */ + stop_smooth(bl); + } else if (bl->smooth->params.target_pct == 0) { + /* set_backlight_value advised us to stop smoothing as it ended */ + stop_smooth(bl); + } + } else { + /* From udev monitor, consume! */ + struct udev_device *dev = udev_monitor_receive_device(bl_mon); + if (dev) { + // Ok, the event was from internal monitor + const char *id = udev_device_get_sysname(dev); + const char *action = udev_device_get_action(dev); + if (action) { + bl_t *bl = map_get(bls, id); + if (!strcmp(action, UDEV_ACTION_CHANGE) && bl) { + // Load cached value + int old_bl_value = atoi(udev_device_get_sysattr_value(bl->dev, "brightness")); + /* Keep our device ref in sync! */ + udev_device_unref(bl->dev); + bl->dev = udev_device_ref(dev); + + int val = atoi(udev_device_get_sysattr_value(dev, "brightness")); + if (val != old_bl_value) { + const double pct = (double)val / bl->max; + emit_signals(bl, pct); + } + } else if (!strcmp(action, UDEV_ACTION_ADD) && !bl) { + if (store_internal_device(dev, &bl) != 0) { + m_log("failed to store new device.\n"); + } else { + sd_bus_emit_object_added(bus, bl->obj_path); + } + } else if (!strcmp(action, UDEV_ACTION_RM) && bl) { + sd_bus_emit_object_removed(bus, bl->obj_path); + map_remove(bls, id); + } + } + udev_device_unref(dev); + } else { + dev = udev_monitor_receive_device(drm_mon); + if (dev) { + // The event was from external monitor! + update_external_devices(); + udev_device_unref(dev); + } + } + } + } +} + +static void destroy(void) { + map_free(bls); + udev_monitor_unref(bl_mon); + udev_monitor_unref(drm_mon); +} + +static void stop_smooth(bl_t *bl) { + if (bl->smooth) { + m_deregister_fd(bl->smooth->fd); // this will automatically close it! + free(bl->smooth); + bl->smooth = NULL; + } +} + +static void bl_dtor(void *data) { + bl_t *bl = (bl_t *)data; + sd_bus_slot_unref(bl->slot); + if (bl->is_internal) { + udev_device_unref(bl->dev); + } + stop_smooth(bl); + free((void *)bl->sn); + free(bl); +} + +static int store_internal_device(struct udev_device *dev, void *userdata) { + int ret = -ENOMEM; + const int max = atoi(udev_device_get_sysattr_value(dev, "max_brightness")); + const char *id = udev_device_get_sysname(dev); + bl_t *d = calloc(1, sizeof(bl_t)); + if (d) { + d->is_internal = true; + d->dev = udev_device_ref(dev); + d->max = max; + d->sn = strdup(id); + // Unused. But receive() callback expects brightness value to be cached + udev_device_get_sysattr_value(dev, "brightness"); + snprintf(d->obj_path, sizeof(d->obj_path) - 1, "%s/%s", object_path, d->sn); + ret = sd_bus_add_object_vtable(bus, &d->slot, d->obj_path, bus_interface, vtable, d); + if (ret < 0) { + m_log("Failed to add object vtable on path '%s': %d\n", d->obj_path, ret); + bl_dtor(d); + } else { + ret = map_put(bls, d->sn, d); + if (ret == 0 && userdata) { + bl_t **bltmp = (bl_t **)userdata; + *bltmp = d; + } + } + } + return ret; +} + +static double get_internal_backlight(bl_t *bl) { + int val = atoi(udev_device_get_sysattr_value(bl->dev, "brightness")); + return (double)val / bl->max; +} + +static double get_external_backlight(bl_t *bl) { + double value = 0.0; +#ifdef DDC_PRESENT + DDCA_Display_Handle dh = NULL; + if (ddca_open_display2(bl->dev, false, &dh) == 0) { + DDCA_Any_Vcp_Value *valrec = NULL; + if (!ddca_get_any_vcp_value_using_explicit_type(dh, br_code, DDCA_NON_TABLE_VCP_VALUE, &valrec)) { + value = (double)VALREC_CUR_VAL(valrec) / bl->max; + ddca_free_any_vcp_value(valrec); + } + ddca_close_display(dh); + } +#endif + return value; +} + +map_ret_code get_backlight(void *userdata, const char *key, void *data) { + bl_t *bl = (bl_t *)data; + double *val = (double *)userdata; + if (bl->is_internal) { + *val = get_internal_backlight(bl); + } else { + *val = get_external_backlight(bl); + } + return MAP_OK; +} + +static int set_internal_backlight(bl_t *bl, int value) { + char val[15] = {0}; + snprintf(val, sizeof(val) - 1, "%d", value); + return udev_device_set_sysattr_value(bl->dev, "brightness", val); +} + +static int set_external_backlight(bl_t *bl, int value) { + int ret = -1; +#ifdef DDC_PRESENT + DDCA_Display_Handle dh = NULL; + if (!ddca_open_display2(bl->dev, false, &dh)) { + int8_t new_sh = (value >> 8) & 0xff; + int8_t new_sl = value & 0xff; + ret = ddca_set_non_table_vcp_value(dh, br_code, new_sh, new_sl); + ddca_close_display(dh); + } +#endif + return ret; +} + +/* Set a target_pct eventually computing smooth step */ +static int set_backlight_value(bl_t *bl, double *target_pct, double smooth_step) { + const double next_pct = next_backlight_pct(bl, target_pct, smooth_step); + const int value = (int)round(bl->max * next_pct); + int ret; + if (bl->is_internal) { + ret = set_internal_backlight(bl, value); + } else { + ret = set_external_backlight(bl, value); + } + if (ret == 0) { + /* + * For external monitor: + * Emit signals now as we will never receive them from udev monitor. + * For internal monitor: + * Emit signals now or they will be filtered by receive() callback check + * that new_val is != from cached one. + */ + emit_signals(bl, next_pct); + } + if (next_pct == *target_pct) { + m_log("%s reached target backlight: %.2lf.\n", bl->sn, next_pct); + *target_pct = 0; // eventually disable smooth (if called by set_backlight and not by timerfd) + } + return ret; +} + +static map_ret_code set_backlight(void *userdata, const char *key, void *data) { + smooth_params_t params = *((smooth_params_t *)userdata); + bl_t *bl = (bl_t *)data; + + stop_smooth(bl); + if (set_backlight_value(bl, ¶ms.target_pct, params.step) == 0) { + if (is_smooth(¶ms)) { + bl->smooth = calloc(1, sizeof(smooth_t)); + if (bl->smooth) { + memcpy(&bl->smooth->params, ¶ms, sizeof(smooth_params_t)); + + // set and start timer fd + bl->smooth->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); + m_register_fd(bl->smooth->fd, true, bl); + struct itimerspec timerValue = {{0}}; + timerValue.it_value.tv_sec = params.wait / 1000; + timerValue.it_value.tv_nsec = 1000 * 1000 * (params.wait % 1000); // ms + timerValue.it_interval.tv_sec = params.wait / 1000; + timerValue.it_interval.tv_nsec = 1000 * 1000 * (params.wait % 1000); // ms + timerfd_settime(bl->smooth->fd, 0, &timerValue, NULL); + } + } + return MAP_OK; + } + return MAP_ERR; +} + +static void emit_signals(bl_t *bl, double pct) { + sd_bus_emit_signal(bus, object_path, main_interface, "Changed", "sd", bl->sn, pct); + // TODO: drop this once BACKLIGHT is killed! + sd_bus_emit_signal(bus, old_object_path, old_bus_interface, "Changed", "sd", bl->sn, pct); + sd_bus_emit_signal(bus, bl->obj_path, bus_interface, "Changed", "d", pct); +} + +static void sanitize_target_step(double *target_pct, double *smooth_step) { + if (target_pct) { + if (*target_pct > 1.0) { + *target_pct = 1.0; + } else if (*target_pct < 0.0) { + *target_pct = 0.0; + } + } + + if (smooth_step) { + if (*smooth_step >= 1.0 || *smooth_step < 0.0) { + *smooth_step = 0.0; // disable smoothing + } + } +} + +static double next_backlight_pct(bl_t *bl, double *target_pct, double smooth_step) { + double curr_pct = 0.0; + get_backlight(&curr_pct, NULL, bl); + + if (verse != 0) { + *target_pct = curr_pct + (verse * *target_pct); + sanitize_target_step(target_pct, &smooth_step); + } + + if (smooth_step > 0) { + if (*target_pct < curr_pct) { + curr_pct = (curr_pct - smooth_step < *target_pct) ? *target_pct : curr_pct - smooth_step; + } else if (*target_pct > curr_pct) { + curr_pct = (curr_pct + smooth_step) > *target_pct ? *target_pct : curr_pct + smooth_step; + } + } else { + curr_pct = *target_pct; + } + return curr_pct; +} + +static inline bool is_smooth(smooth_params_t *params) { + return params->step > 0 && params->wait > 0 && params->target_pct > 0; +} + +int method_setbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + ASSERT_AUTH(); + + bool old_interface = false; + smooth_params_t params = {0}; + int r = sd_bus_message_read(m, "d(du)", ¶ms.target_pct, ¶ms.step, ¶ms.wait); + if (r < 0) { + sd_bus_message_rewind(m, true); + // Try to read this as a BACKLIGHT request. TODO: drop once BACKLIGHT is killed! + r = sd_bus_message_read(m, "d(bdu)", ¶ms.target_pct, NULL, ¶ms.step, ¶ms.wait); + old_interface = r >= 0; + } + if (r >= 0) { + m_log("Target pct: %s%.2lf\n", verse > 0 ? "+" : (verse < 0 ? "-" : ""), params.target_pct); + bl_t *d = (bl_t *)userdata; + if (d) { + r = set_backlight(¶ms, NULL, d); + } else { + r = map_iterate(bls, set_backlight, ¶ms); + } + verse = 0; // reset verse + if (!old_interface) { + r = sd_bus_reply_method_return(m, NULL); + } else { + // TODO: drop once BACKLIGHT is killed! + r = sd_bus_reply_method_return(m, "b", true); + } + } + return r; +} + +int method_getbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + bl_t *d = (bl_t *)userdata; + double pct = 0.0; + if (d) { + get_backlight(&pct, NULL, d); + return sd_bus_reply_method_return(m, "d", pct); + } + + sd_bus_message *reply = NULL; + sd_bus_message_new_method_return(m, &reply); + sd_bus_message_open_container(reply, SD_BUS_TYPE_ARRAY, "(sd)"); + for (map_itr_t *itr = map_itr_new(bls); itr; itr = map_itr_next(itr)) { + bl_t *d = (bl_t *)map_itr_get_data(itr); + const char *sn = map_itr_get_key(itr); + get_backlight(&pct, NULL, d); + sd_bus_message_open_container(reply, SD_BUS_TYPE_STRUCT, "sd"); + sd_bus_message_append(reply, "sd", sn, pct); + sd_bus_message_close_container(reply); + } + sd_bus_message_close_container(reply); + sd_bus_send(NULL, reply, NULL); + sd_bus_message_unref(reply); + return 0; +} + +int method_raisebrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + verse = 1; + return method_setbrightness(m, userdata, ret_error); +} + +int method_lowerbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + verse = -1; + return method_setbrightness(m, userdata, ret_error); +} diff --git a/src/modules/dpms.c b/src/modules/dpms.c index 467e0a7..e58bb8b 100644 --- a/src/modules/dpms.c +++ b/src/modules/dpms.c @@ -133,7 +133,7 @@ static int method_setdpms(sd_bus_message *m, void *userdata, sd_bus_error *ret_e const char *display = NULL, *env = NULL; int level; - ASSERT_AUTH(); + ASSERT_AUTH(); /* Read the parameters */ int r = sd_bus_message_read(m, "ssi", &display, &env, &level); diff --git a/src/modules/gamma.c b/src/modules/gamma.c index 4293546..e52bde6 100644 --- a/src/modules/gamma.c +++ b/src/modules/gamma.c @@ -246,7 +246,6 @@ static void client_dtor(void *c) { if (cl->plugin) { cl->plugin->dtor(cl->priv); } - free(cl->priv); free((char *)cl->display); free((char *)cl->env); free(cl); @@ -376,7 +375,7 @@ static int start_client(gamma_client *cl, int temp, bool is_smooth, unsigned int // NOTE: it seems like on wayland smooth transitions are not working. // Forcefully disable them for now. - if (cl->is_smooth && cl->plugin == plugins[WL]) { + if (cl->plugin == plugins[WL] && cl->is_smooth) { fprintf(stderr, "Smooth transitions are not supported on wayland.\n"); cl->is_smooth = false; } diff --git a/src/modules/gamma.h b/src/modules/gamma.h index 87c8d90..07f793a 100644 --- a/src/modules/gamma.h +++ b/src/modules/gamma.h @@ -40,7 +40,7 @@ typedef struct _gamma_plugin { int (*validate)(const char **id, const char *env, void **priv_data); int (*set)(void *priv_data, const int temp); int (*get)(void *priv_data); - int (*dtor)(void *priv_data); + void (*dtor)(void *priv_data); char obj_path[100]; } gamma_plugin; @@ -48,7 +48,7 @@ typedef struct _gamma_plugin { static int validate(const char **id, const char *env, void **priv_data); \ static int set(void *priv_data, const int temp); \ static int get(void *priv_data); \ - static int dtor(void *priv_data); \ + static void dtor(void *priv_data); \ static void _ctor_ register_gamma_plugin(void) { \ static gamma_plugin self = { name, validate, set, get, dtor }; \ gamma_register_new(&self); \ diff --git a/src/modules/gamma_plugins/drm.c b/src/modules/gamma_plugins/drm.c index 0f6e455..c5c091b 100644 --- a/src/modules/gamma_plugins/drm.c +++ b/src/modules/gamma_plugins/drm.c @@ -109,8 +109,9 @@ static int get(void *priv_data) { return temp; } -static int dtor(void *priv_data) { +static void dtor(void *priv_data) { drm_gamma_priv *priv = (drm_gamma_priv *)priv_data; drmModeFreeResources(priv->res); - return close(priv->fd); + close(priv->fd); + free(priv_data); } diff --git a/src/modules/gamma_plugins/wl.c b/src/modules/gamma_plugins/wl.c index 75e08a4..1a951b5 100644 --- a/src/modules/gamma_plugins/wl.c +++ b/src/modules/gamma_plugins/wl.c @@ -1,6 +1,9 @@ +#define USE_STACK_T // this is needed for a bug in libmodule: stack_t type is already declared in signal.h + #include "wlr-gamma-control-unstable-v1-client-protocol.h" #include "gamma.h" #include "wl_utils.h" +#include struct output { struct wl_output *wl_output; @@ -16,6 +19,7 @@ typedef struct { struct wl_list outputs; struct wl_registry *registry; struct zwlr_gamma_control_manager_v1 *gamma_control_manager; + bool not_first_time; } wlr_gamma_priv; static int create_gamma_table(uint32_t ramp_size, uint16_t **table); @@ -39,20 +43,61 @@ static const struct wl_registry_listener registry_listener = { .global_remove = registry_handle_global_remove, }; +static stack_t *clients; +static bool leaving; + GAMMA("Wl"); +MODULE("GAMMASWAY"); + +static bool check(void) { + return true; +} + +static bool evaluate(void) { + return true; +} + +static void init(void) { + clients = stack_new(dtor); +} + +static void receive(const msg_t *msg, const void *userdata) { + if (msg && !msg->is_pubsub) { + wlr_gamma_priv *priv = (wlr_gamma_priv *)msg->fd_msg->userptr; + wl_display_dispatch(priv->dpy); + } +} + +static void destroy(void) { + leaving = true; // notify dtor that all clients must be destroyed + stack_free(clients); +} + static int validate(const char **id, const char *env, void **priv_data) { struct wl_display *display = fetch_wl_display(*id, env); if (display == NULL) { return WRONG_PLUGIN; } + /* Check if we already have a running client for this display */ + for (stack_itr_t *itr = stack_itr_new(clients); itr; itr = stack_itr_next(itr)) { + wlr_gamma_priv *p = (wlr_gamma_priv *)stack_itr_get_data(itr); + if (p->dpy == display) { + *priv_data = p; + // this is not the first time we use this client. + // No need to register its fd + p->not_first_time = true; + free(itr); + return 0; + } + } + int ret = UNSUPPORTED; /* init private data */ *priv_data = calloc(1, sizeof(wlr_gamma_priv)); wlr_gamma_priv *priv = (wlr_gamma_priv *)*priv_data; if (!priv) { - wl_display_disconnect(display); return -ENOMEM; } @@ -102,6 +147,7 @@ static int set(void *priv_data, const int temp) { struct output *output; wl_list_for_each(output, &priv->outputs, link) { + lseek(output->table_fd, 0, SEEK_SET); uint16_t *r = output->table; uint16_t *g = output->table + output->ramp_size; uint16_t *b = output->table + 2 * output->ramp_size; @@ -110,16 +156,38 @@ static int set(void *priv_data, const int temp) { output->table_fd); } wl_display_flush(priv->dpy); + // Register this fd and listen on events + if (!priv->not_first_time) { + m_register_fd(wl_display_get_fd(priv->dpy), false, priv); + stack_push(clients, priv); + } return 0; } static int get(void *priv_data) { // Unsupported ? - return -1; + // Ok anyway, just return 0; + // It will be supported one day hopefully + return 0; } -static int dtor(void *priv_data) { +static void dtor(void *priv_data) { + if (!leaving) { + /* Check if we already have a running client for this display */ + for (stack_itr_t *itr = stack_itr_new(clients); itr; itr = stack_itr_next(itr)) { + wlr_gamma_priv *p = (wlr_gamma_priv *)stack_itr_get_data(itr); + if (p == priv_data) { + // do not remove this client as we need to keep it alive; + free(itr); + return; + } + } + } + wlr_gamma_priv *priv = (wlr_gamma_priv *)priv_data; + if (priv->dpy) { + m_deregister_fd(wl_display_get_fd(priv->dpy)); + } struct output *output, *tmp_output; wl_list_for_each_safe(output, tmp_output, &priv->outputs, link) { wl_list_remove(&output->link); @@ -131,10 +199,10 @@ static int dtor(void *priv_data) { if (priv->gamma_control_manager) { zwlr_gamma_control_manager_v1_destroy(priv->gamma_control_manager); } + free(priv_data); // NOTE: dpy is disconnected on program exit to workaround // gamma protocol limitation that resets gamma as soon as display is disconnected. // See wl_utils.c - return 0; } static int create_gamma_table(uint32_t ramp_size, uint16_t **table) { diff --git a/src/modules/gamma_plugins/xorg.c b/src/modules/gamma_plugins/xorg.c index 735d698..ab83b42 100644 --- a/src/modules/gamma_plugins/xorg.c +++ b/src/modules/gamma_plugins/xorg.c @@ -63,8 +63,9 @@ static int get(void *priv_data) { return temp; } -static int dtor(void *priv_data) { +static void dtor(void *priv_data) { xorg_gamma_priv *priv = (xorg_gamma_priv *)priv_data; XRRFreeScreenResources(priv->res); - return XCloseDisplay(priv->dpy); + XCloseDisplay(priv->dpy); + free(priv_data); } diff --git a/src/modules/keyboard.c b/src/modules/keyboard.c index fa51e14..c5816da 100644 --- a/src/modules/keyboard.c +++ b/src/modules/keyboard.c @@ -1,18 +1,21 @@ -#include #include #include #include -#include #include #define KBD_SUBSYSTEM "leds" #define KBD_SYSNAME_MATCH "kbd_backlight" +#define WITH_KBD_DEVICE(k, fn) \ + struct udev_device *kbd_dev = udev_device_new_from_subsystem_sysname(udev, KBD_SUBSYSTEM, k->sysname); \ + fn; \ + udev_device_unref(kbd_dev); + typedef struct { int max; char obj_path[100]; sd_bus_slot *slot; // vtable's slot - struct udev_device *dev; + const char *sysname; } kbd_t; static void dtor_kbd(void *data); @@ -20,10 +23,13 @@ static int kbd_new(struct udev_device *dev, void *userdata); static inline int set_value(kbd_t *k, const char *sysattr, int value); static map_ret_code set_brightness(void *userdata, const char *key, void *data); static map_ret_code set_timeout(void *userdata, const char *key, void *data); +static map_ret_code append_backlight(void *userdata, const char *key, void *data); +static map_ret_code append_timeout(void *userdata, const char *key, void *data); static int method_setkeyboard(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); static int method_getkeyboard(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); static int method_settimeout(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); static int method_gettimeout(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +static int fetch_timeout(kbd_t *k); MODULE("KEYBOARD"); @@ -32,7 +38,9 @@ static const char main_interface[] = "org.clightd.clightd.KbdBacklight"; static const sd_bus_vtable main_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("Set", "d", "b", method_setkeyboard, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Get", NULL, "a(sd)", method_getkeyboard, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("SetTimeout", "i", "b", method_settimeout, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetTimeout", NULL, "a(si)", method_gettimeout, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_SIGNAL("Changed", "sd", 0), SD_BUS_VTABLE_END }; @@ -52,7 +60,7 @@ static map_t *kbds; static struct udev_monitor *mon; static void module_pre_start(void) { - + } static bool check(void) { @@ -65,6 +73,7 @@ static bool evaluate(void) { static void init(void) { kbds = map_new(true, dtor_kbd); + sd_bus_add_object_manager(bus, NULL, object_path); int r = sd_bus_add_object_vtable(bus, NULL, object_path, @@ -93,16 +102,22 @@ static void receive(const msg_t *msg, const void *userdata) { if (strstr(key, KBD_SYSNAME_MATCH)) { const char *action = udev_device_get_action(dev); if (action) { + kbd_t *k = map_get(kbds, key); if (!strcmp(action, UDEV_ACTION_ADD)) { - // Register new interface - kbd_new(dev, NULL); + if (!k) { + kbd_new(dev, &k); + if (k) { + sd_bus_emit_object_added(bus, k->obj_path); + } + } } else if (!strcmp(action, UDEV_ACTION_RM)) { - // Remove the interface - map_remove(kbds, key); + if (k) { + sd_bus_emit_object_removed(bus, k->obj_path); + map_remove(kbds, key); + } } else if (!strcmp(action, UDEV_ACTION_CHANGE)) { // Changed event! - /* Note: it seems like "change" udev signal is never triggered for kbd backlight though */ - kbd_t *k = map_get(kbds, key); + /* Note: it seems like "change" udev signal is never triggered for kbd backlight though */ if (k) { int curr = atoi(udev_device_get_sysattr_value(dev, "brightness")); const double pct = (double)curr / k->max; @@ -126,7 +141,6 @@ static void destroy(void) { static void dtor_kbd(void *data) { kbd_t *k = (kbd_t *)data; sd_bus_slot_unref(k->slot); - udev_device_unref(k->dev); free(k); } @@ -138,15 +152,13 @@ static int kbd_new(struct udev_device *dev, void *userdata) { } k->max = atoi(udev_device_get_sysattr_value(dev, "max_brightness")); - k->dev = udev_device_ref(dev); - - const char *name = udev_device_get_sysname(dev); + k->sysname = strdup(udev_device_get_sysname(dev)); /* * Substitute wrong chars, eg: dell::kbd_backlight -> dell__kbd_backlight * See spec: https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-object-path */ - snprintf(k->obj_path, sizeof(k->obj_path) - 1, "%s/%s", object_path, name); + snprintf(k->obj_path, sizeof(k->obj_path) - 1, "%s/%s", object_path, k->sysname); char *ptr = NULL; while ((ptr = strchr(k->obj_path, ':'))) { *ptr = '_'; @@ -157,7 +169,12 @@ static int kbd_new(struct udev_device *dev, void *userdata) { m_log("Failed to add object vtable on path '%s': %d\n", k->obj_path, r); dtor_kbd(k); } else { - map_put(kbds, name, k); + map_put(kbds, k->sysname, k); + } + + if (userdata) { + kbd_t **ktmp = (kbd_t **)userdata; + *ktmp = k; } return 0; } @@ -165,7 +182,11 @@ static int kbd_new(struct udev_device *dev, void *userdata) { static inline int set_value(kbd_t *k, const char *sysattr, int value) { char val[15] = {0}; snprintf(val, sizeof(val) - 1, "%d", value); - return udev_device_set_sysattr_value(k->dev, sysattr, val); + int ret; + WITH_KBD_DEVICE(k, { + ret = udev_device_set_sysattr_value(kbd_dev, sysattr, val); + }); + return ret; } static map_ret_code set_brightness(void *userdata, const char *key, void *data) { @@ -174,7 +195,7 @@ static map_ret_code set_brightness(void *userdata, const char *key, void *data) if (set_value(k, "brightness", (int)round(target_pct * k->max)) >= 0) { // Emit on global object - sd_bus_emit_signal(bus, object_path, main_interface, "Changed", "sd", udev_device_get_sysname(k->dev), target_pct); + sd_bus_emit_signal(bus, object_path, main_interface, "Changed", "sd", k->sysname, target_pct); // Emit on specific object too! sd_bus_emit_signal(bus, k->obj_path, bus_interface, "Changed", "d", target_pct); return MAP_OK; @@ -214,13 +235,45 @@ static int method_setkeyboard(sd_bus_message *m, void *userdata, sd_bus_error *r static int method_getkeyboard(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { kbd_t *k = (kbd_t *)userdata; - int curr = atoi(udev_device_get_sysattr_value(k->dev, "brightness")); + if (k) { + int curr; + WITH_KBD_DEVICE(k, { + curr = atoi(udev_device_get_sysattr_value(kbd_dev, "brightness")); + }); + const double pct = (double)curr / k->max; + return sd_bus_reply_method_return(m, "d", pct); + } + sd_bus_message *reply = NULL; + sd_bus_message_new_method_return(m, &reply); + sd_bus_message_open_container(reply, SD_BUS_TYPE_ARRAY, "(sd)"); + int r = map_iterate(kbds, append_backlight, reply); + sd_bus_message_close_container(reply); + if (r == 0) { + r = sd_bus_send(NULL, reply, NULL); + } + sd_bus_message_unref(reply); + return r; +} + +static map_ret_code append_backlight(void *userdata, const char *key, void *data) { + sd_bus_message *reply = (sd_bus_message *)userdata; + + kbd_t *k = (kbd_t *)data; + int curr; + WITH_KBD_DEVICE(k, { + curr = atoi(udev_device_get_sysattr_value(kbd_dev, "brightness")); + }); const double pct = (double)curr / k->max; - return sd_bus_reply_method_return(m, "d", pct); + + sd_bus_message_open_container(reply, SD_BUS_TYPE_STRUCT, "sd"); + sd_bus_message_append(reply, "sd", key, pct); + sd_bus_message_close_container(reply); + + return MAP_OK; } static int method_settimeout(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { -// ASSERT_AUTH(); + ASSERT_AUTH(); int timeout; int r = sd_bus_message_read(m, "i", &timeout); @@ -242,7 +295,30 @@ static int method_settimeout(sd_bus_message *m, void *userdata, sd_bus_error *re static int method_gettimeout(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { kbd_t *k = (kbd_t *)userdata; - const char *timeout = udev_device_get_sysattr_value(k->dev, "stop_timeout"); + if (k) { + int tm = fetch_timeout(k); + if (tm >= 0) { + return sd_bus_reply_method_return(m, "i", tm); + } + return sd_bus_error_set_errno(ret_error, -tm); + } + sd_bus_message *reply = NULL; + sd_bus_message_new_method_return(m, &reply); + sd_bus_message_open_container(reply, SD_BUS_TYPE_ARRAY, "(si)"); + int r = map_iterate(kbds, append_timeout, reply); + sd_bus_message_close_container(reply); + if (r == 0) { + r = sd_bus_send(NULL, reply, NULL); + } + sd_bus_message_unref(reply); + return r; +} + +static int fetch_timeout(kbd_t *k) { + const char *timeout; + WITH_KBD_DEVICE(k, { + timeout = udev_device_get_sysattr_value(kbd_dev, "stop_timeout"); + }); if (timeout) { int tm; char suffix = 's'; @@ -256,9 +332,22 @@ static int method_gettimeout(sd_bus_message *m, void *userdata, sd_bus_error *re break; } } - return sd_bus_reply_method_return(m, "i", tm); + return tm; } - return sd_bus_error_set_errno(ret_error, EINVAL); + return -EINVAL; + } + return -ENOENT; +} + +static map_ret_code append_timeout(void *userdata, const char *key, void *data) { + sd_bus_message *reply = (sd_bus_message *)userdata; + + kbd_t *k = (kbd_t *)data; + int tm = fetch_timeout(k); + if (tm >= 0) { + sd_bus_message_open_container(reply, SD_BUS_TYPE_STRUCT, "si"); + sd_bus_message_append(reply, "si", key, tm); + sd_bus_message_close_container(reply); } - return sd_bus_error_set_errno(ret_error, ENOENT); + return MAP_OK; } diff --git a/src/modules/screen_plugins/wl.c b/src/modules/screen_plugins/wl.c index 070fe44..6737ced 100644 --- a/src/modules/screen_plugins/wl.c +++ b/src/modules/screen_plugins/wl.c @@ -55,8 +55,6 @@ static int get_frame_brightness(const char *id, const char *env) { registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener, NULL); - - wl_display_dispatch(display); wl_display_roundtrip(display); if (screencopy_manager == NULL) { diff --git a/src/modules/sensor.c b/src/modules/sensor.c index e2de721..3e0456d 100644 --- a/src/modules/sensor.c +++ b/src/modules/sensor.c @@ -44,14 +44,20 @@ static void init(void) { NULL); for (int i = 0; i < SENSOR_NUM && !r; i++) { if (sensors[i]) { - snprintf(sensors[i]->obj_path, sizeof(sensors[i]->obj_path) - 1, "%s/%s", object_path, sensors[i]->name); - r += sd_bus_add_object_vtable(bus, - NULL, - sensors[i]->obj_path, - bus_interface, - vtable, - sensors[i]); - r += m_register_fd(sensors[i]->init_monitor(), false, sensors[i]); + int fd = sensors[i]->init_monitor(); + if (fd != -1) { + snprintf(sensors[i]->obj_path, sizeof(sensors[i]->obj_path) - 1, "%s/%s", object_path, sensors[i]->name); + r += sd_bus_add_object_vtable(bus, + NULL, + sensors[i]->obj_path, + bus_interface, + vtable, + sensors[i]); + r += m_register_fd(fd, false, sensors[i]); + } else { + fprintf(stderr, "Sensor '%s' unsupported.\n", sensors[i]->name); + sensors[i] = NULL; + } } } if (r < 0) { @@ -69,9 +75,12 @@ static void receive(const msg_t *msg, const void *userdata) { const char *action = NULL; sensor->fetch_props_dev(dev, &node, &action); - sd_bus_emit_signal(bus, sensor->obj_path, bus_interface, "Changed", "ss", node, action); - /* Changed is emitted on Sensor main object too */ - sd_bus_emit_signal(bus, object_path, bus_interface, "Changed", "ss", node, action); + if (strcmp(action, UDEV_ACTION_ADD) == 0 || + strcmp(action, UDEV_ACTION_RM) == 0) { + sd_bus_emit_signal(bus, sensor->obj_path, bus_interface, "Changed", "ss", node, action); + /* Changed is emitted on Sensor main object too */ + sd_bus_emit_signal(bus, object_path, bus_interface, "Changed", "ss", node, action); + } sensor->destroy_dev(dev); } } diff --git a/src/modules/sensor.h b/src/modules/sensor.h index 0cb8066..77e97cb 100644 --- a/src/modules/sensor.h +++ b/src/modules/sensor.h @@ -26,7 +26,7 @@ #define _SENSORS \ X(ALS) \ X(YOCTOLIGHT) \ - X(CAMERA_PW) \ + X(PIPEWIRE) \ X(CAMERA) \ X(CUSTOM) diff --git a/src/modules/sensors/als.c b/src/modules/sensors/als.c index 2d3fab1..016ad1c 100644 --- a/src/modules/sensors/als.c +++ b/src/modules/sensors/als.c @@ -74,19 +74,17 @@ static int capture(void *dev, double *pct, const int num_captures, char *setting } for (int i = 0; i < num_captures; i++) { + struct udev_device *non_cached_dev = udev_device_new_from_syspath(udev, udev_device_get_syspath(dev)); double illuminance = -1; - for (int i = 0; i < SIZE(ill_names) && illuminance == -1; i++) { - val = udev_device_get_sysattr_value(dev, ill_names[i]); + for (int j = 0; j < SIZE(ill_names) && illuminance == -1; j++) { + val = udev_device_get_sysattr_value(non_cached_dev, ill_names[j]); if (val) { illuminance = atof(val) * scale; + ctr++; + pct[i] = compute_value(illuminance); } } - - if (illuminance >= 1) { - ctr++; - pct[i] = compute_value(illuminance); - } - + udev_device_unref(non_cached_dev); usleep(interval * 1000); } return ctr; diff --git a/src/modules/sensors/camera.c b/src/modules/sensors/camera.c index dea6b28..c614e26 100644 --- a/src/modules/sensors/camera.c +++ b/src/modules/sensors/camera.c @@ -6,22 +6,50 @@ #include #define CAMERA_NAME "Camera" -#define CAMERA_ILL_MAX 255 #define CAMERA_SUBSYSTEM "video4linux" -#define HISTOGRAM_STEPS 40 #define SET_V4L2(id, val) set_v4l2_control(id, val, #id, true) #define SET_V4L2_DEF(id) set_v4l2_control_def(id, #id) -static const __u32 supported_fmts[] = { - V4L2_PIX_FMT_GREY, - V4L2_PIX_FMT_YUYV, - V4L2_PIX_FMT_MJPEG +struct buffer { + uint8_t *start; + size_t length; +}; + +struct mjpeg_dec { + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr err; + int (*dec_cb)(uint8_t **frame, int len); +}; + +typedef enum { X_AXIS, Y_AXIS, MAX_AXIS } crop_axis; +typedef enum { DISABLED, CROP_API, SELECTION_API, MANUAL} crop_type_t; + +typedef struct { + bool enabled; + double area_pct[2]; // start - end +} crop_info_t; + +struct state { + int device_fd; + uint32_t pixelformat; + uint32_t width; + uint32_t height; + crop_info_t crop[MAX_AXIS]; + crop_type_t crop_type; + struct buffer buf; + char *settings; + struct mjpeg_dec *decoder; + map_t *stored_values; }; static void set_v4l2_control_def(uint32_t id, const char *name); static void set_v4l2_control(uint32_t id, int32_t val, const char *name, bool store); static void set_camera_settings_def(void); +static void inline fill_crop_rect(crop_info_t *cr, struct v4l2_rect *rect); +static int set_selection(crop_info_t *cr); +static int set_crop(crop_info_t *cr); +static int try_set_crop(crop_info_t *crop); static void set_camera_settings(void); static void restore_camera_settings(void); static int set_camera_fmt(void); @@ -38,33 +66,13 @@ static int send_frame(struct v4l2_buffer *buf); static int recv_frame(struct v4l2_buffer *buf); static double compute_brightness(unsigned int size); -struct buffer { - uint8_t *start; - size_t length; -}; - -struct histogram { - double count; - double sum; -}; - -struct mjpeg_dec { - struct jpeg_decompress_struct cinfo; - struct jpeg_error_mgr err; - int (*dec_cb)(uint8_t **frame, int len); -}; - -struct state { - int device_fd; - uint32_t pixelformat; - struct buffer buf; - char *settings; - struct mjpeg_dec *decoder; - map_t *stored_values; -}; - static struct state state; static struct udev_monitor *mon; +static const __u32 supported_fmts[] = { + V4L2_PIX_FMT_GREY, + V4L2_PIX_FMT_YUYV, + V4L2_PIX_FMT_MJPEG +}; SENSOR(CAMERA_NAME); @@ -123,7 +131,7 @@ static int capture(void *dev, double *pct, const int num_captures, char *setting set_camera_settings(); create_decoder(); for (int i = 0; i < num_captures; i++) { - struct v4l2_buffer buf = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .memory = V4L2_MEMORY_MMAP}; + struct v4l2_buffer buf = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .memory = V4L2_MEMORY_MMAP}; if (send_frame(&buf) == 0 && recv_frame(&buf) == 0) { pct[ctr++] = compute_brightness(buf.bytesused); } @@ -198,23 +206,130 @@ static void set_camera_settings_def(void) { SET_V4L2_DEF(V4L2_CID_BRIGHTNESS); } +static void inline fill_crop_rect(crop_info_t *cr, struct v4l2_rect *rect) { + if (state.crop[Y_AXIS].enabled) { + const double *a_pct = cr[Y_AXIS].area_pct; + const double height_pct = a_pct[1] - a_pct[0]; + rect->height = height_pct * state.height; + rect->top = a_pct[0] * state.height; + } + if (state.crop[X_AXIS].enabled) { + const double *a_pct = cr[X_AXIS].area_pct; + const double width_pct = a_pct[1] - a_pct[0]; + rect->width = width_pct * state.width; + rect->left = a_pct[0] * state.width; + } +} + +// https://www.kernel.org/doc/html/v4.12/media/uapi/v4l/vidioc-g-selection.html +static int set_selection(crop_info_t *cr) { + struct v4l2_selection selection = {0}; + selection.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + selection.target = V4L2_SEL_TGT_CROP; + if (-1 == xioctl(VIDIOC_G_SELECTION, &selection)) { + INFO("VIDIOC_G_SELECTION failed: %m\n"); + return -errno; + } + if (cr) { + fill_crop_rect(cr, &selection.r); + } else { + // Reset default + selection.target = V4L2_SEL_TGT_CROP_DEFAULT; + } + if (-1 == xioctl(VIDIOC_S_SELECTION, &selection)) { + INFO("VIDIOC_S_SELECTION failed: %m\n"); + return -errno; + } + state.crop_type = SELECTION_API; + return 0; +} + +// https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/crop.html +static int set_crop(crop_info_t *cr) { + struct v4l2_crop crop = {0}; + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(VIDIOC_G_CROP, &crop)) { + INFO("VIDIOC_G_CROP failed: %m\n"); + return -errno; + } + + if (cr) { + fill_crop_rect(cr, &crop.c); + } else { + // Reset default + struct v4l2_cropcap cropcap = {0}; + cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(VIDIOC_CROPCAP, &cropcap)) { + INFO("VIDIOC_CROPCAP failed: %m\n"); + return -errno; + } + crop.c = cropcap.defrect; + } + if (-1 == xioctl(VIDIOC_S_CROP, &crop)) { + INFO("VIDIOC_S_CROP failed: %m\n"); + return -errno; + } + state.crop_type = CROP_API; + return 0; +} + +static int try_set_crop(crop_info_t *crop) { + /** Try "new" selection API **/ + if (set_selection(crop) != 0) { + /** Try "old" crop API **/ + return set_crop(crop); + } + return 0; +} + /* Parse settings string! */ static void set_camera_settings(void) { /* Set default values */ set_camera_settings_def(); if (state.settings && strlen(state.settings)) { char *token; - char *rest = state.settings; - + char *rest = state.settings; + while ((token = strtok_r(rest, ",", &rest))) { uint32_t v4l2_op; int32_t v4l2_val; + char axis; + double area_pct[2]; + if (sscanf(token, "%u=%d", &v4l2_op, &v4l2_val) == 2) { SET_V4L2(v4l2_op, v4l2_val); + } else if (sscanf(token, "%c=%lf-%lf", &axis, &area_pct[0], &area_pct[1]) == 3) { + int8_t crop_idx = -1; + if (area_pct[0] >= area_pct[1]) { + fprintf(stderr, "Start should be lesser than end: %lf-%lf\n", area_pct[0], area_pct[1]); + } else { + switch (axis) { + case 'x': + crop_idx = X_AXIS; + break; + case 'y': + crop_idx = Y_AXIS; + break; + default: + fprintf(stderr, "wrong axis specified: %c; 'x' or 'y' supported.\n", axis); + break; + } + } + if (crop_idx != -1 && !state.crop[crop_idx].enabled) { + state.crop[crop_idx].enabled = true; + state.crop[crop_idx].area_pct[0] = area_pct[0]; + state.crop[crop_idx].area_pct[1] = area_pct[1]; + } } else { fprintf(stderr, "Expected a=b format in '%s' token.\n", token); } } + if (state.crop[X_AXIS].enabled || state.crop[Y_AXIS].enabled) { + if (try_set_crop(state.crop) != 0) { + INFO("Unsupported crop/selection v4l2 API; fallback at manually skipping pixels.\n") + state.crop_type = MANUAL; + } + } } } @@ -225,6 +340,18 @@ static void restore_camera_settings(void) { INFO("Restoring setting for '%s'\n", ctrl_name) set_v4l2_control(old_ctrl->id, old_ctrl->value, ctrl_name, false); } + + // Restore crop if needed + switch (state.crop_type) { + case SELECTION_API: + set_selection(NULL); + break; + case CROP_API: + set_crop(NULL); + break; + default: + break; + } } static int set_camera_fmt(void) { @@ -241,6 +368,8 @@ static int set_camera_fmt(void) { INFO("Image fmt: %s\n", (char *)&fmt.fmt.pix.pixelformat); INFO("Image res: %d x %d\n", fmt.fmt.pix.width, fmt.fmt.pix.height); + state.height = fmt.fmt.pix.height; + state.width = fmt.fmt.pix.width; return 0; } @@ -269,12 +398,6 @@ static int check_camera_caps(void) { INFO("Failed to set priority\n"); } - struct v4l2_format fmt = {0}; - fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - fmt.fmt.pix.width = 160; - fmt.fmt.pix.height = 120; - fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; - /* Check supported formats */ struct v4l2_fmtdesc fmtdesc = {0}; fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; @@ -418,7 +541,7 @@ static int send_frame(struct v4l2_buffer *buf) { return 0; } -static int recv_frame(struct v4l2_buffer *buf) { +static int recv_frame(struct v4l2_buffer *buf) { /* Dequeue the buffer */ if(-1 == xioctl(VIDIOC_DQBUF, buf)) { perror("VIDIOC_DQBUF"); @@ -429,7 +552,6 @@ static int recv_frame(struct v4l2_buffer *buf) { static double compute_brightness(unsigned int size) { double brightness = 0.0; - uint8_t *img_data = state.buf.start; if (state.decoder) { size = state.decoder->dec_cb(&img_data, size); @@ -437,94 +559,37 @@ static double compute_brightness(unsigned int size) { return brightness; } } - - brightness = get_frame_brightness(img_data, size, (state.pixelformat == V4L2_PIX_FMT_YUYV)); - if (img_data != state.buf.start) { - free(img_data); - } - return brightness; -} - -double get_frame_brightness(uint8_t *img_data, size_t size, bool is_yuv) { - double brightness = 0.0; - double min = CAMERA_ILL_MAX; - double max = 0.0; + rect_info_t full = { + .row_start = 0, + .row_end = state.height, + .col_start = 0, + .col_end = state.width, + }; - /* - * If greyscale (rgb is converted to grey) -> increment by 1. - * If YUYV -> increment by 2: we only want Y! - */ - const int inc = 1 + is_yuv; - const double total = size / inc; - - /* Find minimum and maximum brightness */ - for (int i = 0; i < size; i += inc) { - if (img_data[i] < min) { - min = img_data[i]; - } - if (img_data[i] > max) { - max = img_data[i]; - } - } - - /* Ok, we should never get in here */ - if (max == 0.0) { - return brightness; - } - - /* Calculate histogram */ - struct histogram hist[HISTOGRAM_STEPS] = {0}; - const double step_size = (max - min) / HISTOGRAM_STEPS; - for (int i = 0; i < size; i += inc) { - int bucket = (img_data[i] - min) / step_size; - if (bucket >= 0 && bucket < HISTOGRAM_STEPS) { - hist[bucket].sum += img_data[i]; - hist[bucket].count++; - } - } - - /* Find (approximate) quartiles for histogram steps */ - const double quartile_size = total / 4; - double quartiles[3] = {0.0, 0.0, 0.0}; - int j = 0; - for (int i = 0; i < HISTOGRAM_STEPS && j < 3; i++) { - quartiles[j] += hist[i].count; - if (quartiles[j] >= quartile_size) { - quartiles[j] = (quartile_size / quartiles[j]) + i; - j++; - } - } - - /* - * Results may be clustered in a single estimated quartile, - * in which case consider full range. - */ - int min_bucket = 0; - int max_bucket = HISTOGRAM_STEPS - 1; - if (quartiles[2] > quartiles[0]) { - /* Trim outlier buckets via interquartile range */ - const double iqr = (quartiles[2] - quartiles[0]) * 1.5; - min_bucket = quartiles[0] - iqr; - max_bucket = quartiles[2] + iqr; - if (min_bucket < 0) { - min_bucket = 0; + if (state.crop_type == MANUAL) { + // Manual crop if needed + rect_info_t crop = { + .row_start = 0, + .row_end = state.height, + .col_start = 0, + .col_end = state.width, + }; + if (state.crop[X_AXIS].enabled) { + crop.col_start = state.crop[X_AXIS].area_pct[0] * state.width; + crop.col_end = state.crop[X_AXIS].area_pct[1] * state.width; } - if (max_bucket > HISTOGRAM_STEPS - 1) { - max_bucket = HISTOGRAM_STEPS - 1; + if (state.crop[Y_AXIS].enabled) { + crop.row_start = state.crop[Y_AXIS].area_pct[0] * state.height; + crop.row_end = state.crop[Y_AXIS].area_pct[1] * state.height; } + INFO("Manual crop: rows[%d-%d], cols[%d-%d]\n", crop.row_start, crop.row_end, crop.col_start, crop.col_end); + brightness = get_frame_brightness(img_data, &crop, &full, (state.pixelformat == V4L2_PIX_FMT_YUYV)); + } else { + brightness = get_frame_brightness(img_data, NULL, &full, (state.pixelformat == V4L2_PIX_FMT_YUYV)); } - - /* - * Find the highest brightness bucket with - * a significant sample count - * and return the average brightness for it. - */ - for (int i = max_bucket; i >= min_bucket; i--) { - if (hist[i].count > step_size) { - brightness = hist[i].sum / hist[i].count; - break; - } + if (img_data != state.buf.start) { + free(img_data); } - return (double)brightness / CAMERA_ILL_MAX; + return brightness; } diff --git a/src/modules/sensors/camera.h b/src/modules/sensors/camera.h index aa28e35..b981d3e 100644 --- a/src/modules/sensors/camera.h +++ b/src/modules/sensors/camera.h @@ -9,4 +9,114 @@ #define INFO(fmt, ...) #endif -double get_frame_brightness(uint8_t *img_data, size_t size, bool is_yuv); +#define CAMERA_ILL_MAX 255 +#define HISTOGRAM_STEPS 40 + +typedef struct { + int row_start; + int row_end; + int col_start; + int col_end; +} rect_info_t; + +struct histogram { + double count; + double sum; +}; + +static double get_frame_brightness(uint8_t *img_data, rect_info_t *crop, rect_info_t *full, bool is_yuv) { + double brightness = 0.0; + double min = CAMERA_ILL_MAX; + double max = 0.0; + + /* + * If greyscale (rgb is converted to grey) -> increment by 1. + * If YUYV -> increment by 2: we only want Y! + */ + const int inc = 1 + is_yuv; + + // If no crop is requested, just go with full image + if (crop == NULL) { + crop = full; + } + + /* Find minimum and maximum brightness */ + int total = 0; // compute total used pixels + for (int row = crop->row_start; row < crop->row_end; row++) { + for (int col = crop->col_start; col < crop->col_end * inc; col += inc) { + const int idx = (row * full->col_end * inc) + col; + if (img_data[idx] < min) { + min = img_data[idx]; + } + if (img_data[idx] > max) { + max = img_data[idx]; + } + total++; + } + } + INFO("Total computed pixels: %d\n", total); + + /* Ok, we should never get in here */ + if (max == 0.0) { + return brightness; + } + + /* Calculate histogram */ + struct histogram hist[HISTOGRAM_STEPS] = {0}; + const double step_size = (max - min) / HISTOGRAM_STEPS; + for (int row = crop->row_start; row < crop->row_end; row++) { + for (int col = crop->col_start; col < crop->col_end * inc; col += inc) { + const int idx = (row * full->col_end * inc) + col; + int bucket = (img_data[idx] - min) / step_size; + if (bucket >= 0 && bucket < HISTOGRAM_STEPS) { + hist[bucket].sum += img_data[idx]; + hist[bucket].count++; + } + } + } + + /* Find (approximate) quartiles for histogram steps */ + const double quartile_size = (double)total / 4; + double quartiles[3] = {0}; + int j = 0; + for (int i = 0; i < HISTOGRAM_STEPS && j < 3; i++) { + quartiles[j] += hist[i].count; + if (quartiles[j] >= quartile_size) { + quartiles[j] = (quartile_size / quartiles[j]) + i; + j++; + } + } + + /* + * Results may be clustered in a single estimated quartile, + * in which case consider full range. + */ + int min_bucket = 0; + int max_bucket = HISTOGRAM_STEPS - 1; + if (quartiles[2] > quartiles[0]) { + /* Trim outlier buckets via interquartile range */ + const double iqr = (quartiles[2] - quartiles[0]) * 1.5; + min_bucket = quartiles[0] - iqr; + max_bucket = quartiles[2] + iqr; + if (min_bucket < 0) { + min_bucket = 0; + } + if (max_bucket > HISTOGRAM_STEPS - 1) { + max_bucket = HISTOGRAM_STEPS - 1; + } + } + + /* + * Find the highest brightness bucket with + * a significant sample count + * and return the average brightness for it. + */ + for (int i = max_bucket; i >= min_bucket; i--) { + if (hist[i].count > step_size) { + brightness = hist[i].sum / hist[i].count; + break; + } + } + return (double)brightness / CAMERA_ILL_MAX; +} + diff --git a/src/modules/sensors/custom.c b/src/modules/sensors/custom.c index 6622ed2..c45d53c 100644 --- a/src/modules/sensors/custom.c +++ b/src/modules/sensors/custom.c @@ -57,7 +57,7 @@ static void fetch_props_dev(void *dev, const char **node, const char **action) { } if (action) { - static const char *actions[] = { "Removed", "Added" }; + static const char *actions[] = { UDEV_ACTION_RM, UDEV_ACTION_ADD }; int idx = access(dev, F_OK) == 0; *action = actions[idx]; diff --git a/src/modules/sensors/pipewire.c b/src/modules/sensors/pipewire.c index f118a35..8df8c3d 100644 --- a/src/modules/sensors/pipewire.c +++ b/src/modules/sensors/pipewire.c @@ -1,14 +1,13 @@ #ifdef PIPEWIRE_PRESENT #include "camera.h" - +#include #include #include #include - #include -#define PW_NAME "Camera_PW" +#define PW_NAME "Pipewire" typedef struct { uint32_t id; @@ -51,6 +50,7 @@ static pw_mon_t pw_mon; static pw_data_t *last_recved; static pw_data_t **nodes; static int nodes_len; +static int uid; static void _ctor_ init_libpipewire(void) { pw_init(NULL, NULL); @@ -60,91 +60,123 @@ static void _dtor_ destroy_libpipewire(void) { pw_deinit(); } +static void set_env() { + char path[64]; + snprintf(path, sizeof(path), "/run/user/%d", uid); + setenv("XDG_RUNTIME_DIR", path, 1);; +} + static void on_process(void *_data) { - pw_data_t *data = _data; + printf("kek\n"); + pw_data_t *pw = _data; - struct pw_buffer *b; - if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { + struct pw_buffer *b = pw_stream_dequeue_buffer(pw->stream); + if (b == NULL) { fprintf(stderr, "out of buffers: %m"); goto err; } - - const bool is_yuv = data->format.info.raw.format == SPA_VIDEO_FORMAT_YUY2; + + const bool is_yuv = pw->format.info.raw.format == SPA_VIDEO_FORMAT_YUY2; struct spa_buffer *buf = b->buffer; - uint8_t *sdata; - if ((sdata = buf->datas[0].data) == NULL) { + + uint8_t *sdata = buf->datas[0].data; + if (sdata == NULL) { goto err; } - data->pct[data->capture_idx++] = get_frame_brightness(sdata, buf->datas[0].chunk->size, is_yuv); - pw_stream_queue_buffer(data->stream, b); + + rect_info_t full = { + .row_start = 0, + .row_end = pw->format.info.raw.size.height, + .col_start = 0, + .col_end = pw->format.info.raw.size.width, + }; + + pw->pct[pw->capture_idx++] = get_frame_brightness(sdata, NULL, &full, is_yuv); + pw_stream_queue_buffer(pw->stream, b); return; err: - data->with_err = true; + pw->with_err = true; } static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) { - pw_data_t *data = _data; - + pw_data_t *pw = _data; + if (param == NULL || id != SPA_PARAM_Format) { return; } - + if (spa_format_parse(param, - &data->format.media_type, - &data->format.media_subtype) < 0) { - data->with_err = true; + &pw->format.media_type, + &pw->format.media_subtype) < 0) { + + pw->with_err = true; return; } - - if (data->format.media_type != SPA_MEDIA_TYPE_video || - data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) { - data->with_err = true; + + if (pw->format.media_type != SPA_MEDIA_TYPE_video || + pw->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) { + + pw->with_err = true; return; } - - if (spa_format_video_raw_parse(param, &data->format.info.raw) < 0) { - data->with_err = true; + + if (spa_format_video_raw_parse(param, &pw->format.info.raw) < 0) { + pw->with_err = true; return; } } +static void on_stream_state_changed(void *_data, enum pw_stream_state old, + enum pw_stream_state state, const char *error) { + pw_data_t *pw = _data; + INFO("Stream state: \"%s\"\n", pw_stream_state_as_string(state)); + switch (state) { + case PW_STREAM_STATE_UNCONNECTED: + pw->with_err = true; + break; + case PW_STREAM_STATE_PAUSED: + /* because we started inactive, activate ourselves now */ + pw_stream_set_active(pw->stream, true); + break; + default: + break; + } +} + /* these are the stream events we listen for */ static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .param_changed = on_stream_param_changed, + .state_changed = on_stream_state_changed, .process = on_process, }; static void build_format(struct spa_pod_builder *b, const struct spa_pod **params) { *params = spa_pod_builder_add_object(b, - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(2, - SPA_VIDEO_FORMAT_GRAY8, // V4L2_PIX_FMT_GREY - SPA_VIDEO_FORMAT_YUY2), // V4L2_PIX_FMT_YUYV - SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( - &SPA_RECTANGLE(160, 120), - &SPA_RECTANGLE(1, 1), - &SPA_RECTANGLE(640, 480)), - SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction( - &SPA_FRACTION(25, 1), - &SPA_FRACTION(0, 1), - &SPA_FRACTION(120, 1))); + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(2, + SPA_VIDEO_FORMAT_GRAY8, // V4L2_PIX_FMT_GREY + SPA_VIDEO_FORMAT_YUY2), // V4L2_PIX_FMT_YUYV + SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( + &SPA_RECTANGLE(160, 120), + &SPA_RECTANGLE(1, 1), + &SPA_RECTANGLE(640, 480)), + SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction( + &SPA_FRACTION(25, 1), + &SPA_FRACTION(0, 1), + &SPA_FRACTION(120, 1))); } static bool validate_dev(void *dev) { - pw_data_t *pw = (pw_data_t *)dev; - if (pw->stream) { - return pw_stream_set_active(pw->stream, true) == 0; - } return true; } static void fetch_dev(const char *interface, void **dev) { pw_data_t *pw = NULL; - if (interface) { + if (interface && strlen(interface)) { uint32_t id = atoi(interface); for (int i = 0; i < nodes_len && !pw; i++) { if (nodes[i]->node.id == id) { @@ -155,51 +187,47 @@ static void fetch_dev(const char *interface, void **dev) { pw = nodes[0]; } - // TODO error checking if (pw) { + set_env(); + const struct spa_pod *params; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - + pw->loop = pw_loop_new(NULL); - pw->stream = pw_stream_new_simple( - pw->loop, - "clightd-camera-pw", - pw_properties_new( - PW_KEY_MEDIA_TYPE, "Video", - PW_KEY_MEDIA_CATEGORY, "Capture", - PW_KEY_MEDIA_ROLE, "Camera", - NULL), - &stream_events, - pw); - + pw->stream = pw_stream_new_simple(pw->loop, + "clightd-camera-pw", + pw_properties_new( + PW_KEY_MEDIA_TYPE, "Video", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Camera", + NULL), + &stream_events, + pw); build_format(&b, ¶ms); - int res; - // FIXME: fix when correct device is passed ?? if ((res = pw_stream_connect(pw->stream, - PW_DIRECTION_INPUT, - /*pw->node.id*/PW_ID_ANY, - PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ - PW_STREAM_FLAG_INACTIVE | - PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */ - ¶ms, 1)) /* extra parameters, see above */ < 0) { - + PW_DIRECTION_INPUT, + pw->node.id, + PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ + PW_STREAM_FLAG_INACTIVE | + PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */ + ¶ms, 1)) /* extra parameters, see above */ < 0) { + fprintf(stderr, "can't connect: %s\n", spa_strerror(res)); free(pw); - return; + } else { + *dev = pw; } + unsetenv("XDG_RUNTIME_DIR"); } - *dev = pw; } static void fetch_props_dev(void *dev, const char **node, const char **action) { static char str_id[32]; pw_data_t *pw = (pw_data_t *)dev; - sprintf(str_id, "%d", pw->node.id); *node = str_id; - if (action) { *action = pw->node.action; } @@ -244,8 +272,9 @@ static const struct pw_proxy_events proxy_events = { }; static void registry_event_global(void *data, uint32_t id, - uint32_t permissions, const char *type, uint32_t version, - const struct spa_dict *props) { + uint32_t permissions, const char *type, uint32_t version, + const struct spa_dict *props) { + if (strcmp(type, PW_TYPE_INTERFACE_Node) == 0) { const char *mc = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); const char *mr = spa_dict_lookup(props, PW_KEY_MEDIA_ROLE); @@ -277,28 +306,45 @@ static const struct pw_registry_events registry_events = { }; static int init_monitor(void) { - // TODO... setenv... ?? - setenv("XDG_RUNTIME_DIR", "/run/user/1000", 1); + DIR *d = opendir("/run/user/"); + if (d) { + struct dirent *dir; + while ((dir = readdir(d)) != NULL) { + if (dir->d_type == DT_DIR) { + if (sscanf(dir->d_name, "%d", &uid) == 1) { + printf("Found user %d\n", uid); + break; + } + } + } + closedir(d); + } + + // Unsupported... can this happen? + if (uid == 0) { + return -1; + } + + set_env(); + pw_mon.mon_loop = pw_loop_new(NULL); pw_mon.context = pw_context_new(pw_mon.mon_loop, NULL, 0); - pw_mon.core = pw_context_connect(pw_mon.context, NULL, 0); - pw_mon.registry = pw_core_get_registry(pw_mon.core, PW_VERSION_REGISTRY, 0); - - spa_zero(pw_mon.registry_listener); - pw_registry_add_listener(pw_mon.registry, &pw_mon.registry_listener, ®istry_events, NULL); - + pw_mon.core = pw_context_connect(pw_mon.context, NULL, 0); + pw_mon.registry = pw_core_get_registry(pw_mon.core, PW_VERSION_REGISTRY, 0); + + spa_zero(pw_mon.registry_listener); + pw_registry_add_listener(pw_mon.registry, &pw_mon.registry_listener, ®istry_events, NULL); + int fd = pw_loop_get_fd(pw_mon.mon_loop); pw_loop_enter(pw_mon.mon_loop); + + unsetenv("XDG_RUNTIME_DIR"); return fd; } static void recv_monitor(void **dev) { last_recved = NULL; - while (pw_loop_iterate(pw_mon.mon_loop, -1) >= 0) { - if (last_recved) { - break; - } - } + pw_loop_iterate(pw_mon.mon_loop, 0); *dev = last_recved; } @@ -314,11 +360,13 @@ static int capture(void *dev, double *pct, const int num_captures, char *setting pw_data_t *pw = (pw_data_t *)dev; pw->pct = pct; pw->settings = settings; - pw->stored_values = map_new(true, free); - + if (!pw->stored_values) { + pw->stored_values = map_new(true, free); + } set_camera_settings(pw); pw_loop_enter(pw->loop); while (pw->capture_idx < num_captures && !pw->with_err) { + printf("top %d\n", pw->capture_idx); if (pw_loop_iterate(pw->loop, -1) < 0) { break; } @@ -331,24 +379,24 @@ static int capture(void *dev, double *pct, const int num_captures, char *setting // Stolen from https://github.com/PipeWire/pipewire/blob/master/spa/plugins/v4l2/v4l2-utils.c#L1017 static uint32_t control_to_prop_id(uint32_t control_id) { switch (control_id) { - case V4L2_CID_BRIGHTNESS: - return SPA_PROP_brightness; - case V4L2_CID_CONTRAST: - return SPA_PROP_contrast; - case V4L2_CID_SATURATION: - return SPA_PROP_saturation; - case V4L2_CID_HUE: - return SPA_PROP_hue; - case V4L2_CID_GAMMA: - return SPA_PROP_gamma; - case V4L2_CID_EXPOSURE: - return SPA_PROP_exposure; - case V4L2_CID_GAIN: - return SPA_PROP_gain; - case V4L2_CID_SHARPNESS: - return SPA_PROP_sharpness; - default: - return SPA_PROP_START_CUSTOM + control_id; + case V4L2_CID_BRIGHTNESS: + return SPA_PROP_brightness; + case V4L2_CID_CONTRAST: + return SPA_PROP_contrast; + case V4L2_CID_SATURATION: + return SPA_PROP_saturation; + case V4L2_CID_HUE: + return SPA_PROP_hue; + case V4L2_CID_GAMMA: + return SPA_PROP_gamma; + case V4L2_CID_EXPOSURE: + return SPA_PROP_exposure; + case V4L2_CID_GAIN: + return SPA_PROP_gain; + case V4L2_CID_SHARPNESS: + return SPA_PROP_sharpness; + default: + return SPA_PROP_START_CUSTOM + control_id; } } diff --git a/src/utils/udev.h b/src/utils/udev.h index 870c7ea..724a8be 100644 --- a/src/utils/udev.h +++ b/src/utils/udev.h @@ -1,9 +1,5 @@ #include -#define UDEV_ACTION_ADD "add" -#define UDEV_ACTION_RM "remove" -#define UDEV_ACTION_CHANGE "change" - typedef struct { const char *sysattr_key; const char *sysattr_val; diff --git a/src/utils/wl_utils.c b/src/utils/wl_utils.c index 70ad5bc..5daa594 100644 --- a/src/utils/wl_utils.c +++ b/src/utils/wl_utils.c @@ -60,7 +60,6 @@ struct wl_display *fetch_wl_display(const char *display, const char *env) { } } return NULL; - } /* From dca0b4b0fd1a0342f27679de0fdf711735738956 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Fri, 26 Nov 2021 20:03:12 +0100 Subject: [PATCH 04/23] Rebased back clightd.service too. Signed-off-by: Federico Di Pierro --- Scripts/clightd.service | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Scripts/clightd.service b/Scripts/clightd.service index ef1b2c1..5ea5b8c 100644 --- a/Scripts/clightd.service +++ b/Scripts/clightd.service @@ -1,11 +1,14 @@ [Unit] Description=Bus service to manage various screen related properties (gamma, dpms, backlight) Requires=@POLKIT_NAME@.service +@AFTER@ [Service] Type=dbus BusName=org.clightd.clightd User=root +# Default backlight vcp code; update if needed +Environment=CLIGHTD_BL_VCP=0x10 ExecStart=@CMAKE_INSTALL_FULL_LIBEXECDIR@/clightd Restart=on-failure RestartSec=5 From b5efb06758fceb2af88efec8cf2b232f090f2ddc Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Sun, 28 Nov 2021 14:27:01 +0100 Subject: [PATCH 05/23] SOme fixes for pipewire + update todo." Signed-off-by: Federico Di Pierro --- TODO.md | 23 +++++++++++++++++++---- src/modules/sensors/camera.h | 2 ++ src/modules/sensors/pipewire.c | 28 +++++++++++++++++++++------- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/TODO.md b/TODO.md index 0d7614d..5daeae2 100644 --- a/TODO.md +++ b/TODO.md @@ -39,22 +39,37 @@ - [x] call sd_bus_emit_object_added() sd_bus_emit_object_removed() When object path are created/deleted - [x] Fix: udev_reference is a snapshot of an udev device at a current time. Wrong! -### Generic -- [x] When built with ddcutil, clightd.service should be started after systemd-modules-load.service -- [x] Show commit hash in version - ### ALS - [x] Fix: avoid using cached udev_dev reference in loop (thus always returning same ambient brightness read during a Capture request) - [x] Fixed EIO errors ### Sensor - [x] Only emit Sensor.Changed signal for added/removed devices +- [ ] Add a List method in Sensor api to list all available devices ("as") ### Pipewire - [x] Support pipewire for Camera sensor? This would allow multiple application sharing camera - [x] Pipewire run as root needs XDG_RUNTIME_DIR env -> workaround: find the first /run/user folder and use that - [ ] Unify camera settings between camera and pipewire sensors... ? - [x] Support monitor sensor api for pipewire +- [x] Fix segfault +- [x] Fix subsequent Capture +- [ ] Check if installing it on system causes pipewire module to be disabled because clightd starts before /run/user/1000 is created! +-> in case, disable monitor for now and instead rely upon user-provided interface string or PW_ID_ANY +- [ ] Use caller uid instead of defaulting to first found user during Capture! +- [ ] Use a map to store list of nodes? +- [ ] Free list of nodes upon exit! +- [ ] Fix xdg_runtime_dir set to create monitor + +### Generic +- [x] When built with ddcutil, clightd.service should be started after systemd-modules-load.service +- [x] Show commit hash in version +- [ ] All api that require eg Xauth or xdg rutime user, fallback at automatically fetching a default value given the caller: +> unsigned int uid; +// sd_bus_creds *c; +// sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &c); +// sd_bus_creds_get_euid(c, &uid); (/run/user/1000) +// Function fill_bus_creds() -> fills a global "runtime_dir", "xauth_dir" etc etc given a sd_bus_message, then exposes "sender_get_runtime_dir() etc etc" ## 5.x - [ ] Keep it up to date with possible ddcutil api changes diff --git a/src/modules/sensors/camera.h b/src/modules/sensors/camera.h index b981d3e..50db17e 100644 --- a/src/modules/sensors/camera.h +++ b/src/modules/sensors/camera.h @@ -1,3 +1,5 @@ +#pragma once + #include #include #include diff --git a/src/modules/sensors/pipewire.c b/src/modules/sensors/pipewire.c index 8df8c3d..442ffeb 100644 --- a/src/modules/sensors/pipewire.c +++ b/src/modules/sensors/pipewire.c @@ -67,7 +67,6 @@ static void set_env() { } static void on_process(void *_data) { - printf("kek\n"); pw_data_t *pw = _data; struct pw_buffer *b = pw_stream_dequeue_buffer(pw->stream); @@ -78,7 +77,6 @@ static void on_process(void *_data) { const bool is_yuv = pw->format.info.raw.format == SPA_VIDEO_FORMAT_YUY2; struct spa_buffer *buf = b->buffer; - uint8_t *sdata = buf->datas[0].data; if (sdata == NULL) { goto err; @@ -125,6 +123,13 @@ static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_p pw->with_err = true; return; } + + uint8_t params_buffer[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); + const struct spa_pod *params = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, + SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<stream, ¶ms, 1); } static void on_stream_state_changed(void *_data, enum pw_stream_state old, @@ -237,11 +242,20 @@ static void destroy_dev(void *dev) { pw_data_t *pw = (pw_data_t *)dev; if (pw->stream) { pw_stream_destroy(pw->stream); + pw->stream = NULL; } if (pw->loop) { pw_loop_destroy(pw->loop); + pw->loop = NULL; } map_free(pw->stored_values); + pw->stored_values = NULL; + + pw->pct = NULL; + pw->settings = NULL; + pw->capture_idx = 0; + pw->with_err = false; + if (!strcmp(pw->node.action, UDEV_ACTION_RM)) { int found_idx = -1; for (int i = 0; i < nodes_len; i++) { @@ -306,6 +320,7 @@ static const struct pw_registry_events registry_events = { }; static int init_monitor(void) { + // FIXME: improve this...? DIR *d = opendir("/run/user/"); if (d) { struct dirent *dir; @@ -360,13 +375,12 @@ static int capture(void *dev, double *pct, const int num_captures, char *setting pw_data_t *pw = (pw_data_t *)dev; pw->pct = pct; pw->settings = settings; - if (!pw->stored_values) { - pw->stored_values = map_new(true, free); - } + pw->capture_idx = 0; + pw->with_err = false; + pw->stored_values = map_new(true, free); set_camera_settings(pw); pw_loop_enter(pw->loop); while (pw->capture_idx < num_captures && !pw->with_err) { - printf("top %d\n", pw->capture_idx); if (pw_loop_iterate(pw->loop, -1) < 0) { break; } @@ -468,7 +482,7 @@ static void set_camera_settings(pw_data_t *pw) { static void restore_camera_settings(pw_data_t *pw) { for (map_itr_t *itr = map_itr_new(pw->stored_values); itr; itr = map_itr_next(itr)) { struct v4l2_control *old_ctrl = map_itr_get_data(itr); - const char *ctrl_name = map_itr_get_key(itr); + const char *ctrl_name = map_itr_get_key(itr); INFO("Restoring setting for '%s'\n", ctrl_name) set_camera_setting(pw, old_ctrl->id, old_ctrl->value, false); } From 52984a7915d734865f73df031863e0f12100a52d Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Sun, 28 Nov 2021 19:14:17 +0100 Subject: [PATCH 06/23] Use a map to store list of pipewire nodes. Signed-off-by: Federico Di Pierro --- TODO.md | 5 +- src/modules/sensors/pipewire.c | 142 +++++++++++++++++---------------- 2 files changed, 78 insertions(+), 69 deletions(-) diff --git a/TODO.md b/TODO.md index 5daeae2..4f2acba 100644 --- a/TODO.md +++ b/TODO.md @@ -57,9 +57,10 @@ - [ ] Check if installing it on system causes pipewire module to be disabled because clightd starts before /run/user/1000 is created! -> in case, disable monitor for now and instead rely upon user-provided interface string or PW_ID_ANY - [ ] Use caller uid instead of defaulting to first found user during Capture! -- [ ] Use a map to store list of nodes? -- [ ] Free list of nodes upon exit! +- [x] Use a map to store list of nodes? +- [x] Free list of nodes upon exit! - [ ] Fix xdg_runtime_dir set to create monitor +- [ ] Fix memleaks ### Generic - [x] When built with ddcutil, clightd.service should be started after systemd-modules-load.service diff --git a/src/modules/sensors/pipewire.c b/src/modules/sensors/pipewire.c index 442ffeb..85de16d 100644 --- a/src/modules/sensors/pipewire.c +++ b/src/modules/sensors/pipewire.c @@ -8,6 +8,9 @@ #include #define PW_NAME "Pipewire" +#define BUILD_KEY(id) \ + char key[20]; \ + snprintf(key, sizeof(key), "Node%d", id); typedef struct { uint32_t id; @@ -17,19 +20,23 @@ typedef struct { } pw_node_t; typedef struct { - pw_node_t node; - struct pw_loop *loop; - struct pw_stream *stream; - struct spa_video_info format; double *pct; int capture_idx; char *settings; map_t *stored_values; bool with_err; +} capture_settings_t; + +typedef struct { + pw_node_t node; + struct pw_loop *loop; + struct pw_stream *stream; + struct spa_video_info format; + capture_settings_t cap_set; } pw_data_t; typedef struct { - struct pw_loop *mon_loop; + struct pw_loop *loop; struct pw_context *context; struct pw_core *core; struct pw_registry *registry; @@ -47,16 +54,25 @@ static void restore_camera_settings(pw_data_t *pw); SENSOR(PW_NAME); static pw_mon_t pw_mon; -static pw_data_t *last_recved; -static pw_data_t **nodes; -static int nodes_len; +static pw_data_t *last_recved, *first_node; +static map_t *nodes; static int uid; +static void free_node(void *dev) { + pw_data_t *pw = (pw_data_t *)dev; + pw->node.action = NULL; // see destroy_dev() impl + destroy_dev(pw); + pw_proxy_destroy(pw->node.proxy); + free(pw); +} + static void _ctor_ init_libpipewire(void) { pw_init(NULL, NULL); + nodes = map_new(true, free_node); } static void _dtor_ destroy_libpipewire(void) { + map_free(nodes); pw_deinit(); } @@ -89,12 +105,12 @@ static void on_process(void *_data) { .col_end = pw->format.info.raw.size.width, }; - pw->pct[pw->capture_idx++] = get_frame_brightness(sdata, NULL, &full, is_yuv); + pw->cap_set.pct[pw->cap_set.capture_idx++] = get_frame_brightness(sdata, NULL, &full, is_yuv); pw_stream_queue_buffer(pw->stream, b); return; err: - pw->with_err = true; + pw->cap_set.with_err = true; } static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) { @@ -108,19 +124,19 @@ static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_p &pw->format.media_type, &pw->format.media_subtype) < 0) { - pw->with_err = true; + pw->cap_set.with_err = true; return; } if (pw->format.media_type != SPA_MEDIA_TYPE_video || pw->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) { - pw->with_err = true; + pw->cap_set.with_err = true; return; } if (spa_format_video_raw_parse(param, &pw->format.info.raw) < 0) { - pw->with_err = true; + pw->cap_set.with_err = true; return; } @@ -138,7 +154,7 @@ static void on_stream_state_changed(void *_data, enum pw_stream_state old, INFO("Stream state: \"%s\"\n", pw_stream_state_as_string(state)); switch (state) { case PW_STREAM_STATE_UNCONNECTED: - pw->with_err = true; + pw->cap_set.with_err = true; break; case PW_STREAM_STATE_PAUSED: /* because we started inactive, activate ourselves now */ @@ -183,13 +199,15 @@ static void fetch_dev(const char *interface, void **dev) { pw_data_t *pw = NULL; if (interface && strlen(interface)) { uint32_t id = atoi(interface); - for (int i = 0; i < nodes_len && !pw; i++) { - if (nodes[i]->node.id == id) { - pw = nodes[i]; + map_itr_t *itr; + for (itr = map_itr_new(nodes); itr && pw; itr = map_itr_next(itr)) { + pw_data_t *node = map_itr_get_data(itr); + if (node->node.id == id) { + pw = node; } } - } else if (nodes_len > 0) { - pw = nodes[0]; + } else { + pw = first_node; } if (pw) { @@ -248,27 +266,12 @@ static void destroy_dev(void *dev) { pw_loop_destroy(pw->loop); pw->loop = NULL; } - map_free(pw->stored_values); - pw->stored_values = NULL; + map_free(pw->cap_set.stored_values); + memset(&pw->cap_set, 0, sizeof(pw->cap_set)); - pw->pct = NULL; - pw->settings = NULL; - pw->capture_idx = 0; - pw->with_err = false; - - if (!strcmp(pw->node.action, UDEV_ACTION_RM)) { - int found_idx = -1; - for (int i = 0; i < nodes_len; i++) { - if (found_idx != -1) { - nodes[i - 1] = nodes[i]; - nodes[i] = NULL; - } - if (pw == nodes[i]) { - found_idx = i; - } - } - free(pw); - nodes = realloc(nodes, --nodes_len); + if (pw->node.action && !strcmp(pw->node.action, UDEV_ACTION_RM)) { + BUILD_KEY(pw->node.id); + map_remove(nodes, key); } } @@ -293,12 +296,6 @@ static void registry_event_global(void *data, uint32_t id, const char *mc = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); const char *mr = spa_dict_lookup(props, PW_KEY_MEDIA_ROLE); if (mc && mr && !strcmp(mc, "Video/Source") && !strcmp(mr, "Camera")) { - void *tmp = realloc(nodes, ++nodes_len); - if (!tmp) { - return; - } - nodes = tmp; - pw_data_t *pw = calloc(1, sizeof(pw_data_t)); if (!pw) { return; @@ -309,7 +306,11 @@ static void registry_event_global(void *data, uint32_t id, pw->node.proxy = pw_registry_bind(pw_mon.registry, id, type, PW_VERSION_NODE, sizeof(pw_data_t)); pw_proxy_add_listener(pw->node.proxy, &pw->node.proxy_listener, &proxy_events, pw); last_recved = pw; // used by recv_monitor - nodes[nodes_len - 1] = pw; + if (map_length(nodes) == 0) { + first_node = pw; + } + BUILD_KEY(id); + map_put(nodes, key, pw); } } } @@ -342,16 +343,16 @@ static int init_monitor(void) { set_env(); - pw_mon.mon_loop = pw_loop_new(NULL); - pw_mon.context = pw_context_new(pw_mon.mon_loop, NULL, 0); + pw_mon.loop = pw_loop_new(NULL); + pw_mon.context = pw_context_new(pw_mon.loop, NULL, 0); pw_mon.core = pw_context_connect(pw_mon.context, NULL, 0); pw_mon.registry = pw_core_get_registry(pw_mon.core, PW_VERSION_REGISTRY, 0); spa_zero(pw_mon.registry_listener); pw_registry_add_listener(pw_mon.registry, &pw_mon.registry_listener, ®istry_events, NULL); - int fd = pw_loop_get_fd(pw_mon.mon_loop); - pw_loop_enter(pw_mon.mon_loop); + int fd = pw_loop_get_fd(pw_mon.loop); + pw_loop_enter(pw_mon.loop); unsetenv("XDG_RUNTIME_DIR"); return fd; @@ -359,35 +360,42 @@ static int init_monitor(void) { static void recv_monitor(void **dev) { last_recved = NULL; - pw_loop_iterate(pw_mon.mon_loop, 0); + pw_loop_iterate(pw_mon.loop, 0); *dev = last_recved; } static void destroy_monitor(void) { - pw_loop_leave(pw_mon.mon_loop); - pw_proxy_destroy((struct pw_proxy*)pw_mon.registry); - pw_core_disconnect(pw_mon.core); - pw_context_destroy(pw_mon.context); - pw_loop_destroy(pw_mon.mon_loop); + if (pw_mon.registry) { + pw_proxy_destroy((struct pw_proxy*)pw_mon.registry); + } + if (pw_mon.core) { + pw_core_disconnect(pw_mon.core); + pw_core_destroy(pw_mon.core, NULL); + } + if (pw_mon.context) { + pw_context_destroy(pw_mon.context); + } + if (pw_mon.loop) { + pw_loop_leave(pw_mon.loop); + pw_loop_destroy(pw_mon.loop); + } } static int capture(void *dev, double *pct, const int num_captures, char *settings) { pw_data_t *pw = (pw_data_t *)dev; - pw->pct = pct; - pw->settings = settings; - pw->capture_idx = 0; - pw->with_err = false; - pw->stored_values = map_new(true, free); + pw->cap_set.pct = pct; + pw->cap_set.settings = settings; + pw->cap_set.stored_values = map_new(true, free); set_camera_settings(pw); pw_loop_enter(pw->loop); - while (pw->capture_idx < num_captures && !pw->with_err) { + while (pw->cap_set.capture_idx < num_captures && !pw->cap_set.with_err) { if (pw_loop_iterate(pw->loop, -1) < 0) { break; } } pw_loop_leave(pw->loop); restore_camera_settings(pw); - return pw->capture_idx; + return pw->cap_set.capture_idx; } // Stolen from https://github.com/PipeWire/pipewire/blob/master/spa/plugins/v4l2/v4l2-utils.c#L1017 @@ -431,7 +439,7 @@ static void set_camera_setting(pw_data_t *pw, uint32_t op, float val, bool store struct v4l2_control *v = malloc(sizeof(struct v4l2_control)); v->value = old_val; v->id = op; - map_put(pw->stored_values, ctrl->name, v); + map_put(pw->cap_set.stored_values, ctrl->name, v); } } else { INFO("Value %2.lf for '%s' already set.\n", val, ctrl->name); @@ -462,9 +470,9 @@ static void set_camera_settings_def(pw_data_t *pw) { static void set_camera_settings(pw_data_t *pw) { /* Set default values */ set_camera_settings_def(pw); - if (pw->settings && strlen(pw->settings)) { + if (pw->cap_set.settings && strlen(pw->cap_set.settings)) { char *token; - char *rest = pw->settings; + char *rest = pw->cap_set.settings; while ((token = strtok_r(rest, ",", &rest))) { uint32_t v4l2_op; @@ -480,7 +488,7 @@ static void set_camera_settings(pw_data_t *pw) { } static void restore_camera_settings(pw_data_t *pw) { - for (map_itr_t *itr = map_itr_new(pw->stored_values); itr; itr = map_itr_next(itr)) { + for (map_itr_t *itr = map_itr_new(pw->cap_set.stored_values); itr; itr = map_itr_next(itr)) { struct v4l2_control *old_ctrl = map_itr_get_data(itr); const char *ctrl_name = map_itr_get_key(itr); INFO("Restoring setting for '%s'\n", ctrl_name) From f33d4f86a186e096cca937607c81bdfb9fa650d7 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Sun, 28 Nov 2021 20:52:56 +0100 Subject: [PATCH 07/23] Added bus_utils and xorg_utils files. They are used to: * bus_utils -> fetch xauthority path and xdg runtime dir from bus API caller * xorg_utils -> centralize xorg display fetching in a single call All in all, this work allows Clightd to be able to provide sensible default values when called with empty strings. Moreover, only initialize streaming in capture() callback instead of fetch_dev() for pipewire, and by default capture using bus caller xdg_runtime_dir. Signed-off-by: Federico Di Pierro --- TODO.md | 12 ++-- src/modules/dpms.c | 5 ++ src/modules/dpms_plugins/xorg.c | 19 ++---- src/modules/gamma.c | 5 ++ src/modules/gamma_plugins/xorg.c | 8 +-- src/modules/screen.c | 3 + src/modules/screen_plugins/xorg.c | 15 ++--- src/modules/sensor.c | 5 +- src/modules/sensors/pipewire.c | 103 +++++++++++++++--------------- src/utils/bus_utils.c | 42 ++++++++++++ src/utils/bus_utils.h | 5 ++ src/utils/wl_utils.c | 9 +++ src/utils/wl_utils.h | 2 + src/utils/xorg_utils.c | 25 ++++++++ src/utils/xorg_utils.h | 5 ++ 15 files changed, 174 insertions(+), 89 deletions(-) create mode 100644 src/utils/bus_utils.c create mode 100644 src/utils/bus_utils.h create mode 100644 src/utils/xorg_utils.c create mode 100644 src/utils/xorg_utils.h diff --git a/TODO.md b/TODO.md index 4f2acba..76b033a 100644 --- a/TODO.md +++ b/TODO.md @@ -45,7 +45,6 @@ ### Sensor - [x] Only emit Sensor.Changed signal for added/removed devices -- [ ] Add a List method in Sensor api to list all available devices ("as") ### Pipewire - [x] Support pipewire for Camera sensor? This would allow multiple application sharing camera @@ -56,7 +55,7 @@ - [x] Fix subsequent Capture - [ ] Check if installing it on system causes pipewire module to be disabled because clightd starts before /run/user/1000 is created! -> in case, disable monitor for now and instead rely upon user-provided interface string or PW_ID_ANY -- [ ] Use caller uid instead of defaulting to first found user during Capture! +- [x] Use caller uid instead of defaulting to first found user during Capture! - [x] Use a map to store list of nodes? - [x] Free list of nodes upon exit! - [ ] Fix xdg_runtime_dir set to create monitor @@ -65,12 +64,9 @@ ### Generic - [x] When built with ddcutil, clightd.service should be started after systemd-modules-load.service - [x] Show commit hash in version -- [ ] All api that require eg Xauth or xdg rutime user, fallback at automatically fetching a default value given the caller: -> unsigned int uid; -// sd_bus_creds *c; -// sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &c); -// sd_bus_creds_get_euid(c, &uid); (/run/user/1000) -// Function fill_bus_creds() -> fills a global "runtime_dir", "xauth_dir" etc etc given a sd_bus_message, then exposes "sender_get_runtime_dir() etc etc" +- [x] All api that require eg Xauth or xdg rutime user, fallback at automatically fetching a default value given the caller: +- [x] test X +- [ ] test wl ## 5.x - [ ] Keep it up to date with possible ddcutil api changes diff --git a/src/modules/dpms.c b/src/modules/dpms.c index e58bb8b..069265c 100644 --- a/src/modules/dpms.c +++ b/src/modules/dpms.c @@ -2,6 +2,7 @@ #include "dpms.h" #include "polkit.h" +#include "bus_utils.h" static int method_getdpms(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); static int method_setdpms(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); @@ -94,6 +95,8 @@ static int method_getdpms(sd_bus_message *m, void *userdata, sd_bus_error *ret_e return r; } + bus_sender_fill_creds(m); // used by PW plugin + /* * Note: this is freed by drm plugin if it is an empty string * to get a default drm device. @@ -148,6 +151,8 @@ static int method_setdpms(sd_bus_message *m, void *userdata, sd_bus_error *ret_e return -EINVAL; } + bus_sender_fill_creds(m); // used by PW plugin + /* * Note: this is freed by drm plugin if it is an empty string * to get a default drm device. diff --git a/src/modules/dpms_plugins/xorg.c b/src/modules/dpms_plugins/xorg.c index deeba07..9e9961c 100644 --- a/src/modules/dpms_plugins/xorg.c +++ b/src/modules/dpms_plugins/xorg.c @@ -1,6 +1,7 @@ -#include +#include "xorg_utils.h" #include #include "dpms.h" +#include "bus_utils.h" DPMS("Xorg"); @@ -17,9 +18,7 @@ static int get(const char **display, const char *xauthority) { CARD16 s; int ret = WRONG_PLUGIN; - setenv("XAUTHORITY", xauthority, 1); - - Display *dpy = XOpenDisplay(*display); + Display *dpy = fetch_xorg_display(display, xauthority); if (dpy) { if (DPMSCapable(dpy)) { DPMSInfo(dpy, &s, &onoff); @@ -28,19 +27,14 @@ static int get(const char **display, const char *xauthority) { ret = UNSUPPORTED; } XCloseDisplay(dpy); - } - - unsetenv("XAUTHORITY"); + } return ret; } static int set(const char **display, const char *xauthority, int dpms_level) { int ret = WRONG_PLUGIN; - /* set xauthority cookie */ - setenv("XAUTHORITY", xauthority, 1); - - Display *dpy = XOpenDisplay(*display); + Display *dpy = fetch_xorg_display(display, xauthority); if (dpy) { if (DPMSCapable(dpy)) { DPMSEnable(dpy); @@ -52,8 +46,5 @@ static int set(const char **display, const char *xauthority, int dpms_level) { } XCloseDisplay(dpy); } - - /* Drop xauthority cookie */ - unsetenv("XAUTHORITY"); return ret; } diff --git a/src/modules/gamma.c b/src/modules/gamma.c index e52bde6..6b0714f 100644 --- a/src/modules/gamma.c +++ b/src/modules/gamma.c @@ -10,6 +10,7 @@ #include #include #include "gamma.h" +#include "bus_utils.h" static unsigned short get_red(int temp); static unsigned short get_green(int temp); @@ -269,6 +270,8 @@ static int method_setgamma(sd_bus_message *m, void *userdata, sd_bus_error *ret_ if (temp < 1000 || temp > 10000) { error = EINVAL; } else { + bus_sender_fill_creds(m); + gamma_client *sc = map_get(clients, display); if (!sc) { sc = fetch_client(userdata, display, env, &error); @@ -311,6 +314,8 @@ static int method_getgamma(sd_bus_message *m, void *userdata, sd_bus_error *ret_ return r; } + bus_sender_fill_creds(m); // used by PW plugin + gamma_client *cl = map_get(clients, display); if (cl) { temp = cl->current_temp; diff --git a/src/modules/gamma_plugins/xorg.c b/src/modules/gamma_plugins/xorg.c index ab83b42..ff72ea1 100644 --- a/src/modules/gamma_plugins/xorg.c +++ b/src/modules/gamma_plugins/xorg.c @@ -1,5 +1,7 @@ #include "gamma.h" #include +#include "bus_utils.h" +#include "xorg_utils.h" #include typedef struct { @@ -12,9 +14,7 @@ GAMMA("Xorg"); static int validate(const char **id, const char *env, void **priv_data) { int ret = WRONG_PLUGIN; - setenv("XAUTHORITY", env, 1); - - Display *dpy = XOpenDisplay(*id); + Display *dpy = fetch_xorg_display(id, env); if (dpy) { int screen = DefaultScreen(dpy); Window root = RootWindow(dpy, screen); @@ -30,8 +30,6 @@ static int validate(const char **id, const char *env, void **priv_data) { XCloseDisplay(dpy); } } - - unsetenv("XAUTHORITY"); return ret; } diff --git a/src/modules/screen.c b/src/modules/screen.c index f3c0d87..b69f44f 100644 --- a/src/modules/screen.c +++ b/src/modules/screen.c @@ -1,6 +1,7 @@ #ifdef SCREEN_PRESENT #include "screen.h" +#include "bus_utils.h" #define MONITOR_ILL_MAX 255 @@ -117,6 +118,8 @@ static int method_getbrightness(sd_bus_message* m, void* userdata, sd_bus_error* return r; } + bus_sender_fill_creds(m); + screen_plugin *plugin = userdata; int br = WRONG_PLUGIN; if (!plugin) { diff --git a/src/modules/screen_plugins/xorg.c b/src/modules/screen_plugins/xorg.c index 21d1dea..8c8ad7b 100644 --- a/src/modules/screen_plugins/xorg.c +++ b/src/modules/screen_plugins/xorg.c @@ -1,20 +1,13 @@ #include "screen.h" +#include "bus_utils.h" +#include "xorg_utils.h" #include -static int getRootBrightness(const char *screen_name); - SCREEN("Xorg"); -static int get_frame_brightness(const char *id, const char *env) { - setenv("XAUTHORITY", env, 1); - const int br = getRootBrightness(id); - unsetenv("XAUTHORITY"); - return br; -} - /* Robbed from calise source code, thanks!! */ -static int getRootBrightness(const char *screen_name) { - Display *dpy = XOpenDisplay(screen_name); +static int get_frame_brightness(const char *id, const char *env) { + Display *dpy = fetch_xorg_display(&id, env); if (!dpy) { return WRONG_PLUGIN; } diff --git a/src/modules/sensor.c b/src/modules/sensor.c index 3e0456d..bd07e28 100644 --- a/src/modules/sensor.c +++ b/src/modules/sensor.c @@ -1,5 +1,6 @@ #include #include +#include "bus_utils.h" #define SENSOR_MAX_CAPTURES 20 @@ -186,7 +187,7 @@ static int method_issensoravailable(sd_bus_message *m, void *userdata, sd_bus_er static int method_capturesensor(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { ASSERT_AUTH(); - + const char *interface = NULL; char *settings = NULL; const int num_captures; @@ -201,6 +202,8 @@ static int method_capturesensor(sd_bus_message *m, void *userdata, sd_bus_error return -EINVAL; } + bus_sender_fill_creds(m); // used by PW plugin + void *dev = NULL; sensor_t *sensor = NULL; double *pct = calloc(num_captures, sizeof(double)); diff --git a/src/modules/sensors/pipewire.c b/src/modules/sensors/pipewire.c index 85de16d..1e94654 100644 --- a/src/modules/sensors/pipewire.c +++ b/src/modules/sensors/pipewire.c @@ -1,6 +1,7 @@ #ifdef PIPEWIRE_PRESENT #include "camera.h" +#include "bus_utils.h" #include #include #include @@ -77,9 +78,13 @@ static void _dtor_ destroy_libpipewire(void) { } static void set_env() { - char path[64]; - snprintf(path, sizeof(path), "/run/user/%d", uid); - setenv("XDG_RUNTIME_DIR", path, 1);; + if (bus_sender_runtime_dir()) { + setenv("XDG_RUNTIME_DIR", bus_sender_runtime_dir(), 1); + } else { + char path[64]; + snprintf(path, sizeof(path), "/run/user/%d", uid); + setenv("XDG_RUNTIME_DIR", path, 1);; + } } static void on_process(void *_data) { @@ -169,7 +174,7 @@ static void on_stream_state_changed(void *_data, enum pw_stream_state old, static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .param_changed = on_stream_param_changed, - .state_changed = on_stream_state_changed, +// .state_changed = on_stream_state_changed, .process = on_process, }; @@ -199,51 +204,18 @@ static void fetch_dev(const char *interface, void **dev) { pw_data_t *pw = NULL; if (interface && strlen(interface)) { uint32_t id = atoi(interface); - map_itr_t *itr; - for (itr = map_itr_new(nodes); itr && pw; itr = map_itr_next(itr)) { + for (map_itr_t *itr = map_itr_new(nodes); itr; itr = map_itr_next(itr)) { pw_data_t *node = map_itr_get_data(itr); if (node->node.id == id) { pw = node; + free(itr); + break; } } } else { pw = first_node; } - - if (pw) { - set_env(); - - const struct spa_pod *params; - uint8_t buffer[1024]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - - pw->loop = pw_loop_new(NULL); - pw->stream = pw_stream_new_simple(pw->loop, - "clightd-camera-pw", - pw_properties_new( - PW_KEY_MEDIA_TYPE, "Video", - PW_KEY_MEDIA_CATEGORY, "Capture", - PW_KEY_MEDIA_ROLE, "Camera", - NULL), - &stream_events, - pw); - build_format(&b, ¶ms); - int res; - if ((res = pw_stream_connect(pw->stream, - PW_DIRECTION_INPUT, - pw->node.id, - PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ - PW_STREAM_FLAG_INACTIVE | - PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */ - ¶ms, 1)) /* extra parameters, see above */ < 0) { - - fprintf(stderr, "can't connect: %s\n", spa_strerror(res)); - free(pw); - } else { - *dev = pw; - } - unsetenv("XDG_RUNTIME_DIR"); - } + *dev = pw; } static void fetch_props_dev(void *dev, const char **node, const char **action) { @@ -267,6 +239,8 @@ static void destroy_dev(void *dev) { pw->loop = NULL; } map_free(pw->cap_set.stored_values); + pw->cap_set.stored_values = NULL; + memset(&pw->cap_set, 0, sizeof(pw->cap_set)); if (pw->node.action && !strcmp(pw->node.action, UDEV_ACTION_RM)) { @@ -277,9 +251,8 @@ static void destroy_dev(void *dev) { static void removed_proxy(void *data) { pw_data_t *pw = (pw_data_t *)data; - pw->node.action = UDEV_ACTION_RM; + pw->node.action = UDEV_ACTION_RM; // this will kill our module during destroy_dev() call! INFO("Removed node %d\n", pw->node.id); - pw_proxy_destroy(pw->node.proxy); last_recved = pw; } @@ -386,15 +359,45 @@ static int capture(void *dev, double *pct, const int num_captures, char *setting pw->cap_set.pct = pct; pw->cap_set.settings = settings; pw->cap_set.stored_values = map_new(true, free); - set_camera_settings(pw); - pw_loop_enter(pw->loop); - while (pw->cap_set.capture_idx < num_captures && !pw->cap_set.with_err) { - if (pw_loop_iterate(pw->loop, -1) < 0) { - break; + + set_env(); + + const struct spa_pod *params; + uint8_t buffer[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + + pw->loop = pw_loop_new(NULL); + pw->stream = pw_stream_new_simple(pw->loop, + "clightd-camera-pw", + pw_properties_new( + PW_KEY_MEDIA_TYPE, "Video", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Camera", + NULL), + &stream_events, + pw); + build_format(&b, ¶ms); + int res; + if ((res = pw_stream_connect(pw->stream, + PW_DIRECTION_INPUT, + pw->node.id, + PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ + PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */ + ¶ms, 1)) /* extra parameters, see above */ < 0) { + + fprintf(stderr, "Can't connect: %s\n", spa_strerror(res)); + } else { + set_camera_settings(pw); + pw_loop_enter(pw->loop); + while (pw->cap_set.capture_idx < num_captures && !pw->cap_set.with_err) { + if (pw_loop_iterate(pw->loop, -1) < 0) { + break; + } } + pw_loop_leave(pw->loop); + restore_camera_settings(pw); } - pw_loop_leave(pw->loop); - restore_camera_settings(pw); + unsetenv("XDG_RUNTIME_DIR"); return pw->cap_set.capture_idx; } diff --git a/src/utils/bus_utils.c b/src/utils/bus_utils.c new file mode 100644 index 0000000..647a17e --- /dev/null +++ b/src/utils/bus_utils.c @@ -0,0 +1,42 @@ +#include "bus_utils.h" +#include + +static char xdg_runtime_dir[PATH_MAX + 1]; +static char xauth_path[PATH_MAX + 1]; + +int bus_sender_fill_creds(sd_bus_message *m) { + xdg_runtime_dir[0] = 0; + xauth_path[0] = 0; + + sd_bus_creds *c = NULL; + sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &c); + if (c) { + uid_t uid = 0; + if (sd_bus_creds_get_euid(c, &uid) >= 0) { + snprintf(xdg_runtime_dir, PATH_MAX, "/run/user/%d", uid); + struct passwd *p = NULL; + setpwent(); + for(p = getpwent(); p; p = getpwent()) { + if (p->pw_uid == uid && p->pw_dir) { + snprintf(xauth_path, PATH_MAX, "%s/.Xauthority", p->pw_dir); + return 0;; + } + } + } + } + return -1; +} + +const char *bus_sender_runtime_dir(void) { + if (xdg_runtime_dir[0] != 0) { + return xdg_runtime_dir; + } + return NULL; +} + +const char *bus_sender_xauth(void) { + if (xauth_path[0] != 0) { + return xauth_path; + } + return NULL; +} diff --git a/src/utils/bus_utils.h b/src/utils/bus_utils.h new file mode 100644 index 0000000..ecd7268 --- /dev/null +++ b/src/utils/bus_utils.h @@ -0,0 +1,5 @@ +#include "commons.h" + +int bus_sender_fill_creds(sd_bus_message *m); +const char *bus_sender_runtime_dir(void); +const char *bus_sender_xauth(void); diff --git a/src/utils/wl_utils.c b/src/utils/wl_utils.c index 5daa594..e39c013 100644 --- a/src/utils/wl_utils.c +++ b/src/utils/wl_utils.c @@ -2,9 +2,12 @@ #include "wl_utils.h" #include "commons.h" +#include "bus_utils.h" #include #include +#define WL_DISPLAY_DEF "wayland-0" + typedef struct { struct wl_display *dpy; char *env; @@ -30,6 +33,12 @@ static void wl_info_dtor(void *data) { } struct wl_display *fetch_wl_display(const char *display, const char *env) { + if (!env || env[0] == 0) { + env = bus_sender_runtime_dir(); + } + if (!display || display[0] == 0) { + display = WL_DISPLAY_DEF; + } if (env) { wl_info *info = map_get(wl_map, display); if (!info) { diff --git a/src/utils/wl_utils.h b/src/utils/wl_utils.h index 37a260a..1c2cd4d 100644 --- a/src/utils/wl_utils.h +++ b/src/utils/wl_utils.h @@ -1,3 +1,5 @@ +#pragma once + #include #include diff --git a/src/utils/xorg_utils.c b/src/utils/xorg_utils.c new file mode 100644 index 0000000..d21670c --- /dev/null +++ b/src/utils/xorg_utils.c @@ -0,0 +1,25 @@ +#if defined GAMMA_PRESENT || defined DPMS_PRESENT || defined SCREEN_PRESENT + +#include "bus_utils.h" +#include "xorg_utils.h" + +#define XORG_DISPLAY_DEF ":0" + +Display *fetch_xorg_display(const char **display, const char *xauthority) { + if (!*display || display[0] == 0) { + *display = XORG_DISPLAY_DEF; + } + if (!xauthority || xauthority[0] == 0) { + xauthority = bus_sender_xauth(); + } + + if (xauthority) { + setenv("XAUTHORITY", xauthority, 1); + Display *dpy = XOpenDisplay(*display); + unsetenv("XAUTHORITY"); + return dpy; + } + return NULL; +} + +#endif diff --git a/src/utils/xorg_utils.h b/src/utils/xorg_utils.h new file mode 100644 index 0000000..06fb402 --- /dev/null +++ b/src/utils/xorg_utils.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +Display *fetch_xorg_display(const char **display, const char *xauthority); From d654407b6833b668327887a7a8b87364258b4ed7 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Mon, 29 Nov 2021 21:10:08 +0100 Subject: [PATCH 08/23] Some fixes to pipewire module and global. * Signal.c needs to set SIGBLOCK mask before everything else, so that threads created by Clightd deps won't intercept any signal. DDCutils library is still messing though; needs some more > * Fixed memleak in keyboard module * Cleanup pipewire module * Added some checks to pipewire::init_monitor() to avoid crashing when monitor fails to be created * Updated todo Signed-off-by: Federico Di Pierro --- TODO.md | 11 +++-- src/commons.h | 12 ++++- src/modules/keyboard.c | 3 +- src/modules/sensors/camera.h | 83 ++++++++++++++++++++++++++++++++++ src/modules/sensors/pipewire.c | 60 +++++++++++------------- src/modules/signal.c | 18 +++++--- src/utils/bus_utils.c | 10 ++-- 7 files changed, 146 insertions(+), 51 deletions(-) diff --git a/TODO.md b/TODO.md index 76b033a..eaa83b3 100644 --- a/TODO.md +++ b/TODO.md @@ -30,7 +30,7 @@ - [x] Drop bl_store_vpcode() and only load vpcode from CLIGHTD_BL_VCP env? - [x] Add CLIGHTD_BL_VCP Environment variable to systemd script with a comment thus it is simple to update it if needed - [x] Expose Max and Internal properties -- [ ] Update dbus api wiki +- [x] Update dbus api wiki - [x] add a page about monitor hotplugging (dep on ddcutil >= 1.2.0 and refresh time!) - [x] Investigate memleaks (related to ddca_redetect_displays()?) -> see here: https://github.com/rockowitz/ddcutil/issues/202 - [x] Instead of 30s sleep, use an udev_monitor on drm subsystem? @@ -38,6 +38,7 @@ ### KbdBacklight - [x] call sd_bus_emit_object_added() sd_bus_emit_object_removed() When object path are created/deleted - [x] Fix: udev_reference is a snapshot of an udev device at a current time. Wrong! +- [x] Fixed (small) memleak ### ALS - [x] Fix: avoid using cached udev_dev reference in loop (thus always returning same ambient brightness read during a Capture request) @@ -54,12 +55,12 @@ - [x] Fix segfault - [x] Fix subsequent Capture - [ ] Check if installing it on system causes pipewire module to be disabled because clightd starts before /run/user/1000 is created! --> in case, disable monitor for now and instead rely upon user-provided interface string or PW_ID_ANY +-> in case, disable monitor for now and instead rely upon user-provided interface string or PW_ID_ANY (?) -> we lose IsAvailable... - [x] Use caller uid instead of defaulting to first found user during Capture! - [x] Use a map to store list of nodes? - [x] Free list of nodes upon exit! -- [ ] Fix xdg_runtime_dir set to create monitor -- [ ] Fix memleaks +- [x] Fix xdg_runtime_dir set to create monitor +- [x] Fix memleaks ### Generic - [x] When built with ddcutil, clightd.service should be started after systemd-modules-load.service @@ -67,6 +68,8 @@ - [x] All api that require eg Xauth or xdg rutime user, fallback at automatically fetching a default value given the caller: - [x] test X - [ ] test wl +- [x] Fix clightd not cleanly exiting when built with DDC or YOCTOLIGHT (most probably libusb or whatever is creating another thread that is stealing the signal!) +DDC? ## 5.x - [ ] Keep it up to date with possible ddcutil api changes diff --git a/src/commons.h b/src/commons.h index 4bf83b2..f38cb2a 100644 --- a/src/commons.h +++ b/src/commons.h @@ -20,8 +20,16 @@ #include #define SIZE(x) (sizeof(x) / sizeof(*x)) -#define _ctor_ __attribute__((constructor (101))) // Used for Sensors registering -#define _dtor_ __attribute__((destructor (101))) // Used for libusb dtor + +/* + * Used for the signal blocking ctor; + * it is needed as some libraries (libusb, libddcutil, libpipewire) spawn threads + * that would mess with the signal receiving mechanism of Clightd + */ +#define _ctor_sig_ __attribute__((constructor (101))) + +#define _ctor_ __attribute__((constructor (102))) // Used for plugins registering (sensor, gamma, dpms, screen) and libusb/libpipewire init +#define _dtor_ __attribute__((destructor (102))) // Used for libusb and libpipewire dtor /* Used by dpms, gamma and screen*/ #define UNSUPPORTED INT_MIN diff --git a/src/modules/keyboard.c b/src/modules/keyboard.c index c5816da..3e25fdf 100644 --- a/src/modules/keyboard.c +++ b/src/modules/keyboard.c @@ -72,7 +72,7 @@ static bool evaluate(void) { } static void init(void) { - kbds = map_new(true, dtor_kbd); + kbds = map_new(false, dtor_kbd); sd_bus_add_object_manager(bus, NULL, object_path); int r = sd_bus_add_object_vtable(bus, NULL, @@ -141,6 +141,7 @@ static void destroy(void) { static void dtor_kbd(void *data) { kbd_t *k = (kbd_t *)data; sd_bus_slot_unref(k->slot); + free((void *)k->sysname); free(k); } diff --git a/src/modules/sensors/camera.h b/src/modules/sensors/camera.h index 50db17e..5b12d32 100644 --- a/src/modules/sensors/camera.h +++ b/src/modules/sensors/camera.h @@ -122,3 +122,86 @@ static double get_frame_brightness(uint8_t *img_data, rect_info_t *crop, rect_in return (double)brightness / CAMERA_ILL_MAX; } +// typedef enum { X_AXIS, Y_AXIS, MAX_AXIS } crop_axis; +// typedef enum { DISABLED, CROP_API, SELECTION_API, MANUAL} crop_type_t; +// +// typedef struct { +// bool enabled; +// double area_pct[2]; // start - end +// } crop_info_t; + +// extern void set_camera_settings_def(void *priv); +// extern void set_camera_setting(void *priv, uint32_t op, float val, bool store); +// +// #define SET_V4L2(id, val) set_camera_setting(id, val, #id, true) +// +// static void set_camera_settings(void *priv, char *settings, crop_info_t *crop, crop_type_t *crop_type) { +// /* Set default values */ +// set_camera_settings_def(priv); +// if (settings && strlen(settings)) { +// char *token; +// char *rest = settings; +// +// while ((token = strtok_r(rest, ",", &rest))) { +// uint32_t v4l2_op; +// int32_t v4l2_val; +// char axis; +// double area_pct[2]; +// +// if (sscanf(token, "%u=%d", &v4l2_op, &v4l2_val) == 2) { +// SET_V4L2(v4l2_op, v4l2_val); +// } else if (sscanf(token, "%c=%lf-%lf", &axis, &area_pct[0], &area_pct[1]) == 3) { +// int8_t crop_idx = -1; +// if (area_pct[0] >= area_pct[1]) { +// fprintf(stderr, "Start should be lesser than end: %lf-%lf\n", area_pct[0], area_pct[1]); +// } else { +// switch (axis) { +// case 'x': +// crop_idx = X_AXIS; +// break; +// case 'y': +// crop_idx = Y_AXIS; +// break; +// default: +// fprintf(stderr, "wrong axis specified: %c; 'x' or 'y' supported.\n", axis); +// break; +// } +// } +// if (crop_idx != -1 && !crop[crop_idx].enabled) { +// crop[crop_idx].enabled = true; +// crop[crop_idx].area_pct[0] = area_pct[0]; +// crop[crop_idx].area_pct[1] = area_pct[1]; +// } +// } else { +// fprintf(stderr, "Expected a=b format in '%s' token.\n", token); +// } +// } +// if (crop[X_AXIS].enabled || crop[Y_AXIS].enabled) { +// if (try_set_crop(state.crop) != 0) { +// INFO("Unsupported crop/selection v4l2 API; fallback at manually skipping pixels.\n") +// *crop_type = MANUAL; +// } +// } +// } +// } +// +// static void restore_camera_settings(map_t *stored_values, crop_type_t crop_type) { +// for (map_itr_t *itr = map_itr_new(stored_values); itr; itr = map_itr_next(itr)) { +// struct v4l2_control *old_ctrl = map_itr_get_data(itr); +// const char *ctrl_name = map_itr_get_key(itr); +// INFO("Restoring setting for '%s'\n", ctrl_name) +// set_camera_setting(old_ctrl->id, old_ctrl->value, ctrl_name, false); +// } +// +// // Restore crop if needed +// switch (crop_type) { +// case SELECTION_API: +// set_selection(NULL); +// break; +// case CROP_API: +// set_crop(NULL); +// break; +// default: +// break; +// } +// } diff --git a/src/modules/sensors/pipewire.c b/src/modules/sensors/pipewire.c index 1e94654..3be6540 100644 --- a/src/modules/sensors/pipewire.c +++ b/src/modules/sensors/pipewire.c @@ -2,7 +2,7 @@ #include "camera.h" #include "bus_utils.h" -#include +#include #include #include #include @@ -153,28 +153,10 @@ static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_p pw_stream_update_params(pw->stream, ¶ms, 1); } -static void on_stream_state_changed(void *_data, enum pw_stream_state old, - enum pw_stream_state state, const char *error) { - pw_data_t *pw = _data; - INFO("Stream state: \"%s\"\n", pw_stream_state_as_string(state)); - switch (state) { - case PW_STREAM_STATE_UNCONNECTED: - pw->cap_set.with_err = true; - break; - case PW_STREAM_STATE_PAUSED: - /* because we started inactive, activate ourselves now */ - pw_stream_set_active(pw->stream, true); - break; - default: - break; - } -} - /* these are the stream events we listen for */ static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .param_changed = on_stream_param_changed, -// .state_changed = on_stream_state_changed, .process = on_process, }; @@ -294,20 +276,22 @@ static const struct pw_registry_events registry_events = { }; static int init_monitor(void) { - // FIXME: improve this...? - DIR *d = opendir("/run/user/"); - if (d) { - struct dirent *dir; - while ((dir = readdir(d)) != NULL) { - if (dir->d_type == DT_DIR) { - if (sscanf(dir->d_name, "%d", &uid) == 1) { - printf("Found user %d\n", uid); - break; - } - } + /* + * Pipewire needs an XDG_RUNTIME_DIR set; + * at this phase, we are not being called by anyone; + * thus we need to workaround this by fetching + * a real user id, and use it to build the XDG_RUNTIME_DIR env. + */ + setpwent(); + char path[64]; + for (struct passwd *p = getpwent(); p; p = getpwent()) { + snprintf(path, sizeof(path), "/run/user/%d/", p->pw_uid); + if (access(path, F_OK) == 0) { + uid = p->pw_uid; + break; } - closedir(d); } + endpwent(); // Unsupported... can this happen? if (uid == 0) { @@ -317,9 +301,21 @@ static int init_monitor(void) { set_env(); pw_mon.loop = pw_loop_new(NULL); + if (!pw_mon.loop) { + return -1; + } pw_mon.context = pw_context_new(pw_mon.loop, NULL, 0); + if (!pw_mon.context) { + return -1; + } pw_mon.core = pw_context_connect(pw_mon.context, NULL, 0); + if (!pw_mon.core) { + return -1; + } pw_mon.registry = pw_core_get_registry(pw_mon.core, PW_VERSION_REGISTRY, 0); + if (!pw_mon.registry) { + return -1; + } spa_zero(pw_mon.registry_listener); pw_registry_add_listener(pw_mon.registry, &pw_mon.registry_listener, ®istry_events, NULL); @@ -343,13 +339,11 @@ static void destroy_monitor(void) { } if (pw_mon.core) { pw_core_disconnect(pw_mon.core); - pw_core_destroy(pw_mon.core, NULL); } if (pw_mon.context) { pw_context_destroy(pw_mon.context); } if (pw_mon.loop) { - pw_loop_leave(pw_mon.loop); pw_loop_destroy(pw_mon.loop); } } diff --git a/src/modules/signal.c b/src/modules/signal.c index 5033be3..87e2ec1 100644 --- a/src/modules/signal.c +++ b/src/modules/signal.c @@ -5,8 +5,17 @@ MODULE("SIGNAL"); -static void module_pre_start(void) { - +static sigset_t mask; + +/* + * Block signals for any thread spawned, + * so that only main thread will indeed receive SIGTERM/SIGINT signals + */ +static _ctor_sig_ void block_signals(void) { + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + sigprocmask(SIG_BLOCK, &mask, NULL); } static bool check(void) { @@ -18,11 +27,6 @@ static bool evaluate(void) { } static void init(void) { - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, SIGINT); - sigaddset(&mask, SIGTERM); - sigprocmask(SIG_BLOCK, &mask, NULL); m_register_fd(signalfd(-1, &mask, 0), true, NULL); } diff --git a/src/utils/bus_utils.c b/src/utils/bus_utils.c index 647a17e..d4a6214 100644 --- a/src/utils/bus_utils.c +++ b/src/utils/bus_utils.c @@ -8,23 +8,25 @@ int bus_sender_fill_creds(sd_bus_message *m) { xdg_runtime_dir[0] = 0; xauth_path[0] = 0; + int ret = -1; sd_bus_creds *c = NULL; sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &c); if (c) { uid_t uid = 0; if (sd_bus_creds_get_euid(c, &uid) >= 0) { snprintf(xdg_runtime_dir, PATH_MAX, "/run/user/%d", uid); - struct passwd *p = NULL; setpwent(); - for(p = getpwent(); p; p = getpwent()) { + for (struct passwd *p = getpwent(); p; p = getpwent()) { if (p->pw_uid == uid && p->pw_dir) { snprintf(xauth_path, PATH_MAX, "%s/.Xauthority", p->pw_dir); - return 0;; + ret = 0;; + break; } } + endpwent(); } } - return -1; + return ret; } const char *bus_sender_runtime_dir(void) { From 33dc769b5eca9598a27c0901435fd45e6cf6bdd4 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Wed, 1 Dec 2021 22:20:28 +0100 Subject: [PATCH 09/23] Fixed block_signals initialization before any ctor. Signed-off-by: Federico Di Pierro --- TODO.md | 1 - src/commons.h | 11 ++--------- src/modules/signal.c | 20 +++++++++++++++----- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/TODO.md b/TODO.md index eaa83b3..2a3a734 100644 --- a/TODO.md +++ b/TODO.md @@ -69,7 +69,6 @@ - [x] test X - [ ] test wl - [x] Fix clightd not cleanly exiting when built with DDC or YOCTOLIGHT (most probably libusb or whatever is creating another thread that is stealing the signal!) -DDC? ## 5.x - [ ] Keep it up to date with possible ddcutil api changes diff --git a/src/commons.h b/src/commons.h index f38cb2a..27dc600 100644 --- a/src/commons.h +++ b/src/commons.h @@ -21,15 +21,8 @@ #define SIZE(x) (sizeof(x) / sizeof(*x)) -/* - * Used for the signal blocking ctor; - * it is needed as some libraries (libusb, libddcutil, libpipewire) spawn threads - * that would mess with the signal receiving mechanism of Clightd - */ -#define _ctor_sig_ __attribute__((constructor (101))) - -#define _ctor_ __attribute__((constructor (102))) // Used for plugins registering (sensor, gamma, dpms, screen) and libusb/libpipewire init -#define _dtor_ __attribute__((destructor (102))) // Used for libusb and libpipewire dtor +#define _ctor_ __attribute__((constructor (101))) // Used for plugins registering (sensor, gamma, dpms, screen) and libusb/libpipewire init +#define _dtor_ __attribute__((destructor (101))) // Used for libusb and libpipewire dtor /* Used by dpms, gamma and screen*/ #define UNSUPPORTED INT_MIN diff --git a/src/modules/signal.c b/src/modules/signal.c index 87e2ec1..bac01fb 100644 --- a/src/modules/signal.c +++ b/src/modules/signal.c @@ -7,17 +7,27 @@ MODULE("SIGNAL"); static sigset_t mask; -/* - * Block signals for any thread spawned, - * so that only main thread will indeed receive SIGTERM/SIGINT signals - */ -static _ctor_sig_ void block_signals(void) { + +static void block_signals(void) { sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM); sigprocmask(SIG_BLOCK, &mask, NULL); } +/* + * Block signals for any thread spawned, + * so that only main thread will indeed receive SIGTERM/SIGINT signals + * + * It is needed as some libraries (libusb, libddcutil, libpipewire) spawn threads + * that would mess with the signal receiving mechanism of Clightd + * + * NOTE: we cannot use a normal constructor because libraries ctor are + * always run before program ctor, as expected; + * see: https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/elf/dl-init.c#L109 + */ +__attribute__((section(".preinit_array"), used)) static typeof(block_signals) *preinit_p = block_signals; + static bool check(void) { return true; } From c72d0aea489955e89642eec174ce7379ac5f0629 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Fri, 3 Dec 2021 23:00:05 +0100 Subject: [PATCH 10/23] Some more improvements to Pipewire plugin: * Implemented crop settings * unified camera and pipewire sensor settings Signed-off-by: Federico Di Pierro --- TODO.md | 7 +- src/modules/sensors/camera.c | 226 +++++++------------------------- src/modules/sensors/camera.h | 231 ++++++++++++++++++++------------- src/modules/sensors/pipewire.c | 130 +++++++++---------- 4 files changed, 249 insertions(+), 345 deletions(-) diff --git a/TODO.md b/TODO.md index 2a3a734..e6520fe 100644 --- a/TODO.md +++ b/TODO.md @@ -50,7 +50,7 @@ ### Pipewire - [x] Support pipewire for Camera sensor? This would allow multiple application sharing camera - [x] Pipewire run as root needs XDG_RUNTIME_DIR env -> workaround: find the first /run/user folder and use that -- [ ] Unify camera settings between camera and pipewire sensors... ? +- [x] Unify camera settings between camera and pipewire sensors... ? - [x] Support monitor sensor api for pipewire - [x] Fix segfault - [x] Fix subsequent Capture @@ -61,13 +61,16 @@ - [x] Free list of nodes upon exit! - [x] Fix xdg_runtime_dir set to create monitor - [x] Fix memleaks +- [x] Support crop settings +- [ ] Fix set_camera_setting() impl? seems like a bug in pipewire -> maybe use v4l2 plugin? https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/master/spa/plugins/v4l2/v4l2-source.c#L353 ? + ### Generic - [x] When built with ddcutil, clightd.service should be started after systemd-modules-load.service - [x] Show commit hash in version - [x] All api that require eg Xauth or xdg rutime user, fallback at automatically fetching a default value given the caller: - [x] test X -- [ ] test wl +- [x] test wl - [x] Fix clightd not cleanly exiting when built with DDC or YOCTOLIGHT (most probably libusb or whatever is creating another thread that is stealing the signal!) ## 5.x diff --git a/src/modules/sensors/camera.c b/src/modules/sensors/camera.c index c614e26..2a0084e 100644 --- a/src/modules/sensors/camera.c +++ b/src/modules/sensors/camera.c @@ -8,9 +8,6 @@ #define CAMERA_NAME "Camera" #define CAMERA_SUBSYSTEM "video4linux" -#define SET_V4L2(id, val) set_v4l2_control(id, val, #id, true) -#define SET_V4L2_DEF(id) set_v4l2_control_def(id, #id) - struct buffer { uint8_t *start; size_t length; @@ -22,36 +19,16 @@ struct mjpeg_dec { int (*dec_cb)(uint8_t **frame, int len); }; -typedef enum { X_AXIS, Y_AXIS, MAX_AXIS } crop_axis; -typedef enum { DISABLED, CROP_API, SELECTION_API, MANUAL} crop_type_t; - -typedef struct { - bool enabled; - double area_pct[2]; // start - end -} crop_info_t; - struct state { int device_fd; uint32_t pixelformat; - uint32_t width; - uint32_t height; - crop_info_t crop[MAX_AXIS]; - crop_type_t crop_type; + uint32_t width; // real width, can be cropped + uint32_t height; // real height, can be cropped struct buffer buf; - char *settings; struct mjpeg_dec *decoder; - map_t *stored_values; }; -static void set_v4l2_control_def(uint32_t id, const char *name); -static void set_v4l2_control(uint32_t id, int32_t val, const char *name, bool store); -static void set_camera_settings_def(void); static void inline fill_crop_rect(crop_info_t *cr, struct v4l2_rect *rect); -static int set_selection(crop_info_t *cr); -static int set_crop(crop_info_t *cr); -static int try_set_crop(crop_info_t *crop); -static void set_camera_settings(void); -static void restore_camera_settings(void); static int set_camera_fmt(void); static int check_camera_caps(void); static void create_decoder(void); @@ -104,7 +81,6 @@ static void destroy_dev(void *dev) { if (state.device_fd >= 0) { close(state.device_fd); } - map_free(state.stored_values); /* reset state */ memset(&state, 0, sizeof(struct state)); state.device_fd = -1; @@ -123,12 +99,10 @@ static void destroy_monitor(void) { } static int capture(void *dev, double *pct, const int num_captures, char *settings) { - state.settings = settings; - state.stored_values = map_new(true, free); int ctr = 0; if (set_camera_fmt() == 0 && init_mmap() == 0 && start_stream() == 0) { - set_camera_settings(); + set_camera_settings(&state, settings); create_decoder(); for (int i = 0; i < num_captures; i++) { struct v4l2_buffer buf = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .memory = V4L2_MEMORY_MMAP}; @@ -138,91 +112,60 @@ static int capture(void *dev, double *pct, const int num_captures, char *setting } destroy_decoder(); stop_stream(); - restore_camera_settings(); + restore_camera_settings(&state); } destroy_mmap(); return ctr; } -static void set_v4l2_control_def(uint32_t id, const char *name) { - struct v4l2_queryctrl arg = {0}; - arg.id = id; - if (-1 == xioctl(VIDIOC_QUERYCTRL, &arg)) { - INFO("%s unsupported\n", name); - } else { - INFO("%s (%u) default val: %d\n", name, id, arg.default_value); - set_v4l2_control(id, arg.default_value, name, true); - } -} - -static void set_v4l2_control(uint32_t id, int32_t val, const char *name, bool store) { +static struct v4l2_control *set_camera_setting(void *priv, uint32_t id, float val, const char *name, bool store) { struct v4l2_control old_ctrl = {0}; old_ctrl.id = id; + int32_t v = (int32_t)val; + /* Store initial value, if set. */ if (-1 == xioctl(VIDIOC_G_CTRL, &old_ctrl)) { INFO("'%s' unsupported\n", name); - return; + return NULL; } - if (old_ctrl.value != val) { + if (old_ctrl.value != v) { struct v4l2_control ctrl ={0}; ctrl.id = id; - ctrl.value = val; + ctrl.value = v; if (-1 == xioctl(VIDIOC_S_CTRL, &ctrl)) { INFO("%s unsupported\n", name); } else { - INFO("Set '%s' val: %d\n", name, val); + INFO("Set '%s' val: %d\n", name, v); if (store) { struct v4l2_control *store_ctrl = calloc(1, sizeof(struct v4l2_control)); if (store_ctrl) { memcpy(store_ctrl, &old_ctrl, sizeof(struct v4l2_control)); - INFO("Storing initial setting for '%s': %d\n", name, val); - map_put(state.stored_values, name, (void *)store_ctrl); + INFO("Storing initial setting for '%s': %d\n", name, v); + return store_ctrl; } else { INFO("failed to store initial setting for '%s'\n", name) } } } } else { - INFO("Value %d for '%s' already set.\n", val, name); + INFO("Value %d for '%s' already set.\n", v, name); } -} - -/* Properly set everything to default value */ -static void set_camera_settings_def(void) { - SET_V4L2_DEF(V4L2_CID_SCENE_MODE); - SET_V4L2_DEF(V4L2_CID_AUTO_WHITE_BALANCE); - SET_V4L2_DEF(V4L2_CID_EXPOSURE_AUTO); - SET_V4L2_DEF(V4L2_CID_AUTOGAIN); - SET_V4L2_DEF(V4L2_CID_ISO_SENSITIVITY_AUTO); - SET_V4L2_DEF(V4L2_CID_BACKLIGHT_COMPENSATION); - SET_V4L2_DEF(V4L2_CID_AUTOBRIGHTNESS); - - SET_V4L2_DEF(V4L2_CID_WHITE_BALANCE_TEMPERATURE); - SET_V4L2_DEF(V4L2_CID_EXPOSURE_ABSOLUTE); - SET_V4L2_DEF(V4L2_CID_IRIS_ABSOLUTE); - SET_V4L2_DEF(V4L2_CID_GAIN); - SET_V4L2_DEF(V4L2_CID_ISO_SENSITIVITY); - SET_V4L2_DEF(V4L2_CID_BRIGHTNESS); + return NULL; } static void inline fill_crop_rect(crop_info_t *cr, struct v4l2_rect *rect) { - if (state.crop[Y_AXIS].enabled) { - const double *a_pct = cr[Y_AXIS].area_pct; - const double height_pct = a_pct[1] - a_pct[0]; - rect->height = height_pct * state.height; - rect->top = a_pct[0] * state.height; - } - if (state.crop[X_AXIS].enabled) { - const double *a_pct = cr[X_AXIS].area_pct; - const double width_pct = a_pct[1] - a_pct[0]; - rect->width = width_pct * state.width; - rect->left = a_pct[0] * state.width; - } + const double height_pct = cr[Y_AXIS].area_pct[1] - cr[Y_AXIS].area_pct[0]; + rect->height = height_pct * state.height; + rect->top = cr[Y_AXIS].area_pct[0] * state.height; + + const double width_pct = cr[X_AXIS].area_pct[1] - cr[X_AXIS].area_pct[0]; + rect->width = width_pct * state.width; + rect->left = cr[X_AXIS].area_pct[0] * state.width; } // https://www.kernel.org/doc/html/v4.12/media/uapi/v4l/vidioc-g-selection.html -static int set_selection(crop_info_t *cr) { +static int set_selection(crop_info_t *cr) { struct v4l2_selection selection = {0}; selection.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; selection.target = V4L2_SEL_TGT_CROP; @@ -240,7 +183,6 @@ static int set_selection(crop_info_t *cr) { INFO("VIDIOC_S_SELECTION failed: %m\n"); return -errno; } - state.crop_type = SELECTION_API; return 0; } @@ -269,89 +211,32 @@ static int set_crop(crop_info_t *cr) { INFO("VIDIOC_S_CROP failed: %m\n"); return -errno; } - state.crop_type = CROP_API; return 0; } -static int try_set_crop(crop_info_t *crop) { - /** Try "new" selection API **/ - if (set_selection(crop) != 0) { - /** Try "old" crop API **/ - return set_crop(crop); - } - return 0; -} - -/* Parse settings string! */ -static void set_camera_settings(void) { - /* Set default values */ - set_camera_settings_def(); - if (state.settings && strlen(state.settings)) { - char *token; - char *rest = state.settings; - - while ((token = strtok_r(rest, ",", &rest))) { - uint32_t v4l2_op; - int32_t v4l2_val; - char axis; - double area_pct[2]; - - if (sscanf(token, "%u=%d", &v4l2_op, &v4l2_val) == 2) { - SET_V4L2(v4l2_op, v4l2_val); - } else if (sscanf(token, "%c=%lf-%lf", &axis, &area_pct[0], &area_pct[1]) == 3) { - int8_t crop_idx = -1; - if (area_pct[0] >= area_pct[1]) { - fprintf(stderr, "Start should be lesser than end: %lf-%lf\n", area_pct[0], area_pct[1]); - } else { - switch (axis) { - case 'x': - crop_idx = X_AXIS; - break; - case 'y': - crop_idx = Y_AXIS; - break; - default: - fprintf(stderr, "wrong axis specified: %c; 'x' or 'y' supported.\n", axis); - break; - } - } - if (crop_idx != -1 && !state.crop[crop_idx].enabled) { - state.crop[crop_idx].enabled = true; - state.crop[crop_idx].area_pct[0] = area_pct[0]; - state.crop[crop_idx].area_pct[1] = area_pct[1]; - } - } else { - fprintf(stderr, "Expected a=b format in '%s' token.\n", token); - } - } - if (state.crop[X_AXIS].enabled || state.crop[Y_AXIS].enabled) { - if (try_set_crop(state.crop) != 0) { - INFO("Unsupported crop/selection v4l2 API; fallback at manually skipping pixels.\n") - state.crop_type = MANUAL; - } +static int try_set_crop(void *priv, crop_info_t *crop, crop_type_t *crop_type) { + int ret; + int cr_type = *crop_type > 0 ? *crop_type : SELECTION_API; + do { + switch (cr_type) { + case SELECTION_API: + ret = set_selection(crop); + break; + case CROP_API: + ret = set_crop(crop); + break; + default: + break; } + } while (ret != 0 && *crop_type != cr_type--); + if (ret == 0) { + *crop_type = cr_type; + + // Update our pixel size as we are cropping + state.width *= crop[X_AXIS].area_pct[1] - crop[X_AXIS].area_pct[0]; + state.height *= crop[Y_AXIS].area_pct[1] - crop[Y_AXIS].area_pct[0]; } -} - -static void restore_camera_settings(void) { - for (map_itr_t *itr = map_itr_new(state.stored_values); itr; itr = map_itr_next(itr)) { - struct v4l2_control *old_ctrl = map_itr_get_data(itr); - const char *ctrl_name = map_itr_get_key(itr); - INFO("Restoring setting for '%s'\n", ctrl_name) - set_v4l2_control(old_ctrl->id, old_ctrl->value, ctrl_name, false); - } - - // Restore crop if needed - switch (state.crop_type) { - case SELECTION_API: - set_selection(NULL); - break; - case CROP_API: - set_crop(NULL); - break; - default: - break; - } + return ret; } static int set_camera_fmt(void) { @@ -566,28 +451,7 @@ static double compute_brightness(unsigned int size) { .col_start = 0, .col_end = state.width, }; - - if (state.crop_type == MANUAL) { - // Manual crop if needed - rect_info_t crop = { - .row_start = 0, - .row_end = state.height, - .col_start = 0, - .col_end = state.width, - }; - if (state.crop[X_AXIS].enabled) { - crop.col_start = state.crop[X_AXIS].area_pct[0] * state.width; - crop.col_end = state.crop[X_AXIS].area_pct[1] * state.width; - } - if (state.crop[Y_AXIS].enabled) { - crop.row_start = state.crop[Y_AXIS].area_pct[0] * state.height; - crop.row_end = state.crop[Y_AXIS].area_pct[1] * state.height; - } - INFO("Manual crop: rows[%d-%d], cols[%d-%d]\n", crop.row_start, crop.row_end, crop.col_start, crop.col_end); - brightness = get_frame_brightness(img_data, &crop, &full, (state.pixelformat == V4L2_PIX_FMT_YUYV)); - } else { - brightness = get_frame_brightness(img_data, NULL, &full, (state.pixelformat == V4L2_PIX_FMT_YUYV)); - } + brightness = get_frame_brightness(img_data, &full, (state.pixelformat == V4L2_PIX_FMT_YUYV)); if (img_data != state.buf.start) { free(img_data); } diff --git a/src/modules/sensors/camera.h b/src/modules/sensors/camera.h index 5b12d32..0688ab8 100644 --- a/src/modules/sensors/camera.h +++ b/src/modules/sensors/camera.h @@ -14,6 +14,14 @@ #define CAMERA_ILL_MAX 255 #define HISTOGRAM_STEPS 40 +#define SET_V4L2(op, val) \ +do { \ + struct v4l2_control *data = set_camera_setting(priv, op, val, #op, true); \ + if (data) { \ + map_put(stored_values, #op, data); \ + } \ +} while(false) + typedef struct { int row_start; int row_end; @@ -26,7 +34,23 @@ struct histogram { double sum; }; -static double get_frame_brightness(uint8_t *img_data, rect_info_t *crop, rect_info_t *full, bool is_yuv) { +typedef enum { X_AXIS, Y_AXIS, MAX_AXIS } crop_axis; +typedef enum { DISABLED, CROP_API, SELECTION_API, MANUAL} crop_type_t; + +typedef struct { + bool enabled; + double area_pct[2]; // start - end +} crop_info_t; + +static struct v4l2_control *set_camera_setting(void *priv, uint32_t op, float val, const char *op_name, bool store); +static int try_set_crop(void *priv, crop_info_t *crop, crop_type_t *crop_type); + +static map_t *stored_values; +static crop_info_t crop[MAX_AXIS]; +static crop_type_t crop_type; +static bool camera_set; + +static double get_frame_brightness(uint8_t *img_data, rect_info_t *full, bool is_yuv) { double brightness = 0.0; double min = CAMERA_ILL_MAX; double max = 0.0; @@ -37,15 +61,24 @@ static double get_frame_brightness(uint8_t *img_data, rect_info_t *crop, rect_in */ const int inc = 1 + is_yuv; - // If no crop is requested, just go with full image - if (crop == NULL) { - crop = full; + rect_info_t crop_rect = *full; + if (crop_type == MANUAL) { + if (crop[X_AXIS].enabled) { + crop_rect.col_start = crop[X_AXIS].area_pct[0] * full->col_end; + crop_rect.col_end = crop[X_AXIS].area_pct[1] * full->col_end; + } + if (crop[Y_AXIS].enabled) { + crop_rect.row_start = crop[Y_AXIS].area_pct[0] * full->row_end; + crop_rect.row_end = crop[Y_AXIS].area_pct[1] * full->row_end; + } + INFO("Manual crop: rows[%d-%d], cols[%d-%d]\n", crop_rect.row_start, crop_rect.row_end, + crop_rect.col_start, crop_rect.col_end); } /* Find minimum and maximum brightness */ int total = 0; // compute total used pixels - for (int row = crop->row_start; row < crop->row_end; row++) { - for (int col = crop->col_start; col < crop->col_end * inc; col += inc) { + for (int row = crop_rect.row_start; row < crop_rect.row_end; row++) { + for (int col = crop_rect.col_start; col < crop_rect.col_end * inc; col += inc) { const int idx = (row * full->col_end * inc) + col; if (img_data[idx] < min) { min = img_data[idx]; @@ -66,8 +99,8 @@ static double get_frame_brightness(uint8_t *img_data, rect_info_t *crop, rect_in /* Calculate histogram */ struct histogram hist[HISTOGRAM_STEPS] = {0}; const double step_size = (max - min) / HISTOGRAM_STEPS; - for (int row = crop->row_start; row < crop->row_end; row++) { - for (int col = crop->col_start; col < crop->col_end * inc; col += inc) { + for (int row = crop_rect.row_start; row < crop_rect.row_end; row++) { + for (int col = crop_rect.col_start; col < crop_rect.col_end * inc; col += inc) { const int idx = (row * full->col_end * inc) + col; int bucket = (img_data[idx] - min) / step_size; if (bucket >= 0 && bucket < HISTOGRAM_STEPS) { @@ -122,86 +155,104 @@ static double get_frame_brightness(uint8_t *img_data, rect_info_t *crop, rect_in return (double)brightness / CAMERA_ILL_MAX; } -// typedef enum { X_AXIS, Y_AXIS, MAX_AXIS } crop_axis; -// typedef enum { DISABLED, CROP_API, SELECTION_API, MANUAL} crop_type_t; -// -// typedef struct { -// bool enabled; -// double area_pct[2]; // start - end -// } crop_info_t; +static void set_camera_settings_def(void *priv) { + SET_V4L2(V4L2_CID_SCENE_MODE, -1); + SET_V4L2(V4L2_CID_AUTO_WHITE_BALANCE, -1); + SET_V4L2(V4L2_CID_EXPOSURE_AUTO, -1); + SET_V4L2(V4L2_CID_AUTOGAIN, -1); + SET_V4L2(V4L2_CID_ISO_SENSITIVITY_AUTO, -1); + SET_V4L2(V4L2_CID_BACKLIGHT_COMPENSATION, -1); + SET_V4L2(V4L2_CID_AUTOBRIGHTNESS, -1); + + SET_V4L2(V4L2_CID_WHITE_BALANCE_TEMPERATURE, -1); + SET_V4L2(V4L2_CID_EXPOSURE_ABSOLUTE, -1); + SET_V4L2(V4L2_CID_IRIS_ABSOLUTE, -1); + SET_V4L2(V4L2_CID_GAIN, -1); + SET_V4L2(V4L2_CID_ISO_SENSITIVITY, -1); + SET_V4L2(V4L2_CID_BRIGHTNESS, -1); +} -// extern void set_camera_settings_def(void *priv); -// extern void set_camera_setting(void *priv, uint32_t op, float val, bool store); -// -// #define SET_V4L2(id, val) set_camera_setting(id, val, #id, true) -// -// static void set_camera_settings(void *priv, char *settings, crop_info_t *crop, crop_type_t *crop_type) { -// /* Set default values */ -// set_camera_settings_def(priv); -// if (settings && strlen(settings)) { -// char *token; -// char *rest = settings; -// -// while ((token = strtok_r(rest, ",", &rest))) { -// uint32_t v4l2_op; -// int32_t v4l2_val; -// char axis; -// double area_pct[2]; -// -// if (sscanf(token, "%u=%d", &v4l2_op, &v4l2_val) == 2) { -// SET_V4L2(v4l2_op, v4l2_val); -// } else if (sscanf(token, "%c=%lf-%lf", &axis, &area_pct[0], &area_pct[1]) == 3) { -// int8_t crop_idx = -1; -// if (area_pct[0] >= area_pct[1]) { -// fprintf(stderr, "Start should be lesser than end: %lf-%lf\n", area_pct[0], area_pct[1]); -// } else { -// switch (axis) { -// case 'x': -// crop_idx = X_AXIS; -// break; -// case 'y': -// crop_idx = Y_AXIS; -// break; -// default: -// fprintf(stderr, "wrong axis specified: %c; 'x' or 'y' supported.\n", axis); -// break; -// } -// } -// if (crop_idx != -1 && !crop[crop_idx].enabled) { -// crop[crop_idx].enabled = true; -// crop[crop_idx].area_pct[0] = area_pct[0]; -// crop[crop_idx].area_pct[1] = area_pct[1]; -// } -// } else { -// fprintf(stderr, "Expected a=b format in '%s' token.\n", token); -// } -// } -// if (crop[X_AXIS].enabled || crop[Y_AXIS].enabled) { -// if (try_set_crop(state.crop) != 0) { -// INFO("Unsupported crop/selection v4l2 API; fallback at manually skipping pixels.\n") -// *crop_type = MANUAL; -// } -// } -// } -// } -// -// static void restore_camera_settings(map_t *stored_values, crop_type_t crop_type) { -// for (map_itr_t *itr = map_itr_new(stored_values); itr; itr = map_itr_next(itr)) { -// struct v4l2_control *old_ctrl = map_itr_get_data(itr); -// const char *ctrl_name = map_itr_get_key(itr); -// INFO("Restoring setting for '%s'\n", ctrl_name) -// set_camera_setting(old_ctrl->id, old_ctrl->value, ctrl_name, false); -// } -// -// // Restore crop if needed -// switch (crop_type) { -// case SELECTION_API: -// set_selection(NULL); -// break; -// case CROP_API: -// set_crop(NULL); -// break; -// default: -// break; -// } -// } +static void set_camera_settings(void *priv, char *settings) { + // Already called; expect a restore_camera_settings() call + if (camera_set) { + return; + } + + /* Default values: until end of axis */ + crop[X_AXIS].area_pct[1] = 1.0; + crop[Y_AXIS].area_pct[1] = 1.0; + + stored_values = map_new(true, free); + + /* Set default values */ + set_camera_settings_def(priv); + if (settings && strlen(settings)) { + char *token; + char *rest = settings; + + while ((token = strtok_r(rest, ",", &rest))) { + uint32_t v4l2_op; + int32_t v4l2_val; + char axis; + double area_pct[2]; + + if (sscanf(token, "%u=%d", &v4l2_op, &v4l2_val) == 2) { + SET_V4L2(v4l2_op, v4l2_val); + } else if (sscanf(token, "%c=%lf-%lf", &axis, &area_pct[0], &area_pct[1]) == 3) { + int8_t crop_idx = -1; + if (area_pct[0] >= area_pct[1]) { + fprintf(stderr, "Start should be lesser than end: %lf-%lf\n", area_pct[0], area_pct[1]); + } else { + switch (axis) { + case 'x': + crop_idx = X_AXIS; + break; + case 'y': + crop_idx = Y_AXIS; + break; + default: + fprintf(stderr, "wrong axis specified: %c; 'x' or 'y' supported.\n", axis); + break; + } + } + if (crop_idx != -1 && !crop[crop_idx].enabled) { + crop[crop_idx].enabled = true; + crop[crop_idx].area_pct[0] = area_pct[0]; + crop[crop_idx].area_pct[1] = area_pct[1]; + } + } else { + fprintf(stderr, "Expected a=b format in '%s' token.\n", token); + } + } + if (crop[X_AXIS].enabled || crop[Y_AXIS].enabled) { + if (try_set_crop(priv, crop, &crop_type) != 0) { + INFO("Unsupported crop/selection v4l2 API; fallback at manually skipping pixels.\n") + crop_type = MANUAL; + } + } else { + crop_type = DISABLED; + } + } + camera_set = true; +} + +static void restore_camera_settings(void *priv) { + for (map_itr_t *itr = map_itr_new(stored_values); itr; itr = map_itr_next(itr)) { + struct v4l2_control *old_ctrl = map_itr_get_data(itr); + const char *ctrl_name = map_itr_get_key(itr); + INFO("Restoring setting for '%s'\n", ctrl_name) + set_camera_setting(priv, old_ctrl->id, old_ctrl->value, ctrl_name, false); + } + + // Restore crop if needed + if (crop_type != DISABLED && crop_type != MANUAL) { + try_set_crop(priv, NULL, &crop_type); + } + + map_free(stored_values); + stored_values = NULL; + + memset(crop, 0, sizeof(crop)); + crop_type = -1; + camera_set = false; +} diff --git a/src/modules/sensors/pipewire.c b/src/modules/sensors/pipewire.c index 3be6540..3f1987e 100644 --- a/src/modules/sensors/pipewire.c +++ b/src/modules/sensors/pipewire.c @@ -23,9 +23,9 @@ typedef struct { typedef struct { double *pct; int capture_idx; - char *settings; - map_t *stored_values; bool with_err; + uint32_t width; // real width, can be cropped + uint32_t height; // real height, can be cropped } capture_settings_t; typedef struct { @@ -44,13 +44,8 @@ typedef struct { struct spa_hook registry_listener; } pw_mon_t; -extern const struct pw_stream_control *pw_stream_get_control(struct pw_stream *stream, uint32_t id); static void build_format(struct spa_pod_builder *b, const struct spa_pod **params); static uint32_t control_to_prop_id(uint32_t control_id); -static void set_camera_setting(pw_data_t *pw, uint32_t op, float val, bool store); -static void set_camera_settings_def(pw_data_t *pw); -static void set_camera_settings(pw_data_t *pw); -static void restore_camera_settings(pw_data_t *pw); SENSOR(PW_NAME); @@ -105,12 +100,11 @@ static void on_process(void *_data) { rect_info_t full = { .row_start = 0, - .row_end = pw->format.info.raw.size.height, + .row_end = pw->cap_set.height, .col_start = 0, - .col_end = pw->format.info.raw.size.width, + .col_end = pw->cap_set.width, }; - - pw->cap_set.pct[pw->cap_set.capture_idx++] = get_frame_brightness(sdata, NULL, &full, is_yuv); + pw->cap_set.pct[pw->cap_set.capture_idx++] = get_frame_brightness(sdata, &full, is_yuv); pw_stream_queue_buffer(pw->stream, b); return; @@ -151,6 +145,12 @@ static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_p SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<stream, ¶ms, 1); + + pw->cap_set.width = pw->format.info.raw.size.width; + pw->cap_set.height = pw->format.info.raw.size.height; + + INFO("Image fmt: %d\n", pw->format.info.raw.format); + INFO("Image res: %d x %d\n", pw->cap_set.width, pw->cap_set.height); } /* these are the stream events we listen for */ @@ -220,8 +220,6 @@ static void destroy_dev(void *dev) { pw_loop_destroy(pw->loop); pw->loop = NULL; } - map_free(pw->cap_set.stored_values); - pw->cap_set.stored_values = NULL; memset(&pw->cap_set, 0, sizeof(pw->cap_set)); @@ -351,9 +349,7 @@ static void destroy_monitor(void) { static int capture(void *dev, double *pct, const int num_captures, char *settings) { pw_data_t *pw = (pw_data_t *)dev; pw->cap_set.pct = pct; - pw->cap_set.settings = settings; - pw->cap_set.stored_values = map_new(true, free); - + set_env(); const struct spa_pod *params; @@ -381,12 +377,15 @@ static int capture(void *dev, double *pct, const int num_captures, char *setting fprintf(stderr, "Can't connect: %s\n", spa_strerror(res)); } else { - set_camera_settings(pw); pw_loop_enter(pw->loop); while (pw->cap_set.capture_idx < num_captures && !pw->cap_set.with_err) { if (pw_loop_iterate(pw->loop, -1) < 0) { break; } + if (pw->cap_set.width != 0) { + // Settings must be set once we receive on_params_changed() + set_camera_settings(pw, settings); + } } pw_loop_leave(pw->loop); restore_camera_settings(pw); @@ -395,8 +394,8 @@ static int capture(void *dev, double *pct, const int num_captures, char *setting return pw->cap_set.capture_idx; } -// Stolen from https://github.com/PipeWire/pipewire/blob/master/spa/plugins/v4l2/v4l2-utils.c#L1017 -static uint32_t control_to_prop_id(uint32_t control_id) { +// Stolen from https://github.com/PipeWire/pipewire/blob/master/spa/plugins/v4l2/v4l2-utils.c#L1049 +static inline enum spa_prop control_to_prop_id(uint32_t control_id) { switch (control_id) { case V4L2_CID_BRIGHTNESS: return SPA_PROP_brightness; @@ -419,78 +418,65 @@ static uint32_t control_to_prop_id(uint32_t control_id) { } } -static void set_camera_setting(pw_data_t *pw, uint32_t op, float val, bool store) { +static struct v4l2_control *set_camera_setting(void *priv, uint32_t op, float val, const char *op_name, bool store) { + pw_data_t *pw = (pw_data_t *)priv; enum spa_prop pw_op = control_to_prop_id(op); const struct pw_stream_control *ctrl = pw_stream_get_control(pw->stream, pw_op); if (ctrl) { - INFO("%s (%u) default val: %.2lf\n", ctrl->name, op, ctrl->def); + INFO("%s (%u) default val: %.2lf\n", op_name, pw_op, ctrl->def); if (val < 0) { val = ctrl->def; } if (ctrl->values[0] != val) { float old_val = ctrl->values[0]; - pw_stream_set_control(pw->stream, pw_op, 1, &val); - INFO("Set '%s' val: %.2lf\n", ctrl->name, val); - if (store) { - INFO("Storing initial setting for '%s': %.2lf\n", ctrl->name, old_val); - struct v4l2_control *v = malloc(sizeof(struct v4l2_control)); - v->value = old_val; - v->id = op; - map_put(pw->cap_set.stored_values, ctrl->name, v); + int ret = pw_stream_set_control(pw->stream, pw_op, 1, &val); + if (ret == 0) { + INFO("Set '%s' val: %.2lf\n", op_name, val); + if (store) { + INFO("Storing initial setting for '%s': %.2lf\n", op_name, old_val); + struct v4l2_control *v = malloc(sizeof(struct v4l2_control)); + v->value = old_val; + v->id = op; + return v; + } + } else { + INFO("Failed to set '%s'.\n", op_name); } } else { INFO("Value %2.lf for '%s' already set.\n", val, ctrl->name); } } else { - INFO("%u unsupported\n", op); + INFO("'%s' unsupported\n", op_name); } + return NULL; } -static void set_camera_settings_def(pw_data_t *pw) { - set_camera_setting(pw, V4L2_CID_SCENE_MODE, -1, true); - set_camera_setting(pw, V4L2_CID_AUTO_WHITE_BALANCE, -1, true); - set_camera_setting(pw, V4L2_CID_EXPOSURE_AUTO, -1, true); - set_camera_setting(pw, V4L2_CID_AUTOGAIN, -1, true); - set_camera_setting(pw, V4L2_CID_ISO_SENSITIVITY_AUTO, -1, true); - set_camera_setting(pw, V4L2_CID_BACKLIGHT_COMPENSATION, -1, true); - set_camera_setting(pw, V4L2_CID_AUTOBRIGHTNESS, -1, true); +static int try_set_crop(void *priv, crop_info_t *crop, crop_type_t *crop_type) { + pw_data_t *pw = (pw_data_t *)priv; + uint8_t params_buffer[1024]; - set_camera_setting(pw, V4L2_CID_WHITE_BALANCE_TEMPERATURE, -1, true); - set_camera_setting(pw, V4L2_CID_EXPOSURE_ABSOLUTE, -1, true); - set_camera_setting(pw, V4L2_CID_IRIS_ABSOLUTE, -1, true); - set_camera_setting(pw, V4L2_CID_GAIN, -1, true); - set_camera_setting(pw, V4L2_CID_ISO_SENSITIVITY, -1, true); - set_camera_setting(pw, V4L2_CID_BRIGHTNESS, -1, true); -} - -/* Parse settings string! */ -static void set_camera_settings(pw_data_t *pw) { - /* Set default values */ - set_camera_settings_def(pw); - if (pw->cap_set.settings && strlen(pw->cap_set.settings)) { - char *token; - char *rest = pw->cap_set.settings; - - while ((token = strtok_r(rest, ",", &rest))) { - uint32_t v4l2_op; - int32_t v4l2_val; - if (sscanf(token, "%u=%d", &v4l2_op, &v4l2_val) == 2) { - float val = v4l2_val; - set_camera_setting(pw, v4l2_op, val, true); - } else { - fprintf(stderr, "Expected a=b format in '%s' token.\n", token); - } - } + struct spa_meta_region *reg = NULL; + if (crop != NULL) { + reg = malloc(sizeof(struct spa_meta_region)); + reg->region.size.width = (crop[X_AXIS].area_pct[1] - crop[X_AXIS].area_pct[0]) * pw->format.info.raw.size.width; + reg->region.size.height = (crop[Y_AXIS].area_pct[1] - crop[Y_AXIS].area_pct[0]) * pw->format.info.raw.size.height; + reg->region.position.x = crop[X_AXIS].area_pct[0] * pw->format.info.raw.size.width; + reg->region.position.y = crop[Y_AXIS].area_pct[0] * pw->format.info.raw.size.height; } -} - -static void restore_camera_settings(pw_data_t *pw) { - for (map_itr_t *itr = map_itr_new(pw->cap_set.stored_values); itr; itr = map_itr_next(itr)) { - struct v4l2_control *old_ctrl = map_itr_get_data(itr); - const char *ctrl_name = map_itr_get_key(itr); - INFO("Restoring setting for '%s'\n", ctrl_name) - set_camera_setting(pw, old_ctrl->id, old_ctrl->value, false); + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); + const struct spa_pod *params = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop), + SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_region)), reg); + free(reg); + int ret = pw_stream_update_params(pw->stream, ¶ms, 1); + if (ret >= 0) { + *crop_type = CROP_API; + pw->cap_set.width *= crop[X_AXIS].area_pct[1] - crop[X_AXIS].area_pct[0]; + pw->cap_set.height *= crop[Y_AXIS].area_pct[1] - crop[Y_AXIS].area_pct[0]; + ret = 0; } + return ret; } #endif From 330fc257d601dcf49a5872ba104d61d3e69f9ad6 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Sat, 4 Dec 2021 11:06:38 +0100 Subject: [PATCH 11/23] Small improvements + memleak fixed. Signed-off-by: Federico Di Pierro --- src/modules/sensors/camera.c | 4 ---- src/modules/sensors/camera.h | 12 +++++++++++- src/modules/sensors/pipewire.c | 16 +++++----------- src/utils/bus_utils.c | 1 + 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/modules/sensors/camera.c b/src/modules/sensors/camera.c index 2a0084e..468a29f 100644 --- a/src/modules/sensors/camera.c +++ b/src/modules/sensors/camera.c @@ -231,10 +231,6 @@ static int try_set_crop(void *priv, crop_info_t *crop, crop_type_t *crop_type) { } while (ret != 0 && *crop_type != cr_type--); if (ret == 0) { *crop_type = cr_type; - - // Update our pixel size as we are cropping - state.width *= crop[X_AXIS].area_pct[1] - crop[X_AXIS].area_pct[0]; - state.height *= crop[Y_AXIS].area_pct[1] - crop[Y_AXIS].area_pct[0]; } return ret; } diff --git a/src/modules/sensors/camera.h b/src/modules/sensors/camera.h index 0688ab8..8e3555b 100644 --- a/src/modules/sensors/camera.h +++ b/src/modules/sensors/camera.h @@ -62,7 +62,8 @@ static double get_frame_brightness(uint8_t *img_data, rect_info_t *full, bool is const int inc = 1 + is_yuv; rect_info_t crop_rect = *full; - if (crop_type == MANUAL) { + switch (crop_type) { + case MANUAL: if (crop[X_AXIS].enabled) { crop_rect.col_start = crop[X_AXIS].area_pct[0] * full->col_end; crop_rect.col_end = crop[X_AXIS].area_pct[1] * full->col_end; @@ -73,6 +74,15 @@ static double get_frame_brightness(uint8_t *img_data, rect_info_t *full, bool is } INFO("Manual crop: rows[%d-%d], cols[%d-%d]\n", crop_rect.row_start, crop_rect.row_end, crop_rect.col_start, crop_rect.col_end); + break; + case SELECTION_API: + case CROP_API: + // Update crop size + crop_rect.col_end *= crop[X_AXIS].area_pct[1] - crop[X_AXIS].area_pct[0]; + crop_rect.row_end *= crop[Y_AXIS].area_pct[1] - crop[Y_AXIS].area_pct[0]; + break; + default: + break; } /* Find minimum and maximum brightness */ diff --git a/src/modules/sensors/pipewire.c b/src/modules/sensors/pipewire.c index 3f1987e..7b86a67 100644 --- a/src/modules/sensors/pipewire.c +++ b/src/modules/sensors/pipewire.c @@ -24,8 +24,7 @@ typedef struct { double *pct; int capture_idx; bool with_err; - uint32_t width; // real width, can be cropped - uint32_t height; // real height, can be cropped + bool has_media_info; } capture_settings_t; typedef struct { @@ -100,9 +99,9 @@ static void on_process(void *_data) { rect_info_t full = { .row_start = 0, - .row_end = pw->cap_set.height, + .row_end = pw->format.info.raw.size.height, .col_start = 0, - .col_end = pw->cap_set.width, + .col_end = pw->format.info.raw.size.width, }; pw->cap_set.pct[pw->cap_set.capture_idx++] = get_frame_brightness(sdata, &full, is_yuv); pw_stream_queue_buffer(pw->stream, b); @@ -146,11 +145,8 @@ static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_p SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<stream, ¶ms, 1); - pw->cap_set.width = pw->format.info.raw.size.width; - pw->cap_set.height = pw->format.info.raw.size.height; - INFO("Image fmt: %d\n", pw->format.info.raw.format); - INFO("Image res: %d x %d\n", pw->cap_set.width, pw->cap_set.height); + INFO("Image res: %d x %d\n", pw->format.info.raw.size.width, pw->format.info.raw.size.height); } /* these are the stream events we listen for */ @@ -382,7 +378,7 @@ static int capture(void *dev, double *pct, const int num_captures, char *setting if (pw_loop_iterate(pw->loop, -1) < 0) { break; } - if (pw->cap_set.width != 0) { + if (pw->cap_set.has_media_info) { // Settings must be set once we receive on_params_changed() set_camera_settings(pw, settings); } @@ -472,8 +468,6 @@ static int try_set_crop(void *priv, crop_info_t *crop, crop_type_t *crop_type) int ret = pw_stream_update_params(pw->stream, ¶ms, 1); if (ret >= 0) { *crop_type = CROP_API; - pw->cap_set.width *= crop[X_AXIS].area_pct[1] - crop[X_AXIS].area_pct[0]; - pw->cap_set.height *= crop[Y_AXIS].area_pct[1] - crop[Y_AXIS].area_pct[0]; ret = 0; } return ret; diff --git a/src/utils/bus_utils.c b/src/utils/bus_utils.c index d4a6214..1575ad6 100644 --- a/src/utils/bus_utils.c +++ b/src/utils/bus_utils.c @@ -25,6 +25,7 @@ int bus_sender_fill_creds(sd_bus_message *m) { } endpwent(); } + free(c); } return ret; } From 59123a277be0aaf62778ba7e220ea63b32ba90d7 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Sat, 4 Dec 2021 11:37:30 +0100 Subject: [PATCH 12/23] Small fix. Signed-off-by: Federico Di Pierro --- TODO.md | 2 +- src/modules/sensors/pipewire.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index e6520fe..a309c65 100644 --- a/TODO.md +++ b/TODO.md @@ -62,7 +62,7 @@ - [x] Fix xdg_runtime_dir set to create monitor - [x] Fix memleaks - [x] Support crop settings -- [ ] Fix set_camera_setting() impl? seems like a bug in pipewire -> maybe use v4l2 plugin? https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/master/spa/plugins/v4l2/v4l2-source.c#L353 ? +- [ ] Fix set_camera_setting() impl? seems like a bug in pipewire (?) ### Generic diff --git a/src/modules/sensors/pipewire.c b/src/modules/sensors/pipewire.c index 7b86a67..c9da295 100644 --- a/src/modules/sensors/pipewire.c +++ b/src/modules/sensors/pipewire.c @@ -145,6 +145,7 @@ static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_p SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<stream, ¶ms, 1); + pw->cap_set.has_media_info = true; INFO("Image fmt: %d\n", pw->format.info.raw.format); INFO("Image res: %d x %d\n", pw->format.info.raw.size.width, pw->format.info.raw.size.height); } From 28206e29680e866483c705321ab1fc758eeb1b1f Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Sun, 12 Dec 2021 20:04:26 +0100 Subject: [PATCH 13/23] Fixed pipewire monitor implementation. Clightd pipewire support requires a CLIGHTD_PIPEWIRE_RUNTIME_DIR env var to be specified. It defaults to /run/user/1000/. See clightd.service to update it. Basically, Clightd will either: * start pipewire monitor right away if pipewire socket is already present in CLIGHTD_PIPEWIRE_RUNTIME_DIR * watch through inotify for pipewire socket to be created and then start the monitor If CLIGHTD_PIPEWIRE_RUNTIME_DIR is empty or points to a non-directory file, pipewire support will be disabled. Signed-off-by: Federico Di Pierro --- Scripts/clightd.service | 1 + TODO.md | 3 +- src/modules/sensors/pipewire.c | 225 ++++++++++++++++++++++----------- 3 files changed, 151 insertions(+), 78 deletions(-) diff --git a/Scripts/clightd.service b/Scripts/clightd.service index 5ea5b8c..4b9fdc2 100644 --- a/Scripts/clightd.service +++ b/Scripts/clightd.service @@ -9,6 +9,7 @@ BusName=org.clightd.clightd User=root # Default backlight vcp code; update if needed Environment=CLIGHTD_BL_VCP=0x10 +Environment=CLIGHTD_PIPEWIRE_RUNTIME_DIR="/run/user/1000/" ExecStart=@CMAKE_INSTALL_FULL_LIBEXECDIR@/clightd Restart=on-failure RestartSec=5 diff --git a/TODO.md b/TODO.md index a309c65..12eb742 100644 --- a/TODO.md +++ b/TODO.md @@ -54,8 +54,7 @@ - [x] Support monitor sensor api for pipewire - [x] Fix segfault - [x] Fix subsequent Capture -- [ ] Check if installing it on system causes pipewire module to be disabled because clightd starts before /run/user/1000 is created! --> in case, disable monitor for now and instead rely upon user-provided interface string or PW_ID_ANY (?) -> we lose IsAvailable... +- [x] Add a CLIGHTD_PW_RUNTIME_DIR env variable (in clightd.service, see CLIGHTD_BL_CODE) that defaults to /run/user/1000/. If the env variable is empty -> disable pipewire. If folder does not exist: disable pipewire. Otherwise: inotify on folder to wait for socket to appear. If socket is already there, immediately start monitoring. - [x] Use caller uid instead of defaulting to first found user during Capture! - [x] Use a map to store list of nodes? - [x] Free list of nodes upon exit! diff --git a/src/modules/sensors/pipewire.c b/src/modules/sensors/pipewire.c index c9da295..2e1a9d3 100644 --- a/src/modules/sensors/pipewire.c +++ b/src/modules/sensors/pipewire.c @@ -2,16 +2,21 @@ #include "camera.h" #include "bus_utils.h" -#include #include #include #include #include +#include +#include + +#define EVENT_SIZE ( sizeof (struct inotify_event) ) +#define EVENT_BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) ) #define PW_NAME "Pipewire" +#define PW_ENV_NAME "CLIGHTD_PIPEWIRE_RUNTIME_DIR" #define BUILD_KEY(id) \ - char key[20]; \ - snprintf(key, sizeof(key), "Node%d", id); + char key[10]; \ + snprintf(key, sizeof(key), "%d", id); typedef struct { uint32_t id; @@ -43,42 +48,70 @@ typedef struct { struct spa_hook registry_listener; } pw_mon_t; +static void free_node(void *dev); static void build_format(struct spa_pod_builder *b, const struct spa_pod **params); static uint32_t control_to_prop_id(uint32_t control_id); +static int register_monitor_fd(const char *pw_runtime_dir); SENSOR(PW_NAME); +MODULE(PW_NAME); + static pw_mon_t pw_mon; static pw_data_t *last_recved, *first_node; static map_t *nodes; -static int uid; +static int efd = -1; -static void free_node(void *dev) { - pw_data_t *pw = (pw_data_t *)dev; - pw->node.action = NULL; // see destroy_dev() impl - destroy_dev(pw); - pw_proxy_destroy(pw->node.proxy); - free(pw); +static void module_pre_start(void) { + } -static void _ctor_ init_libpipewire(void) { - pw_init(NULL, NULL); +static bool check(void) { + return true; +} + +static bool evaluate(void) { + return true; +} + +static void init(void) { nodes = map_new(true, free_node); } -static void _dtor_ destroy_libpipewire(void) { +static void receive(const msg_t *msg, const void *userdata) { + if (!msg->is_pubsub) { + if (msg->fd_msg->userptr != NULL) { + // Pipewire monitor event! Notify Sensor + uint64_t u = 1; + write(efd, &u, sizeof(uint64_t)); + } else { + // Inotify event! + char buffer[EVENT_BUF_LEN]; + size_t len = read(msg->fd_msg->fd, buffer, EVENT_BUF_LEN); + int i = 0; + while (i < len) { + struct inotify_event *event = (struct inotify_event *)&buffer[i]; + if (event->len && strcmp(event->name, "pipewire-0") == 0) { + if (register_monitor_fd(getenv(PW_ENV_NAME)) != 0) { + fprintf(stderr, "Failed to start pipewire monitor.\n"); + } + } + i += EVENT_SIZE + event->len; + } + } + } +} + +static void destroy(void) { map_free(nodes); - pw_deinit(); } -static void set_env() { - if (bus_sender_runtime_dir()) { - setenv("XDG_RUNTIME_DIR", bus_sender_runtime_dir(), 1); - } else { - char path[64]; - snprintf(path, sizeof(path), "/run/user/%d", uid); - setenv("XDG_RUNTIME_DIR", path, 1);; - } +static void free_node(void *dev) { + pw_data_t *pw = (pw_data_t *)dev; + pw->node.action = NULL; // see destroy_dev() impl + destroy_dev(pw); + pw_proxy_destroy(pw->node.proxy); + free(pw); } static void on_process(void *_data) { @@ -182,15 +215,7 @@ static bool validate_dev(void *dev) { static void fetch_dev(const char *interface, void **dev) { pw_data_t *pw = NULL; if (interface && strlen(interface)) { - uint32_t id = atoi(interface); - for (map_itr_t *itr = map_itr_new(nodes); itr; itr = map_itr_next(itr)) { - pw_data_t *node = map_itr_get_data(itr); - if (node->node.id == id) { - pw = node; - free(itr); - break; - } - } + pw = map_get(nodes, interface); } else { pw = first_node; } @@ -270,65 +295,106 @@ static const struct pw_registry_events registry_events = { .global = registry_event_global, }; -static int init_monitor(void) { - /* - * Pipewire needs an XDG_RUNTIME_DIR set; - * at this phase, we are not being called by anyone; - * thus we need to workaround this by fetching - * a real user id, and use it to build the XDG_RUNTIME_DIR env. - */ - setpwent(); - char path[64]; - for (struct passwd *p = getpwent(); p; p = getpwent()) { - snprintf(path, sizeof(path), "/run/user/%d/", p->pw_uid); - if (access(path, F_OK) == 0) { - uid = p->pw_uid; +static int register_monitor_fd(const char *pw_runtime_dir) { + setenv("XDG_RUNTIME_DIR", pw_runtime_dir, 1); + + pw_init(NULL, NULL); + int ret = -1; + do { + pw_mon.loop = pw_loop_new(NULL); + if (!pw_mon.loop) { break; } - } - endpwent(); - - // Unsupported... can this happen? - if (uid == 0) { - return -1; - } - - set_env(); + pw_mon.context = pw_context_new(pw_mon.loop, NULL, 0); + if (!pw_mon.context) { + break; + } + pw_mon.core = pw_context_connect(pw_mon.context, NULL, 0); + if (!pw_mon.core) { + break; + } + pw_mon.registry = pw_core_get_registry(pw_mon.core, PW_VERSION_REGISTRY, 0); + if (!pw_mon.registry) { + break; + } + + spa_zero(pw_mon.registry_listener); + pw_registry_add_listener(pw_mon.registry, &pw_mon.registry_listener, ®istry_events, NULL); + + int fd = pw_loop_get_fd(pw_mon.loop); + m_register_fd(fd, false, &pw_mon); + + pw_loop_enter(pw_mon.loop); + ret = 0; + } while (false); - pw_mon.loop = pw_loop_new(NULL); - if (!pw_mon.loop) { - return -1; - } - pw_mon.context = pw_context_new(pw_mon.loop, NULL, 0); - if (!pw_mon.context) { - return -1; - } - pw_mon.core = pw_context_connect(pw_mon.context, NULL, 0); - if (!pw_mon.core) { - return -1; + unsetenv("XDG_RUNTIME_DIR"); + if (ret == -1) { + destroy_monitor(); } - pw_mon.registry = pw_core_get_registry(pw_mon.core, PW_VERSION_REGISTRY, 0); - if (!pw_mon.registry) { - return -1; + return ret; +} + +static int init_monitor(void) { + const char *pw_runtime_dir = getenv(PW_ENV_NAME); + if (pw_runtime_dir && strlen(pw_runtime_dir)) { + struct stat s; + if (stat(pw_runtime_dir, &s) == -1 || !S_ISDIR(s.st_mode)) { + fprintf(stderr, "Failed to stat '%s' or not a folder. Killing pipewire.\n", pw_runtime_dir); + goto err; + } + + /* + * Pipewire needs an XDG_RUNTIME_DIR set; + * at this phase, we are not being called by anyone; + * thus we need to workaround this by fetching the pipewire socket + * for the XDG_RUNTIME_DIR specified in CLIGHTD_PIPEWIRE_RUNTIME_DIR env variable; + * eventually being notified of socket creation using inotify mechanism. + */ + char path[256]; + snprintf(path, sizeof(path), "%s/pipewire-0", pw_runtime_dir); + if (access(path, F_OK) == 0) { + // Pipewire socket already present! Register the monitor right away + if (register_monitor_fd(pw_runtime_dir) == -1) { + fprintf(stderr, "Failed to register monitor. Killing pipewire.\n"); + goto err; + } + } else { + // Register an inotify watcher + int inot_fd = inotify_init(); + if (inotify_add_watch(inot_fd, pw_runtime_dir, IN_CREATE) >= 0) { + m_register_fd(inot_fd, true, NULL); + } else { + fprintf(stderr, "Failed to watch folder '%s': %m\n", pw_runtime_dir); + close(inot_fd); + goto err; + } + } + + // Return an eventfd to notify Sensor for new devices + efd = eventfd(0, 0); + return efd; } + // Env not found. Disable. + fprintf(stderr, "No '" PW_ENV_NAME "' env found. Killing pipewire.\n"); - spa_zero(pw_mon.registry_listener); - pw_registry_add_listener(pw_mon.registry, &pw_mon.registry_listener, ®istry_events, NULL); - - int fd = pw_loop_get_fd(pw_mon.loop); - pw_loop_enter(pw_mon.loop); - - unsetenv("XDG_RUNTIME_DIR"); - return fd; +err: + return -1; } static void recv_monitor(void **dev) { + // Consume the eventfd + uint64_t u; + read(efd, &u, sizeof(uint64_t)); + + // Actually search for new nodes last_recved = NULL; pw_loop_iterate(pw_mon.loop, 0); *dev = last_recved; } static void destroy_monitor(void) { + printf("destroy_mon\n"); if (pw_mon.registry) { pw_proxy_destroy((struct pw_proxy*)pw_mon.registry); } @@ -341,13 +407,20 @@ static void destroy_monitor(void) { if (pw_mon.loop) { pw_loop_destroy(pw_mon.loop); } + memset(&pw_mon, 0, sizeof(pw_mon)); + + if (efd != -1) { + close(efd); + } + + pw_deinit(); } static int capture(void *dev, double *pct, const int num_captures, char *settings) { pw_data_t *pw = (pw_data_t *)dev; pw->cap_set.pct = pct; - set_env(); + setenv("XDG_RUNTIME_DIR", bus_sender_runtime_dir(), 1); const struct spa_pod *params; uint8_t buffer[1024]; From 8338c255e8ac66ba21d79c09258f3e3b5cbcca43 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Sun, 12 Dec 2021 20:07:21 +0100 Subject: [PATCH 14/23] Added comment. Signed-off-by: Federico Di Pierro --- Scripts/clightd.service | 1 + 1 file changed, 1 insertion(+) diff --git a/Scripts/clightd.service b/Scripts/clightd.service index 4b9fdc2..2b49291 100644 --- a/Scripts/clightd.service +++ b/Scripts/clightd.service @@ -9,6 +9,7 @@ BusName=org.clightd.clightd User=root # Default backlight vcp code; update if needed Environment=CLIGHTD_BL_VCP=0x10 +# Default pipewire runtime dir watched by Clightd Environment=CLIGHTD_PIPEWIRE_RUNTIME_DIR="/run/user/1000/" ExecStart=@CMAKE_INSTALL_FULL_LIBEXECDIR@/clightd Restart=on-failure From 67fd937cbdffb9036247c3733c248f6e7d8ee64d Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Sun, 12 Dec 2021 21:55:52 +0100 Subject: [PATCH 15/23] Only set SystemdService= in dbus service if systemd is found. Signed-off-by: Federico Di Pierro --- CMakeLists.txt | 4 +++- Scripts/clightd.service | 2 +- Scripts/org.clightd.clightd.service | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 737f61b..aa8fb8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,7 +112,7 @@ set_target_properties( # Installation of targets (must be before file configuration to work) install(TARGETS ${PROJECT_NAME} - RUNTIME DESTINATION "${CMAKE_INSTALL_LIBEXECDIR}") + RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_LIBEXECDIR}") set(SCRIPT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Scripts") @@ -143,6 +143,8 @@ if(SYSTEMD_BASE_FOUND) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/clightd.service DESTINATION ${SYSTEMD_SERVICE_DIR}) + + set(SYSTEMD_SERVICE "SystemdService=clightd.service") endif() # Install dbus service diff --git a/Scripts/clightd.service b/Scripts/clightd.service index 2b49291..5dc2e24 100644 --- a/Scripts/clightd.service +++ b/Scripts/clightd.service @@ -10,7 +10,7 @@ User=root # Default backlight vcp code; update if needed Environment=CLIGHTD_BL_VCP=0x10 # Default pipewire runtime dir watched by Clightd -Environment=CLIGHTD_PIPEWIRE_RUNTIME_DIR="/run/user/1000/" +Environment=CLIGHTD_PIPEWIRE_RUNTIME_DIR=/run/user/1000/ ExecStart=@CMAKE_INSTALL_FULL_LIBEXECDIR@/clightd Restart=on-failure RestartSec=5 diff --git a/Scripts/org.clightd.clightd.service b/Scripts/org.clightd.clightd.service index e80f75e..4adf6da 100644 --- a/Scripts/org.clightd.clightd.service +++ b/Scripts/org.clightd.clightd.service @@ -2,4 +2,4 @@ Name=org.clightd.clightd Exec=@CMAKE_INSTALL_FULL_LIBEXECDIR@/clightd User=root -SystemdService=clightd.service +@SYSTEMD_SERVICE@ From d9e7699331717ed8447d4f19b73f9352017739fb Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Tue, 14 Dec 2021 21:56:37 +0100 Subject: [PATCH 16/23] Fixed a small bug in pipewire, leading to a crash. Moreover, avoid using strlen() to check that strings are not empty. Signed-off-by: Federico Di Pierro --- TODO.md | 6 ++++-- src/modules/backlight2.c | 2 +- src/modules/screen_plugins/fb.c | 2 +- src/modules/sensors/als.h | 2 +- src/modules/sensors/camera.h | 2 +- src/modules/sensors/custom.c | 4 ++-- src/modules/sensors/pipewire.c | 27 +++++++++++++++++++-------- src/utils/drm_utils.c | 2 +- src/utils/udev.c | 2 +- 9 files changed, 31 insertions(+), 18 deletions(-) diff --git a/TODO.md b/TODO.md index 12eb742..0b8b116 100644 --- a/TODO.md +++ b/TODO.md @@ -55,14 +55,14 @@ - [x] Fix segfault - [x] Fix subsequent Capture - [x] Add a CLIGHTD_PW_RUNTIME_DIR env variable (in clightd.service, see CLIGHTD_BL_CODE) that defaults to /run/user/1000/. If the env variable is empty -> disable pipewire. If folder does not exist: disable pipewire. Otherwise: inotify on folder to wait for socket to appear. If socket is already there, immediately start monitoring. +- [ ] Document the new env variable! - [x] Use caller uid instead of defaulting to first found user during Capture! - [x] Use a map to store list of nodes? - [x] Free list of nodes upon exit! - [x] Fix xdg_runtime_dir set to create monitor - [x] Fix memleaks - [x] Support crop settings -- [ ] Fix set_camera_setting() impl? seems like a bug in pipewire (?) - +- [ ] Fix set_camera_setting() impl? seems like a bug in pipewire (?) (get works, but set doesn't) ### Generic - [x] When built with ddcutil, clightd.service should be started after systemd-modules-load.service @@ -70,7 +70,9 @@ - [x] All api that require eg Xauth or xdg rutime user, fallback at automatically fetching a default value given the caller: - [x] test X - [x] test wl +- [ ] Document the new behavior! - [x] Fix clightd not cleanly exiting when built with DDC or YOCTOLIGHT (most probably libusb or whatever is creating another thread that is stealing the signal!) +- [x] do not use strlen() to only check that strign is not empty ## 5.x - [ ] Keep it up to date with possible ddcutil api changes diff --git a/src/modules/backlight2.c b/src/modules/backlight2.c index 529076b..281d683 100644 --- a/src/modules/backlight2.c +++ b/src/modules/backlight2.c @@ -111,7 +111,7 @@ MODULE("BACKLIGHT2"); } static void get_info_id(char *id, const int size, const DDCA_Display_Info *dinfo) { - if (!strlen(dinfo->sn) || !strcasecmp(dinfo->sn, "Unspecified")) { + if ((dinfo->sn[0] == '\0') || !strcasecmp(dinfo->sn, "Unspecified")) { switch(dinfo->path.io_mode) { case DDCA_IO_I2C: snprintf(id, size, "/dev/i2c-%d", dinfo->path.path.i2c_busno); diff --git a/src/modules/screen_plugins/fb.c b/src/modules/screen_plugins/fb.c index c5d7d5d..24c6fff 100644 --- a/src/modules/screen_plugins/fb.c +++ b/src/modules/screen_plugins/fb.c @@ -15,7 +15,7 @@ static int get_frame_brightness(const char *id, const char *env) { int ret = WRONG_PLUGIN; struct udev_device *dev = NULL; - if (!id || !strlen(id)) { + if (id == NULL || id[0] == '\0') { /* Fetch first matching device from udev */ get_udev_device(NULL, FB_SUBSYSTEM, NULL, NULL, &dev); if (!dev) { diff --git a/src/modules/sensors/als.h b/src/modules/sensors/als.h index 33df5fa..1a3dc40 100644 --- a/src/modules/sensors/als.h +++ b/src/modules/sensors/als.h @@ -25,7 +25,7 @@ static inline void parse_settings(char *settings, int *interval) { /* Default value */ *interval = ALS_INTERVAL; - if (settings && strlen(settings)) { + if (settings && settings[0] != '\0') { char *token; char *rest = settings; diff --git a/src/modules/sensors/camera.h b/src/modules/sensors/camera.h index 8e3555b..75629b8 100644 --- a/src/modules/sensors/camera.h +++ b/src/modules/sensors/camera.h @@ -196,7 +196,7 @@ static void set_camera_settings(void *priv, char *settings) { /* Set default values */ set_camera_settings_def(priv); - if (settings && strlen(settings)) { + if (settings && settings[0] != '\0') { char *token; char *rest = settings; diff --git a/src/modules/sensors/custom.c b/src/modules/sensors/custom.c index c45d53c..dab17ca 100644 --- a/src/modules/sensors/custom.c +++ b/src/modules/sensors/custom.c @@ -22,7 +22,7 @@ static bool validate_dev(void *dev) { static void fetch_dev(const char *interface, void **dev) { *dev = NULL; - if (interface && strlen(interface) > 0) { + if (interface && interface[0] != '\0') { char fullpath[PATH_MAX + 1] = {0}; /* User gave us a relative path! prepend CUSTOM_FLD as prefix! */ @@ -104,7 +104,7 @@ static void parse_settings(char *settings, int *min, int *max, int *interval) { *max = CUSTOM_ILL_MAX; *interval = CUSTOM_INTERVAL; - if (settings && strlen(settings)) { + if (settings && settings[0] != '\0') { char *token; char *rest = settings; diff --git a/src/modules/sensors/pipewire.c b/src/modules/sensors/pipewire.c index 2e1a9d3..73b51f6 100644 --- a/src/modules/sensors/pipewire.c +++ b/src/modules/sensors/pipewire.c @@ -58,9 +58,10 @@ SENSOR(PW_NAME); MODULE(PW_NAME); static pw_mon_t pw_mon; -static pw_data_t *last_recved, *first_node; +static pw_data_t *last_recved; static map_t *nodes; static int efd = -1; +static bool pw_inited; static void module_pre_start(void) { @@ -94,6 +95,10 @@ static void receive(const msg_t *msg, const void *userdata) { if (event->len && strcmp(event->name, "pipewire-0") == 0) { if (register_monitor_fd(getenv(PW_ENV_NAME)) != 0) { fprintf(stderr, "Failed to start pipewire monitor.\n"); + } else { + // We're done with the inotify fd; close it. + m_deregister_fd(msg->fd_msg->fd); + break; } } i += EVENT_SIZE + event->len; @@ -214,10 +219,15 @@ static bool validate_dev(void *dev) { static void fetch_dev(const char *interface, void **dev) { pw_data_t *pw = NULL; - if (interface && strlen(interface)) { + if (interface && interface[0] != '\0') { pw = map_get(nodes, interface); } else { - pw = first_node; + // Peek first pw node found + map_itr_t *itr = map_itr_new(nodes); + if (itr) { + pw = map_itr_get_data(itr); + free(itr); + } } *dev = pw; } @@ -281,9 +291,6 @@ static void registry_event_global(void *data, uint32_t id, pw->node.proxy = pw_registry_bind(pw_mon.registry, id, type, PW_VERSION_NODE, sizeof(pw_data_t)); pw_proxy_add_listener(pw->node.proxy, &pw->node.proxy_listener, &proxy_events, pw); last_recved = pw; // used by recv_monitor - if (map_length(nodes) == 0) { - first_node = pw; - } BUILD_KEY(id); map_put(nodes, key, pw); } @@ -299,6 +306,7 @@ static int register_monitor_fd(const char *pw_runtime_dir) { setenv("XDG_RUNTIME_DIR", pw_runtime_dir, 1); pw_init(NULL, NULL); + pw_inited = true; int ret = -1; do { pw_mon.loop = pw_loop_new(NULL); @@ -337,7 +345,7 @@ static int register_monitor_fd(const char *pw_runtime_dir) { static int init_monitor(void) { const char *pw_runtime_dir = getenv(PW_ENV_NAME); - if (pw_runtime_dir && strlen(pw_runtime_dir)) { + if (pw_runtime_dir && pw_runtime_dir[0] != '\0') { struct stat s; if (stat(pw_runtime_dir, &s) == -1 || !S_ISDIR(s.st_mode)) { fprintf(stderr, "Failed to stat '%s' or not a folder. Killing pipewire.\n", pw_runtime_dir); @@ -394,7 +402,10 @@ static void recv_monitor(void **dev) { } static void destroy_monitor(void) { - printf("destroy_mon\n"); + if (!pw_inited) { + return; + } + if (pw_mon.registry) { pw_proxy_destroy((struct pw_proxy*)pw_mon.registry); } diff --git a/src/utils/drm_utils.c b/src/utils/drm_utils.c index 4f50c20..676cd98 100644 --- a/src/utils/drm_utils.c +++ b/src/utils/drm_utils.c @@ -7,7 +7,7 @@ #define DRM_SUBSYSTEM "drm" int drm_open_card(const char **card) { - if (!*card || !strlen(*card)) { + if (*card == NULL || *card[0] == '\0') { /* Fetch first matching device from udev */ struct udev_device *dev = NULL; get_udev_device(NULL, DRM_SUBSYSTEM, NULL, NULL, &dev); diff --git a/src/utils/udev.c b/src/utils/udev.c index ca08f81..f9f1f31 100644 --- a/src/utils/udev.c +++ b/src/utils/udev.c @@ -33,7 +33,7 @@ void get_udev_device(const char *interface, const char *subsystem, const udev_ma sd_bus_error **ret_error, struct udev_device **dev) { *dev = NULL; /* if no interface is specified, try to get first matching device */ - if (!interface || !strlen(interface)) { + if (interface == NULL || interface[0] == '\0') { get_first_matching_device(dev, subsystem, match); } else { char *name = strrchr(interface, '/'); From ea12936e30c7f7dfb4f66341e4daa984dfcdbbd8 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Fri, 17 Dec 2021 20:31:43 +0100 Subject: [PATCH 17/23] Added CLIGHTD_BL_VCP monitor specific env variable support. Signed-off-by: Federico Di Pierro --- Scripts/clightd.service | 6 +++++- TODO.md | 3 ++- src/modules/backlight2.c | 17 +++++++++++++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Scripts/clightd.service b/Scripts/clightd.service index 5dc2e24..f229b27 100644 --- a/Scripts/clightd.service +++ b/Scripts/clightd.service @@ -7,7 +7,11 @@ Requires=@POLKIT_NAME@.service Type=dbus BusName=org.clightd.clightd User=root -# Default backlight vcp code; update if needed +# Default backlight vcp code; update if needed. +# Moreover, you can also specify per-monitor BL VCP value, +# using CLIGHTD_BL_VCP_$mon_id, where mon_id is the monitor identifier +# as seen by Clightd; you can explore them using: +# $ busctl call org.clightd.clightd /org/clightd/clightd/Backlight2 org.clightd.clightd.Backlight2 Get Environment=CLIGHTD_BL_VCP=0x10 # Default pipewire runtime dir watched by Clightd Environment=CLIGHTD_PIPEWIRE_RUNTIME_DIR=/run/user/1000/ diff --git a/TODO.md b/TODO.md index 0b8b116..aecdc2d 100644 --- a/TODO.md +++ b/TODO.md @@ -34,6 +34,7 @@ - [x] add a page about monitor hotplugging (dep on ddcutil >= 1.2.0 and refresh time!) - [x] Investigate memleaks (related to ddca_redetect_displays()?) -> see here: https://github.com/rockowitz/ddcutil/issues/202 - [x] Instead of 30s sleep, use an udev_monitor on drm subsystem? +- [x] Add support for monitor id specific CLIGHTD_BL_VCP env ### KbdBacklight - [x] call sd_bus_emit_object_added() sd_bus_emit_object_removed() When object path are created/deleted @@ -62,7 +63,7 @@ - [x] Fix xdg_runtime_dir set to create monitor - [x] Fix memleaks - [x] Support crop settings -- [ ] Fix set_camera_setting() impl? seems like a bug in pipewire (?) (get works, but set doesn't) +- [ ] Fix set_camera_setting() impl? seems like a bug in pipewire (?) (pw_stream_get_control does not work -> open issue?) ### Generic - [x] When built with ddcutil, clightd.service should be started after systemd-modules-load.service diff --git a/src/modules/backlight2.c b/src/modules/backlight2.c index 281d683..ffae2fd 100644 --- a/src/modules/backlight2.c +++ b/src/modules/backlight2.c @@ -6,6 +6,7 @@ #define BL_SUBSYSTEM "backlight" #define DRM_SUBSYSTEM "drm" +#define BL_VCP_ENV "CLIGHTD_BL_VCP" typedef struct { double target_pct; @@ -104,9 +105,9 @@ MODULE("BACKLIGHT2"); static DDCA_Vcp_Feature_Code br_code = 0x10; static void bl_load_vpcode(void) { - if (getenv("CLIGHTD_BL_VCP")) { - br_code = strtol(getenv("CLIGHTD_BL_VCP"), NULL, 16); - m_log("Set 0x%x vcp code.\n", br_code); + if (getenv(BL_VCP_ENV)) { + br_code = strtol(getenv(BL_VCP_ENV), NULL, 16); + m_log("Set default 0x%x vcp code.\n", br_code); } } @@ -423,9 +424,17 @@ static int set_external_backlight(bl_t *bl, int value) { #ifdef DDC_PRESENT DDCA_Display_Handle dh = NULL; if (!ddca_open_display2(bl->dev, false, &dh)) { + DDCA_Vcp_Feature_Code specific_br_code; + char specific_br_env[64]; + snprintf(specific_br_env, sizeof(specific_br_env), BL_VCP_ENV"_%s", bl->sn); + if (getenv(specific_br_env)) { + specific_br_code = strtol(getenv(specific_br_env), NULL, 16); + } else { + specific_br_code = br_code; + } int8_t new_sh = (value >> 8) & 0xff; int8_t new_sl = value & 0xff; - ret = ddca_set_non_table_vcp_value(dh, br_code, new_sh, new_sl); + ret = ddca_set_non_table_vcp_value(dh, specific_br_code, new_sh, new_sl); ddca_close_display(dh); } #endif From 247c734b9138c26e96e9dca317fd037ebedddfbe Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Sat, 18 Dec 2021 18:44:31 +0100 Subject: [PATCH 18/23] Fixed webcam default settings set. Signed-off-by: Federico Di Pierro --- src/modules/sensors/camera.c | 9 +++++++++ src/modules/sensors/pipewire.c | 21 ++++++++++++++------- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/modules/sensors/camera.c b/src/modules/sensors/camera.c index 468a29f..bfc234d 100644 --- a/src/modules/sensors/camera.c +++ b/src/modules/sensors/camera.c @@ -129,6 +129,15 @@ static struct v4l2_control *set_camera_setting(void *priv, uint32_t id, float va return NULL; } + if (v < 0) { + /* Set default value */ + struct v4l2_queryctrl arg = {0}; + arg.id = id; + xioctl(VIDIOC_QUERYCTRL, &arg); + INFO("%s (%u) default val: %d\n", name, id, arg.default_value); + v = arg.default_value; + } + if (old_ctrl.value != v) { struct v4l2_control ctrl ={0}; ctrl.id = id; diff --git a/src/modules/sensors/pipewire.c b/src/modules/sensors/pipewire.c index 73b51f6..06ccab7 100644 --- a/src/modules/sensors/pipewire.c +++ b/src/modules/sensors/pipewire.c @@ -29,7 +29,7 @@ typedef struct { double *pct; int capture_idx; bool with_err; - bool has_media_info; + char *settings; } capture_settings_t; typedef struct { @@ -119,6 +119,15 @@ static void free_node(void *dev) { free(pw); } +static void on_state_changed(void *_data, enum pw_stream_state old, + enum pw_stream_state state, const char *error) { + pw_data_t *pw = _data; + if (state == PW_STREAM_STATE_STREAMING) { + /* Camera entered streaming mode; set settings */ + set_camera_settings(pw, pw->cap_set.settings); + } +} + static void on_process(void *_data) { pw_data_t *pw = _data; @@ -183,7 +192,6 @@ static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_p SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<stream, ¶ms, 1); - pw->cap_set.has_media_info = true; INFO("Image fmt: %d\n", pw->format.info.raw.format); INFO("Image res: %d x %d\n", pw->format.info.raw.size.width, pw->format.info.raw.size.height); } @@ -191,6 +199,7 @@ static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_p /* these are the stream events we listen for */ static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, + .state_changed = on_state_changed, .param_changed = on_stream_param_changed, .process = on_process, }; @@ -430,7 +439,8 @@ static void destroy_monitor(void) { static int capture(void *dev, double *pct, const int num_captures, char *settings) { pw_data_t *pw = (pw_data_t *)dev; pw->cap_set.pct = pct; - + pw->cap_set.settings = settings; + setenv("XDG_RUNTIME_DIR", bus_sender_runtime_dir(), 1); const struct spa_pod *params; @@ -463,10 +473,6 @@ static int capture(void *dev, double *pct, const int num_captures, char *setting if (pw_loop_iterate(pw->loop, -1) < 0) { break; } - if (pw->cap_set.has_media_info) { - // Settings must be set once we receive on_params_changed() - set_camera_settings(pw, settings); - } } pw_loop_leave(pw->loop); restore_camera_settings(pw); @@ -506,6 +512,7 @@ static struct v4l2_control *set_camera_setting(void *priv, uint32_t op, float va if (ctrl) { INFO("%s (%u) default val: %.2lf\n", op_name, pw_op, ctrl->def); if (val < 0) { + /* Set default value */ val = ctrl->def; } if (ctrl->values[0] != val) { From 7be06a12a539d7847bba3a534f388cab709286a1 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Sat, 18 Dec 2021 18:51:47 +0100 Subject: [PATCH 19/23] Properly test pipewire build too. Signed-off-by: Federico Di Pierro --- .build.yml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/.build.yml b/.build.yml index 09283ce..398eb3e 100644 --- a/.build.yml +++ b/.build.yml @@ -13,18 +13,20 @@ packages: - libjpeg-turbo - wayland - libdrm + - pipewire sources: - https://github.com/FedeDP/Clightd tasks: - prepare: | cd Clightd - mkdir build build-no-gamma build-no-dpms build-no-ddc build-no-screen build-no-yoctolight build-no-extras - (cd build && cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_GAMMA=1 -DENABLE_DPMS=1 -DENABLE_DDC=1 -DENABLE_SCREEN=1 -DENABLE_YOCTOLIGHT=1 ..) - (cd build-no-gamma && cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_DPMS=1 -DENABLE_DDC=1 -DENABLE_SCREEN=1 -DENABLE_YOCTOLIGHT=1 ..) - (cd build-no-dpms && cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_GAMMA=1 -DENABLE_DDC=1 -DENABLE_SCREEN=1 -DENABLE_YOCTOLIGHT=1 ..) - (cd build-no-ddc && cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_GAMMA=1 -DENABLE_DPMS=1 -DENABLE_SCREEN=1 -DENABLE_YOCTOLIGHT=1 ..) - (cd build-no-screen && cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_GAMMA=1 -DENABLE_DPMS=1 -DENABLE_DDC=1 -DENABLE_YOCTOLIGHT=1 ..) - (cd build-no-yoctolight && cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_GAMMA=1 -DENABLE_DPMS=1 -DENABLE_DDC=1 -DENABLE_SCREEN=1 ..) + mkdir build build-no-gamma build-no-dpms build-no-ddc build-no-screen build-no-yoctolight build-no-pipewire build-no-extras + (cd build && cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_GAMMA=1 -DENABLE_DPMS=1 -DENABLE_DDC=1 -DENABLE_SCREEN=1 -DENABLE_YOCTOLIGHT=1 -DENABLE_PIPEWIRE=1 ..) + (cd build-no-gamma && cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_DPMS=1 -DENABLE_DDC=1 -DENABLE_SCREEN=1 -DENABLE_YOCTOLIGHT=1 -DENABLE_PIPEWIRE=1 ..) + (cd build-no-dpms && cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_GAMMA=1 -DENABLE_DDC=1 -DENABLE_SCREEN=1 -DENABLE_YOCTOLIGHT=1 -DENABLE_PIPEWIRE=1 ..) + (cd build-no-ddc && cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_GAMMA=1 -DENABLE_DPMS=1 -DENABLE_SCREEN=1 -DENABLE_YOCTOLIGHT=1 -DENABLE_PIPEWIRE=1 ..) + (cd build-no-screen && cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_GAMMA=1 -DENABLE_DPMS=1 -DENABLE_DDC=1 -DENABLE_YOCTOLIGHT=1 -DENABLE_PIPEWIRE=1 ..) + (cd build-no-yoctolight && cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_GAMMA=1 -DENABLE_DPMS=1 -DENABLE_DDC=1 -DENABLE_SCREEN=1 -DENABLE_PIPEWIRE=1 ..) + (cd build-no-pipewire && cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_GAMMA=1 -DENABLE_DPMS=1 -DENABLE_DDC=1 -DENABLE_SCREEN=1 -DENABLE_YOCTOLIGHT=1 ..) (cd build-no-extras && cmake -DCMAKE_BUILD_TYPE=Debug ..) - build: | cd Clightd @@ -34,6 +36,7 @@ tasks: (cd build-no-ddc && make) (cd build-no-screen && make) (cd build-no-yoctolight && make) + (cd build-no-pipewire && make) (cd build-no-extras && make) triggers: - action: email From 8f6e10be84cb4d7cc6df9353ea8c45f159ae30f2 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Mon, 20 Dec 2021 19:21:32 +0100 Subject: [PATCH 20/23] Disable API cropping; just use manual crop. Given that frame are small enough, it is not a real problem to just skip some bytes. Moreover, i do not own a camera with cropping support. Signed-off-by: Federico Di Pierro --- TODO.md | 2 + src/modules/sensors/camera.c | 72 ---------------------------------- src/modules/sensors/camera.h | 51 ++++++------------------ src/modules/sensors/pipewire.c | 42 +++++++------------- 4 files changed, 28 insertions(+), 139 deletions(-) diff --git a/TODO.md b/TODO.md index aecdc2d..9aca2c3 100644 --- a/TODO.md +++ b/TODO.md @@ -64,6 +64,8 @@ - [x] Fix memleaks - [x] Support crop settings - [ ] Fix set_camera_setting() impl? seems like a bug in pipewire (?) (pw_stream_get_control does not work -> open issue?) +- [x] Test crop +- [x] Drop crop API support for pipewire or webcam; they add lots of complexity while giving no real perf improvements considering we are using small frames ### Generic - [x] When built with ddcutil, clightd.service should be started after systemd-modules-load.service diff --git a/src/modules/sensors/camera.c b/src/modules/sensors/camera.c index bfc234d..2a64384 100644 --- a/src/modules/sensors/camera.c +++ b/src/modules/sensors/camera.c @@ -28,7 +28,6 @@ struct state { struct mjpeg_dec *decoder; }; -static void inline fill_crop_rect(crop_info_t *cr, struct v4l2_rect *rect); static int set_camera_fmt(void); static int check_camera_caps(void); static void create_decoder(void); @@ -173,77 +172,6 @@ static void inline fill_crop_rect(crop_info_t *cr, struct v4l2_rect *rect) { rect->left = cr[X_AXIS].area_pct[0] * state.width; } -// https://www.kernel.org/doc/html/v4.12/media/uapi/v4l/vidioc-g-selection.html -static int set_selection(crop_info_t *cr) { - struct v4l2_selection selection = {0}; - selection.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - selection.target = V4L2_SEL_TGT_CROP; - if (-1 == xioctl(VIDIOC_G_SELECTION, &selection)) { - INFO("VIDIOC_G_SELECTION failed: %m\n"); - return -errno; - } - if (cr) { - fill_crop_rect(cr, &selection.r); - } else { - // Reset default - selection.target = V4L2_SEL_TGT_CROP_DEFAULT; - } - if (-1 == xioctl(VIDIOC_S_SELECTION, &selection)) { - INFO("VIDIOC_S_SELECTION failed: %m\n"); - return -errno; - } - return 0; -} - -// https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/crop.html -static int set_crop(crop_info_t *cr) { - struct v4l2_crop crop = {0}; - crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (-1 == xioctl(VIDIOC_G_CROP, &crop)) { - INFO("VIDIOC_G_CROP failed: %m\n"); - return -errno; - } - - if (cr) { - fill_crop_rect(cr, &crop.c); - } else { - // Reset default - struct v4l2_cropcap cropcap = {0}; - cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (-1 == xioctl(VIDIOC_CROPCAP, &cropcap)) { - INFO("VIDIOC_CROPCAP failed: %m\n"); - return -errno; - } - crop.c = cropcap.defrect; - } - if (-1 == xioctl(VIDIOC_S_CROP, &crop)) { - INFO("VIDIOC_S_CROP failed: %m\n"); - return -errno; - } - return 0; -} - -static int try_set_crop(void *priv, crop_info_t *crop, crop_type_t *crop_type) { - int ret; - int cr_type = *crop_type > 0 ? *crop_type : SELECTION_API; - do { - switch (cr_type) { - case SELECTION_API: - ret = set_selection(crop); - break; - case CROP_API: - ret = set_crop(crop); - break; - default: - break; - } - } while (ret != 0 && *crop_type != cr_type--); - if (ret == 0) { - *crop_type = cr_type; - } - return ret; -} - static int set_camera_fmt(void) { struct v4l2_format fmt = {0}; fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; diff --git a/src/modules/sensors/camera.h b/src/modules/sensors/camera.h index 75629b8..b99cf02 100644 --- a/src/modules/sensors/camera.h +++ b/src/modules/sensors/camera.h @@ -35,7 +35,6 @@ struct histogram { }; typedef enum { X_AXIS, Y_AXIS, MAX_AXIS } crop_axis; -typedef enum { DISABLED, CROP_API, SELECTION_API, MANUAL} crop_type_t; typedef struct { bool enabled; @@ -43,11 +42,9 @@ typedef struct { } crop_info_t; static struct v4l2_control *set_camera_setting(void *priv, uint32_t op, float val, const char *op_name, bool store); -static int try_set_crop(void *priv, crop_info_t *crop, crop_type_t *crop_type); static map_t *stored_values; static crop_info_t crop[MAX_AXIS]; -static crop_type_t crop_type; static bool camera_set; static double get_frame_brightness(uint8_t *img_data, rect_info_t *full, bool is_yuv) { @@ -62,33 +59,21 @@ static double get_frame_brightness(uint8_t *img_data, rect_info_t *full, bool is const int inc = 1 + is_yuv; rect_info_t crop_rect = *full; - switch (crop_type) { - case MANUAL: - if (crop[X_AXIS].enabled) { - crop_rect.col_start = crop[X_AXIS].area_pct[0] * full->col_end; - crop_rect.col_end = crop[X_AXIS].area_pct[1] * full->col_end; - } - if (crop[Y_AXIS].enabled) { - crop_rect.row_start = crop[Y_AXIS].area_pct[0] * full->row_end; - crop_rect.row_end = crop[Y_AXIS].area_pct[1] * full->row_end; - } - INFO("Manual crop: rows[%d-%d], cols[%d-%d]\n", crop_rect.row_start, crop_rect.row_end, - crop_rect.col_start, crop_rect.col_end); - break; - case SELECTION_API: - case CROP_API: - // Update crop size - crop_rect.col_end *= crop[X_AXIS].area_pct[1] - crop[X_AXIS].area_pct[0]; - crop_rect.row_end *= crop[Y_AXIS].area_pct[1] - crop[Y_AXIS].area_pct[0]; - break; - default: - break; + if (crop[X_AXIS].enabled) { + crop_rect.col_start = crop[X_AXIS].area_pct[0] * full->col_end; + crop_rect.col_end = crop[X_AXIS].area_pct[1] * full->col_end; } + if (crop[Y_AXIS].enabled) { + crop_rect.row_start = crop[Y_AXIS].area_pct[0] * full->row_end; + crop_rect.row_end = crop[Y_AXIS].area_pct[1] * full->row_end; + } + INFO("Rect: rows[%d-%d], cols[%d-%d]\n", crop_rect.row_start, crop_rect.row_end, + crop_rect.col_start, crop_rect.col_end); /* Find minimum and maximum brightness */ int total = 0; // compute total used pixels for (int row = crop_rect.row_start; row < crop_rect.row_end; row++) { - for (int col = crop_rect.col_start; col < crop_rect.col_end * inc; col += inc) { + for (int col = crop_rect.col_start * inc; col < crop_rect.col_end * inc; col += inc) { const int idx = (row * full->col_end * inc) + col; if (img_data[idx] < min) { min = img_data[idx]; @@ -110,7 +95,7 @@ static double get_frame_brightness(uint8_t *img_data, rect_info_t *full, bool is struct histogram hist[HISTOGRAM_STEPS] = {0}; const double step_size = (max - min) / HISTOGRAM_STEPS; for (int row = crop_rect.row_start; row < crop_rect.row_end; row++) { - for (int col = crop_rect.col_start; col < crop_rect.col_end * inc; col += inc) { + for (int col = crop_rect.col_start * inc; col < crop_rect.col_end * inc; col += inc) { const int idx = (row * full->col_end * inc) + col; int bucket = (img_data[idx] - min) / step_size; if (bucket >= 0 && bucket < HISTOGRAM_STEPS) { @@ -234,14 +219,6 @@ static void set_camera_settings(void *priv, char *settings) { fprintf(stderr, "Expected a=b format in '%s' token.\n", token); } } - if (crop[X_AXIS].enabled || crop[Y_AXIS].enabled) { - if (try_set_crop(priv, crop, &crop_type) != 0) { - INFO("Unsupported crop/selection v4l2 API; fallback at manually skipping pixels.\n") - crop_type = MANUAL; - } - } else { - crop_type = DISABLED; - } } camera_set = true; } @@ -254,15 +231,9 @@ static void restore_camera_settings(void *priv) { set_camera_setting(priv, old_ctrl->id, old_ctrl->value, ctrl_name, false); } - // Restore crop if needed - if (crop_type != DISABLED && crop_type != MANUAL) { - try_set_crop(priv, NULL, &crop_type); - } - map_free(stored_values); stored_values = NULL; memset(crop, 0, sizeof(crop)); - crop_type = -1; camera_set = false; } diff --git a/src/modules/sensors/pipewire.c b/src/modules/sensors/pipewire.c index 06ccab7..96e772a 100644 --- a/src/modules/sensors/pipewire.c +++ b/src/modules/sensors/pipewire.c @@ -302,6 +302,10 @@ static void registry_event_global(void *data, uint32_t id, last_recved = pw; // used by recv_monitor BUILD_KEY(id); map_put(nodes, key, pw); + +// pw_node_enum_params((struct pw_node*)pw->node.proxy, 0, +// SPA_PARAM_Props, 0, 0, NULL); + } } } @@ -464,7 +468,7 @@ static int capture(void *dev, double *pct, const int num_captures, char *setting pw->node.id, PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */ - ¶ms, 1)) /* extra parameters, see above */ < 0) { + ¶ms, 1)) /* extra parameters, see above */ < 0) { fprintf(stderr, "Can't connect: %s\n", spa_strerror(res)); } else { @@ -508,6 +512,16 @@ static inline enum spa_prop control_to_prop_id(uint32_t control_id) { static struct v4l2_control *set_camera_setting(void *priv, uint32_t op, float val, const char *op_name, bool store) { pw_data_t *pw = (pw_data_t *)priv; enum spa_prop pw_op = control_to_prop_id(op); +// if (val > 0) { +// uint8_t params_buffer[1024]; +// struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); +// const struct spa_pod *params = spa_pod_builder_add_object(&b, +// SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, +// pw_op, SPA_POD_Int(val), 0); +// int ret = pw_node_set_param(pw->node.proxy, SPA_PARAM_Props, 0, params); +// printf("top %d\n", ret); +// } + const struct pw_stream_control *ctrl = pw_stream_get_control(pw->stream, pw_op); if (ctrl) { INFO("%s (%u) default val: %.2lf\n", op_name, pw_op, ctrl->def); @@ -539,30 +553,4 @@ static struct v4l2_control *set_camera_setting(void *priv, uint32_t op, float va return NULL; } -static int try_set_crop(void *priv, crop_info_t *crop, crop_type_t *crop_type) { - pw_data_t *pw = (pw_data_t *)priv; - uint8_t params_buffer[1024]; - - struct spa_meta_region *reg = NULL; - if (crop != NULL) { - reg = malloc(sizeof(struct spa_meta_region)); - reg->region.size.width = (crop[X_AXIS].area_pct[1] - crop[X_AXIS].area_pct[0]) * pw->format.info.raw.size.width; - reg->region.size.height = (crop[Y_AXIS].area_pct[1] - crop[Y_AXIS].area_pct[0]) * pw->format.info.raw.size.height; - reg->region.position.x = crop[X_AXIS].area_pct[0] * pw->format.info.raw.size.width; - reg->region.position.y = crop[Y_AXIS].area_pct[0] * pw->format.info.raw.size.height; - } - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); - const struct spa_pod *params = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, - SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop), - SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_region)), reg); - free(reg); - int ret = pw_stream_update_params(pw->stream, ¶ms, 1); - if (ret >= 0) { - *crop_type = CROP_API; - ret = 0; - } - return ret; -} - #endif From d39b40869b88e7a516decefbda8acad556674645 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Thu, 23 Dec 2021 15:53:04 +0100 Subject: [PATCH 21/23] Some more work on pipewire module; initial support for fetching default values for controls; setting and getting current value is currently unsupported in libpipewire. Put set_camera_settings() on hold for Pipewire module until supported. Added a warning stating that Pipewire support is currently in experimental phase. Only a real issue left: Pipewire freezes during Capture() if another application is already owning the stream, freezing entire Clightd application. Signed-off-by: Federico Di Pierro --- TODO.md | 7 +- src/modules/sensors/pipewire.c | 125 ++++++++++++++++++++++++++++++--- 2 files changed, 121 insertions(+), 11 deletions(-) diff --git a/TODO.md b/TODO.md index 9aca2c3..af2fcc9 100644 --- a/TODO.md +++ b/TODO.md @@ -63,9 +63,9 @@ - [x] Fix xdg_runtime_dir set to create monitor - [x] Fix memleaks - [x] Support crop settings -- [ ] Fix set_camera_setting() impl? seems like a bug in pipewire (?) (pw_stream_get_control does not work -> open issue?) - [x] Test crop -- [x] Drop crop API support for pipewire or webcam; they add lots of complexity while giving no real perf improvements considering we are using small frames +- [x] Drop crop API support for both pipewire and webcam; they add lots of complexity while giving no real perf improvements considering we are using small frames +- [ ] Fix: pipewire capture while webcam is already owned by another app freezes during pw_loop_iterate() ### Generic - [x] When built with ddcutil, clightd.service should be started after systemd-modules-load.service @@ -80,6 +80,9 @@ ## 5.x - [ ] Keep it up to date with possible ddcutil api changes +### Pipewire +- [ ] Fix set_camera_setting() impl -> how to get current value? how to set a new value? + ## 6.x (api break release) ### Generic diff --git a/src/modules/sensors/pipewire.c b/src/modules/sensors/pipewire.c index 96e772a..ade6aba 100644 --- a/src/modules/sensors/pipewire.c +++ b/src/modules/sensors/pipewire.c @@ -1,5 +1,7 @@ #ifdef PIPEWIRE_PRESENT +#warning "Experimental support. Camera settings are not supported. Possible freezes." + #include "camera.h" #include "bus_utils.h" #include @@ -21,6 +23,7 @@ typedef struct { uint32_t id; struct spa_hook proxy_listener; + struct spa_hook object_listener; struct pw_proxy *proxy; const char *action; } pw_node_t; @@ -123,8 +126,10 @@ static void on_state_changed(void *_data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { pw_data_t *pw = _data; if (state == PW_STREAM_STATE_STREAMING) { - /* Camera entered streaming mode; set settings */ - set_camera_settings(pw, pw->cap_set.settings); + /* Camera entered streaming mode; set settings. TODO: unsupported atm */ +// set_camera_settings(pw, pw->cap_set.settings); + } else if (state == PW_STREAM_STATE_ERROR) { + pw->cap_set.with_err = true; } } @@ -282,6 +287,105 @@ static const struct pw_proxy_events proxy_events = { .removed = removed_proxy, }; +// see https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/master/spa/include/spa/debug/pod.h +static void parse_props(pw_data_t *pw, const struct spa_pod *pod) +{ + struct spa_pod_prop *prop; + struct spa_pod_object *obj = (struct spa_pod_object *)pod; + SPA_POD_OBJECT_FOREACH(obj, prop) { + printf("top %d\n", prop->key); + switch (prop->key) { + case SPA_PROP_device: { + const char *str = NULL; + spa_pod_get_string(&prop->value, &str); + printf("String \"%s\"\n", str); + break; + } + case SPA_PROP_INFO_id: { + uint32_t id; + if (spa_pod_get_id(&prop->value, &id) < 0) { + break; + } + printf("ID: %d\n", id); + break; + } + case SPA_PROP_INFO_name: { + const char *name; + if (spa_pod_get_string(&prop->value, &name) < 0) { + break; + } + printf("Name: %s\n", name); + break; + } + case SPA_PROP_INFO_type: { + if (spa_pod_is_choice(&prop->value)) { + struct spa_pod_object_body *body = (struct spa_pod_object_body *)SPA_POD_BODY(&prop->value); + struct spa_pod_choice_body *b = (struct spa_pod_choice_body *)body; + const struct spa_type_info *ti = spa_debug_type_find(spa_type_choice, b->type); + void *p; + uint32_t size = SPA_POD_BODY_SIZE(&prop->value); + + // TODO: store default values somewhere (easy) + // TODO: find current values?? + printf("Choice: type %s\n", ti ? ti->name : "unknown"); + SPA_POD_CHOICE_BODY_FOREACH(b, size, p) { + switch (b->child.type) { + case SPA_TYPE_Bool: + printf("\tBool %s\n", (*(int32_t *) p) ? "true" : "false"); + break; + case SPA_TYPE_Int: + printf("\tInt %d\n", *(int32_t *) p); + break; + default: + INFO("Unmanaged type: %d\n", b->child.type); + break; + } + } + } + break; + } + default: + break; + } + } + printf("\n"); +} + +static void node_event_param(void *object, int seq, + uint32_t id, uint32_t index, uint32_t next, + const struct spa_pod *param) +{ + switch (id) { + case SPA_PARAM_PropInfo: + parse_props(object, param); + break; + default: + break; + } +} + +static void node_event_info(void *object, const struct pw_node_info *info) { + pw_data_t *pw = (pw_data_t *)object; + const struct spa_dict_item *prop; + spa_dict_for_each(prop, info->props) { + printf("'%s' -> '%s'\n", prop->key, prop->value); + } + printf("\n"); + + // TODO: store the device path and use it before calling capture to check + // if device is actually available? Shouldn't pipewire handle this? + const char *str; + if ((str = spa_dict_lookup(info->props, PW_KEY_OBJECT_PATH))) { + printf("ObjectPath: %s\n", str); + } +} + +static const struct pw_node_events node_events = { + PW_VERSION_NODE_EVENTS, + .info = node_event_info, + .param = node_event_param, +}; + static void registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { @@ -299,13 +403,16 @@ static void registry_event_global(void *data, uint32_t id, pw->node.action = UDEV_ACTION_ADD; pw->node.proxy = pw_registry_bind(pw_mon.registry, id, type, PW_VERSION_NODE, sizeof(pw_data_t)); pw_proxy_add_listener(pw->node.proxy, &pw->node.proxy_listener, &proxy_events, pw); +// pw_proxy_add_object_listener(pw->node.proxy, +// &pw->node.object_listener, +// &node_events, pw); + last_recved = pw; // used by recv_monitor BUILD_KEY(id); map_put(nodes, key, pw); // pw_node_enum_params((struct pw_node*)pw->node.proxy, 0, -// SPA_PARAM_Props, 0, 0, NULL); - +// SPA_PARAM_PropInfo, 0, 0, NULL); } } } @@ -479,7 +586,7 @@ static int capture(void *dev, double *pct, const int num_captures, char *setting } } pw_loop_leave(pw->loop); - restore_camera_settings(pw); +// restore_camera_settings(pw); } unsetenv("XDG_RUNTIME_DIR"); return pw->cap_set.capture_idx; @@ -509,6 +616,7 @@ static inline enum spa_prop control_to_prop_id(uint32_t control_id) { } } +// TODO: unsupported in pipewire right now static struct v4l2_control *set_camera_setting(void *priv, uint32_t op, float val, const char *op_name, bool store) { pw_data_t *pw = (pw_data_t *)priv; enum spa_prop pw_op = control_to_prop_id(op); @@ -516,10 +624,9 @@ static struct v4l2_control *set_camera_setting(void *priv, uint32_t op, float va // uint8_t params_buffer[1024]; // struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); // const struct spa_pod *params = spa_pod_builder_add_object(&b, -// SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, -// pw_op, SPA_POD_Int(val), 0); -// int ret = pw_node_set_param(pw->node.proxy, SPA_PARAM_Props, 0, params); -// printf("top %d\n", ret); +// SPA_TYPE_OBJECT_Props, 0, +// SPA_PROP_brightness, SPA_POD_Float(140)); +// pw_node_set_param(pw->node.proxy, SPA_PARAM_Props, 0, params); // } const struct pw_stream_control *ctrl = pw_stream_get_control(pw->stream, pw_op); From e68e43cc9a173af5ecac41edfb6e4dd2f6259a05 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Mon, 3 Jan 2022 14:30:25 +0100 Subject: [PATCH 22/23] Add a 2s timeout on pipewire loop to avoid locking. Signed-off-by: Federico Di Pierro --- src/modules/sensors/pipewire.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/modules/sensors/pipewire.c b/src/modules/sensors/pipewire.c index ade6aba..3d691c7 100644 --- a/src/modules/sensors/pipewire.c +++ b/src/modules/sensors/pipewire.c @@ -39,6 +39,7 @@ typedef struct { pw_node_t node; struct pw_loop *loop; struct pw_stream *stream; + struct spa_source *timer; struct spa_video_info format; capture_settings_t cap_set; } pw_data_t; @@ -258,6 +259,9 @@ static void fetch_props_dev(void *dev, const char **node, const char **action) { static void destroy_dev(void *dev) { pw_data_t *pw = (pw_data_t *)dev; + if (pw->timer) { + pw_loop_destroy_source(pw->loop, pw->timer); + } if (pw->stream) { pw_stream_destroy(pw->stream); pw->stream = NULL; @@ -547,6 +551,12 @@ static void destroy_monitor(void) { pw_deinit(); } +static void on_timeout(void *userdata, uint64_t expirations) { + pw_data_t *pw = (pw_data_t *)userdata; + INFO("Stream timed out. Leaving.\n"); + pw->cap_set.with_err = true; +} + static int capture(void *dev, double *pct, const int num_captures, char *settings) { pw_data_t *pw = (pw_data_t *)dev; pw->cap_set.pct = pct; @@ -579,6 +589,10 @@ static int capture(void *dev, double *pct, const int num_captures, char *setting fprintf(stderr, "Can't connect: %s\n", spa_strerror(res)); } else { + /* Use a 2s timeout to avoid locking on the pw_loop_iterate() loop! */ + struct timespec timeout = { .tv_sec = 2 }; + pw->timer = pw_loop_add_timer(pw->loop, on_timeout, pw); + pw_loop_update_timer(pw->loop, pw->timer, &timeout, NULL, false); pw_loop_enter(pw->loop); while (pw->cap_set.capture_idx < num_captures && !pw->cap_set.with_err) { if (pw_loop_iterate(pw->loop, -1) < 0) { From caabd84f83edf6f24b657e5f3919a242151fce53 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Thu, 6 Jan 2022 11:25:06 +0100 Subject: [PATCH 23/23] Updated todo. Signed-off-by: Federico Di Pierro --- TODO.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TODO.md b/TODO.md index af2fcc9..b96f908 100644 --- a/TODO.md +++ b/TODO.md @@ -56,7 +56,7 @@ - [x] Fix segfault - [x] Fix subsequent Capture - [x] Add a CLIGHTD_PW_RUNTIME_DIR env variable (in clightd.service, see CLIGHTD_BL_CODE) that defaults to /run/user/1000/. If the env variable is empty -> disable pipewire. If folder does not exist: disable pipewire. Otherwise: inotify on folder to wait for socket to appear. If socket is already there, immediately start monitoring. -- [ ] Document the new env variable! +- [x] Document the new env variable! - [x] Use caller uid instead of defaulting to first found user during Capture! - [x] Use a map to store list of nodes? - [x] Free list of nodes upon exit! @@ -65,7 +65,7 @@ - [x] Support crop settings - [x] Test crop - [x] Drop crop API support for both pipewire and webcam; they add lots of complexity while giving no real perf improvements considering we are using small frames -- [ ] Fix: pipewire capture while webcam is already owned by another app freezes during pw_loop_iterate() +- [x] Fix: pipewire capture while webcam is already owned by another app freezes during pw_loop_iterate() ### Generic - [x] When built with ddcutil, clightd.service should be started after systemd-modules-load.service @@ -73,7 +73,7 @@ - [x] All api that require eg Xauth or xdg rutime user, fallback at automatically fetching a default value given the caller: - [x] test X - [x] test wl -- [ ] Document the new behavior! +- [x] Document the new behavior! - [x] Fix clightd not cleanly exiting when built with DDC or YOCTOLIGHT (most probably libusb or whatever is creating another thread that is stealing the signal!) - [x] do not use strlen() to only check that strign is not empty