blob: adbd0da94c77a9cb089f5f1f51c0cd17c79dcfec [file] [log] [blame]
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -07001/*
2 * Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
5 */
6
David Sodmanbf3f2842014-11-12 08:26:58 -08007#include <ctype.h>
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -07008#include <fcntl.h>
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -07009#include <stdint.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <unistd.h>
Dominik Behr93899452014-08-18 22:16:21 -070014#include <sys/select.h>
15#include <errno.h>
16#include <libudev.h>
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -070017#include "input.h"
David Sodmanbbcb0522014-09-19 10:34:07 -070018#include "dbus_interface.h"
19#include "dbus.h"
David Sodmanbf3f2842014-11-12 08:26:58 -080020#include "keysym.h"
David Sodmanbbcb0522014-09-19 10:34:07 -070021#include "util.h"
Dominik Behr580462b2014-11-20 13:50:05 -080022#include "main.h"
David Sodmanbf3f2842014-11-12 08:26:58 -080023
Dominik Behr93899452014-08-18 22:16:21 -070024struct input_dev {
25 int fd;
26 char *path;
27};
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -070028
David Sodmanbf3f2842014-11-12 08:26:58 -080029struct keyboard_state {
30 int shift_state;
31 int control_state;
32 int alt_state;
David Sodmane0cc8312014-11-18 11:16:36 -080033 int search_state;
David Sodmanbf3f2842014-11-12 08:26:58 -080034};
35
Dominik Behr93899452014-08-18 22:16:21 -070036struct {
37 struct udev *udev;
38 struct udev_monitor *udev_monitor;
39 int udev_fd;
40 unsigned int ndevs;
41 struct input_dev *devs;
David Sodmanbf3f2842014-11-12 08:26:58 -080042 struct keyboard_state kbd_state;
David Sodmanbbcb0522014-09-19 10:34:07 -070043 dbus_t *dbus;
David Sodmanbf3f2842014-11-12 08:26:58 -080044 uint32_t current_terminal;
45 terminal_t *terminals[MAX_TERMINALS];
Dominik Behr93899452014-08-18 22:16:21 -070046} input = {
47 .udev = NULL,
48 .udev_monitor = NULL,
49 .udev_fd = -1,
50 .ndevs = 0,
51 .devs = NULL,
David Sodmanbf3f2842014-11-12 08:26:58 -080052 .dbus = NULL,
53 .current_terminal = 0
Dominik Behr93899452014-08-18 22:16:21 -070054};
55
David Sodmane0cc8312014-11-18 11:16:36 -080056static void report_user_activity(int activity_type)
57{
David Sodman19e4f9d2015-03-10 11:11:09 -070058 dbus_bool_t allow_off = false;
David Sodmane0cc8312014-11-18 11:16:36 -080059 dbus_method_call1(input.dbus, kPowerManagerServiceName,
60 kPowerManagerServicePath,
61 kPowerManagerInterface,
62 kHandleUserActivityMethod,
David Sodman19e4f9d2015-03-10 11:11:09 -070063 DBUS_TYPE_INT32, &activity_type);
David Sodmane0cc8312014-11-18 11:16:36 -080064
65 switch (activity_type) {
66 case USER_ACTIVITY_BRIGHTNESS_UP_KEY_PRESS:
67 (void)dbus_method_call0(input.dbus,
68 kPowerManagerServiceName,
69 kPowerManagerServicePath,
70 kPowerManagerInterface,
71 kIncreaseScreenBrightnessMethod);
72 break;
73 case USER_ACTIVITY_BRIGHTNESS_DOWN_KEY_PRESS:
David Sodman19e4f9d2015-03-10 11:11:09 -070074 /*
75 * Shouldn't allow the screen to go
76 * completely off while frecon is active
77 * so passing false to allow_off
78 */
79 (void)dbus_method_call1(input.dbus,
David Sodmane0cc8312014-11-18 11:16:36 -080080 kPowerManagerServiceName,
81 kPowerManagerServicePath,
82 kPowerManagerInterface,
David Sodman19e4f9d2015-03-10 11:11:09 -070083 kDecreaseScreenBrightnessMethod,
84 DBUS_TYPE_BOOLEAN, &allow_off);
David Sodmane0cc8312014-11-18 11:16:36 -080085 break;
86 }
87}
88
David Sodmanbf3f2842014-11-12 08:26:58 -080089static int input_special_key(struct input_key_event *ev)
90{
91 unsigned int i;
David Sodmanb0697c22014-12-12 10:29:25 -080092 terminal_t *terminal;
David Sodmanbf3f2842014-11-12 08:26:58 -080093
94 uint32_t ignore_keys[] = {
95 BTN_TOUCH, // touchpad events
96 BTN_TOOL_FINGER,
97 BTN_TOOL_DOUBLETAP,
98 BTN_TOOL_TRIPLETAP,
99 BTN_TOOL_QUADTAP,
100 BTN_TOOL_QUINTTAP,
101 BTN_LEFT, // mouse buttons
102 BTN_RIGHT,
103 BTN_MIDDLE,
104 BTN_SIDE,
105 BTN_EXTRA,
106 BTN_FORWARD,
107 BTN_BACK,
108 BTN_TASK
109 };
110
David Sodmanafba0d92015-01-27 19:07:46 -0800111 terminal = input.terminals[input.current_terminal];
112
David Sodmanbf3f2842014-11-12 08:26:58 -0800113 for (i = 0; i < ARRAY_SIZE(ignore_keys); i++)
114 if (ev->code == ignore_keys[i])
115 return 1;
116
117 switch (ev->code) {
118 case KEY_LEFTSHIFT:
119 case KEY_RIGHTSHIFT:
120 input.kbd_state.shift_state = ! !ev->value;
121 return 1;
122 case KEY_LEFTCTRL:
123 case KEY_RIGHTCTRL:
124 input.kbd_state.control_state = ! !ev->value;
125 return 1;
126 case KEY_LEFTALT:
127 case KEY_RIGHTALT:
128 input.kbd_state.alt_state = ! !ev->value;
129 return 1;
David Sodmane0cc8312014-11-18 11:16:36 -0800130 case KEY_LEFTMETA: // search key
131 input.kbd_state.search_state = ! !ev->value;
132 return 1;
David Sodmanbf3f2842014-11-12 08:26:58 -0800133 }
134
David Sodmanafba0d92015-01-27 19:07:46 -0800135 if (term_is_active(terminal)) {
136 if (input.kbd_state.shift_state && ev->value) {
137 switch (ev->code) {
138 case KEY_PAGEUP:
David Sodmane0cc8312014-11-18 11:16:36 -0800139 term_page_up(input.terminals[input.current_terminal]);
140 return 1;
David Sodmanafba0d92015-01-27 19:07:46 -0800141 case KEY_PAGEDOWN:
David Sodmane0cc8312014-11-18 11:16:36 -0800142 term_page_down(input.terminals[input.current_terminal]);
143 return 1;
David Sodmanafba0d92015-01-27 19:07:46 -0800144 case KEY_UP:
145 term_line_up(input.terminals[input.current_terminal]);
146 return 1;
147 case KEY_DOWN:
148 term_line_down(input.terminals[input.current_terminal]);
149 return 1;
150 }
151 }
152
153 if (input.kbd_state.search_state && ev->value) {
154 switch (ev->code) {
155 case KEY_UP:
156 term_page_up(input.terminals[input.current_terminal]);
157 return 1;
158 case KEY_DOWN:
159 term_page_down(input.terminals[input.current_terminal]);
160 return 1;
161 }
162 }
163
164 if (!(input.kbd_state.search_state || input.kbd_state.alt_state ||
165 input.kbd_state.control_state) &&
166 ev->value && (ev->code >= KEY_F1) && (ev->code <= KEY_F10)) {
167 switch (ev->code) {
168 case KEY_F1:
169 case KEY_F2:
170 case KEY_F3:
171 case KEY_F4:
172 case KEY_F5:
173 break;
174 case KEY_F6:
175 case KEY_F7:
176 report_user_activity(USER_ACTIVITY_BRIGHTNESS_DOWN_KEY_PRESS -
177 (ev->code - KEY_F6));
178 break;
179 case KEY_F8:
180 case KEY_F9:
181 case KEY_F10:
182 break;
183 }
184 return 1;
David Sodmane0cc8312014-11-18 11:16:36 -0800185 }
186 }
187
David Sodmanbf3f2842014-11-12 08:26:58 -0800188 if (input.kbd_state.alt_state && input.kbd_state.control_state && ev->value) {
David Sodman1d1c67f2015-03-12 11:01:08 -0700189 /*
190 * Special case for key sequence that is used by external program. Just
191 * explicitly ignore here and do nothing.
192 */
193 if (input.kbd_state.shift_state)
194 return 1;
195
David Sodmanb0697c22014-12-12 10:29:25 -0800196 if (ev->code == KEY_F1) {
David Sodmanb0697c22014-12-12 10:29:25 -0800197 if (term_is_active(terminal)) {
David Sodmanbf3f2842014-11-12 08:26:58 -0800198 input_ungrab();
David Sodmanb0697c22014-12-12 10:29:25 -0800199 terminal->active = false;
David Sodman35d6bd82014-11-24 08:23:00 -0800200 video_release(input.terminals[input.current_terminal]->video);
David Sodmanbf3f2842014-11-12 08:26:58 -0800201 (void)dbus_method_call0(input.dbus,
202 kLibCrosServiceName,
203 kLibCrosServicePath,
204 kLibCrosServiceInterface,
205 kTakeDisplayOwnership);
David Sodmanb0697c22014-12-12 10:29:25 -0800206 }
207 } else if ((ev->code >= KEY_F2) && (ev->code < KEY_F2 + MAX_TERMINALS)) {
208 (void)dbus_method_call0(input.dbus,
209 kLibCrosServiceName,
210 kLibCrosServicePath,
211 kLibCrosServiceInterface,
212 kReleaseDisplayOwnership);
David Sodmanbf3f2842014-11-12 08:26:58 -0800213 if (term_is_active(terminal))
214 terminal->active = false;
215 input.current_terminal = ev->code - KEY_F2;
216 terminal = input.terminals[input.current_terminal];
217 if (terminal == NULL) {
218 input.terminals[input.current_terminal] =
Dominik Behr580462b2014-11-20 13:50:05 -0800219 term_init(input.current_terminal);
David Sodmanbf3f2842014-11-12 08:26:58 -0800220 terminal =
221 input.terminals[input.current_terminal];
222 if (!term_is_valid(terminal)) {
223 LOG(ERROR, "Term init failed");
224 return 1;
225 }
226 }
227 input.terminals[input.current_terminal]->active = true;
228 input_grab();
229 video_setmode(terminal->video);
230 term_redraw(terminal);
231 }
232
233 return 1;
234
235 }
236
237 return 0;
238}
239
240static void input_get_keysym_and_unicode(struct input_key_event *event,
241 uint32_t *keysym, uint32_t *unicode)
242{
243 struct {
244 uint32_t code;
245 uint32_t keysym;
246 } non_ascii_keys[] = {
247 { KEY_ESC, KEYSYM_ESC},
248 { KEY_HOME, KEYSYM_HOME},
249 { KEY_LEFT, KEYSYM_LEFT},
250 { KEY_UP, KEYSYM_UP},
251 { KEY_RIGHT, KEYSYM_RIGHT},
252 { KEY_DOWN, KEYSYM_DOWN},
253 { KEY_PAGEUP, KEYSYM_PAGEUP},
254 { KEY_PAGEDOWN, KEYSYM_PAGEDOWN},
255 { KEY_END, KEYSYM_END},
256 { KEY_INSERT, KEYSYM_INSERT},
257 { KEY_DELETE, KEYSYM_DELETE},
258 };
259
260 for (unsigned i = 0; i < ARRAY_SIZE(non_ascii_keys); i++) {
261 if (non_ascii_keys[i].code == event->code) {
262 *keysym = non_ascii_keys[i].keysym;
263 *unicode = -1;
264 return;
265 }
266 }
267
268 if (event->code >= ARRAY_SIZE(keysym_table) / 2) {
269 *keysym = '?';
270 } else {
271 *keysym = keysym_table[event->code * 2 + input.kbd_state.shift_state];
272 if ((input.kbd_state.control_state) && isascii(*keysym))
273 *keysym = tolower(*keysym) - 'a' + 1;
274 }
275
276 *unicode = *keysym;
277}
278
Dominik Behr93899452014-08-18 22:16:21 -0700279static int input_add(const char *devname)
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700280{
Dominik Behr93899452014-08-18 22:16:21 -0700281 int ret = 0, fd = -1;
282 /* for some reason every device has a null enumerations and notifications
283 of every device come with NULL string first */
284 if (!devname) {
285 ret = -EINVAL;
286 goto errorret;
287 }
288 ret = fd = open(devname, O_RDONLY);
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700289 if (fd < 0)
Dominik Behr93899452014-08-18 22:16:21 -0700290 goto errorret;
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700291
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700292 ret = ioctl(fd, EVIOCGRAB, (void *) 1);
293
294 if (!ret) {
295 ioctl(fd, EVIOCGRAB, (void *) 0);
296 } else {
David Sodmanbbcb0522014-09-19 10:34:07 -0700297 LOG(ERROR, "Evdev device %s grabbed by another process",
Dominik Behr93899452014-08-18 22:16:21 -0700298 devname);
299 ret = -EBUSY;
300 goto closefd;
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700301 }
302
Dominik Behr93899452014-08-18 22:16:21 -0700303 struct input_dev *newdevs =
304 realloc(input.devs, (input.ndevs + 1) * sizeof (struct input_dev));
305 if (!newdevs) {
306 ret = -ENOMEM;
307 goto closefd;
308 }
309 input.devs = newdevs;
310 input.devs[input.ndevs].fd = fd;
311 input.devs[input.ndevs].path = strdup(devname);
312 if (!input.devs[input.ndevs].path) {
313 ret = -ENOMEM;
314 goto closefd;
315 }
316 input.ndevs++;
317
318 return fd;
319
320closefd:
321 close(fd);
322errorret:
323 return ret;
324}
325
326static void input_remove(const char *devname)
327{
328 if (!devname) {
329 return;
330 }
331 unsigned int u;
332 for (u = 0; u < input.ndevs; u++) {
333 if (!strcmp(devname, input.devs[u].path)) {
334 free(input.devs[u].path);
335 close(input.devs[u].fd);
336 input.ndevs--;
337 if (u != input.ndevs) {
338 input.devs[u] = input.devs[input.ndevs];
339 }
340 return;
341 }
342 }
343}
344
Dominik Behr93899452014-08-18 22:16:21 -0700345
346int input_init()
347{
348 input.udev = udev_new();
349 if (!input.udev)
350 return -ENOENT;
351 input.udev_monitor = udev_monitor_new_from_netlink(input.udev, "udev");
352 if (!input.udev_monitor) {
353 udev_unref(input.udev);
354 return -ENOENT;
355 }
356 udev_monitor_filter_add_match_subsystem_devtype(input.udev_monitor, "input",
357 NULL);
358 udev_monitor_enable_receiving(input.udev_monitor);
359 input.udev_fd = udev_monitor_get_fd(input.udev_monitor);
360
361 struct udev_enumerate *udev_enum;
362 struct udev_list_entry *devices, *deventry;
363 udev_enum = udev_enumerate_new(input.udev);
364 udev_enumerate_add_match_subsystem(udev_enum, "input");
365 udev_enumerate_scan_devices(udev_enum);
366 devices = udev_enumerate_get_list_entry(udev_enum);
367 udev_list_entry_foreach(deventry, devices) {
368 const char *syspath;
369 struct udev_device *dev;
370 syspath = udev_list_entry_get_name(deventry);
371 dev = udev_device_new_from_syspath(input.udev, syspath);
372 input_add(udev_device_get_devnode(dev));
373 udev_device_unref(dev);
374 }
375 udev_enumerate_unref(udev_enum);
376
377 if (!isatty(fileno(stdout)))
378 setbuf(stdout, NULL);
379
David Sodmanbbcb0522014-09-19 10:34:07 -0700380 if (input.ndevs == 0) {
381 LOG(ERROR, "No valid inputs for terminal");
382 exit(EXIT_SUCCESS);
383 }
384
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700385 return 0;
386}
387
388void input_close()
389{
Dominik Behr93899452014-08-18 22:16:21 -0700390 unsigned int u;
391 for (u = 0; u < input.ndevs; u++) {
392 free(input.devs[u].path);
393 close(input.devs[u].fd);
394 }
395 free(input.devs);
396 input.devs = NULL;
397 input.ndevs = 0;
398
399 udev_monitor_unref(input.udev_monitor);
400 input.udev_monitor = NULL;
401 udev_unref(input.udev);
402 input.udev = NULL;
403 input.udev_fd = -1;
404
David Sodmanbbcb0522014-09-19 10:34:07 -0700405 dbus_destroy(input.dbus);
406
407}
408
409void input_set_dbus(dbus_t* dbus)
410{
411 input.dbus = dbus;
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700412}
413
Dominik Behr93899452014-08-18 22:16:21 -0700414int input_setfds(fd_set * read_set, fd_set * exception_set)
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700415{
Dominik Behr93899452014-08-18 22:16:21 -0700416 unsigned int u;
417 int max = -1;
418 for (u = 0; u < input.ndevs; u++) {
419 FD_SET(input.devs[u].fd, read_set);
420 FD_SET(input.devs[u].fd, exception_set);
421 if (input.devs[u].fd > max)
422 max = input.devs[u].fd;
423 }
424
425 FD_SET(input.udev_fd, read_set);
426 FD_SET(input.udev_fd, exception_set);
427 if (input.udev_fd > max)
428 max = input.udev_fd;
429 return max;
430}
431
Dominik Behr93899452014-08-18 22:16:21 -0700432struct input_key_event *input_get_event(fd_set * read_set,
433 fd_set * exception_set)
434{
435 unsigned int u;
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700436 struct input_event ev;
437 int ret;
438
Dominik Behr93899452014-08-18 22:16:21 -0700439 if (FD_ISSET(input.udev_fd, exception_set)) {
440 /* udev died on us? */
David Sodmanbbcb0522014-09-19 10:34:07 -0700441 LOG(ERROR, "Exception on udev fd");
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700442 }
443
Dominik Behr93899452014-08-18 22:16:21 -0700444 if (FD_ISSET(input.udev_fd, read_set)
445 && !FD_ISSET(input.udev_fd, exception_set)) {
446 /* we got an udev notification */
447 struct udev_device *dev =
448 udev_monitor_receive_device(input.udev_monitor);
449 if (dev) {
450 if (!strcmp("add", udev_device_get_action(dev))) {
451 input_add(udev_device_get_devnode(dev));
452 } else
453 if (!strcmp("remove", udev_device_get_action(dev)))
454 {
455 input_remove(udev_device_get_devnode(dev));
456 }
457 udev_device_unref(dev);
458 }
459 }
460
461 for (u = 0; u < input.ndevs; u++) {
462 if (FD_ISSET(input.devs[u].fd, read_set)
463 && !FD_ISSET(input.devs[u].fd, exception_set)) {
464 ret =
465 read(input.devs[u].fd, &ev, sizeof (struct input_event));
Michael Spang24d20122015-04-22 13:58:16 -0400466 if (ret < 0) {
467 if (errno == EINTR || errno == EAGAIN)
468 continue;
469 if (errno != ENODEV) {
470 LOG(ERROR, "read: %s: %s", input.devs[u].path,
471 strerror(errno));
472 }
473 input_remove(input.devs[u].path);
474 return NULL;
475 } else if (ret < (int) sizeof (struct input_event)) {
David Sodmanbbcb0522014-09-19 10:34:07 -0700476 LOG(ERROR, "expected %d bytes, got %d",
Dominik Behr93899452014-08-18 22:16:21 -0700477 (int) sizeof (struct input_event), ret);
478 return NULL;
479 }
480
481 if (ev.type == EV_KEY) {
482 struct input_key_event *event =
483 malloc(sizeof (*event));
484 event->code = ev.code;
485 event->value = ev.value;
Dominik Behr93899452014-08-18 22:16:21 -0700486 return event;
487 }
488 }
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700489 }
490
491 return NULL;
492}
493
David Sodmanbf3f2842014-11-12 08:26:58 -0800494int input_run(bool standalone)
495{
496 fd_set read_set, exception_set;
497 terminal_t* terminal;
498
499 if (standalone) {
500 (void)dbus_method_call0(input.dbus,
501 kLibCrosServiceName,
502 kLibCrosServicePath,
503 kLibCrosServiceInterface,
504 kReleaseDisplayOwnership);
505
Dominik Behr580462b2014-11-20 13:50:05 -0800506 input.terminals[input.current_terminal] = term_init(input.current_terminal);
David Sodmanbf3f2842014-11-12 08:26:58 -0800507 terminal = input.terminals[input.current_terminal];
508 if (term_is_valid(terminal)) {
509 input_grab();
510 }
511 }
512
513 while (1) {
514 terminal = input.terminals[input.current_terminal];
515
516 FD_ZERO(&read_set);
517 FD_ZERO(&exception_set);
518 term_add_fd(terminal, &read_set, &exception_set);
519
520 int maxfd = input_setfds(&read_set, &exception_set);
521
522 maxfd = MAX(maxfd, term_fd(terminal)) + 1;
523
524 select(maxfd, &read_set, NULL, &exception_set, NULL);
525
526 if (term_exception(terminal, &exception_set))
527 return -1;
528
David Sodmanafba0d92015-01-27 19:07:46 -0800529 struct input_key_event *event;
530 event = input_get_event(&read_set, &exception_set);
531 if (event) {
532 if (!input_special_key(event) && event->value) {
533 uint32_t keysym, unicode;
534 // current_terminal can possibly change during
535 // execution of input_special_key
536 terminal = input.terminals[input.current_terminal];
537 if (term_is_active(terminal)) {
538 // Only report user activity when the terminal is active
539 report_user_activity(USER_ACTIVITY_OTHER);
540 input_get_keysym_and_unicode(
541 event, &keysym, &unicode);
542 term_key_event(terminal,
543 keysym, unicode);
David Sodmanbf3f2842014-11-12 08:26:58 -0800544 }
545 }
David Sodmanafba0d92015-01-27 19:07:46 -0800546 input_put_event(event);
David Sodmanbf3f2842014-11-12 08:26:58 -0800547 }
548
David Sodmanbf3f2842014-11-12 08:26:58 -0800549 term_dispatch_io(terminal, &read_set);
550
551 if (term_is_valid(terminal)) {
552 if (term_is_child_done(terminal)) {
David Sodman5e5c37e2014-12-19 14:14:46 -0800553 if (terminal->video) {
554 // necessary in case chrome is playing full screen
555 // video or graphics
556 //TODO: This is still a race with Chrome. This
557 //needs to be fixed with bug 444209
558 video_setmode(terminal->video);
559 }
David Sodmanbf3f2842014-11-12 08:26:58 -0800560 term_close(terminal);
Dominik Behr580462b2014-11-20 13:50:05 -0800561 input.terminals[input.current_terminal] = term_init(input.current_terminal);
David Sodmanbf3f2842014-11-12 08:26:58 -0800562 terminal = input.terminals[input.current_terminal];
563 if (!term_is_valid(terminal)) {
564 return -1;
565 }
566 }
567 }
568 }
569
570 return 0;
571}
572
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700573void input_put_event(struct input_key_event *event)
574{
575 free(event);
576}
David Sodmanbbcb0522014-09-19 10:34:07 -0700577
578void input_grab()
579{
580 unsigned int i;
581 for (i = 0; i < input.ndevs; i++) {
582 (void)ioctl(input.devs[i].fd, EVIOCGRAB, (void *) 1);
583 }
584}
585
586void input_ungrab()
587{
588 unsigned int i;
589 for (i = 0; i < input.ndevs; i++) {
590 (void)ioctl(input.devs[i].fd, EVIOCGRAB, (void*) 0);
591 }
592}