diff --git a/CMakeLists.txt b/CMakeLists.txt index 20accb0..c7fa0a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -143,6 +143,10 @@ if(DOTNET) install(FILES ${CS_BINDINGS} DESTINATION "lib") endif() +if(EXISTS /etc/bash_completion.d) + install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/survive_autocomplete.sh DESTINATION "/etc/bash_completion.d") +endif() + install(DIRECTORY include/libsurvive DESTINATION include) file(GLOB REDIST_HEADERS diff --git a/api_example.c b/api_example.c index 817c905..3f77c7e 100644 --- a/api_example.c +++ b/api_example.c @@ -65,7 +65,7 @@ int main(int argc, char **argv) { button_event->button_id); } for (int i = 0; i < button_event->axis_count; i++) { - printf(" %20s (%2d) %8d ", SurviveAxisStr(subtype, button_event->axis_ids[i]), + printf(" %20s (%2d) %+5.4f ", SurviveAxisStr(subtype, button_event->axis_ids[i]), button_event->axis_ids[i], button_event->axis_val[i]); } printf("\n"); diff --git a/bindings/cs/libsurvive.net/cfunctions.cs b/bindings/cs/libsurvive.net/cfunctions.cs index 74f6c15..f0f0e56 100644 --- a/bindings/cs/libsurvive.net/cfunctions.cs +++ b/bindings/cs/libsurvive.net/cfunctions.cs @@ -48,7 +48,7 @@ namespace libsurvive [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public UInt32[] axis_ids; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public Int32[] axis_values; + public float[] axis_values; } class Cfunctions diff --git a/include/libsurvive/survive.h b/include/libsurvive/survive.h index fef8cb0..e119344 100644 --- a/include/libsurvive/survive.h +++ b/include/libsurvive/survive.h @@ -98,7 +98,7 @@ struct SurviveObject { SurviveObjectSubtype object_subtype; int32_t buttonmask; int32_t touchmask; - int16_t axis[16]; + SurviveAxisVal_t axis[16]; int8_t charge; int8_t charging : 1; @@ -241,7 +241,7 @@ typedef struct { uint8_t buttonId; enum SurviveAxis ids[16]; - int32_t axisValues[16]; + SurviveAxisVal_t axisValues[16]; SurviveObject *so; } ButtonQueueEntry; @@ -426,7 +426,7 @@ SURVIVE_EXPORT void survive_default_sweep_angle_process(SurviveObject *so, survi survive_timecode timecode, int8_t plane, FLT angle); SURVIVE_EXPORT void survive_default_button_process(SurviveObject *so, enum SurviveInputEvent eventType, enum SurviveButton buttonId, const enum SurviveAxis *axisIds, - const int32_t *axisVals); + const SurviveAxisVal_t *axisVals); SURVIVE_EXPORT void survive_default_imupose_process(SurviveObject *so, uint32_t timecode, const SurvivePose *imu2world); SURVIVE_EXPORT void survive_default_pose_process(SurviveObject *so, survive_timecode timecode, const SurvivePose *pose); SURVIVE_EXPORT void survive_default_velocity_process(SurviveObject *so, survive_timecode timecode, diff --git a/include/libsurvive/survive_api.h b/include/libsurvive/survive_api.h index 2ad0c47..c746e94 100644 --- a/include/libsurvive/survive_api.h +++ b/include/libsurvive/survive_api.h @@ -38,7 +38,7 @@ typedef struct SurviveSimpleButtonEvent { uint8_t axis_count; enum SurviveAxis axis_ids[SURVIVE_MAX_AXIS_COUNT]; - int32_t axis_val[SURVIVE_MAX_AXIS_COUNT]; + SurviveAxisVal_t axis_val[SURVIVE_MAX_AXIS_COUNT]; } SurviveSimpleButtonEvent; /*** diff --git a/include/libsurvive/survive_types.h b/include/libsurvive/survive_types.h index 1d8bf77..29fece2 100644 --- a/include/libsurvive/survive_types.h +++ b/include/libsurvive/survive_types.h @@ -156,6 +156,7 @@ enum SurviveAxis { SURVIVE_AXIS_IPD = 0, SURVIVE_AXIS_FACE_PROXIMITY = 1 }; +typedef float SurviveAxisVal_t; typedef enum { SURVIVE_OBJECT_TYPE_UNKNOWN = 0, @@ -282,7 +283,7 @@ typedef void (*imu_process_func)(SurviveObject *so, int mask, FLT *accelgyro, su * A general button press event */ typedef void (*button_process_func)(SurviveObject *so, enum SurviveInputEvent eventType, enum SurviveButton buttonId, - const enum SurviveAxis *axisIds, const int32_t *axisVals); + const enum SurviveAxis *axisIds, const SurviveAxisVal_t *axisVals); /** * Called when a pose is solved for at a given time. Given in 'tracking' coordinate frame. diff --git a/src/driver_vive.c b/src/driver_vive.c index 77dd4dd..de44fe9 100755 --- a/src/driver_vive.c +++ b/src/driver_vive.c @@ -835,8 +835,8 @@ int survive_vive_usb_poll(SurviveContext *ctx, void *v) { struct SurviveUSBInfo *usbInfo = &sv->udev[i]; FLT now = OGRelativeTime(); - if (usbInfo->device_info->pid == 0x2101 && usbInfo->so->conf == 0 && sv->requestPairing && - (sv->lastPairTime + 1) < now && now > 3) { + if ((usbInfo->device_info->pid == 0x2102 || usbInfo->device_info->pid == 0x2101) && usbInfo->so->conf == 0 && + sv->requestPairing && (sv->lastPairTime + 1) < now && now > 3) { survive_release_ctx_lock(ctx); int r = update_feature_report(usbInfo->handle, 0, vive_request_pairing, sizeof(vive_request_pairing)); survive_get_ctx_lock(ctx); @@ -1063,16 +1063,16 @@ typedef struct { uint8_t proximityValid; uint8_t rawAxisCnt; - int32_t rawAxis[16]; - uint8_t proximity[6]; + SurviveAxisVal_t rawAxis[16]; + SurviveAxisVal_t /*uint8_t*/ proximity[6]; uint32_t pressedButtons; uint32_t touchedButtons; uint16_t triggerOrBattery; uint8_t batteryCharge; uint32_t hardwareId; - uint16_t touchpadHorizontal; - uint16_t touchpadVertical; - uint16_t triggerHighRes; + SurviveAxisVal_t /*uint16_t*/ touchpadHorizontal; + SurviveAxisVal_t /*uint16_t*/ touchpadVertical; + SurviveAxisVal_t /*uint16_t*/ triggerHighRes; } buttonEvent; void incrementAndPostButtonQueue(SurviveContext *ctx) { @@ -1116,6 +1116,8 @@ static void get_eventTypes_for_idx(const SurviveObject *so, uint8_t idx, uint8_t switch (source) { case BUTTON_EVENT_SOURCE_RF: { switch (so->object_subtype) { + case SURVIVE_OBJECT_SUBTYPE_KNUCKLES_R: + case SURVIVE_OBJECT_SUBTYPE_KNUCKLES_L: case SURVIVE_OBJECT_SUBTYPE_WAND: { switch (idx) { case 1: @@ -1160,6 +1162,7 @@ static uint8_t get_button_id_for_idx(const SurviveObject *so, uint8_t idx, enum } default: { switch (so->object_subtype) { + case SURVIVE_OBJECT_SUBTYPE_TRACKER: case SURVIVE_OBJECT_SUBTYPE_WAND: { switch (idx) { case 20: @@ -1254,20 +1257,33 @@ static void registerButtonEvent(SurviveObject *so, buttonEvent *event, enum Butt } if ((event->touchpadHorizontalValid) && (event->touchpadVerticalValid)) { - if ((so->axis[1] != event->touchpadHorizontal) || (so->axis[2] != event->touchpadVertical)) { - entry->eventType = SURVIVE_INPUT_EVENT_AXIS_CHANGED; - entry->axisValues[0] = (int16_t)event->touchpadHorizontal; - entry->axisValues[1] = (int16_t)event->touchpadVertical; - entry->ids[0] = 2; - entry->ids[1] = 3; + enum SurviveAxis ax = SURVIVE_AXIS_TRACKPAD_X; + enum SurviveAxis ay = SURVIVE_AXIS_TRACKPAD_Y; - if (so->object_subtype == SURVIVE_OBJECT_SUBTYPE_KNUCKLES_R || - so->object_subtype == SURVIVE_OBJECT_SUBTYPE_KNUCKLES_L) { - if ((so->buttonmask & (1 << SURVIVE_BUTTON_TRACKPAD)) == 0) { - entry->ids[0] = SURVIVE_AXIS_JOYSTICK_X; - entry->ids[1] = SURVIVE_AXIS_JOYSTICK_Y; + if (so->object_subtype == SURVIVE_OBJECT_SUBTYPE_KNUCKLES_R || + so->object_subtype == SURVIVE_OBJECT_SUBTYPE_KNUCKLES_L) { + if ((so->buttonmask & (1 << SURVIVE_BUTTON_TRACKPAD)) == 0) { + + if ((so->axis[ax] != 0) || (so->axis[ay] != 0)) { + entry->eventType = SURVIVE_INPUT_EVENT_AXIS_CHANGED; + + entry->axisValues[0] = so->axis[entry->ids[0] = ax] = 0; + entry->axisValues[1] = so->axis[entry->ids[1] = ay] = 0; + + incrementAndPostButtonQueue(so->ctx); + entry = prepareNextButtonEvent(so); } + + ax = SURVIVE_AXIS_JOYSTICK_X; + ay = SURVIVE_AXIS_JOYSTICK_Y; } + } + + if ((so->axis[ax] != event->touchpadHorizontal) || (so->axis[ay] != event->touchpadVertical)) { + entry->eventType = SURVIVE_INPUT_EVENT_AXIS_CHANGED; + + entry->axisValues[0] = so->axis[entry->ids[0] = ax] = event->touchpadHorizontal; + entry->axisValues[1] = so->axis[entry->ids[1] = ay] = event->touchpadVertical; incrementAndPostButtonQueue(so->ctx); entry = prepareNextButtonEvent(so); @@ -1310,12 +1326,6 @@ static void registerButtonEvent(SurviveObject *so, buttonEvent *event, enum Butt if (event->triggerHighResValid) { so->axis[0] = event->triggerHighRes; } - if (event->touchpadHorizontalValid) { - so->axis[1] = event->touchpadHorizontal; - } - if (event->touchpadVerticalValid) { - so->axis[2] = event->touchpadVertical; - } if (event->proximityValid) { for (int i = 0; i < 6; i++) { so->axis[3 + i] = event->proximity[i]; @@ -1759,15 +1769,15 @@ static bool read_event(SurviveObject *w, uint16_t time, uint8_t **readPtr, uint8 if (flagTrigger) { bEvent.triggerHighResValid = 1; - bEvent.triggerHighRes = POP_BYTE(payloadPtr) * 128; + bEvent.triggerHighRes = POP_BYTE(payloadPtr) / 255.; } if (flagMotion) { bEvent.touchpadHorizontalValid = 1; bEvent.touchpadVerticalValid = 1; - bEvent.touchpadHorizontal = POP_SHORT(payloadPtr); - bEvent.touchpadVertical = POP_SHORT(payloadPtr); + bEvent.touchpadHorizontal = (int16_t)POP_SHORT(payloadPtr) / 32768.; + bEvent.touchpadVertical = (int16_t)POP_SHORT(payloadPtr) / 32768.; } } else { @@ -1794,14 +1804,14 @@ static bool read_event(SurviveObject *w, uint16_t time, uint8_t **readPtr, uint8 bEvent.proximityValid = 1; // Non-touching proximity to fingers - bEvent.proximity[0] = POP_BYTE(payloadPtr); // Middle finger - bEvent.proximity[1] = POP_BYTE(payloadPtr); // Ring finger - bEvent.proximity[2] = POP_BYTE(payloadPtr); // Pinky finger - bEvent.proximity[3] = POP_BYTE(payloadPtr); // Index finger (trigger) + bEvent.proximity[0] = POP_BYTE(payloadPtr) / 255.; // Middle finger + bEvent.proximity[1] = POP_BYTE(payloadPtr) / 255.; // Ring finger + bEvent.proximity[2] = POP_BYTE(payloadPtr) / 255.; // Pinky finger + bEvent.proximity[3] = POP_BYTE(payloadPtr) / 255.; // Index finger (trigger) // Contact force (Squeeze strength) - bEvent.proximity[4] = POP_BYTE(payloadPtr); - bEvent.proximity[5] = POP_BYTE(payloadPtr); + bEvent.proximity[4] = POP_BYTE(payloadPtr) / 255.; + bEvent.proximity[5] = POP_BYTE(payloadPtr) / 255.; } else { SV_WARN("Unknown gen two event %s 0x%02hX 0b%s [Time:%04hX] [Payload: %s] <>", w->codename, *(payloadPtr - 1), byteToBin(*(payloadPtr - 1)), time, @@ -2052,7 +2062,7 @@ static bool handle_input(SurviveObject *w, uint8_t flags, uint8_t **payloadPtr, if (flagTrigger) { bEvent.triggerHighResValid = 1; VERIFY_LENGTH_OR_FAIL(*payloadPtr, 1); - bEvent.triggerHighRes = POP_BYTE(*payloadPtr) * 128; + bEvent.triggerHighRes = POP_BYTE(*payloadPtr) / 255.; } if (flagMotion) { @@ -2060,8 +2070,8 @@ static bool handle_input(SurviveObject *w, uint8_t flags, uint8_t **payloadPtr, bEvent.touchpadVerticalValid = 1; VERIFY_LENGTH_OR_FAIL(*payloadPtr, 4); - bEvent.touchpadHorizontal = POP_SHORT(*payloadPtr); - bEvent.touchpadVertical = POP_SHORT(*payloadPtr); + bEvent.touchpadHorizontal = (int16_t)POP_SHORT(*payloadPtr) / 32768.; + bEvent.touchpadVertical = (int16_t)POP_SHORT(*payloadPtr) / 32768.; } SV_VERBOSE(150, "handle_input flags %d %d %d", flagButton, flagTrigger, flagMotion); } else { @@ -2088,15 +2098,15 @@ static bool handle_input(SurviveObject *w, uint8_t flags, uint8_t **payloadPtr, bEvent.proximityValid = 1; // Non-touching proximity to fingers - bEvent.proximity[0] = POP_BYTE(*payloadPtr); // Middle finger - bEvent.proximity[1] = POP_BYTE(*payloadPtr); // Ring finger - bEvent.proximity[2] = POP_BYTE(*payloadPtr); // Pinky finger - bEvent.proximity[3] = POP_BYTE(*payloadPtr); // Index finger (trigger) + bEvent.proximity[0] = POP_BYTE(*payloadPtr) / 255.; // Middle finger + bEvent.proximity[1] = POP_BYTE(*payloadPtr) / 255.; // Ring finger + bEvent.proximity[2] = POP_BYTE(*payloadPtr) / 255.; // Pinky finger + bEvent.proximity[3] = POP_BYTE(*payloadPtr) / 255.; // Index finger (trigger) // Contact force (Squeeze strength) - bEvent.proximity[4] = POP_BYTE(*payloadPtr); + bEvent.proximity[4] = POP_BYTE(*payloadPtr) / 255.; // Trackpad force - bEvent.proximity[5] = POP_BYTE(*payloadPtr); + bEvent.proximity[5] = POP_BYTE(*payloadPtr) / 255.; } else { SV_WARN("Unknown gen two event 0x%02hX 0b%s [Payload: %s] <>", *(*payloadPtr - 1), byteToBin(*(*payloadPtr - 1)), packetToHex(*payloadPtr, payloadEndPtr)); @@ -2106,7 +2116,7 @@ static bool handle_input(SurviveObject *w, uint8_t flags, uint8_t **payloadPtr, } } - registerButtonEvent(w, &bEvent, BUTTON_EVENT_SOURCE_DEFAULT); + registerButtonEvent(w, &bEvent, BUTTON_EVENT_SOURCE_RF); return true; exit_failure: @@ -2945,8 +2955,9 @@ void survive_data_cb_locked(SurviveUSBInterface *si) { buttonEvent evt = {0}; evt.rawAxisCnt = 2; - evt.rawAxis[0] = IPD; - evt.rawAxis[1] = proximity; + evt.rawAxis[0] = IPD / 65536.; + evt.rawAxis[1] = proximity / 32768.; + evt.pressedButtonsValid = 1; evt.pressedButtons = onFace; registerButtonEvent(obj, &evt, BUTTON_EVENT_SOURCE_DEFAULT); @@ -2961,13 +2972,12 @@ void survive_data_cb_locked(SurviveUSBInterface *si) { bEvent.pressedButtons = read_buffer32(readdata, 0x7); bEvent.triggerHighResValid = 1; - bEvent.triggerHighRes = read_buffer16(readdata, 0x19); + bEvent.triggerHighRes = read_buffer16(readdata, 0x19) / 32768.; bEvent.touchpadHorizontalValid = 1; - - bEvent.touchpadHorizontal = read_buffer16(readdata, 0x13); bEvent.touchpadVerticalValid = 1; - bEvent.touchpadVertical = read_buffer16(readdata, 0x15); + bEvent.touchpadHorizontal = (int16_t)read_buffer16(readdata, 0x13) / 32768.; + bEvent.touchpadVertical = (int16_t)read_buffer16(readdata, 0x15) / 32768.; registerButtonEvent(obj, &bEvent, BUTTON_EVENT_SOURCE_DEFAULT); } diff --git a/src/survive_api.c b/src/survive_api.c index ada903f..3869371 100644 --- a/src/survive_api.c +++ b/src/survive_api.c @@ -190,7 +190,7 @@ static void lh_fn(SurviveContext *ctx, uint8_t lighthouse, SurvivePose *lighthou } static void button_fn(SurviveObject *so, enum SurviveInputEvent eventType, enum SurviveButton buttonId, - const enum SurviveAxis *axisIds, const int32_t *axisVals) { + const enum SurviveAxis *axisIds, const SurviveAxisVal_t *axisVals) { SurviveSimpleContext *actx = so->ctx->user_ptr; OGLockMutex(actx->poll_mutex); survive_default_button_process(so, eventType, buttonId, axisIds, axisVals); diff --git a/src/survive_default_devices.c b/src/survive_default_devices.c index 990dddb..9aa1477 100644 --- a/src/survive_default_devices.c +++ b/src/survive_default_devices.c @@ -297,14 +297,18 @@ static int process_jsontok(scratch_space_t *scratch, char *d, stack_entry_t *sta memcpy(scratch->so->serial_number, d + t->start, size); } else if (jsoneq(d, stack->key, "model_number") == 0) { - if(strncmp("Knuckles Right", d + t->start, t->end - t->start) == 0) { + if (strncmp("Knuckles Right", d + t->start, t->end - t->start) == 0 || + strncmp("Knuckles EV3.0 Right", d + t->start, t->end - t->start) == 0) { scratch->so->object_subtype = SURVIVE_OBJECT_SUBTYPE_KNUCKLES_R; - } else if (strncmp("Knuckles Left", d + t->start, t->end - t->start) == 0) { + } else if (strncmp("Knuckles Left", d + t->start, t->end - t->start) == 0 || + strncmp("Knuckles EV3.0 Left", d + t->start, t->end - t->start) == 0) { scratch->so->object_subtype = SURVIVE_OBJECT_SUBTYPE_KNUCKLES_L; } else if (strncmp("Utah MP", d + t->start, t->end - t->start) == 0) { scratch->so->object_subtype = SURVIVE_OBJECT_SUBTYPE_INDEX; } else if (strncmp("Vive Controller MV", d + t->start, t->end - t->start) == 0) { scratch->so->object_subtype = SURVIVE_OBJECT_SUBTYPE_WAND; + } else if (strncmp("VIVE Tracker Pro MV", d + t->start, t->end - t->start) == 0) { + scratch->so->object_subtype = SURVIVE_OBJECT_SUBTYPE_TRACKER; } } else if (jsoneq(d, stack->key, "model_name") == 0) { if (strncmp("Vive Tracker MV", d + t->start, t->end - t->start) == 0) { diff --git a/src/survive_process.c b/src/survive_process.c index 933f8e0..6725634 100644 --- a/src/survive_process.c +++ b/src/survive_process.c @@ -119,7 +119,7 @@ void survive_default_lightcap_process(SurviveObject *so, const LightcapElement * } void survive_default_button_process(SurviveObject *so, enum SurviveInputEvent eventType, enum SurviveButton buttonId, - const enum SurviveAxis *axisIds, const int32_t *axisValues) {} + const enum SurviveAxis *axisIds, const SurviveAxisVal_t *axisValues) {} STATIC_CONFIG_ITEM(REPORT_IN_IMU, "report-in-imu", 'i', "Debug option to output poses in IMU space.", 0) void survive_default_imupose_process(SurviveObject *so, uint32_t timecode, const SurvivePose *imu2world) { @@ -193,6 +193,9 @@ int survive_default_config_process(SurviveObject *so, char *ct0conf, int len) { FILE *f = fopen(raw_fname, "w"); fwrite(ct0conf, len, 1, f); fclose(f); + + SurviveContext *ctx = so->ctx; + SV_INFO("Wrote out %d bytes to %s", len, raw_fname); } } diff --git a/src/survive_recording.c b/src/survive_recording.c index e713265..101ae46 100644 --- a/src/survive_recording.c +++ b/src/survive_recording.c @@ -204,7 +204,7 @@ void survive_recording_sweep_process(SurviveObject *so, survive_channel channel, } void survive_recording_button_process(SurviveObject *so, enum SurviveInputEvent eventType, enum SurviveButton buttonId, - const enum SurviveAxis *axisId, const int32_t *axisVals) { + const enum SurviveAxis *axisId, const float *axisVals) { SurviveRecordingData *recordingData = so->ctx->recptr; if (!recordingData) { diff --git a/src/survive_recording.h b/src/survive_recording.h index 1fb1e87..e34ba6e 100644 --- a/src/survive_recording.h +++ b/src/survive_recording.h @@ -41,7 +41,7 @@ void survive_recording_info_process(SurviveContext *ctx, const char *fault); void survive_recording_sweep_process(SurviveObject *so, survive_channel channel, int sensor_id, survive_timecode timecode, bool flag); void survive_recording_button_process(SurviveObject *so, enum SurviveInputEvent eventType, enum SurviveButton buttonId, - const enum SurviveAxis *axisIds, const int32_t *axisVals); + const enum SurviveAxis *axisIds, const SurviveAxisVal_t *axisVals); void survive_recording_angle_process(struct SurviveObject *so, int sensor_id, int acode, uint32_t timecode, FLT length, FLT angle, uint32_t lh); void survive_recording_sweep_angle_process(SurviveObject *so, survive_channel channel, int sensor_id, diff --git a/survive-buttons.c b/survive-buttons.c index 03a2176..09d85e8 100644 --- a/survive-buttons.c +++ b/survive-buttons.c @@ -29,7 +29,7 @@ struct ObjectButtonInfo { struct button_state buttons[32]; struct axis_state { bool changed; - int32_t v; + SurviveAxisVal_t v; } axes[16]; }; @@ -51,7 +51,7 @@ FLT last_redraw = 0; bool needsRedraw = true; static void button_process(SurviveObject *so, enum SurviveInputEvent eventType, enum SurviveButton buttonId, - const enum SurviveAxis *axisIds, const int32_t *axisVals) { + const enum SurviveAxis *axisIds, const SurviveAxisVal_t *axisVals) { struct SurviveContext *ctx = so->ctx; survive_default_button_process(so, eventType, buttonId, axisIds, axisVals); @@ -116,7 +116,7 @@ static void redraw(SurviveContext *ctx) { if (!buttonInfos[i].axes[k].changed) continue; - printf("\33[2KAxis %24s (%2d) %8d\r\n", SurviveAxisStr(so->object_subtype, k), k, + printf("\33[2KAxis %24s (%2d) %+5.4f\r\n", SurviveAxisStr(so->object_subtype, k), k, buttonInfos[i].axes[k].v); } diff --git a/survive_autocomplete.sh b/survive_autocomplete.sh index caafcb7..979b8cc 100755 --- a/survive_autocomplete.sh +++ b/survive_autocomplete.sh @@ -3,7 +3,8 @@ #place this script in /etc/bash_completion.d/ for general use. _script() { - _script_commands=$(./survive-cli -m $1 $2 $3) + local cmd="${1##*/}" + _script_commands=$($cmd -m $1 $2 $3) local cur prev COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" @@ -12,10 +13,13 @@ _script() return 0 } -complete -o nospace -F _script ./calibrate complete -o nospace -F _script ./simple_pose_test -complete -o nospace -F _script ./data_recorder +complete -o nospace -F _script ./survive-buttons complete -o nospace -F _script ./survive-cli complete -o nospace -F _script ./survive-websocketd +complete -o nospace -F _script simple_pose_test +complete -o nospace -F _script survive-buttons +complete -o nospace -F _script survive-cli +complete -o nospace -F _script survive-websocketd