]> git.vomp.tv Git - vompclient.git/blob - inputlinux.cc
Log conversion
[vompclient.git] / inputlinux.cc
1 /*
2     Copyright 2004-2020 Chris Tallon; 2012 Marten Richter
3
4     This file is part of VOMP.
5
6     VOMP is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     VOMP is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with VOMP.  If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #include <stdio.h>
21 #include <linux/input.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <sys/ioctl.h>
25 #include <fcntl.h>
26 #include <errno.h>
27
28 #include <cstdio>
29 #include <iostream>
30 #include <ostream>
31
32 #include "i18n.h"
33 #include "vdr.h"
34 #include "woptionpane.h"
35
36 #include "inputlinux.h"
37
38 static const char* TAG = "InputLinux";
39
40 #define test_bit(input,b)  ((1 << ((b) % 8))&(input)[b / 8] )
41
42 bool InputLinux::init()
43 {
44   if (initted) return false;
45   initted = 1;
46
47   InitHWCListwithDefaults();
48   InitKeymap();
49
50   for (int eventid = 0; eventid < 100; eventid++)
51   {
52     char buffer[1024];
53     sprintf(buffer,"/dev/input/event%d", eventid);
54
55     struct stat test_buf;
56     if (stat(buffer, &test_buf) == 0)
57     {
58       LogNT::getInstance()->info(TAG, "Probe /dev/input/event{}", eventid);
59       // file exists
60       unsigned long ev_type = 0;
61       int new_fd = open(buffer, O_RDONLY);
62       if (new_fd < 0)
63       {
64         LogNT::getInstance()->info(TAG, "Can not open /dev/input/event{}", eventid);
65         continue;
66       }
67
68       if (ioctl(new_fd, EVIOCGBIT(0, EV_MAX), &ev_type) < 0)
69       {
70         LogNT::getInstance()->info(TAG, "Ioctl failed /dev/input/event{} {}", eventid, errno);
71         close(new_fd);
72       }
73
74       //Now test if it generates keyboard presses
75       if (test_bit(reinterpret_cast<char*>(&ev_type), EV_KEY))
76       {
77         LogNT::getInstance()->info(TAG, "Add /dev/input/event{} to List", eventid);
78         devices.push_back(new_fd);
79
80         // Grab the device - make it exclusive to vomp. Fixes rubbish input going to console in background
81         ioctl(new_fd, EVIOCGRAB, 1);
82       }
83       else
84       {
85         close(new_fd);
86       }
87     }
88   }
89   return true;
90 }
91
92 void InputLinux::shutdown()
93 {
94   if (!initted) return;
95
96   while (devices.size())
97   {
98     int cur_fd = devices.back();
99     devices.pop_back();
100     ioctl(cur_fd, EVIOCGRAB, 0);
101     close(cur_fd);
102   }
103
104   initted = 0;
105 }
106
107 UCHAR InputLinux::TranslateHWCFixed(int code)
108 {
109   // Translate /dev/input codes to VOMP codes for the hard coded buttons
110   switch (code)
111   {
112     case KEY_DOWN:      return DOWN;
113     case KEY_UP:        return UP;
114     case KEY_LEFT:      return LEFT;
115     case KEY_RIGHT:     return RIGHT;
116     case KEY_M:
117     case KEY_MEDIA:     return MENU;
118     case KEY_BACKSPACE:
119     case KEY_EXIT:      return BACK;
120     case KEY_ENTER:
121     case KEY_SPACE:
122     case KEY_OK:        return OK;
123     case KEY_SLEEP:
124     case KEY_POWER:
125     case KEY_ESC:       return POWER;
126     case POWER:         return POWER; // Where does this come from?
127     default:
128       return NA_UNKNOWN;
129   }
130 }
131
132 void InputLinux::InitHWCListwithDefaults()
133 {
134   LogNT::getInstance()->info(TAG, "InitHWCListwithDefaults");
135
136   // Processing VK_Messages
137   translist[KEY_9] = NINE;
138   translist[KEY_8] = EIGHT;
139   translist[KEY_7] = SEVEN;
140   translist[KEY_6] = SIX;
141   translist[KEY_5] = FIVE;
142   translist[KEY_4] = FOUR;
143   translist[KEY_3] = THREE;
144   translist[KEY_2] = TWO;
145   translist[KEY_1] = ONE;
146   translist[KEY_0] = ZERO;
147   translist[KEY_KPDOT] = STAR;
148   // translist[KEY_#] = HASH;
149
150   translist[KEY_KP9] = NINE;
151   translist[KEY_KP8] = EIGHT;
152   translist[KEY_KP7] = SEVEN;
153   translist[KEY_KP6] = SIX;
154   translist[KEY_KP5] = FIVE;
155   translist[KEY_KP4] = FOUR;
156   translist[KEY_KP3] = THREE;
157   translist[KEY_KP2] = TWO;
158   translist[KEY_KP1] = ONE;
159   translist[KEY_KP0] = ZERO;
160
161   translist[KEY_NUMERIC_9] = NINE;
162   translist[KEY_NUMERIC_8] = EIGHT;
163   translist[KEY_NUMERIC_7] = SEVEN;
164   translist[KEY_NUMERIC_6] = SIX;
165   translist[KEY_NUMERIC_5] = FIVE;
166   translist[KEY_NUMERIC_4] = FOUR;
167   translist[KEY_NUMERIC_3] = THREE;
168   translist[KEY_NUMERIC_2] = TWO;
169   translist[KEY_NUMERIC_1] = ONE;
170   translist[KEY_NUMERIC_0] = ZERO;
171   translist[KEY_NUMERIC_STAR] = STAR;
172   translist[KEY_NUMERIC_POUND] = HASH;
173
174
175   translist[KEY_J] = GO; //j for JUMP TO instead of go to
176   translist[KEY_R] = RED;
177   translist[KEY_G] = GREEN;
178   translist[KEY_Y] = YELLOW;
179   translist[KEY_B] = BLUE;
180   //Processing Remote Style Messages
181   translist[KEY_GREEN] = GREEN;
182   translist[KEY_RED] = RED;
183   translist[KEY_YELLOW] = YELLOW;
184   translist[KEY_BLUE] = BLUE;
185   translist[KEY_MENU] = MENU;
186
187   translist[KEY_RECORD] = RECORD;
188   translist[KEY_PLAY] = PLAY; //Playback Televison
189   translist[KEY_PAUSE] = PAUSE;
190   translist[KEY_STOP] = STOP;
191   translist[KEY_PLAYPAUSE] = PLAYPAUSE;
192   translist[KEY_P] = PLAYPAUSE;
193   translist[KEY_NEXT] = SKIPFORWARD;
194   translist[KEY_F2] = SKIPFORWARD;
195   translist[KEY_PREVIOUS] = SKIPBACK;
196   translist[KEY_F1] = SKIPBACK;
197   translist[KEY_FORWARD] = FORWARD;
198   translist[KEY_FASTFORWARD] = FORWARD;
199   translist[KEY_F] = FORWARD;
200   translist[KEY_BACK] = REVERSE;
201   translist[KEY_REWIND] = REVERSE;
202   translist[KEY_T] = REVERSE;
203   translist[KEY_MUTE] = MUTE;
204   translist[KEY_F8] = MUTE;
205   translist[KEY_F10] = VOLUMEUP;
206   translist[KEY_F9] = VOLUMEDOWN;
207   translist[KEY_VOLUMEUP] = VOLUMEUP;
208   translist[KEY_VOLUMEDOWN] = VOLUMEDOWN;
209   translist[KEY_CHANNELUP] = CHANNELUP;
210   translist[KEY_CHANNELDOWN] = CHANNELDOWN;
211   translist[KEY_PAGEUP] = CHANNELUP;
212   translist[KEY_PAGEDOWN] = CHANNELDOWN;
213 }
214
215 #define NAMETRICK(pre, code) linux_keymap[pre ## code]=  #code
216 //extracte from linux/input.h
217
218 static const char * linux_keymap[KEY_MAX+1];
219
220 void InputLinux::InitKeymap()
221 {
222   for (int i=0;i<KEY_MAX+1;i++)
223   {
224     linux_keymap[i] = NULL;
225   }
226
227   NAMETRICK(KEY_,RESERVED);
228   NAMETRICK(KEY_,ESC);
229   NAMETRICK(KEY_,1);
230   NAMETRICK(KEY_,2);
231   NAMETRICK(KEY_,3);
232   NAMETRICK(KEY_,4);
233   NAMETRICK(KEY_,5);
234   NAMETRICK(KEY_,6);
235   NAMETRICK(KEY_,7);
236   NAMETRICK(KEY_,8);
237   NAMETRICK(KEY_,9);
238   NAMETRICK(KEY_,0);
239   NAMETRICK(KEY_,MINUS);
240   NAMETRICK(KEY_,EQUAL);
241   NAMETRICK(KEY_,BACKSPACE);
242   NAMETRICK(KEY_,TAB);
243   NAMETRICK(KEY_,Q);
244   NAMETRICK(KEY_,W);
245   NAMETRICK(KEY_,E);
246   NAMETRICK(KEY_,R);
247   NAMETRICK(KEY_,T);
248   NAMETRICK(KEY_,Y);
249   NAMETRICK(KEY_,U);
250   NAMETRICK(KEY_,I);
251   NAMETRICK(KEY_,O);
252   NAMETRICK(KEY_,P);
253   NAMETRICK(KEY_,LEFTBRACE);
254   NAMETRICK(KEY_,RIGHTBRACE);
255   NAMETRICK(KEY_,ENTER);
256   NAMETRICK(KEY_,LEFTCTRL);
257   NAMETRICK(KEY_,A);
258   NAMETRICK(KEY_,S);
259   NAMETRICK(KEY_,D);
260   NAMETRICK(KEY_,F);
261   NAMETRICK(KEY_,G);
262   NAMETRICK(KEY_,H);
263   NAMETRICK(KEY_,J);
264   NAMETRICK(KEY_,K);
265   NAMETRICK(KEY_,L);
266   NAMETRICK(KEY_,SEMICOLON);
267   NAMETRICK(KEY_,APOSTROPHE);
268   NAMETRICK(KEY_,GRAVE);
269   NAMETRICK(KEY_,LEFTSHIFT);
270   NAMETRICK(KEY_,BACKSLASH);
271   NAMETRICK(KEY_,Z);
272   NAMETRICK(KEY_,X);
273   NAMETRICK(KEY_,C);
274   NAMETRICK(KEY_,V);
275   NAMETRICK(KEY_,B);
276   NAMETRICK(KEY_,N);
277   NAMETRICK(KEY_,M);
278   NAMETRICK(KEY_,COMMA);
279   NAMETRICK(KEY_,DOT);
280   NAMETRICK(KEY_,SLASH);
281   NAMETRICK(KEY_,RIGHTSHIFT);
282   NAMETRICK(KEY_,KPASTERISK);
283   NAMETRICK(KEY_,LEFTALT);
284   NAMETRICK(KEY_,SPACE);
285   NAMETRICK(KEY_,CAPSLOCK);
286   NAMETRICK(KEY_,F1);
287   NAMETRICK(KEY_,F2);
288   NAMETRICK(KEY_,F3);
289   NAMETRICK(KEY_,F4);
290   NAMETRICK(KEY_,F5);
291   NAMETRICK(KEY_,F6);
292   NAMETRICK(KEY_,F7);
293   NAMETRICK(KEY_,F8);
294   NAMETRICK(KEY_,F9);
295   NAMETRICK(KEY_,F10);
296   NAMETRICK(KEY_,NUMLOCK);
297   NAMETRICK(KEY_,SCROLLLOCK);
298   NAMETRICK(KEY_,KP7);
299   NAMETRICK(KEY_,KP8);
300   NAMETRICK(KEY_,KP9);
301   NAMETRICK(KEY_,KPMINUS);
302   NAMETRICK(KEY_,KP4);
303   NAMETRICK(KEY_,KP5);
304   NAMETRICK(KEY_,KP6);
305   NAMETRICK(KEY_,KPPLUS);
306   NAMETRICK(KEY_,KP1);
307   NAMETRICK(KEY_,KP2);
308   NAMETRICK(KEY_,KP3);
309   NAMETRICK(KEY_,KP0);
310   NAMETRICK(KEY_,KPDOT);
311   NAMETRICK(KEY_,F11);
312   NAMETRICK(KEY_,F12);
313   NAMETRICK(KEY_,KPENTER);
314   NAMETRICK(KEY_,RIGHTCTRL);
315   NAMETRICK(KEY_,KPSLASH);
316   NAMETRICK(KEY_,SYSRQ);
317   NAMETRICK(KEY_,RIGHTALT);
318   NAMETRICK(KEY_,LINEFEED);
319   NAMETRICK(KEY_,HOME);
320   NAMETRICK(KEY_,UP);
321   NAMETRICK(KEY_,PAGEUP);
322   NAMETRICK(KEY_,LEFT);
323   NAMETRICK(KEY_,RIGHT);
324   NAMETRICK(KEY_,END);
325   NAMETRICK(KEY_,DOWN);
326   NAMETRICK(KEY_,PAGEDOWN);
327   NAMETRICK(KEY_,INSERT);
328   NAMETRICK(KEY_,DELETE);
329   NAMETRICK(KEY_,MACRO);
330   NAMETRICK(KEY_,MUTE);
331   NAMETRICK(KEY_,VOLUMEDOWN);
332   NAMETRICK(KEY_,VOLUMEUP);
333   NAMETRICK(KEY_,POWER);
334   NAMETRICK(KEY_,KPEQUAL);
335   NAMETRICK(KEY_,KPPLUSMINUS);
336   NAMETRICK(KEY_,PLAY);
337   NAMETRICK(KEY_,PAUSE);
338   NAMETRICK(KEY_,SCALE);
339   NAMETRICK(KEY_,KPCOMMA);
340   NAMETRICK(KEY_,YEN);
341   NAMETRICK(KEY_,LEFTMETA);
342   NAMETRICK(KEY_,RIGHTMETA);
343   NAMETRICK(KEY_,COMPOSE);
344   NAMETRICK(KEY_,STOP);
345   NAMETRICK(KEY_,AGAIN);
346   NAMETRICK(KEY_,PROPS);
347   NAMETRICK(KEY_,UNDO);
348   NAMETRICK(KEY_,FRONT);
349   NAMETRICK(KEY_,COPY);
350   NAMETRICK(KEY_,OPEN);
351   NAMETRICK(KEY_,PASTE);
352   NAMETRICK(KEY_,FIND);
353   NAMETRICK(KEY_,CUT);
354   NAMETRICK(KEY_,HELP);
355   NAMETRICK(KEY_,MENU);
356   NAMETRICK(KEY_,CALC);
357   NAMETRICK(KEY_,SETUP);
358   NAMETRICK(KEY_,SLEEP);
359   NAMETRICK(KEY_,WAKEUP);
360   NAMETRICK(KEY_,FILE);
361   NAMETRICK(KEY_,SENDFILE);
362   NAMETRICK(KEY_,DELETEFILE);
363   NAMETRICK(KEY_,XFER);
364   NAMETRICK(KEY_,PROG1);
365   NAMETRICK(KEY_,PROG2);
366   NAMETRICK(KEY_,WWW);
367   NAMETRICK(KEY_,MSDOS);
368   NAMETRICK(KEY_,COFFEE);
369   NAMETRICK(KEY_,DIRECTION);
370   NAMETRICK(KEY_,CYCLEWINDOWS);
371   NAMETRICK(KEY_,MAIL);
372   NAMETRICK(KEY_,BOOKMARKS);
373   NAMETRICK(KEY_,COMPUTER);
374   NAMETRICK(KEY_,BACK);
375   NAMETRICK(KEY_,FORWARD);
376   NAMETRICK(KEY_,FASTFORWARD);
377   NAMETRICK(KEY_,CLOSECD);
378   NAMETRICK(KEY_,EJECTCD);
379   NAMETRICK(KEY_,EJECTCLOSECD);
380   NAMETRICK(KEY_,NEXTSONG);
381   NAMETRICK(KEY_,PLAYPAUSE);
382   NAMETRICK(KEY_,PREVIOUSSONG);
383   NAMETRICK(KEY_,STOPCD);
384   NAMETRICK(KEY_,RECORD);
385   NAMETRICK(KEY_,REWIND);
386   NAMETRICK(KEY_,PHONE);
387   NAMETRICK(KEY_,ISO);
388   NAMETRICK(KEY_,CONFIG);
389   NAMETRICK(KEY_,HOMEPAGE);
390   NAMETRICK(KEY_,REFRESH);
391   NAMETRICK(KEY_,EXIT);
392   NAMETRICK(KEY_,MOVE);
393   NAMETRICK(KEY_,EDIT);
394   NAMETRICK(KEY_,SCROLLUP);
395   NAMETRICK(KEY_,SCROLLDOWN);
396   NAMETRICK(KEY_,KPLEFTPAREN);
397   NAMETRICK(KEY_,KPRIGHTPAREN);
398   NAMETRICK(KEY_,NEW);
399   NAMETRICK(KEY_,REDO);
400   NAMETRICK(KEY_,OK);
401   NAMETRICK(KEY_,SELECT);
402   NAMETRICK(KEY_,GOTO);
403   NAMETRICK(KEY_,CLEAR);
404   NAMETRICK(KEY_,POWER2);
405   NAMETRICK(KEY_,OPTION);
406   NAMETRICK(KEY_,INFO);
407   NAMETRICK(KEY_,TIME);
408   NAMETRICK(KEY_,VENDOR);
409   NAMETRICK(KEY_,ARCHIVE);
410   NAMETRICK(KEY_,PROGRAM);
411   NAMETRICK(KEY_,CHANNEL);
412   NAMETRICK(KEY_,FAVORITES);
413   NAMETRICK(KEY_,EPG);
414   NAMETRICK(KEY_,PVR);
415   NAMETRICK(KEY_,MHP);
416   NAMETRICK(KEY_,LANGUAGE);
417   NAMETRICK(KEY_,TITLE);
418   NAMETRICK(KEY_,SUBTITLE);
419   NAMETRICK(KEY_,ANGLE);
420   NAMETRICK(KEY_,ZOOM);
421   NAMETRICK(KEY_,MODE);
422   NAMETRICK(KEY_,KEYBOARD);
423   NAMETRICK(KEY_,SCREEN);
424   NAMETRICK(KEY_,RED);
425   NAMETRICK(KEY_,GREEN);
426   NAMETRICK(KEY_,YELLOW);
427   NAMETRICK(KEY_,BLUE);
428   NAMETRICK(KEY_,CHANNELUP);
429   NAMETRICK(KEY_,CHANNELDOWN);
430   NAMETRICK(KEY_,FIRST);
431   NAMETRICK(KEY_,LAST);
432   NAMETRICK(KEY_,AB);
433   NAMETRICK(KEY_,NEXT);
434   NAMETRICK(KEY_,RESTART);
435   NAMETRICK(KEY_,SLOW);
436   NAMETRICK(KEY_,SHUFFLE);
437   NAMETRICK(KEY_,BREAK);
438   NAMETRICK(KEY_,PREVIOUS);
439   NAMETRICK(KEY_,DIGITS);
440   NAMETRICK(KEY_,TEEN);
441   NAMETRICK(KEY_,TWEN);
442   NAMETRICK(KEY_,VIDEOPHONE);
443   NAMETRICK(KEY_,GAMES);
444   NAMETRICK(KEY_,ZOOMIN);
445   NAMETRICK(KEY_,ZOOMOUT);
446   NAMETRICK(KEY_,ZOOMRESET);
447   NAMETRICK(KEY_,DOLLAR);
448   NAMETRICK(KEY_,EURO);
449   NAMETRICK(KEY_,MEDIA);
450   NAMETRICK(KEY_,FRAMEBACK);
451   NAMETRICK(KEY_,FRAMEFORWARD);
452   NAMETRICK(KEY_,CONTEXT_MENU);
453   NAMETRICK(KEY_,MEDIA_REPEAT);
454   NAMETRICK(KEY_,NUMERIC_0);
455   NAMETRICK(KEY_,NUMERIC_1);
456   NAMETRICK(KEY_,NUMERIC_2);
457   NAMETRICK(KEY_,NUMERIC_3);
458   NAMETRICK(KEY_,NUMERIC_4);
459   NAMETRICK(KEY_,NUMERIC_5);
460   NAMETRICK(KEY_,NUMERIC_6);
461   NAMETRICK(KEY_,NUMERIC_7);
462   NAMETRICK(KEY_,NUMERIC_8);
463   NAMETRICK(KEY_,NUMERIC_9);
464   NAMETRICK(KEY_,NUMERIC_STAR);
465   NAMETRICK(KEY_,NUMERIC_POUND);
466 }
467
468 const char* InputLinux::getHardCodedHardwareKeyNamesForVompKey(UCHAR vompKey)
469 {
470   switch (vompKey)
471   {
472   case DOWN:
473     return tr("Down");
474   case UP:
475     return tr("Up");
476   case LEFT:
477     return tr("Left");
478   case RIGHT:
479     return tr("Right");
480   case MENU:
481     return tr("M");
482   case BACK:
483     return tr("Backspace, Back");
484   case OK:
485     return tr("Return, Space");
486   default:
487     return "";
488   }
489 }
490
491 std::string InputLinux::getHardwareKeyName(int hardwareKey)
492 {
493   const char* desc = linux_keymap[hardwareKey];
494
495   std::string retval;
496
497   if (desc)
498   {
499     retval = desc;
500   }
501   else
502   {
503     char* rt = new char[10];
504     sprintf(rt, "0x%x", hardwareKey);
505     retval = rt;
506   }
507
508   return retval;
509 }
510
511 void InputLinux::EnterLearningMode(UCHAR vompKey)
512 {
513   learnMode = vompKey; // Armed
514 }
515
516 bool InputLinux::start()
517 {
518   LogNT::getInstance()->info(TAG, "start called");
519
520   threadStartProtect.lock(); // Make sure listenThread is fully initted before start returns
521   listenThread = std::thread( [this]
522   {
523     threadStartProtect.lock();
524     threadStartProtect.unlock();
525     listenLoop();
526   });
527   threadStartProtect.unlock();
528   return true;
529 }
530
531 void InputLinux::stop()
532 {
533   threadStartProtect.lock(); // Also use it to protect against starting while stopping
534
535   if (listenThread.joinable())
536   {
537     listenLoopStop = true;
538     write(pfds[1], "1", 1); // break the select in listenLoop
539     listenThread.join();
540   }
541
542   threadStartProtect.unlock();
543 }
544
545 void InputLinux::listenLoop()
546 {
547   fd_set readfds;
548   int maxfd;
549
550   if (pipe2(pfds, O_NONBLOCK) == -1)
551   {
552     LogNT::getInstance()->error(TAG, "pipe2() fail");
553     return;
554   }
555
556   LogNT::getInstance()->info(TAG, "Listen loop");
557
558   while(1)
559   {
560     if (listenLoopStop) break;
561
562     FD_ZERO(&readfds);
563
564     maxfd = 0;
565     for (unsigned int i = 0; i < devices.size(); i++)
566     {
567       int cur_fd = devices[i];
568       maxfd = max(cur_fd, maxfd);
569       FD_SET(cur_fd, &readfds);
570     }
571
572     FD_SET(pfds[0], &readfds);
573     maxfd = max(pfds[0], maxfd);
574
575     // 0 = nothing happened and timeout expired
576     // >0 = num of descriptors that changed
577     // -1 = error
578     if (select(maxfd + 1, &readfds, NULL, NULL, NULL) < 1)
579     {
580       LogNT::getInstance()->error(TAG, "Select fail");
581       break;
582     }
583
584     if (FD_ISSET(pfds[0], &readfds))
585     {
586       // assume quit signal
587       LogNT::getInstance()->info(TAG, "pfds quit");
588       break;
589
590       // FUTURE: read the byte and do different things? Read listenLoopStop and maybe other bools?
591     }
592
593     for (unsigned int i = 0; i < devices.size(); i++)
594     {
595       int cur_fd = devices[i];
596       if (FD_ISSET(cur_fd, &readfds))
597       {
598         struct input_event ev;
599         int count = read(cur_fd, &ev, sizeof(ev));
600         if (count == sizeof(ev))
601         {
602           if (ev.type == EV_KEY && ev.value == 1)
603           {
604             sendInputKey(TranslateHWC(ev.code));
605           }
606         }
607       }
608     }
609   }
610
611   close(pfds[1]);
612   close(pfds[0]);
613 }
614
615 // FIXME surely NA_SIGNAL can go