// ======================================================================================= // Controller Server: Extremely simple program to make the inputs from an Xbox 360 // controller available to Maya. // // Author: Dave Moore (dmoore@onepartcode.com) // Inspired by Joystick Server, by Jorge Luis Imperial-Sosa // // Notes: When I say "extemely simple", I mean it. This is about the minimum possible // modification to the sample "Clock" server from the Maya devkit to get this up // and running. // - Controller must be plugged in when the app starts // - There is no support for multiple controllers. // - There is no error checking. // - There is no recording capability. // // It should be straightforward to add such things, but it's not necessary for my // purposes. Have fun! // // Usage: Plug in your Xenon controller, and run the app. In Maya, run the following // command to attach to the server: // defineDataServer -device xenon -server XenonController; // // You can then use scripts, or the Device Editor (Windows|Animation Editors|Device // Editor) to create mappings between the controls and objects in your scene. Note // that all analog controls are mapped [-1, 1] with the exception of the triggers, // which are [0, 1]. Digital buttons report either 0.0 or 1.0. // // Thoughts: Auto detection would be nice // Should map any DirectInput controller // Add the recording callbacks? // // LICENSE: // This code has been placed in the public domain. Do whatever you want with it; no // copyright, no restrictions whatsoever. // // The code comes with ABSOLUTELY NO WARRANTY. // // ======================================================================================= #include #include #include #include #define ServerName "XenonController" static int handle_client(int client_fd); // Hard coded CapChannel LeftXAxis; CapChannel LeftYAxis; CapChannel RightXAxis; CapChannel RightYAxis; CapChannel LeftTrigger; CapChannel RightTrigger; CapChannel DPadX; CapChannel DPadY; CapChannel XButton; CapChannel YButton; CapChannel AButton; CapChannel BButton; CapChannel BackButton; CapChannel StartButton; CapChannel LeftStickClick; CapChannel RightStickClick; CapChannel LeftShoulder; CapChannel RightShoulder; int main(int argc, char const* argv[]) { // Create the channels for the xenon controller. Everything is created as a 1-d // "unknown" channel. This kinda sucks, since we know that there are boolean values, // but there doesn't appear to be support for these types in the mocap server API. // Just map them to 0/1 floats. #define CREATE_UNK_CHANNEL(Name) Name = CapCreateChannel( #Name, CAP_USAGE_UNKNOWN, 1) { CREATE_UNK_CHANNEL(LeftXAxis); CREATE_UNK_CHANNEL(LeftYAxis); CREATE_UNK_CHANNEL(RightXAxis); CREATE_UNK_CHANNEL(RightYAxis); CREATE_UNK_CHANNEL(DPadX); CREATE_UNK_CHANNEL(DPadY); CREATE_UNK_CHANNEL(LeftTrigger); CREATE_UNK_CHANNEL(RightTrigger); CREATE_UNK_CHANNEL(XButton); CREATE_UNK_CHANNEL(YButton); CREATE_UNK_CHANNEL(AButton); CREATE_UNK_CHANNEL(BButton); CREATE_UNK_CHANNEL(BackButton); CREATE_UNK_CHANNEL(StartButton); CREATE_UNK_CHANNEL(LeftStickClick); CREATE_UNK_CHANNEL(RightStickClick); CREATE_UNK_CHANNEL(LeftShoulder); CREATE_UNK_CHANNEL(RightShoulder); } #undef CREATE_UNK_CHANNEL for(;;) { /* * Set up the server socket and wait for a connection. */ int client_fd = CapServe(ServerName); if (client_fd < 0) { CapError(-1, CAP_SEV_FATAL, ServerName "0", NULL); exit(1); } /* Handle client requests */ int status = handle_client(client_fd); if (status < 0) { CapError(-1, CAP_SEV_FATAL, ServerName "1", NULL); } /* Shutdown the client */ closesocket(client_fd); } return 0; } #define HANDLE_AXIS(Channel, var) do { \ float value = (var) / 32767.0f; \ if (value < -1.0f) value = -1.0f; \ if (value > 1.0f) value = 1.0f; \ CapSetData(Channel, &value); \ } while (false) #define DPAD_AXIS(Channel, sv, Pos, Neg) do { \ float value = (((sv.Gamepad.wButtons & (Pos)) ? 1.0f : 0.0f) - \ ((sv.Gamepad.wButtons & (Neg)) ? 1.0f : 0.0f)); \ CapSetData(Channel, &value); \ } while (false) #define HANDLE_TRIGGER(Channel, var) do { \ float value = (var) / 255.0f; \ CapSetData(Channel, &value); \ } while (false) #define HANDLE_BUTTON(Channel, sv, mask) do { \ float value = (sv.Gamepad.wButtons & (mask)) ? 1.0f : 0.0f; \ CapSetData(Channel, &value); \ } while (false) static void get_data(int client_fd) { XINPUT_STATE state; ZeroMemory( &state, sizeof(XINPUT_STATE) ); if( XInputGetState( 0, &state ) == ERROR_SUCCESS ) { HANDLE_AXIS(LeftXAxis, state.Gamepad.sThumbLX); HANDLE_AXIS(LeftYAxis, state.Gamepad.sThumbLY); HANDLE_AXIS(RightXAxis, state.Gamepad.sThumbRX); HANDLE_AXIS(RightYAxis, state.Gamepad.sThumbRY); DPAD_AXIS(DPadX, state, XINPUT_GAMEPAD_DPAD_RIGHT, XINPUT_GAMEPAD_DPAD_LEFT); DPAD_AXIS(DPadY, state, XINPUT_GAMEPAD_DPAD_UP, XINPUT_GAMEPAD_DPAD_DOWN); HANDLE_TRIGGER(LeftTrigger, state.Gamepad.bLeftTrigger); HANDLE_TRIGGER(RightTrigger, state.Gamepad.bRightTrigger); HANDLE_BUTTON(XButton, state, XINPUT_GAMEPAD_X); HANDLE_BUTTON(YButton, state, XINPUT_GAMEPAD_Y); HANDLE_BUTTON(AButton, state, XINPUT_GAMEPAD_A); HANDLE_BUTTON(BButton, state, XINPUT_GAMEPAD_B); HANDLE_BUTTON(BackButton, state, XINPUT_GAMEPAD_BACK); HANDLE_BUTTON(StartButton, state, XINPUT_GAMEPAD_START); HANDLE_BUTTON(LeftStickClick, state, XINPUT_GAMEPAD_LEFT_THUMB); HANDLE_BUTTON(RightStickClick, state, XINPUT_GAMEPAD_RIGHT_THUMB); HANDLE_BUTTON(LeftShoulder, state, XINPUT_GAMEPAD_LEFT_SHOULDER); HANDLE_BUTTON(RightShoulder, state, XINPUT_GAMEPAD_RIGHT_SHOULDER); } } static int handle_client(int client_fd) { for (;;) { fd_set rd_fds; FD_ZERO(&rd_fds); FD_SET((unsigned int)client_fd, &rd_fds); int status = select(FD_SETSIZE, &rd_fds, NULL, NULL, NULL); if (status < 0) { /* Otherwise, give a fatal error message */ CapError(client_fd, CAP_SEV_FATAL, ServerName, "select failed"); CapError(client_fd, CAP_SEV_FATAL, "select", NULL); exit(1); } else if (status == 0) { /* We got a timeout? Try again */ continue; } else { /* There is data on the client file descriptor */ CapCommand cmd = CapGetCommand(client_fd); switch (cmd) { case CAP_CMD_QUIT: return 0; case CAP_CMD_ERROR: return -1; case CAP_CMD_AUTHORIZE: status = CapAuthorize(client_fd, 1); break; case CAP_CMD_INIT: /* Initial client/server handshake */ status = CapInitialize(client_fd, ServerName); break; case CAP_CMD_VERSION: /* Send version information */ status = CapVersion(client_fd, ServerName, "1.0", "Xenon Controller capture server - v1.0"); break; case CAP_CMD_INFO: status = CapInfo(client_fd, 0.0, 0.0, 0.0, 0, 1); break; case CAP_CMD_DATA: /* Send frame data */ get_data(client_fd); status = CapData(client_fd); break; case CAP_CMD_START_RECORD: case CAP_CMD_STOP_RECORD: default: status = CapError(client_fd, CAP_SEV_ERROR, ServerName, "Unknown or unsupported server command."); break; } if (status < 0) { return -1; } } } }