]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/login/logind-button.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / login / logind-button.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
e9d21f24
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2012 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
e9d21f24
LP
21#include <errno.h>
22#include <fcntl.h>
07630cea 23#include <string.h>
e9d21f24
LP
24#include <sys/ioctl.h>
25#include <unistd.h>
26#include <linux/input.h>
e9d21f24 27
cc377381 28#include "sd-messages.h"
07630cea 29
b5efdb8a 30#include "alloc-util.h"
3ffd4af2
LP
31#include "fd-util.h"
32#include "logind-button.h"
07630cea 33#include "string-util.h"
e9d21f24 34#include "util.h"
e9d21f24 35
d5dd44b0
LP
36#define CONST_MAX4(a, b, c, d) CONST_MAX(CONST_MAX(a, b), CONST_MAX(c, d))
37
38#define ULONG_BITS (sizeof(unsigned long)*8)
39
40static bool bitset_get(const unsigned long *bits, unsigned i) {
41 return (bits[i / ULONG_BITS] >> (i % ULONG_BITS)) & 1UL;
42}
43
44static void bitset_put(unsigned long *bits, unsigned i) {
45 bits[i / ULONG_BITS] |= (unsigned long) 1 << (i % ULONG_BITS);
46}
47
e9d21f24
LP
48Button* button_new(Manager *m, const char *name) {
49 Button *b;
50
51 assert(m);
52 assert(name);
53
54 b = new0(Button, 1);
55 if (!b)
56 return NULL;
57
58 b->name = strdup(name);
6b430fdb
ZJS
59 if (!b->name)
60 return mfree(b);
e9d21f24
LP
61
62 if (hashmap_put(m->buttons, b->name, b) < 0) {
63 free(b->name);
6b430fdb 64 return mfree(b);
e9d21f24
LP
65 }
66
67 b->manager = m;
68 b->fd = -1;
69
70 return b;
71}
72
73void button_free(Button *b) {
74 assert(b);
75
76 hashmap_remove(b->manager->buttons, b->name);
77
ed4ba7e4
LP
78 sd_event_source_unref(b->io_event_source);
79 sd_event_source_unref(b->check_event_source);
c30a0c62 80
ece174c5 81 if (b->fd >= 0)
c30a0c62
LP
82 /* If the device has been unplugged close() returns
83 * ENODEV, let's ignore this, hence we don't use
03e334a1 84 * safe_close() */
4fba5796 85 (void) close(b->fd);
e9d21f24
LP
86
87 free(b->name);
88 free(b->seat);
89 free(b);
90}
91
92int button_set_seat(Button *b, const char *sn) {
93 char *s;
94
95 assert(b);
96 assert(sn);
97
98 s = strdup(sn);
99 if (!s)
100 return -ENOMEM;
101
102 free(b->seat);
103 b->seat = s;
104
105 return 0;
106}
107
3c56cab4
BW
108static void button_lid_switch_handle_action(Manager *manager, bool is_edge) {
109 HandleAction handle_action;
110
111 assert(manager);
112
113 /* If we are docked, handle the lid switch differently */
602a41c2 114 if (manager_is_docked_or_external_displays(manager))
3c56cab4
BW
115 handle_action = manager->handle_lid_switch_docked;
116 else
117 handle_action = manager->handle_lid_switch;
118
119 manager_handle_action(manager, INHIBIT_HANDLE_LID_SWITCH, handle_action, manager->lid_switch_ignore_inhibited, is_edge);
120}
121
ed4ba7e4
LP
122static int button_recheck(sd_event_source *e, void *userdata) {
123 Button *b = userdata;
beaafb2e 124
ed4ba7e4
LP
125 assert(b);
126 assert(b->lid_closed);
127
3c56cab4 128 button_lid_switch_handle_action(b->manager, false);
ed4ba7e4
LP
129 return 1;
130}
e9d21f24 131
ed4ba7e4
LP
132static int button_install_check_event_source(Button *b) {
133 int r;
e9d21f24
LP
134 assert(b);
135
ed4ba7e4 136 /* Install a post handler, so that we keep rechecking as long as the lid is closed. */
6de0e0e5 137
ed4ba7e4
LP
138 if (b->check_event_source)
139 return 0;
140
141 r = sd_event_add_post(b->manager->event, &b->check_event_source, button_recheck, b);
142 if (r < 0)
143 return r;
144
145 return sd_event_source_set_priority(b->check_event_source, SD_EVENT_PRIORITY_IDLE+1);
e9d21f24
LP
146}
147
cc377381
LP
148static int button_dispatch(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
149 Button *b = userdata;
e9d21f24
LP
150 struct input_event ev;
151 ssize_t l;
152
cc377381
LP
153 assert(s);
154 assert(fd == b->fd);
e9d21f24
LP
155 assert(b);
156
157 l = read(b->fd, &ev, sizeof(ev));
158 if (l < 0)
159 return errno != EAGAIN ? -errno : 0;
160 if ((size_t) l < sizeof(ev))
161 return -EIO;
162
e9d21f24
LP
163 if (ev.type == EV_KEY && ev.value > 0) {
164
165 switch (ev.code) {
166
167 case KEY_POWER:
168 case KEY_POWER2:
c485437f 169 log_struct(LOG_INFO,
e2cc6eca 170 LOG_MESSAGE("Power key pressed."),
2b044526 171 "MESSAGE_ID=" SD_MESSAGE_POWER_KEY_STR,
c485437f 172 NULL);
7b77ed8c 173
ed4ba7e4 174 manager_handle_action(b->manager, INHIBIT_HANDLE_POWER_KEY, b->manager->handle_power_key, b->manager->power_key_ignore_inhibited, true);
7b77ed8c 175 break;
e9d21f24 176
8e7fd6ad
LP
177 /* The kernel is a bit confused here:
178
179 KEY_SLEEP = suspend-to-ram, which everybody else calls "suspend"
180 KEY_SUSPEND = suspend-to-disk, which everybody else calls "hibernate"
181 */
182
e9d21f24 183 case KEY_SLEEP:
c485437f 184 log_struct(LOG_INFO,
e2cc6eca 185 LOG_MESSAGE("Suspend key pressed."),
2b044526 186 "MESSAGE_ID=" SD_MESSAGE_SUSPEND_KEY_STR,
c485437f 187 NULL);
7b77ed8c 188
ed4ba7e4 189 manager_handle_action(b->manager, INHIBIT_HANDLE_SUSPEND_KEY, b->manager->handle_suspend_key, b->manager->suspend_key_ignore_inhibited, true);
7b77ed8c 190 break;
e9d21f24 191
8e7fd6ad 192 case KEY_SUSPEND:
c485437f 193 log_struct(LOG_INFO,
e2cc6eca 194 LOG_MESSAGE("Hibernate key pressed."),
2b044526 195 "MESSAGE_ID=" SD_MESSAGE_HIBERNATE_KEY_STR,
c485437f 196 NULL);
7b77ed8c 197
ed4ba7e4 198 manager_handle_action(b->manager, INHIBIT_HANDLE_HIBERNATE_KEY, b->manager->handle_hibernate_key, b->manager->hibernate_key_ignore_inhibited, true);
7b77ed8c 199 break;
e9d21f24 200 }
8e7fd6ad 201
e9d21f24
LP
202 } else if (ev.type == EV_SW && ev.value > 0) {
203
7b77ed8c 204 if (ev.code == SW_LID) {
c485437f 205 log_struct(LOG_INFO,
e2cc6eca 206 LOG_MESSAGE("Lid closed."),
2b044526 207 "MESSAGE_ID=" SD_MESSAGE_LID_CLOSED_STR,
c485437f 208 NULL);
65b51162 209
ed4ba7e4 210 b->lid_closed = true;
3c56cab4 211 button_lid_switch_handle_action(b->manager, true);
ed4ba7e4 212 button_install_check_event_source(b);
2d62c530
LP
213
214 } else if (ev.code == SW_DOCK) {
215 log_struct(LOG_INFO,
e2cc6eca 216 LOG_MESSAGE("System docked."),
2b044526 217 "MESSAGE_ID=" SD_MESSAGE_SYSTEM_DOCKED_STR,
2d62c530
LP
218 NULL);
219
220 b->docked = true;
65b51162
LP
221 }
222
223 } else if (ev.type == EV_SW && ev.value == 0) {
224
7b77ed8c 225 if (ev.code == SW_LID) {
c485437f 226 log_struct(LOG_INFO,
e2cc6eca 227 LOG_MESSAGE("Lid opened."),
2b044526 228 "MESSAGE_ID=" SD_MESSAGE_LID_OPENED_STR,
c485437f 229 NULL);
7b77ed8c 230
ed4ba7e4
LP
231 b->lid_closed = false;
232 b->check_event_source = sd_event_source_unref(b->check_event_source);
2d62c530
LP
233
234 } else if (ev.code == SW_DOCK) {
235 log_struct(LOG_INFO,
e2cc6eca 236 LOG_MESSAGE("System undocked."),
2b044526 237 "MESSAGE_ID=" SD_MESSAGE_SYSTEM_UNDOCKED_STR,
2d62c530
LP
238 NULL);
239
240 b->docked = false;
e9d21f24
LP
241 }
242 }
243
244 return 0;
245}
246
2546b70a
LP
247static int button_suitable(Button *b) {
248 unsigned long types[CONST_MAX(EV_KEY, EV_SW)/ULONG_BITS+1];
249
250 assert(b);
251 assert(b->fd);
252
253 if (ioctl(b->fd, EVIOCGBIT(EV_SYN, sizeof(types)), types) < 0)
254 return -errno;
255
256 if (bitset_get(types, EV_KEY)) {
257 unsigned long keys[CONST_MAX4(KEY_POWER, KEY_POWER2, KEY_SLEEP, KEY_SUSPEND)/ULONG_BITS+1];
258
259 if (ioctl(b->fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys) < 0)
260 return -errno;
261
262 if (bitset_get(keys, KEY_POWER) ||
263 bitset_get(keys, KEY_POWER2) ||
264 bitset_get(keys, KEY_SLEEP) ||
265 bitset_get(keys, KEY_SUSPEND))
266 return true;
267 }
268
269 if (bitset_get(types, EV_SW)) {
270 unsigned long switches[CONST_MAX(SW_LID, SW_DOCK)/ULONG_BITS+1];
271
272 if (ioctl(b->fd, EVIOCGBIT(EV_SW, sizeof(switches)), switches) < 0)
273 return -errno;
274
275 if (bitset_get(switches, SW_LID) ||
276 bitset_get(switches, SW_DOCK))
277 return true;
278 }
279
280 return false;
281}
282
d5dd44b0
LP
283static int button_set_mask(Button *b) {
284 unsigned long
285 types[CONST_MAX(EV_KEY, EV_SW)/ULONG_BITS+1] = {},
286 keys[CONST_MAX4(KEY_POWER, KEY_POWER2, KEY_SLEEP, KEY_SUSPEND)/ULONG_BITS+1] = {},
287 switches[CONST_MAX(SW_LID, SW_DOCK)/ULONG_BITS+1] = {};
288 struct input_mask mask;
289
290 assert(b);
291 assert(b->fd >= 0);
292
293 bitset_put(types, EV_KEY);
294 bitset_put(types, EV_SW);
295
296 mask = (struct input_mask) {
297 .type = EV_SYN,
298 .codes_size = sizeof(types),
299 .codes_ptr = PTR_TO_UINT64(types),
300 };
301
302 if (ioctl(b->fd, EVIOCSMASK, &mask) < 0)
303 /* Log only at debug level if the kernel doesn't do EVIOCSMASK yet */
304 return log_full_errno(IN_SET(errno, ENOTTY, EOPNOTSUPP, EINVAL) ? LOG_DEBUG : LOG_WARNING,
305 errno, "Failed to set EV_SYN event mask on /dev/input/%s: %m", b->name);
306
307 bitset_put(keys, KEY_POWER);
308 bitset_put(keys, KEY_POWER2);
309 bitset_put(keys, KEY_SLEEP);
310 bitset_put(keys, KEY_SUSPEND);
311
312 mask = (struct input_mask) {
313 .type = EV_KEY,
314 .codes_size = sizeof(keys),
315 .codes_ptr = PTR_TO_UINT64(keys),
316 };
317
318 if (ioctl(b->fd, EVIOCSMASK, &mask) < 0)
319 return log_warning_errno(errno, "Failed to set EV_KEY event mask on /dev/input/%s: %m", b->name);
320
321 bitset_put(switches, SW_LID);
322 bitset_put(switches, SW_DOCK);
323
324 mask = (struct input_mask) {
325 .type = EV_SW,
326 .codes_size = sizeof(switches),
327 .codes_ptr = PTR_TO_UINT64(switches),
328 };
329
330 if (ioctl(b->fd, EVIOCSMASK, &mask) < 0)
331 return log_warning_errno(errno, "Failed to set EV_SW event mask on /dev/input/%s: %m", b->name);
332
333 return 0;
334}
335
cc377381
LP
336int button_open(Button *b) {
337 char *p, name[256];
338 int r;
339
340 assert(b);
341
7f6e12b0 342 b->fd = safe_close(b->fd);
cc377381 343
63c372cb 344 p = strjoina("/dev/input/", b->name);
cc377381
LP
345
346 b->fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
4a62c710 347 if (b->fd < 0)
2546b70a
LP
348 return log_warning_errno(errno, "Failed to open %s: %m", p);
349
350 r = button_suitable(b);
351 if (r < 0)
352 return log_warning_errno(r, "Failed to determine whether input device is relevant to us: %m");
353 if (r == 0) {
354 log_debug("Device %s does not expose keys or switches relevant to us, ignoring.", p);
355 return -EADDRNOTAVAIL;
356 }
cc377381
LP
357
358 if (ioctl(b->fd, EVIOCGNAME(sizeof(name)), name) < 0) {
76ef789d 359 r = log_error_errno(errno, "Failed to get input name: %m");
cc377381
LP
360 goto fail;
361 }
362
d5dd44b0
LP
363 (void) button_set_mask(b);
364
ed4ba7e4 365 r = sd_event_add_io(b->manager->event, &b->io_event_source, b->fd, EPOLLIN, button_dispatch, b);
cc377381 366 if (r < 0) {
da927ba9 367 log_error_errno(r, "Failed to add button event: %m");
cc377381
LP
368 goto fail;
369 }
370
371 log_info("Watching system buttons on /dev/input/%s (%s)", b->name, name);
372
373 return 0;
374
375fail:
66e40583 376 b->fd = safe_close(b->fd);
cc377381
LP
377 return r;
378}
379
2d62c530 380int button_check_switches(Button *b) {
d5dd44b0 381 unsigned long switches[CONST_MAX(SW_LID, SW_DOCK)/ULONG_BITS+1] = {};
65b51162
LP
382 assert(b);
383
ed4ba7e4
LP
384 if (b->fd < 0)
385 return -EINVAL;
386
387 if (ioctl(b->fd, EVIOCGSW(sizeof(switches)), switches) < 0)
388 return -errno;
65b51162 389
d5dd44b0
LP
390 b->lid_closed = bitset_get(switches, SW_LID);
391 b->docked = bitset_get(switches, SW_DOCK);
ed4ba7e4 392
2d62c530 393 if (b->lid_closed)
ed4ba7e4 394 button_install_check_event_source(b);
ed4ba7e4
LP
395
396 return 0;
65b51162 397}