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