• Main Page
  • Classes
  • Files
  • File List

CMSWindowsXInput.cpp

00001 /*
00002  * synergy -- mouse and keyboard sharing utility
00003  * Copyright (C) 2012 Bolton Software Ltd.
00004  * Copyright (C) 2012 Nick Bolton
00005  * 
00006  * This package is free software; you can redistribute it and/or
00007  * modify it under the terms of the GNU General Public License
00008  * found in the file COPYING that should have accompanied this file.
00009  * 
00010  * This package is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  * GNU General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU General Public License
00016  * along with this program.  If notsee <http://www.gnu.org/licenses/>.
00017  */
00018 
00019 #include "CMSWindowsXInput.h"
00020 #include "XScreen.h"
00021 #include "CThread.h"
00022 #include "TMethodJob.h"
00023 #include "CLog.h"
00024 #include "XInputHook.h"
00025 #include "CMSWindowsScreen.h"
00026 
00027 #include "XInput.h"
00028 
00029 typedef DWORD (WINAPI *XInputGetStateFunc)(DWORD, XINPUT_STATE*);
00030 typedef DWORD (WINAPI *XInputSetStateFunc)(DWORD, XINPUT_VIBRATION*);
00031 
00032 CMSWindowsXInput::CMSWindowsXInput(CMSWindowsScreen* screen, const CGameDeviceInfo& gameDeviceInfo) :
00033 m_screen(screen),
00034 m_gameDeviceInfo(gameDeviceInfo),
00035 m_xInputPollThread(NULL),
00036 m_xInputTimingThread(NULL),
00037 m_xInputFeedbackThread(NULL),
00038 m_gameButtonsLast(0),
00039 m_gameLeftTriggerLast(0),
00040 m_gameRightTriggerLast(0),
00041 m_gameLeftStickXLast(0),
00042 m_gameLeftStickYLast(0),
00043 m_gameRightStickXLast(0),
00044 m_gameRightStickYLast(0),
00045 m_gameLastTimingSent(0),
00046 m_gameTimingWaiting(false),
00047 m_gameFakeLag(0),
00048 m_gamePollFreq(kGamePollFreqDefault),
00049 m_gamePollFreqAdjust(0),
00050 m_gameFakeLagMin(kGamePollFreqDefault),
00051 m_gameTimingStarted(false),
00052 m_gameTimingFirst(0),
00053 m_gameFakeLagLast(0),
00054 m_gameTimingCalibrated(false),
00055 m_xinputModule(NULL)
00056 {
00057     m_xinputModule = LoadLibrary("xinput1_3.dll");
00058     if (m_xinputModule == NULL)
00059     {
00060         throw XScreenXInputFailure("could not load xinput library");
00061     }
00062 
00063     if (screen->isPrimary())
00064     {
00065         // only capture xinput on the server.
00066         m_xInputPollThread = new CThread(new TMethodJob<CMSWindowsXInput>(
00067             this, &CMSWindowsXInput::xInputPollThread));
00068     }
00069     else
00070     {
00071         // check for queued timing requests on client.
00072         m_xInputTimingThread = new CThread(new TMethodJob<CMSWindowsXInput>(
00073             this, &CMSWindowsXInput::xInputTimingThread));
00074 
00075         // check for waiting feedback state on client.
00076         m_xInputFeedbackThread = new CThread(new TMethodJob<CMSWindowsXInput>(
00077             this, &CMSWindowsXInput::xInputFeedbackThread));
00078     }
00079 }
00080 
00081 CMSWindowsXInput::~CMSWindowsXInput()
00082 {
00083 }
00084 
00085 void
00086 CMSWindowsXInput::fakeGameDeviceButtons(GameDeviceID id, GameDeviceButton buttons) const
00087 {
00088     SetXInputButtons(id, buttons);
00089 }
00090 
00091 void
00092 CMSWindowsXInput::fakeGameDeviceSticks(GameDeviceID id, SInt16 x1, SInt16 y1, SInt16 x2, SInt16 y2) const
00093 {
00094     SetXInputSticks(id, x1, y1, x2, y2);
00095 }
00096 
00097 void
00098 CMSWindowsXInput::fakeGameDeviceTriggers(GameDeviceID id, UInt8 t1, UInt8 t2) const
00099 {
00100     SetXInputTriggers(id, t1, t2);
00101 }
00102 
00103 void
00104 CMSWindowsXInput::queueGameDeviceTimingReq() const
00105 {
00106     QueueXInputTimingReq();
00107 }
00108 
00109 void
00110 CMSWindowsXInput::gameDeviceTimingResp(UInt16 freq)
00111 {
00112     if (!m_gameTimingStarted)
00113     {
00114         // record when timing started for calibration period.
00115         m_gameTimingFirst = (UInt16)(ARCH->time() * 1000);
00116         m_gameTimingStarted = true;
00117     }
00118 
00119     m_gameTimingWaiting = false;
00120     m_gameFakeLagLast = m_gameFakeLag;
00121     m_gameFakeLag = (UInt16)((ARCH->time() - m_gameLastTimingSent) * 1000);
00122     m_gameFakeLagRecord.push_back(m_gameFakeLag);
00123 
00124     if (m_gameFakeLag < m_gameFakeLagMin)
00125     {
00126         // record the lowest value so that the poll frequency
00127         // is adjusted to track.
00128         m_gameFakeLagMin = m_gameFakeLag;
00129     }
00130     else if (m_gameFakeLag > (m_gameFakeLagLast * 2))
00131     {
00132         // if fake lag has increased significantly since the last
00133         // timing, then we must have reached the safe minimum.
00134         m_gameFakeLagMin = m_gameFakeLagLast;
00135     }
00136 
00137     // only change poll frequency if it's a sensible value.
00138     if (freq > kGamePollFreqMin && freq < kGamePollFreqMax)
00139     {
00140         m_gamePollFreq = freq;
00141     }
00142 
00143     UInt16 timeSinceStart = ((UInt16)(ARCH->time() * 1000) - m_gameTimingFirst);
00144     if (!m_gameTimingCalibrated && (timeSinceStart < kGameCalibrationPeriod))
00145     {
00146         // during the calibration period, increase polling speed
00147         // to try and find the lowest lag value.
00148         m_gamePollFreqAdjust = 1;
00149         LOG((CLOG_DEBUG2 "calibrating game device poll frequency, start=%d, now=%d, since=%d",
00150             m_gameTimingFirst, (int)(ARCH->time() * 1000), timeSinceStart));
00151     }
00152     else
00153     {
00154         // @bug - calibration seems to re-occur after a period of time,
00155         // though, this would actually be a nice feature (but could be
00156         // a bit risky -- could cause poor game play)... setting this
00157         // stops calibration from happening again.
00158         m_gameTimingCalibrated = true;
00159 
00160         // only adjust poll frequency if outside desired limits.
00161         m_gamePollFreqAdjust = 0;
00162         if (m_gameFakeLag > m_gameFakeLagMin * 3)
00163         {
00164             m_gamePollFreqAdjust = 1;
00165         }
00166         else if (m_gameFakeLag < m_gameFakeLagMin)
00167         {
00168             m_gamePollFreqAdjust = -1;
00169         }
00170     }
00171 
00172     LOG((CLOG_DEBUG3 "game device timing, lag=%dms, freq=%dms, adjust=%dms, min=%dms",
00173         m_gameFakeLag, m_gamePollFreq, m_gamePollFreqAdjust, m_gameFakeLagMin));
00174 
00175     if (m_gameFakeLagRecord.size() >= kGameLagRecordMax)
00176     {
00177         UInt16 v, min = 65535, max = 0, total = 0;
00178         std::vector<UInt16>::iterator it;
00179         for (it = m_gameFakeLagRecord.begin(); it < m_gameFakeLagRecord.end(); ++it)
00180         {
00181             v = *it;
00182             if (v < min)
00183             {
00184                 min = v;
00185             }
00186             if (v > max)
00187             {
00188                 max = v;
00189             }
00190             total += v;
00191         }
00192 
00193         LOG((CLOG_INFO "game device timing, min=%dms, max=%dms, avg=%dms",
00194             min, max, (UInt16)(total / m_gameFakeLagRecord.size())));
00195         m_gameFakeLagRecord.clear();
00196     }
00197 }
00198 
00199 void
00200 CMSWindowsXInput::gameDeviceFeedback(GameDeviceID id, UInt16 m1, UInt16 m2)
00201 {
00202     XInputSetStateFunc xInputSetStateFunc =
00203         (XInputSetStateFunc)GetProcAddress(m_xinputModule, "XInputSetState");
00204 
00205     if (xInputSetStateFunc == NULL)
00206     {
00207         throw XScreenXInputFailure("could not get function address: XInputSetState");
00208     }
00209 
00210     XINPUT_VIBRATION vibration;
00211     ZeroMemory(&vibration, sizeof(XINPUT_VIBRATION));
00212     vibration.wLeftMotorSpeed = m1;
00213     vibration.wRightMotorSpeed = m2;
00214     xInputSetStateFunc(id, &vibration);
00215 }
00216 
00217 void
00218 CMSWindowsXInput::xInputPollThread(void*)
00219 {
00220     LOG((CLOG_DEBUG "xinput poll thread started"));
00221 
00222     XInputGetStateFunc xInputGetStateFunc = 
00223         (XInputGetStateFunc)GetProcAddress(m_xinputModule, "XInputGetState");
00224 
00225     if (xInputGetStateFunc == NULL)
00226     {
00227         throw XScreenXInputFailure("could not get function address: XInputGetState");
00228     }
00229 
00230     int index = 0;
00231     XINPUT_STATE state;
00232     bool end = false;
00233     while (!end)
00234     {
00235         ZeroMemory(&state, sizeof(XINPUT_STATE));
00236         DWORD result = xInputGetStateFunc(index, &state);
00237 
00238         // timeout the timing request after 10 seconds
00239         if (m_gameTimingWaiting && (ARCH->time() - m_gameLastTimingSent > 2))
00240         {
00241             m_gameTimingWaiting = false;
00242             LOG((CLOG_DEBUG "game device timing request timed out"));
00243         }
00244         
00245         // xinput controller is connected
00246         if (result == ERROR_SUCCESS)
00247         {
00248             // @todo game device state class
00249             bool buttonsChanged = state.Gamepad.wButtons != m_gameButtonsLast;
00250             bool leftTriggerChanged = state.Gamepad.bLeftTrigger != m_gameLeftTriggerLast;
00251             bool rightTriggerChanged = state.Gamepad.bRightTrigger != m_gameRightTriggerLast;
00252             bool leftStickXChanged = state.Gamepad.sThumbLX != m_gameLeftStickXLast;
00253             bool leftStickYChanged = state.Gamepad.sThumbLY != m_gameLeftStickYLast;
00254             bool rightStickXChanged = state.Gamepad.sThumbRX != m_gameRightStickXLast;
00255             bool rightStickYChanged = state.Gamepad.sThumbRY != m_gameRightStickYLast;
00256 
00257             m_gameButtonsLast = state.Gamepad.wButtons;
00258             m_gameLeftTriggerLast = state.Gamepad.bLeftTrigger;
00259             m_gameRightTriggerLast = state.Gamepad.bRightTrigger;
00260             m_gameLeftStickXLast = state.Gamepad.sThumbLX;
00261             m_gameLeftStickYLast = state.Gamepad.sThumbLY;
00262             m_gameRightStickXLast = state.Gamepad.sThumbRX;
00263             m_gameRightStickYLast = state.Gamepad.sThumbRY;
00264 
00265             bool eventSent = false;
00266 
00267             if (buttonsChanged)
00268             {
00269                 LOG((CLOG_DEBUG "xinput buttons changed"));
00270 
00271                 // xinput buttons convert exactly to synergy buttons
00272                 m_screen->sendEvent(m_screen->getGameDeviceButtonsEvent(),
00273                     new IPrimaryScreen::CGameDeviceButtonInfo(index, state.Gamepad.wButtons));
00274 
00275                 eventSent = true;
00276             }
00277 
00278             if (leftStickXChanged || leftStickYChanged || rightStickXChanged || rightStickYChanged)
00279             {
00280                 LOG((CLOG_DEBUG "xinput sticks changed"));
00281 
00282                 m_screen->sendEvent(m_screen->getGameDeviceSticksEvent(),
00283                     new IPrimaryScreen::CGameDeviceStickInfo(
00284                     index,
00285                     m_gameLeftStickXLast, m_gameLeftStickYLast,
00286                     m_gameRightStickXLast, m_gameRightStickYLast));
00287 
00288                 eventSent = true;
00289             }
00290 
00291             if (leftTriggerChanged || rightTriggerChanged)
00292             {
00293                 LOG((CLOG_DEBUG "xinput triggers changed"));
00294 
00295                 // @todo seems wrong re-using x/y for a single value...
00296                 m_screen->sendEvent(m_screen->getGameDeviceTriggersEvent(),
00297                     new IPrimaryScreen::CGameDeviceTriggerInfo(
00298                         index,
00299                         state.Gamepad.bLeftTrigger,
00300                         state.Gamepad.bRightTrigger));
00301 
00302                 eventSent = true;
00303             }
00304 
00305             if (/*eventSent && */!m_gameTimingWaiting && (ARCH->time() - m_gameLastTimingSent > .5))
00306             {
00307                 m_screen->sendEvent(m_screen->getGameDeviceTimingReqEvent(), NULL);
00308                 m_gameLastTimingSent = ARCH->time();
00309                 m_gameTimingWaiting = true;
00310 
00311                 LOG((CLOG_DEBUG "game device timing request at %.4f", m_gameLastTimingSent));
00312             }
00313         }
00314 
00315         UInt16 sleep = m_gamePollFreq + m_gamePollFreqAdjust;
00316         LOG((CLOG_DEBUG5 "xinput poll sleeping for %dms", sleep));
00317         Sleep(sleep);
00318     }
00319 }
00320 
00321 void
00322 CMSWindowsXInput::xInputTimingThread(void*)
00323 {
00324     LOG((CLOG_DEBUG "xinput timing thread started"));
00325 
00326     bool end = false;
00327     while (!end)
00328     {
00329         // if timing request was queued, a timing response is queued
00330         // when the xinput status is faked; if it was faked, go tell
00331         // the server when this happened.
00332         if (DequeueXInputTimingResp())
00333         {
00334             LOG((CLOG_DEBUG "dequeued game device timing response"));
00335             m_screen->sendEvent(m_screen->getGameDeviceTimingRespEvent(),
00336                 new IPrimaryScreen::CGameDeviceTimingRespInfo(GetXInputFakeFreqMillis()));
00337         }
00338 
00339         // give the cpu a break.
00340         Sleep(1);
00341     }
00342 }
00343 
00344 void
00345 CMSWindowsXInput::xInputFeedbackThread(void*)
00346 {
00347     LOG((CLOG_DEBUG "xinput feedback thread started"));
00348 
00349     int index = 0;
00350     bool end = false;
00351     while (!end)
00352     {
00353         WORD leftMotor, rightMotor;
00354         if (DequeueXInputFeedback(&leftMotor, &rightMotor))
00355         {
00356             LOG((CLOG_DEBUG "dequeued game device feedback"));
00357             m_screen->sendEvent(m_screen->getGameDeviceFeedbackEvent(),
00358                 new IPrimaryScreen::CGameDeviceFeedbackInfo(index, leftMotor, rightMotor));
00359         }
00360 
00361         Sleep(50);
00362     }
00363 }

Generated on Sun May 19 2013 00:00:05 for Synergy by  doxygen 1.7.1