dvb chan tuner api upgrade, slip/ripple handle drag keyfrm fix, load menu tweaks
[goodguy/cinelerra.git] / cinelerra-5.1 / cinelerra / shuttle.C
index 627f162a8d6f0e71a5f65bfbc33940f1fa970208..df2850dc8e4f22a5c6915e7062ddb151378b74d4 100644 (file)
@@ -2,22 +2,33 @@
 // Copyright 2013 Eric Messick (FixedImagePhoto.com/Contact)
 // reworked 2019 for cinelerra-gg by William Morrow
 
-// keys.h collides with linux/input_events.h
-#define KEYS_H
-
 #include "arraylist.h"
 #include "cstrdup.h"
 #include "file.h"
 #include "guicast.h"
+#include "keys.h"
 #include "linklist.h"
 #include "loadfile.h"
 #include "mainmenu.h"
-#include "mwindow.h"
 #include "shuttle.h"
 #include "thread.h"
 
+#include "mwindow.h"
+#include "mwindowgui.h"
+#include "awindow.h"
+#include "awindowgui.h"
+#include "cwindow.h"
+#include "cwindowgui.h"
+#include "vwindow.h"
+#include "vwindowgui.h"
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
 #include <sys/time.h>
-#include <sys/types.h>
 #include <sys/stat.h>
 
 #include <X11/Xlib.h>
@@ -31,6 +42,7 @@ static Time milliTimeClock()
 }
 
 KeySymMapping KeySymMapping::key_sym_mapping[] = {
+// button keycodes
        { "XK_Button_1", XK_Button_1 },
        { "XK_Button_2", XK_Button_2 },
        { "XK_Button_3", XK_Button_3 },
@@ -40,17 +52,78 @@ KeySymMapping KeySymMapping::key_sym_mapping[] = {
        { NULL, 0 }
 };
 
-KeySym KeySymMapping::to_keysym(const char *str)
+int KeySymMapping::get_mask(const char *&str)
 {
+       int mask = 0;
+       while( *str ) {
+               if( !strncmp("Shift-",str,6) ) {
+                       mask |= ShiftMask;
+                       str += 6;  continue;
+               }
+               if( !strncmp("Ctrl-",str,5) ) {
+                       mask |= ControlMask;
+                       str += 5;  continue;
+               }
+               else if( !strncmp("Alt-",str,4) ) {
+                       mask |= Mod1Mask;
+                       str += 4;  continue;
+               }
+               break;
+       }
+       return mask;
+}
+
+SKeySym KeySymMapping::to_keysym(const char *str)
+{
+       if( !strncmp("FWD_",str, 4) ) {
+               float speed = atof(str+4) / SHUTTLE_MAX_SPEED;
+               if( speed > SHUTTLE_MAX_SPEED ) return 0;
+               int key_code = (SKEY_MAX+SKEY_MIN)/2. +
+                       (SKEY_MAX-SKEY_MIN)/2. * speed;
+               if( key_code > SKEY_MAX ) key_code = SKEY_MAX;
+               return key_code;
+       }
+       if( !strncmp("REV_",str, 4) ) {
+               float speed = atof(str+4) / SHUTTLE_MAX_SPEED;
+               if( speed > SHUTTLE_MAX_SPEED ) return 0;
+               int key_code = (SKEY_MAX+SKEY_MIN)/2. -
+                       (SKEY_MAX-SKEY_MIN)/2. * speed;
+               if( key_code < SKEY_MIN ) key_code = SKEY_MIN;
+               return key_code;
+       }
+       int mask = get_mask(str);
        for( KeySymMapping *ksp = &key_sym_mapping[0]; ksp->str; ++ksp )
-               if( !strcmp(str, ksp->str) ) return ksp->sym;
+               if( !strcmp(str, ksp->str) )
+                       return SKeySym(ksp->sym, mask);
        return 0;
 }
 
-const char *KeySymMapping::to_string(KeySym ks)
+const char *KeySymMapping::to_string(SKeySym ks)
 {
-       for( KeySymMapping *ksp = &key_sym_mapping[0]; ksp->sym; ++ksp )
-               if( ksp->sym == ks ) return ksp->str;
+       for( KeySymMapping *ksp = &key_sym_mapping[0]; ksp->sym.key; ++ksp ) {
+               if( ksp->sym.key == ks.key ) {
+                       static char string[BCSTRLEN];
+                       char *sp = string, *ep = sp+sizeof(string);
+                       if( ks.msk & Mod1Mask ) sp += snprintf(sp, ep-sp, "Alt-");
+                       if( ks.msk & ControlMask ) sp += snprintf(sp, ep-sp, "Ctrl-");
+                       if( ks.msk & ShiftMask ) sp += snprintf(sp, ep-sp, "Shift-");
+                       snprintf(sp, ep-sp, "%s", ksp->str);
+                       return string;
+               }
+       }
+       if( ks >= SKEY_MIN && ks <= SKEY_MAX ) {
+               double speed = SHUTTLE_MAX_SPEED *
+                       (ks-(SKEY_MAX+SKEY_MIN)/2.) / ((SKEY_MAX-SKEY_MIN)/2.);
+               static char text[BCSTRLEN];
+               sprintf(text, "%s_%0.3f", speed>=0 ? "FWD" : "REV", fabs(speed));
+               char *bp = strchr(text,'.');
+               if( bp ) {
+                       char *cp = bp+strlen(bp);
+                       while( --cp>bp && *cp=='0' ) *cp=0;
+                       if( cp == bp ) *cp = 0;
+               }
+               return text;
+       }
        return 0;
 }
 
@@ -58,18 +131,10 @@ TransName::TransName(int cin, const char *nm, const char *re)
 {
        this->cin = cin;
        this->name = cstrdup(nm);
-       this->err = regcomp(&this->regex, re, REG_NOSUB);
-       if( err ) {
-               fprintf(stderr, "error compiling regex for [%s]: %s\n", name, re);
-               char emsg[BCTEXTLEN];
-               regerror(err, &regex, emsg, sizeof(emsg));
-               fprintf(stderr, "regerror: %s\n", emsg);
-       }
 }
 TransName::~TransName()
 {
        delete [] name;
-       regfree(&regex);
 }
 
 void Translation::init(int def)
@@ -124,14 +189,14 @@ void Translation::clear()
        for( int i=0; i<NUM_JOGS; ++i ) jog[i].clear();
 }
 
-void Translation::append_stroke(KeySym sym, int press)
+void Translation::append_stroke(SKeySym sym, int press)
 {
        Stroke *s = pressed_strokes->append();
        s->keysym = sym;
        s->press = press;
 }
 
-void Translation::add_keysym(KeySym sym, int press_release)
+void Translation::add_keysym(SKeySym sym, int press_release)
 {
 //printf("add_keysym(0x%x, %d)\n", (int)sym, press_release);
        switch( press_release ) {
@@ -166,9 +231,8 @@ void Translation::add_release(int all_keys)
 {
 //printf("add_release(%d)\n", all_keys);
        modifiers.release(all_keys);
-       if( !all_keys ) {
+       if( !all_keys )
                pressed_strokes = released_strokes;
-       }
        if( keysym_down ) {
                append_stroke(keysym_down, 0);
                keysym_down = 0;
@@ -178,7 +242,7 @@ void Translation::add_release(int all_keys)
 
 void Translation::add_keystroke(const char *keySymName, int press_release)
 {
-       KeySym sym;
+       SKeySym sym;
 
        if( is_key && !strncmp(keySymName, "RELEASE", 8) ) {
                add_release(0);
@@ -192,13 +256,18 @@ void Translation::add_keystroke(const char *keySymName, int press_release)
                fprintf(stderr, "unrecognized KeySym: %s\n", keySymName);
 }
 
-void Translation::add_string(const char *str)
+void Translation::add_string(char *&str)
 {
-       while( str && *str ) {
-               if( *str >= ' ' && *str <= '~' )
-                       add_keysym((KeySym)(*str), PRESS_RELEASE);
-               ++str;
+       int delim = *str++;
+       if( !delim ) return;
+       while( str && *str && *str!=delim ) {
+               if( *str < ' ' || *str > '~' ) continue;
+               int mask = KeySymMapping::get_mask((const char *&)str);
+               if( str[0] == '\\' && str[1] ) ++str;
+               add_keysym(SKeySym(*str++, mask), PRESS_RELEASE);
+               mask = 0;
        }
+       if( *str == delim ) ++str;
 }
 
 int Translation::start_line(const char *key)
@@ -273,11 +342,11 @@ void Translation::finish_line()
 void Translation::print_line(const char *key)
 {
        if( is_key ) {
-               print_strokes(key, "D", pressed_strokes);
-               print_strokes(key, "U", released_strokes);
+               print_strokes(key, "D", pressed);
+               print_strokes(key, "U", released);
        }
        else {
-               print_strokes(key, "", pressed_strokes);
+               print_strokes(key, "", pressed);
        }
        printf("\n");
 }
@@ -288,7 +357,7 @@ void Translation::print_line(const char *key)
 // PRESS_RELEASE -> released, but to be re-pressed if necessary
 // RELEASE -> up
 
-void Modifiers::mark_as_down(KeySym sym, int hold)
+void Modifiers::mark_as_down(SKeySym sym, int hold)
 {
        Modifiers &modifiers = *this;
        for( int i=0; i<size(); ++i ) {
@@ -303,7 +372,7 @@ void Modifiers::mark_as_down(KeySym sym, int hold)
        s.press = hold ? HOLD : PRESS;
 }
 
-void Modifiers::mark_as_up(KeySym sym)
+void Modifiers::mark_as_up(SKeySym sym)
 {
        Modifiers &modifiers = *this;
        for( int i=0; i<size(); ++i ) {
@@ -345,7 +414,7 @@ void Modifiers::re_press()
 
 
 Shuttle::Shuttle(MWindow *mwindow)
- : Thread(0, 0, 0)
+ : Thread(1, 0, 0)
 {
        this->mwindow = mwindow;
 
@@ -357,18 +426,26 @@ Shuttle::Shuttle(MWindow *mwindow)
        wx = wy = 0;
        jogvalue = 0xffff;
        shuttlevalue = 0xffff;
-       last_shuttle.tv_sec = 0;
-       last_shuttle.tv_usec = 0;
-       need_synthetic_shuttle = 0;
-       dev_name = 0;
+       dev_index = -1;
 
        done = -1;
        failed = 0;
        first_time = 1;
        tr = 0;
+       debug = 0;
+       usb_direct = 0;
+
        last_translation = 0;
        last_focused = 0;
 
+#ifdef HAVE_SHUTTLE_USB
+       devsh = 0;
+       claimed = -1;
+       last_jog = 0;
+       last_shuttle = 0;
+       last_btns = 0;
+#endif
+
        default_translation = new Translation(this);
        config_path = 0;
        config_mtime = 0;
@@ -390,6 +467,7 @@ int Shuttle::send_button(unsigned int button, int press)
        memset(b, 0, sizeof(*b));
        b->type = press ? ButtonPress : ButtonRelease;
        b->time = milliTimeClock();
+       b->send_event = 1;
        b->display = wdw->top_level->display;
        b->root = wdw->top_level->rootwin;
        b->window = win;
@@ -403,15 +481,21 @@ int Shuttle::send_button(unsigned int button, int press)
        wdw->top_level->put_event((XEvent *) b);
        return 0;
 }
-int Shuttle::send_key(KeySym keysym, int press)
+int Shuttle::send_keycode(unsigned key, unsigned msk, int press, int send)
 {
-       KeyCode keycode = XKeysymToKeycode(wdw->top_level->display, keysym);
-       if( debug )
-               printf("key: %04x %d\n", (unsigned)keycode, press);
+       if( debug ) {
+               const char *cp = !send ? 0 :
+                       KeySymMapping::to_string(SKeySym(key, msk));
+               if( cp )
+                       printf("key: %s %d\n", cp, press);
+               else
+                       printf("key: %04x/%04x %d\n", key, msk, press);
+       }
        XKeyEvent *k = new XKeyEvent();
        memset(k, 0, sizeof(*k));
        k->type = press ? KeyPress : KeyRelease;
        k->time = milliTimeClock();
+       k->send_event = send;
        k->display = wdw->top_level->display;
        k->root = wdw->top_level->rootwin;
        k->window = win;
@@ -420,17 +504,19 @@ int Shuttle::send_key(KeySym keysym, int press)
        k->x = wx;
        k->y = wy;
        k->state = msk;
-       k->keycode = keycode;
+       k->keycode = key;
        k->same_screen = 1;
        wdw->top_level->put_event((XEvent *) k);
        return 0;
 }
 
-int Shuttle::send_keysym(KeySym keysym, int press)
+int Shuttle::send_keysym(SKeySym keysym, int press)
 {
        return keysym >= XK_Button_1 && keysym <= XK_Scroll_Down ?
                send_button((unsigned int)keysym - XK_Button_0, press) :
-               send_key(keysym, press ? True : False);
+               send_keycode(keysym.key, keysym.msk, press, 1);
+//     unsigned int keycode = XKeysymToKeycode(wdw->top_level->display, keysym);
+//     return send_keycode(keycode, press, 0);
 }
 
 
@@ -440,10 +526,10 @@ static Stroke *fetch_stroke(Translation *translation, int kjs, int index)
        if( translation ) {
                switch( kjs ) {
                default:
-               case KJS_KEY_DOWN:  ret = translation->key_down[index].first;   break;
-               case KJS_KEY_UP:    ret = translation->key_up[index].first;     break;
-               case KJS_JOG:       ret = translation->jog[index].first;        break;
-               case KJS_SHUTTLE:   ret = translation->shuttles[index].first;   break;
+               case KJS_KEY_DOWN:  ret = translation->key_down[index].first;     break;
+               case KJS_KEY_UP:    ret = translation->key_up[index].first;       break;
+               case KJS_JOG:       ret = translation->jog[index].first;          break;
+               case KJS_SHUTTLE:   ret = translation->shuttles[index-S_7].first; break;
                }
        }
        return ret;
@@ -471,17 +557,15 @@ void Shuttle::key(unsigned short code, unsigned int value)
 }
 
 
-void Shuttle:: shuttle(int value)
+void Shuttle::shuttle(int value)
 {
        if( value < S_7 || value > S7 ) {
                fprintf(stderr, "shuttle(%d) out of range\n", value);
                return;
        }
-       gettimeofday(&last_shuttle, 0);
-       need_synthetic_shuttle = value != 0;
-       if( value != shuttlevalue ) {
+       if( value != (int)shuttlevalue ) {
                shuttlevalue = value;
-               send_stroke_sequence(KJS_SHUTTLE, value+7);
+               send_stroke_sequence(KJS_SHUTTLE, value);
        }
 }
 
@@ -495,29 +579,14 @@ void Shuttle:: shuttle(int value)
 // event either!
 void Shuttle::jog(unsigned int value)
 {
-       int direction;
-       struct timeval now;
-       struct timeval delta;
-
-       // We should generate a synthetic event for the shuttle going
-       // to the home position if we have not seen one recently
-       if( need_synthetic_shuttle ) {
-               gettimeofday( &now, 0 );
-               timersub( &now, &last_shuttle, &delta );
-
-               if( delta.tv_sec >= 1 || delta.tv_usec >= 5000 ) {
-                       shuttle(0);
-                       need_synthetic_shuttle = 0;
-               }
-       }
-
        if( jogvalue != 0xffff ) {
                value = value & 0xff;
-               direction = ((value - jogvalue) & 0x80) ? -1 : 1;
+               int direction = ((value - jogvalue) & 0x80) ? -1 : 1;
+               int index = direction > 0 ? 1 : 0;
                while( jogvalue != value ) {
                        // driver fails to send an event when jogvalue == 0
                        if( jogvalue != 0 ) {
-       send_stroke_sequence(KJS_JOG, direction > 0 ? 1 : 0);
+                               send_stroke_sequence(KJS_JOG, index);
                        }
                        jogvalue = (jogvalue + direction) & 0xff;
                }
@@ -540,21 +609,68 @@ void Shuttle::jogshuttle(unsigned short code, unsigned int value)
        }
 }
 
-const char *Shuttle::probe()
+static const struct shuttle_dev {
+       const char *path;
+       unsigned vendor, product;
+} shuttle_devs[] = {
+       { "/dev/input/by-id/usb-Contour_Design_ShuttleXpress-event-if00",
+               0x0b33, 0x0020 },
+       { "/dev/input/by-id/usb-Contour_Design_ShuttlePRO_v2-event-if00",
+               0x0b33, 0x0030 },
+       { "/dev/input/by-id/usb-Contour_Design_ShuttlePro-event-if00",
+               0x0b33, 0x0030 },
+       { "/dev/input/by-id/usb-Contour_Design_ShuttlePRO_v2-event-joystick",
+               0x0b33, 0x0030 },
+};
+
+#ifdef HAVE_SHUTTLE_USB
+void Shuttle::usb_probe(int idx)
+{
+       int ret = libusb_init(0);
+       if( ret < 0 ) return;
+       claimed = 0;
+       const struct shuttle_dev *s = &shuttle_devs[idx];
+       devsh = libusb_open_device_with_vid_pid(0, s->vendor, s->product);
+       if( devsh ) {
+               int sh_iface = SHUTTLE_INTERFACE;
+               libusb_detach_kernel_driver(devsh, sh_iface);
+               ret = libusb_claim_interface(devsh, sh_iface);
+               if( ret >= 0 ) claimed = 1;
+       }
+       if( !claimed )
+               usb_done();
+}
+
+void Shuttle::usb_done()
+{
+       if( devsh ) {
+               if( claimed > 0 ) {
+                       int sh_iface = SHUTTLE_INTERFACE;
+                       libusb_release_interface(devsh, sh_iface);
+                       libusb_attach_kernel_driver(devsh, sh_iface);
+                       claimed = 0;
+               }
+               libusb_close(devsh);
+               devsh = 0;
+       }
+       if( claimed >= 0 ) {
+               libusb_exit(0);
+               claimed = -1;
+       }
+}
+#endif
+
+int Shuttle::probe()
 {
        struct stat st;
-       static const char *shuttle_devs[] = {
-               "/dev/input/by-id/usb-Contour_Design_ShuttleXpress-event-if00",
-               "/dev/input/by-id/usb-Contour_Design_ShuttlePRO_v2-event-if00",
-       };
        int ret = sizeof(shuttle_devs) / sizeof(shuttle_devs[0]);
-       while( --ret >= 0 && stat(shuttle_devs[ret] , &st) );
-       return ret >= 0 ? shuttle_devs[ret] : 0;
+       while( --ret >= 0 && stat(shuttle_devs[ret].path , &st) );
+       return ret;
 }
 
-void Shuttle::start(const char *dev_name)
+void Shuttle::start(int idx)
 {
-       this->dev_name = dev_name;
+       this->dev_index = idx;
        first_time = 1;
        done = 0;
        Thread::start();
@@ -569,6 +685,14 @@ void Shuttle::stop()
        }
 }
 
+BC_WindowBase *Shuttle::owns(BC_WindowBase *wdw, Window win)
+{
+       if( wdw->win == win ) return wdw;
+       if( (wdw=wdw->top_level)->win == win ) return wdw;
+       for( int i=wdw->popups.size(); --i>=0; )
+               if( wdw->popups[i]->win == win ) return wdw;
+       return 0;
+}
 
 int Shuttle::get_focused_window_translation()
 {
@@ -576,7 +700,7 @@ int Shuttle::get_focused_window_translation()
        Display *dpy = gui->display;
        Window focus = 0;
        int ret = 0, revert = 0;
-       char win_title[BCTEXTLEN];
+       char win_title[BCTEXTLEN];  win_title[0] = 0;
        gui->lock_window("Shuttle::get_focused_window_translation");
        XGetInputFocus(dpy, &focus, &revert);
        if( last_focused != focus ) {
@@ -592,7 +716,7 @@ int Shuttle::get_focused_window_translation()
                        win_title[len] = 0;
                        XFree(list);
                        if( debug )
-                               printf("new focus: %08x\n", (unsigned)focus);
+                               printf("new focus: %08x %s\n", (unsigned)focus, win_title);
                }
                else {
                        last_focused = 0;
@@ -610,13 +734,17 @@ int Shuttle::get_focused_window_translation()
        this->msk = 0;
        BC_WindowBase *wdw = 0;
        int cin = -1;
-       if( (wdw=mwindow->gui) && wdw->win == focus )
+       if( (wdw=owns(mwindow->gui, focus)) != 0 )
                cin = FOCUS_MWINDOW;
-       else if( (wdw=mwindow->awindow->gui) && wdw->win == focus )
+       else if( (wdw=owns(mwindow->awindow->gui, focus)) != 0 )
                cin = FOCUS_AWINDOW;
-       else if( (wdw=mwindow->cwindow->gui) && wdw->win == focus )
+       else if( (wdw=owns(mwindow->cwindow->gui, focus)) != 0 ) {
+               if( mwindow->cwindow->gui->canvas->get_fullscreen() )
+                       wdw = mwindow->cwindow->gui->canvas->get_canvas();
                cin = FOCUS_CWINDOW;
-       else if( (wdw=mwindow->gui->mainmenu->load_file->thread->window) &&
+       }
+       else if( mwindow->gui->mainmenu->load_file->thread->running() &&
+                (wdw=mwindow->gui->mainmenu->load_file->thread->window) != 0 &&
                 wdw->win == focus )
                cin = FOCUS_LOAD;
        else {
@@ -624,7 +752,9 @@ int Shuttle::get_focused_window_translation()
                while( --i >= 0 ) {
                        VWindow *vwdw =  mwindow->vwindows[i];
                        if( !vwdw->is_running() ) continue;
-                       if( (wdw=vwdw->gui) && wdw->win == focus ) {
+                       if( (wdw=owns(vwdw->gui, focus)) != 0 ) {
+                               if( vwdw->gui->canvas->get_fullscreen() )
+                                       wdw = vwdw->gui->canvas->get_canvas();
                                cin = FOCUS_VIEWER;  break;
                        }
                }
@@ -634,7 +764,7 @@ int Shuttle::get_focused_window_translation()
        int root_x = 0, root_y = 0, win_x = 0, win_y = 0, x = 0, y = 0;
        unsigned int mask = 0, width = 0, height = 0, border_width = 0, depth = 0;
        wdw->lock_window("Shuttle::get_focused_window_translation 1");
-       if( XQueryPointer(wdw->display, focus, &root, &child,
+       if( XQueryPointer(wdw->top_level->display, focus, &root, &child,
                        &root_x, &root_y, &win_x, &win_y, &mask) ) {
                if( !child ) {
                        if( wdw->active_menubar )
@@ -643,9 +773,11 @@ int Shuttle::get_focused_window_translation()
                                child = wdw->active_popup_menu->win;
                        else if( wdw->active_subwindow )
                                child = wdw->active_subwindow->win;
+                       else
+                               child = wdw->win;
                }
-               if( child )
-                       XGetGeometry(wdw->display, child, &root, &x, &y,
+               else
+                       XGetGeometry(wdw->top_level->display, child, &root, &x, &y,
                                &width, &height, &border_width, &depth);
        }
        wdw->unlock_window();
@@ -662,29 +794,32 @@ int Shuttle::get_focused_window_translation()
                if( tr->is_default ) return 1;
                for( int i=0; i<tr->names.size(); ++i ) {
                        TransName *name = tr->names[i];
-                       if( name->cin != cin ) continue;
-                       if( regexec(&name->regex, win_title, 0, NULL, 0) )
-                               return 1;
+                       if( name->cin == cin ) return 1;
                }
        }
        tr = default_translation;
        return 0;
 }
 
-void Shuttle::handle_event()
+int Shuttle::load_translation()
 {
-       if( read_config_file() > 0 ) {
-               done = 1;
-               return;
-       }
+       if( read_config_file() > 0 )
+               return done = 1;
        if( get_focused_window_translation() < 0 )
-               return;
+               return 0;
        if( last_translation != tr ) {
                last_translation = tr;
                if( debug )
                        printf("new translation: %s\n", tr->name);
        }
-//fprintf(stderr, "event: (%d, %d, 0x%x)\n", ev.type, ev.code, ev.value);
+       return 0;
+}
+
+void Shuttle::handle_event()
+{
+       if( load_translation() ) return;
+//     if( debug )
+//             printf("event: (%d, %d, 0x%x)\n", ev.type, ev.code, ev.value);
        switch( ev.type ) {
        case EVENT_TYPE_DONE:
        case EVENT_TYPE_ACTIVE_KEY:
@@ -703,7 +838,55 @@ void Shuttle::handle_event()
 
 void Shuttle::run()
 {
-       for( enable_cancel(); !done; sleep(1) ) {
+       if( dev_index < 0 ) return;
+       const char *dev_name = shuttle_devs[dev_index].path;
+
+#ifdef HAVE_SHUTTLE_USB
+       if( usb_direct )
+               usb_probe(dev_index);
+
+       disable_cancel();
+       while( devsh && !done ) {
+               int len = 0;
+               static const int IN_ENDPOINT = 0x81;
+               unsigned char dat[5];
+               int ret = libusb_interrupt_transfer(devsh,
+                               IN_ENDPOINT, dat, sizeof(dat), &len, 100);
+               if( ret != 0 ) {
+                       if( ret == LIBUSB_ERROR_TIMEOUT ) continue;
+                       printf("shuttle: %s\n  %s\n",
+                               dev_name, libusb_strerror((libusb_error)ret));
+                       sleep(1);  continue;
+               }
+               if( load_translation() ) break;
+               if( debug ) {
+                       printf("shuttle: ");
+                       for( int i=0; i<len; ++i ) printf(" %02x", dat[i]);
+                       printf("\n");
+               }
+               if( last_shuttle != dat[0] )
+                       shuttle((char)(last_shuttle = dat[0]));
+
+               if( last_jog != dat[1] )
+                       jog(last_jog = dat[1]);
+
+               unsigned btns = (dat[4]<<8) | dat[3];
+               unsigned dif = last_btns ^ btns;
+               if( dif ) {
+                       last_btns = btns;
+                       for( int i=0; i<15; ++i ) {
+                               unsigned msk = 1 << i;
+                               if( !(dif & msk) ) continue;
+                               key(i+EVENT_CODE_KEY1, btns & msk ? 1 : 0);
+                       }
+               }
+       }
+       usb_done();
+#endif
+       usb_direct = 0;
+       enable_cancel();
+
+       for( ; !done; sleep(1) ) {
                fd = open(dev_name, O_RDONLY);
                if( fd < 0 ) {
                        perror(dev_name);
@@ -762,6 +945,7 @@ int Shuttle::read_config_file()
        config_mtime = st.st_mtime;
        translations.clear();
        debug = 0;
+       usb_direct = 0;
 #define ws(ch) (ch==' ' || ch=='\t')
        char line[BCTEXTLEN], *cp;
        Translation *trans = 0;
@@ -804,9 +988,7 @@ int Shuttle::read_config_file()
                                }
                        }
                        while( ws(*cp) ) ++cp;
-// regex in TransName constructor
                        trans->names.append(new TransName(cin, name, cp));
-                       if( trans->names.last()->err ) { ret = 1;  break; }
                        ret = fgets(cp=line,sizeof(line),fp) ? 0 : 1;
                        if( ret ) {
                                fprintf(stderr, "hit eof, no translation def for: %s\n",
@@ -835,6 +1017,9 @@ int Shuttle::read_config_file()
                        if( !strcmp(tok, "DEBUG") ) {
                                debug = 1;  goto skip;
                        }
+                       if( !strcmp(tok, "USB_DIRECT") ) {
+                               usb_direct = 1;  goto skip;
+                       }
                        key = tok;
                        if( !trans ) {
                                fprintf(stderr, "no translation section defining key: %s\n", key);
@@ -846,13 +1031,21 @@ int Shuttle::read_config_file()
                                while( ws(*cp) ) ++cp;
                                if( !*cp || *cp == '#' || *cp == '\n' ) break;
                                if( *cp == '"' ) {
-                                       tok = ++cp;
-                                       while( *cp && *cp != '"' && *cp != '\n' ) {
-                                               if( *cp != '\\' ) { ++cp;  continue; }
-                                               for( char *bp=cp; *bp; ++bp ) bp[0] = bp[1];
+                                       while( *cp ) {
+                                               if( *cp == '"' )
+                                                       trans->add_string(cp);
+                                               while( ws(*cp) ) ++cp;
+                                               if( !*cp || *cp == '#' || *cp == '\n' ) break;
+                                               tok = cp;
+                                               while( *cp && !ws(*cp) && *cp != '\n' ) ++cp;
+                                               *cp = 0;
+                                               SKeySym sym = KeySymMapping::to_keysym(tok);
+                                               if( !sym ) {
+                                                       fprintf(stderr, "unknown keysym: %s\n", tok);
+                                                       ret = 1;  break;
+                                               }
+                                               trans->add_keysym(sym, PRESS_RELEASE);
                                        }
-                                       *cp++ = 0;
-                                       trans->add_string(tok);
                                        continue;
                                }
                                tok = cp;