2 // Copyright 2013 Eric Messick (FixedImagePhoto.com/Contact)
3 // reworked 2019 for cinelerra-gg by William Morrow
16 #include "mwindowgui.h"
18 #include "awindowgui.h"
20 #include "cwindowgui.h"
22 #include "vwindowgui.h"
34 #include <X11/keysym.h>
36 static Time milliTimeClock()
40 return (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
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"
53 KeySym KeySymMapping::to_keysym(const char *str)
55 for( KeySymMapping *ksp = &key_sym_mapping[0]; ksp->str; ++ksp )
56 if( !strcmp(str, ksp->str) ) return ksp->sym;
60 const char *KeySymMapping::to_string(KeySym ks)
62 for( KeySymMapping *ksp = &key_sym_mapping[0]; ksp->sym; ++ksp )
63 if( ksp->sym == ks ) return ksp->str;
67 TransName::TransName(int cin, const char *nm, const char *re)
70 this->name = cstrdup(nm);
71 this->err = regcomp(&this->regex, re, REG_NOSUB);
73 fprintf(stderr, "error compiling regex for [%s]: %s\n", name, re);
75 regerror(err, ®ex, emsg, sizeof(emsg));
76 fprintf(stderr, "regerror: %s\n", emsg);
79 TransName::~TransName()
85 void Translation::init(int def)
89 first_release_stroke = 0;
95 Translation::Translation(Shuttle *shuttle)
97 { // initial default translation
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);
114 Translation::Translation(Shuttle *shuttle, const char *name)
118 this->shuttle = shuttle;
119 this->name = cstrdup(name);
122 Translation::~Translation()
127 void Translation::clear()
129 names.remove_all_objects();
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();
137 void Translation::append_stroke(KeySym sym, int press)
139 Stroke *s = pressed_strokes->append();
144 void Translation::add_keysym(KeySym sym, int press_release)
146 //printf("add_keysym(0x%x, %d)\n", (int)sym, press_release);
147 switch( press_release ) {
149 append_stroke(sym, 1);
150 modifiers.mark_as_down(sym, 0);
153 append_stroke(sym, 0);
154 modifiers.mark_as_up(sym);
157 append_stroke(sym, 1);
158 modifiers.mark_as_down(sym, 1);
162 if( first_release_stroke ) {
163 modifiers.re_press();
164 first_release_stroke = 0;
167 append_stroke(keysym_down, 0);
169 append_stroke(sym, 1);
175 void Translation::add_release(int all_keys)
177 //printf("add_release(%d)\n", all_keys);
178 modifiers.release(all_keys);
180 pressed_strokes = released_strokes;
183 append_stroke(keysym_down, 0);
186 first_release_stroke = 1;
189 void Translation::add_keystroke(const char *keySymName, int press_release)
193 if( is_key && !strncmp(keySymName, "RELEASE", 8) ) {
197 sym = KeySymMapping::to_keysym(keySymName);
199 add_keysym(sym, press_release);
202 fprintf(stderr, "unrecognized KeySym: %s\n", keySymName);
205 void Translation::add_string(const char *str)
207 while( str && *str ) {
208 if( *str >= ' ' && *str <= '~' )
209 add_keysym((KeySym)(*str), PRESS_RELEASE);
214 int Translation::start_line(const char *key)
217 released_strokes = 0;
218 pressed = released = 0;
220 if( !strcasecmp("JL", key) ) {
223 else if( !strcasecmp("JR", key) ) {
227 char c = 0; int k = -1, n = 0;
228 if( sscanf(key, "%c%d%n", &c, &k, &n) != 2 ) return 1;
232 if( k >= K1 && k <= K15 ) {
233 pressed = &key_down[k];
234 released = &key_up[k];
239 if( k >= S_7 && k <= S7 ) {
240 pressed = &shuttles[k-S_7];
245 fprintf(stderr, "bad key name: [%s]%s\n", name, key);
248 if( pressed->first ) {
249 fprintf(stderr, "dupl key name: [%s]%s\n", name, key);
253 pressed_strokes = pressed;
254 released_strokes = released;
258 void Translation::print_stroke(Stroke *s)
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');
266 void Translation::print_strokes(const char *name, const char *up_dn, Strokes *strokes)
268 printf("%s[%s]: ", name, up_dn);
269 for( Stroke *s=strokes->first; s; s=s->next )
274 void Translation::finish_line()
276 //printf("finish_line()\n");
283 void Translation::print_line(const char *key)
286 print_strokes(key, "D", pressed_strokes);
287 print_strokes(key, "U", released_strokes);
290 print_strokes(key, "", pressed_strokes);
295 // press values in Modifiers:
298 // PRESS_RELEASE -> released, but to be re-pressed if necessary
301 void Modifiers::mark_as_down(KeySym sym, int hold)
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;
311 Stroke &s = append();
313 s.press = hold ? HOLD : PRESS;
316 void Modifiers::mark_as_up(KeySym sym)
318 Modifiers &modifiers = *this;
319 for( int i=0; i<size(); ++i ) {
320 Stroke &s = modifiers[i];
321 if( s.keysym == sym ) {
328 void Modifiers::release(int allkeys)
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;
337 else if( allkeys && s.press == HOLD ) {
338 trans->append_stroke(s.keysym, 0);
344 void Modifiers::re_press()
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);
357 Shuttle::Shuttle(MWindow *mwindow)
360 this->mwindow = mwindow;
369 shuttlevalue = 0xffff;
370 last_shuttle.tv_sec = 0;
371 last_shuttle.tv_usec = 0;
372 need_synthetic_shuttle = 0;
379 last_translation = 0;
382 default_translation = new Translation(this);
391 delete default_translation;
392 delete [] config_path;
395 int Shuttle::send_button(unsigned int button, int press)
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;
413 wdw->top_level->put_event((XEvent *) b);
416 int Shuttle::send_key(KeySym keysym, int press)
418 KeyCode keycode = XKeysymToKeycode(wdw->top_level->display, keysym);
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;
433 k->keycode = keycode;
435 wdw->top_level->put_event((XEvent *) k);
439 int Shuttle::send_keysym(KeySym keysym, int press)
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);
447 static Stroke *fetch_stroke(Translation *translation, int kjs, int index)
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;
462 void Shuttle::send_stroke_sequence(int kjs, int index)
465 Stroke *s = fetch_stroke(tr, kjs, index);
466 if( !s ) s = fetch_stroke(default_translation, kjs, index);
468 send_keysym(s->keysym, s->press);
473 void Shuttle::key(unsigned short code, unsigned int value)
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);
480 send_stroke_sequence(value ? KJS_KEY_DOWN : KJS_KEY_UP, code);
484 void Shuttle:: shuttle(int value)
486 if( value < S_7 || value > S7 ) {
487 fprintf(stderr, "shuttle(%d) out of range\n", value);
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);
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.
504 // Note, this fails if jogvalue happens to be 0, as we don't see that
506 void Shuttle::jog(unsigned int value)
510 struct timeval delta;
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 );
518 if( delta.tv_sec >= 1 || delta.tv_usec >= 5000 ) {
520 need_synthetic_shuttle = 0;
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);
532 jogvalue = (jogvalue + direction) & 0xff;
538 void Shuttle::jogshuttle(unsigned short code, unsigned int value)
544 case EVENT_CODE_SHUTTLE:
548 fprintf(stderr, "jogshuttle(%d, %d) invalid code\n", code, value);
553 const char *Shuttle::probe()
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",
560 int ret = sizeof(shuttle_devs) / sizeof(shuttle_devs[0]);
561 while( --ret >= 0 && stat(shuttle_devs[ret] , &st) );
562 return ret >= 0 ? shuttle_devs[ret] : 0;
565 void Shuttle::start(const char *dev_name)
567 this->dev_name = dev_name;
575 if( running() && !done ) {
583 int Shuttle::get_focused_window_translation()
585 MWindowGUI *gui = mwindow->gui;
586 Display *dpy = gui->display;
588 int ret = 0, revert = 0;
589 char win_title[BCTEXTLEN];
590 gui->lock_window("Shuttle::get_focused_window_translation");
591 XGetInputFocus(dpy, &focus, &revert);
592 if( last_focused != focus ) {
593 last_focused = focus;
594 Atom prop = XInternAtom(dpy, "WM_NAME", False);
596 unsigned long remain, len;
598 if( XGetWindowProperty(dpy, focus, prop, 0, sizeof(win_title)-1, False,
599 AnyPropertyType, &type, &form, &len, &remain, &list) == Success ) {
600 len = len*(form/8) - remain;
601 memcpy(win_title, list, len);
605 printf("new focus: %08x\n", (unsigned)focus);
609 fprintf(stderr, "XGetWindowProperty failed for window 0x%x\n",
614 gui->unlock_window();
617 this->wdw = 0; this->win = 0;
618 this->wx = 0; this->wy = 0;
619 this->rx = 0; this->ry = 0;
621 BC_WindowBase *wdw = 0;
623 if( (wdw=mwindow->gui) && wdw->win == focus )
625 else if( (wdw=mwindow->awindow->gui) && wdw->win == focus )
627 else if( (wdw=mwindow->cwindow->gui) && wdw->win == focus )
629 else if( (wdw=mwindow->gui->mainmenu->load_file->thread->window) &&
633 int i = mwindow->vwindows.size();
635 VWindow *vwdw = mwindow->vwindows[i];
636 if( !vwdw->is_running() ) continue;
637 if( (wdw=vwdw->gui) && wdw->win == focus ) {
638 cin = FOCUS_VIEWER; break;
642 if( cin < 0 ) return -1;
643 Window root = 0, child = 0;
644 int root_x = 0, root_y = 0, win_x = 0, win_y = 0, x = 0, y = 0;
645 unsigned int mask = 0, width = 0, height = 0, border_width = 0, depth = 0;
646 wdw->lock_window("Shuttle::get_focused_window_translation 1");
647 if( XQueryPointer(wdw->display, focus, &root, &child,
648 &root_x, &root_y, &win_x, &win_y, &mask) ) {
650 if( wdw->active_menubar )
651 child = wdw->active_menubar->win;
652 else if( wdw->active_popup_menu )
653 child = wdw->active_popup_menu->win;
654 else if( wdw->active_subwindow )
655 child = wdw->active_subwindow->win;
658 XGetGeometry(wdw->display, child, &root, &x, &y,
659 &width, &height, &border_width, &depth);
661 wdw->unlock_window();
662 if( !child || !wdw->match_window(child) ) return -1;
669 this->wx = win_x - x;
670 this->wy = win_y - y;
671 for( tr=translations.first; tr; tr=tr->next ) {
672 if( tr->is_default ) return 1;
673 for( int i=0; i<tr->names.size(); ++i ) {
674 TransName *name = tr->names[i];
675 if( name->cin != cin ) continue;
676 if( regexec(&name->regex, win_title, 0, NULL, 0) )
680 tr = default_translation;
684 void Shuttle::handle_event()
686 if( read_config_file() > 0 ) {
690 if( get_focused_window_translation() < 0 )
692 if( last_translation != tr ) {
693 last_translation = tr;
695 printf("new translation: %s\n", tr->name);
697 //fprintf(stderr, "event: (%d, %d, 0x%x)\n", ev.type, ev.code, ev.value);
699 case EVENT_TYPE_DONE:
700 case EVENT_TYPE_ACTIVE_KEY:
703 key(ev.code, ev.value);
705 case EVENT_TYPE_JOGSHUTTLE:
706 jogshuttle(ev.code, ev.value);
709 fprintf(stderr, "handle_event() invalid type code\n");
716 for( enable_cancel(); !done; sleep(1) ) {
717 fd = open(dev_name, O_RDONLY);
720 if( first_time ) break;
723 if( !ioctl(fd, EVIOCGRAB, 1) ) { // exclusive access
726 int ret = read(fd, &ev, sizeof(ev));
728 if( ret != sizeof(ev) ) {
729 if( ret < 0 ) { perror("read event"); break; }
730 fprintf(stderr, "bad read: %d\n", ret);
737 perror( "evgrab ioctl" );
743 int Shuttle::read_config_file()
747 config_path = (env=getenv("SHUTTLE_CONFIG_FILE")) != 0 ? cstrdup(env) :
748 (env=getenv("HOME")) != 0 ? cstrcat(2, env, "/.shuttlerc") : 0;
749 if( !config_path ) { fprintf(stderr, "no config file\n"); return 1; }
752 if( stat(config_path, &st) ) {
754 char shuttlerc[BCTEXTLEN];
755 snprintf(shuttlerc, sizeof(shuttlerc), "%s/shuttlerc",
756 File::get_cindat_path());
757 if( stat(shuttlerc, &st) ) {
761 delete [] config_path;
762 config_path = cstrdup(shuttlerc);
764 if( config_mtime > 0 &&
765 config_mtime == st.st_mtime ) return 0;
766 FILE *fp = fopen(config_path, "r");
772 config_mtime = st.st_mtime;
773 translations.clear();
775 #define ws(ch) (ch==' ' || ch=='\t')
776 char line[BCTEXTLEN], *cp;
777 Translation *trans = 0;
779 int ret = fgets(cp=line,sizeof(line),fp) ? 0 : -1;
784 while( !ret && *cp == '[' ) {
786 while( *cp && *cp != ']' ) ++cp;
788 if( !name || !*name ) { ret = 1; break; }
790 if( !strcasecmp("default", name) )
792 else if( !strcasecmp("cinelerra", name) )
794 else if( !strcasecmp("resources", name) )
796 else if( !strcasecmp("composer", name) )
798 else if( !strcasecmp("viewer", name) )
800 else if( !strcasecmp("load", name) )
803 fprintf(stderr, "unknown focus target window: %s\n",
808 if( cin == FOCUS_DEFAULT ) {
809 trans = default_translation;
813 trans = new Translation(this, name);
816 while( ws(*cp) ) ++cp;
817 // regex in TransName constructor
818 trans->names.append(new TransName(cin, name, cp));
819 if( trans->names.last()->err ) { ret = 1; break; }
820 ret = fgets(cp=line,sizeof(line),fp) ? 0 : 1;
822 fprintf(stderr, "hit eof, no translation def for: %s\n",
823 trans->names.last()->name);
829 if( debug && trans ) {
830 printf("------------------------\n");
831 TransNames &names = trans->names;
832 for( int i=0; i<names.size(); ++i ) {
833 TransName *tp = names[i];
834 printf("[%s] # %d\n\n", tp->name, tp->cin);
837 // rules lines: "tok <stroke list>\n"
838 while( !ret && *cp != '[' ) {
839 const char *key = 0, *tok = 0;
840 while( ws(*cp) ) ++cp;
841 if( !*cp || *cp == '\n' || *cp == '#' ) goto skip;
843 while( *cp && !ws(*cp) && *cp != '\n' ) ++cp;
845 if( !strcmp(tok, "DEBUG") ) {
846 debug = 1; goto skip;
850 fprintf(stderr, "no translation section defining key: %s\n", key);
854 ret = trans->start_line(key);
855 while( !ret && *cp && *cp != '\n' ) {
856 while( ws(*cp) ) ++cp;
857 if( !*cp || *cp == '#' || *cp == '\n' ) break;
860 while( *cp && *cp != '"' && *cp != '\n' ) {
861 if( *cp != '\\' ) { ++cp; continue; }
862 for( char *bp=cp; *bp; ++bp ) bp[0] = bp[1];
865 trans->add_string(tok);
869 while( *cp && !ws(*cp) && *cp!='/' && *cp != '\n' ) ++cp;
870 int dhu = PRESS_RELEASE;
874 case 'D': dhu = PRESS; break;
875 case 'H': dhu = HOLD; break;
876 case 'U': dhu = RELEASE; break;
878 fprintf(stderr, "invalid up/down modifier [%s]%s: '%c'\n",
879 trans->name, tok, *cp);
886 trans->add_keystroke(tok, dhu);
889 trans->finish_line();
891 trans->print_line(key);
892 skip: ret = fgets(cp=line,sizeof(line),fp) ? 0 : -1;
896 if( trans != default_translation )
897 translations.append(trans);
902 fprintf(stderr, "shuttle config err file: %s, line:%d\n",