diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:25:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:25:50 +0000 |
commit | 19f4f86bfed21c5326ed2acebe1163f3a83e832b (patch) | |
tree | d59b9989ce55ed23693e80974d94c856f1c2c8b1 /src/login/logind-button.c | |
parent | Initial commit. (diff) | |
download | systemd-upstream.tar.xz systemd-upstream.zip |
Adding upstream version 241.upstream/241upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/login/logind-button.c')
-rw-r--r-- | src/login/logind-button.c | 377 |
1 files changed, 377 insertions, 0 deletions
diff --git a/src/login/logind-button.c b/src/login/logind-button.c new file mode 100644 index 0000000..daffbf0 --- /dev/null +++ b/src/login/logind-button.c @@ -0,0 +1,377 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <linux/input.h> + +#include "sd-messages.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "logind-button.h" +#include "missing_input.h" +#include "string-util.h" +#include "util.h" + +#define CONST_MAX4(a, b, c, d) CONST_MAX(CONST_MAX(a, b), CONST_MAX(c, d)) + +#define ULONG_BITS (sizeof(unsigned long)*8) + +static bool bitset_get(const unsigned long *bits, unsigned i) { + return (bits[i / ULONG_BITS] >> (i % ULONG_BITS)) & 1UL; +} + +static void bitset_put(unsigned long *bits, unsigned i) { + bits[i / ULONG_BITS] |= (unsigned long) 1 << (i % ULONG_BITS); +} + +Button* button_new(Manager *m, const char *name) { + Button *b; + + assert(m); + assert(name); + + b = new0(Button, 1); + if (!b) + return NULL; + + b->name = strdup(name); + if (!b->name) + return mfree(b); + + if (hashmap_put(m->buttons, b->name, b) < 0) { + free(b->name); + return mfree(b); + } + + b->manager = m; + b->fd = -1; + + return b; +} + +void button_free(Button *b) { + assert(b); + + hashmap_remove(b->manager->buttons, b->name); + + sd_event_source_unref(b->io_event_source); + sd_event_source_unref(b->check_event_source); + + if (b->fd >= 0) + /* If the device has been unplugged close() returns + * ENODEV, let's ignore this, hence we don't use + * safe_close() */ + (void) close(b->fd); + + free(b->name); + free(b->seat); + free(b); +} + +int button_set_seat(Button *b, const char *sn) { + char *s; + + assert(b); + assert(sn); + + s = strdup(sn); + if (!s) + return -ENOMEM; + + free(b->seat); + b->seat = s; + + return 0; +} + +static void button_lid_switch_handle_action(Manager *manager, bool is_edge) { + HandleAction handle_action; + + assert(manager); + + /* If we are docked or on external power, handle the lid switch + * differently */ + if (manager_is_docked_or_external_displays(manager)) + handle_action = manager->handle_lid_switch_docked; + else if (manager->handle_lid_switch_ep != _HANDLE_ACTION_INVALID && + manager_is_on_external_power()) + handle_action = manager->handle_lid_switch_ep; + else + handle_action = manager->handle_lid_switch; + + manager_handle_action(manager, INHIBIT_HANDLE_LID_SWITCH, handle_action, manager->lid_switch_ignore_inhibited, is_edge); +} + +static int button_recheck(sd_event_source *e, void *userdata) { + Button *b = userdata; + + assert(b); + assert(b->lid_closed); + + button_lid_switch_handle_action(b->manager, false); + return 1; +} + +static int button_install_check_event_source(Button *b) { + int r; + assert(b); + + /* Install a post handler, so that we keep rechecking as long as the lid is closed. */ + + if (b->check_event_source) + return 0; + + r = sd_event_add_post(b->manager->event, &b->check_event_source, button_recheck, b); + if (r < 0) + return r; + + return sd_event_source_set_priority(b->check_event_source, SD_EVENT_PRIORITY_IDLE+1); +} + +static int button_dispatch(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Button *b = userdata; + struct input_event ev; + ssize_t l; + + assert(s); + assert(fd == b->fd); + assert(b); + + l = read(b->fd, &ev, sizeof(ev)); + if (l < 0) + return errno != EAGAIN ? -errno : 0; + if ((size_t) l < sizeof(ev)) + return -EIO; + + if (ev.type == EV_KEY && ev.value > 0) { + + switch (ev.code) { + + case KEY_POWER: + case KEY_POWER2: + log_struct(LOG_INFO, + LOG_MESSAGE("Power key pressed."), + "MESSAGE_ID=" SD_MESSAGE_POWER_KEY_STR); + + manager_handle_action(b->manager, INHIBIT_HANDLE_POWER_KEY, b->manager->handle_power_key, b->manager->power_key_ignore_inhibited, true); + break; + + /* The kernel is a bit confused here: + + KEY_SLEEP = suspend-to-ram, which everybody else calls "suspend" + KEY_SUSPEND = suspend-to-disk, which everybody else calls "hibernate" + */ + + case KEY_SLEEP: + log_struct(LOG_INFO, + LOG_MESSAGE("Suspend key pressed."), + "MESSAGE_ID=" SD_MESSAGE_SUSPEND_KEY_STR); + + manager_handle_action(b->manager, INHIBIT_HANDLE_SUSPEND_KEY, b->manager->handle_suspend_key, b->manager->suspend_key_ignore_inhibited, true); + break; + + case KEY_SUSPEND: + log_struct(LOG_INFO, + LOG_MESSAGE("Hibernate key pressed."), + "MESSAGE_ID=" SD_MESSAGE_HIBERNATE_KEY_STR); + + manager_handle_action(b->manager, INHIBIT_HANDLE_HIBERNATE_KEY, b->manager->handle_hibernate_key, b->manager->hibernate_key_ignore_inhibited, true); + break; + } + + } else if (ev.type == EV_SW && ev.value > 0) { + + if (ev.code == SW_LID) { + log_struct(LOG_INFO, + LOG_MESSAGE("Lid closed."), + "MESSAGE_ID=" SD_MESSAGE_LID_CLOSED_STR); + + b->lid_closed = true; + button_lid_switch_handle_action(b->manager, true); + button_install_check_event_source(b); + + } else if (ev.code == SW_DOCK) { + log_struct(LOG_INFO, + LOG_MESSAGE("System docked."), + "MESSAGE_ID=" SD_MESSAGE_SYSTEM_DOCKED_STR); + + b->docked = true; + } + + } else if (ev.type == EV_SW && ev.value == 0) { + + if (ev.code == SW_LID) { + log_struct(LOG_INFO, + LOG_MESSAGE("Lid opened."), + "MESSAGE_ID=" SD_MESSAGE_LID_OPENED_STR); + + b->lid_closed = false; + b->check_event_source = sd_event_source_unref(b->check_event_source); + + } else if (ev.code == SW_DOCK) { + log_struct(LOG_INFO, + LOG_MESSAGE("System undocked."), + "MESSAGE_ID=" SD_MESSAGE_SYSTEM_UNDOCKED_STR); + + b->docked = false; + } + } + + return 0; +} + +static int button_suitable(Button *b) { + unsigned long types[CONST_MAX(EV_KEY, EV_SW)/ULONG_BITS+1]; + + assert(b); + assert(b->fd); + + if (ioctl(b->fd, EVIOCGBIT(EV_SYN, sizeof(types)), types) < 0) + return -errno; + + if (bitset_get(types, EV_KEY)) { + unsigned long keys[CONST_MAX4(KEY_POWER, KEY_POWER2, KEY_SLEEP, KEY_SUSPEND)/ULONG_BITS+1]; + + if (ioctl(b->fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys) < 0) + return -errno; + + if (bitset_get(keys, KEY_POWER) || + bitset_get(keys, KEY_POWER2) || + bitset_get(keys, KEY_SLEEP) || + bitset_get(keys, KEY_SUSPEND)) + return true; + } + + if (bitset_get(types, EV_SW)) { + unsigned long switches[CONST_MAX(SW_LID, SW_DOCK)/ULONG_BITS+1]; + + if (ioctl(b->fd, EVIOCGBIT(EV_SW, sizeof(switches)), switches) < 0) + return -errno; + + if (bitset_get(switches, SW_LID) || + bitset_get(switches, SW_DOCK)) + return true; + } + + return false; +} + +static int button_set_mask(Button *b) { + unsigned long + types[CONST_MAX(EV_KEY, EV_SW)/ULONG_BITS+1] = {}, + keys[CONST_MAX4(KEY_POWER, KEY_POWER2, KEY_SLEEP, KEY_SUSPEND)/ULONG_BITS+1] = {}, + switches[CONST_MAX(SW_LID, SW_DOCK)/ULONG_BITS+1] = {}; + struct input_mask mask; + + assert(b); + assert(b->fd >= 0); + + bitset_put(types, EV_KEY); + bitset_put(types, EV_SW); + + mask = (struct input_mask) { + .type = EV_SYN, + .codes_size = sizeof(types), + .codes_ptr = PTR_TO_UINT64(types), + }; + + if (ioctl(b->fd, EVIOCSMASK, &mask) < 0) + /* Log only at debug level if the kernel doesn't do EVIOCSMASK yet */ + return log_full_errno(IN_SET(errno, ENOTTY, EOPNOTSUPP, EINVAL) ? LOG_DEBUG : LOG_WARNING, + errno, "Failed to set EV_SYN event mask on /dev/input/%s: %m", b->name); + + bitset_put(keys, KEY_POWER); + bitset_put(keys, KEY_POWER2); + bitset_put(keys, KEY_SLEEP); + bitset_put(keys, KEY_SUSPEND); + + mask = (struct input_mask) { + .type = EV_KEY, + .codes_size = sizeof(keys), + .codes_ptr = PTR_TO_UINT64(keys), + }; + + if (ioctl(b->fd, EVIOCSMASK, &mask) < 0) + return log_warning_errno(errno, "Failed to set EV_KEY event mask on /dev/input/%s: %m", b->name); + + bitset_put(switches, SW_LID); + bitset_put(switches, SW_DOCK); + + mask = (struct input_mask) { + .type = EV_SW, + .codes_size = sizeof(switches), + .codes_ptr = PTR_TO_UINT64(switches), + }; + + if (ioctl(b->fd, EVIOCSMASK, &mask) < 0) + return log_warning_errno(errno, "Failed to set EV_SW event mask on /dev/input/%s: %m", b->name); + + return 0; +} + +int button_open(Button *b) { + char *p, name[256]; + int r; + + assert(b); + + b->fd = safe_close(b->fd); + + p = strjoina("/dev/input/", b->name); + + b->fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); + if (b->fd < 0) + return log_warning_errno(errno, "Failed to open %s: %m", p); + + r = button_suitable(b); + if (r < 0) + return log_warning_errno(r, "Failed to determine whether input device is relevant to us: %m"); + if (r == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), + "Device %s does not expose keys or switches relevant to us, ignoring.", + p); + + if (ioctl(b->fd, EVIOCGNAME(sizeof(name)), name) < 0) { + r = log_error_errno(errno, "Failed to get input name: %m"); + goto fail; + } + + (void) button_set_mask(b); + + r = sd_event_add_io(b->manager->event, &b->io_event_source, b->fd, EPOLLIN, button_dispatch, b); + if (r < 0) { + log_error_errno(r, "Failed to add button event: %m"); + goto fail; + } + + log_info("Watching system buttons on /dev/input/%s (%s)", b->name, name); + + return 0; + +fail: + b->fd = safe_close(b->fd); + return r; +} + +int button_check_switches(Button *b) { + unsigned long switches[CONST_MAX(SW_LID, SW_DOCK)/ULONG_BITS+1] = {}; + assert(b); + + if (b->fd < 0) + return -EINVAL; + + if (ioctl(b->fd, EVIOCGSW(sizeof(switches)), switches) < 0) + return -errno; + + b->lid_closed = bitset_get(switches, SW_LID); + b->docked = bitset_get(switches, SW_DOCK); + + if (b->lid_closed) + button_install_check_event_source(b); + + return 0; +} |