chiark / gitweb /
logind: support for hybrid sleep (i.e. suspend+hibernate at the same time)
[elogind.git] / src / login / logind-button.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2012 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <assert.h>
23 #include <string.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <sys/ioctl.h>
27 #include <unistd.h>
28 #include <linux/input.h>
29 #include <sys/epoll.h>
30
31 #include "conf-parser.h"
32 #include "util.h"
33 #include "logind-button.h"
34 #include "special.h"
35 #include "dbus-common.h"
36
37 Button* button_new(Manager *m, const char *name) {
38         Button *b;
39
40         assert(m);
41         assert(name);
42
43         b = new0(Button, 1);
44         if (!b)
45                 return NULL;
46
47         b->name = strdup(name);
48         if (!b->name) {
49                 free(b);
50                 return NULL;
51         }
52
53         if (hashmap_put(m->buttons, b->name, b) < 0) {
54                 free(b->name);
55                 free(b);
56                 return NULL;
57         }
58
59         b->manager = m;
60         b->fd = -1;
61
62         return b;
63 }
64
65 void button_free(Button *b) {
66         assert(b);
67
68         hashmap_remove(b->manager->buttons, b->name);
69
70         if (b->fd >= 0) {
71                 hashmap_remove(b->manager->button_fds, INT_TO_PTR(b->fd + 1));
72                 assert_se(epoll_ctl(b->manager->epoll_fd, EPOLL_CTL_DEL, b->fd, NULL) == 0);
73                 close_nointr_nofail(b->fd);
74         }
75
76         free(b->name);
77         free(b->seat);
78         free(b);
79 }
80
81 int button_set_seat(Button *b, const char *sn) {
82         char *s;
83
84         assert(b);
85         assert(sn);
86
87         s = strdup(sn);
88         if (!s)
89                 return -ENOMEM;
90
91         free(b->seat);
92         b->seat = s;
93
94         return 0;
95 }
96
97 int button_open(Button *b) {
98         char name[256], *p;
99         struct epoll_event ev;
100         int r;
101
102         assert(b);
103
104         if (b->fd >= 0) {
105                 close_nointr_nofail(b->fd);
106                 b->fd = -1;
107         }
108
109         p = strappend("/dev/input/", b->name);
110         if (!p)
111                 return log_oom();
112
113         b->fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
114         free(p);
115         if (b->fd < 0) {
116                 log_warning("Failed to open %s: %m", b->name);
117                 return -errno;
118         }
119
120         if (ioctl(b->fd, EVIOCGNAME(sizeof(name)), name) < 0) {
121                 log_error("Failed to get input name: %m");
122                 r = -errno;
123                 goto fail;
124         }
125
126         zero(ev);
127         ev.events = EPOLLIN;
128         ev.data.u32 = FD_OTHER_BASE + b->fd;
129
130         if (epoll_ctl(b->manager->epoll_fd, EPOLL_CTL_ADD, b->fd, &ev) < 0) {
131                 log_error("Failed to add to epoll: %m");
132                 r = -errno;
133                 goto fail;
134         }
135
136         r = hashmap_put(b->manager->button_fds, INT_TO_PTR(b->fd + 1), b);
137         if (r < 0) {
138                 log_error("Failed to add to hash map: %s", strerror(-r));
139                 assert_se(epoll_ctl(b->manager->epoll_fd, EPOLL_CTL_DEL, b->fd, NULL) == 0);
140                 goto fail;
141         }
142
143         log_info("Watching system buttons on /dev/input/%s (%s)", b->name, name);
144
145         return 0;
146
147 fail:
148         close_nointr_nofail(b->fd);
149         b->fd = -1;
150         return r;
151 }
152
153 static int button_handle(
154                 Button *b,
155                 InhibitWhat inhibit_key,
156                 HandleButton handle,
157                 bool ignore_inhibited,
158                 bool is_edge) {
159
160         static const char * const message_table[_HANDLE_BUTTON_MAX] = {
161                 [HANDLE_POWEROFF] = "Powering Off...",
162                 [HANDLE_REBOOT] = "Rebooting...",
163                 [HANDLE_HALT] = "Halting...",
164                 [HANDLE_KEXEC] = "Rebooting via kexec...",
165                 [HANDLE_SUSPEND] = "Suspending...",
166                 [HANDLE_HIBERNATE] = "Hibernating...",
167                 [HANDLE_HYBRID_SLEEP] = "Hibernating and suspend...",
168         };
169
170         static const char * const target_table[_HANDLE_BUTTON_MAX] = {
171                 [HANDLE_POWEROFF] = SPECIAL_POWEROFF_TARGET,
172                 [HANDLE_REBOOT] = SPECIAL_REBOOT_TARGET,
173                 [HANDLE_HALT] = SPECIAL_HALT_TARGET,
174                 [HANDLE_KEXEC] = SPECIAL_KEXEC_TARGET,
175                 [HANDLE_SUSPEND] = SPECIAL_SUSPEND_TARGET,
176                 [HANDLE_HIBERNATE] = SPECIAL_HIBERNATE_TARGET,
177                 [HANDLE_HYBRID_SLEEP] = SPECIAL_HYBRID_SLEEP_TARGET
178         };
179
180         DBusError error;
181         int r;
182         InhibitWhat inhibit_operation;
183
184         assert(b);
185
186         /* If the key handling is turned off, don't do anything */
187         if (handle == HANDLE_IGNORE) {
188                 log_debug("Refusing key handling, as it is turned off.");
189                 return 0;
190         }
191
192         /* If the key handling is inhibited, don't do anything */
193         if (manager_is_inhibited(b->manager, inhibit_key, INHIBIT_BLOCK, NULL, true, false, 0)) {
194                 log_debug("Refusing key handling, %s is inhibited.", inhibit_what_to_string(inhibit_key));
195                 return 0;
196         }
197
198         inhibit_operation = handle == HANDLE_SUSPEND || handle == HANDLE_HIBERNATE || handle == HANDLE_HYBRID_SLEEP ? INHIBIT_SLEEP : INHIBIT_SHUTDOWN;
199
200         /* If the actual operation is inhibited, warn and fail */
201         if (!ignore_inhibited &&
202             manager_is_inhibited(b->manager, inhibit_operation, INHIBIT_BLOCK, NULL, false, false, 0)) {
203
204
205                 /* If this is just a recheck of the lid switch then don't warn about anything */
206                 if (!is_edge) {
207                         log_debug("Refusing operation, %s is inhibited.", inhibit_what_to_string(inhibit_operation));
208                         return 0;
209                 }
210
211                 log_error("Refusing operation, %s is inhibited.", inhibit_what_to_string(inhibit_operation));
212                 warn_melody();
213                 return -EPERM;
214         }
215
216         log_info("%s", message_table[handle]);
217
218         /* We are executing the operation, so make sure we don't
219          * execute another one until the lid is opened/closed again */
220         b->lid_close_queued = false;
221
222         dbus_error_init(&error);
223         r = bus_manager_shutdown_or_sleep_now_or_later(b->manager, target_table[handle], inhibit_operation, &error);
224         if (r < 0) {
225                 log_error("Failed to execute operation: %s", bus_error_message(&error));
226                 dbus_error_free(&error);
227                 return r;
228         }
229
230         return 1;
231 }
232
233 int button_process(Button *b) {
234         struct input_event ev;
235         ssize_t l;
236
237         assert(b);
238
239         l = read(b->fd, &ev, sizeof(ev));
240         if (l < 0)
241                 return errno != EAGAIN ? -errno : 0;
242         if ((size_t) l < sizeof(ev))
243                 return -EIO;
244
245         if (ev.type == EV_KEY && ev.value > 0) {
246
247                 switch (ev.code) {
248
249                 case KEY_POWER:
250                 case KEY_POWER2:
251                         log_info("Power key pressed.");
252                         return button_handle(b, INHIBIT_HANDLE_POWER_KEY, b->manager->handle_power_key, b->manager->power_key_ignore_inhibited, true);
253
254                 /* The kernel is a bit confused here:
255
256                    KEY_SLEEP   = suspend-to-ram, which everybody else calls "suspend"
257                    KEY_SUSPEND = suspend-to-disk, which everybody else calls "hibernate"
258                 */
259
260                 case KEY_SLEEP:
261                         log_info("Suspend key pressed.");
262                         return button_handle(b, INHIBIT_HANDLE_SUSPEND_KEY, b->manager->handle_suspend_key, b->manager->suspend_key_ignore_inhibited, true);
263
264                 case KEY_SUSPEND:
265                         log_info("Hibernate key pressed.");
266                         return button_handle(b, INHIBIT_HANDLE_HIBERNATE_KEY, b->manager->handle_hibernate_key, b->manager->hibernate_key_ignore_inhibited, true);
267                 }
268
269         } else if (ev.type == EV_SW && ev.value > 0) {
270
271                 switch (ev.code) {
272
273                 case SW_LID:
274                         log_info("Lid closed.");
275                         b->lid_close_queued = true;
276
277                         return button_handle(b, INHIBIT_HANDLE_LID_SWITCH, b->manager->handle_lid_switch, b->manager->lid_switch_ignore_inhibited, true);
278                 }
279
280         } else if (ev.type == EV_SW && ev.value == 0) {
281
282                 switch (ev.code) {
283
284                 case SW_LID:
285                         log_info("Lid opened.");
286                         b->lid_close_queued = false;
287                         break;
288                 }
289         }
290
291         return 0;
292 }
293
294 int button_recheck(Button *b) {
295         assert(b);
296
297         if (!b->lid_close_queued)
298                 return 0;
299
300         return button_handle(b, INHIBIT_HANDLE_LID_SWITCH, b->manager->handle_lid_switch, b->manager->lid_switch_ignore_inhibited, false);
301 }
302
303 static const char* const handle_button_table[_HANDLE_BUTTON_MAX] = {
304         [HANDLE_IGNORE] = "ignore",
305         [HANDLE_POWEROFF] = "poweroff",
306         [HANDLE_REBOOT] = "reboot",
307         [HANDLE_HALT] = "halt",
308         [HANDLE_KEXEC] = "kexec",
309         [HANDLE_SUSPEND] = "suspend",
310         [HANDLE_HIBERNATE] = "hibernate",
311         [HANDLE_HYBRID_SLEEP] = "hybrid-sleep",
312 };
313 DEFINE_STRING_TABLE_LOOKUP(handle_button, HandleButton);
314 DEFINE_CONFIG_PARSE_ENUM(config_parse_handle_button, handle_button, HandleButton, "Failed to parse handle button setting");