d32456f709a07a513d1afbff19cc231ab9513e14
[goodguy/cinelerra.git] / cinelerra-5.1 / cinelerra / shuttle.C
1 #ifdef HAVE_SHUTTLE
2 // Copyright 2013 Eric Messick (FixedImagePhoto.com/Contact)
3 // reworked 2019 for cinelerra-gg by William Morrow
4
5 #include "arraylist.h"
6 #include "cstrdup.h"
7 #include "file.h"
8 #include "guicast.h"
9 #include "linklist.h"
10 #include "loadfile.h"
11 #include "mainmenu.h"
12 #include "shuttle.h"
13 #include "thread.h"
14
15 #include "mwindow.h"
16 #include "mwindowgui.h"
17 #include "awindow.h"
18 #include "awindowgui.h"
19 #include "cwindow.h"
20 #include "cwindowgui.h"
21 #include "vwindow.h"
22 #include "vwindowgui.h"
23
24 #include <stdio.h>
25 #include <stdint.h>
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <fcntl.h>
29 #include <string.h>
30 #include <sys/time.h>
31 #include <sys/stat.h>
32
33 #include <X11/Xlib.h>
34 #include <X11/keysym.h>
35
36 static Time milliTimeClock()
37 {
38         struct timeval tv;
39         gettimeofday(&tv, 0);
40         return (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
41 }
42
43 KeySymMapping KeySymMapping::key_sym_mapping[] = {
44         { "XK_Button_1", XK_Button_1 },
45         { "XK_Button_2", XK_Button_2 },
46         { "XK_Button_3", XK_Button_3 },
47         { "XK_Scroll_Up", XK_Scroll_Up },
48         { "XK_Scroll_Down", XK_Scroll_Down },
49 #include "shuttle_keys.h"
50         { NULL, 0 }
51 };
52
53 KeySym KeySymMapping::to_keysym(const char *str)
54 {
55         for( KeySymMapping *ksp = &key_sym_mapping[0]; ksp->str; ++ksp )
56                 if( !strcmp(str, ksp->str) ) return ksp->sym;
57         return 0;
58 }
59
60 const char *KeySymMapping::to_string(KeySym ks)
61 {
62         for( KeySymMapping *ksp = &key_sym_mapping[0]; ksp->sym; ++ksp )
63                 if( ksp->sym == ks ) return ksp->str;
64         return 0;
65 }
66
67 TransName::TransName(int cin, const char *nm, const char *re)
68 {
69         this->cin = cin;
70         this->name = cstrdup(nm);
71         this->err = regcomp(&this->regex, re, REG_NOSUB);
72         if( err ) {
73                 fprintf(stderr, "error compiling regex for [%s]: %s\n", name, re);
74                 char emsg[BCTEXTLEN];
75                 regerror(err, &regex, emsg, sizeof(emsg));
76                 fprintf(stderr, "regerror: %s\n", emsg);
77         }
78 }
79 TransName::~TransName()
80 {
81         delete [] name;
82         regfree(&regex);
83 }
84
85 void Translation::init(int def)
86 {
87         is_default = def;
88         is_key = 0;
89         first_release_stroke = 0;
90         pressed = 0;
91         released = 0;
92         keysym_down = 0;
93 }
94
95 Translation::Translation(Shuttle *shuttle)
96  : modifiers(this)
97 { // initial default translation
98         init(1);
99         this->shuttle = shuttle;
100         this->name = cstrdup("Default");
101         names.append(new TransName(FOCUS_DEFAULT, name, ""));
102         key_down[K6].add_stroke(XK_Button_1, 1);
103         key_up[K6].add_stroke(XK_Button_1, 0);
104         key_down[K7].add_stroke(XK_Button_2, 1);
105         key_up[K7].add_stroke(XK_Button_2, 0);
106         key_down[K8].add_stroke(XK_Button_3, 1);
107         key_up[K8].add_stroke(XK_Button_3, 0);
108         jog[JL].add_stroke(XK_Scroll_Up, 1);
109         jog[JL].add_stroke(XK_Scroll_Up, 0);
110         jog[JR].add_stroke(XK_Scroll_Down, 0);
111         jog[JR].add_stroke(XK_Scroll_Down, 1);
112 }
113
114 Translation::Translation(Shuttle *shuttle, const char *name)
115  : modifiers(this)
116 {
117         init(0);
118         this->shuttle = shuttle;
119         this->name = cstrdup(name);
120 }
121
122 Translation::~Translation()
123 {
124         delete [] name;
125 }
126
127 void Translation::clear()
128 {
129         names.remove_all_objects();
130         init(0);
131         for( int i=0; i<NUM_KEYS; ++i ) key_down[i].clear();
132         for( int i=0; i<NUM_KEYS; ++i ) key_up[i].clear();
133         for( int i=0; i<NUM_SHUTTLES; ++i ) shuttles[i].clear();
134         for( int i=0; i<NUM_JOGS; ++i ) jog[i].clear();
135 }
136
137 void Translation::append_stroke(KeySym sym, int press)
138 {
139         Stroke *s = pressed_strokes->append();
140         s->keysym = sym;
141         s->press = press;
142 }
143
144 void Translation::add_keysym(KeySym sym, int press_release)
145 {
146 //printf("add_keysym(0x%x, %d)\n", (int)sym, press_release);
147         switch( press_release ) {
148         case PRESS:
149                 append_stroke(sym, 1);
150                 modifiers.mark_as_down(sym, 0);
151                 break;
152         case RELEASE:
153                 append_stroke(sym, 0);
154                 modifiers.mark_as_up(sym);
155                 break;
156         case HOLD:
157                 append_stroke(sym, 1);
158                 modifiers.mark_as_down(sym, 1);
159                 break;
160         case PRESS_RELEASE:
161         default:
162                 if( first_release_stroke ) {
163                         modifiers.re_press();
164                         first_release_stroke = 0;
165                 }
166                 if( keysym_down ) {
167                         append_stroke(keysym_down, 0);
168                 }
169                 append_stroke(sym, 1);
170                 keysym_down = sym;
171                 break;
172         }
173 }
174
175 void Translation::add_release(int all_keys)
176 {
177 //printf("add_release(%d)\n", all_keys);
178         modifiers.release(all_keys);
179         if( !all_keys ) {
180                 pressed_strokes = released_strokes;
181         }
182         if( keysym_down ) {
183                 append_stroke(keysym_down, 0);
184                 keysym_down = 0;
185         }
186         first_release_stroke = 1;
187 }
188
189 void Translation::add_keystroke(const char *keySymName, int press_release)
190 {
191         KeySym sym;
192
193         if( is_key && !strncmp(keySymName, "RELEASE", 8) ) {
194                 add_release(0);
195                 return;
196         }
197         sym = KeySymMapping::to_keysym(keySymName);
198         if( sym != 0 ) {
199                 add_keysym(sym, press_release);
200         }
201         else
202                 fprintf(stderr, "unrecognized KeySym: %s\n", keySymName);
203 }
204
205 void Translation::add_string(const char *str)
206 {
207         while( str && *str ) {
208                 if( *str >= ' ' && *str <= '~' )
209                         add_keysym((KeySym)(*str), PRESS_RELEASE);
210                 ++str;
211         }
212 }
213
214 int Translation::start_line(const char *key)
215 {
216         pressed_strokes = 0;
217         released_strokes = 0;
218         pressed = released = 0;
219         is_key = 0;
220         if( !strcasecmp("JL", key) ) {
221                 pressed = &jog[0];
222         }
223         else if( !strcasecmp("JR", key) ) {
224                 pressed = &jog[1];
225         }
226         else {
227                 char c = 0;  int k = -1, n = 0;
228                 if( sscanf(key, "%c%d%n", &c, &k, &n) != 2 ) return 1;
229                 switch( c ) {
230                 case 'K': case 'k':
231                         --k;
232                         if( k >= K1 && k <= K15 ) {
233                                 pressed = &key_down[k];
234                                 released = &key_up[k];
235                                 is_key = 1;
236                         }
237                         break;
238                 case 'S': case 's':
239                         if( k >= S_7 && k <= S7 ) {
240                                 pressed = &shuttles[k-S_7];
241                         }
242                         break;
243                 }
244                 if( !pressed ) {
245                         fprintf(stderr, "bad key name: [%s]%s\n", name, key);
246                         return 1;
247                 }
248                 if( pressed->first ) {
249                         fprintf(stderr, "dupl key name: [%s]%s\n", name, key);
250                         return 1;
251                 }
252         }
253         pressed_strokes = pressed;
254         released_strokes = released;
255         return 0;
256 }
257
258 void Translation::print_stroke(Stroke *s)
259 {
260         if( !s ) return;
261         const char *cp = KeySymMapping::to_string(s->keysym);
262         if( !cp ) { printf("0x%x", (int)s->keysym); cp = "???"; }
263         printf("%s/%c ", cp, s->press ? 'D' : 'U');
264 }
265
266 void Translation::print_strokes(const char *name, const char *up_dn, Strokes *strokes)
267 {
268         printf("%s[%s]: ", name, up_dn);
269         for( Stroke *s=strokes->first; s; s=s->next )
270                 print_stroke(s);
271         printf("\n");
272 }
273
274 void Translation::finish_line()
275 {
276 //printf("finish_line()\n");
277         if( is_key ) {
278                 add_release(0);
279         }
280         add_release(1);
281 }
282
283 void Translation::print_line(const char *key)
284 {
285         if( is_key ) {
286                 print_strokes(key, "D", pressed_strokes);
287                 print_strokes(key, "U", released_strokes);
288         }
289         else {
290                 print_strokes(key, "", pressed_strokes);
291         }
292         printf("\n");
293 }
294
295 // press values in Modifiers:
296 // PRESS -> down
297 // HOLD -> held
298 // PRESS_RELEASE -> released, but to be re-pressed if necessary
299 // RELEASE -> up
300
301 void Modifiers::mark_as_down(KeySym sym, int hold)
302 {
303         Modifiers &modifiers = *this;
304         for( int i=0; i<size(); ++i ) {
305                 Stroke &s = modifiers[i];
306                 if( s.keysym == sym ) {
307                         s.press = hold ? HOLD : PRESS;
308                         return;
309                 }
310         }
311         Stroke &s = append();
312         s.keysym = sym;
313         s.press = hold ? HOLD : PRESS;
314 }
315
316 void Modifiers::mark_as_up(KeySym sym)
317 {
318         Modifiers &modifiers = *this;
319         for( int i=0; i<size(); ++i ) {
320                 Stroke &s = modifiers[i];
321                 if( s.keysym == sym ) {
322                         s.press = RELEASE;
323                         return;
324                 }
325         }
326 }
327
328 void Modifiers::release(int allkeys)
329 {
330         Modifiers &modifiers = *this;
331         for( int i=0; i<size(); ++i ) {
332                 Stroke &s = modifiers[i];
333                 if( s.press == PRESS ) {
334                         trans->append_stroke(s.keysym, 0);
335                         s.press = PRESS_RELEASE;
336                 }
337                 else if( allkeys && s.press == HOLD ) {
338                         trans->append_stroke(s.keysym, 0);
339                         s.press = RELEASE;
340                 }
341         }
342 }
343
344 void Modifiers::re_press()
345 {
346         Modifiers &modifiers = *this;
347         for( int i=0; i<size(); ++i ) {
348                 Stroke &s = modifiers[i];
349                 if( s.press == PRESS_RELEASE ) {
350                         trans->append_stroke(s.keysym, 1);
351                         s.press = PRESS;
352                 }
353         }
354 }
355
356
357 Shuttle::Shuttle(MWindow *mwindow)
358  : Thread(0, 0, 0)
359 {
360         this->mwindow = mwindow;
361
362         fd = -1;
363         wdw = 0;
364         win = 0;
365         msk = 0;
366         rx = ry = 0;
367         wx = wy = 0;
368         jogvalue = 0xffff;
369         shuttlevalue = 0xffff;
370         last_shuttle.tv_sec = 0;
371         last_shuttle.tv_usec = 0;
372         need_synthetic_shuttle = 0;
373         dev_name = 0;
374
375         done = -1;
376         failed = 0;
377         first_time = 1;
378         tr = 0;
379         last_translation = 0;
380         last_focused = 0;
381
382         default_translation = new Translation(this);
383         config_path = 0;
384         config_mtime = 0;
385         ev.type = ~0;
386 }
387
388 Shuttle::~Shuttle()
389 {
390         stop();
391         delete default_translation;
392         delete [] config_path;
393 }
394
395 int Shuttle::send_button(unsigned int button, int press)
396 {
397         if( debug )
398                 printf("btn: %u %d\n", button, press);
399         XButtonEvent *b = new XButtonEvent();
400         memset(b, 0, sizeof(*b));
401         b->type = press ? ButtonPress : ButtonRelease;
402         b->time = milliTimeClock();
403         b->display = wdw->top_level->display;
404         b->root = wdw->top_level->rootwin;
405         b->window = win;
406         b->x_root = rx;
407         b->y_root = ry;
408         b->x = wx;
409         b->y = wy;
410         b->state = msk;
411         b->button = button;
412         b->same_screen = 1;
413         wdw->top_level->put_event((XEvent *) b);
414         return 0;
415 }
416 int Shuttle::send_key(KeySym keysym, int press)
417 {
418         KeyCode keycode = XKeysymToKeycode(wdw->top_level->display, keysym);
419         if( debug )
420                 printf("key: %04x %d\n", (unsigned)keycode, press);
421         XKeyEvent *k = new XKeyEvent();
422         memset(k, 0, sizeof(*k));
423         k->type = press ? KeyPress : KeyRelease;
424         k->time = milliTimeClock();
425         k->display = wdw->top_level->display;
426         k->root = wdw->top_level->rootwin;
427         k->window = win;
428         k->x_root = rx;
429         k->y_root = ry;
430         k->x = wx;
431         k->y = wy;
432         k->state = msk;
433         k->keycode = keycode;
434         k->same_screen = 1;
435         wdw->top_level->put_event((XEvent *) k);
436         return 0;
437 }
438
439 int Shuttle::send_keysym(KeySym keysym, int press)
440 {
441         return keysym >= XK_Button_1 && keysym <= XK_Scroll_Down ?
442                 send_button((unsigned int)keysym - XK_Button_0, press) :
443                 send_key(keysym, press ? True : False);
444 }
445
446
447 static Stroke *fetch_stroke(Translation *translation, int kjs, int index)
448 {
449         Stroke *ret = 0;
450         if( translation ) {
451                 switch( kjs ) {
452                 default:
453                 case KJS_KEY_DOWN:  ret = translation->key_down[index].first;   break;
454                 case KJS_KEY_UP:    ret = translation->key_up[index].first;     break;
455                 case KJS_JOG:       ret = translation->jog[index].first;        break;
456                 case KJS_SHUTTLE:   ret = translation->shuttles[index].first;   break;
457                 }
458         }
459         return ret;
460 }
461
462 void Shuttle::send_stroke_sequence(int kjs, int index)
463 {
464         if( !wdw ) return;
465         Stroke *s = fetch_stroke(tr, kjs, index);
466         if( !s ) s = fetch_stroke(default_translation, kjs, index);
467         while( s ) {
468                 send_keysym(s->keysym, s->press);
469                 s = s->next;
470         }
471 }
472
473 void Shuttle::key(unsigned short code, unsigned int value)
474 {
475         code -= EVENT_CODE_KEY1;
476         if( code >= NUM_KEYS ) {
477                 fprintf(stderr, "key(%d, %d) out of range\n", code + EVENT_CODE_KEY1, value);
478                 return;
479         }
480         send_stroke_sequence(value ? KJS_KEY_DOWN : KJS_KEY_UP, code);
481 }
482
483
484 void Shuttle:: shuttle(int value)
485 {
486         if( value < S_7 || value > S7 ) {
487                 fprintf(stderr, "shuttle(%d) out of range\n", value);
488                 return;
489         }
490         gettimeofday(&last_shuttle, 0);
491         need_synthetic_shuttle = value != 0;
492         if( value != shuttlevalue ) {
493                 shuttlevalue = value;
494                 send_stroke_sequence(KJS_SHUTTLE, value+7);
495         }
496 }
497
498 // Due to a bug (?) in the way Linux HID handles the ShuttlePro, the
499 // center position is not reported for the shuttle wheel.       Instead,
500 // a jog event is generated immediately when it returns.        We check to
501 // see if the time since the last shuttle was more than a few ms ago
502 // and generate a shuttle of 0 if so.
503 //
504 // Note, this fails if jogvalue happens to be 0, as we don't see that
505 // event either!
506 void Shuttle::jog(unsigned int value)
507 {
508         int direction;
509         struct timeval now;
510         struct timeval delta;
511
512         // We should generate a synthetic event for the shuttle going
513         // to the home position if we have not seen one recently
514         if( need_synthetic_shuttle ) {
515                 gettimeofday( &now, 0 );
516                 timersub( &now, &last_shuttle, &delta );
517
518                 if( delta.tv_sec >= 1 || delta.tv_usec >= 5000 ) {
519                         shuttle(0);
520                         need_synthetic_shuttle = 0;
521                 }
522         }
523
524         if( jogvalue != 0xffff ) {
525                 value = value & 0xff;
526                 direction = ((value - jogvalue) & 0x80) ? -1 : 1;
527                 while( jogvalue != value ) {
528                         // driver fails to send an event when jogvalue == 0
529                         if( jogvalue != 0 ) {
530         send_stroke_sequence(KJS_JOG, direction > 0 ? 1 : 0);
531                         }
532                         jogvalue = (jogvalue + direction) & 0xff;
533                 }
534         }
535         jogvalue = value;
536 }
537
538 void Shuttle::jogshuttle(unsigned short code, unsigned int value)
539 {
540         switch( code ) {
541         case EVENT_CODE_JOG:
542                 jog(value);
543                 break;
544         case EVENT_CODE_SHUTTLE:
545                 shuttle(value);
546                 break;
547         default:
548                 fprintf(stderr, "jogshuttle(%d, %d) invalid code\n", code, value);
549                 break;
550         }
551 }
552
553 const char *Shuttle::probe()
554 {
555         struct stat st;
556         static const char *shuttle_devs[] = {
557                 "/dev/input/by-id/usb-Contour_Design_ShuttleXpress-event-if00",
558                 "/dev/input/by-id/usb-Contour_Design_ShuttlePRO_v2-event-if00",
559                 "/dev/input/by-id/usb-Contour_Design_ShuttlePro-event-if00",
560         };
561         int ret = sizeof(shuttle_devs) / sizeof(shuttle_devs[0]);
562         while( --ret >= 0 && stat(shuttle_devs[ret] , &st) );
563         return ret >= 0 ? shuttle_devs[ret] : 0;
564 }
565
566 void Shuttle::start(const char *dev_name)
567 {
568         this->dev_name = dev_name;
569         first_time = 1;
570         done = 0;
571         Thread::start();
572 }
573
574 void Shuttle::stop()
575 {
576         if( running() && !done ) {
577                 done = 1;
578                 cancel();
579                 join();
580         }
581 }
582
583 BC_WindowBase *Shuttle::owns(BC_WindowBase *wdw, Window win)
584 {
585         if( wdw->win == win ) return wdw;
586         if( (wdw=wdw->top_level)->win == win ) return wdw;
587         for( int i=wdw->popups.size(); --i>=0; )
588                 if( wdw->popups[i]->win == win ) return wdw;
589         return 0;
590 }
591
592 int Shuttle::get_focused_window_translation()
593 {
594         MWindowGUI *gui = mwindow->gui;
595         Display *dpy = gui->display;
596         Window focus = 0;
597         int ret = 0, revert = 0;
598         char win_title[BCTEXTLEN];
599         gui->lock_window("Shuttle::get_focused_window_translation");
600         XGetInputFocus(dpy, &focus, &revert);
601         if( last_focused != focus ) {
602                 last_focused = focus;
603                 Atom prop = XInternAtom(dpy, "WM_NAME", False);
604                 Atom type;  int form;
605                 unsigned long remain, len;
606                 unsigned char *list;
607                 if( XGetWindowProperty(dpy, focus, prop, 0, sizeof(win_title)-1, False,
608                         AnyPropertyType, &type, &form, &len, &remain, &list) == Success ) {
609                         len = len*(form/8) - remain;
610                         memcpy(win_title, list, len);
611                         win_title[len] = 0;
612                         XFree(list);
613                         if( debug )
614                                 printf("new focus: %08x\n", (unsigned)focus);
615                 }
616                 else {
617                         last_focused = 0;
618                         fprintf(stderr, "XGetWindowProperty failed for window 0x%x\n",
619                                         (int)focus);
620                         ret = 1;
621                 }
622         }
623         gui->unlock_window();
624         if( ret ) return -1;
625
626         this->wdw = 0;  this->win = 0;
627         this->wx = 0;   this->wy = 0;
628         this->rx = 0;   this->ry = 0;
629         this->msk = 0;
630         BC_WindowBase *wdw = 0;
631         int cin = -1;
632         if( (wdw=owns(mwindow->gui, focus)) != 0 )
633                 cin = FOCUS_MWINDOW;
634         else if( (wdw=owns(mwindow->awindow->gui, focus)) != 0 )
635                 cin = FOCUS_AWINDOW;
636         else if( (wdw=owns(mwindow->cwindow->gui, focus)) != 0 ) {
637                 if( mwindow->cwindow->gui->canvas->get_fullscreen() )
638                         wdw = mwindow->cwindow->gui->canvas->get_canvas();
639                 cin = FOCUS_CWINDOW;
640         }
641         else if( (wdw=mwindow->gui->mainmenu->load_file->thread->window) != 0 &&
642                  wdw->win == focus )
643                 cin = FOCUS_LOAD;
644         else {
645                 int i = mwindow->vwindows.size();
646                 while( --i >= 0 ) {
647                         VWindow *vwdw =  mwindow->vwindows[i];
648                         if( !vwdw->is_running() ) continue;
649                         if( (wdw=owns(vwdw->gui, focus)) != 0 ) {
650                                 if( vwdw->gui->canvas->get_fullscreen() )
651                                         wdw = vwdw->gui->canvas->get_canvas();
652                                 cin = FOCUS_VIEWER;  break;
653                         }
654                 }
655         }
656         if( cin < 0 ) return -1;
657         Window root = 0, child = 0;
658         int root_x = 0, root_y = 0, win_x = 0, win_y = 0, x = 0, y = 0;
659         unsigned int mask = 0, width = 0, height = 0, border_width = 0, depth = 0;
660         wdw->lock_window("Shuttle::get_focused_window_translation 1");
661         if( XQueryPointer(wdw->top_level->display, focus, &root, &child,
662                         &root_x, &root_y, &win_x, &win_y, &mask) ) {
663                 if( !child ) {
664                         if( wdw->active_menubar )
665                                 child = wdw->active_menubar->win;
666                         else if( wdw->active_popup_menu )
667                                 child = wdw->active_popup_menu->win;
668                         else if( wdw->active_subwindow )
669                                 child = wdw->active_subwindow->win;
670                         else
671                                 child = wdw->win;
672                 }
673                 else
674                         XGetGeometry(wdw->top_level->display, child, &root, &x, &y,
675                                 &width, &height, &border_width, &depth);
676         }
677         wdw->unlock_window();
678         if( !child || !wdw->match_window(child) ) return -1;
679 // success
680         this->wdw = wdw;
681         this->win = child;
682         this->msk = mask;
683         this->rx = root_x;
684         this->ry = root_y;
685         this->wx = win_x - x;
686         this->wy = win_y - y;
687         for( tr=translations.first; tr; tr=tr->next ) {
688                 if( tr->is_default ) return 1;
689                 for( int i=0; i<tr->names.size(); ++i ) {
690                         TransName *name = tr->names[i];
691                         if( name->cin != cin ) continue;
692                         if( regexec(&name->regex, win_title, 0, NULL, 0) )
693                                 return 1;
694                 }
695         }
696         tr = default_translation;
697         return 0;
698 }
699
700 void Shuttle::handle_event()
701 {
702         if( read_config_file() > 0 ) {
703                 done = 1;
704                 return;
705         }
706         if( get_focused_window_translation() < 0 )
707                 return;
708         if( last_translation != tr ) {
709                 last_translation = tr;
710                 if( debug )
711                         printf("new translation: %s\n", tr->name);
712         }
713 //fprintf(stderr, "event: (%d, %d, 0x%x)\n", ev.type, ev.code, ev.value);
714         switch( ev.type ) {
715         case EVENT_TYPE_DONE:
716         case EVENT_TYPE_ACTIVE_KEY:
717                 break;
718         case EVENT_TYPE_KEY:
719                 key(ev.code, ev.value);
720                 break;
721         case EVENT_TYPE_JOGSHUTTLE:
722                 jogshuttle(ev.code, ev.value);
723                 break;
724         default:
725                 fprintf(stderr, "handle_event() invalid type code\n");
726                 break;
727         }
728 }
729
730 void Shuttle::run()
731 {
732         for( enable_cancel(); !done; sleep(1) ) {
733                 fd = open(dev_name, O_RDONLY);
734                 if( fd < 0 ) {
735                         perror(dev_name);
736                         if( first_time ) break;
737                         continue;
738                 }
739                 if( !ioctl(fd, EVIOCGRAB, 1) ) { // exclusive access
740                         first_time = 0;
741                         while( !done ) {
742                                 int ret = read(fd, &ev, sizeof(ev));
743                                 if( done ) break;
744                                 if( ret != sizeof(ev) ) {
745                                         if( ret < 0 ) { perror("read event"); break; }
746                                         fprintf(stderr, "bad read: %d\n", ret);
747                                         break;
748                                 }
749                                 handle_event();
750                         }
751                 }
752                 else
753                         perror( "evgrab ioctl" );
754                 close(fd);
755         }
756         done = 2;
757 }
758
759 int Shuttle::read_config_file()
760 {
761         if( !config_path ) {
762                 const char *env;
763                 config_path = (env=getenv("SHUTTLE_CONFIG_FILE")) != 0 ? cstrdup(env) :
764                         (env=getenv("HOME")) != 0 ? cstrcat(2, env, "/.shuttlerc") : 0;
765                 if( !config_path ) { fprintf(stderr, "no config file\n");  return 1; }
766         }
767         struct stat st;
768         if( stat(config_path, &st) ) {
769                 perror(config_path);
770                 char shuttlerc[BCTEXTLEN];
771                 snprintf(shuttlerc, sizeof(shuttlerc), "%s/shuttlerc",
772                                 File::get_cindat_path());
773                 if( stat(shuttlerc, &st) ) {
774                         perror(shuttlerc);
775                         return 1;
776                 }
777                 delete [] config_path;
778                 config_path = cstrdup(shuttlerc);
779         }
780         if( config_mtime > 0 &&
781             config_mtime == st.st_mtime ) return 0;
782         FILE *fp = fopen(config_path, "r");
783         if( !fp ) {
784                 perror(config_path);
785                 return 1;
786         }
787
788         config_mtime = st.st_mtime;
789         translations.clear();
790         debug = 0;
791 #define ws(ch) (ch==' ' || ch=='\t')
792         char line[BCTEXTLEN], *cp;
793         Translation *trans = 0;
794         int no = 0;
795         int ret = fgets(cp=line,sizeof(line),fp) ? 0 : -1;
796         if( !ret ) ++no;
797 // lines
798         while( !ret ) {
799 // translation names
800                 while( !ret && *cp == '[' ) {
801                         char *name = ++cp;
802                         while( *cp && *cp != ']' ) ++cp;
803                         *cp++ = 0;
804                         if( !name || !*name ) { ret = 1;  break; }
805                         int cin = -1;
806                         if( !strcasecmp("default", name) )
807                                 cin = FOCUS_DEFAULT;
808                         else if( !strcasecmp("cinelerra", name) )
809                                 cin = FOCUS_MWINDOW;
810                         else if( !strcasecmp("resources", name) )
811                                 cin = FOCUS_AWINDOW;
812                         else if( !strcasecmp("composer", name) )
813                                 cin = FOCUS_CWINDOW;
814                         else if( !strcasecmp("viewer", name) )
815                                 cin = FOCUS_VIEWER;
816                         else if( !strcasecmp("load", name) )
817                                 cin = FOCUS_LOAD;
818                         else {
819                                 fprintf(stderr, "unknown focus target window: %s\n",
820                                          name);
821                                 ret = 1;  break;
822                         }
823                         if( !trans ) {
824                                 if( cin == FOCUS_DEFAULT ) {
825                                         trans = default_translation;
826                                         trans->clear();
827                                 }
828                                 else {
829                                         trans = new Translation(this, name);
830                                 }
831                         }
832                         while( ws(*cp) ) ++cp;
833 // regex in TransName constructor
834                         trans->names.append(new TransName(cin, name, cp));
835                         if( trans->names.last()->err ) { ret = 1;  break; }
836                         ret = fgets(cp=line,sizeof(line),fp) ? 0 : 1;
837                         if( ret ) {
838                                 fprintf(stderr, "hit eof, no translation def for: %s\n",
839                                         trans->names.last()->name);
840                                 ret = 1;  break;
841                         }
842                         ++no;
843                 }
844                 if( ret ) break;
845                 if( debug && trans ) {
846                         printf("------------------------\n");
847                         TransNames &names = trans->names;
848                         for( int i=0; i<names.size(); ++i ) {
849                                 TransName *tp = names[i];
850                                 printf("[%s] # %d\n\n", tp->name, tp->cin);
851                         }
852                 }
853 // rules lines: "tok <stroke list>\n"
854                 while( !ret && *cp != '[' ) {
855                         const char *key = 0, *tok = 0;
856                         while( ws(*cp) ) ++cp;
857                         if( !*cp || *cp == '\n' || *cp == '#' ) goto skip;
858                         tok = cp;
859                         while( *cp && !ws(*cp) && *cp != '\n' ) ++cp;
860                         *cp++ = 0;
861                         if( !strcmp(tok, "DEBUG") ) {
862                                 debug = 1;  goto skip;
863                         }
864                         key = tok;
865                         if( !trans ) {
866                                 fprintf(stderr, "no translation section defining key: %s\n", key);
867                                 ret = 1;  break;
868                         }
869         
870                         ret = trans->start_line(key);
871                         while( !ret && *cp && *cp != '\n' ) {
872                                 while( ws(*cp) ) ++cp;
873                                 if( !*cp || *cp == '#' || *cp == '\n' ) break;
874                                 if( *cp == '"' ) {
875                                         tok = ++cp;
876                                         while( *cp && *cp != '"' && *cp != '\n' ) {
877                                                 if( *cp != '\\' ) { ++cp;  continue; }
878                                                 for( char *bp=cp; *bp; ++bp ) bp[0] = bp[1];
879                                         }
880                                         *cp++ = 0;
881                                         trans->add_string(tok);
882                                         continue;
883                                 }
884                                 tok = cp;
885                                 while( *cp && !ws(*cp) && *cp!='/' && *cp != '\n' ) ++cp;
886                                 int dhu = PRESS_RELEASE;
887                                 if( *cp == '/' ) {
888                                         *cp++ = 0;
889                                         switch( *cp ) {
890                                         case 'D':  dhu = PRESS;    break;
891                                         case 'H':  dhu = HOLD;     break;
892                                         case 'U':  dhu = RELEASE;  break;
893                                         default:
894                                                 fprintf(stderr, "invalid up/down modifier [%s]%s: '%c'\n",
895                                                         trans->name, tok, *cp);
896                                                 ret = 1;  break;
897                                         }
898                                         ++cp;
899                                 }
900                                 else
901                                         *cp++ = 0;
902                                 trans->add_keystroke(tok, dhu);
903                         }
904                         if( ret ) break;
905                         trans->finish_line();
906                         if( debug )
907                                 trans->print_line(key);
908                 skip:   ret = fgets(cp=line,sizeof(line),fp) ? 0 : -1;
909                         if( !ret ) ++no;
910                 }
911                 if( trans ) {
912                         if( trans != default_translation )
913                                 translations.append(trans);
914                         trans = 0;
915                 }
916         }
917         if( ret > 0 )
918                 fprintf(stderr, "shuttle config err file: %s, line:%d\n",
919                         config_path, no);
920
921         fclose(fp);
922         return ret;
923 }
924 #endif