From 2e28d225c343f02500594688bf8d0528df40600c Mon Sep 17 00:00:00 2001 From: Good Guy Date: Mon, 11 Feb 2019 15:16:20 -0700 Subject: [PATCH] add usb_direct for shuttle, revised shuttle again, titler tweak, transportque design work --- cinelerra-5.1/bld.sh | 2 +- cinelerra-5.1/cinelerra/canvas.C | 18 +- cinelerra-5.1/cinelerra/canvas.h | 1 + cinelerra-5.1/cinelerra/cwindow.C | 15 +- cinelerra-5.1/cinelerra/mwindow.C | 13 +- cinelerra-5.1/cinelerra/playbackengine.C | 88 ++++--- cinelerra-5.1/cinelerra/playbackengine.h | 9 +- cinelerra-5.1/cinelerra/shuttle.C | 257 ++++++++++++++++---- cinelerra-5.1/cinelerra/shuttle.h | 62 +++-- cinelerra-5.1/cinelerra/transportque.C | 7 +- cinelerra-5.1/cinelerra/transportque.h | 3 +- cinelerra-5.1/cinelerra/vdevicex11.C | 60 +---- cinelerra-5.1/cinelerra/vdevicex11.h | 2 - cinelerra-5.1/cinelerra/zwindow.C | 8 +- cinelerra-5.1/configure.ac | 7 +- cinelerra-5.1/doc/99-ShuttlePRO.rules | 2 + cinelerra-5.1/guicast/bccapture.C | 92 +++++-- cinelerra-5.1/guicast/bccapture.h | 7 + cinelerra-5.1/plugins/titler/titlerwindow.C | 2 +- cinelerra-5.1/shuttlerc | 67 +++-- 20 files changed, 492 insertions(+), 230 deletions(-) diff --git a/cinelerra-5.1/bld.sh b/cinelerra-5.1/bld.sh index 7c8837bf..f09b34a0 100755 --- a/cinelerra-5.1/bld.sh +++ b/cinelerra-5.1/bld.sh @@ -1,6 +1,6 @@ #!/bin/bash ( ./autogen.sh - ./configure --with-single-user --with-booby + ./configure --with-single-user --with-booby --with-shuttle-usb make && make install ) 2>&1 | tee log mv Makefile Makefile.cfg cp Makefile.devel Makefile diff --git a/cinelerra-5.1/cinelerra/canvas.C b/cinelerra-5.1/cinelerra/canvas.C index 2b395300..0997e064 100644 --- a/cinelerra-5.1/cinelerra/canvas.C +++ b/cinelerra-5.1/cinelerra/canvas.C @@ -576,6 +576,22 @@ void Canvas::get_scrollbars(EDL *edl, //printf("Canvas::get_scrollbars 5 %d %d\n", get_xscroll(), get_yscroll()); } + +void Canvas::update_geometry(EDL *edl, int x, int y, int w, int h) +{ + int redraw = 0; + if( this->x != x || this->y != y || + this->w != w || this->h != h ) redraw = 1; + if( !redraw ) { + int vx = x, vy = y, vw = w, vh = h; + get_scrollbars(edl, vx, vy, vw, vh); + if( vx != view_x || vy != view_y || + vw != view_w || vh != view_h ) redraw = 1; + } + if( !redraw ) return; + reposition_window(edl, x, y, w, y); +} + void Canvas::reposition_window(EDL *edl, int x, int y, int w, int h) { this->x = view_x = x; this->y = view_y = y; @@ -596,8 +612,6 @@ void Canvas::reposition_window(EDL *edl, int x, int y, int w, int h) canvas_subwindow->flash(0); } } - - draw_refresh(0); } diff --git a/cinelerra-5.1/cinelerra/canvas.h b/cinelerra-5.1/cinelerra/canvas.h index 9a78095e..19842695 100644 --- a/cinelerra-5.1/cinelerra/canvas.h +++ b/cinelerra-5.1/cinelerra/canvas.h @@ -131,6 +131,7 @@ public: // passing -1 causes automatic size detection int canvas_w = -1, int canvas_h = -1); + void update_geometry(EDL *edl, int x, int y, int w, int h); void reposition_window(EDL *edl, int x, int y, int w, int h); virtual void reset_translation() {}; virtual void close_source() {}; diff --git a/cinelerra-5.1/cinelerra/cwindow.C b/cinelerra-5.1/cinelerra/cwindow.C index 6f29f27b..1611f753 100644 --- a/cinelerra-5.1/cinelerra/cwindow.C +++ b/cinelerra-5.1/cinelerra/cwindow.C @@ -255,20 +255,11 @@ void CWindow::update(int dir, int overlays, int tool_window, int operation, int gui->canvas->update_zoom(mwindow->edl->session->cwindow_xscroll, mwindow->edl->session->cwindow_yscroll, mwindow->edl->session->cwindow_zoom); - gui->canvas->reposition_window(mwindow->edl, - mwindow->theme->ccanvas_x, - mwindow->theme->ccanvas_y, - mwindow->theme->ccanvas_w, - mwindow->theme->ccanvas_h); - - - + gui->canvas->update_geometry(mwindow->edl, + mwindow->theme->ccanvas_x, mwindow->theme->ccanvas_y, + mwindow->theme->ccanvas_w, mwindow->theme->ccanvas_h); gui->unlock_window(); - - - - } int CWindow::update_position(double position) diff --git a/cinelerra-5.1/cinelerra/mwindow.C b/cinelerra-5.1/cinelerra/mwindow.C index 3aa564d1..e6a40ee5 100644 --- a/cinelerra-5.1/cinelerra/mwindow.C +++ b/cinelerra-5.1/cinelerra/mwindow.C @@ -1291,7 +1291,7 @@ void MWindow::handle_mixers(EDL *edl, int command, int wait_tracking, track->record = track->play = 0; } zwindow->change_source(mixer_edl); - zwindow->handle_mixer(command, wait_tracking, + zwindow->handle_mixer(command, 0, use_inout, toggle_audio, loop_play, speed); } zwindows_lock->unlock(); @@ -1589,10 +1589,15 @@ void MWindow::init_exportedl() void MWindow::init_shuttle() { #ifdef HAVE_SHUTTLE - const char *dev_name = Shuttle::probe(); - if( dev_name ) { + int ret = Shuttle::probe(); + if( ret >= 0 ) { shuttle = new Shuttle(this); - shuttle->start(dev_name); + if( shuttle->read_config_file() > 0 ) { + printf("shuttle: bad config file\n"); + delete shuttle; shuttle = 0; + return; + } + shuttle->start(ret); } #endif } diff --git a/cinelerra-5.1/cinelerra/playbackengine.C b/cinelerra-5.1/cinelerra/playbackengine.C index 42f77cfd..39fdaa96 100644 --- a/cinelerra-5.1/cinelerra/playbackengine.C +++ b/cinelerra-5.1/cinelerra/playbackengine.C @@ -56,9 +56,12 @@ PlaybackEngine::PlaybackEngine(MWindow *mwindow, Canvas *output) command->command = STOP; next_command = new TransportCommand(); next_command->change_type = CHANGE_ALL; - curr_command = new TransportCommand(); stop_command = new TransportCommand(); stop_command->command = STOP; + stop_command->realtime = 1; + sent_command = new TransportCommand(); + sent_command->command = -1; + sent_lock = new Mutex("PlaybackEngine::sent"); tracking_lock = new Mutex("PlaybackEngine::tracking_lock"); renderengine_lock = new Mutex("PlaybackEngine::renderengine_lock"); tracking_done = new Condition(1, "PlaybackEngine::tracking_done"); @@ -74,7 +77,7 @@ PlaybackEngine::PlaybackEngine(MWindow *mwindow, Canvas *output) PlaybackEngine::~PlaybackEngine() { done = 1; - transport_stop(0); + output_lock->unlock(); Thread::join(); delete preferences; delete_render_engine(); @@ -88,7 +91,8 @@ PlaybackEngine::~PlaybackEngine() delete command; delete next_command; delete stop_command; - delete curr_command; + delete sent_command; + delete sent_lock; delete input_lock; delete output_lock; } @@ -161,16 +165,14 @@ void PlaybackEngine::create_cache() void PlaybackEngine::perform_change() { - switch( command->change_type ) - { + switch( command->change_type ) { case CHANGE_ALL: create_cache(); case CHANGE_EDL: create_render_engine(); + break; case CHANGE_PARAMS: - if(command->change_type != CHANGE_EDL && - (uint32_t)command->change_type != CHANGE_ALL) - render_engine->get_edl()->synchronize_params(command->get_edl()); + render_engine->get_edl()->synchronize_params(command->get_edl()); case CHANGE_NONE: break; } @@ -364,23 +366,27 @@ void PlaybackEngine::run() while( !done ) { // Wait for current command to finish output_lock->lock("PlaybackEngine::run"); -//printf("PlaybackEngine::run 0 %d\n", curr_command->command); - if( curr_command->command < 0 ) continue; -// this covers a glitch that occurs when stop is asserted -// when the render_engine starting, but not initialized - if( curr_command->command == STOP && render_engine ) - render_engine->interrupt_playback(); - wait_render_engine(); + if( done ) break; // Read the new command - input_lock->lock("PlaybackEngine::run"); - command->copy_from(curr_command); - curr_command->command = -1; - input_lock->unlock(); - if( done ) break; + sent_lock->lock("PlaybackEngine::run"); + int command = sent_command->command; + if( command >= 0 ) { + this->command->copy_from(sent_command); +//printf("sent command=%d\n", sent_command->command); + sent_command->command = -1; + if( sent_command->locked ) + input_lock->unlock(); + } + sent_lock->unlock(); + if( command < 0 ) continue; + + interrupt_playback(0); + wait_render_engine(); + //printf("PlaybackEngine::run 1 %d\n", command->command); - switch( command->command ) { + switch( command ) { // Parameter change only case COMMAND_NONE: perform_change(); @@ -442,9 +448,11 @@ void PlaybackEngine::send_command(int command, EDL *edl, int wait_tracking, int int do_stop = 0, do_resume = 0; int curr_command = this->command->command; int curr_single_frame = TransportCommand::single_frame(curr_command); + int curr_direction = TransportCommand::get_direction(curr_command); int curr_audio = this->command->toggle_audio ? !curr_single_frame : curr_single_frame; int single_frame = TransportCommand::single_frame(command); + int direction = TransportCommand::get_direction(command); int next_audio = next_command->toggle_audio ? !single_frame : single_frame; // Dispatch command @@ -468,7 +476,7 @@ void PlaybackEngine::send_command(int command, EDL *edl, int wait_tracking, int // Resume or change direction switch( curr_command ) { default: - transport_stop(wait_tracking); + transport_stop(curr_direction != direction ? 1 : 0); do_resume = 1; // fall through case STOP: @@ -500,18 +508,25 @@ void PlaybackEngine::send_command(int command, EDL *edl, int wait_tracking, int int PlaybackEngine::transport_stop(int wait_tracking) { - input_lock->lock("PlaybackEngine::transport_stop 1"); - curr_command->copy_from(stop_command); - interrupt_playback(wait_tracking); -//printf("send: %d (STOP)\n", STOP); - input_lock->unlock(); + interrupt_playback(0); + input_lock->lock("PlaybackEngine::transport_stop"); + sent_lock->lock("PlaybackEngine::transport_stop"); + sent_command->copy_from(stop_command); + sent_command->locked = wait_tracking ? 1 : 0; + if( !sent_command->locked ) + input_lock->unlock(); + sent_lock->unlock(); output_lock->unlock(); + if( wait_tracking ) { + tracking_done->lock("PlaybackEngine::transport_stop"); + tracking_done->unlock(); + } +//printf("send: %d (STOP) 0\n", STOP); return 0; } int PlaybackEngine::transport_command(int command, int change_type, EDL *new_edl, int use_inout) { - input_lock->lock("PlaybackEngine::transport_command 1"); next_command->command = command; next_command->change_type |= change_type; if( new_edl ) { @@ -530,15 +545,24 @@ int PlaybackEngine::transport_command(int command, int change_type, EDL *new_edl next_command->set_playback_range(new_edl, use_inout, preferences->forward_render_displacement); } - curr_command->copy_from(next_command); + + input_lock->lock("PlaybackEngine::transport_command"); + next_command->locked = + next_command->change_type == CHANGE_NONE || + next_command->change_type == CHANGE_PARAMS ? 0 : 1; + sent_lock->lock("PlaybackEngine::transport_command"); + sent_command->copy_from(next_command); + if( !sent_command->locked ) + input_lock->unlock(); next_command->reset(); + sent_lock->unlock(); + output_lock->unlock(); //static const char *types[] = { "NONE", // "FRAME_FWD", "NORMAL_FWD", "FAST_FWD", "FRAME_REV", "NORMAL_REV", "FAST_REV", // "STOP", "PAUSE", "SLOW_FWD", "SLOW_REV", "REWIND", "GOTO_END", "CURRENT_FRAME", // "LAST_FRAME" }; -//printf("send= %d (%s)\n", command, types[command]); - input_lock->unlock(); - output_lock->unlock(); +//printf("send= %d (%s) %d\n", sent_command->command, +// types[sent_command->command], sent_command->locked); return 0; } diff --git a/cinelerra-5.1/cinelerra/playbackengine.h b/cinelerra-5.1/cinelerra/playbackengine.h index 73ef329c..85fc00ad 100644 --- a/cinelerra-5.1/cinelerra/playbackengine.h +++ b/cinelerra-5.1/cinelerra/playbackengine.h @@ -115,10 +115,11 @@ public: EDL *new_edl=0, int use_inout=0); Condition *input_lock, *output_lock; -// active command - TransportCommand *command; -// stop, next, queued commands - TransportCommand *stop_command, *next_command, *curr_command; + Mutex *sent_lock; +// active command, stop command + TransportCommand *command, *stop_command; +// next command under construction, last sent command + TransportCommand *next_command, *sent_command; // Render engine RenderEngine *render_engine; diff --git a/cinelerra-5.1/cinelerra/shuttle.C b/cinelerra-5.1/cinelerra/shuttle.C index b3049c5d..22adeaf0 100644 --- a/cinelerra-5.1/cinelerra/shuttle.C +++ b/cinelerra-5.1/cinelerra/shuttle.C @@ -52,7 +52,28 @@ 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; @@ -70,15 +91,26 @@ KeySym KeySymMapping::to_keysym(const char *str) 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.); @@ -157,14 +189,14 @@ void Translation::clear() for( int i=0; iappend(); 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 ) { @@ -199,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; @@ -211,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); @@ -225,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) @@ -306,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"); } @@ -321,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; imwindow = mwindow; @@ -390,15 +426,26 @@ Shuttle::Shuttle(MWindow *mwindow) wx = wy = 0; jogvalue = 0xffff; shuttlevalue = 0xffff; - 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; @@ -434,15 +481,15 @@ int Shuttle::send_button(unsigned int button, int press) wdw->top_level->put_event((XEvent *) b); return 0; } -int Shuttle::send_keycode(unsigned int keycode, int press, int send) +int Shuttle::send_keycode(unsigned key, unsigned msk, int press, int send) { if( debug ) { const char *cp = !send ? 0 : - KeySymMapping::to_string(keycode); + KeySymMapping::to_string(SKeySym(key, msk)); if( cp ) printf("key: %s %d\n", cp, press); else - printf("key: %04x %d\n", keycode, press); + printf("key: %04x/%04x %d\n", key, msk, press); } XKeyEvent *k = new XKeyEvent(); memset(k, 0, sizeof(*k)); @@ -457,17 +504,17 @@ int Shuttle::send_keycode(unsigned int keycode, int press, int send) 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_keycode((unsigned int)keysym, press, 1); + send_keycode(keysym.key, keysym.msk, press, 1); // unsigned int keycode = XKeysymToKeycode(wdw->top_level->display, keysym); // return send_keycode(keycode, press, 0); } @@ -562,22 +609,66 @@ 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 }, +}; + +#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", - "/dev/input/by-id/usb-Contour_Design_ShuttlePro-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(); @@ -708,19 +799,23 @@ int Shuttle::get_focused_window_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); } + 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 ) { @@ -741,7 +836,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[BCSTRLEN]; + 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; iadd_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; diff --git a/cinelerra-5.1/cinelerra/shuttle.h b/cinelerra-5.1/cinelerra/shuttle.h index 0f07e8ea..46283dfc 100644 --- a/cinelerra-5.1/cinelerra/shuttle.h +++ b/cinelerra-5.1/cinelerra/shuttle.h @@ -10,6 +10,10 @@ #include #include +#ifdef HAVE_SHUTTLE_USB +#include +#endif + // Copyright 2013 Eric Messick (FixedImagePhoto.com/Contact) // reworked 2019 for cinelerra-gg by William Morrow (aka goodguy) @@ -18,6 +22,7 @@ #define DELAY CurrentTime // playback max speed -64x .. 64x #define SHUTTLE_MAX_SPEED 64. +#define SHUTTLE_INTERFACE 0 // protocol for events from the shuttlepro HUD device // @@ -78,19 +83,34 @@ enum { JL=0,JR }; #define FOCUS_VIEWER 4 #define FOCUS_LOAD 5 +class SKeySym { +public: + union { + struct { uint32_t key, msk; }; + uint64_t v; + }; + SKeySym() { v = 0; } + SKeySym(int k) { key = k; msk = 0; } + SKeySym(unsigned k, unsigned m) { key = k; msk = m; } + bool operator ==(SKeySym &ks) { return ks.v == v; } + operator int() { return key; } +}; + class KeySymMapping { public: - static KeySym to_keysym(const char *str); - static const char *to_string(KeySym ks); + static int get_mask(const char *&str); + static SKeySym to_keysym(const char *str); + static const char *to_string(SKeySym ks); static KeySymMapping key_sym_mapping[]; + const char *str; - KeySym sym; + SKeySym sym; }; class Stroke : public ListItem { public: - KeySym keysym; + SKeySym keysym; int press; // 1:press, 0:release }; @@ -100,7 +120,7 @@ public: Strokes() {} ~Strokes() {} void clear() { while( last ) delete last; } - void add_stroke(KeySym keysym, int press=1) { + void add_stroke(SKeySym keysym, int press=1) { Stroke *s = append(); s->keysym = keysym; s->press = press; } @@ -113,8 +133,8 @@ public: Modifiers(Translation *trans) { this->trans = trans; } ~Modifiers() {} - void mark_as_down(KeySym sym, int hold); - void mark_as_up(KeySym sym); + void mark_as_down(SKeySym sym, int hold); + void mark_as_up(SKeySym sym); void release(int allkeys); void re_press(); }; @@ -144,11 +164,11 @@ public: ~Translation(); void init(int def); void clear(); - void append_stroke(KeySym sym, int press); + void append_stroke(SKeySym sym, int press); void add_release(int all_keys); void add_keystroke(const char *keySymName, int press_release); - void add_keysym(KeySym sym, int press_release); - void add_string(const char *str); + void add_keysym(SKeySym sym, int press_release); + void add_string(char *&str); int start_line(const char *key); void print_strokes(const char *name, const char *up_dn, Strokes *strokes); void print_stroke(Stroke *s); @@ -162,7 +182,7 @@ public: int first_release_stroke; Strokes *pressed, *released; Strokes *pressed_strokes, *released_strokes; - KeySym keysym_down; + SKeySym keysym_down; Strokes key_down[NUM_KEYS]; Strokes key_up[NUM_KEYS]; @@ -183,7 +203,15 @@ class Shuttle : public Thread { int fd; unsigned short jogvalue, shuttlevalue; - const char *dev_name; + int dev_index; +#ifdef HAVE_SHUTTLE_USB + struct libusb_device_handle *devsh; + void usb_probe(int idx); + void usb_done(); + unsigned last_jog, last_shuttle, last_btns; + int claimed; +#endif + Translation *default_translation; Translations translations; public: @@ -191,18 +219,19 @@ public: ~Shuttle(); int send_button(unsigned int button, int press); - int send_keycode(unsigned int keycode, int press, int send); - int send_keysym(KeySym keysym, int press); + int send_keycode(unsigned key, unsigned msk, int press, int send); + int send_keysym(SKeySym keysym, int press); void send_stroke_sequence(int kjs, int index); void key(unsigned short code, unsigned int value); void shuttle(int value); void jog(unsigned int value); void jogshuttle(unsigned short code, unsigned int value); - void start(const char *dev_name); + void start(int idx); void stop(); void handle_event(); + int load_translation(); int get_focused_window_translation(); - static const char *probe(); + static int probe(); void run(); int read_config_file(); static BC_WindowBase *owns(BC_WindowBase *wdw, Window win); @@ -211,6 +240,7 @@ public: int failed; int first_time; int debug; + int usb_direct; MWindow *mwindow; Translation *tr, *last_translation; diff --git a/cinelerra-5.1/cinelerra/transportque.C b/cinelerra-5.1/cinelerra/transportque.C index 318b400b..5021f462 100644 --- a/cinelerra-5.1/cinelerra/transportque.C +++ b/cinelerra-5.1/cinelerra/transportque.C @@ -47,19 +47,19 @@ TransportCommand::~TransportCommand() void TransportCommand::reset() { + command = COMMAND_NONE; + change_type = 0; playbackstart = 0; start_position = 0; end_position = 0; infinite = 0; realtime = 0; resume = 0; + locked = 0; toggle_audio = 0; loop_play = 0; displacement = 0; speed = 0; -// Don't reset the change type for commands which don't perform the change - if(command != STOP) change_type = 0; - command = COMMAND_NONE; } EDL* TransportCommand::get_edl() @@ -90,6 +90,7 @@ void TransportCommand::copy_from(TransportCommand *command) this->playbackstart = command->playbackstart; this->realtime = command->realtime; this->resume = command->resume; + this->locked = command->locked; this->toggle_audio = command->toggle_audio; this->loop_play = command->loop_play; this->displacement = command->displacement; diff --git a/cinelerra-5.1/cinelerra/transportque.h b/cinelerra-5.1/cinelerra/transportque.h index 351cf682..fd5a5cc4 100644 --- a/cinelerra-5.1/cinelerra/transportque.h +++ b/cinelerra-5.1/cinelerra/transportque.h @@ -55,7 +55,6 @@ public: void delete_edl(); void new_edl(); - PlaybackEngine *engine; int command; int change_type; // playback range @@ -67,6 +66,8 @@ public: int displacement; // Send output to device int realtime; +// command must execute + int locked; // Use persistant starting point int resume; // reverse audio duty diff --git a/cinelerra-5.1/cinelerra/vdevicex11.C b/cinelerra-5.1/cinelerra/vdevicex11.C index ea8720db..362777ff 100644 --- a/cinelerra-5.1/cinelerra/vdevicex11.C +++ b/cinelerra-5.1/cinelerra/vdevicex11.C @@ -77,10 +77,6 @@ int VDeviceX11::reset_parameters() capture_bitmap = 0; color_model_selected = 0; is_cleared = 0; - - for( int i = 0; i < SCREENCAP_BORDERS; i++ ) { - screencap_border[i] = 0; - } return 0; } @@ -91,33 +87,9 @@ int VDeviceX11::open_input() device->in_config->h, device->in_config->screencapture_display); //printf("VDeviceX11::open_input 2\n"); - -// create overlay - device->mwindow->gui->lock_window("VDeviceX11::close_all"); - - screencap_border[0] = new BC_Popup(device->mwindow->gui, - device->input_x - SCREENCAP_PIXELS, device->input_y - SCREENCAP_PIXELS, - device->in_config->w + SCREENCAP_PIXELS * 2, SCREENCAP_PIXELS, - SCREENCAP_COLOR, 1); - screencap_border[1] = new BC_Popup(device->mwindow->gui, - device->input_x - SCREENCAP_PIXELS, device->input_y, - SCREENCAP_PIXELS, device->in_config->h, - SCREENCAP_COLOR, 1); - screencap_border[2] = new BC_Popup(device->mwindow->gui, - device->input_x - SCREENCAP_PIXELS, device->input_y + device->in_config->h, - device->in_config->w + SCREENCAP_PIXELS * 2, SCREENCAP_PIXELS, - SCREENCAP_COLOR, 1); - screencap_border[3] = new BC_Popup(device->mwindow->gui, - device->input_x + device->in_config->w, device->input_y, - SCREENCAP_PIXELS, device->in_config->h, - SCREENCAP_COLOR, 1); -usleep(500000); // avoids a bug in gnome-shell 2017/10/19 - - for( int i=0; ishow_window(0); - - device->mwindow->gui->flush(); - device->mwindow->gui->unlock_window(); + capture_bitmap->bars_on(SCREENCAP_PIXELS, SCREENCAP_COLOR, + device->input_x, device->input_y, + device->in_config->w, device->in_config->h); return 0; } @@ -186,15 +158,6 @@ int VDeviceX11::close_all() output->unlock_canvas(); } - if( device->mwindow ) { - device->mwindow->gui->lock_window("VDeviceX11::close_all"); - for( int i=0; imwindow->gui->unlock_window(); - } - reset_parameters(); return 0; @@ -202,20 +165,9 @@ int VDeviceX11::close_all() int VDeviceX11::read_buffer(VFrame *frame) { -//printf("VDeviceX11::read_buffer %d colormodel=%d\n", __LINE__, frame->get_color_model()); - device->mwindow->gui->lock_window("VDeviceX11::close_all"); - - screencap_border[0]->reposition_window(device->input_x - SCREENCAP_PIXELS, - device->input_y - SCREENCAP_PIXELS); - screencap_border[1]->reposition_window(device->input_x - SCREENCAP_PIXELS, - device->input_y); - screencap_border[2]->reposition_window(device->input_x - SCREENCAP_PIXELS, - device->input_y + device->in_config->h); - screencap_border[3]->reposition_window(device->input_x + device->in_config->w, - device->input_y); - device->mwindow->gui->flush(); - device->mwindow->gui->unlock_window(); - + capture_bitmap->bars_reposition( + device->input_x, device->input_y, + device->in_config->w, device->in_config->h); capture_bitmap->capture_frame(frame, device->input_x, device->input_y, device->do_cursor); diff --git a/cinelerra-5.1/cinelerra/vdevicex11.h b/cinelerra-5.1/cinelerra/vdevicex11.h index afc21edb..223c474b 100644 --- a/cinelerra-5.1/cinelerra/vdevicex11.h +++ b/cinelerra-5.1/cinelerra/vdevicex11.h @@ -133,10 +133,8 @@ private: int get_display_colormodel(int file_colormodel); // windows which overlay the screencap area -#define SCREENCAP_BORDERS 4 #define SCREENCAP_PIXELS 5 #define SCREENCAP_COLOR GREEN - BC_Popup *screencap_border[SCREENCAP_BORDERS]; // Bitmap to be written to device BC_Bitmap *bitmap; diff --git a/cinelerra-5.1/cinelerra/zwindow.C b/cinelerra-5.1/cinelerra/zwindow.C index 6508b9e4..a6f6d73f 100644 --- a/cinelerra-5.1/cinelerra/zwindow.C +++ b/cinelerra-5.1/cinelerra/zwindow.C @@ -218,12 +218,12 @@ void ZWindow::handle_close_event(int result) void ZWindow::change_source(EDL *edl) { - if( this->edl && edl != this->edl ) + if( this->edl == edl ) return; + if( !edl || !this->edl || this->edl->equivalent_output(edl) >= 0 ) + zgui->playback_engine->refresh_frame(CHANGE_ALL, edl); + if( this->edl ) this->edl->remove_user(); this->edl = edl; - if( edl != 0 ) { - zgui->playback_engine->refresh_frame(CHANGE_ALL, edl); - } } void ZWindow::stop_playback(int wait) diff --git a/cinelerra-5.1/configure.ac b/cinelerra-5.1/configure.ac index c2211b32..0a6cd86b 100644 --- a/cinelerra-5.1/configure.ac +++ b/cinelerra-5.1/configure.ac @@ -54,6 +54,7 @@ CHECK_WITH([libzmpeg],[build libzmpeg],[LIBZMPEG],[yes]) CHECK_WITH([commercial],[enable commercial capture],[COMMERCIAL],[yes]) CHECK_WITH([thirdparty],[use thirdparty build],[CIN_3RDPARTY],[yes]) CHECK_WITH([shuttle],[shuttle device],[SHUTTLE],[yes]) +CHECK_WITH([shuttle-usb],[use libusb for shuttle],[SHUTTLE_USB],[no]) if test "x$WANT_LV2" != "xno"; then GTK2_LIBS=`pkg-config --libs gtk+-2.0` @@ -823,7 +824,7 @@ if test "x$WANT_CIN_3RDPARTY" != "xno"; then fi for v in GL XFT XXF86VM OSS ALSA FIREWIRE DV DVB \ VIDEO4LINUX2 ESOUND PACTL OPENEXR LV2 \ - COMMERCIAL LIBZMPEG SHUTTLE; do + COMMERCIAL LIBZMPEG SHUTTLE SHUTTLE_USB; do eval vv="\$WANT_$v" if test "x$vv" != "xno"; then CFG_CFLAGS+=" -DHAVE_$v" @@ -850,6 +851,7 @@ echo " using: with-booby = $WANT_BOOBY" echo " using: with-libzmpeg = $WANT_LIBZMPEG" echo " using: with-commerical = $WANT_COMMERCIAL" echo " using: with-shuttle = $WANT_SHUTTLE" +echo " using: with-shuttle-usb = $WANT_SHUTTLE_USB" echo "" echo " using: thirdparty build = $WANT_CIN_3RDPARTY" echo " using: single-user = $WANT_CINBIN_BUILD" @@ -873,6 +875,9 @@ fi if test "x$WANT_BOOBY" != "xno"; then CFG_CFLAGS+=" -DBOOBY" fi +if test "x$WANT_SHUTTLE_USB" != "xno"; then + EXTRA_LIBS+=' -lusb-1.0' +fi # intel lock elision bugs if test "x$WANT_NOELISION" != "xno"; then diff --git a/cinelerra-5.1/doc/99-ShuttlePRO.rules b/cinelerra-5.1/doc/99-ShuttlePRO.rules index 90584f4b..7362fbe3 100644 --- a/cinelerra-5.1/doc/99-ShuttlePRO.rules +++ b/cinelerra-5.1/doc/99-ShuttlePRO.rules @@ -6,3 +6,5 @@ ATTRS{name}=="Contour Design ShuttlePRO v2" MODE="0644" ATTRS{name}=="Contour Design ShuttleXpress" MODE="0644" ATTRS{name}=="Contour Design ShuttlePro" MODE="0644" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0b33", ATTRS{idProduct}=="0020", MODE="0666" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0b33", ATTRS{idProduct}=="0030", MODE="0666" diff --git a/cinelerra-5.1/guicast/bccapture.C b/cinelerra-5.1/guicast/bccapture.C index e955a527..d8a55590 100644 --- a/cinelerra-5.1/guicast/bccapture.C +++ b/cinelerra-5.1/guicast/bccapture.C @@ -44,26 +44,29 @@ BC_Capture::BC_Capture(int w, int h, const char *display_path) data = 0; use_shm = 1; + for( int i=0; i<4; ++i ) border[i] = 0; + bar_w = 0; bar_color = 0; init_window(display_path); allocate_data(); } - BC_Capture::~BC_Capture() { + bars_off(); delete_data(); XCloseDisplay(display); } + int BC_Capture::init_window(const char *display_path) { int bits_per_pixel; if( display_path && display_path[0] == 0 ) display_path = NULL; if( (display = XOpenDisplay(display_path)) == NULL ) { - printf(_("cannot connect to X server.\n")); - if( getenv("DISPLAY") == NULL ) - printf(_("'DISPLAY' environment variable not set.\n")); - exit(-1); + printf(_("cannot connect to X server.\n")); + if( getenv("DISPLAY") == NULL ) + printf(_("'DISPLAY' environment variable not set.\n")); + exit(-1); return 1; } @@ -75,35 +78,82 @@ int BC_Capture::init_window(const char *display_path) server_byte_order = (XImageByteOrder(display) == MSBFirst) ? 0 : 1; char *data = 0; XImage *ximage; - ximage = XCreateImage(display, - vis, - default_depth, - ZPixmap, - 0, - data, - 16, - 16, - 8, - 0); + ximage = XCreateImage(display, vis, default_depth, + ZPixmap, 0, data, 16, 16, 8, 0); bits_per_pixel = ximage->bits_per_pixel; XDestroyImage(ximage); bitmap_color_model = BC_WindowBase::evaluate_color_model(client_byte_order, server_byte_order, bits_per_pixel); // test shared memory // This doesn't ensure the X Server is on the local host - if( use_shm && !XShmQueryExtension(display) ) { - use_shm = 0; - } + if( use_shm && !XShmQueryExtension(display) ) + use_shm = 0; return 0; } +Window BC_Capture::bar(int x, int y, int w, int h, int color) +{ + unsigned long mask = CWEventMask | CWBackPixel | + CWOverrideRedirect | CWSaveUnder; + XSetWindowAttributes attr; + memset(&attr, 0, sizeof(attr)); + Screen *scr = XDefaultScreenOfDisplay(display); + Window root = RootWindowOfScreen(scr); + Visual *vis = DefaultVisualOfScreen(scr); + int depth = DefaultDepthOfScreen(scr); + attr.background_pixel = color; + attr.override_redirect = True; + attr.save_under = True; + Window win = XCreateWindow(display, root, x,y,w,h, 0,depth, + InputOutput, vis, mask, &attr); + XMapWindow(display, win); + return win; +} + +void BC_Capture::bars_on(int bw, int color, int x, int y, int w, int h) +{ + this->bar_w = bw; + this->bar_color = color; + border[0] = bar(x-bw, y-bw, w+2*bw, bw, color); + border[1] = bar(x-bw, y, bw, h, color); + border[2] = bar(x-bw, y+h, w+2*bw, bw, color); + border[3] = bar(x+w, y, bw, h, color); + XFlush(display); +} + +void BC_Capture::bars_off() +{ + for( int i=0; i<4; ++i ) { + if( !border[i] ) continue; + XUnmapWindow(display, border[i]); + } + for( int i=0; i<4; ++i ) { + if( !border[i] ) continue; + XDestroyWindow(display, border[i]); + border[i] = 0; + } + XFlush(display); +} + +void BC_Capture::bars_reposition(int x, int y, int w, int h) +{ + int bw = this->bar_w; + if( border[0] ) + XMoveResizeWindow(display, border[0], x-bw, y-bw, w+2*bw, bw); + if( border[1] ) + XMoveResizeWindow(display, border[1], x-bw, y, bw, h ); + if( border[2] ) + XMoveResizeWindow(display, border[2], x-bw, y+h, w+2*bw, bw); + if( border[3] ) + XMoveResizeWindow(display, border[3], x+w, y, bw, h ); +} int BC_Capture::allocate_data() { // try shared memory if( !display ) return 1; - if( use_shm ) { - ximage = XShmCreateImage(display, vis, default_depth, ZPixmap, (char*)NULL, &shm_info, w, h); + if( use_shm ) { + ximage = XShmCreateImage(display, vis, default_depth, ZPixmap, (char*)NULL, &shm_info, w, h); shm_info.shmid = shmget(IPC_PRIVATE, h * ximage->bytes_per_line, IPC_CREAT | 0600); if( shm_info.shmid == -1 ) { @@ -118,7 +168,7 @@ int BC_Capture::allocate_data() // Crashes here if remote server. BC_Resources::error = 0; XShmAttach(display, &shm_info); - XSync(display, False); + XSync(display, False); if( BC_Resources::error ) { XDestroyImage(ximage); shmdt(shm_info.shmaddr); diff --git a/cinelerra-5.1/guicast/bccapture.h b/cinelerra-5.1/guicast/bccapture.h index 787fb4a6..817eeb67 100644 --- a/cinelerra-5.1/guicast/bccapture.h +++ b/cinelerra-5.1/guicast/bccapture.h @@ -44,6 +44,13 @@ public: int get_w(); int get_h(); + Window border[4]; + int bar_w, bar_color; + Window bar(int x, int y, int w, int h, int color); + void bars_on(int bw, int color, int x, int y, int w, int h); + void bars_off(); + void bars_reposition(int x, int y, int w, int h); + int w, h, default_depth; unsigned char **row_data; diff --git a/cinelerra-5.1/plugins/titler/titlerwindow.C b/cinelerra-5.1/plugins/titler/titlerwindow.C index 25f456df..6ad7e48d 100644 --- a/cinelerra-5.1/plugins/titler/titlerwindow.C +++ b/cinelerra-5.1/plugins/titler/titlerwindow.C @@ -934,7 +934,7 @@ void TitleWindow::check_style(const char *font_name, int update) TitleFont::TitleFont(TitleMain *client, TitleWindow *window, int x, int y) : BC_PopupTextBox(window, &window->fonts, client->config.font, - x, y, 300, 300, LISTBOX_ICON_LIST) + x, y, 340, 300, LISTBOX_ICON_LIST) { this->client = client; this->window = window; diff --git a/cinelerra-5.1/shuttlerc b/cinelerra-5.1/shuttlerc index ecec4d0b..1f5758f9 100644 --- a/cinelerra-5.1/shuttlerc +++ b/cinelerra-5.1/shuttlerc @@ -1,6 +1,8 @@ # uncomment to enable diagnostics #DEBUG +# uncommet to use direct usb +#USB_DIRECT # redefine default, use # also used for resources,load windows @@ -17,17 +19,26 @@ [Cinelerra] - K5 XK_KP_0 # Stop - K9 XK_KP_3 # Play +# Most useful functions have to be on K5-K9 because Xpress only has 5 keys + K5 XK_Home # Beginning + K6 XK_KP_3 # Play, or if playing Stop + K7 XK_KP_0 # Stop + K8 XK_KP_6 # Reverse, or if playing Stop + K9 XK_End # End + +# K10 "[" # Switch if K14 not working +# K11 "]" # Switch if K15 not working + K10 Alt-XK_Left + K11 Alt-XK_Right K12 XK_Home # Beginning K13 XK_End # End K14 "[" # Toggle in K15 "]" # Toggle out - K1 "i" # Pro Only - Clip - K2 "x" # Pro Only - Cut - K3 "c" # Pro Only - Copy - K4 "v" # Pro Only - Paste + K1 "i" # Clip + K2 "x" # Cut + K3 "c" # Copy + K4 "v" # Paste S-7 REV_16 # Next 6 are reverse keys S-6 REV_8 # the number on the end represents speed @@ -44,23 +55,30 @@ S5 FWD_4 S6 FWD_8 S7 FWD_16 - + JL XK_KP_4 # Frame reverse JR XK_KP_1 # Frame forward [Composer] - K5 XK_KP_0 # Stop - K9 XK_KP_3 # Play +# Most useful functions have to be on K5-K9 because Xpress only has 5 keys + K5 XK_Home # Beginning + K6 XK_KP_3 # Play, or if playing Stop + K7 "f" # Go in or out of Fullscreen mode + K8 XK_KP_6 # Reverse, of if playing Stop + K9 XK_End # End + + K10 "[" # Temporary until K14 Pro fixed + K11 "]" # Temporary until K15 Pro fixed K12 XK_Home # Beginning K13 XK_End # End K14 "[" # Toggle in K15 "]" # Toggle out - K1 "i" # Pro Only - Clip - K2 "x" # Pro Only - Cut - K3 "c" # Pro Only - Copy - K4 "v" # Pro Only - Paste + K1 "i" # Clip + K2 "x" # Cut + K3 "c" # Copy + K4 "v" # Paste S-7 REV_16 S-6 REV_8 @@ -77,24 +95,31 @@ S5 FWD_4 S6 FWD_8 S7 FWD_16 - + JL XK_KP_4 # Frame reverse JR XK_KP_1 # Frame forward [Viewer] - K5 "v" # Splice (Viewer only - button is on Xpress) - K9 "b" # Overwrite (Viewer only - button is on Xpress) +# Most useful functions have to be on K6-K9 because Xpress only has 5 keys + K5 XK_Home # Beginning + K6 XK_KP_3 # Play, or if playing Stop + K7 "f" # Go in or out of Fullscreen mode + K8 XK_KP_6 # Reverse, or if playing Stop + K9 XK_End # End + + K10 "[" # Temporary until K14 Pro fixed + K11 "]" # Temporary until K15 Pro fixed K12 XK_Home # Beginning K13 XK_End # End K14 "[" # Toggle in K15 "]" # Toggle out - K1 "i" # Pro Only - Clip - K2 XK_Home # Pro Only - Beginning - K3 "c" # Pro Only - Copy - K4 XK_End # Pro Only - End - + K1 "i" # Clip + K2 "v" # Splice + K3 "c" # Copy + K4 "b" # Overwrite + S-7 REV_16 S-6 REV_8 S-5 REV_4 -- 2.26.2