chiark / gitweb /
logind: rework power key/suspend key/lid switch handling
[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(Button *b, InhibitWhat inhibit_key, HandleButton handle, bool ignore_inhibited) {
154
155         static const char * const message_table[_HANDLE_BUTTON_MAX] = {
156                 [HANDLE_POWEROFF] = "Powering Off...",
157                 [HANDLE_REBOOT] = "Rebooting...",
158                 [HANDLE_HALT] = "Halting...",
159                 [HANDLE_KEXEC] = "Rebooting via kexec...",
160                 [HANDLE_SUSPEND] = "Suspending...",
161                 [HANDLE_HIBERNATE] = "Hibernating..."
162         };
163
164         static const char * const target_table[_HANDLE_BUTTON_MAX] = {
165                 [HANDLE_POWEROFF] = "poweroff.target",
166                 [HANDLE_REBOOT] = "reboot.target",
167                 [HANDLE_HALT] = "halt.target",
168                 [HANDLE_KEXEC] = "kexec.target",
169                 [HANDLE_SUSPEND] = "suspend.target",
170                 [HANDLE_HIBERNATE] = "hibernate.target"
171         };
172
173         DBusError error;
174         int r;
175         InhibitWhat inhibit_operation;
176
177         assert(b);
178
179         /* If the key handling is turned off, don't do anything */
180         if (handle == HANDLE_IGNORE) {
181                 log_debug("Refusing key handling, as it is turned off.");
182                 return 0;
183         }
184
185         /* If the key handling is inhibited, don't do anything */
186         if (manager_is_inhibited(b->manager, inhibit_key, INHIBIT_BLOCK, NULL, true)) {
187                 log_debug("Refusing key handling, %s is inhibited.", inhibit_what_to_string(inhibit_key));
188                 return 0;
189         }
190
191         inhibit_operation = handle == HANDLE_SUSPEND || handle == HANDLE_HIBERNATE ? INHIBIT_SLEEP : INHIBIT_SHUTDOWN;
192
193         /* If the actual operation is inhibited, warn and fail */
194         if (!ignore_inhibited &&
195             manager_is_inhibited(b->manager, inhibit_operation, INHIBIT_BLOCK, NULL, false)) {
196                 log_error("Refusing operation, %s is inhibited.", inhibit_what_to_string(inhibit_operation));
197                 warn_melody();
198                 return -EPERM;
199         }
200
201         log_info("%s", message_table[handle]);
202
203         dbus_error_init(&error);
204         r = bus_manager_shutdown_or_sleep_now_or_later(b->manager, target_table[handle], inhibit_operation, &error);
205         if (r < 0) {
206                 log_error("Failed to execute operation: %s", bus_error_message(&error));
207                 dbus_error_free(&error);
208         }
209
210         return r;
211 }
212
213 int button_process(Button *b) {
214         struct input_event ev;
215         ssize_t l;
216
217         assert(b);
218
219         l = read(b->fd, &ev, sizeof(ev));
220         if (l < 0)
221                 return errno != EAGAIN ? -errno : 0;
222         if ((size_t) l < sizeof(ev))
223                 return -EIO;
224
225         if (ev.type == EV_KEY && ev.value > 0) {
226
227                 switch (ev.code) {
228
229                 case KEY_POWER:
230                 case KEY_POWER2:
231                         log_info("Power key pressed.");
232                         return button_handle(b, INHIBIT_HANDLE_POWER_KEY, b->manager->handle_power_key, b->manager->power_key_ignore_inhibited);
233
234                 case KEY_SLEEP:
235                 case KEY_SUSPEND:
236                         log_info("Sleep key pressed.");
237                         return button_handle(b, INHIBIT_HANDLE_SLEEP_KEY, b->manager->handle_sleep_key, b->manager->sleep_key_ignore_inhibited);
238
239                 }
240         } else if (ev.type == EV_SW && ev.value > 0) {
241
242                 switch (ev.code) {
243
244                 case SW_LID:
245                         log_info("Lid closed.");
246                         return button_handle(b, INHIBIT_HANDLE_LID_SWITCH, b->manager->handle_lid_switch, b->manager->lid_switch_ignore_inhibited);
247                 }
248         }
249
250         return 0;
251 }
252
253 static const char* const handle_button_table[_HANDLE_BUTTON_MAX] = {
254         [HANDLE_IGNORE] = "ignore",
255         [HANDLE_POWEROFF] = "poweroff",
256         [HANDLE_REBOOT] = "reboot",
257         [HANDLE_HALT] = "halt",
258         [HANDLE_KEXEC] = "kexec",
259         [HANDLE_SUSPEND] = "suspend",
260         [HANDLE_HIBERNATE] = "hibernate"
261 };
262 DEFINE_STRING_TABLE_LOOKUP(handle_button, HandleButton);
263 DEFINE_CONFIG_PARSE_ENUM(config_parse_handle_button, handle_button, HandleButton, "Failed to parse handle button setting");