// win_input.c -- win32 mouse and joystick code // 02/21/97 JCB Added extended DirectInput code to support external controllers. // leave this as first line for PCH reasons... // // #include "../server/exe_headers.h" #include #include "glw_win_dx8.h" #include "../client/client.h" #include "../qcommon/qcommon.h" #ifdef _JK2MP #include "../ui/keycodes.h" #else #include "../client/keycodes.h" #endif #include "win_local.h" #include "win_input.h" #define IN_MAX_CONTROLLERS 4 void IN_UIEmptyQueue(); struct inputstate_t { struct controller_t { HANDLE handle; XINPUT_STATE state; XINPUT_FEEDBACK feedback; }; controller_t controllers[IN_MAX_CONTROLLERS]; }; inputstate_t *in_state = NULL; /* ========================================================================= JOYSTICK ========================================================================= */ // Process all the insertions and removals, updating handles and such void IN_ProcessChanges(DWORD dwInsert, DWORD dwRemove) { for(int port = 0; port < IN_MAX_CONTROLLERS; ++port) { // Close removals. if( ((1 << port) & dwRemove) && in_state->controllers[port].handle ) { XInputClose( in_state->controllers[port].handle ); in_state->controllers[port].handle = 0; IN_PadUnplugged(port); } // Open insertions. if( (1 << port) & dwInsert ) { in_state->controllers[port].handle = XInputOpen( XDEVICE_TYPE_GAMEPAD, port, XDEVICE_NO_SLOT, NULL ); IN_PadPlugged(port); } } return; } /********* IN_CheckForNoControllers() If there are no controllers plugged in, the UI is notified so it can display an appropriate message. *********/ void IN_CheckForNoControllers() { extern bool noControllersConnected; if(!noControllersConnected) { extern bool wasPlugged[4]; if( !wasPlugged[0] && !wasPlugged[1] && !wasPlugged[2] && !wasPlugged[3] ) { // Tell the UI that there are no controllers connected // VM_Call( uivm, UI_CONTROLLER_UNPLUGGED, true, -1); noControllersConnected = true; } } } /* ========================================================================= RUMBLE SUPPORT ========================================================================= */ bool IN_RumbleAdjust(int controller, int left, int right) { assert(controller >= 0 && controller < IN_MAX_CONTROLLERS); // Get a device handle for the controller. This may fail. HANDLE handle = in_state->controllers[controller].handle; if (!handle) return false; XINPUT_FEEDBACK* fb = &in_state->controllers[controller].feedback; // If a prior rumble update is still pending, go away if (fb->Header.dwStatus == ERROR_IO_PENDING) return false; fb->Rumble.wLeftMotorSpeed = left; fb->Rumble.wRightMotorSpeed = right; return ERROR_IO_PENDING == XInputSetState(handle, fb); } /* ========================================================================= ========================================================================= */ /* igBool IN_WindowClose(igWindow *window) { SV_Shutdown ("Server quit\n"); CL_Shutdown (); Com_Shutdown (); Sys_Quit (); return true; } */ /* =========== IN_Shutdown =========== */ void IN_Shutdown( void ) { IN_RumbleShutdown(); delete in_state; in_state = NULL; } /* =========== IN_Init =========== */ void IN_Init( void ) { in_state = new inputstate_t; // Initialize support for 4 gamepads XDEVICE_PREALLOC_TYPE xdpt[] = { {XDEVICE_TYPE_GAMEPAD, 4}, {XDEVICE_TYPE_MEMORY_UNIT, 1}, {XDEVICE_TYPE_VOICE_MICROPHONE, 1}, {XDEVICE_TYPE_VOICE_HEADPHONE, 1} }; // Initialize the peripherals. We can only ever // call XInitDevices once, no matter what. static bool bInputInitialized = false; if (!bInputInitialized) XInitDevices( sizeof(xdpt) / sizeof(XDEVICE_PREALLOC_TYPE), xdpt ); bInputInitialized = true; // Zero all of our data, including handles memset(in_state->controllers, 0, sizeof(in_state->controllers)); // Find out the status of all gamepad ports, then open them IN_ProcessChanges( XGetDevices( XDEVICE_TYPE_GAMEPAD ), 0 ); IN_RumbleInit(); } static inline float _joyAxisConvert(SHORT x) { // Change scale float y = x / 32767.0; // Cheesy deadzone if(fabs(y) < 0.25f) { y = 0.0f; } return y; } // How many controls on the xbox gamepad? #define IN_NUM_DIGITAL_BUTTONS 8 #define IN_NUM_ANALOG_BUTTONS 8 // Cutoff where the analog buttons are considered to be "pressed" // This should be smarter. #define IN_ANALOG_BUTTON_THRESHOLD 64 void IN_UpdateGamepad(int port) { // Lookup table to convert the digital buttons to fakeAscii_t, in mask order const fakeAscii_t digitalXlat[IN_NUM_DIGITAL_BUTTONS] = { A_JOY5, // DPAD_UP A_JOY7, // DPAD_DOWN A_JOY8, // DPAD_LEFT A_JOY6, // DPAD_LEFT A_JOY4, // Start A_JOY1, // Back A_JOY2, // Left stick A_JOY3 // Right stick }; // Lookup table to convet the analog buttons to fakeAscii_t, in DX order const fakeAscii_t analogXlat[IN_NUM_ANALOG_BUTTONS] = { A_JOY15, // A A_JOY14, // B A_JOY16, // X A_JOY13, // Y A_JOY10, // Black A_JOY9, // White A_JOY11, // Left trigger A_JOY12 // Right trigger }; // Get new state XINPUT_STATE newState; XInputGetState( in_state->controllers[port].handle, &newState ); // Get old state XINPUT_STATE &oldState(in_state->controllers[port].state); int buttonIdx; bool oldPressed, newPressed; // Check all digital buttons first for (buttonIdx = 0; buttonIdx < IN_NUM_DIGITAL_BUTTONS; ++buttonIdx) { oldPressed = oldState.Gamepad.wButtons & (1 << buttonIdx); newPressed = newState.Gamepad.wButtons & (1 << buttonIdx); if (oldPressed != newPressed) IN_CommonJoyPress(port, digitalXlat[buttonIdx], newPressed); } // Now check all analog buttons for (buttonIdx = 0; buttonIdx < IN_NUM_ANALOG_BUTTONS; ++buttonIdx) { oldPressed = oldState.Gamepad.bAnalogButtons[buttonIdx] > IN_ANALOG_BUTTON_THRESHOLD; newPressed = newState.Gamepad.bAnalogButtons[buttonIdx] > IN_ANALOG_BUTTON_THRESHOLD; if (oldPressed != newPressed) IN_CommonJoyPress(port, analogXlat[buttonIdx], newPressed); } // Update joysticks _padInfo.joyInfo[0].x = _joyAxisConvert(newState.Gamepad.sThumbLX); _padInfo.joyInfo[0].y = _joyAxisConvert(newState.Gamepad.sThumbLY); _padInfo.joyInfo[1].x = _joyAxisConvert(newState.Gamepad.sThumbRX); _padInfo.joyInfo[1].y = _joyAxisConvert(newState.Gamepad.sThumbRY); _padInfo.joyInfo[0].valid = _padInfo.joyInfo[1].valid = true; _padInfo.padId = port; // Copy state back oldState = newState; // Update game IN_CommonUpdate(); } /* ================== IN_Frame Called every frame, even if not generating commands ================== */ //extern int ignoreInputTime; void IN_Frame (void) { if (in_state) { // First, check for changes in device status (removed/inserted pads) DWORD dwInsert, dwRemove; if( XGetDeviceChanges( XDEVICE_TYPE_GAMEPAD, &dwInsert, &dwRemove ) ) { IN_ProcessChanges(dwInsert, dwRemove); } else { IN_CheckForNoControllers(); } // Generate callbacks for each controller that's plugged in for (int port = 0; port < IN_MAX_CONTROLLERS; ++port) if (in_state->controllers[port].handle) IN_UpdateGamepad(port); IN_UIEmptyQueue(); IN_RumbleFrame(); } }