diff options
| author | Jules Blok <jules.blok@gmail.com> | 2015-06-10 00:00:12 +0200 |
|---|---|---|
| committer | Jules Blok <jules.blok@gmail.com> | 2015-06-10 00:00:12 +0200 |
| commit | 7dfced21a2e5aac7195e0e1ad76468e30306766b (patch) | |
| tree | bd671b639d3d911460b767caabad8fc8a759a6bf /Source/Core/InputCommon/ControllerInterface | |
| parent | c25be031fc1031d795157d8344b87b7841145197 (diff) | |
| parent | 7b0a65e295c784f9d573f2ef52d4e2a7b9bb2889 (diff) | |
Merge branch 'master' into stable
Diffstat (limited to 'Source/Core/InputCommon/ControllerInterface')
36 files changed, 5876 insertions, 0 deletions
diff --git a/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp b/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp new file mode 100644 index 0000000000..b0539b520f --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp @@ -0,0 +1,83 @@ +// Copyright 2013 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <sstream> +#include "InputCommon/ControllerInterface/Android/Android.h" + +namespace ciface +{ + +namespace Android +{ + +void Init( std::vector<Core::Device*>& devices ) +{ + devices.push_back(new Touchscreen(0)); + devices.push_back(new Touchscreen(1)); + devices.push_back(new Touchscreen(2)); + devices.push_back(new Touchscreen(3)); +} + +// Touchscreens and stuff +std::string Touchscreen::GetName() const +{ + return "Touchscreen"; +} + +std::string Touchscreen::GetSource() const +{ + return "Android"; +} + +int Touchscreen::GetId() const +{ + return 0; +} +Touchscreen::Touchscreen(int padID) + : _padID(padID) +{ + AddInput(new Button(_padID, ButtonManager::BUTTON_A)); + AddInput(new Button(_padID, ButtonManager::BUTTON_B)); + AddInput(new Button(_padID, ButtonManager::BUTTON_START)); + AddInput(new Button(_padID, ButtonManager::BUTTON_X)); + AddInput(new Button(_padID, ButtonManager::BUTTON_Y)); + AddInput(new Button(_padID, ButtonManager::BUTTON_Z)); + AddInput(new Button(_padID, ButtonManager::BUTTON_UP)); + AddInput(new Button(_padID, ButtonManager::BUTTON_DOWN)); + AddInput(new Button(_padID, ButtonManager::BUTTON_LEFT)); + AddInput(new Button(_padID, ButtonManager::BUTTON_RIGHT)); + AddAnalogInputs(new Axis(_padID, ButtonManager::STICK_MAIN_LEFT), new Axis(_padID, ButtonManager::STICK_MAIN_RIGHT)); + AddAnalogInputs(new Axis(_padID, ButtonManager::STICK_MAIN_UP), new Axis(_padID, ButtonManager::STICK_MAIN_DOWN)); + AddAnalogInputs(new Axis(_padID, ButtonManager::STICK_C_LEFT), new Axis(_padID, ButtonManager::STICK_C_RIGHT)); + AddAnalogInputs(new Axis(_padID, ButtonManager::STICK_C_UP), new Axis(_padID, ButtonManager::STICK_C_DOWN)); + AddAnalogInputs(new Axis(_padID, ButtonManager::TRIGGER_L), new Axis(_padID, ButtonManager::TRIGGER_L)); + AddAnalogInputs(new Axis(_padID, ButtonManager::TRIGGER_R), new Axis(_padID, ButtonManager::TRIGGER_R)); +} +// Buttons and stuff + +std::string Touchscreen::Button::GetName() const +{ + std::ostringstream ss; + ss << "Button " << (int)_index; + return ss.str(); +} + +ControlState Touchscreen::Button::GetState() const +{ + return ButtonManager::GetButtonPressed(_padID, _index); +} +std::string Touchscreen::Axis::GetName() const +{ + std::ostringstream ss; + ss << "Axis " << (int)_index; + return ss.str(); +} + +ControlState Touchscreen::Axis::GetState() const +{ + return ButtonManager::GetAxisValue(_padID, _index) * _neg; +} + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/Android/Android.h b/Source/Core/InputCommon/ControllerInterface/Android/Android.h new file mode 100644 index 0000000000..35b68cdd71 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/Android/Android.h @@ -0,0 +1,53 @@ +// Copyright 2008 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include "DolphinWX/Android/ButtonManager.h" +#include "InputCommon/ControllerInterface/Device.h" + +namespace ciface +{ +namespace Android +{ + +void Init( std::vector<Core::Device*>& devices ); +class Touchscreen : public Core::Device +{ +private: + class Button : public Input + { + public: + std::string GetName() const; + Button(int padID, ButtonManager::ButtonType index) : _padID(padID), _index(index) {} + ControlState GetState() const; + private: + const int _padID; + const ButtonManager::ButtonType _index; + }; + class Axis : public Input + { + public: + std::string GetName() const; + Axis(int padID, ButtonManager::ButtonType index, float neg = 1.0f) : _padID(padID), _index(index), _neg(neg) {} + ControlState GetState() const; + private: + const int _padID; + const ButtonManager::ButtonType _index; + const float _neg; + }; + +public: + Touchscreen(int padID); + ~Touchscreen() {} + + std::string GetName() const; + int GetId() const; + std::string GetSource() const; +private: + const int _padID; +}; + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp new file mode 100644 index 0000000000..6b8b11fd3a --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp @@ -0,0 +1,262 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Common/Thread.h" +#include "InputCommon/ControllerInterface/ControllerInterface.h" + +#ifdef CIFACE_USE_XINPUT + #include "InputCommon/ControllerInterface/XInput/XInput.h" +#endif +#ifdef CIFACE_USE_DINPUT + #include "InputCommon/ControllerInterface/DInput/DInput.h" +#endif +#ifdef CIFACE_USE_XLIB + #include "InputCommon/ControllerInterface/Xlib/Xlib.h" + #ifdef CIFACE_USE_X11_XINPUT2 + #include "InputCommon/ControllerInterface/Xlib/XInput2.h" + #endif +#endif +#ifdef CIFACE_USE_OSX + #include "InputCommon/ControllerInterface/OSX/OSX.h" +#endif +#ifdef CIFACE_USE_SDL + #include "InputCommon/ControllerInterface/SDL/SDL.h" +#endif +#ifdef CIFACE_USE_ANDROID + #include "InputCommon/ControllerInterface/Android/Android.h" +#endif + +using namespace ciface::ExpressionParser; + +namespace +{ +const ControlState INPUT_DETECT_THRESHOLD = 0.55; +} + +ControllerInterface g_controller_interface; + +// +// Init +// +// Detect devices and inputs outputs / will make refresh function later +// +void ControllerInterface::Initialize(void* const hwnd) +{ + if (m_is_init) + return; + + m_hwnd = hwnd; + +#ifdef CIFACE_USE_DINPUT + ciface::DInput::Init(m_devices, (HWND)hwnd); +#endif +#ifdef CIFACE_USE_XINPUT + ciface::XInput::Init(m_devices); +#endif +#ifdef CIFACE_USE_XLIB + ciface::Xlib::Init(m_devices, hwnd); + #ifdef CIFACE_USE_X11_XINPUT2 + ciface::XInput2::Init(m_devices, hwnd); + #endif +#endif +#ifdef CIFACE_USE_OSX + ciface::OSX::Init(m_devices, hwnd); +#endif +#ifdef CIFACE_USE_SDL + ciface::SDL::Init(m_devices); +#endif +#ifdef CIFACE_USE_ANDROID + ciface::Android::Init(m_devices); +#endif + + m_is_init = true; +} + +void ControllerInterface::Reinitialize() +{ + if (!m_is_init) + return; + + Shutdown(); + Initialize(m_hwnd); +} + +// +// DeInit +// +// Remove all devices/ call library cleanup functions +// +void ControllerInterface::Shutdown() +{ + if (!m_is_init) + return; + + for (ciface::Core::Device* d : m_devices) + { + // Set outputs to ZERO before destroying device + for (ciface::Core::Device::Output* o : d->Outputs()) + o->SetState(0); + + // Delete device + delete d; + } + + m_devices.clear(); + +#ifdef CIFACE_USE_XINPUT + ciface::XInput::DeInit(); +#endif +#ifdef CIFACE_USE_DINPUT + // nothing needed +#endif +#ifdef CIFACE_USE_XLIB + // nothing needed +#endif +#ifdef CIFACE_USE_OSX + ciface::OSX::DeInit(); +#endif +#ifdef CIFACE_USE_SDL + // TODO: there seems to be some sort of memory leak with SDL, quit isn't freeing everything up + SDL_Quit(); +#endif +#ifdef CIFACE_USE_ANDROID + // nothing needed +#endif + + m_is_init = false; +} + +// +// UpdateInput +// +// Update input for all devices, return true if all devices returned successful +// +void ControllerInterface::UpdateInput() +{ + for (ciface::Core::Device* d : m_devices) + d->UpdateInput(); +} + +// +// InputReference :: State +// +// Gets the state of an input reference +// override function for ControlReference::State ... +// +ControlState ControllerInterface::InputReference::State( const ControlState ignore ) +{ + if (parsed_expression) + return parsed_expression->GetValue() * range; + else + return 0.0; +} + +// +// OutputReference :: State +// +// Set the state of all binded outputs +// overrides ControlReference::State .. combined them so I could make the GUI simple / inputs == same as outputs one list +// I was lazy and it works so watever +// +ControlState ControllerInterface::OutputReference::State(const ControlState state) +{ + if (parsed_expression) + parsed_expression->SetValue(state); + return 0.0; +} + +// +// UpdateReference +// +// Updates a controlreference's binded devices/controls +// need to call this to re-parse a control reference's expression after changing it +// +void ControllerInterface::UpdateReference(ControllerInterface::ControlReference* ref + , const ciface::Core::DeviceQualifier& default_device) const +{ + delete ref->parsed_expression; + ref->parsed_expression = nullptr; + + ControlFinder finder(*this, default_device, ref->is_input); + ref->parse_error = ParseExpression(ref->expression, finder, &ref->parsed_expression); +} + +// +// InputReference :: Detect +// +// Wait for input on all binded devices +// supports not detecting inputs that were held down at the time of Detect start, +// which is useful for those crazy flightsticks that have certain buttons that are always held down +// or some crazy axes or something +// upon input, return pointer to detected Control +// else return nullptr +// +ciface::Core::Device::Control* ControllerInterface::InputReference::Detect(const unsigned int ms, ciface::Core::Device* const device) +{ + unsigned int time = 0; + std::vector<bool> states(device->Inputs().size()); + + if (device->Inputs().size() == 0) + return nullptr; + + // get starting state of all inputs, + // so we can ignore those that were activated at time of Detect start + std::vector<ciface::Core::Device::Input*>::const_iterator + i = device->Inputs().begin(), + e = device->Inputs().end(); + for (std::vector<bool>::iterator state = states.begin(); i != e; ++i) + *state++ = ((*i)->GetState() > (1 - INPUT_DETECT_THRESHOLD)); + + while (time < ms) + { + device->UpdateInput(); + i = device->Inputs().begin(); + for (std::vector<bool>::iterator state = states.begin(); i != e; ++i,++state) + { + // detected an input + if ((*i)->IsDetectable() && (*i)->GetState() > INPUT_DETECT_THRESHOLD) + { + // input was released at some point during Detect call + // return the detected input + if (false == *state) + return *i; + } + else if ((*i)->GetState() < (1 - INPUT_DETECT_THRESHOLD)) + { + *state = false; + } + } + Common::SleepCurrentThread(10); time += 10; + } + + // no input was detected + return nullptr; +} + +// +// OutputReference :: Detect +// +// Totally different from the inputReference detect / I have them combined so it was simpler to make the GUI. +// The GUI doesn't know the difference between an input and an output / it's odd but I was lazy and it was easy +// +// set all binded outputs to <range> power for x milliseconds return false +// +ciface::Core::Device::Control* ControllerInterface::OutputReference::Detect(const unsigned int ms, ciface::Core::Device* const device) +{ + // ignore device + + // don't hang if we don't even have any controls mapped + if (BoundCount() > 0) + { + State(1); + unsigned int slept = 0; + + // this loop is to make stuff like flashing keyboard LEDs work + while (ms > (slept += 10)) + Common::SleepCurrentThread(10); + + State(0); + } + return nullptr; +} diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h new file mode 100644 index 0000000000..408a36fefa --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h @@ -0,0 +1,130 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include <algorithm> +#include <map> +#include <sstream> +#include <string> +#include <vector> + +#include "Common/CommonTypes.h" +#include "Common/Thread.h" +#include "InputCommon/ControllerInterface/Device.h" +#include "InputCommon/ControllerInterface/ExpressionParser.h" + +// enable disable sources +#ifdef _WIN32 + #define CIFACE_USE_XINPUT + #define CIFACE_USE_DINPUT +#endif +#if defined(HAVE_X11) && HAVE_X11 + #define CIFACE_USE_XLIB + #if defined(HAVE_X11_XINPUT2) && HAVE_X11_XINPUT2 + #define CIFACE_USE_X11_XINPUT2 + #endif +#endif +#if defined(__APPLE__) + #define CIFACE_USE_OSX +#endif +#ifdef ANDROID + #define CIFACE_USE_ANDROID +#endif +#if defined(HAVE_SDL) && HAVE_SDL + #define CIFACE_USE_SDL +#endif + +// +// ControllerInterface +// +// Some crazy shit I made to control different device inputs and outputs +// from lots of different sources, hopefully more easily. +// +class ControllerInterface : public ciface::Core::DeviceContainer +{ +public: + + // + // ControlReference + // + // These are what you create to actually use the inputs, InputReference or OutputReference. + // + // After being bound to devices and controls with ControllerInterface::UpdateReference, + // each one can link to multiple devices and controls + // when you change a ControlReference's expression, + // you must use ControllerInterface::UpdateReference on it to rebind controls + // + class ControlReference + { + friend class ControllerInterface; + public: + virtual ControlState State(const ControlState state = 0) = 0; + virtual ciface::Core::Device::Control* Detect(const unsigned int ms, ciface::Core::Device* const device) = 0; + + ControlState range; + std::string expression; + const bool is_input; + ciface::ExpressionParser::ExpressionParseStatus parse_error; + + virtual ~ControlReference() + { + delete parsed_expression; + } + + int BoundCount() + { + if (parsed_expression) + return parsed_expression->num_controls; + else + return 0; + } + + protected: + ControlReference(const bool _is_input) : range(1), is_input(_is_input), parsed_expression(nullptr) {} + ciface::ExpressionParser::Expression *parsed_expression; + }; + + // + // InputReference + // + // Control reference for inputs + // + class InputReference : public ControlReference + { + public: + InputReference() : ControlReference(true) {} + ControlState State(const ControlState state) override; + ciface::Core::Device::Control* Detect(const unsigned int ms, ciface::Core::Device* const device) override; + }; + + // + // OutputReference + // + // Control reference for outputs + // + class OutputReference : public ControlReference + { + public: + OutputReference() : ControlReference(false) {} + ControlState State(const ControlState state) override; + ciface::Core::Device::Control* Detect(const unsigned int ms, ciface::Core::Device* const device) override; + }; + + ControllerInterface() : m_is_init(false), m_hwnd(nullptr) {} + + void Initialize(void* const hwnd); + void Reinitialize(); + void Shutdown(); + bool IsInit() const { return m_is_init; } + + void UpdateReference(ControlReference* control, const ciface::Core::DeviceQualifier& default_device) const; + void UpdateInput(); + +private: + bool m_is_init; + void* m_hwnd; +}; + +extern ControllerInterface g_controller_interface; diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInput.cpp b/Source/Core/InputCommon/ControllerInterface/DInput/DInput.cpp new file mode 100644 index 0000000000..4a68474618 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/DInput/DInput.cpp @@ -0,0 +1,65 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Common/StringUtil.h" + +#include "InputCommon/ControllerInterface/DInput/DInput.h" + +#include "InputCommon/ControllerInterface/DInput/DInputJoystick.h" +#include "InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h" + +#pragma comment(lib, "Dinput8.lib") +#pragma comment(lib, "dxguid.lib") + +namespace ciface +{ +namespace DInput +{ + +BOOL CALLBACK DIEnumDeviceObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef) +{ + ((std::list<DIDEVICEOBJECTINSTANCE>*)pvRef)->push_back(*lpddoi); + return DIENUM_CONTINUE; +} + +BOOL CALLBACK DIEnumDevicesCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef) +{ + ((std::list<DIDEVICEINSTANCE>*)pvRef)->push_back(*lpddi); + return DIENUM_CONTINUE; +} + +std::string GetDeviceName(const LPDIRECTINPUTDEVICE8 device) +{ + DIPROPSTRING str = {}; + str.diph.dwSize = sizeof(str); + str.diph.dwHeaderSize = sizeof(str.diph); + str.diph.dwHow = DIPH_DEVICE; + + std::string result; + if (SUCCEEDED(device->GetProperty(DIPROP_PRODUCTNAME, &str.diph))) + { + result = StripSpaces(UTF16ToUTF8(str.wsz)); + } + + return result; +} + +void Init(std::vector<Core::Device*>& devices, HWND hwnd) +{ + IDirectInput8* idi8; + if (FAILED(DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, + IID_IDirectInput8, (LPVOID*)&idi8, nullptr))) + { + return; + } + + InitKeyboardMouse(idi8, devices, hwnd); + InitJoystick(idi8, devices, hwnd); + + idi8->Release(); + +} + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInput.h b/Source/Core/InputCommon/ControllerInterface/DInput/DInput.h new file mode 100644 index 0000000000..1823499e98 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/DInput/DInput.h @@ -0,0 +1,28 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#define DINPUT_SOURCE_NAME "DInput" + +#include <list> +#include <windows.h> + +#include "InputCommon/ControllerInterface/Device.h" +#include "InputCommon/ControllerInterface/DInput/DInput8.h" + +namespace ciface +{ +namespace DInput +{ + +//BOOL CALLBACK DIEnumEffectsCallback(LPCDIEFFECTINFO pdei, LPVOID pvRef); +BOOL CALLBACK DIEnumDeviceObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef); +BOOL CALLBACK DIEnumDevicesCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef); +std::string GetDeviceName(const LPDIRECTINPUTDEVICE8 device); + +void Init(std::vector<Core::Device*>& devices, HWND hwnd); + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInput8.h b/Source/Core/InputCommon/ControllerInterface/DInput/DInput8.h new file mode 100644 index 0000000000..4a32edb632 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/DInput/DInput8.h @@ -0,0 +1,8 @@ +// Copyright 2008 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#define DIRECTINPUT_VERSION 0x0800 +#include <dinput.h> diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp b/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp new file mode 100644 index 0000000000..aa1e972126 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp @@ -0,0 +1,281 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <algorithm> +#include <map> +#include <sstream> + +#include "InputCommon/ControllerInterface/DInput/DInput.h" +#include "InputCommon/ControllerInterface/DInput/DInputJoystick.h" +#include "InputCommon/ControllerInterface/DInput/XInputFilter.h" + +namespace ciface +{ +namespace DInput +{ + +#define DATA_BUFFER_SIZE 32 + +void InitJoystick(IDirectInput8* const idi8, std::vector<Core::Device*>& devices, HWND hwnd) +{ + std::list<DIDEVICEINSTANCE> joysticks; + idi8->EnumDevices(DI8DEVCLASS_GAMECTRL, DIEnumDevicesCallback, (LPVOID)&joysticks, DIEDFL_ATTACHEDONLY); + + // this is used to number the joysticks + // multiple joysticks with the same name shall get unique ids starting at 0 + std::map< std::basic_string<TCHAR>, int> name_counts; + + std::vector<DWORD> xinput_guids; + GetXInputGUIDS(&xinput_guids); + + for (DIDEVICEINSTANCE& joystick : joysticks) + { + // skip XInput Devices + if (std::find(xinput_guids.begin(), xinput_guids.end(), joystick.guidProduct.Data1) != xinput_guids.end()) + { + continue; + } + + LPDIRECTINPUTDEVICE8 js_device; + if (SUCCEEDED(idi8->CreateDevice(joystick.guidInstance, &js_device, nullptr))) + { + if (SUCCEEDED(js_device->SetDataFormat(&c_dfDIJoystick))) + { + if (FAILED(js_device->SetCooperativeLevel(GetAncestor(hwnd, GA_ROOT), DISCL_BACKGROUND | DISCL_EXCLUSIVE))) + { + //PanicAlert("SetCooperativeLevel(DISCL_EXCLUSIVE) failed!"); + // fall back to non-exclusive mode, with no rumble + if (FAILED(js_device->SetCooperativeLevel(nullptr, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE))) + { + //PanicAlert("SetCooperativeLevel failed!"); + js_device->Release(); + continue; + } + } + + Joystick* js = new Joystick(/*&*i, */js_device, name_counts[joystick.tszInstanceName]++); + // only add if it has some inputs/outputs + if (js->Inputs().size() || js->Outputs().size()) + devices.push_back(js); + else + delete js; + } + else + { + //PanicAlert("SetDataFormat failed!"); + js_device->Release(); + } + } + } +} + +Joystick::Joystick( /*const LPCDIDEVICEINSTANCE lpddi, */const LPDIRECTINPUTDEVICE8 device, const unsigned int index ) + : m_device(device) + , m_index(index) + //, m_name(TStringToString(lpddi->tszInstanceName)) +{ + // seems this needs to be done before GetCapabilities + // polled or buffered data + DIPROPDWORD dipdw; + dipdw.diph.dwSize = sizeof(DIPROPDWORD); + dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); + dipdw.diph.dwObj = 0; + dipdw.diph.dwHow = DIPH_DEVICE; + dipdw.dwData = DATA_BUFFER_SIZE; + // set the buffer size, + // if we can't set the property, we can't use buffered data + m_buffered = SUCCEEDED(m_device->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph)); + + // seems this needs to be done after SetProperty of buffer size + m_device->Acquire(); + + // get joystick caps + DIDEVCAPS js_caps; + js_caps.dwSize = sizeof(js_caps); + if (FAILED(m_device->GetCapabilities(&js_caps))) + return; + + // max of 32 buttons and 4 hats / the limit of the data format I am using + js_caps.dwButtons = std::min((DWORD)32, js_caps.dwButtons); + js_caps.dwPOVs = std::min((DWORD)4, js_caps.dwPOVs); + + //m_must_poll = (js_caps.dwFlags & DIDC_POLLEDDATAFORMAT) != 0; + + // buttons + for (u8 i = 0; i != js_caps.dwButtons; ++i) + AddInput(new Button(i, m_state_in.rgbButtons[i])); + + // hats + for (u8 i = 0; i != js_caps.dwPOVs; ++i) + { + // each hat gets 4 input instances associated with it, (up down left right) + for (u8 d = 0; d != 4; ++d) + AddInput(new Hat(i, m_state_in.rgdwPOV[i], d)); + } + + // get up to 6 axes and 2 sliders + DIPROPRANGE range; + range.diph.dwSize = sizeof(range); + range.diph.dwHeaderSize = sizeof(range.diph); + range.diph.dwHow = DIPH_BYOFFSET; + // screw EnumObjects, just go through all the axis offsets and try to GetProperty + // this should be more foolproof, less code, and probably faster + for (unsigned int offset = 0; offset < DIJOFS_BUTTON(0) / sizeof(LONG); ++offset) + { + range.diph.dwObj = offset * sizeof(LONG); + // try to set some nice power of 2 values (128) to match the GameCube controls + range.lMin = -(1 << 7); + range.lMax = (1 << 7); + m_device->SetProperty(DIPROP_RANGE, &range.diph); + // but I guess not all devices support setting range + // so I getproperty right afterward incase it didn't set. + // This also checks that the axis is present + if (SUCCEEDED(m_device->GetProperty(DIPROP_RANGE, &range.diph))) + { + const LONG base = (range.lMin + range.lMax) / 2; + const LONG& ax = (&m_state_in.lX)[offset]; + + // each axis gets a negative and a positive input instance associated with it + AddAnalogInputs(new Axis(offset, ax, base, range.lMin-base), + new Axis(offset, ax, base, range.lMax-base)); + } + } + + // force feedback + std::list<DIDEVICEOBJECTINSTANCE> objects; + if (SUCCEEDED(m_device->EnumObjects(DIEnumDeviceObjectsCallback, (LPVOID)&objects, DIDFT_AXIS))) + { + InitForceFeedback(m_device, (int)objects.size()); + } + + ZeroMemory(&m_state_in, sizeof(m_state_in)); + // set hats to center + memset(m_state_in.rgdwPOV, 0xFF, sizeof(m_state_in.rgdwPOV)); +} + +Joystick::~Joystick() +{ + m_device->Unacquire(); + m_device->Release(); +} + +std::string Joystick::GetName() const +{ + return GetDeviceName(m_device); +} + +int Joystick::GetId() const +{ + return m_index; +} + +std::string Joystick::GetSource() const +{ + return DINPUT_SOURCE_NAME; +} + +// update IO + +void Joystick::UpdateInput() +{ + HRESULT hr = 0; + + // just always poll, + // MSDN says if this isn't needed it doesn't do anything + m_device->Poll(); + + if (m_buffered) + { + DIDEVICEOBJECTDATA evtbuf[DATA_BUFFER_SIZE]; + DWORD numevents = DATA_BUFFER_SIZE; + hr = m_device->GetDeviceData(sizeof(*evtbuf), evtbuf, &numevents, 0); + + if (SUCCEEDED(hr)) + { + for (LPDIDEVICEOBJECTDATA evt = evtbuf; evt != (evtbuf + numevents); ++evt) + { + // all the buttons are at the end of the data format + // they are bytes rather than longs + if (evt->dwOfs < DIJOFS_BUTTON(0)) + *(DWORD*)(((BYTE*)&m_state_in) + evt->dwOfs) = evt->dwData; + else + ((BYTE*)&m_state_in)[evt->dwOfs] = (BYTE)evt->dwData; + } + + // seems like this needs to be done maybe... + if (DI_BUFFEROVERFLOW == hr) + hr = m_device->GetDeviceState(sizeof(m_state_in), &m_state_in); + } + } + else + { + hr = m_device->GetDeviceState(sizeof(m_state_in), &m_state_in); + } + + // try reacquire if input lost + if (DIERR_INPUTLOST == hr || DIERR_NOTACQUIRED == hr) + m_device->Acquire(); +} + +// get name + +std::string Joystick::Button::GetName() const +{ + std::ostringstream ss; + ss << "Button " << (int)m_index; + return ss.str(); +} + +std::string Joystick::Axis::GetName() const +{ + std::ostringstream ss; + // axis + if (m_index < 6) + { + ss << "Axis " << (char)('X' + (m_index % 3)); + if (m_index > 2) + ss << 'r'; + } + // slider + else + { + ss << "Slider " << (int)(m_index - 6); + } + + ss << (m_range < 0 ? '-' : '+'); + return ss.str(); +} + +std::string Joystick::Hat::GetName() const +{ + static char tmpstr[] = "Hat . ."; + tmpstr[4] = (char)('0' + m_index); + tmpstr[6] = "NESW"[m_direction]; + return tmpstr; +} + +// get / set state + +ControlState Joystick::Axis::GetState() const +{ + return std::max(0.0, ControlState(m_axis - m_base) / m_range); +} + +ControlState Joystick::Button::GetState() const +{ + return ControlState(m_button > 0); +} + +ControlState Joystick::Hat::GetState() const +{ + // can this func be simplified ? + // hat centered code from MSDN + if (0xFFFF == LOWORD(m_hat)) + return 0; + + return (abs((int)(m_hat / 4500 - m_direction * 2 + 8) % 8 - 4) > 2); +} + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.h b/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.h new file mode 100644 index 0000000000..c89ba509d3 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.h @@ -0,0 +1,74 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include "InputCommon/ControllerInterface/Device.h" +#include "InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h" + +namespace ciface +{ +namespace DInput +{ + +void InitJoystick(IDirectInput8* const idi8, std::vector<Core::Device*>& devices, HWND hwnd); + +class Joystick : public ForceFeedback::ForceFeedbackDevice +{ +private: + class Button : public Input + { + public: + std::string GetName() const; + Button(u8 index, const BYTE& button) : m_index(index), m_button(button) {} + ControlState GetState() const; + private: + const BYTE& m_button; + const u8 m_index; + }; + + class Axis : public Input + { + public: + std::string GetName() const; + Axis(u8 index, const LONG& axis, LONG base, LONG range) : m_index(index), m_axis(axis), m_base(base), m_range(range) {} + ControlState GetState() const; + private: + const LONG& m_axis; + const LONG m_base, m_range; + const u8 m_index; + }; + + class Hat : public Input + { + public: + std::string GetName() const; + Hat(u8 index, const DWORD& hat, u8 direction) : m_index(index), m_hat(hat), m_direction(direction) {} + ControlState GetState() const; + private: + const DWORD& m_hat; + const u8 m_index, m_direction; + }; + +public: + void UpdateInput() override; + + Joystick(const LPDIRECTINPUTDEVICE8 device, const unsigned int index); + ~Joystick(); + + std::string GetName() const; + int GetId() const; + std::string GetSource() const; + +private: + const LPDIRECTINPUTDEVICE8 m_device; + const unsigned int m_index; + + DIJOYSTATE m_state_in; + + bool m_buffered; +}; + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.cpp b/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.cpp new file mode 100644 index 0000000000..6db8a86a3a --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.cpp @@ -0,0 +1,247 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <algorithm> + +#include "InputCommon/ControllerInterface/DInput/DInput.h" +#include "InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h" + + // (lower would be more sensitive) user can lower sensitivity by setting range + // seems decent here ( at 8 ), I don't think anyone would need more sensitive than this + // and user can lower it much farther than they would want to with the range +#define MOUSE_AXIS_SENSITIVITY 8 + + // if input hasn't been received for this many ms, mouse input will be skipped + // otherwise it is just some crazy value +#define DROP_INPUT_TIME 250 + +namespace ciface +{ +namespace DInput +{ + +static const struct +{ + const BYTE code; + const char* const name; +} named_keys[] = +{ +#include "InputCommon/ControllerInterface/DInput/NamedKeys.h" // NOLINT +}; + +// lil silly +static HWND m_hwnd; + +void InitKeyboardMouse(IDirectInput8* const idi8, std::vector<Core::Device*>& devices, HWND _hwnd) +{ + m_hwnd = _hwnd; + + // mouse and keyboard are a combined device, to allow shift+click and stuff + // if that's dumb, I will make a VirtualDevice class that just uses ranges of inputs/outputs from other devices + // so there can be a separated Keyboard and mouse, as well as combined KeyboardMouse + + LPDIRECTINPUTDEVICE8 kb_device = nullptr; + LPDIRECTINPUTDEVICE8 mo_device = nullptr; + + if (SUCCEEDED(idi8->CreateDevice( GUID_SysKeyboard, &kb_device, nullptr))) + { + if (SUCCEEDED(kb_device->SetDataFormat(&c_dfDIKeyboard))) + { + if (SUCCEEDED(kb_device->SetCooperativeLevel(nullptr, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE))) + { + if (SUCCEEDED(idi8->CreateDevice( GUID_SysMouse, &mo_device, nullptr ))) + { + if (SUCCEEDED(mo_device->SetDataFormat(&c_dfDIMouse2))) + { + if (SUCCEEDED(mo_device->SetCooperativeLevel(nullptr, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE))) + { + devices.push_back(new KeyboardMouse(kb_device, mo_device)); + return; + } + } + } + } + } + } + + if (kb_device) + kb_device->Release(); + if (mo_device) + mo_device->Release(); +} + +KeyboardMouse::~KeyboardMouse() +{ + // kb + m_kb_device->Unacquire(); + m_kb_device->Release(); + // mouse + m_mo_device->Unacquire(); + m_mo_device->Release(); +} + +KeyboardMouse::KeyboardMouse(const LPDIRECTINPUTDEVICE8 kb_device, const LPDIRECTINPUTDEVICE8 mo_device) + : m_kb_device(kb_device) + , m_mo_device(mo_device) +{ + m_kb_device->Acquire(); + m_mo_device->Acquire(); + + m_last_update = GetTickCount(); + + ZeroMemory(&m_state_in, sizeof(m_state_in)); + + // KEYBOARD + // add keys + for (u8 i = 0; i < sizeof(named_keys)/sizeof(*named_keys); ++i) + AddInput(new Key(i, m_state_in.keyboard[named_keys[i].code])); + + // MOUSE + // get caps + DIDEVCAPS mouse_caps; + ZeroMemory( &mouse_caps, sizeof(mouse_caps) ); + mouse_caps.dwSize = sizeof(mouse_caps); + m_mo_device->GetCapabilities(&mouse_caps); + // mouse buttons + for (u8 i = 0; i < mouse_caps.dwButtons; ++i) + AddInput(new Button(i, m_state_in.mouse.rgbButtons[i])); + // mouse axes + for (unsigned int i = 0; i < mouse_caps.dwAxes; ++i) + { + const LONG& ax = (&m_state_in.mouse.lX)[i]; + + // each axis gets a negative and a positive input instance associated with it + AddInput(new Axis(i, ax, (2==i) ? -1 : -MOUSE_AXIS_SENSITIVITY)); + AddInput(new Axis(i, ax, -(2==i) ? 1 : MOUSE_AXIS_SENSITIVITY)); + } + // cursor, with a hax for-loop + for (unsigned int i=0; i<4; ++i) + AddInput(new Cursor(!!(i&2), (&m_state_in.cursor.x)[i/2], !!(i&1))); +} + +void GetMousePos(ControlState* const x, ControlState* const y) +{ + POINT point = { 1, 1 }; + GetCursorPos(&point); + // Get the cursor position relative to the upper left corner of the current window (separate or render to main) + HWND hwnd = WindowFromPoint(point); + ScreenToClient(hwnd, &point); + + // Get the size of the current window. (In my case Rect.top and Rect.left was zero.) + RECT rect; + GetClientRect(hwnd, &rect); + // Width and height is the size of the rendering window + unsigned int win_width = rect.right - rect.left; + unsigned int win_height = rect.bottom - rect.top; + + // Return the mouse position as a range from -1 to 1 + *x = (ControlState)point.x / (ControlState)win_width * 2 - 1; + *y = (ControlState)point.y / (ControlState)win_height * 2 - 1; +} + +void KeyboardMouse::UpdateInput() +{ + DIMOUSESTATE2 tmp_mouse; + + // if mouse position hasn't been updated in a short while, skip a dev state + DWORD cur_time = GetTickCount(); + if (cur_time - m_last_update > DROP_INPUT_TIME) + { + // set axes to zero + ZeroMemory(&m_state_in.mouse, sizeof(m_state_in.mouse)); + // skip this input state + m_mo_device->GetDeviceState(sizeof(tmp_mouse), &tmp_mouse); + } + + m_last_update = cur_time; + + HRESULT kb_hr = m_kb_device->GetDeviceState(sizeof(m_state_in.keyboard), &m_state_in.keyboard); + HRESULT mo_hr = m_mo_device->GetDeviceState(sizeof(tmp_mouse), &tmp_mouse); + + if (DIERR_INPUTLOST == kb_hr || DIERR_NOTACQUIRED == kb_hr) + m_kb_device->Acquire(); + + if (DIERR_INPUTLOST == mo_hr || DIERR_NOTACQUIRED == mo_hr) + m_mo_device->Acquire(); + + if (SUCCEEDED(kb_hr) && SUCCEEDED(mo_hr)) + { + // need to smooth out the axes, otherwise it doesn't work for shit + for (unsigned int i = 0; i < 3; ++i) + ((&m_state_in.mouse.lX)[i] += (&tmp_mouse.lX)[i]) /= 2; + + // copy over the buttons + memcpy(m_state_in.mouse.rgbButtons, tmp_mouse.rgbButtons, sizeof(m_state_in.mouse.rgbButtons)); + + // update mouse cursor + GetMousePos(&m_state_in.cursor.x, &m_state_in.cursor.y); + } +} + +std::string KeyboardMouse::GetName() const +{ + return "Keyboard Mouse"; +} + +int KeyboardMouse::GetId() const +{ + // should this be -1, idk + return 0; +} + +std::string KeyboardMouse::GetSource() const +{ + return DINPUT_SOURCE_NAME; +} + +// names +std::string KeyboardMouse::Key::GetName() const +{ + return named_keys[m_index].name; +} + +std::string KeyboardMouse::Button::GetName() const +{ + return std::string("Click ") + char('0' + m_index); +} + +std::string KeyboardMouse::Axis::GetName() const +{ + static char tmpstr[] = "Axis .."; + tmpstr[5] = (char)('X' + m_index); + tmpstr[6] = (m_range<0 ? '-' : '+'); + return tmpstr; +} + +std::string KeyboardMouse::Cursor::GetName() const +{ + static char tmpstr[] = "Cursor .."; + tmpstr[7] = (char)('X' + m_index); + tmpstr[8] = (m_positive ? '+' : '-'); + return tmpstr; +} + +// get/set state +ControlState KeyboardMouse::Key::GetState() const +{ + return (m_key != 0); +} + +ControlState KeyboardMouse::Button::GetState() const +{ + return (m_button != 0); +} + +ControlState KeyboardMouse::Axis::GetState() const +{ + return std::max(0.0, ControlState(m_axis) / m_range); +} + +ControlState KeyboardMouse::Cursor::GetState() const +{ + return std::max(0.0, ControlState(m_axis) / (m_positive ? 1.0 : -1.0)); +} + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h b/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h new file mode 100644 index 0000000000..db4d363df0 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h @@ -0,0 +1,98 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include <windows.h> + +#include "InputCommon/ControllerInterface/Device.h" +#include "InputCommon/ControllerInterface/DInput/DInput8.h" + +namespace ciface +{ +namespace DInput +{ + +void InitKeyboardMouse(IDirectInput8* const idi8, std::vector<Core::Device*>& devices, HWND _hwnd); + +class KeyboardMouse : public Core::Device +{ +private: + struct State + { + BYTE keyboard[256]; + DIMOUSESTATE2 mouse; + struct + { + ControlState x, y; + } cursor; + }; + + class Key : public Input + { + public: + std::string GetName() const; + Key(u8 index, const BYTE& key) : m_index(index), m_key(key) {} + ControlState GetState() const; + private: + const BYTE& m_key; + const u8 m_index; + }; + + class Button : public Input + { + public: + std::string GetName() const; + Button(u8 index, const BYTE& button) : m_index(index), m_button(button) {} + ControlState GetState() const; + private: + const BYTE& m_button; + const u8 m_index; + }; + + class Axis : public Input + { + public: + std::string GetName() const; + Axis(u8 index, const LONG& axis, LONG range) : m_index(index), m_axis(axis), m_range(range) {} + ControlState GetState() const; + private: + const LONG& m_axis; + const LONG m_range; + const u8 m_index; + }; + + class Cursor : public Input + { + public: + std::string GetName() const; + bool IsDetectable() { return false; } + Cursor(u8 index, const ControlState& axis, const bool positive) : m_index(index), m_axis(axis), m_positive(positive) {} + ControlState GetState() const; + private: + const ControlState& m_axis; + const u8 m_index; + const bool m_positive; + }; + +public: + void UpdateInput() override; + + KeyboardMouse(const LPDIRECTINPUTDEVICE8 kb_device, const LPDIRECTINPUTDEVICE8 mo_device); + ~KeyboardMouse(); + + std::string GetName() const; + int GetId() const; + std::string GetSource() const; + +private: + const LPDIRECTINPUTDEVICE8 m_kb_device; + const LPDIRECTINPUTDEVICE8 m_mo_device; + + DWORD m_last_update; + State m_state_in; +}; + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/NamedKeys.h b/Source/Core/InputCommon/ControllerInterface/DInput/NamedKeys.h new file mode 100644 index 0000000000..6802fd7888 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/DInput/NamedKeys.h @@ -0,0 +1,148 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +{ DIK_A, "A" }, +{ DIK_B, "B" }, +{ DIK_C, "C" }, +{ DIK_D, "D" }, +{ DIK_E, "E" }, +{ DIK_F, "F" }, +{ DIK_G, "G" }, +{ DIK_H, "H" }, +{ DIK_I, "I" }, +{ DIK_J, "J" }, +{ DIK_K, "K" }, +{ DIK_L, "L" }, +{ DIK_M, "M" }, +{ DIK_N, "N" }, +{ DIK_O, "O" }, +{ DIK_P, "P" }, +{ DIK_Q, "Q" }, +{ DIK_R, "R" }, +{ DIK_S, "S" }, +{ DIK_T, "T" }, +{ DIK_U, "U" }, +{ DIK_V, "V" }, +{ DIK_W, "W" }, +{ DIK_X, "X" }, +{ DIK_Y, "Y" }, +{ DIK_Z, "Z" }, +{ DIK_0, "0" }, +{ DIK_1, "1" }, +{ DIK_2, "2" }, +{ DIK_3, "3" }, +{ DIK_4, "4" }, +{ DIK_5, "5" }, +{ DIK_6, "6" }, +{ DIK_7, "7" }, +{ DIK_8, "8" }, +{ DIK_9, "9" }, +{ DIK_UP, "UP" }, +{ DIK_DOWN, "DOWN" }, +{ DIK_LEFT, "LEFT" }, +{ DIK_RIGHT, "RIGHT" }, +{ DIK_ABNT_C1, "ABNT_C1" }, +{ DIK_ABNT_C2, "ABNT_C2" }, +{ DIK_ADD, "ADD" }, +{ DIK_APOSTROPHE, "APOSTROPHE" }, +{ DIK_APPS, "APPS" }, +{ DIK_AT, "AT" }, +{ DIK_AX, "AX" }, +{ DIK_BACK, "BACK" }, +{ DIK_BACKSLASH, "BACKSLASH" }, +{ DIK_CALCULATOR, "CALCULATOR" }, +{ DIK_CAPITAL, "CAPITAL" }, +{ DIK_COLON, "COLON" }, +{ DIK_COMMA, "COMMA" }, +{ DIK_CONVERT, "CONVERT" }, +{ DIK_DECIMAL, "DECIMAL" }, +{ DIK_DELETE, "DELETE" }, +{ DIK_DIVIDE, "DIVIDE" }, +{ DIK_EQUALS, "EQUALS" }, +{ DIK_ESCAPE, "ESCAPE" }, +{ DIK_F1, "F1" }, +{ DIK_F2, "F2" }, +{ DIK_F3, "F3" }, +{ DIK_F4, "F4" }, +{ DIK_F5, "F5" }, +{ DIK_F6, "F6" }, +{ DIK_F7, "F7" }, +{ DIK_F8, "F8" }, +{ DIK_F9, "F9" }, +{ DIK_F10, "F10" }, +{ DIK_F11, "F11" }, +{ DIK_F12, "F12" }, +{ DIK_F13, "F13" }, +{ DIK_F14, "F14" }, +{ DIK_F15, "F15" }, +{ DIK_GRAVE, "GRAVE" }, +{ DIK_HOME, "HOME" }, +{ DIK_END, "END" }, +{ DIK_INSERT, "INSERT" }, +{ DIK_KANA, "KANA" }, +{ DIK_KANJI, "KANJI" }, +{ DIK_MAIL, "MAIL" }, +{ DIK_MEDIASELECT, "MEDIASELECT" }, +{ DIK_MEDIASTOP, "MEDIASTOP" }, +{ DIK_MINUS, "MINUS" }, +{ DIK_MULTIPLY, "MULTIPLY" }, +{ DIK_MUTE, "MUTE" }, +{ DIK_MYCOMPUTER, "MYCOMPUTER" }, +{ DIK_NEXTTRACK, "NEXTTRACK" }, +{ DIK_NOCONVERT, "NOCONVERT" }, +{ DIK_NUMLOCK, "NUMLOCK" }, +{ DIK_NUMPAD0, "NUMPAD0" }, +{ DIK_NUMPAD1, "NUMPAD1" }, +{ DIK_NUMPAD2, "NUMPAD2" }, +{ DIK_NUMPAD3, "NUMPAD3" }, +{ DIK_NUMPAD4, "NUMPAD4" }, +{ DIK_NUMPAD5, "NUMPAD5" }, +{ DIK_NUMPAD6, "NUMPAD6" }, +{ DIK_NUMPAD7, "NUMPAD7" }, +{ DIK_NUMPAD8, "NUMPAD8" }, +{ DIK_NUMPAD9, "NUMPAD9" }, +{ DIK_NUMPADCOMMA, "NUMPADCOMMA" }, +{ DIK_NUMPADENTER, "NUMPADENTER" }, +{ DIK_NUMPADEQUALS, "NUMPADEQUALS" }, +{ DIK_OEM_102, "OEM_102" }, +{ DIK_PAUSE, "PAUSE" }, +{ DIK_PERIOD, "PERIOD" }, +{ DIK_PLAYPAUSE, "PLAYPAUSE" }, +{ DIK_POWER, "POWER" }, +{ DIK_PREVTRACK, "PREVTRACK" }, +{ DIK_PRIOR, "PRIOR" }, +{ DIK_NEXT, "NEXT" }, +{ DIK_RETURN, "RETURN" }, +{ DIK_LBRACKET, "LBRACKET" }, +{ DIK_RBRACKET, "RBRACKET" }, +{ DIK_LCONTROL, "LCONTROL" }, +{ DIK_RCONTROL, "RCONTROL" }, +{ DIK_LMENU, "LMENU" }, +{ DIK_RMENU, "RMENU" }, +{ DIK_LSHIFT, "LSHIFT" }, +{ DIK_RSHIFT, "RSHIFT" }, +{ DIK_LWIN, "LWIN" }, +{ DIK_RWIN, "RWIN" }, +{ DIK_SCROLL, "SCROLL" }, +{ DIK_SEMICOLON, "SEMICOLON" }, +{ DIK_SLASH, "SLASH" }, +{ DIK_SLEEP, "SLEEP" }, +{ DIK_SPACE, "SPACE" }, +{ DIK_STOP, "STOP" }, +{ DIK_SUBTRACT, "SUBTRACT" }, +{ DIK_SYSRQ, "SYSRQ" }, +{ DIK_TAB, "TAB" }, +{ DIK_UNDERLINE, "UNDERLINE" }, +{ DIK_UNLABELED, "UNLABELED" }, +{ DIK_VOLUMEDOWN, "VOLUMEDOWN" }, +{ DIK_VOLUMEUP, "VOLUMEUP" }, +{ DIK_WAKE, "WAKE" }, +{ DIK_WEBBACK, "WEBBACK" }, +{ DIK_WEBFAVORITES, "WEBFAVORITES" }, +{ DIK_WEBFORWARD, "WEBFORWARD" }, +{ DIK_WEBHOME, "WEBHOME" }, +{ DIK_WEBREFRESH, "WEBREFRESH" }, +{ DIK_WEBSEARCH, "WEBSEARCH" }, +{ DIK_WEBSTOP, "WEBSTOP" }, +{ DIK_YEN, "YEN" }, diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/XInputFilter.cpp b/Source/Core/InputCommon/ControllerInterface/DInput/XInputFilter.cpp new file mode 100644 index 0000000000..aedfc5dc44 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/DInput/XInputFilter.cpp @@ -0,0 +1,123 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +// This function is contained in a separate file because WbemIdl.h pulls in files which break on +// /Zc:strictStrings, so this compilation unit is compiled without /Zc:strictStrings. + +#include <vector> +#include <WbemIdl.h> +#include <Windows.h> + +#define SAFE_RELEASE(p) { if (p) { (p)->Release(); (p)=nullptr; } } + +namespace ciface +{ +namespace DInput +{ + +//----------------------------------------------------------------------------- +// Modified some MSDN code to get all the XInput device GUID.Data1 values in a vector, +// faster than checking all the devices for each DirectInput device, like MSDN says to do +//----------------------------------------------------------------------------- +void GetXInputGUIDS(std::vector<DWORD>* guids) +{ + IWbemLocator* pIWbemLocator = nullptr; + IEnumWbemClassObject* pEnumDevices = nullptr; + IWbemClassObject* pDevices[20] = {0}; + IWbemServices* pIWbemServices = nullptr; + BSTR bstrNamespace = nullptr; + BSTR bstrDeviceID = nullptr; + BSTR bstrClassName = nullptr; + DWORD uReturned = 0; + VARIANT var; + HRESULT hr; + + // CoInit if needed + hr = CoInitialize(nullptr); + bool bCleanupCOM = SUCCEEDED(hr); + + // Create WMI + hr = CoCreateInstance(__uuidof(WbemLocator), + nullptr, + CLSCTX_INPROC_SERVER, + __uuidof(IWbemLocator), + (LPVOID*) &pIWbemLocator); + if (FAILED(hr) || pIWbemLocator == nullptr) + goto LCleanup; + + bstrNamespace = SysAllocString(L"\\\\.\\root\\cimv2"); if (bstrNamespace == nullptr) goto LCleanup; + bstrClassName = SysAllocString(L"Win32_PNPEntity"); if (bstrClassName == nullptr) goto LCleanup; + bstrDeviceID = SysAllocString(L"DeviceID"); if (bstrDeviceID == nullptr) goto LCleanup; + + // Connect to WMI + hr = pIWbemLocator->ConnectServer(bstrNamespace, nullptr, nullptr, 0L, 0L, nullptr, nullptr, &pIWbemServices); + if (FAILED(hr) || pIWbemServices == nullptr) + goto LCleanup; + + // Switch security level to IMPERSONATE. + CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, + RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE); + + hr = pIWbemServices->CreateInstanceEnum(bstrClassName, 0, nullptr, &pEnumDevices); + if (FAILED(hr) || pEnumDevices == nullptr) + goto LCleanup; + + // Loop over all devices + while (true) + { + // Get 20 at a time + hr = pEnumDevices->Next(10000, 20, pDevices, &uReturned); + if (FAILED(hr) || uReturned == 0) + break; + + for (UINT iDevice = 0; iDevice < uReturned; ++iDevice) + { + // For each device, get its device ID + hr = pDevices[iDevice]->Get(bstrDeviceID, 0L, &var, nullptr, nullptr); + if (SUCCEEDED(hr) && var.vt == VT_BSTR && var.bstrVal != nullptr) + { + // Check if the device ID contains "IG_". If it does, then it's an XInput device + // This information can not be found from DirectInput + if (wcsstr(var.bstrVal, L"IG_")) + { + // If it does, then get the VID/PID from var.bstrVal + DWORD dwPid = 0, dwVid = 0; + WCHAR* strVid = wcsstr(var.bstrVal, L"VID_"); + if (strVid && swscanf(strVid, L"VID_%4X", &dwVid) != 1) + dwVid = 0; + WCHAR* strPid = wcsstr(var.bstrVal, L"PID_"); + if (strPid && swscanf(strPid, L"PID_%4X", &dwPid) != 1) + dwPid = 0; + + // Compare the VID/PID to the DInput device + DWORD dwVidPid = MAKELONG(dwVid, dwPid); + guids->push_back(dwVidPid); + //bIsXinputDevice = true; + } + } + SAFE_RELEASE(pDevices[iDevice]); + } + } + +LCleanup: + if (bstrNamespace) + SysFreeString(bstrNamespace); + if (bstrDeviceID) + SysFreeString(bstrDeviceID); + if (bstrClassName) + SysFreeString(bstrClassName); + for (UINT iDevice = 0; iDevice < 20; iDevice++) + SAFE_RELEASE(pDevices[iDevice]); + SAFE_RELEASE(pEnumDevices); + SAFE_RELEASE(pIWbemLocator); + SAFE_RELEASE(pIWbemServices); + + if (bCleanupCOM) + CoUninitialize(); +} + +} +} + +#undef SAFE_RELEASE diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/XInputFilter.h b/Source/Core/InputCommon/ControllerInterface/DInput/XInputFilter.h new file mode 100644 index 0000000000..6506dd156e --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/DInput/XInputFilter.h @@ -0,0 +1,18 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include <Windows.h> + +namespace ciface +{ +namespace DInput +{ + +void GetXInputGUIDS(std::vector<DWORD>* guids); + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/Device.cpp b/Source/Core/InputCommon/ControllerInterface/Device.cpp new file mode 100644 index 0000000000..fae7e98e2f --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/Device.cpp @@ -0,0 +1,185 @@ +// Copyright 2013 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <sstream> +#include <string> + +// For InputGateOn() +// This is a really bad layering violation, but it's the cleanest +// place I could find to put it. +#include "Core/ConfigManager.h" +#include "Core/Host.h" + +#include "InputCommon/ControllerInterface/Device.h" + +namespace ciface +{ +namespace Core +{ + +// +// Device :: ~Device +// +// Destructor, delete all inputs/outputs on device destruction +// +Device::~Device() +{ + // delete inputs + for (Device::Input* input : m_inputs) + delete input; + + // delete outputs + for (Device::Output* output: m_outputs) + delete output; +} + +void Device::AddInput(Device::Input* const i) +{ + m_inputs.push_back(i); +} + +void Device::AddOutput(Device::Output* const o) +{ + m_outputs.push_back(o); +} + +Device::Input* Device::FindInput(const std::string &name) const +{ + for (Input* input : m_inputs) + { + if (input->GetName() == name) + return input; + } + + return nullptr; +} + +Device::Output* Device::FindOutput(const std::string &name) const +{ + for (Output* output : m_outputs) + { + if (output->GetName() == name) + return output; + } + + return nullptr; +} + +bool Device::Control::InputGateOn() +{ + if (SConfig::GetInstance().m_BackgroundInput) + return true; + else if (Host_RendererHasFocus() || Host_UIHasFocus()) + return true; + else + return false; +} + +// +// DeviceQualifier :: ToString +// +// Get string from a device qualifier / serialize +// +std::string DeviceQualifier::ToString() const +{ + if (source.empty() && (cid < 0) && name.empty()) + return ""; + + std::ostringstream ss; + ss << source << '/'; + if (cid > -1) + ss << cid; + ss << '/' << name; + + return ss.str(); +} + +// +// DeviceQualifier :: FromString +// +// Set a device qualifier from a string / unserialize +// +void DeviceQualifier::FromString(const std::string& str) +{ + std::istringstream ss(str); + + std::getline(ss, source = "", '/'); + + // silly + std::getline(ss, name, '/'); + std::istringstream(name) >> (cid = -1); + + std::getline(ss, name = ""); +} + +// +// DeviceQualifier :: FromDevice +// +// Set a device qualifier from a device +// +void DeviceQualifier::FromDevice(const Device* const dev) +{ + name = dev->GetName(); + cid = dev->GetId(); + source= dev->GetSource(); +} + +bool DeviceQualifier::operator==(const Device* const dev) const +{ + if (dev->GetId() == cid) + if (dev->GetName() == name) + if (dev->GetSource() == source) + return true; + + return false; +} + +bool DeviceQualifier::operator==(const DeviceQualifier& devq) const +{ + if (cid == devq.cid) + if (name == devq.name) + if (source == devq.source) + return true; + + return false; +} + +Device* DeviceContainer::FindDevice(const DeviceQualifier& devq) const +{ + for (Device* d : m_devices) + { + if (devq == d) + return d; + } + + return nullptr; +} + +Device::Input* DeviceContainer::FindInput(const std::string& name, const Device* def_dev) const +{ + if (def_dev) + { + Device::Input* const inp = def_dev->FindInput(name); + if (inp) + return inp; + } + + for (Device* d : m_devices) + { + Device::Input* const i = d->FindInput(name); + + if (i) + return i; + } + + return nullptr; +} + +Device::Output* DeviceContainer::FindOutput(const std::string& name, const Device* def_dev) const +{ + return def_dev->FindOutput(name); +} + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/Device.h b/Source/Core/InputCommon/ControllerInterface/Device.h new file mode 100644 index 0000000000..c27910af5d --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/Device.h @@ -0,0 +1,186 @@ +// Copyright 2013 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include <vector> + +#include "Common/CommonTypes.h" + +// idk in case I wanted to change it to double or something, idk what's best +typedef double ControlState; + +namespace ciface +{ +namespace Core +{ + +// Forward declarations +class DeviceQualifier; + +// +// Device +// +// A device class +// +class Device +{ +public: + class Input; + class Output; + + // + // Control + // + // Control includes inputs and outputs + // + class Control // input or output + { + public: + virtual std::string GetName() const = 0; + virtual ~Control() {} + + bool InputGateOn(); + + virtual Input* ToInput() { return nullptr; } + virtual Output* ToOutput() { return nullptr; } + }; + + // + // Input + // + // An input on a device + // + class Input : public Control + { + public: + // things like absolute axes/ absolute mouse position will override this + virtual bool IsDetectable() { return true; } + + virtual ControlState GetState() const = 0; + + bool ShouldHaveInput(); + + ControlState GetGatedState() + { + if (InputGateOn()) + return GetState(); + else + return 0.0; + } + + Input* ToInput() override { return this; } + }; + + // + // Output + // + // An output on a device + // + class Output : public Control + { + public: + virtual ~Output() {} + + virtual void SetState(ControlState state) = 0; + + void SetGatedState(ControlState state) + { + if (InputGateOn()) + SetState(state); + } + + Output* ToOutput() override { return this; } + }; + + virtual ~Device(); + + virtual std::string GetName() const = 0; + virtual int GetId() const = 0; + virtual std::string GetSource() const = 0; + virtual void UpdateInput() {} + + const std::vector<Input*>& Inputs() const { return m_inputs; } + const std::vector<Output*>& Outputs() const { return m_outputs; } + + Input* FindInput(const std::string& name) const; + Output* FindOutput(const std::string& name) const; + +protected: + void AddInput(Input* const i); + void AddOutput(Output* const o); + + class FullAnalogSurface : public Input + { + public: + FullAnalogSurface(Input* low, Input* high) + : m_low(*low), m_high(*high) + {} + + ControlState GetState() const override + { + return (1 + m_high.GetState() - m_low.GetState()) / 2; + } + + std::string GetName() const override + { + return m_low.GetName() + *m_high.GetName().rbegin(); + } + + private: + Input& m_low; + Input& m_high; + }; + + void AddAnalogInputs(Input* low, Input* high) + { + AddInput(low); + AddInput(high); + AddInput(new FullAnalogSurface(low, high)); + AddInput(new FullAnalogSurface(high, low)); + } + +private: + std::vector<Input*> m_inputs; + std::vector<Output*> m_outputs; +}; + +// +// DeviceQualifier +// +// Device qualifier used to match devices. +// Currently has ( source, id, name ) properties which match a device +// +class DeviceQualifier +{ +public: + DeviceQualifier() : cid(-1) {} + DeviceQualifier(const std::string& _source, const int _id, const std::string& _name) + : source(_source), cid(_id), name(_name) {} + void FromDevice(const Device* const dev); + void FromString(const std::string& str); + std::string ToString() const; + bool operator==(const DeviceQualifier& devq) const; + bool operator==(const Device* const dev) const; + + std::string source; + int cid; + std::string name; +}; + +class DeviceContainer +{ +public: + Device::Input* FindInput(const std::string& name, const Device* def_dev) const; + Device::Output* FindOutput(const std::string& name, const Device* def_dev) const; + + const std::vector<Device*>& Devices() const { return m_devices; } + Device* FindDevice(const DeviceQualifier& devq) const; +protected: + std::vector<Device*> m_devices; +}; + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/ExpressionParser.cpp b/Source/Core/InputCommon/ControllerInterface/ExpressionParser.cpp new file mode 100644 index 0000000000..9837ea74cb --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/ExpressionParser.cpp @@ -0,0 +1,615 @@ +// Copyright 2013 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <algorithm> +#include <cassert> +#include <iostream> +#include <map> +#include <string> +#include <vector> + +#include "InputCommon/ControllerInterface/ExpressionParser.h" + +using namespace ciface::Core; + +namespace ciface +{ +namespace ExpressionParser +{ + +enum TokenType +{ + TOK_DISCARD, + TOK_INVALID, + TOK_EOF, + TOK_LPAREN, + TOK_RPAREN, + TOK_AND, + TOK_OR, + TOK_NOT, + TOK_ADD, + TOK_CONTROL, +}; + +inline std::string OpName(TokenType op) +{ + switch (op) + { + case TOK_AND: + return "And"; + case TOK_OR: + return "Or"; + case TOK_NOT: + return "Not"; + case TOK_ADD: + return "Add"; + default: + assert(false); + return ""; + } +} + +class Token +{ +public: + TokenType type; + ControlQualifier qualifier; + + Token(TokenType type_) : type(type_) {} + Token(TokenType type_, ControlQualifier qualifier_) : type(type_), qualifier(qualifier_) {} + + operator std::string() + { + switch (type) + { + case TOK_INVALID: + return "Invalid"; + case TOK_DISCARD: + return "Discard"; + case TOK_EOF: + return "EOF"; + case TOK_LPAREN: + return "("; + case TOK_RPAREN: + return ")"; + case TOK_AND: + return "&"; + case TOK_OR: + return "|"; + case TOK_NOT: + return "!"; + case TOK_ADD: + return "+"; + case TOK_CONTROL: + return "Device(" + (std::string)qualifier + ")"; + } + } +}; + +class Lexer { +public: + std::string expr; + std::string::iterator it; + + Lexer(const std::string& expr_) : expr(expr_) + { + it = expr.begin(); + } + + bool FetchBacktickString(std::string &value, char otherDelim = 0) + { + value = ""; + while (it != expr.end()) + { + char c = *it; + ++it; + if (c == '`') + return false; + if (c > 0 && c == otherDelim) + return true; + value += c; + } + return false; + } + + Token GetFullyQualifiedControl() + { + ControlQualifier qualifier; + std::string value; + + if (FetchBacktickString(value, ':')) + { + // Found colon, this is the device name + qualifier.has_device = true; + qualifier.device_qualifier.FromString(value); + FetchBacktickString(value); + } + + qualifier.control_name = value; + + return Token(TOK_CONTROL, qualifier); + } + + Token GetBarewordsControl(char c) + { + std::string name; + name += c; + + while (it != expr.end()) + { + c = *it; + if (!isalpha(c)) + break; + name += c; + ++it; + } + + ControlQualifier qualifier; + qualifier.control_name = name; + return Token(TOK_CONTROL, qualifier); + } + + Token NextToken() + { + if (it == expr.end()) + return Token(TOK_EOF); + + char c = *it++; + switch (c) + { + case ' ': + case '\t': + case '\n': + case '\r': + return Token(TOK_DISCARD); + case '(': + return Token(TOK_LPAREN); + case ')': + return Token(TOK_RPAREN); + case '&': + return Token(TOK_AND); + case '|': + return Token(TOK_OR); + case '!': + return Token(TOK_NOT); + case '+': + return Token(TOK_ADD); + case '`': + return GetFullyQualifiedControl(); + default: + if (isalpha(c)) + return GetBarewordsControl(c); + else + return Token(TOK_INVALID); + } + } + + ExpressionParseStatus Tokenize(std::vector<Token> &tokens) + { + while (true) + { + Token tok = NextToken(); + + if (tok.type == TOK_DISCARD) + continue; + + if (tok.type == TOK_INVALID) + { + tokens.clear(); + return EXPRESSION_PARSE_SYNTAX_ERROR; + } + + tokens.push_back(tok); + + if (tok.type == TOK_EOF) + break; + } + return EXPRESSION_PARSE_SUCCESS; + } +}; + +class ExpressionNode +{ +public: + virtual ~ExpressionNode() {} + virtual ControlState GetValue() { return 0; } + virtual void SetValue(ControlState state) {} + virtual int CountNumControls() { return 0; } + virtual operator std::string() { return ""; } +}; + +class DummyExpression : public ExpressionNode +{ +public: + std::string name; + + DummyExpression(const std::string& name_) : name(name_) {} + + ControlState GetValue() override + { + return 0.0; + } + + void SetValue(ControlState value) override + { + } + + int CountNumControls() override + { + return 0; + } + + operator std::string() override + { + return "`" + name + "`"; + } +}; + +class ControlExpression : public ExpressionNode +{ +public: + ControlQualifier qualifier; + Device::Control *control; + + ControlExpression(ControlQualifier qualifier_, Device::Control *control_) : qualifier(qualifier_), control(control_) {} + + virtual ControlState GetValue() override + { + return control->ToInput()->GetGatedState(); + } + + virtual void SetValue(ControlState value) override + { + control->ToOutput()->SetGatedState(value); + } + + virtual int CountNumControls() override + { + return 1; + } + + virtual operator std::string() override + { + return "`" + (std::string)qualifier + "`"; + } +}; + +class BinaryExpression : public ExpressionNode +{ +public: + TokenType op; + ExpressionNode *lhs; + ExpressionNode *rhs; + + BinaryExpression(TokenType op_, ExpressionNode *lhs_, ExpressionNode *rhs_) : op(op_), lhs(lhs_), rhs(rhs_) {} + virtual ~BinaryExpression() + { + delete lhs; + delete rhs; + } + + virtual ControlState GetValue() override + { + ControlState lhsValue = lhs->GetValue(); + ControlState rhsValue = rhs->GetValue(); + switch (op) + { + case TOK_AND: + return std::min(lhsValue, rhsValue); + case TOK_OR: + return std::max(lhsValue, rhsValue); + case TOK_ADD: + return std::min(lhsValue + rhsValue, 1.0); + default: + assert(false); + return 0; + } + } + + virtual void SetValue(ControlState value) override + { + // Don't do anything special with the op we have. + // Treat "A & B" the same as "A | B". + lhs->SetValue(value); + rhs->SetValue(value); + } + + virtual int CountNumControls() override + { + return lhs->CountNumControls() + rhs->CountNumControls(); + } + + virtual operator std::string() override + { + return OpName(op) + "(" + (std::string)(*lhs) + ", " + (std::string)(*rhs) + ")"; + } +}; + +class UnaryExpression : public ExpressionNode +{ +public: + TokenType op; + ExpressionNode *inner; + + UnaryExpression(TokenType op_, ExpressionNode *inner_) : op(op_), inner(inner_) {} + virtual ~UnaryExpression() + { + delete inner; + } + + virtual ControlState GetValue() override + { + ControlState value = inner->GetValue(); + switch (op) + { + case TOK_NOT: + return 1.0 - value; + default: + assert(false); + return 0; + } + } + + virtual void SetValue(ControlState value) override + { + switch (op) + { + case TOK_NOT: + inner->SetValue(1.0 - value); + break; + + default: + assert(false); + } + } + + virtual int CountNumControls() override + { + return inner->CountNumControls(); + } + + virtual operator std::string() override + { + return OpName(op) + "(" + (std::string)(*inner) + ")"; + } +}; + +Device *ControlFinder::FindDevice(ControlQualifier qualifier) +{ + if (qualifier.has_device) + return container.FindDevice(qualifier.device_qualifier); + else + return container.FindDevice(default_device); +} + +Device::Control *ControlFinder::FindControl(ControlQualifier qualifier) +{ + Device *device = FindDevice(qualifier); + if (!device) + return nullptr; + + if (is_input) + return device->FindInput(qualifier.control_name); + else + return device->FindOutput(qualifier.control_name); +} + +class Parser +{ +public: + + Parser(std::vector<Token> tokens_, ControlFinder &finder_) : tokens(tokens_), finder(finder_) + { + m_it = tokens.begin(); + } + + ExpressionParseStatus Parse(Expression **expr_out) + { + ExpressionNode *node; + ExpressionParseStatus status = Toplevel(&node); + if (status != EXPRESSION_PARSE_SUCCESS) + return status; + + *expr_out = new Expression(node); + return EXPRESSION_PARSE_SUCCESS; + } + +private: + std::vector<Token> tokens; + std::vector<Token>::iterator m_it; + ControlFinder &finder; + + Token Chew() + { + return *m_it++; + } + + Token Peek() + { + return *m_it; + } + + bool Expects(TokenType type) + { + Token tok = Chew(); + return tok.type == type; + } + + ExpressionParseStatus Atom(ExpressionNode **expr_out) + { + Token tok = Chew(); + switch (tok.type) + { + case TOK_CONTROL: + { + Device::Control *control = finder.FindControl(tok.qualifier); + if (control == nullptr) + { + *expr_out = new DummyExpression(tok.qualifier); + return EXPRESSION_PARSE_SUCCESS; + } + + *expr_out = new ControlExpression(tok.qualifier, control); + return EXPRESSION_PARSE_SUCCESS; + } + case TOK_LPAREN: + return Paren(expr_out); + default: + return EXPRESSION_PARSE_SYNTAX_ERROR; + } + } + + bool IsUnaryExpression(TokenType type) + { + switch (type) + { + case TOK_NOT: + return true; + default: + return false; + } + } + + ExpressionParseStatus Unary(ExpressionNode **expr_out) + { + ExpressionParseStatus status; + + if (IsUnaryExpression(Peek().type)) + { + Token tok = Chew(); + ExpressionNode *atom_expr; + if ((status = Atom(&atom_expr)) != EXPRESSION_PARSE_SUCCESS) + return status; + *expr_out = new UnaryExpression(tok.type, atom_expr); + return EXPRESSION_PARSE_SUCCESS; + } + + return Atom(expr_out); + } + + bool IsBinaryToken(TokenType type) + { + switch (type) + { + case TOK_AND: + case TOK_OR: + case TOK_ADD: + return true; + default: + return false; + } + } + + ExpressionParseStatus Binary(ExpressionNode **expr_out) + { + ExpressionParseStatus status; + + if ((status = Unary(expr_out)) != EXPRESSION_PARSE_SUCCESS) + return status; + + while (IsBinaryToken(Peek().type)) + { + Token tok = Chew(); + ExpressionNode *unary_expr; + if ((status = Unary(&unary_expr)) != EXPRESSION_PARSE_SUCCESS) + { + delete *expr_out; + return status; + } + + *expr_out = new BinaryExpression(tok.type, *expr_out, unary_expr); + } + + return EXPRESSION_PARSE_SUCCESS; + } + + ExpressionParseStatus Paren(ExpressionNode **expr_out) + { + ExpressionParseStatus status; + + // lparen already chewed + if ((status = Toplevel(expr_out)) != EXPRESSION_PARSE_SUCCESS) + return status; + + if (!Expects(TOK_RPAREN)) + { + delete *expr_out; + return EXPRESSION_PARSE_SYNTAX_ERROR; + } + + return EXPRESSION_PARSE_SUCCESS; + } + + ExpressionParseStatus Toplevel(ExpressionNode **expr_out) + { + return Binary(expr_out); + } +}; + +ControlState Expression::GetValue() +{ + return node->GetValue(); +} + +void Expression::SetValue(ControlState value) +{ + node->SetValue(value); +} + +Expression::Expression(ExpressionNode *node_) +{ + node = node_; + num_controls = node->CountNumControls(); +} + +Expression::~Expression() +{ + delete node; +} + +static ExpressionParseStatus ParseExpressionInner(const std::string& str, ControlFinder &finder, Expression **expr_out) +{ + ExpressionParseStatus status; + Expression *expr; + *expr_out = nullptr; + + if (str == "") + return EXPRESSION_PARSE_SUCCESS; + + Lexer l(str); + std::vector<Token> tokens; + status = l.Tokenize(tokens); + if (status != EXPRESSION_PARSE_SUCCESS) + return status; + + Parser p(tokens, finder); + status = p.Parse(&expr); + if (status != EXPRESSION_PARSE_SUCCESS) + return status; + + *expr_out = expr; + return EXPRESSION_PARSE_SUCCESS; +} + +ExpressionParseStatus ParseExpression(const std::string& str, ControlFinder &finder, Expression **expr_out) +{ + // Add compatibility with old simple expressions, which are simple + // barewords control names. + + ControlQualifier qualifier; + qualifier.control_name = str; + qualifier.has_device = false; + + Device::Control *control = finder.FindControl(qualifier); + if (control) + { + *expr_out = new Expression(new ControlExpression(qualifier, control)); + return EXPRESSION_PARSE_SUCCESS; + } + + return ParseExpressionInner(str, finder, expr_out); +} + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/ExpressionParser.h b/Source/Core/InputCommon/ControllerInterface/ExpressionParser.h new file mode 100644 index 0000000000..9575ff619d --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/ExpressionParser.h @@ -0,0 +1,69 @@ +// Copyright 2013 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include "InputCommon/ControllerInterface/Device.h" + +namespace ciface +{ +namespace ExpressionParser +{ + +class ControlQualifier +{ +public: + bool has_device; + Core::DeviceQualifier device_qualifier; + std::string control_name; + + ControlQualifier() : has_device(false) {} + + operator std::string() + { + if (has_device) + return device_qualifier.ToString() + ":" + control_name; + else + return control_name; + } +}; + +class ControlFinder +{ +public: + ControlFinder(const Core::DeviceContainer &container_, const Core::DeviceQualifier &default_, const bool is_input_) : container(container_), default_device(default_), is_input(is_input_) {} + Core::Device::Control *FindControl(ControlQualifier qualifier); + +private: + Core::Device *FindDevice(ControlQualifier qualifier); + const Core::DeviceContainer &container; + const Core::DeviceQualifier &default_device; + bool is_input; +}; + +class ExpressionNode; +class Expression +{ +public: + Expression() : node(nullptr) {} + Expression(ExpressionNode *node); + ~Expression(); + ControlState GetValue(); + void SetValue (ControlState state); + int num_controls; + ExpressionNode *node; +}; + +enum ExpressionParseStatus +{ + EXPRESSION_PARSE_SUCCESS = 0, + EXPRESSION_PARSE_SYNTAX_ERROR, + EXPRESSION_PARSE_NO_DEVICE, +}; + +ExpressionParseStatus ParseExpression(const std::string& expr, ControlFinder &finder, Expression **expr_out); + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.cpp b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.cpp new file mode 100644 index 0000000000..f67f63fc8b --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.cpp @@ -0,0 +1,210 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <algorithm> +#include <string> +#include "Common/Thread.h" +#include "InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h" + +namespace ciface +{ +namespace ForceFeedback +{ + +// template instantiation +template class ForceFeedbackDevice::Force<DICONSTANTFORCE>; +template class ForceFeedbackDevice::Force<DIRAMPFORCE>; +template class ForceFeedbackDevice::Force<DIPERIODIC>; + +struct ForceType +{ + GUID guid; + const std::string name; +}; + +static const ForceType force_type_names[] = +{ + {GUID_ConstantForce, "Constant"}, // DICONSTANTFORCE + {GUID_RampForce, "Ramp"}, // DIRAMPFORCE + {GUID_Square, "Square"}, // DIPERIODIC ... + {GUID_Sine, "Sine"}, + {GUID_Triangle, "Triangle"}, + {GUID_SawtoothUp, "Sawtooth Up"}, + {GUID_SawtoothDown, "Sawtooth Down"}, + //{GUID_Spring, "Spring"}, // DICUSTOMFORCE ... < I think + //{GUID_Damper, "Damper"}, + //{GUID_Inertia, "Inertia"}, + //{GUID_Friction, "Friction"}, +}; + +bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, int cAxes) +{ + if (cAxes == 0) + return false; + + // TODO: check for DIDC_FORCEFEEDBACK in devcaps? + + // temporary + DWORD rgdwAxes[2] = {DIJOFS_X, DIJOFS_Y}; + LONG rglDirection[2] = {-200, 0}; + + DIEFFECT eff; + memset(&eff, 0, sizeof(eff)); + eff.dwSize = sizeof(DIEFFECT); + eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + eff.dwDuration = INFINITE; // (4 * DI_SECONDS) + eff.dwSamplePeriod = 0; + eff.dwGain = DI_FFNOMINALMAX; + eff.dwTriggerButton = DIEB_NOTRIGGER; + eff.dwTriggerRepeatInterval = 0; + eff.cAxes = std::min<DWORD>(1, cAxes); + eff.rgdwAxes = rgdwAxes; + eff.rglDirection = rglDirection; + + // initialize parameters + DICONSTANTFORCE diCF = { -10000 }; + diCF.lMagnitude = DI_FFNOMINALMAX; + DIRAMPFORCE diRF = { 0 }; + DIPERIODIC diPE = { 0 }; + + // doesn't seem needed + //DIENVELOPE env; + //eff.lpEnvelope = &env; + //ZeroMemory(&env, sizeof(env)); + //env.dwSize = sizeof(env); + + for (const ForceType& f : force_type_names) + { + if (f.guid == GUID_ConstantForce) + { + eff.cbTypeSpecificParams = sizeof(DICONSTANTFORCE); + eff.lpvTypeSpecificParams = &diCF; + } + else if (f.guid == GUID_RampForce) + { + eff.cbTypeSpecificParams = sizeof(DIRAMPFORCE); + eff.lpvTypeSpecificParams = &diRF; + } + else + { + // all other forces need periodic parameters + eff.cbTypeSpecificParams = sizeof(DIPERIODIC); + eff.lpvTypeSpecificParams = &diPE; + } + + LPDIRECTINPUTEFFECT pEffect; + if (SUCCEEDED(device->CreateEffect(f.guid, &eff, &pEffect, nullptr))) + { + if (f.guid == GUID_ConstantForce) + AddOutput(new ForceConstant(f.name, pEffect)); + else if (f.guid == GUID_RampForce) + AddOutput(new ForceRamp(f.name, pEffect)); + else + AddOutput(new ForcePeriodic(f.name, pEffect)); + } + } + + // disable autocentering + if (Outputs().size()) + { + DIPROPDWORD dipdw; + dipdw.diph.dwSize = sizeof( DIPROPDWORD ); + dipdw.diph.dwHeaderSize = sizeof( DIPROPHEADER ); + dipdw.diph.dwObj = 0; + dipdw.diph.dwHow = DIPH_DEVICE; + dipdw.dwData = DIPROPAUTOCENTER_OFF; + device->SetProperty( DIPROP_AUTOCENTER, &dipdw.diph ); + } + + return true; +} + +template<typename P> +ForceFeedbackDevice::Force<P>::~Force() +{ + m_iface->Stop(); + m_iface->Unload(); + m_iface->Release(); +} + +template<typename P> +void ForceFeedbackDevice::Force<P>::Update() +{ + DIEFFECT eff = {}; + eff.dwSize = sizeof(DIEFFECT); + eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + + eff.cbTypeSpecificParams = sizeof(P); + eff.lpvTypeSpecificParams = ¶ms; + + // set params and start effect + m_iface->SetParameters(&eff, DIEP_TYPESPECIFICPARAMS | DIEP_START); +} + +template<typename P> +void ForceFeedbackDevice::Force<P>::Stop() +{ + m_iface->Stop(); +} + +template<> +void ForceFeedbackDevice::ForceConstant::SetState(const ControlState state) +{ + const LONG new_val = LONG(10000 * state); + + if (params.lMagnitude == new_val) + return; + + params.lMagnitude = new_val; + if (new_val) + Update(); + else + Stop(); +} + +template<> +void ForceFeedbackDevice::ForceRamp::SetState(const ControlState state) +{ + const LONG new_val = LONG(10000 * state); + + if (params.lStart == new_val) + return; + + params.lStart = params.lEnd = new_val; + if (new_val) + Update(); + else + Stop(); +} + +template<> +void ForceFeedbackDevice::ForcePeriodic::SetState(const ControlState state) +{ + const DWORD new_val = DWORD(10000 * state); + + if (params.dwMagnitude == new_val) + return; + + params.dwMagnitude = new_val; + if (new_val) + Update(); + else + Stop(); +} + +template <typename P> +ForceFeedbackDevice::Force<P>::Force(const std::string& name, LPDIRECTINPUTEFFECT iface) +: m_name(name), m_iface(iface) +{ + memset(¶ms, 0, sizeof(params)); +} + +template <typename P> +std::string ForceFeedbackDevice::Force<P>::GetName() const +{ + return m_name; +} + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h new file mode 100644 index 0000000000..a528c00602 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h @@ -0,0 +1,53 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include <list> +#include <string> + +#include "InputCommon/ControllerInterface/Device.h" + +#ifdef _WIN32 +#include <Windows.h> +#include "InputCommon/ControllerInterface/DInput/DInput8.h" +#elif __APPLE__ +#include "InputCommon/ControllerInterface/ForceFeedback/OSX/DirectInputAdapter.h" +#endif + +namespace ciface +{ +namespace ForceFeedback +{ + + +class ForceFeedbackDevice : public Core::Device +{ +private: + template <typename P> + class Force : public Output + { + public: + std::string GetName() const; + Force(const std::string& name, LPDIRECTINPUTEFFECT iface); + ~Force(); + void SetState(ControlState state); + void Update(); + void Stop(); + private: + const std::string m_name; + LPDIRECTINPUTEFFECT m_iface; + P params; + }; + typedef Force<DICONSTANTFORCE> ForceConstant; + typedef Force<DIRAMPFORCE> ForceRamp; + typedef Force<DIPERIODIC> ForcePeriodic; + +public: + bool InitForceFeedback(const LPDIRECTINPUTDEVICE8, int cAxes); +}; + + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/ForceFeedback/OSX/DirectInputAdapter.h b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/OSX/DirectInputAdapter.h new file mode 100644 index 0000000000..133a4e2d09 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/OSX/DirectInputAdapter.h @@ -0,0 +1,223 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +/* + * The OS X Force Feedback API is very similar to the DirectInput API, + * but it is no longer object-oriented and all prefixes have been changed. + * + * Our implementation uses the Windows API names so we need to adapt + * for these differences on OS X. + */ + +#pragma once + +#include <atomic> + +typedef LONG* LPLONG; // Missing type for ForceFeedback.h +#include <CoreFoundation/CoreFoundation.h> +#include <ForceFeedback/ForceFeedback.h> +#include "DirectInputConstants.h" // Not stricty necessary +#include "Common/CommonTypes.h" // for LONG + +namespace ciface +{ +namespace ForceFeedback +{ + + +// Prototypes +class IUnknownImpl; +class FFEffectAdapter; +class FFDeviceAdapter; + +// Structs +typedef FFCAPABILITIES DICAPABILITIES; +typedef FFCONDITION DICONDITION; +typedef FFCONSTANTFORCE DICONSTANTFORCE; +typedef FFCUSTOMFORCE DICUSTOMFORCE; +typedef FFEFFECT DIEFFECT; +typedef FFEFFESCAPE DIEFFESCAPE; +typedef FFENVELOPE DIENVELOPE; +typedef FFPERIODIC DIPERIODIC; +typedef FFRAMPFORCE DIRAMPFORCE; + +// Other types +typedef CFUUIDRef GUID; +typedef FFDeviceAdapter* FFDeviceAdapterReference; +typedef FFEffectAdapter* FFEffectAdapterReference; +typedef FFDeviceAdapterReference LPDIRECTINPUTDEVICE8; +typedef FFEffectAdapterReference LPDIRECTINPUTEFFECT; + +// Property structures +#define DIPH_DEVICE 0 + +typedef struct DIPROPHEADER { + DWORD dwSize; + DWORD dwHeaderSize; + DWORD dwObj; + DWORD dwHow; +} DIPROPHEADER, *LPDIPROPHEADER; + +typedef struct DIPROPDWORD { + DIPROPHEADER diph; + DWORD dwData; +} DIPROPDWORD, *LPDIPROPDWORD; + +class IUnknownImpl : public IUnknown +{ +private: + std::atomic<ULONG> m_cRef; + +public: + IUnknownImpl() : m_cRef(1) {} + virtual ~IUnknownImpl() {} + + HRESULT QueryInterface(REFIID iid, LPVOID *ppv) + { + *ppv = nullptr; + + if (CFEqual(&iid, IUnknownUUID)) + *ppv = this; + if (nullptr == *ppv) + return E_NOINTERFACE; + + ((IUnknown*)*ppv)->AddRef(); + + return S_OK; + } + + ULONG AddRef() + { + return ++m_cRef; + } + + ULONG Release() + { + if (--m_cRef == 0) + delete this; + + return m_cRef; + } +}; + +class FFEffectAdapter : public IUnknownImpl +{ +private: + // Only used for destruction + FFDeviceObjectReference m_device; + +public: + FFEffectObjectReference m_effect; + + FFEffectAdapter(FFDeviceObjectReference device, FFEffectObjectReference effect) : m_device(device), m_effect(effect) {} + ~FFEffectAdapter() { FFDeviceReleaseEffect(m_device, m_effect); } + + HRESULT Download() + { + return FFEffectDownload(m_effect); + } + + HRESULT Escape(FFEFFESCAPE *pFFEffectEscape) + { + return FFEffectEscape(m_effect, pFFEffectEscape); + } + + HRESULT GetEffectStatus(FFEffectStatusFlag *pFlags) + { + return FFEffectGetEffectStatus(m_effect, pFlags); + } + + HRESULT GetParameters(FFEFFECT *pFFEffect, FFEffectParameterFlag flags) + { + return FFEffectGetParameters(m_effect, pFFEffect, flags); + } + + HRESULT SetParameters(FFEFFECT *pFFEffect, FFEffectParameterFlag flags) + { + return FFEffectSetParameters(m_effect, pFFEffect, flags); + } + + HRESULT Start(UInt32 iterations, FFEffectStartFlag flags) + { + return FFEffectStart(m_effect, iterations, flags); + } + + HRESULT Stop() + { + return FFEffectStop(m_effect); + } + + HRESULT Unload() + { + return FFEffectUnload(m_effect); + } +}; + +class FFDeviceAdapter : public IUnknownImpl +{ +public: + FFDeviceObjectReference m_device; + + FFDeviceAdapter(FFDeviceObjectReference device) : m_device(device) {} + ~FFDeviceAdapter() { FFReleaseDevice(m_device); } + + static HRESULT Create(io_service_t hidDevice, FFDeviceAdapterReference *pDeviceReference) + { + FFDeviceObjectReference ref; + + HRESULT hr = FFCreateDevice(hidDevice, &ref); + if (SUCCEEDED(hr)) + *pDeviceReference = new FFDeviceAdapter(ref); + + return hr; + } + + HRESULT CreateEffect(CFUUIDRef uuidRef, FFEFFECT *pEffectDefinition, FFEffectAdapterReference *pEffectReference, IUnknown *punkOuter) + { + FFEffectObjectReference ref; + + HRESULT hr = FFDeviceCreateEffect(m_device, uuidRef, pEffectDefinition, &ref); + if (SUCCEEDED(hr)) + *pEffectReference = new FFEffectAdapter(m_device, ref); + + return hr; + } + + HRESULT Escape(FFEFFESCAPE *pFFEffectEscape) + { + return FFDeviceEscape(m_device, pFFEffectEscape); + } + + HRESULT GetForceFeedbackState(FFState *pFFState) + { + return FFDeviceGetForceFeedbackState(m_device, pFFState); + } + + HRESULT SendForceFeedbackCommand(FFCommandFlag flags) + { + return FFDeviceSendForceFeedbackCommand(m_device, flags); + } + + HRESULT SetCooperativeLevel(void *taskIdentifier, FFCooperativeLevelFlag flags) + { + return FFDeviceSetCooperativeLevel(m_device, taskIdentifier, flags); + } + + HRESULT SetProperty(FFProperty property, const LPDIPROPHEADER pdiph) + { + // There are only two properties supported + if (property != DIPROP_FFGAIN && property != DIPROP_AUTOCENTER) + return DIERR_UNSUPPORTED; + + // And they are both device properties + if (pdiph->dwHow != DIPH_DEVICE) + return DIERR_INVALIDPARAM; + + UInt32 value = ((const LPDIPROPDWORD)pdiph)->dwData; + return FFDeviceSetForceFeedbackProperty(m_device, property, &value); + } +}; + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/ForceFeedback/OSX/DirectInputConstants.h b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/OSX/DirectInputConstants.h new file mode 100644 index 0000000000..ceac035b6c --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/OSX/DirectInputConstants.h @@ -0,0 +1,147 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +/* + * Define all constants from ForceFeedbackConstants.h with DirectInput prefixes. + * + * No effort was made to confirm if all definitions are actually supported by + * DirectInput, so some of these definitions may actually only exist on Mac OS X. + */ + +// UUIDs +#define GUID_ConstantForce kFFEffectType_ConstantForce_ID +#define GUID_CustomForce kFFEffectType_CustomForce_ID +#define GUID_Damper kFFEffectType_Damper_ID +#define GUID_Friction kFFEffectType_Friction_ID +#define GUID_Inertia kFFEffectType_Inertia_ID +#define GUID_RampForce kFFEffectType_RampForce_ID +#define GUID_SawtoothDown kFFEffectType_SawtoothDown_ID +#define GUID_SawtoothUp kFFEffectType_SawtoothUp_ID +#define GUID_Sine kFFEffectType_Sine_ID +#define GUID_Spring kFFEffectType_Spring_ID +#define GUID_Square kFFEffectType_Square_ID +#define GUID_Triangle kFFEffectType_Triangle_ID + +// Miscellaneous +#define DI_DEGREES FF_DEGREES +#define DI_DOWNLOADSKIPPED FF_DOWNLOADSKIPPED +#define DI_EFFECTRESTARTED FF_EFFECTRESTARTED +#define DI_FALSE FF_FALSE +#define DI_FFNOMINALMAX FF_FFNOMINALMAX +#define DI_INFINITE FF_INFINITE +#define DI_OK FF_OK +#define DI_SECONDS FF_SECONDS +#define DI_TRUNCATED FF_TRUNCATED +#define DI_TRUNCATEDANDRESTARTED FF_TRUNCATEDANDRESTARTED +#define DIEFF_OBJECTOFFSETS FFEFF_OBJECTOFFSETS +#define DIERR_DEVICEFULL FFERR_DEVICEFULL +#define DIERR_DEVICENOTREG FFERR_DEVICENOTREG +#define DIERR_DEVICEPAUSED FFERR_DEVICEPAUSED +#define DIERR_DEVICERELEASED FFERR_DEVICERELEASED +#define DIERR_EFFECTPLAYING FFERR_EFFECTPLAYING +#define DIERR_EFFECTTYPEMISMATCH FFERR_EFFECTTYPEMISMATCH +#define DIERR_EFFECTTYPENOTSUPPORTED FFERR_EFFECTTYPENOTSUPPORTED +#define DIERR_GENERIC FFERR_GENERIC +#define DIERR_HASEFFECTS FFERR_HASEFFECTS +#define DIERR_INCOMPLETEEFFECT FFERR_INCOMPLETEEFFECT +#define DIERR_INTERNAL FFERR_INTERNAL +#define DIERR_INVALIDDOWNLOADID FFERR_INVALIDDOWNLOADID +#define DIERR_INVALIDPARAM FFERR_INVALIDPARAM +#define DIERR_MOREDATA FFERR_MOREDATA +#define DIERR_NOINTERFACE FFERR_NOINTERFACE +#define DIERR_NOTDOWNLOADED FFERR_NOTDOWNLOADED +#define DIERR_NOTINITIALIZED FFERR_NOTINITIALIZED +#define DIERR_OUTOFMEMORY FFERR_OUTOFMEMORY +#define DIERR_UNPLUGGED FFERR_UNPLUGGED +#define DIERR_UNSUPPORTED FFERR_UNSUPPORTED +#define DIERR_UNSUPPORTEDAXIS FFERR_UNSUPPORTEDAXIS +#define DIJOFS_X FFJOFS_X +#define DIJOFS_Y FFJOFS_Y +#define DIJOFS_Z FFJOFS_Z + +// FFCapabilitiesEffectSubType +#define DICAP_ST_KINESTHETIC FFCAP_ST_KINESTHETIC +#define DICAP_ST_VIBRATION FFCAP_ST_VIBRATION + +// FFCapabilitiesEffectType +#define DICAP_ET_CONSTANTFORCE FFCAP_ET_CONSTANTFORCE +#define DICAP_ET_RAMPFORCE FFCAP_ET_RAMPFORCE +#define DICAP_ET_SQUARE FFCAP_ET_SQUARE +#define DICAP_ET_SINE FFCAP_ET_SINE +#define DICAP_ET_TRIANGLE FFCAP_ET_TRIANGLE +#define DICAP_ET_SAWTOOTHUP FFCAP_ET_SAWTOOTHUP +#define DICAP_ET_SAWTOOTHDOWN FFCAP_ET_SAWTOOTHDOWN +#define DICAP_ET_SPRING FFCAP_ET_SPRING +#define DICAP_ET_DAMPER FFCAP_ET_DAMPER +#define DICAP_ET_INERTIA FFCAP_ET_INERTIA +#define DICAP_ET_FRICTION FFCAP_ET_FRICTION +#define DICAP_ET_CUSTOMFORCE FFCAP_ET_CUSTOMFORCE + +// FFCommandFlag +#define DISFFC_RESET FFSFFC_RESET +#define DISFFC_STOPALL FFSFFC_STOPALL +#define DISFFC_PAUSE FFSFFC_PAUSE +#define DISFFC_CONTINUE FFSFFC_CONTINUE +#define DISFFC_SETACTUATORSON FFSFFC_SETACTUATORSON +#define DISFFC_SETACTUATORSOFF FFSFFC_SETACTUATORSOFF + +// FFCooperativeLevelFlag +#define DISCL_EXCLUSIVE FFSCL_EXCLUSIVE +#define DISCL_NONEXCLUSIVE FFSCL_NONEXCLUSIVE +#define DISCL_FOREGROUND FFSCL_FOREGROUND +#define DISCL_BACKGROUND FFSCL_BACKGROUND + +// FFCoordinateSystemFlag +#define DIEFF_CARTESIAN FFEFF_CARTESIAN +#define DIEFF_POLAR FFEFF_POLAR +#define DIEFF_SPHERICAL FFEFF_SPHERICAL + +// FFEffectParameterFlag +#define DIEP_DURATION FFEP_DURATION +#define DIEP_SAMPLEPERIOD FFEP_SAMPLEPERIOD +#define DIEP_GAIN FFEP_GAIN +#define DIEP_TRIGGERBUTTON FFEP_TRIGGERBUTTON +#define DIEP_TRIGGERREPEATINTERVAL FFEP_TRIGGERREPEATINTERVAL +#define DIEP_AXES FFEP_AXES +#define DIEP_DIRECTION FFEP_DIRECTION +#define DIEP_ENVELOPE FFEP_ENVELOPE +#define DIEP_TYPESPECIFICPARAMS FFEP_TYPESPECIFICPARAMS +#define DIEP_STARTDELAY FFEP_STARTDELAY +#define DIEP_ALLPARAMS FFEP_ALLPARAMS +#define DIEP_START FFEP_START +#define DIEP_NORESTART FFEP_NORESTART +#define DIEP_NODOWNLOAD FFEP_NODOWNLOAD +#define DIEB_NOTRIGGER FFEB_NOTRIGGER + +// FFEffectStartFlag +#define DIES_SOLO FFES_SOLO +#define DIES_NODOWNLOAD FFES_NODOWNLOAD + +// FFEffectStatusFlag +#define DIEGES_NOTPLAYING FFEGES_NOTPLAYING +#define DIEGES_PLAYING FFEGES_PLAYING +#define DIEGES_EMULATED FFEGES_EMULATED + +// FFProperty +#define DIPROP_FFGAIN FFPROP_FFGAIN +#define DIPROP_AUTOCENTER FFPROP_AUTOCENTER +// not defined in ForceFeedbackConstants.h +#define DIPROPAUTOCENTER_OFF 0 +#define DIPROPAUTOCENTER_ON 1 + +// FFState +#define DIGFFS_EMPTY FFGFFS_EMPTY +#define DIGFFS_STOPPED FFGFFS_STOPPED +#define DIGFFS_PAUSED FFGFFS_PAUSED +#define DIGFFS_ACTUATORSON FFGFFS_ACTUATORSON +#define DIGFFS_ACTUATORSOFF FFGFFS_ACTUATORSOFF +#define DIGFFS_POWERON FFGFFS_POWERON +#define DIGFFS_POWEROFF FFGFFS_POWEROFF +#define DIGFFS_SAFETYSWITCHON FFGFFS_SAFETYSWITCHON +#define DIGFFS_SAFETYSWITCHOFF FFGFFS_SAFETYSWITCHOFF +#define DIGFFS_USERFFSWITCHON FFGFFS_USERFFSWITCHON +#define DIGFFS_USERFFSWITCHOFF FFGFFS_USERFFSWITCHOFF +#define DIGFFS_DEVICELOST FFGFFS_DEVICELOST diff --git a/Source/Core/InputCommon/ControllerInterface/OSX/OSX.h b/Source/Core/InputCommon/ControllerInterface/OSX/OSX.h new file mode 100644 index 0000000000..5cb4a87fd8 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/OSX/OSX.h @@ -0,0 +1,20 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include "InputCommon/ControllerInterface/Device.h" + +namespace ciface +{ +namespace OSX +{ + +void Init(std::vector<Core::Device*>& devices, void *window); +void DeInit(); + +void DeviceElementDebugPrint(const void *, void *); + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/OSX/OSX.mm b/Source/Core/InputCommon/ControllerInterface/OSX/OSX.mm new file mode 100644 index 0000000000..18542b6c50 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/OSX/OSX.mm @@ -0,0 +1,219 @@ +// Copyright 2013 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <Foundation/Foundation.h> +#include <IOKit/hid/IOHIDLib.h> +#include <Cocoa/Cocoa.h> + +#include "InputCommon/ControllerInterface/OSX/OSX.h" +#include "InputCommon/ControllerInterface/OSX/OSXKeyboard.h" +#include "InputCommon/ControllerInterface/OSX/OSXJoystick.h" + +#include <map> + +namespace ciface +{ +namespace OSX +{ + + +static IOHIDManagerRef HIDManager = nullptr; +static CFStringRef OurRunLoop = CFSTR("DolphinOSXInput"); +static std::map<std::string, int> kbd_name_counts, joy_name_counts; + +void DeviceElementDebugPrint(const void *value, void *context) +{ + IOHIDElementRef e = (IOHIDElementRef)value; + bool recurse = false; + if (context) + recurse = *(bool*)context; + + std::string type = ""; + switch (IOHIDElementGetType(e)) + { + case kIOHIDElementTypeInput_Axis: + type = "axis"; + break; + case kIOHIDElementTypeInput_Button: + type = "button"; + break; + case kIOHIDElementTypeInput_Misc: + type = "misc"; + break; + case kIOHIDElementTypeInput_ScanCodes: + type = "scancodes"; + break; + case kIOHIDElementTypeOutput: + type = "output"; + break; + case kIOHIDElementTypeFeature: + type = "feature"; + break; + case kIOHIDElementTypeCollection: + type = "collection"; + break; + } + + std::string c_type = ""; + if (type == "collection") + { + switch (IOHIDElementGetCollectionType(e)) + { + case kIOHIDElementCollectionTypePhysical: + c_type = "physical"; + break; + case kIOHIDElementCollectionTypeApplication: + c_type = "application"; + break; + case kIOHIDElementCollectionTypeLogical: + c_type = "logical"; + break; + case kIOHIDElementCollectionTypeReport: + c_type = "report"; + break; + case kIOHIDElementCollectionTypeNamedArray: + c_type = "namedArray"; + break; + case kIOHIDElementCollectionTypeUsageSwitch: + c_type = "usageSwitch"; + break; + case kIOHIDElementCollectionTypeUsageModifier: + c_type = "usageModifier"; + break; + } + } + + c_type.append(" "); + NSLog(@"%s%s%spage: 0x%x usage: 0x%x name: %@ " + "lmin: %ld lmax: %ld pmin: %ld pmax: %ld", + type.c_str(), + type == "collection" ? ":" : "", + type == "collection" ? c_type.c_str() : " ", + IOHIDElementGetUsagePage(e), + IOHIDElementGetUsage(e), + IOHIDElementGetName(e), // usually just nullptr + IOHIDElementGetLogicalMin(e), + IOHIDElementGetLogicalMax(e), + IOHIDElementGetPhysicalMin(e), + IOHIDElementGetPhysicalMax(e)); + + if ((type == "collection") && recurse) + { + CFArrayRef elements = IOHIDElementGetChildren(e); + CFRange range = {0, CFArrayGetCount(elements)}; + // this leaks...but it's just debug code, right? :D + CFArrayApplyFunction(elements, range, + DeviceElementDebugPrint, nullptr); + } +} + +static void DeviceDebugPrint(IOHIDDeviceRef device) +{ +#if 0 +#define shortlog(x) NSLog(@"%s: %@", \ + x, IOHIDDeviceGetProperty(device, CFSTR(x))); + NSLog(@"-------------------------"); + NSLog(@"Got Device: %@", + IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey))); + shortlog(kIOHIDTransportKey) + shortlog(kIOHIDVendorIDKey) + shortlog(kIOHIDVendorIDSourceKey) + shortlog(kIOHIDProductIDKey) + shortlog(kIOHIDVersionNumberKey) + shortlog(kIOHIDManufacturerKey) + shortlog(kIOHIDProductKey) + shortlog(kIOHIDSerialNumberKey) + shortlog(kIOHIDCountryCodeKey) + shortlog(kIOHIDLocationIDKey) + shortlog(kIOHIDDeviceUsageKey) + shortlog(kIOHIDDeviceUsagePageKey) + shortlog(kIOHIDDeviceUsagePairsKey) + shortlog(kIOHIDPrimaryUsageKey) + shortlog(kIOHIDPrimaryUsagePageKey) + shortlog(kIOHIDMaxInputReportSizeKey) + shortlog(kIOHIDMaxOutputReportSizeKey) + shortlog(kIOHIDMaxFeatureReportSizeKey) + shortlog(kIOHIDReportIntervalKey) + shortlog(kIOHIDReportDescriptorKey) +#endif +} + +static void *g_window; + +static void DeviceMatching_callback(void* inContext, + IOReturn inResult, + void *inSender, + IOHIDDeviceRef inIOHIDDeviceRef) +{ + NSString *pName = (NSString *) + IOHIDDeviceGetProperty(inIOHIDDeviceRef, CFSTR(kIOHIDProductKey)); + std::string name = (pName != nullptr) ? [pName UTF8String] : "Unknown device"; + + DeviceDebugPrint(inIOHIDDeviceRef); + + std::vector<Core::Device*> *devices = + (std::vector<Core::Device*> *)inContext; + + // Add to the devices vector if it's of a type we want + if (IOHIDDeviceConformsTo(inIOHIDDeviceRef, + kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard)) + devices->push_back(new Keyboard(inIOHIDDeviceRef, + name, kbd_name_counts[name]++, g_window)); +#if 0 + else if (IOHIDDeviceConformsTo(inIOHIDDeviceRef, + kHIDPage_GenericDesktop, kHIDUsage_GD_Mouse)) + devices->push_back(new Mouse(inIOHIDDeviceRef, + name, mouse_name_counts[name]++)); +#endif + else + devices->push_back(new Joystick(inIOHIDDeviceRef, + name, joy_name_counts[name]++)); +} + +void Init(std::vector<Core::Device*>& devices, void *window) +{ + HIDManager = IOHIDManagerCreate(kCFAllocatorDefault, + kIOHIDOptionsTypeNone); + if (!HIDManager) + NSLog(@"Failed to create HID Manager reference"); + + g_window = window; + + IOHIDManagerSetDeviceMatching(HIDManager, nullptr); + + // Callbacks for acquisition or loss of a matching device + IOHIDManagerRegisterDeviceMatchingCallback(HIDManager, + DeviceMatching_callback, (void *)&devices); + + // Match devices that are plugged in right now + IOHIDManagerScheduleWithRunLoop(HIDManager, + CFRunLoopGetCurrent(), OurRunLoop); + if (IOHIDManagerOpen(HIDManager, kIOHIDOptionsTypeNone) != + kIOReturnSuccess) + NSLog(@"Failed to open HID Manager"); + + kbd_name_counts.clear(); + joy_name_counts.clear(); + + // Wait while current devices are initialized + while (CFRunLoopRunInMode(OurRunLoop, 0, TRUE) == + kCFRunLoopRunHandledSource) {}; + + // Things should be configured now + // Disable hotplugging and other scheduling + IOHIDManagerRegisterDeviceMatchingCallback(HIDManager, nullptr, nullptr); + IOHIDManagerUnscheduleFromRunLoop(HIDManager, + CFRunLoopGetCurrent(), OurRunLoop); +} + +void DeInit() +{ + // This closes all devices as well + IOHIDManagerClose(HIDManager, kIOHIDOptionsTypeNone); + CFRelease(HIDManager); +} + + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.h b/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.h new file mode 100644 index 0000000000..c0fdec5537 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.h @@ -0,0 +1,91 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include <IOKit/hid/IOHIDLib.h> + +#include "InputCommon/ControllerInterface/Device.h" +#include "InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h" + +namespace ciface +{ +namespace OSX +{ + +class Joystick : public ForceFeedback::ForceFeedbackDevice +{ +private: + class Button : public Input + { + public: + std::string GetName() const; + Button(IOHIDElementRef element, IOHIDDeviceRef device) + : m_element(element), m_device(device) {} + ControlState GetState() const; + private: + const IOHIDElementRef m_element; + const IOHIDDeviceRef m_device; + }; + + class Axis : public Input + { + public: + enum direction + { + positive = 0, + negative + }; + std::string GetName() const; + Axis(IOHIDElementRef element, IOHIDDeviceRef device, direction dir); + ControlState GetState() const; + + private: + const IOHIDElementRef m_element; + const IOHIDDeviceRef m_device; + std::string m_name; + const direction m_direction; + float m_neutral; + float m_scale; + }; + + class Hat : public Input + { + public: + enum direction + { + up = 0, + right, + down, + left + }; + std::string GetName() const; + Hat(IOHIDElementRef element, IOHIDDeviceRef device, direction dir); + ControlState GetState() const; + + private: + const IOHIDElementRef m_element; + const IOHIDDeviceRef m_device; + const char* m_name; + const direction m_direction; + }; + +public: + Joystick(IOHIDDeviceRef device, std::string name, int index); + ~Joystick(); + + std::string GetName() const; + std::string GetSource() const; + int GetId() const; + +private: + const IOHIDDeviceRef m_device; + const std::string m_device_name; + const int m_index; + + ForceFeedback::FFDeviceAdapterReference m_ff_device; +}; + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.mm b/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.mm new file mode 100644 index 0000000000..234625a40f --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.mm @@ -0,0 +1,283 @@ +// Copyright 2013 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <sstream> + +#include <Foundation/Foundation.h> +#include <IOKit/hid/IOHIDLib.h> + +#include "InputCommon/ControllerInterface/OSX/OSXJoystick.h" + +namespace ciface +{ +namespace OSX +{ + + +Joystick::Joystick(IOHIDDeviceRef device, std::string name, int index) + : m_device(device) + , m_device_name(name) + , m_index(index) + , m_ff_device(nullptr) +{ + // Buttons + NSDictionary *buttonDict = @{ + @kIOHIDElementTypeKey : [NSNumber numberWithInteger: kIOHIDElementTypeInput_Button], + @kIOHIDElementUsagePageKey : [NSNumber numberWithInteger: kHIDPage_Button] + }; + + CFArrayRef buttons = IOHIDDeviceCopyMatchingElements(m_device, + (CFDictionaryRef)buttonDict, kIOHIDOptionsTypeNone); + + if (buttons) + { + for (int i = 0; i < CFArrayGetCount(buttons); i++) + { + IOHIDElementRef e = + (IOHIDElementRef)CFArrayGetValueAtIndex(buttons, i); + //DeviceElementDebugPrint(e, nullptr); + + AddInput(new Button(e, m_device)); + } + CFRelease(buttons); + } + + // Axes + NSDictionary *axisDict = @{ + @kIOHIDElementTypeKey : [NSNumber numberWithInteger: kIOHIDElementTypeInput_Misc] + }; + + CFArrayRef axes = IOHIDDeviceCopyMatchingElements(m_device, + (CFDictionaryRef)axisDict, kIOHIDOptionsTypeNone); + + if (axes) + { + for (int i = 0; i < CFArrayGetCount(axes); i++) + { + IOHIDElementRef e = + (IOHIDElementRef)CFArrayGetValueAtIndex(axes, i); + //DeviceElementDebugPrint(e, nullptr); + + if (IOHIDElementGetUsage(e) == kHIDUsage_GD_Hatswitch) + { + AddInput(new Hat(e, m_device, Hat::up)); + AddInput(new Hat(e, m_device, Hat::right)); + AddInput(new Hat(e, m_device, Hat::down)); + AddInput(new Hat(e, m_device, Hat::left)); + } + else + { + AddAnalogInputs(new Axis(e, m_device, Axis::negative), + new Axis(e, m_device, Axis::positive)); + } + } + CFRelease(axes); + } + + // Force Feedback + FFCAPABILITIES ff_caps; + if (SUCCEEDED(ForceFeedback::FFDeviceAdapter::Create(IOHIDDeviceGetService(m_device), &m_ff_device)) && + SUCCEEDED(FFDeviceGetForceFeedbackCapabilities(m_ff_device->m_device, &ff_caps))) + { + InitForceFeedback(m_ff_device, ff_caps.numFfAxes); + } +} + +Joystick::~Joystick() +{ + if (m_ff_device) + m_ff_device->Release(); +} + +std::string Joystick::GetName() const +{ + return m_device_name; +} + +std::string Joystick::GetSource() const +{ + return "Input"; +} + +int Joystick::GetId() const +{ + return m_index; +} + +ControlState Joystick::Button::GetState() const +{ + IOHIDValueRef value; + if (IOHIDDeviceGetValue(m_device, m_element, &value) == kIOReturnSuccess) + return IOHIDValueGetIntegerValue(value); + else + return 0; +} + +std::string Joystick::Button::GetName() const +{ + std::ostringstream s; + s << IOHIDElementGetUsage(m_element); + return std::string("Button ") + s.str(); +} + +Joystick::Axis::Axis(IOHIDElementRef element, IOHIDDeviceRef device, direction dir) + : m_element(element) + , m_device(device) + , m_direction(dir) +{ + // Need to parse the element a bit first + std::string description("unk"); + + int const usage = IOHIDElementGetUsage(m_element); + switch (usage) + { + case kHIDUsage_GD_X: + description = "X"; + break; + case kHIDUsage_GD_Y: + description = "Y"; + break; + case kHIDUsage_GD_Z: + description = "Z"; + break; + case kHIDUsage_GD_Rx: + description = "Rx"; + break; + case kHIDUsage_GD_Ry: + description = "Ry"; + break; + case kHIDUsage_GD_Rz: + description = "Rz"; + break; + case kHIDUsage_GD_Wheel: + description = "Wheel"; + break; + case kHIDUsage_Csmr_ACPan: + description = "Pan"; + break; + default: + { + std::ostringstream s; + s << usage; + description = s.str(); + break; + } + } + + m_name = std::string("Axis ") + description; + m_name.append((m_direction == positive) ? "+" : "-"); + + m_neutral = (IOHIDElementGetLogicalMax(m_element) + + IOHIDElementGetLogicalMin(m_element)) / 2.; + m_scale = 1 / fabs(IOHIDElementGetLogicalMax(m_element) - m_neutral); +} + +ControlState Joystick::Axis::GetState() const +{ + IOHIDValueRef value; + + if (IOHIDDeviceGetValue(m_device, m_element, &value) == kIOReturnSuccess) + { + // IOHIDValueGetIntegerValue() crashes when trying + // to convert unusually large element values. + if (IOHIDValueGetLength(value) > 2) + return 0; + + float position = IOHIDValueGetIntegerValue(value); + + if (m_direction == positive && position > m_neutral) + return (position - m_neutral) * m_scale; + if (m_direction == negative && position < m_neutral) + return (m_neutral - position) * m_scale; + } + + return 0; +} + +std::string Joystick::Axis::GetName() const +{ + return m_name; +} + +Joystick::Hat::Hat(IOHIDElementRef element, IOHIDDeviceRef device, direction dir) + : m_element(element) + , m_device(device) + , m_direction(dir) +{ + switch (dir) + { + case up: + m_name = "Up"; + break; + case right: + m_name = "Right"; + break; + case down: + m_name = "Down"; + break; + case left: + m_name = "Left"; + break; + default: + m_name = "unk"; + } +} + +ControlState Joystick::Hat::GetState() const +{ + IOHIDValueRef value; + int position; + + if (IOHIDDeviceGetValue(m_device, m_element, &value) == kIOReturnSuccess) + { + position = IOHIDValueGetIntegerValue(value); + + switch (position) + { + case 0: + if (m_direction == up) + return 1; + break; + case 1: + if (m_direction == up || m_direction == right) + return 1; + break; + case 2: + if (m_direction == right) + return 1; + break; + case 3: + if (m_direction == right || m_direction == down) + return 1; + break; + case 4: + if (m_direction == down) + return 1; + break; + case 5: + if (m_direction == down || m_direction == left) + return 1; + break; + case 6: + if (m_direction == left) + return 1; + break; + case 7: + if (m_direction == left || m_direction == up) + return 1; + break; + }; + } + + return 0; +} + +std::string Joystick::Hat::GetName() const +{ + return m_name; +} + + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/OSX/OSXKeyboard.h b/Source/Core/InputCommon/ControllerInterface/OSX/OSXKeyboard.h new file mode 100644 index 0000000000..973bd1fefd --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/OSX/OSXKeyboard.h @@ -0,0 +1,78 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include <IOKit/hid/IOHIDLib.h> + +#include "InputCommon/ControllerInterface/Device.h" + +namespace ciface +{ +namespace OSX +{ + +class Keyboard : public Core::Device +{ +private: + class Key : public Input + { + public: + std::string GetName() const; + Key(IOHIDElementRef element, IOHIDDeviceRef device); + ControlState GetState() const; + private: + const IOHIDElementRef m_element; + const IOHIDDeviceRef m_device; + std::string m_name; + }; + + class Cursor : public Input + { + public: + std::string GetName() const; + bool IsDetectable() { return false; } + Cursor(u8 index, const float& axis, const bool positive) : m_axis(axis), m_index(index), m_positive(positive) {} + ControlState GetState() const; + private: + const float& m_axis; + const u8 m_index; + const bool m_positive; + }; + + class Button : public Input + { + public: + std::string GetName() const; + Button(u8 index, const unsigned char& button) : m_button(button), m_index(index) {} + ControlState GetState() const; + private: + const unsigned char& m_button; + const u8 m_index; + }; + +public: + void UpdateInput() override; + + Keyboard(IOHIDDeviceRef device, std::string name, int index, void *window); + + std::string GetName() const; + std::string GetSource() const; + int GetId() const; + +private: + struct + { + float x, y; + } m_cursor; + + const IOHIDDeviceRef m_device; + const std::string m_device_name; + int m_index; + uint32_t m_windowid; + unsigned char m_mousebuttons[3]; +}; + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/OSX/OSXKeyboard.mm b/Source/Core/InputCommon/ControllerInterface/OSX/OSXKeyboard.mm new file mode 100644 index 0000000000..9c5707bc5d --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/OSX/OSXKeyboard.mm @@ -0,0 +1,274 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <sstream> + +#include <Foundation/Foundation.h> +#include <IOKit/hid/IOHIDLib.h> +#include <Cocoa/Cocoa.h> + +#include "InputCommon/ControllerInterface/OSX/OSXKeyboard.h" + +namespace ciface +{ +namespace OSX +{ + +Keyboard::Keyboard(IOHIDDeviceRef device, std::string name, int index, void *window) + : m_device(device) + , m_device_name(name) + , m_index(index) +{ + // This class should only recieve Keyboard or Keypad devices + // Now, filter on just the buttons we can handle sanely + NSDictionary *matchingElements = @{ + @kIOHIDElementTypeKey : [NSNumber numberWithInteger: kIOHIDElementTypeInput_Button], + @kIOHIDElementMinKey : [NSNumber numberWithInteger: 0], + @kIOHIDElementMaxKey : [NSNumber numberWithInteger: 1] + }; + + CFArrayRef elements = IOHIDDeviceCopyMatchingElements(m_device, + (CFDictionaryRef)matchingElements, kIOHIDOptionsTypeNone); + + if (elements) + { + for (int i = 0; i < CFArrayGetCount(elements); i++) + { + IOHIDElementRef e = + (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i); + //DeviceElementDebugPrint(e, nullptr); + + AddInput(new Key(e, m_device)); + } + CFRelease(elements); + } + + m_windowid = [[reinterpret_cast<NSView*>(window) window] windowNumber]; + + // cursor, with a hax for-loop + for (unsigned int i=0; i<4; ++i) + AddInput(new Cursor(!!(i&2), (&m_cursor.x)[i/2], !!(i&1))); + + for (u8 i = 0; i < sizeof(m_mousebuttons) / sizeof(m_mousebuttons[0]); ++i) + AddInput(new Button(i, m_mousebuttons[i])); +} + +void Keyboard::UpdateInput() +{ + CGRect bounds = CGRectZero; + uint32_t windowid[1] = { m_windowid }; + CFArrayRef windowArray = CFArrayCreate(nullptr, (const void **) windowid, 1, nullptr); + CFArrayRef windowDescriptions = CGWindowListCreateDescriptionFromArray(windowArray); + CFDictionaryRef windowDescription = (CFDictionaryRef) CFArrayGetValueAtIndex((CFArrayRef) windowDescriptions, 0); + + if (CFDictionaryContainsKey(windowDescription, kCGWindowBounds)) + { + CFDictionaryRef boundsDictionary = (CFDictionaryRef) CFDictionaryGetValue(windowDescription, kCGWindowBounds); + + if (boundsDictionary != nullptr) + CGRectMakeWithDictionaryRepresentation(boundsDictionary, &bounds); + } + + CFRelease(windowDescriptions); + CFRelease(windowArray); + + CGEventRef event = CGEventCreate(nil); + CGPoint loc = CGEventGetLocation(event); + CFRelease(event); + + loc.x -= bounds.origin.x; + loc.y -= bounds.origin.y; + m_cursor.x = loc.x / bounds.size.width * 2 - 1.0; + m_cursor.y = loc.y / bounds.size.height * 2 - 1.0; + + m_mousebuttons[0] = CGEventSourceButtonState(kCGEventSourceStateHIDSystemState, kCGMouseButtonLeft); + m_mousebuttons[1] = CGEventSourceButtonState(kCGEventSourceStateHIDSystemState, kCGMouseButtonRight); + m_mousebuttons[2] = CGEventSourceButtonState(kCGEventSourceStateHIDSystemState, kCGMouseButtonCenter); +} + +std::string Keyboard::GetName() const +{ + return m_device_name; +} + +std::string Keyboard::GetSource() const +{ + return "Keyboard"; +} + +int Keyboard::GetId() const +{ + return m_index; +} + +Keyboard::Key::Key(IOHIDElementRef element, IOHIDDeviceRef device) + : m_element(element) + , m_device(device) +{ + static const struct PrettyKeys + { + const uint32_t code; + const char *const name; + } named_keys[] = { + { kHIDUsage_KeyboardA, "A" }, + { kHIDUsage_KeyboardB, "B" }, + { kHIDUsage_KeyboardC, "C" }, + { kHIDUsage_KeyboardD, "D" }, + { kHIDUsage_KeyboardE, "E" }, + { kHIDUsage_KeyboardF, "F" }, + { kHIDUsage_KeyboardG, "G" }, + { kHIDUsage_KeyboardH, "H" }, + { kHIDUsage_KeyboardI, "I" }, + { kHIDUsage_KeyboardJ, "J" }, + { kHIDUsage_KeyboardK, "K" }, + { kHIDUsage_KeyboardL, "L" }, + { kHIDUsage_KeyboardM, "M" }, + { kHIDUsage_KeyboardN, "N" }, + { kHIDUsage_KeyboardO, "O" }, + { kHIDUsage_KeyboardP, "P" }, + { kHIDUsage_KeyboardQ, "Q" }, + { kHIDUsage_KeyboardR, "R" }, + { kHIDUsage_KeyboardS, "S" }, + { kHIDUsage_KeyboardT, "T" }, + { kHIDUsage_KeyboardU, "U" }, + { kHIDUsage_KeyboardV, "V" }, + { kHIDUsage_KeyboardW, "W" }, + { kHIDUsage_KeyboardX, "X" }, + { kHIDUsage_KeyboardY, "Y" }, + { kHIDUsage_KeyboardZ, "Z" }, + { kHIDUsage_Keyboard1, "1" }, + { kHIDUsage_Keyboard2, "2" }, + { kHIDUsage_Keyboard3, "3" }, + { kHIDUsage_Keyboard4, "4" }, + { kHIDUsage_Keyboard5, "5" }, + { kHIDUsage_Keyboard6, "6" }, + { kHIDUsage_Keyboard7, "7" }, + { kHIDUsage_Keyboard8, "8" }, + { kHIDUsage_Keyboard9, "9" }, + { kHIDUsage_Keyboard0, "0" }, + { kHIDUsage_KeyboardReturnOrEnter, "Return" }, + { kHIDUsage_KeyboardEscape, "Escape" }, + { kHIDUsage_KeyboardDeleteOrBackspace, "Backspace" }, + { kHIDUsage_KeyboardTab, "Tab" }, + { kHIDUsage_KeyboardSpacebar, "Space" }, + { kHIDUsage_KeyboardHyphen, "-" }, + { kHIDUsage_KeyboardEqualSign, "=" }, + { kHIDUsage_KeyboardOpenBracket, "[" }, + { kHIDUsage_KeyboardCloseBracket, "]" }, + { kHIDUsage_KeyboardBackslash, "\\" }, + { kHIDUsage_KeyboardSemicolon, ";" }, + { kHIDUsage_KeyboardQuote, "'" }, + { kHIDUsage_KeyboardGraveAccentAndTilde, "Tilde" }, + { kHIDUsage_KeyboardComma, "," }, + { kHIDUsage_KeyboardPeriod, "." }, + { kHIDUsage_KeyboardSlash, "/" }, + { kHIDUsage_KeyboardCapsLock, "Caps Lock" }, + { kHIDUsage_KeyboardF1, "F1" }, + { kHIDUsage_KeyboardF2, "F2" }, + { kHIDUsage_KeyboardF3, "F3" }, + { kHIDUsage_KeyboardF4, "F4" }, + { kHIDUsage_KeyboardF5, "F5" }, + { kHIDUsage_KeyboardF6, "F6" }, + { kHIDUsage_KeyboardF7, "F7" }, + { kHIDUsage_KeyboardF8, "F8" }, + { kHIDUsage_KeyboardF9, "F9" }, + { kHIDUsage_KeyboardF10, "F10" }, + { kHIDUsage_KeyboardF11, "F11" }, + { kHIDUsage_KeyboardF12, "F12" }, + { kHIDUsage_KeyboardInsert, "Insert" }, + { kHIDUsage_KeyboardHome, "Home" }, + { kHIDUsage_KeyboardPageUp, "Page Up" }, + { kHIDUsage_KeyboardDeleteForward, "Delete" }, + { kHIDUsage_KeyboardEnd, "End" }, + { kHIDUsage_KeyboardPageDown, "Page Down" }, + { kHIDUsage_KeyboardRightArrow, "Right Arrow" }, + { kHIDUsage_KeyboardLeftArrow, "Left Arrow" }, + { kHIDUsage_KeyboardDownArrow, "Down Arrow" }, + { kHIDUsage_KeyboardUpArrow, "Up Arrow" }, + { kHIDUsage_KeypadSlash, "Keypad /" }, + { kHIDUsage_KeypadAsterisk, "Keypad *" }, + { kHIDUsage_KeypadHyphen, "Keypad -" }, + { kHIDUsage_KeypadPlus, "Keypad +" }, + { kHIDUsage_KeypadEnter, "Keypad Enter" }, + { kHIDUsage_Keypad1, "Keypad 1" }, + { kHIDUsage_Keypad2, "Keypad 2" }, + { kHIDUsage_Keypad3, "Keypad 3" }, + { kHIDUsage_Keypad4, "Keypad 4" }, + { kHIDUsage_Keypad5, "Keypad 5" }, + { kHIDUsage_Keypad6, "Keypad 6" }, + { kHIDUsage_Keypad7, "Keypad 7" }, + { kHIDUsage_Keypad8, "Keypad 8" }, + { kHIDUsage_Keypad9, "Keypad 9" }, + { kHIDUsage_Keypad0, "Keypad 0" }, + { kHIDUsage_KeypadPeriod, "Keypad ." }, + { kHIDUsage_KeyboardNonUSBackslash, "Paragraph" }, + { kHIDUsage_KeypadEqualSign, "Keypad =" }, + { kHIDUsage_KeypadComma, "Keypad ," }, + { kHIDUsage_KeyboardLeftControl, "Left Control" }, + { kHIDUsage_KeyboardLeftShift, "Left Shift" }, + { kHIDUsage_KeyboardLeftAlt, "Left Alt" }, + { kHIDUsage_KeyboardLeftGUI, "Left Command" }, + { kHIDUsage_KeyboardRightControl, "Right Control" }, + { kHIDUsage_KeyboardRightShift, "Right Shift" }, + { kHIDUsage_KeyboardRightAlt, "Right Alt" }, + { kHIDUsage_KeyboardRightGUI, "Right Command" }, + { 184, "Eject" }, + }; + + const uint32_t keycode = IOHIDElementGetUsage(m_element); + for (auto & named_key : named_keys) + { + if (named_key.code == keycode) + { + m_name = named_key.name; + return; + } + } + + std::stringstream ss; + ss << "Key " << keycode; + m_name = ss.str(); +} + +ControlState Keyboard::Key::GetState() const +{ + IOHIDValueRef value; + + if (IOHIDDeviceGetValue(m_device, m_element, &value) == kIOReturnSuccess) + return IOHIDValueGetIntegerValue(value); + else + return 0; +} + +ControlState Keyboard::Cursor::GetState() const +{ + return std::max(0.0, ControlState(m_axis) / (m_positive ? 1.0 : -1.0)); +} + +ControlState Keyboard::Button::GetState() const +{ + return (m_button != 0); +} + +std::string Keyboard::Cursor::GetName() const +{ + static char tmpstr[] = "Cursor .."; + tmpstr[7] = (char)('X' + m_index); + tmpstr[8] = (m_positive ? '+' : '-'); + return tmpstr; +} + +std::string Keyboard::Button::GetName() const +{ + return std::string("Click ") + char('0' + m_index); +} + +std::string Keyboard::Key::GetName() const +{ + return m_name; +} + + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp new file mode 100644 index 0000000000..6cdfa470cd --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp @@ -0,0 +1,346 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <algorithm> +#include <map> +#include <sstream> + +#include "Common/StringUtil.h" +#include "InputCommon/ControllerInterface/SDL/SDL.h" + +#ifdef _WIN32 +#pragma comment(lib, "SDL2.lib") +#endif + +namespace ciface +{ +namespace SDL +{ + +// 10ms = 100Hz which homebrew docs very roughly imply is within WiiMote normal +// range, used for periodic haptic effects though often ignored by devices +static const u16 RUMBLE_PERIOD = 10; +static const u16 RUMBLE_LENGTH_MAX = 500; // ms: enough to span multiple frames at low FPS, but still finite + +static std::string GetJoystickName(int index) +{ +#if SDL_VERSION_ATLEAST(2, 0, 0) + return SDL_JoystickNameForIndex(index); +#else + return SDL_JoystickName(index); +#endif +} + +void Init( std::vector<Core::Device*>& devices ) +{ + // this is used to number the joysticks + // multiple joysticks with the same name shall get unique ids starting at 0 + std::map<std::string, int> name_counts; + +#ifdef USE_SDL_HAPTIC + if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC) >= 0) + { + // Correctly initialized + } + else +#endif + if (SDL_Init(SDL_INIT_JOYSTICK) < 0) + { + // Failed to initialize + return; + } + + // joysticks + for (int i = 0; i < SDL_NumJoysticks(); ++i) + { + SDL_Joystick* dev = SDL_JoystickOpen(i); + if (dev) + { + Joystick* js = new Joystick(dev, i, name_counts[GetJoystickName(i)]++); + // only add if it has some inputs/outputs + if (js->Inputs().size() || js->Outputs().size()) + devices.push_back( js ); + else + delete js; + } + } +} + +Joystick::Joystick(SDL_Joystick* const joystick, const int sdl_index, const unsigned int index) + : m_joystick(joystick) + , m_sdl_index(sdl_index) + , m_index(index) +{ + // really bad HACKS: + // to not use SDL for an XInput device + // too many people on the forums pick the SDL device and ask: + // "why don't my 360 gamepad triggers/rumble work correctly" +#ifdef _WIN32 + // checking the name is probably good (and hacky) enough + // but I'll double check with the num of buttons/axes + std::string lcasename = GetName(); + std::transform(lcasename.begin(), lcasename.end(), lcasename.begin(), tolower); + + if ((std::string::npos != lcasename.find("xbox 360")) && + (10 == SDL_JoystickNumButtons(joystick)) && + (5 == SDL_JoystickNumAxes(joystick)) && + (1 == SDL_JoystickNumHats(joystick)) && + (0 == SDL_JoystickNumBalls(joystick))) + { + // this device won't be used + return; + } +#endif + + if (SDL_JoystickNumButtons(joystick) > 255 || + SDL_JoystickNumAxes(joystick) > 255 || + SDL_JoystickNumHats(joystick) > 255 || + SDL_JoystickNumBalls(joystick) > 255) + { + // This device is invalid, don't use it + // Some crazy devices(HP webcam 2100) end up as HID devices + // SDL tries parsing these as joysticks + return; + } + + // get buttons + for (u8 i = 0; i != SDL_JoystickNumButtons(m_joystick); ++i) + AddInput(new Button(i, m_joystick)); + + // get hats + for (u8 i = 0; i != SDL_JoystickNumHats(m_joystick); ++i) + { + // each hat gets 4 input instances associated with it, (up down left right) + for (u8 d = 0; d != 4; ++d) + AddInput(new Hat(i, m_joystick, d)); + } + + // get axes + for (u8 i = 0; i != SDL_JoystickNumAxes(m_joystick); ++i) + { + // each axis gets a negative and a positive input instance associated with it + AddAnalogInputs(new Axis(i, m_joystick, -32768), + new Axis(i, m_joystick, 32767)); + } + +#ifdef USE_SDL_HAPTIC + // try to get supported ff effects + m_haptic = SDL_HapticOpenFromJoystick( m_joystick ); + if (m_haptic) + { + //SDL_HapticSetGain( m_haptic, 1000 ); + //SDL_HapticSetAutocenter( m_haptic, 0 ); + + const unsigned int supported_effects = SDL_HapticQuery( m_haptic ); + + // constant effect + if (supported_effects & SDL_HAPTIC_CONSTANT) + AddOutput(new ConstantEffect(m_haptic)); + + // ramp effect + if (supported_effects & SDL_HAPTIC_RAMP) + AddOutput(new RampEffect(m_haptic)); + + // sine effect + if (supported_effects & SDL_HAPTIC_SINE) + AddOutput(new SineEffect(m_haptic)); + + // triangle effect + if (supported_effects & SDL_HAPTIC_TRIANGLE) + AddOutput(new TriangleEffect(m_haptic)); + + // left-right effect + if (supported_effects & SDL_HAPTIC_LEFTRIGHT) + AddOutput(new LeftRightEffect(m_haptic)); + } +#endif + +} + +Joystick::~Joystick() +{ +#ifdef USE_SDL_HAPTIC + if (m_haptic) + { + // stop/destroy all effects + SDL_HapticStopAll(m_haptic); + // close haptic first + SDL_HapticClose(m_haptic); + } +#endif + + // close joystick + SDL_JoystickClose(m_joystick); +} + +#ifdef USE_SDL_HAPTIC +void Joystick::HapticEffect::Update() +{ + if (m_id == -1 && m_effect.type > 0) + { + m_id = SDL_HapticNewEffect(m_haptic, &m_effect); + if (m_id > -1) + SDL_HapticRunEffect(m_haptic, m_id, 1); + } + else if (m_id > -1 && m_effect.type == 0) + { + SDL_HapticStopEffect(m_haptic, m_id); + SDL_HapticDestroyEffect(m_haptic, m_id); + m_id = -1; + } + else if (m_id > -1) + { + SDL_HapticUpdateEffect(m_haptic, m_id, &m_effect); + } +} + +std::string Joystick::ConstantEffect::GetName() const +{ + return "Constant"; +} + +std::string Joystick::RampEffect::GetName() const +{ + return "Ramp"; +} + +std::string Joystick::SineEffect::GetName() const +{ + return "Sine"; +} + +std::string Joystick::TriangleEffect::GetName() const +{ + return "Triangle"; +} + +std::string Joystick::LeftRightEffect::GetName() const +{ + return "LeftRight"; +} + +void Joystick::HapticEffect::SetState(ControlState state) +{ + memset(&m_effect, 0, sizeof(m_effect)); + if (state) + { + SetSDLHapticEffect(state); + } + else + { + // this module uses type==0 to indicate 'off' + m_effect.type = 0; + } + Update(); +} + +void Joystick::ConstantEffect::SetSDLHapticEffect(ControlState state) +{ + m_effect.type = SDL_HAPTIC_CONSTANT; + m_effect.constant.length = RUMBLE_LENGTH_MAX; + m_effect.constant.level = (Sint16)(state * 0x7FFF); +} + +void Joystick::RampEffect::SetSDLHapticEffect(ControlState state) +{ + m_effect.type = SDL_HAPTIC_RAMP; + m_effect.ramp.length = RUMBLE_LENGTH_MAX; + m_effect.ramp.start = (Sint16)(state * 0x7FFF); +} + +void Joystick::SineEffect::SetSDLHapticEffect(ControlState state) +{ + m_effect.type = SDL_HAPTIC_SINE; + m_effect.periodic.period = RUMBLE_PERIOD; + m_effect.periodic.magnitude = (Sint16)(state * 0x7FFF); + m_effect.periodic.offset = 0; + m_effect.periodic.phase = 18000; + m_effect.periodic.length = RUMBLE_LENGTH_MAX; + m_effect.periodic.delay = 0; + m_effect.periodic.attack_length = 0; +} + +void Joystick::TriangleEffect::SetSDLHapticEffect(ControlState state) +{ + m_effect.type = SDL_HAPTIC_TRIANGLE; + m_effect.periodic.period = RUMBLE_PERIOD; + m_effect.periodic.magnitude = (Sint16)(state * 0x7FFF); + m_effect.periodic.offset = 0; + m_effect.periodic.phase = 18000; + m_effect.periodic.length = RUMBLE_LENGTH_MAX; + m_effect.periodic.delay = 0; + m_effect.periodic.attack_length = 0; +} + +void Joystick::LeftRightEffect::SetSDLHapticEffect(ControlState state) +{ + m_effect.type = SDL_HAPTIC_LEFTRIGHT; + m_effect.leftright.length = RUMBLE_LENGTH_MAX; + // max ranges tuned to 'feel' similar in magnitude to triangle/sine on xbox360 controller + m_effect.leftright.large_magnitude = (Uint16)(state * 0x4000); + m_effect.leftright.small_magnitude = (Uint16)(state * 0xFFFF); +} +#endif + +void Joystick::UpdateInput() +{ + // each joystick is doin this, o well + SDL_JoystickUpdate(); +} + +std::string Joystick::GetName() const +{ + return StripSpaces(GetJoystickName(m_sdl_index)); +} + +std::string Joystick::GetSource() const +{ + return "SDL"; +} + +int Joystick::GetId() const +{ + return m_index; +} + +std::string Joystick::Button::GetName() const +{ + std::ostringstream ss; + ss << "Button " << (int)m_index; + return ss.str(); +} + +std::string Joystick::Axis::GetName() const +{ + std::ostringstream ss; + ss << "Axis " << (int)m_index << (m_range<0 ? '-' : '+'); + return ss.str(); +} + +std::string Joystick::Hat::GetName() const +{ + static char tmpstr[] = "Hat . ."; + // I don't think more than 10 hats are supported + tmpstr[4] = (char)('0' + m_index); + tmpstr[6] = "NESW"[m_direction]; + return tmpstr; +} + +ControlState Joystick::Button::GetState() const +{ + return SDL_JoystickGetButton(m_js, m_index); +} + +ControlState Joystick::Axis::GetState() const +{ + return std::max(0.0, ControlState(SDL_JoystickGetAxis(m_js, m_index)) / m_range); +} + +ControlState Joystick::Hat::GetState() const +{ + return (SDL_JoystickGetHat(m_js, m_index) & (1 << m_direction)) > 0; +} + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.h b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.h new file mode 100644 index 0000000000..751f40e9f4 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.h @@ -0,0 +1,153 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include <list> + +#include <SDL.h> + +#include "InputCommon/ControllerInterface/Device.h" + + +#if SDL_VERSION_ATLEAST(1, 3, 0) + #define USE_SDL_HAPTIC +#endif + +#ifdef USE_SDL_HAPTIC + #include <SDL_haptic.h> +#endif + +namespace ciface +{ +namespace SDL +{ + +void Init( std::vector<Core::Device*>& devices ); + +class Joystick : public Core::Device +{ +private: + + class Button : public Core::Device::Input + { + public: + std::string GetName() const override; + Button(u8 index, SDL_Joystick* js) : m_js(js), m_index(index) {} + ControlState GetState() const override; + private: + SDL_Joystick* const m_js; + const u8 m_index; + }; + + class Axis : public Core::Device::Input + { + public: + std::string GetName() const override; + Axis(u8 index, SDL_Joystick* js, Sint16 range) : m_js(js), m_range(range), m_index(index) {} + ControlState GetState() const override; + private: + SDL_Joystick* const m_js; + const Sint16 m_range; + const u8 m_index; + }; + + class Hat : public Input + { + public: + std::string GetName() const override; + Hat(u8 index, SDL_Joystick* js, u8 direction) : m_js(js), m_direction(direction), m_index(index) {} + ControlState GetState() const override; + private: + SDL_Joystick* const m_js; + const u8 m_direction; + const u8 m_index; + }; + +#ifdef USE_SDL_HAPTIC + class HapticEffect : public Output + { + public: + HapticEffect(SDL_Haptic* haptic) : m_haptic(haptic), m_id(-1) {} + ~HapticEffect() { m_effect.type = 0; Update(); } + + protected: + void Update(); + virtual void SetSDLHapticEffect(ControlState state) = 0; + + SDL_HapticEffect m_effect; + SDL_Haptic* m_haptic; + int m_id; + private: + virtual void SetState(ControlState state) override final; + }; + + class ConstantEffect : public HapticEffect + { + public: + ConstantEffect(SDL_Haptic* haptic) : HapticEffect(haptic) {} + std::string GetName() const override; + private: + void SetSDLHapticEffect(ControlState state) override; + }; + + class RampEffect : public HapticEffect + { + public: + RampEffect(SDL_Haptic* haptic) : HapticEffect(haptic) {} + std::string GetName() const override; + private: + void SetSDLHapticEffect(ControlState state) override; + }; + + class SineEffect : public HapticEffect + { + public: + SineEffect(SDL_Haptic* haptic) : HapticEffect(haptic) {} + std::string GetName() const override; + private: + void SetSDLHapticEffect(ControlState state) override; + }; + + class TriangleEffect : public HapticEffect + { + public: + TriangleEffect(SDL_Haptic* haptic) : HapticEffect(haptic) {} + std::string GetName() const override; + private: + void SetSDLHapticEffect(ControlState state) override; + }; + + class LeftRightEffect : public HapticEffect + { + public: + LeftRightEffect(SDL_Haptic* haptic) : HapticEffect(haptic) {} + std::string GetName() const override; + private: + void SetSDLHapticEffect(ControlState state) override; + }; +#endif + +public: + void UpdateInput() override; + + Joystick(SDL_Joystick* const joystick, const int sdl_index, const unsigned int index); + ~Joystick(); + + std::string GetName() const override; + int GetId() const override; + std::string GetSource() const override; + +private: + SDL_Joystick* const m_joystick; + const int m_sdl_index; + const unsigned int m_index; + +#ifdef USE_SDL_HAPTIC + SDL_Haptic* m_haptic; +#endif +}; + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/XInput/XInput.cpp b/Source/Core/InputCommon/ControllerInterface/XInput/XInput.cpp new file mode 100644 index 0000000000..0e2c0bcad3 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/XInput/XInput.cpp @@ -0,0 +1,265 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + + +#include "InputCommon/ControllerInterface/XInput/XInput.h" + +#ifndef XINPUT_GAMEPAD_GUIDE +#define XINPUT_GAMEPAD_GUIDE 0x0400 +#endif + +namespace ciface +{ +namespace XInput +{ + +static const struct +{ + const char* const name; + const WORD bitmask; +} named_buttons[] = +{ + { "Button A", XINPUT_GAMEPAD_A }, + { "Button B", XINPUT_GAMEPAD_B }, + { "Button X", XINPUT_GAMEPAD_X }, + { "Button Y", XINPUT_GAMEPAD_Y }, + { "Pad N", XINPUT_GAMEPAD_DPAD_UP }, + { "Pad S", XINPUT_GAMEPAD_DPAD_DOWN }, + { "Pad W", XINPUT_GAMEPAD_DPAD_LEFT }, + { "Pad E", XINPUT_GAMEPAD_DPAD_RIGHT }, + { "Start", XINPUT_GAMEPAD_START }, + { "Back", XINPUT_GAMEPAD_BACK }, + { "Shoulder L", XINPUT_GAMEPAD_LEFT_SHOULDER }, + { "Shoulder R", XINPUT_GAMEPAD_RIGHT_SHOULDER }, + { "Guide", XINPUT_GAMEPAD_GUIDE }, + { "Thumb L", XINPUT_GAMEPAD_LEFT_THUMB }, + { "Thumb R", XINPUT_GAMEPAD_RIGHT_THUMB } +}; + +static const char* const named_triggers[] = +{ + "Trigger L", + "Trigger R" +}; + +static const char* const named_axes[] = +{ + "Left X", + "Left Y", + "Right X", + "Right Y" +}; + +static const char* const named_motors[] = +{ + "Motor L", + "Motor R" +}; + +static HMODULE hXInput = nullptr; + +typedef decltype(&XInputGetCapabilities) XInputGetCapabilities_t; +typedef decltype(&XInputSetState) XInputSetState_t; +typedef decltype(&XInputGetState) XInputGetState_t; + +static XInputGetCapabilities_t PXInputGetCapabilities = nullptr; +static XInputSetState_t PXInputSetState = nullptr; +static XInputGetState_t PXInputGetState = nullptr; + +static bool haveGuideButton = false; + +void Init(std::vector<Core::Device*>& devices) +{ + if (!hXInput) + { + // Try for the most recent version we were compiled against (will only work if running on Win8+) + hXInput = ::LoadLibrary(XINPUT_DLL); + if (!hXInput) + { + // Drop back to DXSDK June 2010 version. Requires DX June 2010 redist. + hXInput = ::LoadLibrary(TEXT("xinput1_3.dll")); + if (!hXInput) + { + return; + } + } + + PXInputGetCapabilities = (XInputGetCapabilities_t)::GetProcAddress(hXInput, "XInputGetCapabilities"); + PXInputSetState = (XInputSetState_t)::GetProcAddress(hXInput, "XInputSetState"); + + // Ordinal 100 is the same as XInputGetState, except it doesn't dummy out the guide + // button info. Try loading it and fall back if needed. + PXInputGetState = (XInputGetState_t)::GetProcAddress(hXInput, (LPCSTR)100); + if (PXInputGetState) + haveGuideButton = true; + else + PXInputGetState = (XInputGetState_t)::GetProcAddress(hXInput, "XInputGetState"); + + if (!PXInputGetCapabilities || + !PXInputSetState || + !PXInputGetState) + { + ::FreeLibrary(hXInput); + hXInput = nullptr; + return; + } + } + + XINPUT_CAPABILITIES caps; + for (int i = 0; i != 4; ++i) + if (ERROR_SUCCESS == PXInputGetCapabilities(i, 0, &caps)) + devices.push_back(new Device(caps, i)); +} + +void DeInit() +{ + if (hXInput) + { + ::FreeLibrary(hXInput); + hXInput = nullptr; + } +} + +Device::Device(const XINPUT_CAPABILITIES& caps, u8 index) + : m_index(index), m_subtype(caps.SubType) +{ + ZeroMemory(&m_state_out, sizeof(m_state_out)); + + // XInputGetCaps seems to always claim all capabilities are supported + // but I will leave all this stuff in, incase m$ fixes xinput up a bit + + // get supported buttons + for (int i = 0; i != sizeof(named_buttons)/sizeof(*named_buttons); ++i) + { + // Guide button is never reported in caps + if ((named_buttons[i].bitmask & caps.Gamepad.wButtons) || + ((named_buttons[i].bitmask & XINPUT_GAMEPAD_GUIDE) && haveGuideButton)) + AddInput(new Button(i, m_state_in.Gamepad.wButtons)); + } + + // get supported triggers + for (int i = 0; i != sizeof(named_triggers)/sizeof(*named_triggers); ++i) + { + //BYTE val = (&caps.Gamepad.bLeftTrigger)[i]; // should be max value / MSDN lies + if ((&caps.Gamepad.bLeftTrigger)[i]) + AddInput(new Trigger(i, (&m_state_in.Gamepad.bLeftTrigger)[i], 255 )); + } + + // get supported axes + for (int i = 0; i != sizeof(named_axes)/sizeof(*named_axes); ++i) + { + //SHORT val = (&caps.Gamepad.sThumbLX)[i]; // xinput doesn't give the range / MSDN is a liar + if ((&caps.Gamepad.sThumbLX)[i]) + { + const SHORT& ax = (&m_state_in.Gamepad.sThumbLX)[i]; + + // each axis gets a negative and a positive input instance associated with it + AddInput(new Axis(i, ax, -32768)); + AddInput(new Axis(i, ax, 32767)); + } + } + + // get supported motors + for (int i = 0; i != sizeof(named_motors)/sizeof(*named_motors); ++i) + { + //WORD val = (&caps.Vibration.wLeftMotorSpeed)[i]; // should be max value / nope, more lies + if ((&caps.Vibration.wLeftMotorSpeed)[i]) + AddOutput(new Motor(i, this, (&m_state_out.wLeftMotorSpeed)[i], 65535)); + } + + ZeroMemory(&m_state_in, sizeof(m_state_in)); +} + +std::string Device::GetName() const +{ + switch (m_subtype) + { + case XINPUT_DEVSUBTYPE_GAMEPAD: + return "Gamepad"; + case XINPUT_DEVSUBTYPE_WHEEL: + return "Wheel"; + case XINPUT_DEVSUBTYPE_ARCADE_STICK: + return "Arcade Stick"; + case XINPUT_DEVSUBTYPE_FLIGHT_STICK: + return "Flight Stick"; + case XINPUT_DEVSUBTYPE_DANCE_PAD: + return "Dance Pad"; + case XINPUT_DEVSUBTYPE_GUITAR: + return "Guitar"; + case XINPUT_DEVSUBTYPE_DRUM_KIT: + return "Drum Kit"; + default: + return "Device"; + } +} + +int Device::GetId() const +{ + return m_index; +} + +std::string Device::GetSource() const +{ + return "XInput"; +} + +// Update I/O + +void Device::UpdateInput() +{ + PXInputGetState(m_index, &m_state_in); +} + +void Device::UpdateMotors() +{ + PXInputSetState(m_index, &m_state_out); +} + +// GET name/source/id + +std::string Device::Button::GetName() const +{ + return named_buttons[m_index].name; +} + +std::string Device::Axis::GetName() const +{ + return std::string(named_axes[m_index]) + (m_range<0 ? '-' : '+'); +} + +std::string Device::Trigger::GetName() const +{ + return named_triggers[m_index]; +} + +std::string Device::Motor::GetName() const +{ + return named_motors[m_index]; +} + +// GET / SET STATES + +ControlState Device::Button::GetState() const +{ + return (m_buttons & named_buttons[m_index].bitmask) > 0; +} + +ControlState Device::Trigger::GetState() const +{ + return ControlState(m_trigger) / m_range; +} + +ControlState Device::Axis::GetState() const +{ + return std::max( 0.0, ControlState(m_axis) / m_range ); +} + +void Device::Motor::SetState(ControlState state) +{ + m_motor = (WORD)(state * m_range); + m_parent->UpdateMotors(); +} + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/XInput/XInput.h b/Source/Core/InputCommon/ControllerInterface/XInput/XInput.h new file mode 100644 index 0000000000..5dcfb0cee5 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/XInput/XInput.h @@ -0,0 +1,100 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +// XInput suffers a similar issue as XAudio2. Since Win8, it is part of the OS. +// However, unlike XAudio2 they have not made the API incompatible - so we just +// compile against the latest version and fall back to dynamically loading the +// old DLL. + +#pragma once + +#include <windows.h> +#include <XInput.h> + +#include "InputCommon/ControllerInterface/Device.h" + +#ifndef XINPUT_DEVSUBTYPE_FLIGHT_STICK +#error You are building this module against the wrong version of DirectX. You probably need to remove DXSDK_DIR from your include path and/or _WIN32_WINNT is wrong. +#endif + +namespace ciface +{ +namespace XInput +{ + +void Init(std::vector<Core::Device*>& devices); +void DeInit(); + +class Device : public Core::Device +{ +private: + class Button : public Core::Device::Input + { + public: + std::string GetName() const; + Button(u8 index, const WORD& buttons) : m_index(index), m_buttons(buttons) {} + ControlState GetState() const; + private: + const WORD& m_buttons; + u8 m_index; + }; + + class Axis : public Core::Device::Input + { + public: + std::string GetName() const; + Axis(u8 index, const SHORT& axis, SHORT range) : m_index(index), m_axis(axis), m_range(range) {} + ControlState GetState() const; + private: + const SHORT& m_axis; + const SHORT m_range; + const u8 m_index; + }; + + class Trigger : public Core::Device::Input + { + public: + std::string GetName() const; + Trigger(u8 index, const BYTE& trigger, BYTE range) : m_index(index), m_trigger(trigger), m_range(range) {} + ControlState GetState() const; + private: + const BYTE& m_trigger; + const BYTE m_range; + const u8 m_index; + }; + + class Motor : public Core::Device::Output + { + public: + std::string GetName() const; + Motor(u8 index, Device* parent, WORD &motor, WORD range) : m_index(index), m_parent(parent), m_motor(motor), m_range(range) {} + void SetState(ControlState state); + private: + WORD& m_motor; + const WORD m_range; + const u8 m_index; + Device* m_parent; + }; + +public: + void UpdateInput() override; + + Device(const XINPUT_CAPABILITIES& capabilities, u8 index); + + std::string GetName() const; + int GetId() const; + std::string GetSource() const; + + void UpdateMotors(); + +private: + XINPUT_STATE m_state_in; + XINPUT_VIBRATION m_state_out; + const BYTE m_subtype; + const u8 m_index; +}; + + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.cpp b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.cpp new file mode 100644 index 0000000000..e4983e80bb --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.cpp @@ -0,0 +1,369 @@ +// Copyright 2013 Max Eliaser +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <cmath> +#include <cstring> +#include <X11/XKBlib.h> + +#include "InputCommon/ControllerInterface/Xlib/XInput2.h" + +// This is an input plugin using the XInput 2.0 extension to the X11 protocol, +// loosely based on the old XLib plugin. (Has nothing to do with the XInput +// API on Windows.) + +// This plugin creates one KeyboardMouse object for each master pointer/ +// keyboard pair. Each KeyboardMouse object exports four types of controls: +// * Mouse button controls: hardcoded at five of them, but could be made to +// support infinitely many mouse buttons in theory; XInput2 has no limit. +// * Mouse cursor controls: one for each cardinal direction. Calculated by +// comparing the absolute position of the mouse pointer on screen to the +// center of the emulator window. +// * Mouse axis controls: one for each cardinal direction. Calculated using +// a running average of relative mouse motion on each axis. +// * Key controls: these correspond to a limited subset of the keyboard +// keys. + + +// Mouse axis control tuning. Unlike absolute mouse position, relative mouse +// motion data needs to be tweaked and smoothed out a bit to be usable. + +// Mouse axis control output is simply divided by this number. In practice, +// that just means you can use a smaller "dead zone" if you bind axis controls +// to a joystick. No real need to make this customizable. +#define MOUSE_AXIS_SENSITIVITY 8.0f + +// The mouse axis controls use a weighted running average. Each frame, the new +// value is the average of the old value and the amount of relative mouse +// motion during that frame. The old value is weighted by a ratio of +// MOUSE_AXIS_SMOOTHING:1 compared to the new value. Increasing +// MOUSE_AXIS_SMOOTHING makes the controls smoother, decreasing it makes them +// more responsive. This might be useful as a user-customizable option. +#define MOUSE_AXIS_SMOOTHING 1.5f + +namespace ciface +{ +namespace XInput2 +{ + +// This function will add zero or more KeyboardMouse objects to devices. +void Init(std::vector<Core::Device*>& devices, void* const hwnd) +{ + Display* dpy = XOpenDisplay(nullptr); + + // xi_opcode is important; it will be used to identify XInput events by + // the polling loop in UpdateInput. + int xi_opcode, event, error; + + // verify that the XInput extension is available + if (!XQueryExtension(dpy, "XInputExtension", &xi_opcode, &event, &error)) + return; + + // verify that the XInput extension is at at least version 2.0 + int major = 2, minor = 0; + + if (XIQueryVersion(dpy, &major, &minor) != Success) + return; + + // register all master devices with Dolphin + + XIDeviceInfo* all_masters; + XIDeviceInfo* current_master; + int num_masters; + + all_masters = XIQueryDevice(dpy, XIAllMasterDevices, &num_masters); + + for (int i = 0; i < num_masters; i++) + { + current_master = &all_masters[i]; + if (current_master->use == XIMasterPointer) + // Since current_master is a master pointer, its attachment must + // be a master keyboard. + devices.push_back(new KeyboardMouse((Window)hwnd, xi_opcode, current_master->deviceid, current_master->attachment)); + } + + XCloseDisplay(dpy); + + XIFreeDeviceInfo(all_masters); +} + +// Apply the event mask to the device and all its slaves. Only used in the +// constructor. Remember, each KeyboardMouse has its own copy of the event +// stream, which is how multiple event masks can "coexist." +void KeyboardMouse::SelectEventsForDevice(Window window, XIEventMask *mask, int deviceid) +{ + // Set the event mask for the master device. + mask->deviceid = deviceid; + XISelectEvents(m_display, window, mask, 1); + + // Query all the master device's slaves and set the same event mask for + // those too. There are two reasons we want to do this. For mouse devices, + // we want the raw motion events, and only slaves (i.e. physical hardware + // devices) emit those. For keyboard devices, selecting slaves avoids + // dealing with key focus. + + XIDeviceInfo* all_slaves; + XIDeviceInfo* current_slave; + int num_slaves; + + all_slaves = XIQueryDevice(m_display, XIAllDevices, &num_slaves); + + for (int i = 0; i < num_slaves; i++) + { + current_slave = &all_slaves[i]; + if ((current_slave->use != XISlavePointer && current_slave->use != XISlaveKeyboard) || current_slave->attachment != deviceid) + continue; + mask->deviceid = current_slave->deviceid; + XISelectEvents(m_display, window, mask, 1); + } + + XIFreeDeviceInfo(all_slaves); +} + +KeyboardMouse::KeyboardMouse(Window window, int opcode, int pointer, int keyboard) + : m_window(window), xi_opcode(opcode), pointer_deviceid(pointer), keyboard_deviceid(keyboard) +{ + memset(&m_state, 0, sizeof(m_state)); + + // The cool thing about each KeyboardMouse object having its own Display + // is that each one gets its own separate copy of the X11 event stream, + // which it can individually filter to get just the events it's interested + // in. So be aware that each KeyboardMouse object actually has its own X11 + // "context." + m_display = XOpenDisplay(nullptr); + + int min_keycode, max_keycode; + XDisplayKeycodes(m_display, &min_keycode, &max_keycode); + + int unused; // should always be 1 + XIDeviceInfo* pointer_device = XIQueryDevice(m_display, pointer_deviceid, &unused); + name = std::string(pointer_device->name); + XIFreeDeviceInfo(pointer_device); + + XIEventMask mask; + unsigned char mask_buf[(XI_LASTEVENT + 7)/8]; + + mask.mask_len = sizeof(mask_buf); + mask.mask = mask_buf; + memset(mask_buf, 0, sizeof(mask_buf)); + + XISetMask(mask_buf, XI_ButtonPress); + XISetMask(mask_buf, XI_ButtonRelease); + XISetMask(mask_buf, XI_RawMotion); + XISetMask(mask_buf, XI_KeyPress); + XISetMask(mask_buf, XI_KeyRelease); + + SelectEventsForDevice(DefaultRootWindow(m_display), &mask, pointer_deviceid); + SelectEventsForDevice(DefaultRootWindow(m_display), &mask, keyboard_deviceid); + + // Keyboard Keys + for (int i = min_keycode; i <= max_keycode; ++i) + { + Key* temp_key = new Key(m_display, i, m_state.keyboard); + if (temp_key->m_keyname.length()) + AddInput(temp_key); + else + delete temp_key; + } + + // Mouse Buttons + for (int i = 0; i < 5; i++) + AddInput(new Button(i, &m_state.buttons)); + + // Mouse Cursor, X-/+ and Y-/+ + for (int i = 0; i != 4; ++i) + AddInput(new Cursor(!!(i & 2), !!(i & 1), (i & 2) ? &m_state.cursor.y : &m_state.cursor.x)); + + // Mouse Axis, X-/+ and Y-/+ + for (int i = 0; i != 4; ++i) + AddInput(new Axis(!!(i & 2), !!(i & 1), (i & 2) ? &m_state.axis.y : &m_state.axis.x)); +} + +KeyboardMouse::~KeyboardMouse() +{ + XCloseDisplay(m_display); +} + +// Update the mouse cursor controls +void KeyboardMouse::UpdateCursor() +{ + double root_x, root_y, win_x, win_y; + Window root, child; + + // unused-- we're not interested in button presses here, as those are + // updated using events + XIButtonState button_state; + XIModifierState mods; + XIGroupState group; + + XIQueryPointer(m_display, pointer_deviceid, m_window, &root, &child, &root_x, &root_y, &win_x, &win_y, &button_state, &mods, &group); + + free (button_state.mask); + + XWindowAttributes win_attribs; + XGetWindowAttributes(m_display, m_window, &win_attribs); + + // the mouse position as a range from -1 to 1 + m_state.cursor.x = win_x / (float)win_attribs.width * 2 - 1; + m_state.cursor.y = win_y / (float)win_attribs.height * 2 - 1; +} + +void KeyboardMouse::UpdateInput() +{ + XFlush(m_display); + + // Get the absolute position of the mouse pointer + UpdateCursor(); + + // for the axis controls + float delta_x = 0.0f, delta_y = 0.0f; + double delta_delta; + + // Iterate through the event queue - update the axis controls, mouse + // button controls, and keyboard controls. + XEvent event; + while (XPending(m_display)) + { + XNextEvent(m_display, &event); + + if (event.xcookie.type != GenericEvent) + continue; + if (event.xcookie.extension != xi_opcode) + continue; + if (!XGetEventData(m_display, &event.xcookie)) + continue; + + // only one of these will get used + XIDeviceEvent* dev_event = (XIDeviceEvent*)event.xcookie.data; + XIRawEvent* raw_event = (XIRawEvent*)event.xcookie.data; + + switch (event.xcookie.evtype) + { + case XI_ButtonPress: + m_state.buttons |= 1<<(dev_event->detail-1); + break; + case XI_ButtonRelease: + m_state.buttons &= ~(1<<(dev_event->detail-1)); + break; + case XI_KeyPress: + m_state.keyboard[dev_event->detail / 8] |= 1<<(dev_event->detail % 8); + break; + case XI_KeyRelease: + m_state.keyboard[dev_event->detail / 8] &= ~(1<<(dev_event->detail % 8)); + break; + case XI_RawMotion: + // always safe because there is always at least one byte in + // raw_event->valuators.mask, and if a bit is set in the mask, + // then the value in raw_values is also available. + if (XIMaskIsSet(raw_event->valuators.mask, 0)) + { + delta_delta = raw_event->raw_values[0]; + // test for inf and nan + if (delta_delta == delta_delta && 1+delta_delta != delta_delta) + delta_x += delta_delta; + } + if (XIMaskIsSet(raw_event->valuators.mask, 1)) + { + delta_delta = raw_event->raw_values[1]; + // test for inf and nan + if (delta_delta == delta_delta && 1+delta_delta != delta_delta) + delta_y += delta_delta; + } + break; + } + + XFreeEventData(m_display, &event.xcookie); + } + + // apply axis smoothing + m_state.axis.x *= MOUSE_AXIS_SMOOTHING; + m_state.axis.x += delta_x; + m_state.axis.x /= MOUSE_AXIS_SMOOTHING+1.0f; + m_state.axis.y *= MOUSE_AXIS_SMOOTHING; + m_state.axis.y += delta_y; + m_state.axis.y /= MOUSE_AXIS_SMOOTHING+1.0f; +} + +std::string KeyboardMouse::GetName() const +{ + // This is the name string we got from the X server for this master + // pointer/keyboard pair. + return name; +} + +std::string KeyboardMouse::GetSource() const +{ + return "XInput2"; +} + +int KeyboardMouse::GetId() const +{ + return -1; +} + +KeyboardMouse::Key::Key(Display* const display, KeyCode keycode, const char* keyboard) + : m_display(display), m_keyboard(keyboard), m_keycode(keycode) +{ + int i = 0; + KeySym keysym = 0; + do + { + keysym = XkbKeycodeToKeysym(m_display, keycode, i, 0); + i++; + } + while (keysym == NoSymbol && i < 8); + + // Convert to upper case for the keyname + if (keysym >= 97 && keysym <= 122) + keysym -= 32; + + // 0x0110ffff is the top of the unicode character range according + // to keysymdef.h although it is probably more than we need. + if (keysym == NoSymbol || keysym > 0x0110ffff || + XKeysymToString(keysym) == nullptr) + m_keyname = std::string(); + else + m_keyname = std::string(XKeysymToString(keysym)); +} + +ControlState KeyboardMouse::Key::GetState() const +{ + return (m_keyboard[m_keycode / 8] & (1 << (m_keycode % 8))) != 0; +} + +KeyboardMouse::Button::Button(unsigned int index, unsigned int* buttons) + : m_buttons(buttons), m_index(index) +{ + // this will be a problem if we remove the hardcoded five-button limit + name = std::string("Click ") + (char)('1' + m_index); +} + +ControlState KeyboardMouse::Button::GetState() const +{ + return ((*m_buttons & (1 << m_index)) != 0); +} + +KeyboardMouse::Cursor::Cursor(u8 index, bool positive, const float* cursor) + : m_cursor(cursor), m_index(index), m_positive(positive) +{ + name = std::string("Cursor ") + (char)('X' + m_index) + (m_positive ? '+' : '-'); +} + +ControlState KeyboardMouse::Cursor::GetState() const +{ + return std::max(0.0f, *m_cursor / (m_positive ? 1.0f : -1.0f)); +} + +KeyboardMouse::Axis::Axis(u8 index, bool positive, const float* axis) + : m_axis(axis), m_index(index), m_positive(positive) +{ + name = std::string("Axis ") + (char)('X' + m_index) + (m_positive ? '+' : '-'); +} + +ControlState KeyboardMouse::Axis::GetState() const +{ + return std::max(0.0f, *m_axis / (m_positive ? MOUSE_AXIS_SENSITIVITY : -MOUSE_AXIS_SENSITIVITY)); +} + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h new file mode 100644 index 0000000000..09a59706e3 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h @@ -0,0 +1,120 @@ +// Copyright 2013 Max Eliaser +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +// See XInput2.cpp for extensive documentation. + +#pragma once + +extern "C" { +#include <X11/keysym.h> +#include <X11/Xlib.h> +#include <X11/extensions/XInput2.h> +} + +#include "InputCommon/ControllerInterface/Device.h" + +namespace ciface +{ +namespace XInput2 +{ + +void Init(std::vector<Core::Device*>& devices, void* const hwnd); + +class KeyboardMouse : public Core::Device +{ + +private: + struct State + { + char keyboard[32]; + unsigned int buttons; + struct + { + float x, y; + } cursor, axis; + }; + + class Key : public Input + { + friend class KeyboardMouse; + public: + std::string GetName() const override { return m_keyname; } + Key(Display* display, KeyCode keycode, const char* keyboard); + ControlState GetState() const override; + + private: + std::string m_keyname; + Display* const m_display; + const char* const m_keyboard; + const KeyCode m_keycode; + }; + + class Button : public Input + { + public: + std::string GetName() const override { return name; } + Button(unsigned int index, unsigned int* buttons); + ControlState GetState() const override; + + private: + const unsigned int* m_buttons; + const unsigned int m_index; + std::string name; + }; + + class Cursor : public Input + { + public: + std::string GetName() const override { return name; } + bool IsDetectable() override { return false; } + Cursor(u8 index, bool positive, const float* cursor); + ControlState GetState() const override; + + private: + const float* m_cursor; + const u8 m_index; + const bool m_positive; + std::string name; + }; + + class Axis : public Input + { + public: + std::string GetName() const override { return name; } + bool IsDetectable() override { return false; } + Axis(u8 index, bool positive, const float* axis); + ControlState GetState() const override; + + private: + const float* m_axis; + const u8 m_index; + const bool m_positive; + std::string name; + }; + +private: + void SelectEventsForDevice(Window window, XIEventMask *mask, int deviceid); + void UpdateCursor(); + +public: + void UpdateInput() override; + + KeyboardMouse(Window window, int opcode, int pointer_deviceid, int keyboard_deviceid); + ~KeyboardMouse(); + + std::string GetName() const override; + std::string GetSource() const override; + int GetId() const override; + +private: + Window m_window; + Display* m_display; + State m_state; + int xi_opcode; + const int pointer_deviceid, keyboard_deviceid; + std::string name; +}; + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/Xlib/Xlib.cpp b/Source/Core/InputCommon/ControllerInterface/Xlib/Xlib.cpp new file mode 100644 index 0000000000..e198f1112d --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/Xlib/Xlib.cpp @@ -0,0 +1,159 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <cstring> +#include <X11/XKBlib.h> + +#include "InputCommon/ControllerInterface/Xlib/Xlib.h" + +namespace ciface +{ +namespace Xlib +{ + +void Init(std::vector<Core::Device*>& devices, void* const hwnd) +{ + devices.push_back(new KeyboardMouse((Window)hwnd)); +} + +KeyboardMouse::KeyboardMouse(Window window) : m_window(window) +{ + memset(&m_state, 0, sizeof(m_state)); + + m_display = XOpenDisplay(nullptr); + + int min_keycode, max_keycode; + XDisplayKeycodes(m_display, &min_keycode, &max_keycode); + + // Keyboard Keys + for (int i = min_keycode; i <= max_keycode; ++i) + { + Key *temp_key = new Key(m_display, i, m_state.keyboard); + if (temp_key->m_keyname.length()) + AddInput(temp_key); + else + delete temp_key; + } + + // Mouse Buttons + AddInput(new Button(Button1Mask, m_state.buttons)); + AddInput(new Button(Button2Mask, m_state.buttons)); + AddInput(new Button(Button3Mask, m_state.buttons)); + AddInput(new Button(Button4Mask, m_state.buttons)); + AddInput(new Button(Button5Mask, m_state.buttons)); + + // Mouse Cursor, X-/+ and Y-/+ + for (int i = 0; i != 4; ++i) + AddInput(new Cursor(!!(i & 2), !!(i & 1), (&m_state.cursor.x)[!!(i & 2)])); +} + +KeyboardMouse::~KeyboardMouse() +{ + XCloseDisplay(m_display); +} + +void KeyboardMouse::UpdateInput() +{ + XQueryKeymap(m_display, m_state.keyboard); + + int root_x, root_y, win_x, win_y; + Window root, child; + XQueryPointer(m_display, m_window, &root, &child, &root_x, &root_y, &win_x, &win_y, &m_state.buttons); + + // update mouse cursor + XWindowAttributes win_attribs; + XGetWindowAttributes(m_display, m_window, &win_attribs); + + // the mouse position as a range from -1 to 1 + m_state.cursor.x = (float)win_x / (float)win_attribs.width * 2 - 1; + m_state.cursor.y = (float)win_y / (float)win_attribs.height * 2 - 1; +} + +std::string KeyboardMouse::GetName() const +{ + return "Keyboard Mouse"; +} + +std::string KeyboardMouse::GetSource() const +{ + return "Xlib"; +} + +int KeyboardMouse::GetId() const +{ + return 0; +} + +KeyboardMouse::Key::Key(Display* const display, KeyCode keycode, const char* keyboard) + : m_display(display), m_keyboard(keyboard), m_keycode(keycode) +{ + int i = 0; + KeySym keysym = 0; + do + { + keysym = XkbKeycodeToKeysym(m_display, keycode, i, 0); + i++; + } + while (keysym == NoSymbol && i < 8); + + // Convert to upper case for the keyname + if (keysym >= 97 && keysym <= 122) + keysym -= 32; + + // 0x0110ffff is the top of the unicode character range according + // to keysymdef.h although it is probably more than we need. + if (keysym == NoSymbol || keysym > 0x0110ffff || + XKeysymToString(keysym) == nullptr) + m_keyname = std::string(); + else + m_keyname = std::string(XKeysymToString(keysym)); +} + +ControlState KeyboardMouse::Key::GetState() const +{ + return (m_keyboard[m_keycode / 8] & (1 << (m_keycode % 8))) != 0; +} + +ControlState KeyboardMouse::Button::GetState() const +{ + return ((m_buttons & m_index) != 0); +} + +ControlState KeyboardMouse::Cursor::GetState() const +{ + return std::max(0.0f, m_cursor / (m_positive ? 1.0f : -1.0f)); +} + +std::string KeyboardMouse::Key::GetName() const +{ + return m_keyname; +} + +std::string KeyboardMouse::Cursor::GetName() const +{ + static char tmpstr[] = "Cursor .."; + tmpstr[7] = (char)('X' + m_index); + tmpstr[8] = (m_positive ? '+' : '-'); + return tmpstr; +} + +std::string KeyboardMouse::Button::GetName() const +{ + char button = '0'; + switch (m_index) + { + case Button1Mask: button = '1'; break; + case Button2Mask: button = '2'; break; + case Button3Mask: button = '3'; break; + case Button4Mask: button = '4'; break; + case Button5Mask: button = '5'; break; + } + + static char tmpstr[] = "Click ."; + tmpstr[6] = button; + return tmpstr; +} + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/Xlib/Xlib.h b/Source/Core/InputCommon/ControllerInterface/Xlib/Xlib.h new file mode 100644 index 0000000000..6fe72e4da7 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/Xlib/Xlib.h @@ -0,0 +1,93 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include <X11/keysym.h> +#include <X11/Xlib.h> + +#include "InputCommon/ControllerInterface/Device.h" + +namespace ciface +{ +namespace Xlib +{ + +void Init(std::vector<Core::Device*>& devices, void* const hwnd); + +class KeyboardMouse : public Core::Device +{ + +private: + struct State + { + char keyboard[32]; + unsigned int buttons; + struct + { + float x, y; + } cursor; + }; + + class Key : public Input + { + friend class KeyboardMouse; + public: + std::string GetName() const override; + Key(Display* display, KeyCode keycode, const char* keyboard); + ControlState GetState() const override; + + private: + std::string m_keyname; + Display* const m_display; + const char* const m_keyboard; + const KeyCode m_keycode; + }; + + class Button : public Input + { + public: + std::string GetName() const override; + Button(unsigned int index, unsigned int& buttons) + : m_buttons(buttons), m_index(index) {} + ControlState GetState() const override; + + private: + const unsigned int& m_buttons; + const unsigned int m_index; + }; + + class Cursor : public Input + { + public: + std::string GetName() const override; + bool IsDetectable() override { return false; } + Cursor(u8 index, bool positive, const float& cursor) + : m_cursor(cursor), m_index(index), m_positive(positive) {} + ControlState GetState() const override; + + private: + const float& m_cursor; + const u8 m_index; + const bool m_positive; + }; + +public: + void UpdateInput() override; + + KeyboardMouse(Window window); + ~KeyboardMouse(); + + std::string GetName() const override; + std::string GetSource() const override; + int GetId() const override; + +private: + Window m_window; + Display* m_display; + State m_state; +}; + +} +} |
