2 // Copyright 2013 Eric Messick (FixedImagePhoto.com/Contact)
3 // reworked 2019 for cinelerra-gg by William Morrow
17 #include "mwindowgui.h"
19 #include "awindowgui.h"
21 #include "cwindowgui.h"
23 #include "vwindowgui.h"
35 #include <X11/keysym.h>
37 static Time milliTimeClock()
41 return (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
44 KeySymMapping KeySymMapping::key_sym_mapping[] = {
46 { "XK_Button_1", XK_Button_1 },
47 { "XK_Button_2", XK_Button_2 },
48 { "XK_Button_3", XK_Button_3 },
49 { "XK_Scroll_Up", XK_Scroll_Up },
50 { "XK_Scroll_Down", XK_Scroll_Down },
51 #include "shuttle_keys.h"
55 KeySym KeySymMapping::to_keysym(const char *str)
57 if( !strncmp("FWD_",str, 4) ) {
58 float speed = atof(str+4) / SHUTTLE_MAX_SPEED;
59 if( speed > SHUTTLE_MAX_SPEED ) return 0;
60 int key_code = (SKEY_MAX+SKEY_MIN)/2. +
61 (SKEY_MAX-SKEY_MIN)/2. * speed;
62 if( key_code > SKEY_MAX ) key_code = SKEY_MAX;
65 if( !strncmp("REV_",str, 4) ) {
66 float speed = atof(str+4) / SHUTTLE_MAX_SPEED;
67 if( speed > SHUTTLE_MAX_SPEED ) return 0;
68 int key_code = (SKEY_MAX+SKEY_MIN)/2. -
69 (SKEY_MAX-SKEY_MIN)/2. * speed;
70 if( key_code < SKEY_MIN ) key_code = SKEY_MIN;
73 for( KeySymMapping *ksp = &key_sym_mapping[0]; ksp->str; ++ksp )
74 if( !strcmp(str, ksp->str) ) return ksp->sym;
78 const char *KeySymMapping::to_string(KeySym ks)
80 for( KeySymMapping *ksp = &key_sym_mapping[0]; ksp->sym; ++ksp )
81 if( ksp->sym == ks ) return ksp->str;
82 if( ks >= SKEY_MIN && ks <= SKEY_MAX ) {
83 double speed = SHUTTLE_MAX_SPEED *
84 (ks-(SKEY_MAX+SKEY_MIN)/2.) / ((SKEY_MAX-SKEY_MIN)/2.);
85 static char text[BCSTRLEN];
86 sprintf(text, "%s_%0.3f", speed>=0 ? "FWD" : "REV", fabs(speed));
87 char *bp = strchr(text,'.');
89 char *cp = bp+strlen(bp);
90 while( --cp>bp && *cp=='0' ) *cp=0;
91 if( cp == bp ) *cp = 0;
98 TransName::TransName(int cin, const char *nm, const char *re)
101 this->name = cstrdup(nm);
103 TransName::~TransName()
108 void Translation::init(int def)
112 first_release_stroke = 0;
118 Translation::Translation(Shuttle *shuttle)
120 { // initial default translation
122 this->shuttle = shuttle;
123 this->name = cstrdup("Default");
124 names.append(new TransName(FOCUS_DEFAULT, name, ""));
125 key_down[K6].add_stroke(XK_Button_1, 1);
126 key_up[K6].add_stroke(XK_Button_1, 0);
127 key_down[K7].add_stroke(XK_Button_2, 1);
128 key_up[K7].add_stroke(XK_Button_2, 0);
129 key_down[K8].add_stroke(XK_Button_3, 1);
130 key_up[K8].add_stroke(XK_Button_3, 0);
131 jog[JL].add_stroke(XK_Scroll_Up, 1);
132 jog[JL].add_stroke(XK_Scroll_Up, 0);
133 jog[JR].add_stroke(XK_Scroll_Down, 0);
134 jog[JR].add_stroke(XK_Scroll_Down, 1);
137 Translation::Translation(Shuttle *shuttle, const char *name)
141 this->shuttle = shuttle;
142 this->name = cstrdup(name);
145 Translation::~Translation()
150 void Translation::clear()
152 names.remove_all_objects();
154 for( int i=0; i<NUM_KEYS; ++i ) key_down[i].clear();
155 for( int i=0; i<NUM_KEYS; ++i ) key_up[i].clear();
156 for( int i=0; i<NUM_SHUTTLES; ++i ) shuttles[i].clear();
157 for( int i=0; i<NUM_JOGS; ++i ) jog[i].clear();
160 void Translation::append_stroke(KeySym sym, int press)
162 Stroke *s = pressed_strokes->append();
167 void Translation::add_keysym(KeySym sym, int press_release)
169 //printf("add_keysym(0x%x, %d)\n", (int)sym, press_release);
170 switch( press_release ) {
172 append_stroke(sym, 1);
173 modifiers.mark_as_down(sym, 0);
176 append_stroke(sym, 0);
177 modifiers.mark_as_up(sym);
180 append_stroke(sym, 1);
181 modifiers.mark_as_down(sym, 1);
185 if( first_release_stroke ) {
186 modifiers.re_press();
187 first_release_stroke = 0;
190 append_stroke(keysym_down, 0);
192 append_stroke(sym, 1);
198 void Translation::add_release(int all_keys)
200 //printf("add_release(%d)\n", all_keys);
201 modifiers.release(all_keys);
203 pressed_strokes = released_strokes;
206 append_stroke(keysym_down, 0);
209 first_release_stroke = 1;
212 void Translation::add_keystroke(const char *keySymName, int press_release)
216 if( is_key && !strncmp(keySymName, "RELEASE", 8) ) {
220 sym = KeySymMapping::to_keysym(keySymName);
222 add_keysym(sym, press_release);
225 fprintf(stderr, "unrecognized KeySym: %s\n", keySymName);
228 void Translation::add_string(const char *str)
230 while( str && *str ) {
231 if( *str >= ' ' && *str <= '~' )
232 add_keysym((KeySym)(*str), PRESS_RELEASE);
237 int Translation::start_line(const char *key)
240 released_strokes = 0;
241 pressed = released = 0;
243 if( !strcasecmp("JL", key) ) {
246 else if( !strcasecmp("JR", key) ) {
250 char c = 0; int k = -1, n = 0;
251 if( sscanf(key, "%c%d%n", &c, &k, &n) != 2 ) return 1;
255 if( k >= K1 && k <= K15 ) {
256 pressed = &key_down[k];
257 released = &key_up[k];
262 if( k >= S_7 && k <= S7 ) {
263 pressed = &shuttles[k-S_7];
268 fprintf(stderr, "bad key name: [%s]%s\n", name, key);
271 if( pressed->first ) {
272 fprintf(stderr, "dupl key name: [%s]%s\n", name, key);
276 pressed_strokes = pressed;
277 released_strokes = released;
281 void Translation::print_stroke(Stroke *s)
284 const char *cp = KeySymMapping::to_string(s->keysym);
285 if( !cp ) { printf("0x%x", (int)s->keysym); cp = "???"; }
286 printf("%s/%c ", cp, s->press ? 'D' : 'U');
289 void Translation::print_strokes(const char *name, const char *up_dn, Strokes *strokes)
291 printf("%s[%s]: ", name, up_dn);
292 for( Stroke *s=strokes->first; s; s=s->next )
297 void Translation::finish_line()
299 //printf("finish_line()\n");
306 void Translation::print_line(const char *key)
309 print_strokes(key, "D", pressed_strokes);
310 print_strokes(key, "U", released_strokes);
313 print_strokes(key, "", pressed_strokes);
318 // press values in Modifiers:
321 // PRESS_RELEASE -> released, but to be re-pressed if necessary
324 void Modifiers::mark_as_down(KeySym sym, int hold)
326 Modifiers &modifiers = *this;
327 for( int i=0; i<size(); ++i ) {
328 Stroke &s = modifiers[i];
329 if( s.keysym == sym ) {
330 s.press = hold ? HOLD : PRESS;
334 Stroke &s = append();
336 s.press = hold ? HOLD : PRESS;
339 void Modifiers::mark_as_up(KeySym sym)
341 Modifiers &modifiers = *this;
342 for( int i=0; i<size(); ++i ) {
343 Stroke &s = modifiers[i];
344 if( s.keysym == sym ) {
351 void Modifiers::release(int allkeys)
353 Modifiers &modifiers = *this;
354 for( int i=0; i<size(); ++i ) {
355 Stroke &s = modifiers[i];
356 if( s.press == PRESS ) {
357 trans->append_stroke(s.keysym, 0);
358 s.press = PRESS_RELEASE;
360 else if( allkeys && s.press == HOLD ) {
361 trans->append_stroke(s.keysym, 0);
367 void Modifiers::re_press()
369 Modifiers &modifiers = *this;
370 for( int i=0; i<size(); ++i ) {
371 Stroke &s = modifiers[i];
372 if( s.press == PRESS_RELEASE ) {
373 trans->append_stroke(s.keysym, 1);
380 Shuttle::Shuttle(MWindow *mwindow)
383 this->mwindow = mwindow;
392 shuttlevalue = 0xffff;
399 last_translation = 0;
402 default_translation = new Translation(this);
411 delete default_translation;
412 delete [] config_path;
415 int Shuttle::send_button(unsigned int button, int press)
418 printf("btn: %u %d\n", button, press);
419 XButtonEvent *b = new XButtonEvent();
420 memset(b, 0, sizeof(*b));
421 b->type = press ? ButtonPress : ButtonRelease;
422 b->time = milliTimeClock();
424 b->display = wdw->top_level->display;
425 b->root = wdw->top_level->rootwin;
434 wdw->top_level->put_event((XEvent *) b);
437 int Shuttle::send_keycode(unsigned int keycode, int press, int send)
440 const char *cp = !send ? 0 :
441 KeySymMapping::to_string(keycode);
443 printf("key: %s %d\n", cp, press);
445 printf("key: %04x %d\n", keycode, press);
447 XKeyEvent *k = new XKeyEvent();
448 memset(k, 0, sizeof(*k));
449 k->type = press ? KeyPress : KeyRelease;
450 k->time = milliTimeClock();
451 k->send_event = send;
452 k->display = wdw->top_level->display;
453 k->root = wdw->top_level->rootwin;
460 k->keycode = keycode;
462 wdw->top_level->put_event((XEvent *) k);
466 int Shuttle::send_keysym(KeySym keysym, int press)
468 return keysym >= XK_Button_1 && keysym <= XK_Scroll_Down ?
469 send_button((unsigned int)keysym - XK_Button_0, press) :
470 send_keycode((unsigned int)keysym, press, 1);
471 // unsigned int keycode = XKeysymToKeycode(wdw->top_level->display, keysym);
472 // return send_keycode(keycode, press, 0);
476 static Stroke *fetch_stroke(Translation *translation, int kjs, int index)
482 case KJS_KEY_DOWN: ret = translation->key_down[index].first; break;
483 case KJS_KEY_UP: ret = translation->key_up[index].first; break;
484 case KJS_JOG: ret = translation->jog[index].first; break;
485 case KJS_SHUTTLE: ret = translation->shuttles[index-S_7].first; break;
491 void Shuttle::send_stroke_sequence(int kjs, int index)
494 Stroke *s = fetch_stroke(tr, kjs, index);
495 if( !s ) s = fetch_stroke(default_translation, kjs, index);
497 send_keysym(s->keysym, s->press);
502 void Shuttle::key(unsigned short code, unsigned int value)
504 code -= EVENT_CODE_KEY1;
505 if( code >= NUM_KEYS ) {
506 fprintf(stderr, "key(%d, %d) out of range\n", code + EVENT_CODE_KEY1, value);
509 send_stroke_sequence(value ? KJS_KEY_DOWN : KJS_KEY_UP, code);
513 void Shuttle::shuttle(int value)
515 if( value < S_7 || value > S7 ) {
516 fprintf(stderr, "shuttle(%d) out of range\n", value);
519 if( value != (int)shuttlevalue ) {
520 shuttlevalue = value;
521 send_stroke_sequence(KJS_SHUTTLE, value);
525 // Due to a bug (?) in the way Linux HID handles the ShuttlePro, the
526 // center position is not reported for the shuttle wheel. Instead,
527 // a jog event is generated immediately when it returns. We check to
528 // see if the time since the last shuttle was more than a few ms ago
529 // and generate a shuttle of 0 if so.
531 // Note, this fails if jogvalue happens to be 0, as we don't see that
533 void Shuttle::jog(unsigned int value)
535 if( jogvalue != 0xffff ) {
536 value = value & 0xff;
537 int direction = ((value - jogvalue) & 0x80) ? -1 : 1;
538 int index = direction > 0 ? 1 : 0;
539 while( jogvalue != value ) {
540 // driver fails to send an event when jogvalue == 0
541 if( jogvalue != 0 ) {
542 send_stroke_sequence(KJS_JOG, index);
544 jogvalue = (jogvalue + direction) & 0xff;
550 void Shuttle::jogshuttle(unsigned short code, unsigned int value)
556 case EVENT_CODE_SHUTTLE:
560 fprintf(stderr, "jogshuttle(%d, %d) invalid code\n", code, value);
565 const char *Shuttle::probe()
568 static const char *shuttle_devs[] = {
569 "/dev/input/by-id/usb-Contour_Design_ShuttleXpress-event-if00",
570 "/dev/input/by-id/usb-Contour_Design_ShuttlePRO_v2-event-if00",
571 "/dev/input/by-id/usb-Contour_Design_ShuttlePro-event-if00",
573 int ret = sizeof(shuttle_devs) / sizeof(shuttle_devs[0]);
574 while( --ret >= 0 && stat(shuttle_devs[ret] , &st) );
575 return ret >= 0 ? shuttle_devs[ret] : 0;
578 void Shuttle::start(const char *dev_name)
580 this->dev_name = dev_name;
588 if( running() && !done ) {
595 BC_WindowBase *Shuttle::owns(BC_WindowBase *wdw, Window win)
597 if( wdw->win == win ) return wdw;
598 if( (wdw=wdw->top_level)->win == win ) return wdw;
599 for( int i=wdw->popups.size(); --i>=0; )
600 if( wdw->popups[i]->win == win ) return wdw;
604 int Shuttle::get_focused_window_translation()
606 MWindowGUI *gui = mwindow->gui;
607 Display *dpy = gui->display;
609 int ret = 0, revert = 0;
610 char win_title[BCTEXTLEN]; win_title[0] = 0;
611 gui->lock_window("Shuttle::get_focused_window_translation");
612 XGetInputFocus(dpy, &focus, &revert);
613 if( last_focused != focus ) {
614 last_focused = focus;
615 Atom prop = XInternAtom(dpy, "WM_NAME", False);
617 unsigned long remain, len;
619 if( XGetWindowProperty(dpy, focus, prop, 0, sizeof(win_title)-1, False,
620 AnyPropertyType, &type, &form, &len, &remain, &list) == Success ) {
621 len = len*(form/8) - remain;
622 memcpy(win_title, list, len);
626 printf("new focus: %08x %s\n", (unsigned)focus, win_title);
630 fprintf(stderr, "XGetWindowProperty failed for window 0x%x\n",
635 gui->unlock_window();
638 this->wdw = 0; this->win = 0;
639 this->wx = 0; this->wy = 0;
640 this->rx = 0; this->ry = 0;
642 BC_WindowBase *wdw = 0;
644 if( (wdw=owns(mwindow->gui, focus)) != 0 )
646 else if( (wdw=owns(mwindow->awindow->gui, focus)) != 0 )
648 else if( (wdw=owns(mwindow->cwindow->gui, focus)) != 0 ) {
649 if( mwindow->cwindow->gui->canvas->get_fullscreen() )
650 wdw = mwindow->cwindow->gui->canvas->get_canvas();
653 else if( mwindow->gui->mainmenu->load_file->thread->running() &&
654 (wdw=mwindow->gui->mainmenu->load_file->thread->window) != 0 &&
658 int i = mwindow->vwindows.size();
660 VWindow *vwdw = mwindow->vwindows[i];
661 if( !vwdw->is_running() ) continue;
662 if( (wdw=owns(vwdw->gui, focus)) != 0 ) {
663 if( vwdw->gui->canvas->get_fullscreen() )
664 wdw = vwdw->gui->canvas->get_canvas();
665 cin = FOCUS_VIEWER; break;
669 if( cin < 0 ) return -1;
670 Window root = 0, child = 0;
671 int root_x = 0, root_y = 0, win_x = 0, win_y = 0, x = 0, y = 0;
672 unsigned int mask = 0, width = 0, height = 0, border_width = 0, depth = 0;
673 wdw->lock_window("Shuttle::get_focused_window_translation 1");
674 if( XQueryPointer(wdw->top_level->display, focus, &root, &child,
675 &root_x, &root_y, &win_x, &win_y, &mask) ) {
677 if( wdw->active_menubar )
678 child = wdw->active_menubar->win;
679 else if( wdw->active_popup_menu )
680 child = wdw->active_popup_menu->win;
681 else if( wdw->active_subwindow )
682 child = wdw->active_subwindow->win;
687 XGetGeometry(wdw->top_level->display, child, &root, &x, &y,
688 &width, &height, &border_width, &depth);
690 wdw->unlock_window();
691 if( !child || !wdw->match_window(child) ) return -1;
698 this->wx = win_x - x;
699 this->wy = win_y - y;
700 for( tr=translations.first; tr; tr=tr->next ) {
701 if( tr->is_default ) return 1;
702 for( int i=0; i<tr->names.size(); ++i ) {
703 TransName *name = tr->names[i];
704 if( name->cin == cin ) return 1;
707 tr = default_translation;
711 void Shuttle::handle_event()
713 if( read_config_file() > 0 ) {
717 if( get_focused_window_translation() < 0 )
719 if( last_translation != tr ) {
720 last_translation = tr;
722 printf("new translation: %s\n", tr->name);
725 // printf("event: (%d, %d, 0x%x)\n", ev.type, ev.code, ev.value);
727 case EVENT_TYPE_DONE:
728 case EVENT_TYPE_ACTIVE_KEY:
731 key(ev.code, ev.value);
733 case EVENT_TYPE_JOGSHUTTLE:
734 jogshuttle(ev.code, ev.value);
737 fprintf(stderr, "handle_event() invalid type code\n");
744 for( enable_cancel(); !done; sleep(1) ) {
745 fd = open(dev_name, O_RDONLY);
748 if( first_time ) break;
751 if( !ioctl(fd, EVIOCGRAB, 1) ) { // exclusive access
754 int ret = read(fd, &ev, sizeof(ev));
756 if( ret != sizeof(ev) ) {
757 if( ret < 0 ) { perror("read event"); break; }
758 fprintf(stderr, "bad read: %d\n", ret);
765 perror( "evgrab ioctl" );
771 int Shuttle::read_config_file()
775 config_path = (env=getenv("SHUTTLE_CONFIG_FILE")) != 0 ? cstrdup(env) :
776 (env=getenv("HOME")) != 0 ? cstrcat(2, env, "/.shuttlerc") : 0;
777 if( !config_path ) { fprintf(stderr, "no config file\n"); return 1; }
780 if( stat(config_path, &st) ) {
782 char shuttlerc[BCTEXTLEN];
783 snprintf(shuttlerc, sizeof(shuttlerc), "%s/shuttlerc",
784 File::get_cindat_path());
785 if( stat(shuttlerc, &st) ) {
789 delete [] config_path;
790 config_path = cstrdup(shuttlerc);
792 if( config_mtime > 0 &&
793 config_mtime == st.st_mtime ) return 0;
794 FILE *fp = fopen(config_path, "r");
800 config_mtime = st.st_mtime;
801 translations.clear();
803 #define ws(ch) (ch==' ' || ch=='\t')
804 char line[BCTEXTLEN], *cp;
805 Translation *trans = 0;
807 int ret = fgets(cp=line,sizeof(line),fp) ? 0 : -1;
812 while( !ret && *cp == '[' ) {
814 while( *cp && *cp != ']' ) ++cp;
816 if( !name || !*name ) { ret = 1; break; }
818 if( !strcasecmp("default", name) )
820 else if( !strcasecmp("cinelerra", name) )
822 else if( !strcasecmp("resources", name) )
824 else if( !strcasecmp("composer", name) )
826 else if( !strcasecmp("viewer", name) )
828 else if( !strcasecmp("load", name) )
831 fprintf(stderr, "unknown focus target window: %s\n",
836 if( cin == FOCUS_DEFAULT ) {
837 trans = default_translation;
841 trans = new Translation(this, name);
844 while( ws(*cp) ) ++cp;
845 trans->names.append(new TransName(cin, name, cp));
846 ret = fgets(cp=line,sizeof(line),fp) ? 0 : 1;
848 fprintf(stderr, "hit eof, no translation def for: %s\n",
849 trans->names.last()->name);
855 if( debug && trans ) {
856 printf("------------------------\n");
857 TransNames &names = trans->names;
858 for( int i=0; i<names.size(); ++i ) {
859 TransName *tp = names[i];
860 printf("[%s] # %d\n\n", tp->name, tp->cin);
863 // rules lines: "tok <stroke list>\n"
864 while( !ret && *cp != '[' ) {
865 const char *key = 0, *tok = 0;
866 while( ws(*cp) ) ++cp;
867 if( !*cp || *cp == '\n' || *cp == '#' ) goto skip;
869 while( *cp && !ws(*cp) && *cp != '\n' ) ++cp;
871 if( !strcmp(tok, "DEBUG") ) {
872 debug = 1; goto skip;
876 fprintf(stderr, "no translation section defining key: %s\n", key);
880 ret = trans->start_line(key);
881 while( !ret && *cp && *cp != '\n' ) {
882 while( ws(*cp) ) ++cp;
883 if( !*cp || *cp == '#' || *cp == '\n' ) break;
886 while( *cp && *cp != '"' && *cp != '\n' ) {
887 if( *cp != '\\' ) { ++cp; continue; }
888 for( char *bp=cp; *bp; ++bp ) bp[0] = bp[1];
891 trans->add_string(tok);
895 while( *cp && !ws(*cp) && *cp!='/' && *cp != '\n' ) ++cp;
896 int dhu = PRESS_RELEASE;
900 case 'D': dhu = PRESS; break;
901 case 'H': dhu = HOLD; break;
902 case 'U': dhu = RELEASE; break;
904 fprintf(stderr, "invalid up/down modifier [%s]%s: '%c'\n",
905 trans->name, tok, *cp);
912 trans->add_keystroke(tok, dhu);
915 trans->finish_line();
917 trans->print_line(key);
918 skip: ret = fgets(cp=line,sizeof(line),fp) ? 0 : -1;
922 if( trans != default_translation )
923 translations.append(trans);
928 fprintf(stderr, "shuttle config err file: %s, line:%d\n",