257 lines
8.3 KiB
C
257 lines
8.3 KiB
C
/* SPDX-License-Identifier: LGPL-2.1+ */
|
|
|
|
#include "bus-util.h"
|
|
#include "device-util.h"
|
|
#include "hash-funcs.h"
|
|
#include "logind-brightness.h"
|
|
#include "logind.h"
|
|
#include "process-util.h"
|
|
#include "stdio-util.h"
|
|
|
|
/* Brightness and LED devices tend to be very slow to write to (often being I2C and such). Writes to the
|
|
* sysfs attributes are synchronous, and hence will freeze our process on access. We can't really have that,
|
|
* hence we add some complexity: whenever we need to write to the brightness attribute, we do so in a forked
|
|
* off process, which terminates when it is done. Watching that process allows us to watch completion of the
|
|
* write operation.
|
|
*
|
|
* To make this even more complex: clients are likely to send us many write requests in a short time-frame
|
|
* (because they implement reactive brightness sliders on screen). Let's coalesce writes to make this
|
|
* efficient: whenever we get requests to change brightness while we are still writing to the brightness
|
|
* attribute, let's remember the request and restart a new one when the initial operation finished. When we
|
|
* get another request while one is ongoing and one is pending we'll replace the pending one with the new
|
|
* one.
|
|
*
|
|
* The bus messages are answered when the first write operation finishes that started either due to the
|
|
* request or due to a later request that overrode the requested one.
|
|
*
|
|
* Yes, this is complex, but I don't see an easier way if we want to be both efficient and still support
|
|
* completion notification. */
|
|
|
|
typedef struct BrightnessWriter {
|
|
Manager *manager;
|
|
|
|
sd_device *device;
|
|
char *path;
|
|
|
|
pid_t child;
|
|
|
|
uint32_t brightness;
|
|
bool again;
|
|
|
|
Set *current_messages;
|
|
Set *pending_messages;
|
|
|
|
sd_event_source* child_event_source;
|
|
} BrightnessWriter;
|
|
|
|
static void brightness_writer_free(BrightnessWriter *w) {
|
|
if (!w)
|
|
return;
|
|
|
|
if (w->manager && w->path)
|
|
(void) hashmap_remove_value(w->manager->brightness_writers, w->path, w);
|
|
|
|
sd_device_unref(w->device);
|
|
free(w->path);
|
|
|
|
set_free(w->current_messages);
|
|
set_free(w->pending_messages);
|
|
|
|
w->child_event_source = sd_event_source_unref(w->child_event_source);
|
|
|
|
free(w);
|
|
}
|
|
|
|
DEFINE_TRIVIAL_CLEANUP_FUNC(BrightnessWriter*, brightness_writer_free);
|
|
|
|
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
|
|
brightness_writer_hash_ops,
|
|
char,
|
|
string_hash_func,
|
|
string_compare_func,
|
|
BrightnessWriter,
|
|
brightness_writer_free);
|
|
|
|
static void brightness_writer_reply(BrightnessWriter *w, int error) {
|
|
int r;
|
|
|
|
assert(w);
|
|
|
|
for (;;) {
|
|
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
|
|
|
m = set_steal_first(w->current_messages);
|
|
if (!m)
|
|
break;
|
|
|
|
if (error == 0)
|
|
r = sd_bus_reply_method_return(m, NULL);
|
|
else
|
|
r = sd_bus_reply_method_errnof(m, error, "Failed to write to brightness device: %m");
|
|
if (r < 0)
|
|
log_warning_errno(r, "Failed to send method reply, ignoring: %m");
|
|
}
|
|
}
|
|
|
|
static int brightness_writer_fork(BrightnessWriter *w);
|
|
|
|
static int on_brightness_writer_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
|
|
BrightnessWriter *w = userdata;
|
|
int r;
|
|
|
|
assert(s);
|
|
assert(si);
|
|
assert(w);
|
|
|
|
assert(si->si_pid == w->child);
|
|
w->child = 0;
|
|
w->child_event_source = sd_event_source_unref(w->child_event_source);
|
|
|
|
brightness_writer_reply(w,
|
|
si->si_code == CLD_EXITED &&
|
|
si->si_status == EXIT_SUCCESS ? 0 : -EPROTO);
|
|
|
|
if (w->again) {
|
|
/* Another request to change the brightness has been queued. Act on it, but make the pending
|
|
* messages the current ones. */
|
|
w->again = false;
|
|
set_free(w->current_messages);
|
|
w->current_messages = TAKE_PTR(w->pending_messages);
|
|
|
|
r = brightness_writer_fork(w);
|
|
if (r >= 0)
|
|
return 0;
|
|
|
|
brightness_writer_reply(w, r);
|
|
}
|
|
|
|
brightness_writer_free(w);
|
|
return 0;
|
|
}
|
|
|
|
static int brightness_writer_fork(BrightnessWriter *w) {
|
|
int r;
|
|
|
|
assert(w);
|
|
assert(w->manager);
|
|
assert(w->child == 0);
|
|
assert(!w->child_event_source);
|
|
|
|
r = safe_fork("(sd-bright)", FORK_DEATHSIG|FORK_NULL_STDIO|FORK_CLOSE_ALL_FDS|FORK_LOG, &w->child);
|
|
if (r < 0)
|
|
return r;
|
|
if (r == 0) {
|
|
char brs[DECIMAL_STR_MAX(uint32_t)+1];
|
|
|
|
/* Child */
|
|
xsprintf(brs, "%" PRIu32, w->brightness);
|
|
|
|
r = sd_device_set_sysattr_value(w->device, "brightness", brs);
|
|
if (r < 0) {
|
|
log_device_error_errno(w->device, r, "Failed to write brightness to device: %m");
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
|
|
_exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
r = sd_event_add_child(w->manager->event, &w->child_event_source, w->child, WEXITED, on_brightness_writer_exit, w);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to watch brightness writer child " PID_FMT ": %m", w->child);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int set_add_message(Set **set, sd_bus_message *message) {
|
|
int r;
|
|
|
|
assert(set);
|
|
|
|
if (!message)
|
|
return 0;
|
|
|
|
r = sd_bus_message_get_expect_reply(message);
|
|
if (r <= 0)
|
|
return r;
|
|
|
|
r = set_ensure_allocated(set, &bus_message_hash_ops);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = set_put(*set, message);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
sd_bus_message_ref(message);
|
|
return 1;
|
|
}
|
|
|
|
int manager_write_brightness(
|
|
Manager *m,
|
|
sd_device *device,
|
|
uint32_t brightness,
|
|
sd_bus_message *message) {
|
|
|
|
_cleanup_(brightness_writer_freep) BrightnessWriter *w = NULL;
|
|
BrightnessWriter *existing;
|
|
const char *path;
|
|
int r;
|
|
|
|
assert(m);
|
|
assert(device);
|
|
|
|
r = sd_device_get_syspath(device, &path);
|
|
if (r < 0)
|
|
return log_device_error_errno(device, r, "Failed to get sysfs path for brightness device: %m");
|
|
|
|
existing = hashmap_get(m->brightness_writers, path);
|
|
if (existing) {
|
|
/* There's already a writer for this device. Let's update it with the new brightness, and add
|
|
* our message to the set of message to reply when done. */
|
|
|
|
r = set_add_message(&existing->pending_messages, message);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to add message to set: %m");
|
|
|
|
/* We override any previously requested brightness here: we coalesce writes, and the newest
|
|
* requested brightness is the one we'll put into effect. */
|
|
existing->brightness = brightness;
|
|
existing->again = true; /* request another iteration of the writer when the current one is
|
|
* complete */
|
|
return 0;
|
|
}
|
|
|
|
r = hashmap_ensure_allocated(&m->brightness_writers, &brightness_writer_hash_ops);
|
|
if (r < 0)
|
|
return log_oom();
|
|
|
|
w = new(BrightnessWriter, 1);
|
|
if (!w)
|
|
return log_oom();
|
|
|
|
*w = (BrightnessWriter) {
|
|
.device = sd_device_ref(device),
|
|
.path = strdup(path),
|
|
.brightness = brightness,
|
|
};
|
|
|
|
if (!w->path)
|
|
return log_oom();
|
|
|
|
r = hashmap_put(m->brightness_writers, w->path, w);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to add brightness writer to hashmap: %m");
|
|
w->manager = m;
|
|
|
|
r = set_add_message(&w->current_messages, message);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to add message to set: %m");
|
|
|
|
r = brightness_writer_fork(w);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
TAKE_PTR(w);
|
|
return 0;
|
|
}
|